Шпаргалка формул нейросетей · Оптимизатор

Adam: инерция, масштаб и поправка смещения

Оптимизатор по умолчанию для нейросетей и основа AdamW, которым обучают языковые модели. Разбираем по косточкам оба момента, выводим поправку смещения, смотрим, почему шаг Adam не зависит от масштаба градиента и сам уменьшается в шуме, гоняем пять оптимизаторов по поверхностям в 3D и разбираемся, чем AdamW лучше L2-регуляризации.

Javharbek Abdulatipov · 40 мин чтения
$$\theta_t=\theta_{t-1}-\eta\,\frac{\hat m_t}{\sqrt{\hat v_t}+\epsilon}$$

Зачем нужен Adam

У нейросети миллионы весов, и их градиенты живут в очень разных масштабах: у эмбеддингов редких токенов — редкие крупные толчки, у последних слоёв — постоянные мелкие, у слоёв нормализации — третьи. Обычный SGD делает шаг $\eta g$, и один темп $\eta$ для всех одновременно слишком велик для одних весов и слишком мал для других. К тому же мини-батчевый градиент шумит, а поверхность потерь полна вытянутых оврагов и плато.

Adam (adaptive moment estimation, Кингма и Ба, 2014; ICLR 2015) решает обе задачи одной формулой:

  • первый момент $m_t$ — экспоненциальное среднее градиентов: инерция, которая гасит шум и зигзаги (как Momentum);
  • второй момент $v_t$ — экспоненциальное среднее квадратов градиента: типичный размер градиента каждого веса, на который делится шаг (как RMSProp);
  • поправка смещения — деление на $1-\beta^t$, которое убирает систематическую недооценку моментов на первых шагах.

Результат: шаг каждого веса порядка $\eta$ независимо от масштаба его градиента, без ручной подстройки под каждый слой. Поэтому Adam — оптимизатор по умолчанию в большинстве задач, а его вариант AdamW обучает трансформеры и большие языковые модели.

Главное за минуту

$m_t$ — куда идти (сглаженный градиент), $\sqrt{v_t}$ — насколько крупно шагать (среднеквадратичный градиент), $1-\beta^t$ — поправка за старт с нуля. Шаг $\eta\,\hat m_t/(\sqrt{\hat v_t}+\epsilon)$ безразмерен: умножьте градиенты на 1000 — шаги не изменятся. При чистом сигнале шаг $\approx\eta$, в шуме — меньше. AdamW выносит затухание весов из градиента.

Интуиция: компас и спидометр

Представьте, что вы ведёте машину по бездорожью по показаниям дрожащего компаса. Глупо поворачивать руль при каждом его рывке — разумнее следить за усреднённым направлением. Это $m_t$. А скорость стоит выбирать по тому, насколько обычно сильны рывки: на ровной дороге можно ехать уверенно, на ухабах — осторожно. Это $\sqrt{v_t}$: шаг Adam — направление, делённое на типичную силу толчков.

На рисунке — поток градиентов одного веса: среднее $+0{,}5$ до шага 100 и $-0{,}5$ после, плюс шум. Оранжевая линия — сглаженный градиент $\hat m_t$, бирюзовые — коридор $\pm\sqrt{\hat v_t}$, внизу — итоговый шаг в единицах $\eta$.

Рисунок 1. Моменты Adam на шумном градиенте ($\sigma=1$). Ползунок $t$ выбирает шаг, значения которого показаны под рисунком. На первом шаге $\hat m_1/\sqrt{\hat v_1}=1$ ровно — шаг равен $\eta$ в сторону, противоположную $g_1$, каким бы ни был $g_1$; без поправки отношение было бы $3{,}16$. После смены знака среднего $\hat m_t$ становится отрицательным навсегда с шага 109, а $\sqrt{\hat v_t}$ почти не меняется ($\approx1{,}1$): величина шага определяется отношением сигнала к шуму, $0{,}5/\sqrt{1{,}25}\approx0{,}45$. Даже на шаге 200 шаг без поправки (штрих) в $2{,}35$ раза больше правильного.
Что заметить

