Содержание курса 05 / 33

05 / 33 · Основы

Батч-нормализация, градиенты и свёртки

Механизмы обучения, на которых держится каждая нейросетевая генеративная модель курса, — батч-нормализация, проблема затухающих и взрывающихся градиентов, а также размерности и параметры свёрточного слоя.

На этой странице 7

В предыдущей заметке мы подгоняли модель с несколькими параметрами. Начиная с этой заметки, модели в курсе — нейронные сети. Автоэнкодеры следующей заметки собраны из блоков свёртка → батч-нормализация → активация, а каждая следующая модель обучается обратным распространением градиентов через множество таких слоёв. Метрики качества, которые появятся в курсе позже, тоже пропускают изображения через предобученные свёрточные сети. В этой заметке собраны необходимые сведения: что вычисляет батч-нормализация (batch normalization) и чем от неё отличается нормализация по экземпляру, почему градиенты могут затухать или взрываться, как устроен свёрточный слой и из чего складывается типичный запуск обучения.

Батч-нормализация#

Батч-нормализация (Ioffe and Szegedy, 2015) стандартизирует активации слоя по статистикам текущего мини-батча. Возьмём один признак (в свёрточной сети — один канал) и мини-батч из nn его значений x1,…,xnx_1, \dots, x_n. Сначала слой вычисляет среднее по батчу (batch mean) и дисперсию по батчу (batch variance)

μB=1n∑i=1nxi,σB2=1n∑i=1n(xi−μB)2,\mu_B = \frac1n\sum_{i=1}^{n} x_i, \qquad \sigma_B^2 = \frac1n\sum_{i=1}^{n} \left(x_i - \mu_B\right)^2,

а затем стандартизирует каждое значение:

x^i=xi−μBσB2+ε.\hat x_i = \frac{x_i - \mu_B}{\sqrt{\sigma_B^2 + \varepsilon}} .

Малая константа ε>0\varepsilon > 0 (в PyTorch 10−510^{-5}) обеспечивает устойчивость деления, когда разброс в батче почти нулевой. После этого шага у батча среднее 00 и дисперсия σB2/(σB2+ε)≈1\sigma_B^2/(\sigma_B^2 + \varepsilon) \approx 1.

Рисунок

Батч-нормализация одного признака

Гистограмма батча до и после нормализацииТри гистограммы на общей горизонтальной оси. Верхняя — скошенный вправо батч с центром около 6.32. Стрелка с подписью «вычитаем mu B» переносит её в средний ряд, где центр уже в нуле, а ширина та же. Две стрелки с подписью «делим на квадратный корень из sigma B в квадрате плюс epsilon» сжимают отметки плюс и минус одного стандартного отклонения до плюс и минус единицы, и получается нижняя гистограмма: уже, выше, по-прежнему скошенная, со средним 0 и дисперсией 1. Для сравнения в нижнем ряду штриховой линией нарисована стандартная нормальная кривая.батч xᵢμB = 6.32вычитаем μBxᵢ − μB−σBσBделим на √(σB2 + ε)x̂iсреднее 0.00, дисперсия 1.00𝒩(0, 1)051015−11Гистограмма батча до и после нормализацииТри гистограммы на общей горизонтальной оси. Верхняя — скошенный вправо батч с центром около 6.32. Стрелка с подписью «вычитаем mu B» переносит её в средний ряд, где центр уже в нуле, а ширина та же. Две стрелки с подписью «делим на квадратный корень из sigma B в квадрате плюс epsilon» сжимают отметки плюс и минус одного стандартного отклонения до плюс и минус единицы, и получается нижняя гистограмма: уже, выше, по-прежнему скошенная, со средним 0 и дисперсией 1. Для сравнения в нижнем ряду штриховой линией нарисована стандартная нормальная кривая.батч xᵢμB = 6.32вычитаем μBxᵢ − μB−σBσBделим на √(σB2 + ε)x̂iсреднее 0.00, дисперсия 1.00𝒩(0, 1)051015−11
Скошенный батч из n = 256 активаций, полученных как x = 2 + 4·exp(0.45 z) при z ~ 𝒩(0, 1) и разбитых на интервалы шириной 0.6; среднее по батчу 6.32, стандартное отклонение по батчу 2.06. Вычитание среднего сдвигает гистограмму к нулю; деление на √(σ_B² + ε) сжимает её до единичной дисперсии. Площади сохраняются, поэтому более узкая гистограмма выше. Форма не меняется: батч остаётся скошенным и не превращается в стандартное нормальное распределение, показанное штриховой линией. Здесь ε = 10⁻⁵.

