Пролог: Парадокс глубины
Представьте, что вы строите небоскрёб. Каждый новый этаж — это слой нейросети. Но после 20 этажей здание вдруг начинает… рушиться. Так было в компьютерном зрении до 2015 года: чем глубже сеть, тем хуже она работала.
ResNet решил это гениально просто: добавил «лифты» между этажами — остаточные связи (skip-connections). Теперь, если новый слой бесполезен, сеть просто «пропускает» его через эти лифты.
Разберём на простом примере
Как ResNet из картинки делает предсказание?
Допустим у нас есть задача предсказать, что в данном изображении будет «человек» класс (0) или «машина» класс (1).
1. Входные данные: RGB-изображение
Ваша фотография — это 3 матрицы чисел (Red, Green, Blue). Например, крошечное изображение 4×4:
Канал R: Канал G: Канал B: [[2, 4, 1, 3] [[2, 4, 1, 3] [[2, 4, 1, 3] [0, 5, 8, 2] [0, 5, 8, 2] [0, 5, 8, 2] [4, 2, 7, 1] [4, 2, 7, 1] [4, 2, 7, 1] [3, 6, 0, 4]] [3, 6, 0, 4]] [3, 6, 0, 4]]
2. Свёртка: «Фильтр-детектор»
Сеть применяет к изображению фильтры (например, 3×3). Вот как это работает:
-
Фильтр «скользит» по картинке, умножая и суммируя числа.
-
Пример рассчета
================================================== Рассчитаем выход для позиции (0, 0): Канал 0: (2*1) + (4*-1) + (1*1) + (0*0) + (5*1) + (8*-1) + (4*-1) + (2*0) + (7*1) = 2 + -4 + 1 + 0 + 5 + -8 + -4 + 0 + 7 = -1 Канал 0 Сумма: -1 Канал 1: Такой же как и на предыдущем шаге. Канал 2:Такой же как и на предыдущем шаге. Результат для позиции (0, 0) (Сумма по всем каналам): -1 - 1 -1 = -3 ================================================== Рассчитаем выход для позиции (0, 1): Канал 0: (4*1) + (1*-1) + (3*1) + (5*0) + (8*1) + (2*-1) + (2*-1) + (7*0) + (1*1) = 4 + -1 + 3 + 0 + 8 + -2 + -2 + 0 + 1 = 11 Канал 0 Сумма: 11 Канал 1: Такой же как и на предыдущем шаге. Канал 2: Такой же как и на предыдущем шаге. Результат для позиции (0, 1) (Сумма по всем каналам): 11 + 11 + 11 = 33 ================================================== Рассчитаем выход для позиции (1, 0): Канал 0: (0*1) + (5*-1) + (8*1) + (4*0) + (2*1) + (7*-1) + (3*-1) + (6*0) + (0*1) = 0 + -5 + 8 + 0 + 2 + -7 + -3 + 0 + 0 = -5 Канал 0 Сумма: -5 Канал 1: Такой же как и на предыдущем шаге. Канал 2: Такой же как и на предыдущем шаге. Результат для позиции (1, 0) (Сумма по всем каналам): -5 -5 -5 = -15 ================================================== Рассчитаем выход для позиции (1, 1): Канал 0: (5*1) + (8*-1) + (2*1) + (2*0) + (7*1) + (1*-1) + (6*-1) + (0*0) + (4*1) = 5 + -8 + 2 + 0 + 7 + -1 + -6 + 0 + 4 = 3 Канал 0 Сумма: 3 Канал 1: Такой же как и на предыдущем шаге. Канал 2: Такой же как и на предыдущем шаге. Результат для позиции (1, 1) (Сумма по всем каналам): 3 + 3 + 3 = 9 ==================================================
-
Результат — новая матрица признаков:
[[ -3, 33] [-15, 9]]
🔍 Фишка ResNet: Если фильтр «испортил» картинку, оригинал можно вернуть через skip-connection!
3. Pooling: «Сжатие без потерь»
MaxPooling оставляет только самое яркое значение в области 2×2:
Исходная матрица: После MaxPooling: [[ -3, 33] [[33]] [-15, 9]]
(Как если бы вы сжали фото, оставив только ключевые детали)
4. Линейный слой: «Финальный вердикт»
-
Признаки преобразуются в вектор (например,
[0.6, -1.2, 3.8]). -
Умножается на веса классификатора:
# Для классов "человек" (0) и "машина" (1): Класс 0: 0.6*0.7 + (-1.2)*0.5 + 3.8*1.0 + 0.1 = 3.7 Класс 1: 0.6*(-1.2) + (-1.2)*0.3 + 3.8*(-0.4) - 0.2 = -2.8 -
Итог: Softmax(3.8, -1.59) → 99% вероятность «человек».
Ключевые компоненты ResNet в коде
1. Residual Block — «Лифт для градиентов»
class ResBlock(nn.Module): def forward(self, x): skip = x # Сохраняем оригинальный вход! h = self.conv1(x) # Первая свёртка h = self.bn1(h) # Нормализация h = self.relu(h) # Активация h = self.conv2(h) # Вторая свёртка h = self.bn2(h) # Нормализация return self.relu(skip + h) # Складываем с исходным значением
Почему работает: Сеть учит не «абсолютное преобразование», а разницу от исходного x.
2. BatchNorm — «Стабилизатор обучения»
# Нормализует данные: (x - mean)/stddev x = (x - torch.mean(x)) / torch.sqrt(torch.var(x) + 1e-5) x = gamma * x + beta # gamma/beta — обучаемые параметры
Эффект: Градиенты не «взрываются» и не «затухают» даже в 100+ слоях.
Как сеть учится? Обучаемые параметры
Что настраивается:
-
Ядра свёрток: Числовые значения в фильтрах (например, 3×3 матрицы)
-
Параметры BatchNorm: γ (масштаб) и β (сдвиг) для каждого канала
-
Веса линейных слоёв: Матрицы в финальных слоях
-
Смещения (bias): Добавочные константы в слоях
Процесс обучения:
-
Прямой проход: Изображение преобразуется в предсказание (как в примере выше).
-
Расчёт ошибки: Сравнение предсказания с истинной меткой через функцию потерь (например, кросс-энтропия).
-
Обратное распространение:
-
Вычисляются градиенты (производные ошибки по каждому параметру)
-
Градиенты «протекают» обратно по сети через остаточные связи
-
-
Оптимизация: Параметры корректируются в сторону, уменьшающую ошибку:
параметр = параметр - learning_rate * градиент
Почему ResNet всё ещё актуален?
-
Скорость: Работает в несколько раз быстрее ViT на небольших данных.
-
Количество данных для обучения: Нужно в разы меньше данных, чем для обучения трансформеров.
-
Масштабируемость: От 18 до 152 слоёв без коллапса.
Философский итог: Простая идея (остаточные связи) оказалась мощнее сложной математики. Иногда гениальное решение лежит на поверхности!
В следующей части: Как ViT (Vision Transformer) научился «понимать» изображения без свёрток — и почему это не всегда лучше ResNet.
P.S. Если хотите глубже разобрать BatchNorm или увидеть обучение ResNet с нуля — пишите в комментариях!
ссылка на оригинал статьи https://habr.com/ru/articles/921608/
Добавить комментарий