Оптимизаторы: SGD и Adam

Разбираем оптимизаторы — алгоритмы, которые решают, как именно двигать веса по градиентам.

Оптимизатор — объект из torch.optim, который обновляет веса модели на основе их градиентов и заданного правила.

Что делает оптимизатор

backward даёт нам градиенты — направление, куда веса нужно двигать, чтобы уменьшить loss. Но насколько двигать и по какому правилу — решает оптимизатор. Самый простой — SGD (стохастический градиентный спуск): он вычитает из каждого веса его градиент, умноженный на шаг обучения lr.

import torch

# создаём оптимизатор, передав параметры модели и шаг
optimizer = torch.optim.SGD(model.parameters(), lr=0.01)

# внутри цикла:
optimizer.zero_grad()
loss.backward()
optimizer.step()    # w = w - 0.01 * w.grad для каждого веса

Заметьте: оптимизатору при создании передают model.parameters() — так он узнаёт, какие именно тензоры ему поручено обновлять.

Learning rate — самый важный гиперпараметр

Шаг обучения lr определяет размер шага по градиенту. Это, пожалуй, самая чувствительная настройка во всём обучении:

lrЧто происходит
слишком большойшаги перелетают минимум, loss скачет или растёт (расходится)
слишком маленькийобучение идёт мучительно медленно
в самый разloss плавно и стабильно снижается

Типичные стартовые значения: 0.10.01 для SGD, 0.001 для Adam. Если loss «взрывается» в NaN — первое подозрение всегда на слишком большой lr.

Adam — умный оптимизатор по умолчанию

SGD прост, но требует аккуратной настройки lr и сходится медленно. Adam адаптирует размер шага для каждого веса автоматически (запоминая историю градиентов) и работает «из коробки» почти везде. Поэтому для большинства задач Adam — разумный выбор по умолчанию.

# SGD с инерцией (momentum помогает быстрее проходить пологие участки)
opt_sgd  = torch.optim.SGD(model.parameters(), lr=0.01, momentum=0.9)

# Adam — адаптивный, хорош по умолчанию
opt_adam = torch.optim.Adam(model.parameters(), lr=0.001)

API у всех оптимизаторов одинаковый: создал → в цикле zero_grad() / step(). Сменить SGD на Adam — это поменять одну строку, остальной цикл не трогаете. Поэтому оптимизатор удобно подбирать экспериментом.

Руками воспроизводим шаг SGD

Чтобы оптимизатор перестал быть «чёрным ящиком», повторим шаг SGD в чистом Python. Минимизируем f(w) = (w - 5)² — минимум в точке w = 5. Градиент f'(w) = 2(w - 5). Этот код запускается:

# ручной градиентный спуск: ищем минимум (w - 5)^2
w = 0.0          # начальное значение веса
lr = 0.1         # learning rate

for step in range(20):
    grad = 2 * (w - 5)      # производная f по w
    w = w - lr * grad       # ровно то, что делает optimizer.step() для SGD

print("итоговый w:", round(w, 4))
print("минимум должен быть в w = 5")

Вывод:

итоговый w: 4.9424
минимум должен быть в w = 5

За 20 шагов вес уверенно подполз к 5. Это в точности то, что optimizer.step() делает в PyTorch — только сразу для миллионов весов и с автоматически посчитанными autograd градиентами. Попробуйте поставить lr = 1.5 и увидите, как шаги начнут «перелетать» минимум и расходиться — наглядная иллюстрация слишком большого learning rate.

Итог

  • Оптимизатор из torch.optim решает, как двигать веса по их градиентам; ему передают model.parameters().
  • lr (learning rate) — критичный гиперпараметр: велик — расходится, мал — медленно.
  • SGD прост; Adam адаптивен и хорош как выбор по умолчанию.
  • Шаг SGD — это w = w - lr * grad; сменить оптимизатор — одна строка.
Проверьте себя
1. Что произойдёт, если learning rate слишком большой?
AОбучение пойдёт быстро и точно
BШаги будут перелетать минимум, loss начнёт скакать или расти
CГрадиенты станут нулевыми
DОптимизатор переключится на Adam
2. Чем Adam обычно удобнее SGD на практике?
AОн не требует градиентов
BОн адаптирует размер шага для каждого веса и хорошо работает из коробки
CОн не нуждается в learning rate
DОн работает только на GPU
3. Что нужно передать оптимизатору при его создании?
AФункцию потерь
BПараметры модели model.parameters()
CОбучающие данные
DЧисло эпох
Поддержать проект