Глава 5. Трасса 66: Идея Residual Connections
Если слой плохо умеет учить сложную функцию H(x),
может быть, ему проще учить разницу H(x) − x?
Глава 4 закончилась странным вопросом: почему сеть, у которой больше слоёв, не может хотя бы повторить успех мелкой?
Если лишние слои умеют стать прозрачными, глубокая модель не должна проигрывать на train.
Значит, прозрачность для оптимизатора слишком дорогая.
Нужно сделать её дешёвой — прямо в архитектуре.
5.1. Тождественное преобразование как продукт, а не чудо
Запишем желание формально.
Хотим, чтобы блок умел реализовать:
y = x
В обычном стеке блок считает что-то вроде:
y = F(x) # F — несколько свёрток + нелинейности
Чтобы получить identity, нужно F(x) ≈ x.
Для случайно инициализированных свёрток это не стартовая точка и не особенно удобная мишень.
Теперь сменим параметризацию:
y = F(x) + x
Тогда identity получается при F(x) ≈ 0.
Обнулить добавку часто проще, чем воспроизвести весь вход через нелинейный путь.
Слой больше не обязан «рисовать представление заново».
Он обязан уметь дорисовать разницу — residual.
Отсюда и имя.
5.2. Shortcut: автобан для градиента
Сложение с x — это не только удобная параметризация вперёд.
Это дорога назад.
При обратном проходе градиент по shortcut-ветке проходит почти без цепочки локальных производных тяжёлого F.
Да, детали зависят от конкретной реализации и соседних нормализаций, но качественная картина такая:
∂loss/∂x получает вклад
• через F (как раньше — может затухать)
• напрямую через +x (короткий путь)
Появляется трасса, по которой сигнал ошибки может проехать через десятки блоков, не умножаясь на пятьдесят «почти нулей» подряд.
flowchart LR
L[Loss] --> H[Поздние слои]
H --> S[Shortcut path]
H --> F[Residual path F]
S --> E[Ранние слои]
F --> E
Инженерный смысл:
Мы не запретили глубокий нелинейный путь.
Мы добавили параллельный простой путь,
который гарантирует, что глубина не обязана быть тюрьмой для градиента.
Поэтому глубокие ResNet-подобные модели наконец смогли выигрывать у мелких не только «в теории выразительности», но и на практике оптимизации.
5.3. Что именно изменилось в постановке обучения
Полезно явно проговорить смену контракта.
| Раньше | После residual |
|---|---|
| Каждый блок строит новое представление | Блок может лишь поправить старое |
| Identity — трудная мишень | Identity — режим по умолчанию при F≈0 |
| Глубина легко вредит оптимизации | Глубина становится ресурсом, который можно не использовать |
| «Больше слоёв» = ставка ва-банк | «Больше слоёв» = больше опциональных поправок |
Это снова обмен, а не магия:
| Получили | Чем заплатили |
|---|---|
| Обучаемость очень глубоких сетей | Чуть более сложный граф (+ skip, согласование размерностей) |
| Лучший train/val на большой глубине | Нужны 1×1 conv / stride-трюки, когда меняется размер/каналы |
| Простую ментальную модель «учи разницу» | Риск думать, что skip всегда бесплатен по памяти/bandwidth |
Когда размер тензора меняется (меньше H×W или другое число каналов), чистый y = F(x) + x уже не складывается.
Тогда делают проекционный shortcut: линейное/свёрточное выравнивание x, чтобы формы совпали. Идея та же — сохранить короткий путь.
5.4. Ансамблевая интуиция
Есть красивая интерпретация (не единственная и не догма):
глубокий residual-стек ведёт себя похоже на ансамбль более коротких путей.
Потому что раскрывая скобки по всем возможным маршрутам «через F / мимо F», получаешь множество путей разной эффективной глубины.
3 блока, у каждого ветка F и skip:
пути длины 0..3 существуют одновременно
сеть может опираться на короткие, если длинные вредят
Это помогает понять, почему residual так устойчив: оптимизатору не обязательно использовать всю глубину целиком. Он может опереться на короткие маршруты и постепенно оживлять длинные.
Не обязательно верить интерпретации буквально, чтобы пользоваться блоком.
Но как рабочая картинка для архитектора она полезна: skip создаёт разнообразие эффективных глубин внутри одной модели.
5.5. Откуда тогда возьмутся CSP и DenseNet?
Если вы честно дочитали до конца главы 4 и этой, у вас уже могут родиться следующие гипотезы:
- Если полезно добавлять
xк F(x) — может, полезно конкатенировать все промежуточные карты (плотные связи)? - Если градиенты дублируются и признаки повторяются — может, не весь тензор надо гнать через тяжёлый F?
- Если shortcut хорош — может, сделать много коротких связей разных масштабов?
Исторически из соседних ветвей этого дерева вырастут DenseNet, CSPNet и дальше C3/C2f в YOLO-семействе.
Мы не будем забегать: сначала добьём стабилизацию распределений (следующая глава), потом в детекции увидим, как идея «не делай лишнюю работу» превращается в конкретные блоки скорости.
Пока зафиксируем победу ResNet-логики:
| Проблема | Лишние слои не умеют быть прозрачными |
| Решение | y = x + F(x) — прозрачность по умолчанию |
| Эффект | Градиент и признак имеют короткий путь |
| Обмен | Простота стека → граф с развилками |
Имя «ResNet» можно произносить только сейчас — после того, как идея уже выведена из боли главы 4.
Если бы вы были автором статьи…
-
Почему
+, а не concat?
Сложение сохраняет размер и даёт identity через ноль. Concat сохраняет «сырые» признаки, но раздувает каналы. Когда вы выберете второе? -
Что будет, если поставить skip через всю сеть одним прыжком, а не через каждый блок?
Какой эксперимент покажет, где короткий hop важнее длинного? -
Сформулируйте абляцию: ResNet без residual (тот же F, но без
+ x).
Какой график вы ожидаете на train error для 18 vs 34 слоёв?
Прежде чем перейти к нормализации, стоит отдельно разобрать, как сделать каждый residual-блок дешевле: идея bottleneck (глава 5a) — сузить каналы перед тяжёлым 3×3.
А сразу после неё — второй стабилизатор эпохи: договорённость между слоями о масштабе активаций (глава 6).