← Назад к вопросам

Расскажите про идею ResNet, напишите ResidualBlock

1.0 Junior🔥 71 комментариев
#Глубокое обучение

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI30 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

ResNet: Остаточные соединения (Residual Networks)

ResNet (Residual Network) — революционная архитектура нейронных сетей, введённая Microsoft в 2015 году. Позволяет обучать очень глубокие сети (до 1000 слоёв), чего было невозможно раньше из-за проблемы затухания градиента.

Основная идея

Без skip connections:

Вход -> Conv -> ReLU -> Conv -> ReLU -> Выход

С skip connection (ResNet):

Вход -----> (+) -> Выход
       |    ^
       v    |
       Conv -> ReLU -> Conv

Вместо изучения функции F(x), сеть изучает остаток (residual) H(x) = F(x) - x. Затем окончательный результат: F(x) = H(x) + x.

Почему это работает:

  • Если нижние слои уже хорошо работают, верхний слой может просто выучить f(x) = 0 (skip connection передаст x без изменений)
  • Градиент может беспрепятственно распространяться через skip connections
  • Решается проблема затухания градиента в очень глубоких сетях

ResidualBlock реализация

Базовый вариант (для малых изображений, 32x32):

import tensorflow as tf
from tensorflow.keras import layers, Model

class ResidualBlock(layers.Layer):
    def __init__(self, filters, kernel_size=3, stride=1, activation='relu'):
        super(ResidualBlock, self).__init__()
        self.filters = filters
        self.stride = stride
        
        # Основной путь
        self.conv1 = layers.Conv2D(
            filters, kernel_size=kernel_size, 
            strides=stride, padding='same', use_bias=False
        )
        self.bn1 = layers.BatchNormalization()
        self.act1 = layers.Activation(activation)
        
        self.conv2 = layers.Conv2D(
            filters, kernel_size=kernel_size,
            strides=1, padding='same', use_bias=False
        )
        self.bn2 = layers.BatchNormalization()
        
        # Skip connection (1x1 conv если меняется размер/каналы)
        self.skip_conv = None
        self.skip_bn = None
        
        self.activation = layers.Activation(activation)
    
    def build(self, input_shape):
        # Если количество каналов или размер меняется, нужна адаптация
        if input_shape[-1] != self.filters or self.stride != 1:
            self.skip_conv = layers.Conv2D(
                self.filters, kernel_size=1,
                strides=self.stride, padding='same', use_bias=False
            )
            self.skip_bn = layers.BatchNormalization()
    
    def call(self, inputs):
        # Основной путь
        x = self.conv1(inputs)
        x = self.bn1(x)
        x = self.act1(x)
        
        x = self.conv2(x)
        x = self.bn2(x)
        
        # Skip connection
        if self.skip_conv is not None:
            skip = self.skip_conv(inputs)
            skip = self.skip_bn(skip)
        else:
            skip = inputs
        
        # Складываем
        x = x + skip
        x = self.activation(x)
        return x

# Использование
block = ResidualBlock(filters=64, stride=1)
output = block(inputs)  # inputs shape: (batch, height, width, 3)

Bottleneck вариант (для больших изображений, ImageNet):

class BottleneckBlock(layers.Layer):
    def __init__(self, filters, stride=1):
        super(BottleneckBlock, self).__init__()
        
        # 1x1 conv для уменьшения размерности
        self.conv1 = layers.Conv2D(filters // 4, 1, use_bias=False)
        self.bn1 = layers.BatchNormalization()
        
        # 3x3 conv (основной)
        self.conv2 = layers.Conv2D(
            filters // 4, 3, strides=stride, padding='same', use_bias=False
        )
        self.bn2 = layers.BatchNormalization()
        
        # 1x1 conv для восстановления размерности
        self.conv3 = layers.Conv2D(filters, 1, use_bias=False)
        self.bn3 = layers.BatchNormalization()
        
        # Skip connection
        self.skip_conv = None
        self.skip_bn = None
        if stride != 1:
            self.skip_conv = layers.Conv2D(filters, 1, strides=stride, use_bias=False)
            self.skip_bn = layers.BatchNormalization()
    
    def call(self, inputs):
        # 1x1 conv
        x = self.conv1(inputs)
        x = self.bn1(x)
        x = tf.nn.relu(x)
        
        # 3x3 conv
        x = self.conv2(x)
        x = self.bn2(x)
        x = tf.nn.relu(x)
        
        # 1x1 conv
        x = self.conv3(x)
        x = self.bn3(x)
        
        # Skip
        if self.skip_conv is not None:
            skip = self.skip_conv(inputs)
            skip = self.skip_bn(skip)
        else:
            skip = inputs
        
        x = x + skip
        x = tf.nn.relu(x)
        return x

Полный ResNet модель

class ResNet(Model):
    def __init__(self, num_classes=10):
        super(ResNet, self).__init__()
        
        # Initial convolution
        self.conv1 = layers.Conv2D(64, 7, strides=2, padding='same', use_bias=False)
        self.bn1 = layers.BatchNormalization()
        self.pool1 = layers.MaxPooling2D(3, strides=2, padding='same')
        
        # Residual blocks
        self.layer1 = self._make_layer(64, 3, stride=1)    # 64-dimensional
        self.layer2 = self._make_layer(128, 4, stride=2)   # 128-dimensional
        self.layer3 = self._make_layer(256, 6, stride=2)   # 256-dimensional
        self.layer4 = self._make_layer(512, 3, stride=2)   # 512-dimensional
        
        # Global average pooling and classification
        self.avgpool = layers.GlobalAveragePooling2D()
        self.fc = layers.Dense(num_classes, activation='softmax')
    
    def _make_layer(self, filters, blocks, stride):
        layers_list = []
        layers_list.append(BottleneckBlock(filters, stride=stride))
        for _ in range(1, blocks):
            layers_list.append(BottleneckBlock(filters, stride=1))
        return tf.keras.Sequential(layers_list)
    
    def call(self, inputs):
        x = self.conv1(inputs)
        x = self.bn1(x)
        x = tf.nn.relu(x)
        x = self.pool1(x)
        
        x = self.layer1(x)
        x = self.layer2(x)
        x = self.layer3(x)
        x = self.layer4(x)
        
        x = self.avgpool(x)
        x = self.fc(x)
        return x

# Создание и использование
model = ResNet(num_classes=1000)
model.build((None, 224, 224, 3))
model.compile(optimizer='adam', loss='categorical_crossentropy')

# Или используй pre-trained
from tensorflow.keras.applications import ResNet50
pretrained = ResNet50(weights='imagenet', include_top=True)

Преимущества ResNet

Позволяет обучать очень глубокие сети (50, 101, 152 слоя) ✅ Решает проблему затухания градиентаУлучшает convergence (быстрее обучается) ✅ SOTA результаты на ImageNet и других бенчмарках ✅ Стал стандартом в компьютерном зрении

Варианты архитектуры

  • ResNet-18: 18 слоёв, быстро
  • ResNet-34: 34 слоя
  • ResNet-50: 50 слоёв с bottleneck (более эффективно)
  • ResNet-101: 101 слой
  • ResNet-152: 152 слоя

Расширения

  • ResNeXt: групповые свёртки для лучше выразительности
  • Densely Connected Networks (DenseNet): плотные соединения
  • Squeeze-and-Excitation ResNet: attention механизм

Best Practices

  1. Всегда используй Batch Normalization перед активацией
  2. Skip connection для сокращения информации (нет потерь)
  3. 1x1 свёртки в bottleneck для эффективности
  4. Pre-trained ResNet для transfer learning
  5. Data augmentation при обучении на малых данных