Уберите шум ($\sigma=0$): шаг равен ровно $+1$ с первого шага и до смены знака, а примерно через 50 шагов после неё — почти ровно $-1$: при чистом сигнале Adam ходит шагами $\eta$, как знаковый метод. Увеличьте шум — шаг сжимается. Уменьшите $\beta_1$ до нуля — $\hat m_t$ превращается в сам градиент, и шаг $g_t/\sqrt{\hat v_t}$ скачет вместе с шумом.

Формула по косточкам

$$\begin{aligned}g_t&=\nabla_\theta L(\theta_{t-1})\\ m_t&=\beta_1 m_{t-1}+(1-\beta_1)\,g_t\\ v_t&=\beta_2 v_{t-1}+(1-\beta_2)\,g_t^2\\ \hat m_t&=\frac{m_t}{1-\beta_1^{\,t}},\qquad \hat v_t=\frac{v_t}{1-\beta_2^{\,t}}\\ \theta_t&=\theta_{t-1}-\eta\,\frac{\hat m_t}{\sqrt{\hat v_t}+\epsilon}\end{aligned}$$
$g_t$
градиент на мини-батче шага $t$ (шаги нумеруются с 1);
$m_t$
первый момент: экспоненциальное скользящее среднее (EMA) градиента, $m_0=0$;
$v_t$
второй момент: EMA поэлементного квадрата градиента, $v_0=0$; у каждого веса свои $m$ и $v$;
$\beta_1,\ \beta_2$
коэффициенты затухания, по умолчанию $0{,}9$ и $0{,}999$: память примерно $1/(1-\beta)$ шагов — 10 и 1000;
$1-\beta^t$
поправка смещения: компенсирует старт моментов с нуля;
$\eta$
темп обучения (learning rate), по умолчанию $10^{-3}$; здесь он задаёт типичную длину шага в единицах весов;
$\epsilon$
маленькая добавка ($10^{-8}$) против деления на ноль.

Пройдём три шага руками для градиентов $0{,}5$, $-0{,}2$, $0{,}4$ при стандартных $\beta_1=0{,}9$, $\beta_2=0{,}999$. Последний столбец — каким был бы шаг без поправки смещения.

$t$$g_t$$m_t$$v_t$$\hat m_t$$\sqrt{\hat v_t}$шаг $/\eta$без поправки
10,50,050,000250,50,51,0003,162
2−0,20,0250,000290,13160,38070,3461,469
30,40,06250,000450,23060,38730,5962,948
Первый шаг в деталях

$g_1=0{,}5$: $m_1=0{,}1\cdot0{,}5=0{,}05$ и $v_1=0{,}001\cdot0{,}25=0{,}00025$ — оба момента занижены, в 10 и в 1000 раз. Поправка возвращает $\hat m_1=0{,}5$ и $\hat v_1=0{,}25$, отношение $\hat m_1/\sqrt{\hat v_1}=1$: вес сдвигается ровно на $\eta$. Без поправки было бы $m_1/\sqrt{v_1}=0{,}05/0{,}0158\approx3{,}16$ — шаг завышен в $3{,}16$ раза, потому что $\sqrt{v_1}$ занижен сильнее ($\sqrt{1000}\approx31{,}6$ раза), чем $m_1$ (10 раз). Если бы поправили только $m$, шаг вырос бы в $31{,}6$ раза; только $v$ — уменьшился бы в 10.

Обратите внимание на третий шаг: градиенты меняли знак, поэтому $\hat m_3=0{,}23$ заметно меньше типичного размера градиента $\sqrt{\hat v_3}=0{,}39$, и Adam шагает лишь на $0{,}6\eta$. Чем согласованнее градиенты, тем ближе шаг к $\eta$.

Поправка смещения: откуда 1 − βᵗ

Моменты стартуют с нуля и первые шаги «помнят» этот ноль. Раскроем рекурсию для $m_t$:

$$m_t=(1-\beta_1)\sum_{i=1}^{t}\beta_1^{\,t-i}\,g_i$$

Это взвешенная сумма градиентов, но сумма весов равна не 1, а $(1-\beta_1)(1+\beta_1+\dots+\beta_1^{t-1})=1-\beta_1^t$. Недостающая доля $\beta_1^t$ досталась начальному нулю. Отсюда и поправка.

Вывод: несмещённость $\hat m_t$ и $\hat v_t$

