Практика обучения: батчи, dropout, batchnorm
Формулу градиентного спуска знают все, но сети «не учатся» по десятку прозаичных причин: не тот learning rate, забытый warmup, нулевая инициализация. Эта тема — про инженерную кухню обучения и про то, как ставить диагноз по кривым loss.
Интуиция
Архитектура сети — это рецепт, а обучение — сама выпечка: важны температура (learning rate), время (эпохи) и порядок действий. Приборная панель пекаря — две кривые обучения (learning curves): ошибка на обучающей выборке (train loss) и на отложенной (validation loss). Почти любую проблему видно по их форме — этим мы и займёмся в интерактиве.
Мини-батчи и эпохи
Считать градиент по всему датасету дорого, по одному примеру — слишком шумно. Компромисс — мини-батч (mini-batch): случайная пачка из 32–1024 примеров, по которой оценивается градиент. Полный проход по всем данным называется эпохой (epoch).
Размер батча — это компромисс. Маленький батч даёт шумную оценку градиента: обучение «дрожит», зато шум работает как лёгкая регуляризация и помогает выбираться из плохих мест ландшафта. Большой батч даёт точный градиент и полностью загружает GPU, но ест память, а слишком большой — часто ухудшает обобщение, потому что сходится в «острые» минимумы. На практике берут максимум, который влезает в память, и при удвоении батча пропорционально поднимают learning rate.
Расписание learning rate
Постоянный learning rate — почти всегда не оптимум. Типичное расписание (learning rate schedule):
- Прогрев (warmup) — первые сотни/тысячи шагов lr линейно растёт от нуля. Свежеинициализированная сеть хрупкая, а у Adam ещё не накоплены статистики моментов — большой шаг в начале легко её «разносит».
- Косинусное затухание (cosine decay) — lr плавно спадает по косинусу до ~нуля; стандарт де-факто для обучения LLM.
- Ступенчатое (step decay) — делим lr в 10 раз на фиксированных эпохах; классика из эпохи ResNet.
Dropout: учим ансамбль в одной сети
Dropout — на каждом шаге обучения случайно «выключаем» каждый нейрон слоя с вероятностью p (обычно 0.1–0.5). Сеть не может положиться на конкретный нейрон или на сговор пары нейронов (co-adaptation) — признаки становятся избыточными и устойчивыми. Красивая интерпретация: мы обучаем экспоненциально большой ансамбль подсетей с общими весами.
Критично помнить про режимы: на инференсе dropout выключен — работают все нейроны. Чтобы масштаб активаций совпадал, при обучении оставшиеся активации делят на 1 − p (inverted dropout). В коде это переключатели model.train() / model.eval().
Нормализация активаций
По мере обучения распределения активаций внутри сети «плывут», и каждому слою приходится подстраиваться под меняющийся вход. Нормализация приводит активации к стабильному масштабу (среднее 0, дисперсия 1, плюс обучаемые сдвиг и масштаб) — градиенты становятся здоровее, можно брать больший learning rate, обучение ускоряется.
- Batch normalization — нормируем каждый признак по статистикам текущего батча. Работает отлично в CNN, но зависит от размера батча и требует хранить бегущие статистики для инференса (ещё одно различие train/eval).
- Layer normalization — нормируем по признакам одного примера, батч вообще не участвует. Поэтому она дружит с последовательностями переменной длины и стала стандартом трансформеров.
Инициализация и защита от взрывов
Инициализировать веса нулями — катастрофа: все нейроны слоя получают одинаковые градиенты и навсегда остаются копиями друг друга (симметрия не разрушается) — сеть вырождается в один нейрон. Слишком большие случайные веса — активации насыщаются или взрываются. Правильный масштаб задают схемы Xavier (Glorot, для tanh/sigmoid: дисперсия ≈ 1/n) и He (для ReLU: дисперсия ≈ 2/n), где n — число входов нейрона.
Последний страховочный трос — обрезка градиентов (gradient clipping): если норма градиента превышает порог, градиент масштабируют вниз. Один аномальный батч не сможет «выстрелить» веса в космос; стандартная практика для RNN и LLM.
Диагностика по кривым
- Loss не падает вовсе — lr слишком мал, баг в данных/лоссе или мёртвая инициализация.
- Loss скачет или улетает в NaN — lr велик; уменьшить, добавить warmup и clipping.
- Train падает, val растёт — переобучение; dropout, аугментации, больше данных, ранняя остановка (early stopping).
- Обе кривые высоко и близко — недообучение; больше модель, дольше учить, больше lr.
model.eval() — классический баг: метрики на проде «пляшут», хотя веса те же. И наоборот: включив eval во время обучения, вы молча отключите dropout.- «Почему нельзя инициализировать веса нулями?» — симметрия: одинаковые нейроны получают одинаковые градиенты и никогда не различатся.
- «Что делает dropout на инференсе?» — ничего: он выключен, а масштаб компенсирован делением на 1 − p при обучении.
- «Чем layer norm лучше batch norm для трансформеров?» — не зависит от размера батча и длины последовательности, нет бегущих статистик и различий train/eval.
- «Loss стал NaN на 50-м шаге — действия?» — уменьшить lr, включить warmup и gradient clipping, проверить данные на выбросы/нули в логарифмах.