Небольшой разобранный пример. Для батча x=(1,2,3,6)x = (1, 2, 3, 6) при ε=10−5\varepsilon = 10^{-5}:

первоевтороетретьечетвёртое
xix_i11223366
xi−μBx_i - \mu_B−2-2−1-10033
x^i\hat x_i−1.069-1.069−0.535-0.535001.6041.604

Здесь μB=12/4=3\mu_B = 12/4 = 3 и σB2=(4+1+0+9)/4=3.5\sigma_B^2 = (4 + 1 + 0 + 9)/4 = 3.5, поэтому каждое центрированное значение делится на 3.5+10−5≈1.871\sqrt{3.5 + 10^{-5}} \approx 1.871. Дисперсия делится на nn, а не на n−1n - 1: это обычная дисперсия батча.

Остальная часть слоя#

У полного слоя батч-нормализации два обучаемых параметра на признак — масштаб γ\gamma и сдвиг β\beta, — и его выход равен

yi=γ x^i+β.y_i = \gamma\,\hat x_i + \beta .

Параметры γ\gamma и β\beta — фиксированные числа, а μB\mu_B и σB2\sigma_B^2 меняются от батча к батчу, поэтому тождественное преобразование нельзя воспроизвести для каждого батча по отдельности. Зато его можно получить, если ориентироваться на статистики признака по генеральной совокупности: при γ=Var⁡[x]+ε\gamma = \sqrt{\operatorname{Var}[x] + \varepsilon} и β=E[x]\beta = \mathbb E[x] слой воспроизводит свой вход на инференсе, где вместо μB\mu_B и σB2\sigma_B^2 используются скользящие средние, оценивающие эти статистики (о них ниже). При обучении он делает это лишь приближённо, с точностью до колебаний μB\mu_B и σB2\sigma_B^2 от батча к батчу (Ioffe and Szegedy, 2015, раздел 3). Поэтому стандартизация не ограничивает то, что слой может выразить. Она меняет параметризацию так, что масштаб каждого признака задаётся одним параметром.

Обучение и инференс устроены по-разному. При обучении слой использует статистики батча μB,σB2\mu_B, \sigma_B^2 и, кроме того, ведёт их скользящие средние. В PyTorch, где коэффициент обновления (momentum) по умолчанию равен 0.10.1, это μrun←0.9 μrun+0.1 μB\mu_{\text{run}} \leftarrow 0.9\,\mu_{\text{run}} + 0.1\,\mu_B и σrun2←0.9 σrun2+0.1 nn−1σB2\sigma^2_{\text{run}} \leftarrow 0.9\,\sigma^2_{\text{run}} + 0.1\,\tfrac{n}{n-1}\sigma_B^2; скользящая дисперсия хранит несмещённую оценку. На инференсе, когда «батч» может состоять из одного изображения, вместо статистик батча используются скользящие средние, и выход для одного примера больше не зависит от остальных примеров в батче. В PyTorch переключение выполняет model.eval(). Отключение градиентов через torch.no_grad() не переводит батч-нормализацию на скользящие статистики.

В свёрточной сети BatchNorm2d нормализует каждый канал по всем значениям этого канала в батче; всего таких значений размер батча × высота × ширина. Свёртку, за которой следует батч-нормализация, обычно создают с bias=False, потому что вычитание μB\mu_B сокращает любое постоянное смещение, а его роль берёт на себя β\beta.

По похожей причине батч-нормализация не видит масштаба того, что стоит перед ней. Если свёртка без смещения с весами WW вычисляет значения канала xix_i по входу uu, то умножение uu или WW на множитель r>0r > 0 превращает каждое xix_i в r xir\,x_i, поскольку свёртка линейна. Тогда μB\mu_B становится равным r μBr\,\mu_B, σB2\sigma_B^2 — равной r2σB2r^2\sigma_B^2, и

rxi−rμBr2σB2+ε=xi−μBσB2+ε/r2.\frac{r x_i - r\mu_B}{\sqrt{r^2\sigma_B^2 + \varepsilon}} = \frac{x_i - \mu_B}{\sqrt{\sigma_B^2 + \varepsilon/r^2}} .

С точностью до небольшой замены ε\varepsilon на ε/r2\varepsilon/r^2 выход не меняется (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) стандартизирует каждое изображение отдельно. Для изображения nn и канала cc карты признаков xx размера H×WH \times W

μnc=1HW∑h,wxnchw,σnc2=1HW∑h,w(xnchw−μnc)2,ynchw=γc xnchw−μncσnc2+ε+βc,\begin{gathered} \mu_{nc} = \frac{1}{HW}\sum_{h,w} x_{nchw}, \qquad \sigma_{nc}^2 = \frac{1}{HW}\sum_{h,w} \left(x_{nchw} - \mu_{nc}\right)^2, \\ y_{nchw} = \gamma_c\,\frac{x_{nchw} - \mu_{nc}}{\sqrt{\sigma_{nc}^2 + \varepsilon}} + \beta_c , \end{gathered}