Пусть градиенты стационарны: $\mathbb E[g_i]=\mu$, $\mathbb E[g_i^2]=s$ для всех $i$. Тогда по линейности ожидания

$$\mathbb E[m_t]=(1-\beta_1)\sum_{i=1}^{t}\beta_1^{\,t-i}\mu=\mu\,(1-\beta_1^{\,t}),\qquad \mathbb E[v_t]=s\,(1-\beta_2^{\,t})$$

Деление на $1-\beta^t$ даёт $\mathbb E[\hat m_t]=\mu$ и $\mathbb E[\hat v_t]=s$ — оценки несмещённые с первого шага. Если градиенты нестационарны, остаётся смещение порядка изменения $\mu$ за окно памяти; Кингма и Ба отмечают, что его можно держать малым, потому что веса далёкого прошлого экспоненциально малы.

Поправка нужна только в начале: $\beta_1^t<0{,}01$ уже при $t\ge44$, а вот $\beta_2^t<0{,}01$ — лишь при $t\ge4603$. Поэтому важнее всего она для второго момента.

Рисунок 2. Множители поправки $1/(1-\beta_1^t)$ и $1/(1-\beta_2^t)$ (обе оси логарифмические) и отношение шага без поправки к шагу с поправкой при постоянном градиенте, $(1-\beta_1^t)/\sqrt{1-\beta_2^t}$. При стандартных $\beta$ первые множители — 10 и 1000; шаг без поправки завышен в $3{,}16$ раза на первом шаге, сильнее всего — в $6{,}57$ раза — на 12-м, всё ещё в $2{,}35$ раза на 200-м и в $1{,}26$ раза на 1000-м. При $\beta_2=0{,}99$ первые шаги совпадают ($0{,}1/\sqrt{0{,}01}=1$), но к 13-му шагу завышение достигает $2{,}13$.
Зачем это важно на практике

Без поправки ранние шаги Adam в несколько раз длиннее задуманного — ровно тогда, когда веса только что инициализированы и потери максимальны. Это одна из причин, по которой трансформеры без разогрева темпа (warmup) расходятся на первых сотнях шагов; сама поправка этот риск убирает лишь частично, потому что $\hat v_t$ на первых шагах оценён всего по нескольким градиентам.

Свойства шага

Масштабная инвариантность

Умножим все градиенты веса на $c>0$. Тогда $m_t$ умножится на $c$, $v_t$ — на $c^2$, $\sqrt{v_t}$ — на $c$, и отношение $\hat m_t/\sqrt{\hat v_t}$ не изменится. Шаг Adam — однородная функция нулевой степени от градиентов (пока $\sqrt{\hat v_t}\gg\epsilon$). Эффективный темп каждого веса $\eta/(\sqrt{\hat v_t}+\epsilon)$ подстраивается сам: большой у весов с мелкими градиентами, маленький — у весов с крупными.

Рисунок 3. Четыре параметра с одинаковым шумным градиентом $c\,(1+0{,}5\xi_t)$, но масштабами $c$ от $10^{-3}$ до $10^{3}$; столбики — длина шага на сотом шаге (шкала логарифмическая). Шаги SGD различаются в $10^6$ раз, шаги Adam одинаковы: $8{,}8\cdot10^{-4}=0{,}88\,\eta$, а эффективный темп $\eta/(\sqrt{\hat v}+\epsilon)$ меняется от $8{,}8\cdot10^{-7}$ до $0{,}88$. Уменьшите общий масштаб $k$ до $10^{-6}$: градиент порядка $10^{-9}$ становится меньше $\epsilon=10^{-8}$, и его шаг падает в 10 раз — так $\epsilon$ отключает адаптацию для совсем малых градиентов.

Шаг и отношение сигнал/шум

Пусть градиент веса стационарен со средним $\mu$ и стандартным отклонением $\sigma$. Тогда $\hat m_t\approx\mu$, $\hat v_t\approx\mu^2+\sigma^2$ (второй момент — это квадрат среднего плюс дисперсия), и средний шаг

$$\frac{|\Delta\theta|}{\eta}\approx\frac{|\mu|}{\sqrt{\mu^2+\sigma^2}}=\frac{\mathrm{SNR}}{\sqrt{1+\mathrm{SNR}^2}},\qquad \mathrm{SNR}=\frac{|\mu|}{\sigma}$$

