Блок трансформера: attention, feed-forward, residual, нормализация

Трансформер собран из повторяющихся блоков. Разберём один такой блок по кирпичикам — это шаблон, который стопкой даёт всю модель.

Блок трансформера — повторяемый модуль из слоя внимания и полносвязной сети, обёрнутых остаточными связями и нормализацией.

Четыре составные части

Один блок содержит:

  1. Multi-head attention — токены обмениваются информацией (это мы уже разобрали).
  2. Feed-forward сеть (FFN) — небольшая полносвязная сеть, применяемая к каждому токену отдельно.
  3. Остаточные связи (residual) — выход каждого подслоя прибавляется к его входу.
  4. Нормализация слоя (LayerNorm) — приводит активации к стабильному масштабу.

Feed-forward: «подумать» над каждым токеном

После того как внимание собрало контекст, к каждому токену по отдельности применяется одна и та же маленькая сеть из двух линейных слоёв с нелинейностью между ними. Если внимание отвечает за «обмен информацией между токенами», то FFN — за «обработку» собранной информации внутри токена. Внутренний слой обычно в 4 раза шире — там у модели «пространство для размышлений». Любопытно, что именно в FFN-слоях, как считают, хранится значительная часть фактических знаний модели.

Остаточные связи и нормализация

Два приёма делают глубокие стопки блоков обучаемыми. Residual: вместо того чтобы заменять вход, подслой добавляет к нему поправку (x + f(x)) — так исходный сигнал свободно проходит сквозь десятки слоёв, а градиенты не затухают. LayerNorm: нормализует вектор к нулевому среднему и единичному разбросу, чтобы числа не «разлетались». Посмотрим оба приёма в действии.

import math

# Остаточная связь (residual): выход слоя ПРИБАВЛЯЕТСЯ ко входу,
# а не заменяет его. Это сохраняет исходную информацию и помогает обучению.
x = [1.0, 2.0, 3.0]          # вход в подслой
sublayer_out = [0.1, -0.2, 0.05]  # что насчитал подслой (например, attention)

residual = [a + b for a, b in zip(x, sublayer_out)]
print("вход x:            ", x)
print("выход подслоя:     ", sublayer_out)
print("после residual x+f:", [round(v, 2) for v in residual])

# Нормализация слоя (LayerNorm): приводим вектор к нулевому среднему и единичной дисперсии.
def layer_norm(vec, eps=1e-5):
    mean = sum(vec) / len(vec)
    var = sum((v - mean) ** 2 for v in vec) / len(vec)
    return [(v - mean) / math.sqrt(var + eps) for v in vec]

normed = layer_norm(residual)
print("после LayerNorm:   ", [round(v, 2) for v in normed])
print()
print("Среднее после нормализации ~", round(sum(normed) / len(normed), 5))
print("Residual бережёт сигнал сквозь десятки слоёв, LayerNorm держит числа в узде.")

Вывод:

вход x:             [1.0, 2.0, 3.0]
выход подслоя:      [0.1, -0.2, 0.05]
после residual x+f: [1.1, 1.8, 3.05]
после LayerNorm:    [-1.1, -0.23, 1.32]

Среднее после нормализации ~ -0.0
Residual бережёт сигнал сквозь десятки слоёв, LayerNorm держит числа в узде.

Видно: residual лишь чуть скорректировал вход (а не стёр его), а LayerNorm привёл числа к среднему около нуля и аккуратному масштабу. Без этих двух приёмов обучать сети из десятков слоёв было бы почти невозможно.

Как это собрано в блоке

Концептуально один блок выглядит так (детали порядка нормализации у разных моделей отличаются):

x = x + attention(layernorm(x))     # подслой внимания + residual
x = x + feed_forward(layernorm(x))  # подслой FFN + residual

Каждая строка — это «подслой + нормализация + остаточная связь». Два таких подслоя и образуют один блок трансформера.

Итог

  • Блок = multi-head attention + feed-forward сеть, оба обёрнуты residual и LayerNorm.
  • Внимание обменивает информацию между токенами, FFN обрабатывает её внутри каждого токена.
  • Residual (x + f(x)) сохраняет сигнал и градиенты сквозь глубокую стопку слоёв.
  • LayerNorm держит активации в стабильном масштабе, делая обучение возможным.
Проверьте себя
1. За что отвечает feed-forward сеть в блоке трансформера?
AЗа обмен информацией между токенами
BЗа обработку собранной информации внутри каждого токена по отдельности
CЗа токенизацию
DЗа позиционные кодировки
2. Зачем нужны остаточные связи (residual)?
AЧтобы уменьшить словарь
BЧтобы сохранить сигнал и градиенты сквозь десятки слоёв (выход прибавляется ко входу)
CЧтобы ускорить токенизацию
DОни не нужны
3. Что делает LayerNorm?
AПереводит текст
BПриводит вектор активаций к стабильному масштабу (нулевое среднее, единичный разброс)
CСжимает модель вдвое
DУдаляет редкие токены
Поддержать проект