Зачем нужен 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$.
Уберите шум ($\sigma=0$): шаг равен ровно $+1$ с первого шага и до смены знака, а примерно через 50 шагов после неё — почти ровно $-1$: при чистом сигнале Adam ходит шагами $\eta$, как знаковый метод. Увеличьте шум — шаг сжимается. Уменьшите $\beta_1$ до нуля — $\hat m_t$ превращается в сам градиент, и шаг $g_t/\sqrt{\hat v_t}$ скачет вместе с шумом.
Формула по косточкам
- $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$ | без поправки |
|---|---|---|---|---|---|---|---|
| 1 | 0,5 | 0,05 | 0,00025 | 0,5 | 0,5 | 1,000 | 3,162 |
| 2 | −0,2 | 0,025 | 0,00029 | 0,1316 | 0,3807 | 0,346 | 1,469 |
| 3 | 0,4 | 0,0625 | 0,00045 | 0,2306 | 0,3873 | 0,596 | 2,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$:
Это взвешенная сумма градиентов, но сумма весов равна не 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$. Тогда по линейности ожидания
Деление на $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$. Поэтому важнее всего она для второго момента.
Без поправки ранние шаги 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)$ подстраивается сам: большой у весов с мелкими градиентами, маленький — у весов с крупными.
Шаг и отношение сигнал/шум
Пусть градиент веса стационарен со средним $\mu$ и стандартным отклонением $\sigma$. Тогда $\hat m_t\approx\mu$, $\hat v_t\approx\mu^2+\sigma^2$ (второй момент — это квадрат среднего плюс дисперсия), и средний шаг
При чистом сигнале ($\sigma\ll|\mu|$) шаг $\approx\eta$, в сильном шуме ($\sigma\gg|\mu|$) он пропорционален отношению сигнал/шум. Это автоматическое «торможение»: у минимума средний градиент стремится к нулю, а шум остаётся — и Adam сам укорачивает шаги.
Насколько большим бывает шаг
Обычно $|\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$ — этот случай разбирают Кингма и Ба. Точная верхняя граница следует из неравенства Коши — Буняковского для взвешенных сумм:
(при $\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$, градиент крошечный, а сама яма вытянута. Вторая — банан Розенброка.
Адаптивные методы выигрывают там, где градиенты плохо масштабированы: на плато их шаг остаётся порядка $\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) предложили отделить затухание от градиента:
В AdamW моменты считаются по градиенту одних лишь потерь, а все веса затухают с одной и той же скоростью $\eta\lambda$ за шаг. В Adam с $L_2$ затухание каждого веса делится на его $\sqrt{\hat v_t}$: веса с крупными градиентами почти не регуляризуются, а веса без градиента потерь сжимаются с постоянной скоростью $\approx\eta$ за шаг, независимо от $\lambda$.
В 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}$:
Почему снижение помогает: из раздела о свойствах шаг 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.
Частые ошибки
Adam(weight_decay=…)вместоAdamW. Это $L_2$ внутри градиента: затухание делится на $\sqrt{\hat v_t}$ и работает неравномерно (рисунок 6).- Темп от SGD. $\eta=0{,}1$ хорош для SGD с инерцией, но для Adam это шаг около $0{,}1$ в единицах весов на каждом шаге — обучение почти наверняка разойдётся.
- Затухание для всех параметров. Смещения и веса LayerNorm/BatchNorm обычно исключают из weight decay: их сжатие к нулю вредит, а не регуляризует.
- Неудачный $\epsilon$. Слишком маленький в float16 округляется до нуля, слишком большой ($\gtrsim\sqrt{\hat v}$) молча отключает адаптацию — шаг становится $(\eta/\epsilon)\hat m$.
- Без разогрева на трансформере. Большой $\eta$ с первых шагов при шумной оценке $\hat v_t$ часто приводит к взрыву потерь в начале обучения.
- Расписание не на том шаге.
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$ — главный гиперпараметр.