с обучаемыми масштабом γc\gamma_c и сдвигом βc\beta_c для каждого канала, как в батч-нормализации (InstanceNorm2d в PyTorch включает их только при affine=True). Эти слои различаются лишь тем, какие значения имеют общие среднее и дисперсию:

СлойПо каким значениям вычисляются статистикиОдно среднее и одна дисперсия на
батч-нормализациявсе NN изображений и все H×WH \times W позицийканал
нормализация по экземпляруH×WH \times W позиций одного изображенияизображение и канал
нормализация слоя, изображение (CNN)все каналы и позиции одного изображенияизображение
нормализация слоя, трансформерdd признаков одного токенатокен

Нормализация слоя нормирует по тем последним осям, которые ей заданы (normalized_shape в PyTorch). Строка для CNN — конфигурация, объединяющая всё изображение; в трансформере тот же слой нормирует вектор признаков каждого токена отдельно.

Если четыре значения (1,2,3,6)(1, 2, 3, 6) из разобранного выше примера — это карта 2×22 \times 2 одного канала одного изображения, нормализация по экземпляру (при γc=1\gamma_c = 1, βc=0\beta_c = 0) переводит их в (−1.069,−0.535,0,1.604)(-1.069, -0.535, 0, 1.604), что бы ни содержали остальные изображения батча. Отсюда три следствия:

  • Нет зависимости от батча. Обучение и инференс вычисляют одну и ту же функцию, скользящие средние не нужны, и батч из одного изображения работает.
  • Контраст каждого изображения устраняется. Среднее и разброс каждой карты признаков каждого изображения отбрасываются; γc\gamma_c и βc\beta_c одинаковы для всех изображений и не могут их восстановить. Именно это нужно для быстрого переноса стиля: стилизованное изображение не должно зависеть от контраста изображения содержания (content image) (Ulyanov et al., раздел 2). Там, где общая яркость или контраст изображения несут информацию, слой её выбрасывает.
  • Нужна пространственная протяжённость. На карте 1×11 \times 1 у каждого канала одно значение, его дисперсия равна нулю, и выход равен βc\beta_c при любом входе.

Три применения нормализации по экземпляру:

  • Перенос стиля. Замена батч-нормализации нормализацией по экземпляру в сети стилизации прямого распространения (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), и нормализация по экземпляру тоже подходит.

Затухающие и взрывающиеся градиенты#

Мы обучаем модель, вычисляя градиент функции потерь по каждому весу (обратное распространение ошибки) и делая шаг против него. По цепному правилу градиент, который доходит до одного из первых слоёв, — это произведение множителей, по одному на каждый слой или временной шаг, через который он прошёл. Если в каждом слое один нейрон, hi=φ(ai)h_i = \varphi(a_i) с предактивацией (pre-activation) ai=wihi−1+bia_i = w_i h_{i-1} + b_i и функцией активации φ\varphi, то каждый множитель равен

∂hi∂hi−1=wi φ′(ai),\frac{\partial h_i}{\partial h_{i-1}} = w_i\,\varphi'(a_i),

то есть весу, умноженному на наклон функции активации. В слоях с многими нейронами множители — матрицы Якоби, и важна их величина: произведение многих якобианов обычно растёт или убывает с глубиной геометрически, со скоростью, которую задают их наибольшие сингулярные числа (Pascanu, Mikolov and Bengio, 2013, раздел 2.1; Goodfellow, Bengio and Courville, 2016, разделы 8.2.5 и 10.7). Поэтому если множители слоёв в основном

  • по величине больше 11, произведение растёт геометрически: взрывающиеся градиенты (exploding gradients);
  • по величине меньше 11, произведение убывает геометрически: затухающие градиенты (vanishing gradients).

Чем дальше множители от 11, тем быстрее это происходит, но отклонение не обязано быть большим (см. таблицу ниже). Впервые проблему проанализировали для рекуррентных сетей (Hochreiter, 1991; Bengio, Simard and Frasconi, 1994), где её проще всего увидеть.

Пример: RNN#

Рекуррентная сеть применяет один и тот же вес на каждом временном шаге. Возьмём один рекуррентный нейрон с ReLU, ht=ReLU⁡(w ht−1+u xt+b)h_t = \operatorname{ReLU}(w\,h_{t-1} + u\,x_t + b), и рекуррентный вес w=2w = 2.