При чистом сигнале ($\sigma\ll|\mu|$) шаг $\approx\eta$, в сильном шуме ($\sigma\gg|\mu|$) он пропорционален отношению сигнал/шум. Это автоматическое «торможение»: у минимума средний градиент стремится к нулю, а шум остаётся — и Adam сам укорачивает шаги.

Рисунок 4. Средний шаг Adam (в единицах $\eta$) в зависимости от отношения сигнал/шум $\mu/\sigma$: сплошная — формула выше, точки — симуляция 20 000 шагов с разбросом $\pm$ одно стандартное отклонение, штрих — шаг SGD, растущий пропорционально $\mu$. При $\mu/\sigma=0{,}3$ формула даёт $0{,}287$, симуляция — $0{,}298$ с разбросом $\pm0{,}216$. Разброс при слабом сигнале близок к $\sqrt{(1-\beta_1)/(1+\beta_1)}\approx0{,}23$: уменьшите $\beta_1$ — шаги станут хаотичнее.
Насколько большим бывает шаг

Обычно $|\hat m_t|\le\sqrt{\hat v_t}$ и шаг не больше $\eta$, но это не теорема. Если градиент был нулём и вдруг стал $g$, то при больших $t$ получаем $m_t=(1-\beta_1)g$, $v_t=(1-\beta_2)g^2$, и шаг равен $\eta(1-\beta_1)/\sqrt{1-\beta_2}\approx3{,}16\,\eta$ — этот случай разбирают Кингма и Ба. Точная верхняя граница следует из неравенства Коши — Буняковского для взвешенных сумм:

$$\frac{|m_t|}{\sqrt{v_t}}\le\frac{1-\beta_1}{\sqrt{(1-\beta_2)\,(1-\beta_1^2/\beta_2)}}\approx7{,}27$$

(при $\beta_1^2<\beta_2$). Она достигается, когда градиент растёт в $\beta_2/\beta_1\approx1{,}11$ раза за шаг. Значит, шаг Adam ограничен величиной порядка $\eta$ при любых градиентах — встроенная защита от их взрыва.

Особые случаи

  • $\beta_1=\beta_2=0$: $\hat m_t=g_t$, $\sqrt{\hat v_t}=|g_t|$, шаг $\eta\operatorname{sign}(g_t)$ — знаковый градиентный спуск (signSGD).
  • $\beta_1=0$: RMSProp с поправкой смещения.
  • $\epsilon\gg\sqrt{\hat v_t}$: шаг $\approx(\eta/\epsilon)\,\hat m_t$ — SGD с инерцией и темпом $\eta/\epsilon$.
  • Adam — диагональный предобусловливатель: он масштабирует каждую координату отдельно и не умеет поворачивать оси (см. рисунок 6 на странице SGD).

Математика за формулой

Соберём вместе то, что прячется за четырьмя строками.

  • EMA как взвешенное среднее. $\hat m_t=\sum_i w_i g_i$ с весами $w_i=(1-\beta_1)\beta_1^{t-i}/(1-\beta_1^t)$, которые положительны и в сумме дают 1. Вес убывает вдвое примерно каждые $\ln2/(1-\beta)$ шагов: 7 для $\beta_1=0{,}9$ и 693 для $\beta_2=0{,}999$.
  • Эффективное окно $1/(1-\beta)$: $m$ усредняет примерно 10 последних градиентов, $v$ — примерно 1000. Направление реагирует быстро, масштаб — медленно и потому устойчиво.
  • Второй момент — не дисперсия. $v$ оценивает $\mathbb E[g^2]=\mu^2+\sigma^2$, то есть «нецентрированный» размер градиента. Поэтому при чистом сигнале шаг $\approx\eta$, а не бесконечен.
  • Связь с методом Ньютона. Иногда говорят, что $\sqrt v$ аппроксимирует кривизну. Это верно лишь отчасти: $v$ — оценка диагонали эмпирической матрицы Фишера (по градиентам мини-батчей), которая для некоторых потерь у минимума близка к гессиану, но Adam берёт из неё квадратный корень. Надёжнее понимать Adam как нормировку шага, а не как приближение Ньютона.
