Gradient descent и оптимизаторы для Data Scientist
Карьерник — Duolingo для аналитиков: 10 минут в день тренируй SQL, Python, A/B, статистику, метрики и ещё 3 темы собеса. 1500+ вопросов в Telegram-боте. Бесплатно.
Содержание:
Зачем спрашивают
Любая модель сложнее линейной регрессии тренируется через gradient descent: нейросети, бустинги, логистическая регрессия. На собесе DS обязательно спросят: «Что такое SGD?», «Чем Adam отличается от SGD?», «Почему learning rate важен?». Это базовая интуиция, без которой кандидат не понимает, что происходит при model.fit().
Главная боль без понимания оптимизаторов — модель не сходится, кандидат добавляет слои, увеличивает выборку, переключает архитектуру. На самом деле — слишком большой learning rate, веса прыгают через минимум.
Эта статья — про базовые алгоритмы оптимизации и понимание, как и зачем выбирают между ними.
Vanilla gradient descent
Минимизация функции потерь L(w) обновлением весов в сторону отрицательного градиента:
w ← w - η · ∇L(w)η (eta) — learning rate. Слишком большой — веса прыгают через минимум, не сходимся. Слишком маленький — обучение очень медленное.
В чистом виде vanilla GD считает градиент по всему датасету:
for epoch in range(N):
grad = compute_gradient(loss, w, X_full, y_full)
w = w - lr * gradМинусы:
- Дорогая итерация (один проход = весь датасет)
- На больших датасетах не помещается в память
- Не работает в онлайн-сценариях
В современном ML почти не используется в чистом виде.
SGD и mini-batch
Stochastic Gradient Descent (SGD) — градиент на одном примере:
for epoch in range(N):
for x_i, y_i in zip(X, y):
grad = compute_gradient(loss, w, x_i, y_i)
w = w - lr * gradMini-batch SGD — компромисс: градиент на батче из B примеров (обычно 32–512).
for epoch in range(N):
for batch in get_batches(X, y, batch_size=64):
grad = compute_gradient(loss, w, batch.X, batch.y)
w = w - lr * gradПреимущества SGD/mini-batch:
- Дешёвая итерация
- Шум в градиенте помогает выходить из локальных минимумов и седловых точек
- Подходит для больших датасетов и онлайн-обучения
Размер батча:
- Большие батчи (1024+) — стабильный градиент, быстрее по wallclock на GPU, но требуется больше памяти и иногда хуже обобщает
- Маленькие батчи (32–128) — больше шума, лучше обобщение, медленнее на GPU
- Нет универсального ответа, классические дефолты: 32 для CV, 128–512 для табличных задач
Momentum и Nesterov
Чистый SGD «дрожит» на узких ущельях loss-ландшафта (один градиент сильный по одной оси, слабый по другой). Momentum добавляет инерцию: учитывает не только текущий градиент, но и накопленную «скорость».
v ← β · v + ∇L(w)
w ← w - η · vβ (обычно 0.9) — насколько помним прошлое. Высокий — гладко, медленно меняем направление. Низкий — почти как чистый SGD.
Эффект: ускорение в направлениях с устойчивым градиентом, гасит шум в шумных направлениях.
Nesterov momentum — улучшение, считает градиент в «предсказанной» точке:
v ← β · v + ∇L(w - η · β · v)
w ← w - η · vНа практике — чуть лучше momentum в большинстве задач.
В sklearn — SGDClassifier(momentum=0.9). В torch — optim.SGD(params, lr=0.01, momentum=0.9, nesterov=True).
Adam, RMSProp, AdaGrad
AdaGrad. Адаптивный learning rate per-parameter: для часто обновляемых параметров — меньше, для редких — больше.
G ← G + (∇L)²
w ← w - η · ∇L / (√G + ε)Минус: G монотонно растёт, learning rate монотонно падает к нулю — обучение «затухает».
RMSProp — фикс AdaGrad: экспоненциальное скользящее среднее квадратов градиентов вместо суммы.
G ← β · G + (1 - β) · (∇L)²
w ← w - η · ∇L / (√G + ε)Adam (Adaptive Moment Estimation) — комбинация momentum и RMSProp. Хранит первый момент (как momentum) и второй (как RMSProp).
m ← β₁ · m + (1 - β₁) · ∇L # momentum
v ← β₂ · v + (1 - β₂) · (∇L)² # RMSProp
m̂ ← m / (1 - β₁^t) # bias correction
v̂ ← v / (1 - β₂^t)
w ← w - η · m̂ / (√v̂ + ε)Дефолт PyTorch: Adam(lr=1e-3, betas=(0.9, 0.999), eps=1e-8). Работает «из коробки» в большинстве задач.
AdamW — Adam с правильным weight_decay (decoupled). Часто стандарт в трансформерах вместо обычного Adam.
Когда что:
- Adam/AdamW — дефолт для нейросетей
- SGD + momentum — хорошо для CV (классификация изображений), часто даёт лучшее обобщение
- LBFGS — для маленьких задач, точная оптимизация (sklearn LogisticRegression использует)
Learning rate scheduling
Постоянный lr редко оптимален. Schedules:
Step decay — снижать lr в N раз каждые K эпох:
scheduler = torch.optim.lr_scheduler.StepLR(optimizer, step_size=10, gamma=0.5)Cosine annealing — плавное снижение по косинусу до минимума, иногда с restarts:
scheduler = CosineAnnealingLR(optimizer, T_max=100)ReduceLROnPlateau — снижаем lr, когда метрика на val перестала улучшаться:
scheduler = ReduceLROnPlateau(optimizer, mode='min', factor=0.5, patience=5)Warmup — начинаем с маленького lr, постепенно увеличиваем до целевого. Обязательно для трансформеров и больших моделей.
1cycle policy — Smith. Стартуем с малого lr, поднимаем до пика, опускаем ниже стартового. Стандарт в fastai.
Как подбирать lr:
- Lr finder: тренируем 1 эпоху с lr от 1e-7 до 1, рисуем loss vs lr, берём lr перед минимумом loss
- Эвристика: для Adam — 1e-3 / 1e-4, для SGD — 1e-2 / 1e-1
- Чем больше batch — тем больший lr можно (linear scaling rule)
Частые ошибки
Слишком большой lr. Loss взрывается до NaN или хаотично прыгает. Уменьшить в 10× и попробовать снова.
Слишком маленький lr. Loss падает медленно или вовсе застрял. Увеличить или добавить scheduler.
Не нормализовать признаки/входы. Признаки в разных масштабах → градиент по одним осям сильнее → веса осциллируют. StandardScaler / BatchNorm.
Adam везде «по умолчанию». Adam работает почти везде, но в CV-задачах SGD+momentum часто даёт +1–2% accuracy. На современных трансформерах AdamW лучше Adam.
Не использовать gradient clipping в RNN/LSTM. Vanishing/exploding gradients. torch.nn.utils.clip_grad_norm_(params, 1.0).
Тренировка без warmup на больших моделях. Большой initial lr на свежих весах — нестабильность. Warmup 500–4000 шагов с lr от 0 до целевого.
Считать, что loss всегда снижается монотонно. На SGD loss скачет — нормально. Считать sliding average для оценки тренда.
Связанные темы
- Регуляризация L1 и L2 на собесе DS
- Что такое overfitting
- Bias-variance tradeoff простыми словами
- Подготовка к собесу Data Scientist
- ML vs Deep Learning для аналитика
FAQ
Чем Adam отличается от SGD?
SGD двигает веса по градиенту с константным lr. Adam адаптивно меняет lr per-parameter и использует momentum. Adam быстрее сходится «из коробки», SGD при правильном scheduling часто даёт лучшее обобщение.
Что выбрать для бустингов?
XGBoost/LightGBM/CatBoost используют свои оптимизаторы внутри (newton-like обновления для деревьев). Гиперпараметр learning_rate в них регулирует размер шага бустинга, не градиентного спуска.
Можно ли тренировать нейросеть без learning rate scheduler?
Можно, но обычно хуже. Adam при константном lr через 10–20 эпох начинает осциллировать вокруг минимума. Декреасированиый lr — почти всегда дополнительный +0.5–2% метрики.
Что такое cyclical learning rate?
Циклически меняющийся lr (Smith, 2015) — пилообразный или треугольный график. Помогает выходить из плохих локальных минимумов и седловых точек. 1cycle — современный вариант.
Как избежать NaN в loss?
Проверить: lr слишком большой, нет normalization, division by zero в loss, log от негативных, exp от больших чисел. Включить gradient clipping.
Это официальная информация?
Нет. Статья основана на оригинальных публикациях (Kingma-Ba 2014 для Adam, Polyak 1964 для momentum, Tieleman-Hinton 2012 для RMSProp) и документации PyTorch / TensorFlow.
Тренируйте Data Science — откройте тренажёр с 1500+ вопросами для собесов.