Рисунок

Развёрнутая RNN с рекуррентным весом w = 2

Развёрнутая рекуррентная сеть со взрывающимися множителямиТри временных шага расположены друг под другом. На каждом шаге блок входа подаётся в рекуррентную ячейку со значком ReLU; первые две ячейки ведут к штриховым блокам выхода, третья — к сплошному. Изогнутые красные стрелки переносят скрытое состояние от каждой ячейки к следующей, и каждая подписана «умножить на 2»; под третьей ячейкой продолжается штриховая красная волнистая линия, тоже с подписью «умножить на 2 и так далее, взрыв». Пояснение говорит, что через tau шагов множитель равен w в степени tau.входw = 2выходx1x2x3ячейка ReLU×2×2×2 …взрывскрытое состояние hпроходит черезодно и то же wчерез τ шагов:×wτРазвёрнутая рекуррентная сеть со взрывающимися множителямиТри временных шага расположены друг под другом. На каждом шаге блок входа подаётся в рекуррентную ячейку со значком ReLU; первые две ячейки ведут к штриховым блокам выхода, третья — к сплошному. Изогнутые красные стрелки переносят скрытое состояние от каждой ячейки к следующей, и каждая подписана «умножить на 2»; под третьей ячейкой продолжается штриховая красная волнистая линия, тоже с подписью «умножить на 2 и так далее, взрыв». Пояснение говорит, что через tau шагов множитель равен w в степени tau.входw = 2выходx1x2x3ячейка ReLU×2×2×2 …взрывКрасные стрелки: на каждом шаге скрытоесостояние h проходит через одно и то же w.Через τ шагов множитель равен wτ.
На каждом временном шаге применяется один и тот же вес w = 2, поэтому каждая красная рекуррентная стрелка умножает на 2. Значит, чувствительность более позднего скрытого состояния к более раннему, а с ней и градиент при обратном распространении, равна 2 через один шаг, 4 через два, 8 через три и w в степени τ через τ шагов. Промежуточные выходы показаны штриховой линией, потому что здесь используется только последний.