Сходимость: контрпример и AMSGrad

Исходное доказательство сходимости Adam для выпуклых задач содержало ошибку. Редди, Кейл и Кумар (2018) построили простую выпуклую задачу, на которой Adam сходится к худшей точке: редкий, но большой градиент быстро «забывается» в $v_t$, потому что $\beta_2$-память конечна, и эффективный темп $\eta/\sqrt{\hat v_t}$ может расти.

Их исправление, AMSGrad, использует $\hat v_t=\max(\hat v_{t-1},v_t)$ — эффективный темп больше не растёт, и сходимость в выпуклом случае доказывается. На практике AMSGrad редко заметно лучше Adam (в PyTorch — флаг amsgrad=True). Для невыпуклых задач гарантии получены при убывающем темпе и достаточно большом $\beta_2$.

Гонка оптимизаторов в 3D

Сравним пять методов на двух поверхностях. Первая — плато с ямой: $f=1-e^{-q(x,y)}$, $q=0{,}2(x-1)^2+1{,}2(y-0{,}5)^2$. Вдали от ямы $f\approx1$, градиент крошечный, а сама яма вытянута. Вторая — банан Розенброка.

Рисунок 5. Слева поверхность в 3D (её можно вращать), справа вид сверху; старт можно перетаскивать, «Пуск» проигрывает гонку. В точке старта $(-2{,}4;\,-1{,}5)$ градиент порядка $4\cdot10^{-3}$: SGD с $\eta=0{,}3$ ползёт по плато и достигает $f<0{,}01$ лишь за 246 шагов, Momentum — за 61, RMSProp — за 44, Adam — за 58, AdamW — за 53. Но AdamW останавливается не в минимуме $(1;\,0{,}5)$, а в $(0{,}979;\,0{,}498)$, где $f=9\cdot10^{-5}$: затухание весов тянет к нулю. На Розенброке за 2000 шагов: Momentum — 416 шагов, Adam — 717, AdamW — 818, а SGD и RMSProp не доходят до $f<0{,}01$.
Чему учит гонка

Адаптивные методы выигрывают там, где градиенты плохо масштабированы: на плато их шаг остаётся порядка $\eta$, пока у SGD он пропорционален крошечному градиенту. Но хорошо настроенная инерция может их обогнать (Розенброк), а затухание весов сдвигает решение — это не баг, а регуляризация. Универсального победителя нет.

AdamW: правильное затухание весов

Классическая $L_2$-регуляризация добавляет к потерям $\tfrac\lambda2\|\theta\|^2$, то есть к градиенту — $\lambda\theta$. Для SGD это то же самое, что на каждом шаге умножать веса на $1-\eta\lambda$ (затухание весов, weight decay). Для Adam — нет: слагаемое $\lambda\theta$ попадает в $m$ и $v$ и делится на $\sqrt{\hat v_t}$. Лощилов и Хаттер (2017; ICLR 2019) предложили отделить затухание от градиента:

$$\text{Adam}+L_2:\quad \begin{aligned}g_t&=\nabla L(\theta_{t-1})+\lambda\,\theta_{t-1}\\ \theta_t&=\theta_{t-1}-\eta\,\frac{\hat m_t}{\sqrt{\hat v_t}+\epsilon}\end{aligned}$$
$$\text{AdamW}:\quad \theta_t=\theta_{t-1}-\eta\Bigl(\frac{\hat m_t}{\sqrt{\hat v_t}+\epsilon}+\lambda\,\theta_{t-1}\Bigr)$$

В AdamW моменты считаются по градиенту одних лишь потерь, а все веса затухают с одной и той же скоростью $\eta\lambda$ за шаг. В Adam с $L_2$ затухание каждого веса делится на его $\sqrt{\hat v_t}$: веса с крупными градиентами почти не регуляризуются, а веса без градиента потерь сжимаются с постоянной скоростью $\approx\eta$ за шаг, независимо от $\lambda$.

Рисунок 6. Три веса, стартующие с $\theta=1$; градиент данных у них знакопеременный, $\pm c$ (в среднем ноль), с $c=0$, $0{,}1$ и $10$. Темп $\eta=0{,}01$, $\lambda=0{,}1$, 1000 шагов. AdamW (фиолетовые) уменьшает все три почти одинаково, до $0{,}368$ и $0{,}361$ — около $e^{-\eta\lambda t}=e^{-1}\approx0{,}37$. Adam с $L_2$ (оранжевые) обнуляет вес без градиента (ниже $0{,}01$ уже к шагу 213), сжимает вес с $c=0{,}1$ до $0{,}0003$ и почти не трогает вес с $c=10$ — у того остаётся $0{,}889$.
Вывод

В Adam регуляризация $L_2$ и затухание весов — разные вещи. Если вы хотите затухание, используйте AdamW; в PyTorch torch.optim.Adam(weight_decay=…) реализует именно $L_2$ внутри градиента (если не включён флаг decoupled_weight_decay=True, появившийся в новых версиях). По умолчанию у AdamW $\lambda=0{,}01$; языковые модели обычно обучают с $\lambda=0{,}1$ и не применяют затухание к смещениям и весам нормализаций.

Adam в обучении: разогрев и расписание

Даже адаптивному методу нужен график темпа. Самая распространённая схема для трансформеров — линейный разогрев (warmup) от нуля до $\eta_{\max}$ за первые сотни или тысячи шагов, затем косинусное снижение до $\eta_{\min}$:

$$\eta_t=\eta_{\max}\,\frac{t}{T_w}\qquad(t\le T_w)$$
$$\eta_t=\eta_{\min}+\tfrac12(\eta_{\max}-\eta_{\min})\bigl(1+\cos\pi p\bigr),\quad p=\frac{t-T_w}{T-T_w}$$
Рисунок 7. Adam на шумной квадратичной задаче (10 параметров с кривизнами от $0{,}01$ до 1, шум градиента $0{,}05$), $\eta_{\max}=0{,}05$, 2000 шагов. Вверху — темп, внизу — потери (логарифмическая шкала). С постоянным $\eta$ потери застревают на шумовом уровне $5{,}6\cdot10^{-3}$ (среднее за последние 100 шагов); с разогревом на 200 шагов и косинусом до $0{,}1\,\eta_{\max}$ — $7{,}1\cdot10^{-4}$, в $7{,}9$ раза ниже. Разогрев здесь лишь немного задерживает старт; его польза проявляется в глубоких сетях.

Почему снижение помогает: из раздела о свойствах шаг Adam у минимума пропорционален $\eta$, умноженному на отношение сигнал/шум. Чем меньше $\eta$, тем меньше облако, в котором веса дрожат вокруг минимума.

Почему разогрев нужен: на первых шагах $\hat v_t$ оценён по нескольким градиентам и сильно шумит, а веса только что инициализированы, и градиенты особенно велики. Лю и соавторы (RAdam, 2019) показали, что дисперсия эффективного темпа $\eta/\sqrt{\hat v_t}$ в начале обучения велика, и предложили выпрямлять её аналитически; разогрев решает ту же проблему проще.

Типичный рецепт для языковых моделей

AdamW с $\beta_1=0{,}9$, $\beta_2=0{,}95$, $\lambda=0{,}1$, обрезка градиента по норме 1, разогрев на ~2000 шагов и косинус до 10% от $\eta_{\max}$ — так, например, описано обучение LLaMA. Меньшее $\beta_2$ (окно 20 шагов вместо 1000) быстрее реагирует на всплески градиента и делает обучение больших моделей устойчивее.

В коде: с нуля и в PyTorch

Реализация с нуля — двадцать строк. Флаг decoupled переключает затухание весов между AdamW и $L_2$ внутри градиента.

pythonimport numpy as np

class Adam:
    """Adam; при weight_decay > 0 и decoupled=True — AdamW. Как в torch.optim."""
    def __init__(self, lr=1e-3, betas=(0.9, 0.999), eps=1e-8, weight_decay=0.0, decoupled=True):
        self.lr, (self.b1, self.b2), self.eps = lr, betas, eps
        self.wd, self.decoupled = weight_decay, decoupled
        self.m = self.v = None
        self.t = 0

    def step(self, theta, g):
        if self.m is None:
            self.m, self.v = np.zeros_like(theta), np.zeros_like(theta)
        if self.wd and not self.decoupled:
            g = g + self.wd * theta                        # L2 внутри градиента (Adam)
        self.t += 1
        self.m = self.b1 * self.m + (1 - self.b1) * g      # инерция
        self.v = self.b2 * self.v + (1 - self.b2) * g * g  # масштаб
        m_hat = self.m / (1 - self.b1 ** self.t)           # поправка смещения
        v_hat = self.v / (1 - self.b2 ** self.t)
        if self.wd and self.decoupled:
            theta = theta - self.lr * self.wd * theta      # AdamW: затухание отдельно
        return theta - self.lr * m_hat / (np.sqrt(v_hat) + self.eps)

