Оптимизаторы: 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Шаги будут перелетать минимум, loss начнёт скакать или расти
BОбучение пойдёт быстро и точно
CГрадиенты станут нулевыми
DОптимизатор переключится на Adam
2. Чем Adam обычно удобнее SGD на практике?
AОн не требует градиентов
BОн адаптирует размер шага для каждого веса и хорошо работает из коробки
CОн не нуждается в learning rate
DОн работает только на GPU
3. Что нужно передать оптимизатору при его создании?
AФункцию потерь
BОбучающие данные
CПараметры модели model.parameters()
DЧисло эпох