Что такое BatchNorm
Что проверяют
Знаете ли вы механику, а не только то, что «слой ускоряет обучение»: что считается на обучении, что на инференсе и откуда берутся баги при переключении режимов. Следом почти всегда спрашивают, почему в трансформерах используют LayerNorm, а не BatchNorm.
Как отвечать
BatchNorm (Ioffe и Szegedy, 2015) нормирует активации по мини-батчу отдельно для каждого канала:
- обучение: считаются среднее μ и дисперсия σ² по батчу (у свёрток ещё и по пространственным осям), затем
x̂ = (x − μ) / sqrt(σ² + ε)иy = γ·x̂ + β. Параметры γ и β обучаемые, так сеть может вернуть исходный масштаб, если нормировка мешает; - инференс: статистика батча не используется, берутся
running_meanиrunning_var, накопленные скользящим средним за обучение. В PyTorch режим переключаетmodel.eval(), в Keras аргументtraining; - зачем: можно брать больший learning rate, сеть меньше зависит от инициализации, шум статистик батча даёт лёгкую регуляризацию. Исходное объяснение про internal covariate shift позже оспорили: Santurkar и соавторы (2018) показали, что главное в сглаживании ландшафта функции потерь.
Детали: у свёртки перед BatchNorm ставят bias=False, его всё равно поглощает β. Momentum в фреймворках устроен наоборот: в PyTorch momentum=0.1 это вес нового значения, в Keras momentum=0.99 это вес накопленного.
Альтернативы: LayerNorm нормирует по признакам внутри одного примера и не зависит от батча, поэтому стандартна в трансформерах, в LLM часто RMSNorm. GroupNorm берут в детекции и сегментации, где в батче две-четыре картинки.
Где спотыкаются
Забывают model.eval() на валидации. Слой нормирует по валидационному батчу и заодно переписывает накопленную статистику, а на батче из одного примера BatchNorm1d падает с ошибкой.
Путают формулу: делят на σ + ε вместо корня из σ² + ε.
Учат с крошечным батчем и не замораживают слои при дообучении. Статистика по двум примерам шумная, а новые данные переписывают накопленные средние. В Keras trainable = False у слоя BatchNorm заодно переводит его в режим инференса, в PyTorch нужно отдельно вызвать eval() у этих слоёв.