# Три шага из таблицы: g = 0.5, −0.2, 0.4 (lr = 1, чтобы шаг был в единицах η)
opt, th = Adam(lr=1.0), np.zeros(1)
for g in [0.5, -0.2, 0.4]:
    new = opt.step(th, np.array([g]))
    print(f"t={opt.t}: m̂={opt.m[0] / (1 - 0.9 ** opt.t):.4f}  шаг/η={th[0] - new[0]:.4f}")
    th = new
# t=1: m̂=0.5000  шаг/η=1.0000
# t=2: m̂=0.1316  шаг/η=0.3456
# t=3: m̂=0.2306  шаг/η=0.5955

# Масштабная инвариантность: градиенты ×1000 дают те же шаги
rng = np.random.default_rng(0)
a, b, x, y = Adam(lr=0.01), Adam(lr=0.01), np.zeros(1), np.zeros(1)
for t in range(100):
    g = rng.normal(1.0, 0.5, size=1)
    x, y = a.step(x, g), b.step(y, 1000 * g)
print(x, y, abs(x - y))   # [-0.95081564] [-0.95081565] [8.64890637e-09] — разница только из-за ε

Выход совпадает с таблицей из раздела «Формула по косточкам», а torch.optim.Adam на том же первом шаге сдвигает вес ровно на $\eta$. Типичный цикл обучения с AdamW, раздельными группами затухания, разогревом и косинусом:

pythonimport math
import torch
import torch.nn.functional as F

decay = [p for p in model.parameters() if p.ndim >= 2]     # матрицы весов
no_decay = [p for p in model.parameters() if p.ndim < 2]   # смещения и нормализации
opt = torch.optim.AdamW([{"params": decay, "weight_decay": 0.1},
                         {"params": no_decay, "weight_decay": 0.0}],
                        lr=3e-4, betas=(0.9, 0.95), eps=1e-8)

total, warmup = 10_000, 500
def lr_factor(step):                       # множитель к lr: разогрев, затем косинус до 10%
    if step < warmup:
        return (step + 1) / warmup
    p = (step - warmup) / (total - warmup)
    return 0.1 + 0.9 * 0.5 * (1 + math.cos(math.pi * p))
sched = torch.optim.lr_scheduler.LambdaLR(opt, lr_factor)

for step, (x, y) in zip(range(total), loader):
    loss = F.mse_loss(model(x), y)
    opt.zero_grad(set_to_none=True)
    loss.backward()
    torch.nn.utils.clip_grad_norm_(model.parameters(), 1.0)  # обрезка градиента
    opt.step()
    sched.step()                          # расписание — после каждого шага
Память

Adam хранит $m$ и $v$ — два числа на каждый параметр. Для модели на 7 млрд параметров это $2\cdot7\cdot10^9\cdot4$ байта $=56$ ГБ в float32 только под состояние оптимизатора. Отсюда 8-битные оптимизаторы, шардирование состояния (ZeRO) и экономные варианты вроде Adafactor.

Гиперпараметры и практика

