Часть 1: ResNet-18 — Архитектура, покорившая глубину

от автора

Пролог: Парадокс глубины

Представьте, что вы строите небоскрёб. Каждый новый этаж — это слой нейросети. Но после 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+ слоях.

Как сеть учится? Обучаемые параметры

Что настраивается:

  1. Ядра свёрток: Числовые значения в фильтрах (например, 3×3 матрицы)

  2. Параметры BatchNorm: γ (масштаб) и β (сдвиг) для каждого канала

  3. Веса линейных слоёв: Матрицы в финальных слоях

  4. Смещения (bias): Добавочные константы в слоях

Процесс обучения:

  1. Прямой проход: Изображение преобразуется в предсказание (как в примере выше).

  2. Расчёт ошибки: Сравнение предсказания с истинной меткой через функцию потерь (например, кросс-энтропия).

  3. Обратное распространение:

    • Вычисляются градиенты (производные ошибки по каждому параметру)

    • Градиенты «протекают» обратно по сети через остаточные связи

  4. Оптимизация: Параметры корректируются в сторону, уменьшающую ошибку:

    параметр = параметр - learning_rate * градиент 

Почему ResNet всё ещё актуален?

  1. Скорость: Работает в несколько раз быстрее ViT на небольших данных.

  2. Количество данных для обучения: Нужно в разы меньше данных, чем для обучения трансформеров.

  3. Масштабируемость: От 18 до 152 слоёв без коллапса.

Философский итог: Простая идея (остаточные связи) оказалась мощнее сложной математики. Иногда гениальное решение лежит на поверхности!

В следующей части: Как ViT (Vision Transformer) научился «понимать» изображения без свёрток — и почему это не всегда лучше ResNet.

P.S. Если хотите глубже разобрать BatchNorm или увидеть обучение ResNet с нуля — пишите в комментариях!


ссылка на оригинал статьи https://habr.com/ru/articles/921608/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *