Ответ
Batch Normalization (BN) плохо подходит для последовательностей, в отличие от Layer Normalization (LN), и вот ключевые технические причины, с которыми я сталкивался на практике:
-
Зависимость от статистик батча в RNN: В RNN мы разворачиваем сеть по времени. BN нормировал бы активации, используя среднее и дисперсию по батчу и по всем временным шагам. Это проблема:
- При инференсе (предсказании для одного примера) статистики батча не определены, используются скользящие средние, рассчитанные на обучении. Для последовательностей переменной длины это ненадежно.
- Статистики меняются от шага к шагу, внося нестабильность в обучение.
-
Несовместимость с переменной длиной последовательностей: В одном батче часто пакуют последовательности разной длины (дополняя нулями). BN, вычисляя статистики по всем элементам батча, будет смешивать реальные активации и паддинг-нули, что искажает нормализацию.
Layer Normalization решает эти проблемы:
- Нормирование по измерениям признаков (features): LN вычисляет среднее и дисперсию для каждого примера в батче и для каждого временного шага независимо по всем нейронам (признакам) в слое. Формула для вектора активаций
hдлиныL:LN(h) = γ * (h - μ) / √(σ² + ε) + β, гдеμ, σ²вычисляются поL. - Стабильность: LN не зависит от размера батча, что критично для RNN и идеально подходит для трансформеров, где self-attention уже работает независимо для каждого токена.
- Ускорение сходимости: Как и BN, LN стабилизирует распределение активаций, смягчая проблему исчезающих/взрывающихся градиентов, но делает это более предсказуемым образом для последовательностей.
Пример использования в PyTorch для временного ряда:
import torch.nn as nn
# Допустим, вход имеет shape (batch_size, seq_len, feature_dim)
# LN нормирует по последнему измерению (feature_dim)
layer_norm = nn.LayerNorm(normalized_shape=feature_dim)
# Для входного тензора размера (32, 10, 64) он вычислит 32*10=320 независимых средних и дисперсий по 64 признакам.
# В архитектуре трансформера (например, блок энкодера) LN применяется ДО саб-лейеров:
class TransformerEncoderLayer(nn.Module):
def __init__(self, d_model, nhead):
super().__init__()
self.norm1 = nn.LayerNorm(d_model)
self.self_attn = nn.MultiheadAttention(d_model, nhead)
self.norm2 = nn.LayerNorm(d_model)
self.ff = nn.Linear(d_model, d_model)
def forward(self, src):
# Pre-LN архитектура (сейчас более распространена)
src_norm = self.norm1(src)
src = src + self.self_attn(src_norm, src_norm, src_norm)[0]
src_norm = self.norm2(src)
src = src + self.ff(src_norm)
return src