ПараметрПо умолчаниюТипичный диапазон и смысл
$\eta$$10^{-3}$$10^{-4}$–$3\cdot10^{-3}$; трансформеры — около $3\cdot10^{-4}$ и меньше с ростом модели; дообучение — $10^{-5}$–$5\cdot10^{-5}$
$\beta_1$$0{,}9$почти никогда не меняют; окно направления 10 шагов
$\beta_2$$0{,}999$$0{,}95$–$0{,}98$ для больших трансформеров: быстрее реагирует на всплески
$\epsilon$$10^{-8}$больше ($10^{-6}$ и выше) — при низкой точности или нестабильности; слишком большой $\epsilon$ превращает Adam в SGD с инерцией
$\lambda$ (AdamW)$0{,}01$$0{,}01$–$0{,}1$; не применять к смещениям и нормализациям
разогрев—от сотен до нескольких тысяч шагов, 1–5% обучения
  • Начните с умолчаний. $\eta=10^{-3}$ (или $3\cdot10^{-4}$ для трансформеров), $\beta=(0{,}9;\,0{,}999)$, AdamW с $\lambda=0{,}01$. Подбирать в первую очередь стоит $\eta$, затем $\lambda$ и расписание.
  • Обрезка градиента по норме ($1{,}0$) почти бесплатна и спасает от редких выбросов.
  • Сохраняйте состояние оптимизатора (opt.state_dict()) вместе с весами: при продолжении обучения с нулевыми $m$ и $v$ первые шаги снова будут «холодными».
  • Когда SGD лучше. На задачах компьютерного зрения хорошо настроенный SGD с инерцией часто даёт лучшее обобщение (Уилсон и др., 2017). Для трансформеров, рекомендательных систем с эмбеддингами и задач с разреженными градиентами выигрывает Adam.

Частые ошибки

  1. Adam(weight_decay=…) вместо AdamW. Это $L_2$ внутри градиента: затухание делится на $\sqrt{\hat v_t}$ и работает неравномерно (рисунок 6).
  2. Темп от SGD. $\eta=0{,}1$ хорош для SGD с инерцией, но для Adam это шаг около $0{,}1$ в единицах весов на каждом шаге — обучение почти наверняка разойдётся.
  3. Затухание для всех параметров. Смещения и веса LayerNorm/BatchNorm обычно исключают из weight decay: их сжатие к нулю вредит, а не регуляризует.
  4. Неудачный $\epsilon$. Слишком маленький в float16 округляется до нуля, слишком большой ($\gtrsim\sqrt{\hat v}$) молча отключает адаптацию — шаг становится $(\eta/\epsilon)\hat m$.
  5. Без разогрева на трансформере. Большой $\eta$ с первых шагов при шумной оценке $\hat v_t$ часто приводит к взрыву потерь в начале обучения.
  6. Расписание не на том шаге. scheduler.step() раз за эпоху там, где расписание рассчитано на шаги (или наоборот) растягивает или сжимает разогрев в сотни раз.
  • SGD, Momentum и RMSProp: числитель Adam — EMA-инерция, знаменатель — $\sqrt v$ из RMSProp; там же условие устойчивости и число обусловленности.
  • Кросс-энтропия и softmax: потеря, которую AdamW минимизирует в языковых моделях; её градиент по логитам $s-y$ и есть $g_t$ последнего слоя.
  • MSE и Huber: от масштаба целевой переменной зависит масштаб градиента — Adam к нему нечувствителен, SGD — нет.
  • GELU и SiLU: активации трансформеров, которые обучаются AdamW.

Итоги

1) Adam хранит два экспоненциальных средних: $m_t$ градиента (направление, окно ~10 шагов) и $v_t$ его квадрата (масштаб, окно ~1000 шагов). 2) Оба стартуют с нуля и занижены в $1-\beta^t$ раз; поправка делает их несмещёнными, без неё ранние шаги завышены — в $3{,}16$ раза на первом шаге. 3) Шаг $\eta\,\hat m_t/(\sqrt{\hat v_t}+\epsilon)$ не зависит от масштаба градиента, порядка $\eta$ при чистом сигнале и пропорционален отношению сигнал/шум в шуме. 4) AdamW отделяет затухание весов от градиента — все веса затухают равномерно. 5) На практике: AdamW, разогрев, косинус, обрезка градиента; $\eta$ — главный гиперпараметр.

Проверь себя

1. Первый шаг Adam с поправкой смещения, $g_1=-3$. Как изменится вес?
2. Сколько последних шагов примерно «помнит» $v_t$ при $\beta_2=0{,}999$?
3. Все градиенты модели умножили на 100 (например, потерю умножили на 100). Что станет с шагами Adam?
4. Чем AdamW отличается от Adam с $L_2$-регуляризацией?
5. Во сколько раз завышен первый шаг Adam без поправки смещения при $\beta_1=0{,}9$, $\beta_2=0{,}999$?