← Назад к вопросам

Почему в рекуррентных сетях и трансформерах используют не Batch Normalization, а Layer Normalization?

2.0 Middle🔥 141 комментариев
#Глубокое обучение

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI29 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Почему в RNN и трансформерах используют Layer Normalization вместо Batch Normalization

Это один из ключевых различий между архитектурами. Причины фундаментальные и связаны с особенностями работы этих сетей.

Что такое Batch Normalization (BN)

# Нормализуем по батчу (по другим примерам в батче)
# Input: [batch_size, features]
mean_batch = mean(X, axis=0)  # Средняя по всем примерам
std_batch = std(X, axis=0)     # Стандартное отклонение по примерам

X_normalized = (X - mean_batch) / std_batch

# Пример батча:
# X = [[1.0, 2.0],
#      [2.0, 3.0],
#      [3.0, 4.0]]
# Нормализуем для каждого признака (столбца) отдельно

Что такое Layer Normalization (LN)

# Нормализуем по признакам (по всем признакам одного примера)
# Input: [batch_size, sequence_length, features]  # или [batch_size, features]
mean_layer = mean(X, axis=-1, keepdims=True)  # Средняя по признакам
std_layer = std(X, axis=-1, keepdims=True)     # Стандартное отклонение

X_normalized = (X - mean_layer) / std_layer

# Пример:
# X = [[1.0, 2.0, 3.0],
#      [2.0, 3.0, 4.0]]
# Нормализуем для каждого примера отдельно (по 3 признакам)

Проблема Batch Normalization в RNN

1. Размер батча меняется

# Проблема: Статистика батча нестабильна

# Обучение: batch_size=32
# Статистика: mean, std вычисляются по 32 примерам

# Inference: batch_size=1 (обработка одного примера)
# Статистика: mean и std почти случайны для одного примера!
# Сильный разрыв между train и inference

# RNN часто работает с переменной длиной последовательностей
# batch_size меняется часто

2. Temporal Consistency

В RNN одна последовательность обрабатывается шаг за шагом:

# Временной шаг 1: hidden_state_1 = RNN(x_1, hidden_0)
# Временной шаг 2: hidden_state_2 = RNN(x_2, hidden_1)  ← Зависит от предыдущего
# Временной шаг 3: hidden_state_3 = RNN(x_3, hidden_2)  ← Зависит от предыдущего

# Если применяем BN, статистика меняется между шагами!
# На шаге 1 батч = [x_1_1, x_1_2, ..., x_1_32]  (первые элементы 32 последовательностей)
# На шаге 2 батч = [x_2_1, x_2_2, ..., x_2_32]  (вторые элементы 32 последовательностей)
# Совершенно разные последовательности!

3. Зависимость от батча

# BN зависит от других примеров в батче
# Это создает сложные зависимости

# Пример: есть два батча с одним и тем же примером
# Батч 1: [A, B, C, D]  → нормализуем A с учетом B, C, D
# Батч 2: [A, X, Y, Z]  → нормализуем A с учетом X, Y, Z
# Один и тот же пример A нормализуется по-разному!
# Нестабильно для RNN

Почему Layer Normalization работает лучше

1. Независимость от батча

# LN нормализует каждый пример независимо
# Для примера A:
ln_A = (A - mean(A)) / std(A)

# Неважно, какие другие примеры в батче
# A всегда нормализуется одинаково
# Стабильно как в train, так и в inference

2. Работает с переменным batch size

from tensorflow.keras import layers

# Layer Normalization
model = layers.LSTM(128)
model.add(layers.LayerNormalization())

# Неважно, какой batch_size:
# batch_size=32: работает
# batch_size=1: работает
# batch_size=512: работает
# Результат одинаков (с точностью до масштабирования)

3. Правильно для последовательностей

В RNN каждый временной шаг имеет свой hidden state:

# LSTM на временном шаге t
hidden_t = LSTM(input_t, hidden_{t-1})
hidden_t = LayerNorm(hidden_t)  # Нормализуем по признакам

# Эта нормализация стабильна для всего процесса
# Каждый шаг нормализуется независимо

# vs Batch Normalization:
hidden_t = LSTM(input_t, hidden_{t-1})
hidden_t = BatchNorm(hidden_t)  # Зависит от других примеров в батче
# Если на шаге t батч содержит разные временные шаги
# → Нестабильно!

Математическое сравнение

