Зачем это нужно
Обучение нейросети — это поиск минимума функции потерь $L(\theta)$ по миллионам и миллиардам весов $\theta$. Готовой формулы для минимума нет: потери зависят от весов слишком сложно. Остаётся идти к минимуму шагами, и единственный ориентир на каждом шаге — градиент $\nabla L$: вектор, указывающий направление самого быстрого роста потерь. Шагаем в обратную сторону.
Эта страница — о трёх ступенях одного метода:
- SGD (stochastic gradient descent, стохастический градиентный спуск) — шаг против градиента, посчитанного на маленькой случайной пачке примеров, мини-батче;
- Momentum (инерция, метод тяжёлого шарика) — шаг против накопленной суммы прошлых градиентов: инерция гасит зигзаги и разгоняет спуск вдоль пологих направлений;
- RMSProp — шаг, поделённый на среднеквадратичную величину недавних градиентов: у каждого веса свой масштаб шага.
Сложите вторую и третью ступень, добавьте поправку смещения — получится Adam. А SGD с инерцией по-прежнему обучает многие свёрточные сети для изображений и нередко даёт на них лучшее обобщение, чем адаптивные методы.
$\theta_t=\theta_{t-1}-\eta g_t$. Шаг устойчив, только если $\eta L<2$, где $L$ — наибольшая кривизна потерь. Вытянутая долина (большое число обусловленности $\kappa$) заставляет SGD ползти; инерция $u_t=\beta u_{t-1}+g_t$ при удачных $\eta$ и $\beta$ сокращает путь примерно с $\kappa$ до $\sqrt\kappa$ шагов; RMSProp делит шаг на $\sqrt{v_t}$ и выравнивает масштабы разных весов.
Интуиция: шаг под гору
Представьте, что вы спускаетесь с холма в густом тумане. Видно только склон под ногами: куда и насколько круто он идёт вниз. Разумная стратегия — шагнуть в сторону самого крутого спуска, осмотреться и повторить. Длина шага задаётся темпом обучения (learning rate) $\eta$.
Короткие шаги надёжны, но медленны. Слишком длинный шаг перепрыгивает дно ямы и попадает на противоположный склон — и если там он выше, следующий шаг унесёт ещё дальше. Посмотрите, как это выглядит на простейшей потере $f(\theta)=L\theta^2/2$: её градиент $f'(\theta)=L\theta$, а кривизна (вторая производная) равна $L$.
Всё решает одно число $\eta L$. При $\eta L<1$ спуск монотонный, при $1<\eta L<2$ точка прыгает через минимум, но приближается к нему, при $\eta L>2$ улетает. Шаг $\eta=1/L$ попадает в минимум сразу — но только пока кривизна одна во всех направлениях.
Формула по косточкам
- $\theta_{t-1}$
- текущие веса — вектор всех параметров модели;
- $g_t$
- стохастический градиент: среднее градиентов потерь $\ell$ по мини-батчу $B_t$ из $B$ случайных примеров;
- $\eta$
- темп обучения, положительное число, обычно от $10^{-4}$ до $10^{-1}$;
- $-\eta\,g_t$
- шаг: против градиента, длиной $\eta\|g_t\|$ — тем длиннее, чем круче склон.
Почему это работает. Разложим потери в ряд Тейлора около текущей точки: $L(\theta-\eta g)\approx L(\theta)-\eta\,\|g\|^2$. Пока шаг мал, потери уменьшаются примерно на $\eta\|g\|^2$, и это самое быстрое убывание среди шагов той же длины: скалярное произведение $\langle\nabla L,\,d\rangle$ при фиксированной длине $\|d\|$ минимально, когда $d$ направлен против градиента.
$f(\theta)=\theta^2$, $\theta_0=3$, $\eta=0{,}1$. Градиент $g_1=2\cdot3=6$, шаг $-0{,}1\cdot6=-0{,}6$, новое значение $\theta_1=2{,}4$, потери падают с $9$ до $5{,}76$. Каждый следующий шаг снова умножает $\theta$ на $1-2\eta=0{,}8$.
Насколько большим может быть шаг: лемма о спуске
Пусть градиент $f$ липшицев с константой $L$: $\|\nabla f(a)-\nabla f(b)\|\le L\|a-b\|$ (для дважды дифференцируемой $f$ это значит, что собственные значения гессиана по модулю не больше $L$). Тогда $f$ лежит под параболой, касающейся её в текущей точке, и для шага $\theta-\eta g$ с $g=\nabla f(\theta)$:
Множитель $1-L\eta/2$ положителен при $\eta<2/L$ — это та самая граница устойчивости с рисунка 1. Гарантированное убывание максимально при $\eta=1/L$: тогда $f$ уменьшается хотя бы на $\|g\|^2/(2L)$.
Для квадратичной функции $f=\tfrac12\theta^\top H\theta$ шаг — линейное отображение $\theta_t=(I-\eta H)\,\theta_{t-1}$. В собственном базисе $H$ каждая координата умножается на $1-\eta\lambda_i$, и спуск сходится тогда и только тогда, когда $|1-\eta\lambda_i|<1$ для всех $i$, то есть $0<\eta<2/\lambda_{\max}$ (при положительно определённой $H$).
Число обусловленности: почему спуск бывает медленным
Если кривизны по разным направлениям различаются, шаг обязан подстроиться под самую крутую: $\eta<2/\lambda_{\max}$. Тогда вдоль самого пологого направления координата за шаг уменьшается лишь на долю $\eta\lambda_{\min}<2/\kappa$, где $\kappa=\lambda_{\max}/\lambda_{\min}$ — число обусловленности (condition number). Даже при лучшем постоянном шаге $\eta=2/(\lambda_{\max}+\lambda_{\min})$ ошибка за шаг уменьшается лишь в $(\kappa-1)/(\kappa+1)$ раз:
При $\kappa=100$ множитель равен $0{,}980$, и чтобы уменьшить ошибку в тысячу раз, нужно около 345 шагов. Грубое правило: SGD тратит порядка $\kappa$ шагов на каждый порядок точности.
| Метод | Шаг | Что хранит | Гиперпараметры |
|---|---|---|---|
| SGD | $\theta_t=\theta_{t-1}-\eta g_t$ | ничего | $\eta$ |
| Momentum | $u_t=\beta u_{t-1}+g_t$, $\theta_t=\theta_{t-1}-\eta u_t$ | $u$: одно число на вес | $\eta$, $\beta\approx0{,}9$ |
| RMSProp | $v_t=\beta v_{t-1}+(1-\beta)g_t^2$, $\theta_t=\theta_{t-1}-\eta\,\dfrac{g_t}{\sqrt{v_t}+\epsilon}$ | $v$: одно число на вес | $\eta$, $\beta=0{,}9$–$0{,}99$, $\epsilon$ |
Стохастичность: мини-батчи и шум
Полный градиент — среднее по всем $N$ примерам обучающей выборки. При миллионах примеров один такой шаг стоил бы прохода по всем данным. SGD берёт случайный мини-батч из $B$ примеров. Его градиент — несмещённая оценка полного: в среднем совпадает с $\nabla L$, но каждый раз отклоняется на случайный шум.
- $\Sigma$
- ковариация градиентов отдельных примеров в текущей точке (с делением на $N$);
- $\frac{N-B}{N-1}$
- поправка за выбор без возвращения: при $B=N$ шум исчезает.
Дисперсия шума падает как $1/B$, а цена шага растёт как $B$. Поэтому за одну эпоху (один проход по данным) маленькие батчи делают много неточных шагов, а большие — мало точных. На рисунке — линейная регрессия $y\approx wx+b$ на 200 точках с потерей $L=\frac1{2N}\sum(wx_i+b-y_i)^2$: это квадратичная «миска» над плоскостью весов $(w;\,b)$.
У минимума полный градиент равен нулю, а стохастический — нет. При постоянном $\eta$ SGD не останавливается, а дрожит в облаке, размер которого растёт как $\sqrt\eta$; поэтому к концу обучения темп снижают. Зато тот же шум помогает уходить с седловых точек и из узких минимумов; есть наблюдения, что он смещает обучение к более пологим минимумам, которые лучше обобщают.
Правило линейного масштабирования батча
Если увеличить батч в $k$ раз, дисперсия шума падает в $k$ раз, но шагов за эпоху становится в $k$ раз меньше. Гоял и соавторы (2017) предложили одновременно увеличивать $\eta$ в $k$ раз: так ResNet-50 обучили на ImageNet с батчем 8192 за час без потери точности. Правило работает до некоторого «критического» размера батча и требует разогрева (warmup) — плавного роста $\eta$ на первых эпохах.
Momentum: инерция тяжёлого шарика
Градиентный спуск — это точка без массы: в каждый момент она идёт ровно туда, куда показывает склон. Б. Т. Поляк (1964) предложил дать ей массу. Тяжёлый шарик катится, накапливая скорость, и не реагирует на каждую кочку. В формуле скорость — вектор $u$:
- $u_t$
- накопленное направление (буфер инерции), $u_0=0$;
- $\beta$
- коэффициент инерции, $0\le\beta<1$, обычно $0{,}9$; при $\beta=0$ — обычный SGD.
Раскроем рекурсию: $u_t=g_t+\beta g_{t-1}+\beta^2 g_{t-2}+\dots$ — взвешенная сумма всех прошлых градиентов с экспоненциально убывающими весами. Если градиенты из шага в шаг указывают в одну сторону, они складываются, и шаг растёт до $\eta/(1-\beta)$ — в 10 раз при $\beta=0{,}9$. Если знак скачет (зигзаг поперёк оврага), слагаемые гасят друг друга.
PyTorch пишет $u_t=\beta u_{t-1}+g_t$, как здесь. Часто встречается и экспоненциальное скользящее среднее (exponential moving average, EMA) $m_t=\beta m_{t-1}+(1-\beta)g_t$: оно отличается постоянным множителем $1-\beta$, который переходит в $\eta$. Именно EMA стоит в числителе Adam. Переключите форму на рисунке: EMA стартует с $0{,}1$ вместо 1 и лишь постепенно доходит до истинного значения — эта недооценка на первых шагах и есть причина поправки смещения в Adam.
Почему инерция ускоряет: теория на квадратичной функции
Возьмём одно направление с кривизной $L$ и обозначим $h=\eta L$. Подставим $g_t=L\theta_{t-1}$ и исключим $u$ (из второй формулы $\eta u_{t-1}=\theta_{t-2}-\theta_{t-1}$). Получится линейная рекуррентность второго порядка:
Её поведение определяют корни характеристического уравнения $\lambda^2-(1+\beta-h)\lambda+\beta=0$. Скорость сходимости — наибольший модуль корня $\rho$: ошибка убывает примерно как $\rho^t$.
Вывод: область устойчивости и оптимальная инерция
Корни комплексные, когда $(1+\beta-h)^2<4\beta$, то есть при $(1-\sqrt\beta)^2<h<(1+\sqrt\beta)^2$. Произведение корней равно $\beta$, поэтому $|\lambda_1|=|\lambda_2|=\sqrt\beta$: $\theta_t$ колеблется и затухает в $\sqrt\beta$ раз за шаг, независимо от $h$. Вне этой полосы корни вещественные. Вместе оба случая дают точную область устойчивости (условия Шура — Кона для квадратного трёхчлена):
— инерция почти вдвое расширяет допустимый шаг (у SGD $\eta L<2$).
Пусть кривизны заполняют отрезок $[\mu;\,L]$, $\kappa=L/\mu$. Подберём $\eta$ и $\beta$ так, чтобы оба края попали на границы комплексной полосы: $(1-\sqrt\beta)^2=\eta\mu$ и $(1+\sqrt\beta)^2=\eta L$. Тогда все направления затухают одинаково быстро:
Сравните с лучшим SGD, $\rho=(\kappa-1)/(\kappa+1)$. При $\kappa=100$ это $0{,}818$ против $0{,}980$: около 35 шагов вместо 345, чтобы уменьшить ошибку в тысячу раз. Число шагов растёт как $\sqrt\kappa$, а не как $\kappa$ — в этом и состоит ускорение тяжёлого шарика.
Нестеров: градиент там, куда несёт инерция
Ю. Е. Нестеров (1983) предложил считать градиент не в текущей точке, а там, куда шарик всё равно унесёт инерция. В форме PyTorch (nesterov=True) это поправка к шагу:
Характеристическое уравнение становится $\lambda^2-(1+\beta)(1-h)\lambda+\beta(1-h)=0$. На пологих направлениях колебания затухают быстрее ($\rho=\sqrt{\beta(1-h)}$ вместо $\sqrt\beta$), но область устойчивости уже: $\eta L<2(1+\beta)/(1+2\beta)$, при $\beta=0{,}9$ — всего $1{,}36$. Переключите рисунок 4 на «Нестеров», чтобы увидеть обе особенности.
RMSProp: свой шаг для каждого веса
У SGD и Momentum один темп $\eta$ на все веса. Но градиенты разных весов могут отличаться на порядки: эмбеддинги редких слов получают редкие крупные толчки, смещения последнего слоя — постоянные мелкие. Общий $\eta$ для одних слишком велик, для других слишком мал. RMSProp (Дж. Хинтон, лекции курса на Coursera, 2012) делит шаг каждого веса на его типичный размер градиента:
- $g_t^2$
- поэлементный квадрат: у каждого веса свой $v$;
- $v_t$
- экспоненциальное скользящее среднее квадрата градиента — оценка $\mathbb E[g^2]$ за последние $\sim1/(1-\beta)$ шагов, $v_0=0$;
- $\sqrt{v_t}$
- среднеквадратичное значение (root mean square, RMS) градиента — отсюда название;
- $\epsilon$
- маленькая добавка ($10^{-8}$) против деления на ноль.
Деление на $\sqrt{v_t}$ делает шаг безразмерным: если умножить все градиенты веса на 1000, то и $\sqrt v$ вырастет в 1000 раз, а шаг не изменится. Типичный шаг становится порядка $\eta$ — темп теперь задаёт длину шага прямо в единицах весов.
Из $v_t\ge(1-\beta)g_t^2$ следует $|g_t|/\sqrt{v_t}\le1/\sqrt{1-\beta}$. Шаг RMSProp никогда не превышает $\eta/\sqrt{1-\beta}$: $3{,}16\,\eta$ при $\beta=0{,}9$ и $10\,\eta$ при $\beta=0{,}99$. Это защита от взрыва градиента, которой у SGD нет.
Откуда RMSProp: AdaGrad и Rprop
Предшественник — AdaGrad (Дучи, Хазан, Сингер, 2011): $v_t=v_{t-1}+g_t^2$, сумма вместо среднего. Для выпуклых задач с редкими признаками он хорош, но в нейросетях $v$ только растёт, и шаг $\eta/\sqrt{v_t}$ неизбежно затухает до нуля. RMSProp заменил сумму экспоненциальным средним с конечной памятью.
Другой корень — Rprop (Ридмиллер и Браун, 1993), который использует только знак градиента. RMSProp можно читать как его вариант для мини-батчей: при $\beta=0$ получается $v_t=g_t^2$ и шаг $\eta\operatorname{sign}(g_t)$.
Важно: RMSProp масштабирует оси координат, но не поворачивает их. Это диагональное предобусловливание. Если овраг идёт по диагонали между весами, деление на $\sqrt v$ помогает слабо — это видно на рисунке 6 при повороте на 45°.
Траектории в 2D и 3D
Сравним три метода на одной задаче — вытянутой квадратичной долине $f=\tfrac12(a^2+\kappa b^2)$, где $a$ и $b$ — координаты вдоль и поперёк оврага: кривизна поперёк в $\kappa$ раз больше, чем вдоль. Старт — в точке $a=-3{,}4$, $b=0{,}9$, где $f=46{,}3$.
Вторая классика — функция Розенброка $f=(1-x)^2+100(y-x^2)^2$: узкий изогнутый «банан» с минимумом в $(1;\,1)$. Кривизна здесь меняется от точки к точке, и никакой постоянный шаг не идеален.
Двумерные картинки полезны, но обманчивы: у настоящей сети миллионы измерений, большинство критических точек там — седловые, а не локальные минимумы, и поверхность потерь сама зависит от мини-батча. Используйте рисунки для интуиции о механизмах (зигзаг, инерция, масштаб), а не для выбора оптимизатора.
В коде: с нуля и в PyTorch
Все три метода помещаются в два маленьких класса. Обратите внимание: состояние ($u$ или $v$) — массив того же размера, что и веса, поэтому Momentum и RMSProp удваивают память под параметры, а Adam — утраивает.
pythonimport numpy as np
class SGD:
"""SGD с инерцией (momentum) и вариантом Нестерова — как в torch.optim.SGD."""
def __init__(self, lr=0.01, momentum=0.0, nesterov=False):
self.lr, self.beta, self.nesterov, self.u = lr, momentum, nesterov, None
def step(self, theta, g):
if self.beta == 0:
return theta - self.lr * g
self.u = g.copy() if self.u is None else self.beta * self.u + g
d = g + self.beta * self.u if self.nesterov else self.u
return theta - self.lr * d
class RMSProp:
"""RMSProp: свой масштаб шага для каждого веса."""
def __init__(self, lr=0.01, beta=0.99, eps=1e-8):
self.lr, self.beta, self.eps, self.v = lr, beta, eps, None
def step(self, theta, g):
if self.v is None:
self.v = np.zeros_like(theta)
self.v = self.beta * self.v + (1 - self.beta) * g * g
return theta - self.lr * g / (np.sqrt(self.v) + self.eps)
# Вытянутая долина f = (a² + 100·b²)/2, как на рисунке 6
H = np.diag([1.0, 100.0])
f = lambda th: 0.5 * th @ H @ th
grad = lambda th: H @ th
for name, opt in [("SGD", SGD(lr=0.018)),
("Momentum", SGD(lr=0.018, momentum=0.9)),
("Nesterov", SGD(lr=0.018, momentum=0.9, nesterov=True)),
("RMSProp", RMSProp(lr=0.1, beta=0.9))]:
th = np.array([-3.4, 0.9])
for t in range(100):
th = opt.step(th, grad(th))
print(f"{name:9s} f = {f(th):.2e}")
# SGD f = 1.53e-01
# Momentum f = 2.23e-04
# Nesterov f = 3.14e+57 ← расходится: ηλ_max = 1.8 > 2(1+β)/(1+2β) ≈ 1.36
# RMSProp f = 2.60e-02
Числа в комментариях совпадают с рисунком 6, а torch.optim.SGD и torch.optim.RMSprop на той же задаче дают те же значения. Нестеров с тем же $\eta$ расходится — ровно по условию устойчивости из раздела про инерцию.
pythonimport torch
import torch.nn.functional as F
model = torch.nn.Linear(10, 1)
# SGD с инерцией и затуханием весов — классика для свёрточных сетей
opt = torch.optim.SGD(model.parameters(), lr=0.1, momentum=0.9,
nesterov=True, weight_decay=1e-4)
# RMSProp: коэффициент сглаживания в PyTorch называется alpha (по умолчанию 0.99)
# opt = torch.optim.RMSprop(model.parameters(), lr=1e-3, alpha=0.99, eps=1e-8)
for x, y in loader: # мини-батчи из DataLoader
opt.zero_grad() # градиенты накапливаются — обнуляем
loss = F.mse_loss(model(x), y)
loss.backward() # g_t в param.grad
opt.step() # θ ← θ − η·(…)
PyTorch хранит сумму сырых градиентов и умножает её на текущий $\eta$: $\theta\leftarrow\theta-\eta_t u_t$. Keras и классическая статья Сутскевера и др. (2013) хранят уже умноженную на темп скорость: $v_t=\beta v_{t-1}-\eta_t g_t$, $\theta\leftarrow\theta+v_t$. При постоянном $\eta$ формы эквивалентны, при расписании — нет: в PyTorch новый $\eta$ сразу применяется ко всему накопленному буферу.
Гиперпараметры и практика
| Параметр | Типичные значения | Как выбирать |
|---|---|---|
| $\eta$ (SGD с инерцией) | $0{,}01$–$0{,}1$ | наибольший, при котором потери ещё стабильно падают; ResNet: $0{,}1$ при батче 256 |
| $\beta$ (momentum) | $0{,}9$, реже $0{,}95$–$0{,}99$ | больше $\beta$ — длиннее память и сильнее разгон, но выше риск раскачки |
| $\beta$ RMSProp (alpha) | $0{,}9$–$0{,}99$ | окно усреднения $1/(1-\beta)$: от 10 до 100 шагов |
| $\eta$ (RMSProp) | $10^{-4}$–$10^{-3}$ | шаг порядка $\eta$ в единицах весов |
| $B$ (батч) | 32–512 | при росте $B$ в $k$ раз — $\eta$ в $k$ раз, с разогревом |
| weight decay | $10^{-4}$–$5\cdot10^{-4}$ | затухание весов; для SGD совпадает с $L_2$-регуляризацией |
- Расписание темпа. Ступенчатое (умножение на $0{,}1$ на 30-й и 60-й эпохах из 90 — классика ResNet), косинусное или «один цикл». Постоянный $\eta$ оставляет шумовое облако у минимума.
- Разогрев (warmup). Первые сотни–тысячи шагов $\eta$ растёт линейно от малого значения — особенно важно при большом батче.
- Обрезка градиента (gradient clipping) по норме защищает от редких огромных градиентов в RNN и трансформерах.
- Поиск темпа (LR range test, Л. Смит): короткая прогонка с экспоненциально растущим $\eta$; берут значение заметно меньше того, где потери начинают расти.
- Нормализация входов, батч-нормализация и разумная инициализация уменьшают число обусловленности и позволяют брать больший $\eta$.
Частые ошибки
- Забытый
zero_grad(). В PyTorch градиенты накапливаются в.grad; без обнуления $g_t$ превращается в сумму всех прошлых градиентов — это не инерция, а ошибка. - Слишком большой темп: потери растут или становятся NaN. По рисунку 1 нужно $\eta<2/\lambda_{\max}$; первым делом уменьшите $\eta$ в 3–10 раз.
- Перенос $\eta$ между оптимизаторами. С инерцией $\beta=0{,}9$ установившийся шаг в 10 раз больше, чем у SGD с тем же $\eta$. У RMSProp и Adam $\eta$ вообще в других единицах: $10^{-3}$ для них нормально, для SGD обычно мало.
- Смена батча без смены темпа. Увеличили $B$ в 8 раз и оставили $\eta$ — сеть учится медленнее за эпоху; уменьшили $B$ — шум может раскачать обучение.
- $\epsilon=10^{-8}$ в низкой точности. В float16 это число округляется до нуля, и при $v\approx0$ шаг RMSProp взрывается; в смешанной точности $v$ держат в float32 или берут $\epsilon$ больше.
- Постоянный темп до конца обучения. Без снижения $\eta$ SGD дрожит у минимума, и последние эпохи почти ничего не дают.
Связи с другими формулами
- Adam: числитель — EMA-инерция, знаменатель — $\sqrt{v}$ из RMSProp, плюс поправка смещения.
- MSE: её градиент $\frac2n\sum_i(\hat y_i-y_i)\,x_i$ — с точностью до множителя 2 тот же $g_t$, что в линейной регрессии на рисунке 2 (там потеря — половина MSE).
- BCE и кросс-энтропия: градиент по логитам $\hat y-y$ — простейший сигнал для спуска.
- Сигмоида: насыщение даёт крошечные градиенты — SGD на них почти стоит, а RMSProp растягивает шаг.
- Huber: ограничивает градиент выбросов — родственник обрезки градиента.
Итоги
1) SGD: $\theta_t=\theta_{t-1}-\eta g_t$, где $g_t$ — несмещённая оценка градиента по мини-батчу; дисперсия шума падает как $1/B$. 2) Устойчивость: $\eta<2/\lambda_{\max}$; скорость задаёт число обусловленности $\kappa$ — порядка $\kappa$ шагов на порядок точности. 3) Momentum $u_t=\beta u_{t-1}+g_t$ — экспоненциальная память о градиентах с окном $1/(1-\beta)$; на квадратичных задачах сокращает путь до порядка $\sqrt\kappa$ шагов и расширяет устойчивость до $\eta L<2(1+\beta)$. 4) RMSProp делит шаг на $\sqrt{v_t}$: шаг безразмерный, порядка $\eta$, но адаптация только диагональная. 5) EMA-инерция + RMSProp + поправка смещения = Adam.