ResNet и skip connections: прорыв в глубоком обучении
🎯 Зачем это нужно?
Представь, что ты строишь башню из LEGO 🏗️. Чем выше башня, тем сложнее добавлять новые блоки наверх — руки не дотягиваются, конструкция шатается. Похожая проблема была и с нейросетями: чем глубже сеть, тем хуже она училась.
До 2015 года самые крутые нейросети имели 20-30 слоёв. А потом появился ResNet со 152 слоями и взорвал все рейтинги! Сегодня эта архитектура используется в:
💻 Компьютерном зрении: Распознавание лиц в iPhone, фильтры Instagram, автопилоты Tesla 📱 Мобильных приложах: Google Photos группирует фото по лицам, Snapchat накладывает маски 🔬 Медицине: Диагностика рака по рентгену точнее человека-радиолога
📚 История вопроса
2015 год. Конкурс ImageNet — Олимпийские игры компьютерного зрения. Команда Microsoft Research во главе с Кэйминг Хэ (Kaiming He) представляет архитектуру, которая звучит парадоксально:
“А что если мы сделаем сеть настолько глубокой, что она просто научится НЕ делать ничего лишнего?” 🤔
Результат: ResNet-152 с ошибкой 3.57% — лучше человека (5-10%)! И это с сетью в 152 слоя, когда все думали, что больше 30 слоёв — это безумие.
💡 Интуиция
Проблема градиентного затухания
Представь игру “Испорченный телефон” с 150 людьми 📞. Каждый человек — это слой нейросети. Когда сообщение доходит до конца, оно уже совсем не похоже на оригинал. В нейросетях та же проблема: градиенты “затухают” по мере распространения назад.
[МЕДИА: image_01] Описание: Схема затухания градиента в глубокой сети vs сохранения в ResNet Промпт: “educational illustration showing gradient vanishing problem in deep networks, fading signal through layers vs ResNet skip connections preserving information, technical diagram style, blue and red colors”
Гениальная идея skip connections
ResNet добавляет “обходные пути” — skip connections (residual connections). Это как если бы в игре “испорченный телефон” каждый 3й человек мог напрямую передать сообщение через несколько людей вперёд.
Формула остаточного блока:
H(x) = F(x) + x
Где:
H(x)— выход блокаF(x)— то, что изучают слои внутри блокаx— вход блока (проходит “напрямую”)
[МЕДИА: image_02] Описание: Схема residual блока с skip connection Промпт: “clean technical diagram of ResNet residual block, showing input x, transformation F(x), skip connection, and addition H(x) = F(x) + x, modern educational style, arrows and mathematical notation”
📐 Формальное определение
Residual Block — базовый строительный блок ResNet, состоящий из:
- Основной путь: Последовательность слоёв (обычно conv → BatchNorm → ReLU → conv → BatchNorm)
- Skip connection: Прямое соединение входа со выходом блока
- Остаточная функция: F(x) = H(x) - x, где сеть изучает “остаток” вместо полного преобразования
Математика skip connection
Вместо изучения отображения H(x), сеть изучает остаточное отображение:
F(x) = H(x) - x
Тогда: H(x) = F(x) + x
Почему это работает?
- Если оптимальное преобразование близко к тождественному (H(x) ≈ x), то F(x) ≈ 0
- Выучить нулевое отображение проще, чем тождественное!
- Градиенты текут напрямую: ∂H/∂x = ∂F/∂x + 1 (всегда есть компонента “1”!)
🔍 Примеры с разбором
Пример 1: Базовый residual блок
import torch
import torch.nn as nn
class ResidualBlock(nn.Module):
def __init__(self, channels):
super().__init__()
self.conv1 = nn.Conv2d(channels, channels, 3, padding=1)
self.bn1 = nn.BatchNorm2d(channels)
self.conv2 = nn.Conv2d(channels, channels, 3, padding=1)
self.bn2 = nn.BatchNorm2d(channels)
self.relu = nn.ReLU()
def forward(self, x):
# Основной путь
residual = self.conv1(x)
residual = self.bn1(residual)
residual = self.relu(residual)
residual = self.conv2(residual)
residual = self.bn2(residual)
# Skip connection: складываем вход с результатом
output = residual + x # Вот она, магия!
output = self.relu(output)
return output
Ключевой момент: output = residual + x — это и есть skip connection!
Пример 2: Bottleneck блок (ResNet-50/101/152)
Для очень глубоких сетей используют bottleneck design:
class BottleneckBlock(nn.Module):
def __init__(self, in_channels, out_channels, stride=1):
super().__init__()
# 1x1 conv уменьшает размерность (bottleneck)
self.conv1 = nn.Conv2d(in_channels, out_channels//4, 1)
self.bn1 = nn.BatchNorm2d(out_channels//4)
# 3x3 conv делает основную работу
self.conv2 = nn.Conv2d(out_channels//4, out_channels//4, 3,
stride=stride, padding=1)
self.bn2 = nn.BatchNorm2d(out_channels//4)
# 1x1 conv восстанавливает размерность
self.conv3 = nn.Conv2d(out_channels//4, out_channels, 1)
self.bn3 = nn.BatchNorm2d(out_channels)
self.relu = nn.ReLU()
# Если размеры не совпадают, нужна проекция
self.shortcut = nn.Sequential()
if in_channels != out_channels or stride != 1:
self.shortcut = nn.Sequential(
nn.Conv2d(in_channels, out_channels, 1, stride=stride),
nn.BatchNorm2d(out_channels)
)
def forward(self, x):
residual = self.conv1(x)
residual = self.bn1(residual)
residual = self.relu(residual)
residual = self.conv2(residual)
residual = self.bn2(residual)
residual = self.relu(residual)
residual = self.conv3(residual)
residual = self.bn3(residual)
# Skip connection с возможной проекцией
shortcut = self.shortcut(x)
output = residual + shortcut
output = self.relu(output)
return output
Bottleneck идея: 256→64→64→256 каналов. Меньше параметров, та же выразительность!
🎮 Практика
Базовый уровень 🟢
Задание 1: Объясни, почему градиент в ResNet не затухает
💡 Подсказка
Посмотри на производную H(x) = F(x) + x по x✅ Ответ
∂H/∂x = ∂F/∂x + 1. Даже если ∂F/∂x → 0, остается слагаемое "1", которое обеспечивает прохождение градиентаЗадание 2: Реализуй простейший residual блок для одномерного случая
def residual_block_1d(x, weight1, bias1, weight2, bias2):
# Твой код здесь
pass
✅ Ответ
```python def residual_block_1d(x, weight1, bias1, weight2, bias2): residual = torch.relu(x @ weight1 + bias1) residual = residual @ weight2 + bias2 return torch.relu(residual + x) # Skip connection! ```Задание 3: Сколько параметров в bottleneck блоке 256→64→64→256?
💡 Подсказка
Считай параметры каждого слоя: conv1x1, conv3x3, conv1x1✅ Ответ
256×64 + 64×64×9 + 64×256 = 16384 + 36864 + 16384 = 69632 параметра (без BatchNorm)Продвинутый уровень 🟡
Задание 4: Сравни количество параметров в bottleneck (256→64→64→256) и обычном блоке (256→256→256)
✅ Ответ
Bottleneck: ~70K параметров Обычный: 256×256×9 + 256×256×9 = 1.18M параметров Экономия в 17 раз!Задание 5: Реализуй ResNet-18 архитектуру
class ResNet18(nn.Module):
def __init__(self, num_classes=1000):
# Твоя архитектура
pass
Задание 6: Объясни, зачем нужна проекция в shortcut connection
✅ Ответ
Когда размеры входа и выхода блока не совпадают (изменение каналов или пространственного разрешения), нельзя просто сложить тензоры. Нужна проекция - обычно 1×1 конволюцияЧеллендж 🔴
Задание 7: Докажи теоретически, почему ResNet может быть не хуже более мелкой сети
✅ Ответ
Если оптимальное отображение для дополнительных слоёв - тождественное, то F(x) = 0. Дополнительные слои научатся выдавать ноль, и глубокая сеть будет эквивалентна мелкойЗадание 8: Реализуй ResNeXt блок (группированные конволюции)
class ResNeXtBlock(nn.Module):
def __init__(self, in_channels, out_channels, groups=32):
# Твоя реализация с группированными conv
pass
⚠️ Частые ошибки
❌ Ошибка: Применять ReLU до сложения в skip connection
# Неправильно!
residual = self.relu(self.conv2(residual))
output = residual + x
✅ Правильно: ReLU после сложения
output = residual + x
output = self.relu(output)
💡 Почему: ReLU до сложения может “обрезать” отрицательные значения, мешая обучению
❌ Ошибка: Забывать про проекцию при изменении размерности ✅ Правильно: Использовать 1×1 conv для согласования размеров 💡 Почему: Нельзя сложить тензоры разных размеров
❌ Ошибка: Думать, что skip connection - это просто “ярлык для ленивых” ✅ Правильно: Это фундаментальное изменение архитектуры 💡 Почему: Skip connections изменяют саму природу обучения - от изучения H(x) к изучению остатка F(x)
🎓 Главное запомнить
✅ ResNet = остаточное обучение: H(x) = F(x) + x ✅ Skip connections решают проблему затухающих градиентов ✅ Используется везде: от распознавания лиц до медицинской диагностики ✅ Ключевая формула: ∂H/∂x = ∂F/∂x + 1 (градиент всегда проходит)
🔗 Связь с другими темами
Назад: Изучив свёрточные нейросети, мы понимаем, как работают слои ResNet Вперёд: ResNet стал основой для Transformer (skip connections там тоже!), EfficientNet, Vision Transformer Параллельно: Batch Normalization, который также помогает обучать глубокие сети
ResNet не просто архитектура - это философия: позволить сети самой решать, что изучать на каждом слое. Иногда лучшее решение - ничего не делать! 🎯
Понял тему? Закрепи в боте! 🚀
Попрактикуйся на задачах и получи персональные рекомендации от AI
💪 Начать тренировку