Вход для обработки:
X: [batch_size=3, sequence_length=4, features=5]

Batch Normalization:
- Норма по оси batch и sequence: axis=(0,1)
- Статистика: mean[5], std[5]
- Зависимость: от 3*4=12 примеров
- Проблема: разные последовательности смешиваются

Layer Normalization:
- Норма по оси features: axis=-1
- Статистика: mean[1], std[1] для каждой позиции
- Зависимость: только от текущего примера
- Преимущество: независимость, стабильность

Практический пример

import tensorflow as tf
from tensorflow.keras import layers, models

# RNN с Layer Normalization (правильно)
model_ln = models.Sequential([
    layers.LSTM(64, return_sequences=True),
    layers.LayerNormalization(),
    layers.LSTM(32),
    layers.LayerNormalization(),
    layers.Dense(10, activation='softmax')
])

# RNN с Batch Normalization (плохо)
model_bn = models.Sequential([
    layers.LSTM(64, return_sequences=True),
    layers.BatchNormalization(),  # ⚠️ Может быть нестабильно
    layers.LSTM(32),
    layers.BatchNormalization(),
    layers.Dense(10, activation='softmax')
])

# Обучение
model_ln.compile(optimizer='adam', loss='categorical_crossentropy')
model_ln.fit(X_train, y_train, epochs=10, batch_size=32)

# При inference с batch_size=1:
y_pred = model_ln.predict(X_test)  # Работает стабильно
# Нормализация одинакова как при train, так и при inference

Трансформеры и Layer Normalization

# Архитектура Transformer блока
class TransformerBlock(layers.Layer):
    def __init__(self, embed_dim, num_heads, ff_dim):
        super().__init__()
        self.att = layers.MultiHeadAttention(num_heads=num_heads, key_dim=embed_dim)
        self.ffn = models.Sequential([
            layers.Dense(ff_dim, activation='relu'),
            layers.Dense(embed_dim)
        ])
        self.layernorm1 = layers.LayerNormalization(epsilon=1e-6)
        self.layernorm2 = layers.LayerNormalization(epsilon=1e-6)
        self.dropout1 = layers.Dropout(0.1)
        self.dropout2 = layers.Dropout(0.1)
    
    def call(self, inputs):
        # Self-attention с Layer Norm
        attn_output = self.att(inputs, inputs)
        attn_output = self.dropout1(attn_output)
        out1 = self.layernorm1(inputs + attn_output)  # Residual + LN
        
        # Feed-forward с Layer Norm
        ffn_output = self.ffn(out1)
        ffn_output = self.dropout2(ffn_output)
        out2 = self.layernorm2(out1 + ffn_output)  # Residual + LN
        
        return out2

Важно: Post-LN (LN после residual) более стабилен в трансформерах:

out = LayerNormalization(x + attention(x))  # Post-LN ✓ (Stable)
out = x + LayerNormalization(attention(x))  # Pre-LN (Older approach)

Когда всё же использовать Batch Normalization

# CNN (Convolutional Neural Networks) — BN отлично работает
model = models.Sequential([
    layers.Conv2D(32, 3),
    layers.BatchNormalization(),  # ✓ Хорошо
    layers.Activation('relu'),
    layers.Conv2D(64, 3),
    layers.BatchNormalization(),  # ✓ Хорошо
])

# Полносвязные сети (если batch_size большой и стабилен)
model = models.Sequential([
    layers.Dense(256),
    layers.BatchNormalization(),  # ✓ Может быть хорошо
])

Сравнительная таблица

ПараметрBatch NormLayer Norm
Зависит от батчаДаНет
Работает при batch_size=1ПлохоОтлично
Для RNN/LSTMНестабильноОтлично
Для ТрансформеровРедкоСтандарт
Для CNNОтличноХорошо
Обучается быстроДаДа
Требует больших батчейДаНет

Итог

Layer Normalization выбирают для RNN/Трансформеров потому что:

  1. Независимость — не зависит от других примеров в батче
  2. Стабильность — одинаковое поведение при train и inference
  3. Гибкость — работает с любым batch_size
  4. Правильная норма — нормализует по признакам, а не по примерам
  5. Особенности последовательностей — учитывает природу RNN/Трансформеров

Batch Normalization остается лучшим выбором для CNN благодаря встроенной регуляризации и ускорению обучения.

Почему в рекуррентных сетях и трансформерах используют не Batch Normalization, а Layer Normalization? | PrepBro