Каждый шаг по рекуррентному пути умножает на одно и то же w=2w = 2, поэтому чувствительность более позднего состояния к более раннему равна 22 через один шаг, 44 через два, 88 через три и так далее. Формально: пусть функция потерь вычисляется только по последнему состоянию hk+τh_{k+\tau}, через τ\tau шагов после hkh_k (как в развёрнутой RNN выше, где используется только последний выход). Пока ReLU находится в активной области (ReLU⁡′=1\operatorname{ReLU}' = 1), каждый множитель шага w φ′(at)w\,\varphi'(a_t) равен ww, и цепное правило даёт

∂ht∂ht−1=wдля любого t,∂hk+τ∂hk=∏s=k+1k+τ∂hs∂hs−1=wτ,∂L∂hk=∂L∂hk+τ wτ.\begin{aligned} \frac{\partial h_t}{\partial h_{t-1}} &= w \quad \text{для любого } t, \\ \frac{\partial h_{k+\tau}}{\partial h_k} &= \prod_{s=k+1}^{k+\tau} \frac{\partial h_s}{\partial h_{s-1}} = w^{\tau}, \\ \frac{\partial L}{\partial h_k} &= \frac{\partial L}{\partial h_{k+\tau}}\, w^{\tau} . \end{aligned}

Градиент, переданный назад на τ\tau шагов, несёт множитель wτw^\tau:

веспосле 3 шаговпосле 10 шаговпосле 50 шагов
w=2w = 28810241024≈1.1⋅1015\approx 1.1\cdot 10^{15}
w=1.1w = 1.11.331.332.592.59117117
w=1w = 1111111
w=0.9w = 0.90.730.730.350.350.00520.0052
w=0.5w = 0.50.1250.125≈9.8⋅10−4\approx 9.8\cdot 10^{-4}≈8.9⋅10−16\approx 8.9\cdot 10^{-16}

Даже вес, который отличается от 11 всего на 10%, за 50 шагов меняет градиент на два порядка. Ничего специфически рекуррентного в таблице нет: стопка из τ\tau слоёв с одним и тем же множителем ww даёт тот же wτw^\tau, и автоэнкодерам следующих заметок нужно только это прочтение по глубине. Рекуррентные нейроны и их гейты подробно разобраны в курсе NLP (рекуррентные сети, LSTM и GRU).

Рисунок

Многократное умножение на w

Множитель градиента w в степени τ в логарифмическом масштабеТри прямые на логарифмической вертикальной оси от 1/1024 до 1024 в зависимости от временного шага от 0 до 10. Прямая для w, равного 2, поднимается до 1024 через закрашенную область взрыва; прямая для w, равного 1, остаётся на уровне 1; прямая для w, равного 0.5, опускается до 1/1024 через закрашенную область затухания. Точки при τ, равном 1, 2 и 3, на растущей прямой подписаны 2, 4 и 8.10243211/321/10240246810временные шаги τмножитель wτ (лог. шкала)взрыв: wτ → ∞затухание: wτ → 0w = 2w = 1w = 0.5248Множитель градиента w в степени τ в логарифмическом масштабеТри прямые на логарифмической вертикальной оси от 1/1024 до 1024 в зависимости от временного шага от 0 до 10. Прямая для w, равного 2, поднимается до 1024 через закрашенную область взрыва; прямая для w, равного 1, остаётся на уровне 1; прямая для w, равного 0.5, опускается до 1/1024 через закрашенную область затухания. Точки при τ, равном 1, 2 и 3, на растущей прямой подписаны 2, 4 и 8.10243211/321/10240246810временные шаги τмножитель wτ (лог. шкала)взрыв: wτ → ∞затухание: wτ → 0w = 2w = 1w = 0.5248
Множитель w в степени τ через τ шагов на логарифмической оси, где степени 2 расположены равномерно. При w = 2 он достигает 1024 через десять шагов (взрыв); при w = 0.5 падает до 1/1024 (затухание); масштаб сохраняет только w = 1. Первые три красные точки, 2, 4 и 8, — множители через один, два и три шага развёрнутой RNN выше.

Что это делает с обучением#

Шаг градиентного спуска — это θ←θ−η ∇θL\theta \leftarrow \theta - \eta\,\nabla_\theta L, поэтому размер шага пропорционален градиенту.

  • Взрыв: градиент приходит умноженным на огромный множитель, и обновление делает очень большой шаг по ландшафту функции потерь. Каждое обновление перескакивает через минимум, и функция потерь скачет или расходится.
  • Затухание: градиент, который доходит до первых слоёв глубокого стека, несёт по одному множителю на каждый слой. Если множители малы, произведение стремится к нулю и шаги становятся крошечными: веса первых слоёв почти не сдвигаются, и бюджет обучения заканчивается задолго до того, как достигнут минимум.
Рисунок

Что огромный или крошечный градиент делает с шагами обучения

Шаги с перелётом и слишком маленькие шагиДве копии функции потерь в форме параболы. На первой, с подписью «взрыв», итерации градиентного спуска начинаются около минимума и прыгают то на одну, то на другую сторону, каждый раз всё дальше, с красными дугами между ними. На второй, с подписью «затухание», одиннадцать синих точек медленно сползают по правой ветви и останавливаются, исчерпав max_steps, далеко от минимума в нуле.w ≫ 1: шаги слишком великиминимумстартпроскакивает,расходитсяw → 0: шаги слишком малыминимумстартостановка наmax_steps = 10Шаги с перелётом и слишком маленькие шагиДве копии функции потерь в форме параболы. На первой, с подписью «взрыв», итерации градиентного спуска начинаются около минимума и прыгают то на одну, то на другую сторону, каждый раз всё дальше, с красными дугами между ними. На второй, с подписью «затухание», одиннадцать синих точек медленно сползают по правой ветви и останавливаются, исчерпав max_steps, далеко от минимума в нуле.w ≫ 1: шаги слишком великиминимумстартпроскакивает,расходитсяw → 0: шаги слишком малыминимумстартостановка наmax_steps = 10
Градиентный спуск на игрушечной функции потерь L(θ) = θ²/2, градиент которой равен θ. Если градиент приходит умноженным на множитель g, набранный при прохождении через сеть, то обновление с шагом обучения η имеет вид θ ← θ − η g θ = (1 − η g) θ. Итерации проскакивают минимум при η g > 1 и уходят всё дальше при η g > 2. Красные точки: η g = 2.3 из θ₀ = 1, поэтому каждый шаг умножает θ на −1.3, и итерации разлетаются наружу. Синие точки: η g = 0.03 из θ₀ = 2.5, поэтому десять шагов сдвигают θ лишь до 1.84, и обучение останавливается, исчерпав max_steps, далеко от минимума.

Распространённые решения#

Остальная часть курса опирается на стандартные решения:

  • Инициализация, которая держит масштаб сигналов около 1: инициализация Ксавье/Глоро (Xavier/Glorot initialization) для насыщающихся активаций (Glorot and Bengio, 2010) и инициализация Хе (He initialization) для ReLU (He et al., 2015); обе описаны в следующем разделе.
  • Слои нормализации, например батч-нормализация, описанная выше.
  • Ненасыщающиеся активации вместо сигмоид. Производная сигмоиды не превышает 14\tfrac14 (это значение достигается в 00), поэтому множитель wi φ′(ai)w_i\,\varphi'(a_i) сигмоидного слоя по величине не больше ∣wi∣/4|w_i|/4: каждый сигмоидный слой уменьшает градиент как минимум в 4 раза даже при ∣wi∣=1|w_i| = 1. У ReLU, ReLU⁡(x)=max⁡(0,x)\operatorname{ReLU}(x) = \max(0, x) (Nair and Hinton, 2010), наклон равен ровно 11 везде, где нейрон активен. LeakyReLU, max⁡(αx, x)\max(\alpha x,\ x) с малым наклоном α\alpha, например 0.20.2 (Maas, Hannun and Ng, 2013), вдобавок сохраняет наклон α\alpha вместо 00 на отрицательных входах, так что неактивный нейрон всё равно пропускает часть градиента.
  • Обрезка градиентов (gradient clipping) против взрыва: градиент перемасштабируют, когда его норма превышает порог (Pascanu, Mikolov and Bengio, 2013, раздел 3.2).
  • Рекуррентные ячейки с гейтами (gated recurrent cells). В LSTM (Hochreiter and Schmidhuber, 1997) состояние ячейки обновляется аддитивно. В исходной ячейке его множитель на шаге равен ровно 11. Ячейка, которую используют сегодня, добавляет гейт забывания (forget gate; Gers, Schmidhuber and Cummins, 2000), и множитель равен значению этого гейта: числу из (0,1)(0, 1), которое сеть может держать около 11. GRU (Cho et al., 2014) работает так же: она интерполирует между старым состоянием и кандидатом, поэтому прямой путь несёт множитель её гейта обновления (update gate).
  • Остаточные связи (residual connections; He et al., 2016a), xl+1=xl+F(xl)x_{l+1} = x_l + F(x_l). При чисто тождественном обходе (identity skip), как в ResNet с предактивацией (He et al., 2016b, формула (5)), якобиан блока равен I+∂F/∂xlI + \partial F/\partial x_l, поэтому у градиента всегда есть путь с множителем ровно 11.

Свёрточный слой#

Как параметризуется и обучается двумерный свёрточный слой (nn.Conv2d) (Goodfellow, Bengio and Courville, 2016, глава 9; Dumoulin and Visin, 2016):

  • Ядра обучаются. Все элементы тензора весов и смещение обучаются градиентным спуском:

    W∈Routc×inc×kH×kW,b∈Routc.W \in \mathbb R^{\text{out}_c \times \text{in}_c \times k_H \times k_W}, \qquad b \in \mathbb R^{\text{out}_c} .
  • Обозначения. in[c]\text{in}[c] — входной канал с номером cc, плоскость размера Hin×WinH_{\text{in}} \times W_{\text{in}} (Win,WoutW_{\text{in}}, W_{\text{out}} с нижними индексами — ширины; WW, WjW_j, Wj,cW_{j,c} — всегда веса). Wj,cW_{j,c} — ядро размера kH×kWk_H \times k_W, которое выходной канал jj применяет к входному каналу cc. Собранный по всем cc тензор Wj∈Rinc×kH×kWW_j \in \mathbb R^{\text{in}_c \times k_H \times k_W} — это фильтр (filter) выходного канала jj.

  • Выходной канал jj, где j=1,…,outcj = 1, \dots, \text{out}_c, суммирует результаты по каналам и добавляет одно скалярное смещение:

    out[j]=∑c=1incWj,c⋆in[c]+bj.\text{out}[j] = \sum_{c=1}^{\text{in}_c} W_{j,c} \star \text{in}[c] + b_j .

    Каждый out[j]\text{out}[j] — плоскость размера Hout×WoutH_{\text{out}} \times W_{\text{out}}. На каждый выходной канал приходится одно смещение, поэтому в векторе смещений outc\text{out}_c элементов.

Рисунок

Размерности в свёрточном слое: один выходной канал

Вычисление одного выходного канала свёрткиРяды для входных каналов 1, 2 и далее до in_c. В каждом ряду входная плоскость in[c] размера 6 на 6 (H_in на W_in) взаимно коррелируется со срезом ядра W j c размера 3 на 3, и получается частичная карта 4 на 4. Три среза ядра обведены вместе как фильтр W j формы in_c на k на k. Стрелки сводят все частичные карты в узел суммирования, сверху добавляется смещение b j, и результат — выходная плоскость out[j] размера H_out на W_out, нарисованная перед стопкой из out_c плоскостей, по одной на фильтр.in[c]Hin × WinWj,ck × kWj,cin[c]c = 1c = 2c = inc⋮⋮фильтр Wjinc × k × kΣпо cbj+ смещениеout[j]Hout × Woutoutcплоскостейповторяем для каждого из outc фильтров W1, …, Woutc → outc плоскостейВычисление одного выходного канала свёрткиРяды для входных каналов 1, 2 и далее до in_c. В каждом ряду входная плоскость in[c] размера 6 на 6 (H_in на W_in) взаимно коррелируется со срезом ядра W j c размера 3 на 3, и получается частичная карта 4 на 4. Три среза ядра обведены вместе как фильтр W j формы in_c на k на k. Стрелки сводят все частичные карты в узел суммирования, сверху добавляется смещение b j, и результат — выходная плоскость out[j] размера H_out на W_out, нарисованная перед стопкой из out_c плоскостей, по одной на фильтр.in[c]Hin × WinWj,ck × kWj,cin[c]c = 1c = 2c = inc⋮⋮фильтр Wjinc × k × kΣbj+ смещениеout[j]Hout × Woutповторяем для каждого из outc фильтров W1, …, Woutc→ outc выходных плоскостей
У каждого выходного канала j свой фильтр: по одному срезу ядра размера k × k на каждый входной канал. Срез для канала c скользит по этому каналу, результаты по всем in_c входным каналам суммируются, и добавляется скалярное смещение канала j. Жёлто-оранжевые окно и клетка показывают, какие входы дают одно выходное значение. Если повторить это со всеми out_c фильтрами, получится по одной выходной плоскости на фильтр, поэтому тензор весов имеет форму out_c × in_c × k × k. Нарисовано для входа 6 × 6, k = 3, шага 1 и без дополнения, поэтому выходные плоскости имеют размер 4 × 4.

Если расписать операцию ⋆\star поэлементно для шага (stride) ss и расширения (dilation) dd, получится скользящее скалярное произведение. Библиотеки глубокого обучения вычисляют взаимную корреляцию (cross-correlation) и не переворачивают ядро, в отличие от свёртки в обработке сигналов; поскольку ядро обучается, эта разница не важна.

out[j]y,x=bj+∑c=1inc∑m=0kH−1∑l=0kW−1Wj,c,m,l⋅ in[c] sy+dm,  sx+dl,\begin{aligned} \text{out}[j]_{y,x} = b_j &+ \sum_{c=1}^{\text{in}_c}\sum_{m=0}^{k_H-1}\sum_{l=0}^{k_W-1} W_{j,c,m,l} \\ &\quad\cdot\ \text{in}[c]_{\,s y + d m,\; s x + d l} , \end{aligned}

где y=0,…,Hout−1y = 0, \dots, H_{\text{out}} - 1, x=0,…,Wout−1x = 0, \dots, W_{\text{out}} - 1, а in[c]\text{in}[c] обозначает вход после дополнения нулями (zero-padding) на pp с каждой стороны, с индексацией от 00. Формула предполагает одну группу, так что каждый выходной канал видит каждый входной; групповые свёртки (grouped convolutions) ограничивают эти связи.

  • Гиперпараметры, которые не обучаются: шаг, дополнение (padding), размер ядра и расширение фиксируются при создании слоя. Они определяют размер выхода (документация PyTorch):

    Hout=⌊NHs⌋+1,NH=Hin+2p−d (kH−1)−1\begin{aligned} H_{\text{out}} &= \left\lfloor \frac{N_H}{s} \right\rfloor + 1, \\ N_H &= H_{\text{in}} + 2p - d\,(k_H - 1) - 1 \end{aligned}

    для высоты, где NHN_H — высота с дополнением минус протяжённость d (kH−1)+1d\,(k_H - 1) + 1 расширенного ядра. Для WoutW_{\text{out}} формула та же, только с WinW_{\text{in}} и kWk_W. В автоэнкодере следующей заметки у каждого из четырёх блоков энкодера k=3, p=1, s=2, d=1k = 3,\ p = 1,\ s = 2,\ d = 1, поэтому Hout=⌊(Hin−1)/2⌋+1H_{\text{out}} = \lfloor (H_{\text{in}} - 1)/2 \rfloor + 1 делит чётные размеры пополам: 64→32→16→8→464 \to 32 \to 16 \to 8 \to 4.

  • Инициализация. Ядра инициализируются случайно. При fanin=inc kHkW\text{fan}_{\text{in}} = \text{in}_c\, k_H k_W и fanout=outc kHkW\text{fan}_{\text{out}} = \text{out}_c\, k_H k_W инициализация Ксавье/Глоро выбирает веса с дисперсией 2/(fanin+fanout)2/(\text{fan}_{\text{in}} + \text{fan}_{\text{out}}) (Glorot and Bengio, 2010), а инициализация Хе — с дисперсией 2/fanin2/\text{fan}_{\text{in}} для слоёв, за которыми следует ReLU (He et al., 2015, раздел 2.2). Обе построены ради цели предыдущего раздела: сохранять масштаб сигналов и градиентов постоянным от слоя к слою. По умолчанию PyTorch для Conv2d использует более простое правило по числу входов (fan-in) — равномерное распределение в пределах ±1/fanin\pm 1/\sqrt{\text{fan}_{\text{in}}} (реализовано как kaiming_uniform_ с a=5a = \sqrt5). Равномерное распределение в пределах ±b\pm b имеет дисперсию b2/3b^2/3, поэтому у этого варианта по умолчанию дисперсия 1/(3 fanin)1/(3\,\text{fan}_{\text{in}}) — в шесть раз меньше дисперсии Хе. Без нормализации дисперсия сигнала в глубоком стеке ReLU-слоёв с такой инициализацией уменьшается примерно в 6 раз на каждом слое. В сетях следующих заметок масштаб восстанавливает батч-нормализация после свёрток.

  • Чему учатся веса. Ядра первых слоёв обычно становятся маленькими детекторами границ, цветовых пятен и простых текстур. Более глубокие слои комбинируют их в части объектов и объекты на более крупных рецептивных полях (Zeiler and Fergus, 2014, раздел 4 и рисунок 2). Поэтому предобученные свёрточные сети полезны как экстракторы признаков для метрик качества курса.

Для ориентира: у слоя с inc=16\text{in}_c = 16, outc=32\text{out}_c = 32 и ядрами 3×33 \times 3 есть 32⋅16⋅3⋅3=460832 \cdot 16 \cdot 3 \cdot 3 = 4608 весов плюс 3232 смещения.

Запуск обучения на практике#

Вокруг модели и её функции потерь каждый запуск обучения в курсе устроен одинаково.

  • Оптимизатор. Большинство моделей используют Adam (Kingma and Ba, 2015), который делит шаг по каждой координате на скользящую оценку величины её градиента (см. заметку о подгонке распределений). Распространённый вариант — AdamW (Loshchilov and Hutter, 2019, раздел 2). L2-штраф λ2∥θ∥2\tfrac\lambda2\|\theta\|^2, добавленный к функции потерь, входит в Adam через градиент и делится на тот же масштаб по каждой координате, поэтому веса с исторически большими градиентами регуляризуются слабее, чем задумано. AdamW вместо этого уменьшает веса напрямую, вне адаптивного шага:

    θ←θ−ηλ θ−η m^v^+ϵ,\theta \leftarrow \theta - \eta\lambda\,\theta - \eta\,\frac{\hat m}{\sqrt{\hat v} + \epsilon},

    где m^\hat m и v^\hat v — скользящие оценки Adam для среднего градиента и квадрата его величины. Для обычного SGD, где m^/(v^+ϵ)\hat m/(\sqrt{\hat v} + \epsilon) заменяется самим градиентом, обе формы дают одно и то же обновление; для Adam они различаются. AdamW в PyTorch по умолчанию использует η=10−3\eta = 10^{-3} и λ=10−2\lambda = 10^{-2}.

  • Валидация. После каждой эпохи функция потерь вычисляется также на отложенных (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×64×641 \times 64 \times 64 в 32×4×432 \times 4 \times 4, а последняя свёртка 3×33\times 3 с шагом 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) (на картах 16×1616 \times 16, 8×88 \times 8 и 4×44 \times 4 для изображений 64×6464 \times 64), а ячейка 23 делает то же в блоках своего критика со спектральной нормализацией. Генераторы сохраняют батч-нормализацию.
  • Домашнее задание 1 следует распорядку из последнего раздела: set_seed в ячейке 3 задаёт все начальные значения и оба флага cuDNN, ячейки 23–31 обучают автоэнкодер с AdamW при настройках по умолчанию 30 эпох и строят графики MSE на обучении и валидации по эпохам, причём валидационный проход выполняется в режиме eval() под torch.no_grad(), а функция обучения должна загружать сохранённые веса, если они есть. В ноутбуке нет сохранённых выходов.

Дальше#

С этими элементами можно построить первую нейросетевую модель курса. Следующая заметка собирает блоки свёртка → батч-нормализация → LeakyReLU в энкодер и декодер и обучает их реконструировать цифры MNIST через 512-мерный код. Она ставит вопросы о том, что улавливает такой код и можно ли генерировать из него новые изображения.

Литература#