05 / 33 · Основы
Батч-нормализация, градиенты и свёртки
Механизмы обучения, на которых держится каждая нейросетевая генеративная модель курса, — батч-нормализация, проблема затухающих и взрывающихся градиентов, а также размерности и параметры свёрточного слоя.
Нейронные сети · Обратное распространение ошибки · Градиентный спуск
На этой странице 7
В предыдущей заметке мы подгоняли модель с несколькими параметрами. Начиная с этой заметки, модели в курсе — нейронные сети. Автоэнкодеры следующей заметки собраны из блоков свёртка → батч-нормализация → активация, а каждая следующая модель обучается обратным распространением градиентов через множество таких слоёв. Метрики качества, которые появятся в курсе позже, тоже пропускают изображения через предобученные свёрточные сети. В этой заметке собраны необходимые сведения: что вычисляет батч-нормализация (batch normalization) и чем от неё отличается нормализация по экземпляру, почему градиенты могут затухать или взрываться, как устроен свёрточный слой и из чего складывается типичный запуск обучения.
Батч-нормализация#
Батч-нормализация (Ioffe and Szegedy, 2015) стандартизирует активации слоя по статистикам текущего мини-батча. Возьмём один признак (в свёрточной сети — один канал) и мини-батч из его значений . Сначала слой вычисляет среднее по батчу (batch mean) и дисперсию по батчу (batch variance)
а затем стандартизирует каждое значение:
Малая константа (в PyTorch ) обеспечивает устойчивость деления, когда разброс в батче почти нулевой. После этого шага у батча среднее и дисперсия .
Батч-нормализация одного признака
Небольшой разобранный пример. Для батча при :
| первое | второе | третье | четвёртое | |
|---|---|---|---|---|
Здесь и , поэтому каждое центрированное значение делится на . Дисперсия делится на , а не на : это обычная дисперсия батча.
Остальная часть слоя#
У полного слоя батч-нормализации два обучаемых параметра на признак — масштаб и сдвиг , — и его выход равен
Параметры и — фиксированные числа, а и меняются от батча к батчу, поэтому тождественное преобразование нельзя воспроизвести для каждого батча по отдельности. Зато его можно получить, если ориентироваться на статистики признака по генеральной совокупности: при и слой воспроизводит свой вход на инференсе, где вместо и используются скользящие средние, оценивающие эти статистики (о них ниже). При обучении он делает это лишь приближённо, с точностью до колебаний и от батча к батчу (Ioffe and Szegedy, 2015, раздел 3). Поэтому стандартизация не ограничивает то, что слой может выразить. Она меняет параметризацию так, что масштаб каждого признака задаётся одним параметром.
Обучение и инференс устроены по-разному. При обучении слой использует статистики батча и, кроме того, ведёт их скользящие средние. В PyTorch, где коэффициент обновления (momentum) по умолчанию равен , это и ; скользящая дисперсия хранит несмещённую оценку. На инференсе, когда «батч» может состоять из одного изображения, вместо статистик батча используются скользящие средние, и выход для одного примера больше не зависит от остальных примеров в батче. В PyTorch переключение выполняет model.eval(). Отключение градиентов через torch.no_grad() не переводит батч-нормализацию на скользящие статистики.
В свёрточной сети BatchNorm2d нормализует каждый канал по всем значениям этого канала в батче; всего таких значений размер батча × высота × ширина. Свёртку, за которой следует батч-нормализация, обычно создают с bias=False, потому что вычитание сокращает любое постоянное смещение, а его роль берёт на себя .
По похожей причине батч-нормализация не видит масштаба того, что стоит перед ней. Если свёртка без смещения с весами вычисляет значения канала по входу , то умножение или на множитель превращает каждое в , поскольку свёртка линейна. Тогда становится равным , — равной , и
С точностью до небольшой замены на выход не меняется (Ioffe and Szegedy, 2015, раздел 3.3). Поэтому свёртка без смещения, за которой следует батч-нормализация, нечувствительна к общему масштабу своего входа и своих весов.
Почему это помогает? В исходной статье батч-нормализацию обосновывали уменьшением «внутреннего ковариатного сдвига» (internal covariate shift) — дрейфа распределения входа каждого слоя в ходе обучения. Santurkar et al. (2018) в разделах 2–3 утверждают, что выигрыш возникает не за счёт уменьшения внутреннего ковариатного сдвига. Согласно их анализу, батч-нормализация делает функцию потерь и её градиенты более гладкими, что позволяет брать больший шаг обучения (learning rate); почему батч-нормализация работает, до сих пор обсуждается. Так или иначе, она держит под контролем масштаб активаций, а это именно тема следующего раздела.
Нормализация по экземпляру#
Батч-нормализация объединяет значения каждого канала по всему батчу, поэтому выход для одного изображения зависит от других изображений в нём. Нормализация по экземпляру (instance normalization; Ulyanov, Vedaldi and Lempitsky, 2016, формула 3) стандартизирует каждое изображение отдельно. Для изображения и канала карты признаков размера
с обучаемыми масштабом и сдвигом для каждого канала, как в батч-нормализации (InstanceNorm2d в PyTorch включает их только при affine=True). Эти слои различаются лишь тем, какие значения имеют общие среднее и дисперсию:
| Слой | По каким значениям вычисляются статистики | Одно среднее и одна дисперсия на |
|---|---|---|
| батч-нормализация | все изображений и все позиций | канал |
| нормализация по экземпляру | позиций одного изображения | изображение и канал |
| нормализация слоя, изображение (CNN) | все каналы и позиции одного изображения | изображение |
| нормализация слоя, трансформер | признаков одного токена | токен |
Нормализация слоя нормирует по тем последним осям, которые ей заданы (normalized_shape в PyTorch). Строка для CNN — конфигурация, объединяющая всё изображение; в трансформере тот же слой нормирует вектор признаков каждого токена отдельно.
Если четыре значения из разобранного выше примера — это карта одного канала одного изображения, нормализация по экземпляру (при , ) переводит их в , что бы ни содержали остальные изображения батча. Отсюда три следствия:
- Нет зависимости от батча. Обучение и инференс вычисляют одну и ту же функцию, скользящие средние не нужны, и батч из одного изображения работает.
- Контраст каждого изображения устраняется. Среднее и разброс каждой карты признаков каждого изображения отбрасываются; и одинаковы для всех изображений и не могут их восстановить. Именно это нужно для быстрого переноса стиля: стилизованное изображение не должно зависеть от контраста изображения содержания (content image) (Ulyanov et al., раздел 2). Там, где общая яркость или контраст изображения несут информацию, слой её выбрасывает.
- Нужна пространственная протяжённость. На карте у каждого канала одно значение, его дисперсия равна нулю, и выход равен при любом входе.
Три применения нормализации по экземпляру:
- Перенос стиля. Замена батч-нормализации нормализацией по экземпляру в сети стилизации прямого распространения (feed-forward), с сохранением этой нормализации и на тесте, заметно улучшила стилизованные изображения (Ulyanov et al., раздел 3).
- CycleGAN. Её используют и генераторы, и дискриминаторы, а обучение идёт батчами из одного изображения (Zhu et al., 2017, раздел 4 и приложение 7.2; см. сети CycleGAN).
- Критик WGAN-GP. Штраф на градиент (gradient penalty) ограничивает градиент выхода критика по каждому входу отдельно, а это имеет смысл, только если каждый выход зависит лишь от своего входа. Батч-нормализация это нарушает, поэтому WGAN-GP не использует её в критике. Допустима любая нормализация без взаимодействия между примерами; Gulrajani et al. (2017), раздел 4, рекомендуют нормализацию слоя (layer normalization), и нормализация по экземпляру тоже подходит.
Затухающие и взрывающиеся градиенты#
Мы обучаем модель, вычисляя градиент функции потерь по каждому весу (обратное распространение ошибки) и делая шаг против него. По цепному правилу градиент, который доходит до одного из первых слоёв, — это произведение множителей, по одному на каждый слой или временной шаг, через который он прошёл. Если в каждом слое один нейрон, с предактивацией (pre-activation) и функцией активации , то каждый множитель равен
то есть весу, умноженному на наклон функции активации. В слоях с многими нейронами множители — матрицы Якоби, и важна их величина: произведение многих якобианов обычно растёт или убывает с глубиной геометрически, со скоростью, которую задают их наибольшие сингулярные числа (Pascanu, Mikolov and Bengio, 2013, раздел 2.1; Goodfellow, Bengio and Courville, 2016, разделы 8.2.5 и 10.7). Поэтому если множители слоёв в основном
- по величине больше , произведение растёт геометрически: взрывающиеся градиенты (exploding gradients);
- по величине меньше , произведение убывает геометрически: затухающие градиенты (vanishing gradients).
Чем дальше множители от , тем быстрее это происходит, но отклонение не обязано быть большим (см. таблицу ниже). Впервые проблему проанализировали для рекуррентных сетей (Hochreiter, 1991; Bengio, Simard and Frasconi, 1994), где её проще всего увидеть.
Пример: RNN#
Рекуррентная сеть применяет один и тот же вес на каждом временном шаге. Возьмём один рекуррентный нейрон с ReLU, , и рекуррентный вес .
Развёрнутая RNN с рекуррентным весом w = 2
Каждый шаг по рекуррентному пути умножает на одно и то же , поэтому чувствительность более позднего состояния к более раннему равна через один шаг, через два, через три и так далее. Формально: пусть функция потерь вычисляется только по последнему состоянию , через шагов после (как в развёрнутой RNN выше, где используется только последний выход). Пока ReLU находится в активной области (), каждый множитель шага равен , и цепное правило даёт
Градиент, переданный назад на шагов, несёт множитель :
| вес | после 3 шагов | после 10 шагов | после 50 шагов |
|---|---|---|---|
Даже вес, который отличается от всего на 10%, за 50 шагов меняет градиент на два порядка. Ничего специфически рекуррентного в таблице нет: стопка из слоёв с одним и тем же множителем даёт тот же , и автоэнкодерам следующих заметок нужно только это прочтение по глубине. Рекуррентные нейроны и их гейты подробно разобраны в курсе NLP (рекуррентные сети, LSTM и GRU).
Многократное умножение на w
Что это делает с обучением#
Шаг градиентного спуска — это , поэтому размер шага пропорционален градиенту.
- Взрыв: градиент приходит умноженным на огромный множитель, и обновление делает очень большой шаг по ландшафту функции потерь. Каждое обновление перескакивает через минимум, и функция потерь скачет или расходится.
- Затухание: градиент, который доходит до первых слоёв глубокого стека, несёт по одному множителю на каждый слой. Если множители малы, произведение стремится к нулю и шаги становятся крошечными: веса первых слоёв почти не сдвигаются, и бюджет обучения заканчивается задолго до того, как достигнут минимум.
Что огромный или крошечный градиент делает с шагами обучения
Распространённые решения#
Остальная часть курса опирается на стандартные решения:
- Инициализация, которая держит масштаб сигналов около 1: инициализация Ксавье/Глоро (Xavier/Glorot initialization) для насыщающихся активаций (Glorot and Bengio, 2010) и инициализация Хе (He initialization) для ReLU (He et al., 2015); обе описаны в следующем разделе.
- Слои нормализации, например батч-нормализация, описанная выше.
- Ненасыщающиеся активации вместо сигмоид. Производная сигмоиды не превышает (это значение достигается в ), поэтому множитель сигмоидного слоя по величине не больше : каждый сигмоидный слой уменьшает градиент как минимум в 4 раза даже при . У ReLU, (Nair and Hinton, 2010), наклон равен ровно везде, где нейрон активен. LeakyReLU, с малым наклоном , например (Maas, Hannun and Ng, 2013), вдобавок сохраняет наклон вместо на отрицательных входах, так что неактивный нейрон всё равно пропускает часть градиента.
- Обрезка градиентов (gradient clipping) против взрыва: градиент перемасштабируют, когда его норма превышает порог (Pascanu, Mikolov and Bengio, 2013, раздел 3.2).
- Рекуррентные ячейки с гейтами (gated recurrent cells). В LSTM (Hochreiter and Schmidhuber, 1997) состояние ячейки обновляется аддитивно. В исходной ячейке его множитель на шаге равен ровно . Ячейка, которую используют сегодня, добавляет гейт забывания (forget gate; Gers, Schmidhuber and Cummins, 2000), и множитель равен значению этого гейта: числу из , которое сеть может держать около . GRU (Cho et al., 2014) работает так же: она интерполирует между старым состоянием и кандидатом, поэтому прямой путь несёт множитель её гейта обновления (update gate).
- Остаточные связи (residual connections; He et al., 2016a), . При чисто тождественном обходе (identity skip), как в ResNet с предактивацией (He et al., 2016b, формула (5)), якобиан блока равен , поэтому у градиента всегда есть путь с множителем ровно .
Свёрточный слой#
Как параметризуется и обучается двумерный свёрточный слой (nn.Conv2d) (Goodfellow, Bengio and Courville, 2016, глава 9; Dumoulin and Visin, 2016):
-
Ядра обучаются. Все элементы тензора весов и смещение обучаются градиентным спуском:
-
Обозначения. — входной канал с номером , плоскость размера ( с нижними индексами — ширины; , , — всегда веса). — ядро размера , которое выходной канал применяет к входному каналу . Собранный по всем тензор — это фильтр (filter) выходного канала .
-
Выходной канал , где , суммирует результаты по каналам и добавляет одно скалярное смещение:
Каждый — плоскость размера . На каждый выходной канал приходится одно смещение, поэтому в векторе смещений элементов.
Размерности в свёрточном слое: один выходной канал
Если расписать операцию поэлементно для шага (stride) и расширения (dilation) , получится скользящее скалярное произведение. Библиотеки глубокого обучения вычисляют взаимную корреляцию (cross-correlation) и не переворачивают ядро, в отличие от свёртки в обработке сигналов; поскольку ядро обучается, эта разница не важна.
где , , а обозначает вход после дополнения нулями (zero-padding) на с каждой стороны, с индексацией от . Формула предполагает одну группу, так что каждый выходной канал видит каждый входной; групповые свёртки (grouped convolutions) ограничивают эти связи.
-
Гиперпараметры, которые не обучаются: шаг, дополнение (padding), размер ядра и расширение фиксируются при создании слоя. Они определяют размер выхода (документация PyTorch):
для высоты, где — высота с дополнением минус протяжённость расширенного ядра. Для формула та же, только с и . В автоэнкодере следующей заметки у каждого из четырёх блоков энкодера , поэтому делит чётные размеры пополам: .
-
Инициализация. Ядра инициализируются случайно. При и инициализация Ксавье/Глоро выбирает веса с дисперсией (Glorot and Bengio, 2010), а инициализация Хе — с дисперсией для слоёв, за которыми следует ReLU (He et al., 2015, раздел 2.2). Обе построены ради цели предыдущего раздела: сохранять масштаб сигналов и градиентов постоянным от слоя к слою. По умолчанию PyTorch для
Conv2dиспользует более простое правило по числу входов (fan-in) — равномерное распределение в пределах (реализовано какkaiming_uniform_с ). Равномерное распределение в пределах имеет дисперсию , поэтому у этого варианта по умолчанию дисперсия — в шесть раз меньше дисперсии Хе. Без нормализации дисперсия сигнала в глубоком стеке ReLU-слоёв с такой инициализацией уменьшается примерно в 6 раз на каждом слое. В сетях следующих заметок масштаб восстанавливает батч-нормализация после свёрток. -
Чему учатся веса. Ядра первых слоёв обычно становятся маленькими детекторами границ, цветовых пятен и простых текстур. Более глубокие слои комбинируют их в части объектов и объекты на более крупных рецептивных полях (Zeiler and Fergus, 2014, раздел 4 и рисунок 2). Поэтому предобученные свёрточные сети полезны как экстракторы признаков для метрик качества курса.
Для ориентира: у слоя с , и ядрами есть весов плюс смещения.
Запуск обучения на практике#
Вокруг модели и её функции потерь каждый запуск обучения в курсе устроен одинаково.
-
Оптимизатор. Большинство моделей используют Adam (Kingma and Ba, 2015), который делит шаг по каждой координате на скользящую оценку величины её градиента (см. заметку о подгонке распределений). Распространённый вариант — AdamW (Loshchilov and Hutter, 2019, раздел 2). L2-штраф , добавленный к функции потерь, входит в Adam через градиент и делится на тот же масштаб по каждой координате, поэтому веса с исторически большими градиентами регуляризуются слабее, чем задумано. AdamW вместо этого уменьшает веса напрямую, вне адаптивного шага:
где и — скользящие оценки Adam для среднего градиента и квадрата его величины. Для обычного SGD, где заменяется самим градиентом, обе формы дают одно и то же обновление; для Adam они различаются.
AdamWв PyTorch по умолчанию использует и . -
Валидация. После каждой эпохи функция потерь вычисляется также на отложенных (held-out) данных, с моделью в режиме оценки (
model.eval(), который переключает батч-нормализацию на скользящие средние и отключает дропаут (dropout)) и без отслеживания градиентов (torch.no_grad(), который экономит память и время и больше ничего не меняет). Если построить обе функции потерь по эпохам, переобучение видно по валидационной кривой, которая выходит на плато или растёт, пока кривая обучения продолжает падать. -
Контрольные точки (checkpoints). Обученные параметры (
state_dictмодели) сохраняются на диск, чтобы модель можно было позже загрузить и оценить, не обучая её заново. -
Воспроизводимость. Каждому участвующему генератору случайных чисел задаётся начальное значение (seed):
randomв Python, NumPy и PyTorch, чейtorch.manual_seedохватывает CPU и все GPU. На GPU остаются ещё два источника различий. cuDNN может замерить время нескольких алгоритмов свёртки и оставить самый быстрый, а он может отличаться от запуска к запуску; кроме того, некоторые алгоритмы недетерминированы.torch.backends.cudnn.benchmark = Falseиtorch.backends.cudnn.deterministic = Trueотключают и то и другое ценой некоторой потери скорости. Даже тогда результаты воспроизводимы только на том же оборудовании и с теми же версиями программ (документация PyTorch, раздел «Reproducibility»).
Ноутбук#
Эти элементы используются в нескольких ноутбуках репозитория HSE-LAMBDA Deep Generative Models (закреплённый коммит; ячейки нумеруются с 1, считая все ячейки, включая текстовые).
- Ноутбук об автоэнкодерах, ячейка 11, определяет
Block:Conv2d(..., bias=False)→BatchNorm2d→LeakyReLU(0.2), ядро 3, дополнение(kernel - 1) // 2и шаг 2 в энкодере. Четыре блока переводят изображение MNIST размера в , а последняя свёртка с шагом 1 сохраняет этот размер, так что латентный код получается 512-мерным. Ячейки 17 и 22 явно переключаются междуae.train()иae.eval(), поэтому батч-нормализация использует статистики батча при обучении и скользящие средние при реконструкции тестовых изображений. - Ноутбук о VAE, ячейки 10 и 12, располагает блоки в порядке свёртка → LeakyReLU → BatchNorm. Его код генерации в ячейках 18 и 23 оборачивает сэмплирование в
torch.no_grad(), не вызываяeval(), поэтому батч-нормализация по-прежнему нормализует по статистикам сгенерированного батча и продолжает обновлять свои буферы скользящих средних. Это и есть описанное выше различие между обучением и инференсом на практике. В ноутбуке о VAE нет сохранённых выходов, поэтому он документирует только процедуру. - Ноутбук о WGAN, ячейка 12, нормализует вторую–четвёртую свёртки критика с помощью
InstanceNorm2d(..., affine=True)(на картах , и для изображений ), а ячейка 23 делает то же в блоках своего критика со спектральной нормализацией. Генераторы сохраняют батч-нормализацию. - Домашнее задание 1 следует распорядку из последнего раздела:
set_seedв ячейке 3 задаёт все начальные значения и оба флага cuDNN, ячейки 23–31 обучают автоэнкодер сAdamWпри настройках по умолчанию 30 эпох и строят графики MSE на обучении и валидации по эпохам, причём валидационный проход выполняется в режимеeval()подtorch.no_grad(), а функция обучения должна загружать сохранённые веса, если они есть. В ноутбуке нет сохранённых выходов.
Дальше#
С этими элементами можно построить первую нейросетевую модель курса. Следующая заметка собирает блоки свёртка → батч-нормализация → LeakyReLU в энкодер и декодер и обучает их реконструировать цифры MNIST через 512-мерный код. Она ставит вопросы о том, что улавливает такой код и можно ли генерировать из него новые изображения.
Литература#
- S. Ioffe, C. Szegedy. Batch normalization: Accelerating deep network training by reducing internal covariate shift. ICML, 2015.
- S. Santurkar, D. Tsipras, A. Ilyas, A. Mądry. How does batch normalization help optimization? NeurIPS, 2018.
- D. Ulyanov, A. Vedaldi, V. Lempitsky. Instance normalization: the missing ingredient for fast stylization. arXiv:1607.08022, 2016. Раздел 2 и формула (3): нормализация по экземпляру и довод о контрасте; раздел 3: её влияние на сети стилизации.
- J. L. Ba, J. R. Kiros, G. E. Hinton. Layer normalization. arXiv:1607.06450, 2016.
- J.-Y. Zhu, T. Park, P. Isola, A. A. Efros. Unpaired image-to-image translation using cycle-consistent adversarial networks. ICCV, 2017. Раздел 4 и приложение 7.2: нормализация по экземпляру в генераторах и дискриминаторах, размер батча 1.
- I. Gulrajani, F. Ahmed, M. Arjovsky, V. Dumoulin, A. Courville. Improved training of Wasserstein GANs. NeurIPS, 2017. Раздел 4, «No critic batch normalization».
- D. P. Kingma, J. Ba. Adam: A method for stochastic optimization. ICLR, 2015.
- I. Loshchilov, F. Hutter. Decoupled weight decay regularization. ICLR, 2019. Раздел 2: для Adam L2-регуляризация и затухание весов (weight decay) различаются; AdamW.
- S. Hochreiter. Untersuchungen zu dynamischen neuronalen Netzen. Diploma thesis, TU München, 1991.
- Y. Bengio, P. Simard, P. Frasconi. Learning long-term dependencies with gradient descent is difficult. IEEE Transactions on Neural Networks, 5(2), 1994.
- R. Pascanu, T. Mikolov, Y. Bengio. On the difficulty of training recurrent neural networks. ICML, 2013. Раздел 2.1 (условия на наибольшее сингулярное число) и раздел 3.2 (обрезка градиента по норме).
- S. Hochreiter, J. Schmidhuber. Long short-term memory. Neural Computation, 9(8), 1997.
- F. A. Gers, J. Schmidhuber, F. Cummins. Learning to forget: Continual prediction with LSTM. Neural Computation, 12(10), 2000.
- K. Cho, B. van Merriënboer, C. Gulcehre, D. Bahdanau, F. Bougares, H. Schwenk, Y. Bengio. Learning phrase representations using RNN encoder–decoder for statistical machine translation. EMNLP, 2014.
- V. Nair, G. E. Hinton. Rectified linear units improve restricted Boltzmann machines. ICML, 2010.
- A. L. Maas, A. Y. Hannun, A. Y. Ng. Rectifier nonlinearities improve neural network acoustic models. ICML Workshop on Deep Learning for Audio, Speech and Language Processing, 2013.
- X. Glorot, Y. Bengio. Understanding the difficulty of training deep feedforward neural networks. AISTATS, 2010.
- K. He, X. Zhang, S. Ren, J. Sun. Delving deep into rectifiers: Surpassing human-level performance on ImageNet classification. ICCV, 2015.
- K. He, X. Zhang, S. Ren, J. Sun. Deep residual learning for image recognition. CVPR, 2016 (в тексте — 2016a).
- K. He, X. Zhang, S. Ren, J. Sun. Identity mappings in deep residual networks. ECCV, 2016 (в тексте — 2016b).
- M. D. Zeiler, R. Fergus. Visualizing and understanding convolutional networks. ECCV, 2014.
- V. Dumoulin, F. Visin. A guide to convolution arithmetic for deep learning. arXiv:1603.07285, 2016.
- I. Goodfellow, Y. Bengio, A. Courville. Deep Learning. MIT Press, 2016. Разделы 8.2.5 и 10.7 (долгосрочные зависимости) и глава 9 (свёрточные сети).
- Документация PyTorch:
BatchNorm2d,InstanceNorm2d,Conv2d,AdamWи Reproducibility.