Расскажите про идею ResNet, напишите ResidualBlock
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
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
- Всегда используй Batch Normalization перед активацией
- Skip connection для сокращения информации (нет потерь)
- 1x1 свёртки в bottleneck для эффективности
- Pre-trained ResNet для transfer learning
- Data augmentation при обучении на малых данных