Главная / Статьи / Остаточные связи: почему глубокие трансформеры вообще удаётся обучить

Остаточные связи: почему глубокие трансформеры вообще удаётся обучить

Почему простое наращивание слоёв ломает обучение, как skip-связи x + F(x) чинят поток градиентов, как их используют блоки с Pre-LayerNorm, и короткая реализация на PyTorch.

1031 слов

Если наивно сложить десятки слоёв друг на друга, обучение становится нестабильным. Остаточные связи, их ещё называют shortcut-связями, решают эту проблему: и информация, и градиенты получают возможность проходить сквозь сеть напрямую.

Почему простое наслоение не работает

К этому моменту блок трансформера состоит из трёх частей: многоголового самовнимания, нормализации слоя и feed-forward сети. Возникает соблазн просто повторять их одну за другой:

Input
   │
   ▼
Attention
   │
   ▼
Feed Forward
   │
   ▼
Attention
   │
   ▼
Feed Forward
   │
   ▼
...

Обучается такое плохо. Чем глубже сеть, тем сложнее её оптимизировать, и до появления остаточных связей в ResNet в 2015 году это было одним из главных препятствий в глубоком обучении. От них зависят почти все современные трансформеры, включая GPT, BERT, LLaMA, Claude и Gemini.

Начнём с сети из двух слоёв:

Input  ↓Layer 1  ↓Layer 2  ↓Output

Обучить её обычно несложно. А теперь представьте 96 слоёв:

Input↓Layer 1↓Layer 2↓...↓Layer 96↓Output

Каждый слой преобразует выход предыдущего. При обратном распространении градиент должен пройти назад через каждый слой, и по дороге он может сжаться почти до нуля (затухание), неограниченно вырасти (взрыв) или становиться всё шумнее. В итоге самые первые слои почти ничему не учатся.

Испорченный телефон

Представьте сообщение, которое шёпотом передают по цепочке из пятидесяти человек:

Person 1↓Person 2↓Person 3↓...↓Person 50

К концу оно сильно меняется: что-то пропало, что-то исказилось. Глубокая сеть ведёт себя похоже: каждый слой немного сдвигает представление, и после достаточного числа слоёв полезный сигнал может исчезнуть.

Добавим короткий путь

А что, если данным не обязательно проходить через каждый слой и они могут ещё и обходить их?

Input
  │
  ├──────────────┐
  ▼              │
Layer            │
  ▼              │
Output           │
  │              │
  └────── Add ◄──┘
        │
        ▼
Final Output

Теперь у исходного входа всегда есть прямой путь к последующим слоям, и это небольшое изменение сильно меняет поведение глубоких сетей.

Математика

Допустим, слой вычисляет вот это:

F(x)

Без остаточной связи выход — это просто результат слоя:

Output = F(x)

С ней выход становится таким:

Output = x + F(x)

Вход прибавляется прямо к преобразованному выходу. Эта операция называется skip-связью, или shortcut-связью.

Почему это помогает

Предположим, конкретному слою нечего добавить. В обычной сети ему пришлось бы как-то научиться работать тождественной функцией, а это, как выясняется, трудно. С остаточной связью тождество достаётся бесплатно. Слою достаточно выучить:

F(x) = 0

чтобы:

Output = x + 0 = x

Вход проходит без изменений, и оптимизация становится гораздо проще.

Учимся на разнице

Отсюда и название. Слою не нужно выучивать всё преобразование целиком. Он учит только разницу, тот самый остаток, между входом и целевым выходом. Хорошее сравнение — редактирование фото: картинку редко перерисовывают полностью, её корректируют, добавляя яркость, контраст или резкость. Слой трансформера тоже редко должен заменять всё представление. Ему достаточно немного его улучшить.

Два коротких пути на блок

В каждом блоке трансформера две остаточные связи. Первая охватывает слой внимания:

Input
  │
  ├──────────────┐
  ▼              │
Multi-Head Attention
  ▼              │
 Add ◄───────────┘
  │
  ▼
Output

Вторая охватывает feed-forward сеть:

Input
  │
  ├──────────────┐
  ▼              │
Feed Forward Network
  ▼              │
 Add ◄───────────┘
  │
  ▼
Output

Pre-LayerNorm

Современные модели GPT используют схему Pre-LayerNorm, которая вычисляется так:

Input
   │
   ▼
LayerNorm
   │
   ▼
Multi-Head Attention
   │
   ▼
Add Residual
   │
   ▼
LayerNorm
   │
   ▼
Feed Forward Network
   │
   ▼
Add Residual
   │
   ▼
Output

LayerNorm здесь применяется до каждого подслоя, а не после, и такая компоновка заметно стабилизирует обучение глубоких трансформеров.

Поток градиентов

При обратном распространении у градиента теперь два пути:

Gradient↓Transformer Layer↓Residual Path

Если основное преобразование оказывается трудным для оптимизации, короткий путь всё равно даёт другую дорогу назад, так что градиент не может исчезнуть полностью. На языке матанализа производная от

x + F(x)

равна

1 + F'(x)

Эта константа 1 очень важна. Даже если

F'(x)

становится очень маленьким, у градиента остаётся прямой путь через тождественную связь. Это одна из главных причин, по которым остаточные сети успешно обучаются.

Числовой пример

Пусть входной эмбеддинг такой:

[0.5, 1.2, -0.3]

а внимание выдаёт:

[0.1, -0.2, 0.4]

Вместо того чтобы заменить вход, их складывают:

[0.5, 1.2, -0.3] + [0.1, -0.2, 0.4] = [0.6, 1.0, 0.1]

Исходная информация сохраняется, а новая к ней подмешивается.

На PyTorch

В коде это почти ничего не стоит:

x = x + self.attention(
    self.layer_norm1(x)
)
x = x + self.ffn(
    self.layer_norm2(x)
)

Это сложение — одна из самых значимых операций во всей архитектуре.

Один полный блок

                Input
                  │
                  ▼
             LayerNorm
                  │
                  ▼
        Multi-Head Attention
                  │
                  ▼
           Add Residual
                  │
                  ▼
             LayerNorm
                  │
                  ▼
      Feed-Forward Network
                  │
                  ▼
           Add Residual
                  │
                  ▼
                Output

Современные LLM повторяют эту структуру десятки и даже сотни раз.

Что меняется с ними и без них

Без остаточных связей глубокие сети трудно оптимизировать, градиенты легче затухают, информация постепенно утекает, а дополнительные слои часто ухудшают результат. С ними информация проходит сквозь сеть напрямую, градиенты остаются стабильными, более глубокие модели становятся обучаемыми, а оптимизация сильно упрощается. Остаточное обучение — один из прорывов, благодаря которым стали возможны сегодняшние большие языковые модели.