Модуль оптимизатора torch.optim в PyTorch

Оптимизаторы — ключевые компоненты в глубоком обучении, отвечающие за настройку параметров модели на основе градиента функции потерь, что позволяет модели постепенно приближаться к оптимальному решению.

В PyTorch модуль torch.optim предоставляет реализации различных алгоритмов оптимизации и является незаменимым инструментом для обучения нейронных сетей.


Зачем нужны оптимизаторы

Оптимизаторы играют жизненно важную роль в глубоком обучении, решая утомительную проблему ручного обновления параметров.

  • Автоматическое обновление параметров: Ручное вычисление и обновление каждого параметра очень утомительно, а оптимизатор делает это автоматически.
  • Ускорение сходимости: Использование алгоритмов оптимизации позволяет быстрее найти оптимальное решение, чем обычный градиентный спуск.
  • Избежание локальных оптимумов: Некоторые оптимизаторы способны выходить из локальных оптимумов.

Распространённые типы оптимизаторов

Разные оптимизаторы подходят для разных сценариев. Выбор правильного оптимизатора может значительно улучшить результаты обучения.

Название оптимизатораОсновные возможностиПрименимые сценарии
SGDПростой и базовый, поддерживает импульсБазовое обучение, простые модели, CNN
AdamАдаптивная скорость обученияБольшинство задач глубокого обучения
AdamWAdam + развязанное затухание весаЗадачи, требующие L2-регуляризации
RMSpropАдаптивная скорость обученияRNN-сети, распознавание речи
AdagradСкорость обучения для каждого параметраРазреженные данные, обработка текста
AdadeltaАдаптивная скорость обученияДолгосрочные задачи обучения

Базовый API оптимизатора

Освоение базового процесса использования оптимизаторов — первый шаг в глубоком обучении.

Базовый процесс использования

Использование оптимизатора следует фиксированному шаблону: создать экземпляр → обнулить градиенты → обратный проход → обновить параметры.

Пример

import torch
import torch.nn as nn
import torch.optim as optim

# 1. Определите простую модель
class SimpleNet(nn.Module):
    def __init__(self):
        super().__init__()
        self.fc = nn.Linear(784, 10)

    def forward(self, x):
        return self.fc(x)

model = SimpleNet()

# 2. Создайте экземпляр оптимизатора
optimizer = optim.Adam(model.parameters(), lr=0.001)

# 3. Цикл обучения
for epoch in range(epochs):
    # Прямой проход
    outputs = model(inputs)
    loss = criterion(outputs, labels)

    # Обратный проход
    optimizer.zero_grad()  # Обнулите кэш градиента, чтобы избежать накопления градиентов
    loss.backward()        # Вычислите градиенты

    # Обновление параметров
    optimizer.step()       # Обновить параметры

Объяснение ключевых методов

Оптимизатор предоставляет несколько основных методов для управления процессом обновления параметров.

  • zero_grad(set_to_none=True): Обнуляет кэш градиентов параметров. Если установлено True, градиенты устанавливаются в None, что экономит больше памяти, чем установка их в 0.
  • step(): Выполняет одно обновление параметров, обновляя параметры модели на основе градиентов и скорости обучения.
  • state_dict(): Получить словарь состояния оптимизатора, который можно использовать для сохранения контрольных точек.
  • load_state_dict(state_dict): Загрузить состояние оптимизатора для возобновления обучения.
  • add_param_group(param_group): Динамически добавить группу параметров.

Примечание: необходимо вызывать zero_grad() перед каждым обратным проходом, иначе градиенты будут накапливаться и вызывать нестабильность обучения. Рекомендуется использовать zero_grad(set_to_none=True) для экономии памяти.

Сохранение и загрузка состояния оптимизатора

При возобновлении обучения необходимо сохранять состояния как модели, так и оптимизатора.

Пример

# Сохранить контрольную точку (сохранить модель, оптимизатор и планировщик вместе)
checkpoint = {
    'epoch': epoch,
    'model_state_dict': model.state_dict(),
    'optimizer_state_dict': optimizer.state_dict(),
    'scheduler_state_dict': scheduler.state_dict(),
    'loss': loss,
}
torch.save(checkpoint, 'checkpoint.pth')

# Загрузить чекпоинт
checkpoint = torch.load('checkpoint.pth')
model.load_state_dict(checkpoint['model_state_dict'])
optimizer.load_state_dict(checkpoint['optimizer_state_dict'])
scheduler.load_state_dict(checkpoint['scheduler_state_dict'])
start_epoch = checkpoint['epoch'] + 1

Подробное объяснение распространённых оптимизаторов

SGD (стохастический градиентный спуск)

SGD — это самый базовый алгоритм оптимизации, обновляющий параметры путём вычисления градиентов по одному образцу или мини-батчу образцов. Это краеугольный камень оптимизации глубокого обучения, и многие продвинутые оптимизаторы разработаны на основе SGD.

Пример

# Описание параметров оптимизатора SGD
# params: параметры для оптимизации (обычно из model.parameters())
# lr: скорость обучения, управляет размером шага обновления параметров, по умолчанию 0.01
# momentum: коэффициент импульса, используется для ускорения сходимости и уменьшения колебаний, по умолчанию 0
# weight_decay: коэффициент L2-регуляризации, используется для предотвращения переобучения, по умолчанию 0
# dampening: демпфирование импульса, управляет вычислением члена импульса, по умолчанию 0
# nesterov: использовать ли импульс Нестерова, по умолчанию False
optimizer = optim.SGD(
    params=model.parameters(),
    lr=0.01,           # Скорость обучения
    momentum=0.9,      # Коэффициент импульса
    weight_decay=1e-4, # L2-регуляризация
    nesterov=True      # Включить импульс Нестерова
)

Описание основных параметров:

  • lr (float): скорость обучения, управляет величиной шага обновления параметров
  • momentum (float): коэффициент импульса, используется для ускорения сходимости и уменьшения колебаний, типичное значение 0.9
  • weight_decay (float): коэффициент L2-регуляризации, используется для предотвращения переобучения, типичное значение 1e-4
  • nesterov (bool): использовать ли импульс Нестерова; его включение может уменьшить колебания

Возможности:

  • Прост в реализации, это базовый алгоритм оптимизации в глубоком обучении
  • Добавление члена импульса может ускорить сходимость и повысить стабильность обучения
  • Более медленная сходимость, но итоговая точность может быть выше
  • Подходит в качестве базового ориентира для сравнения с другими оптимизаторами

Хотя SGD прост, он часто может достигать очень хороших результатов при подходящих гиперпараметрах, что делает его хорошей отправной точкой для изучения алгоритмов оптимизации. В задачах классификации изображений SGD с моментумом остаётся основным выбором.

Adam (адаптивная оценка моментов)

Adam — один из наиболее часто используемых оптимизаторов, сочетающий в себе преимущества моментума и адаптивных скоростей обучения. Он адаптивно настраивает скорость обучения для каждого параметра, вычисляя оценки первого и второго моментов градиентов.

Пример

# Описание параметров оптимизатора Adam
# params: параметры для оптимизации
# lr: скорость обучения, по умолчанию 0.001 (рекомендуемое значение)
# betas: коэффициенты скользящего среднего, используемые для вычисления градиентов и квадратов градиентов (beta1, beta2)
# beta1 управляет оценкой первого момента (моментум), по умолчанию 0.9
# beta2 управляет оценкой второго момента (дисперсия), по умолчанию 0.999
# eps: слагаемое для численной устойчивости, предотвращает деление на ноль, по умолчанию 1e-8
# weight_decay: коэффициент L2-регуляризации, по умолчанию 0
# amsgrad: использовать ли вариант AMSGrad, по умолчанию False
optimizer = optim.Adam(
    params=model.parameters(),
    lr=0.001,                      # Рекомендуется использовать меньшую скорость обучения
    betas=(0.9, 0.999),            # Часто используемые параметры моментума
    eps=1e-8,                      # Слагаемое для численной устойчивости
    weight_decay=1e-4,             # L2-регуляризация
    amsgrad=False                  # Использовать ли AMSGrad
)

Описание основных параметров:

  • betas (Tuple[float, float]): управляют экспоненциальным скользящим средним градиентов и квадратов градиентов
  • eps (float): слагаемое для численной устойчивости, предотвращает обращение знаменателя в ноль
  • amsgrad (bool): использовать ли вариант AMSGrad; его использование может гарантировать сходимость

Возможности:

  • Адаптивная скорость обучения: автоматически корректирует скорость обучения на основе истории градиента параметра
  • Включает концепцию моментума: использует оценку первого момента для ускорения сходимости
  • Высокая робастность: относительно нечувствителен к выбору гиперпараметров
  • Быстрая сходимость, подходит для быстрого прототипирования

Adam — выбор по умолчанию для большинства задач глубокого обучения, но в некоторых специфических сценариях (например, GAN, обучение с подкреплением) может потребоваться попробовать другие оптимизаторы.

AdamW(Adam с уменьшением веса)

AdamW — улучшенная версия Adam, которая отделяет уменьшение веса от обновлений градиента, что теоретически больше способствует сходимости. В практических приложениях AdamW обычно работает лучше, чем Adam.

Пример

# Оптимизатор AdamW
# Основное отличие от Adam: другая реализация weight_decay
# Уменьшение веса в AdamW более корректно и не влияет на вычисление градиента
optimizer = optim.AdamW(
    params=model.parameters(),
    lr=0.001,
    betas=(0.9, 0.999),
    weight_decay=0.01,    # Коэффициент уменьшения веса, обычно устанавливается больше, чем в Adam
    amsgrad=False
)

# Рекомендуемая конфигурация: AdamW обычно использует weight_decay со значением 0.01
# Тогда как Adam обычно использует 0.001

Если ваша задача требует уменьшения веса (L2-регуляризации), настоятельно рекомендуется использовать AdamW вместо Adam.

RMSprop

RMSprop — это оптимизатор с адаптивной скоростью обучения, особенно подходящий для обработки нестационарных целевых функций и рекуррентных нейронных сетей.

Пример

# Оптимизатор RMSprop
# Нормализует скорость обучения путем деления на экспоненциально взвешенное среднее градиентов
optimizer = optim.RMSprop(
    params=model.parameters(),
    lr=0.01,               # Скорость обучения
    alpha=0.99,            # Скорость экспоненциального затухания для квадратов градиентов
    eps=1e-8,              # Слагаемое для численной устойчивости
    weight_decay=0,        # L2-регуляризация
    momentum=0,            # Коэффициент импульса
    centered=False         # Центрировать ли градиенты
)

Adagrad

Adagrad подходит для обработки разреженных данных; он адаптивно настраивает скорость обучения для каждого параметра.

Пример

# Оптимизатор Adagrad
# Подходит для оптимизации разреженных данных, использует меньшую скорость обучения для часто обновляемых параметров
optimizer = optim.Adagrad(
    params=model.parameters(),
    lr=0.01,               # Скорость обучения
    lr_decay=0,            # Затухание скорости обучения
    weight_decay=0,       # L2-регуляризация
    initial_accumulator_value=0  # Начальное значение аккумулятора
)

Продвинутые методы оптимизации

Планирование скорости обучения

Планирование скорости обучения позволяет динамически корректировать скорость обучения во время обучения, что часто может значительно улучшить сходимость модели.

Пример: несколько планировщиков скорости обучения

from torch.optim.lr_scheduler import (
    StepLR,                # Ступенчатое затухание
    MultiStepLR,           # Многоступенчатое затухание
    ExponentialLR,         # Экспоненциальное затухание
    CosineAnnealingLR,     # Косинусное отжигание
    ReduceLROnPlateau,     # Автоматическая корректировка на основе метрик
)

# Метод 1: StepLR - уменьшается каждые 30 эпох
optimizer = optim.SGD(model.parameters(), lr=0.1)
scheduler = StepLR(optimizer, step_size=30, gamma=0.1)

# Метод 2: MultiStepLR - уменьшается в указанные эпохи
optimizer = optim.SGD(model.parameters(), lr=0.1)
scheduler = MultiStepLR(optimizer, milestones=[30, 60, 80], gamma=0.1)

# Метод 3: CosineAnnealingLR - отжиг по косинусоидальной кривой
optimizer = optim.Adam(model.parameters(), lr=0.001)
scheduler = CosineAnnealingLR(optimizer, T_max=50, eta_min=1e-6)

# Метод 4: ReduceLROnPlateau - автоматически настраивается на основе контролируемой метрики
optimizer = optim.Adam(model.parameters(), lr=0.001)
scheduler = ReduceLROnPlateau(
    optimizer, mode='min',     # Отслеживать потери
    factor=0.5,                 # Коэффициент снижения
    patience=5,                 # Число эпох ожидания
    verbose=True                # Выводить информацию
)

# Цикл обучения
for epoch in range(100):
    train_loss = train(...)
    val_loss = validate(...)

    # StepLR и другие планировщики
    scheduler.step()

    # ReduceLROnPlateau требует передачи контролируемой метрики
    scheduler.step(val_loss)

Планировщик скорости обучения нужно использовать вместе с оптимизатором; step() должен вызываться после optimizer.step(), иначе обновление скорости обучения может работать некорректно.

Оптимизация групп параметров

Группировка параметров позволяет задавать разные скорости обучения для разных слоёв, что особенно полезно при трансферном обучении.

Пример

# Пример оптимизации группировки параметров
# Установка разных скоростей обучения для разных слоёв
# Обычно: для backbone-сети используется меньшая скорость обучения, а для классификационной головы — большая
optimizer = optim.SGD([
    {'params': model.base.parameters(), 'lr': 1e-3},      # Базовые слои: большая скорость обучения
    {'params': model.classifier.parameters(), 'lr': 1e-2} # Классификационный слой: большая скорость обучения
], lr=1e-4)  # Глобальная скорость обучения по умолчанию (используется, когда группа параметров не указана)

# Более распространённая запись в практических приложениях
optimizer = optim.Adam([
    {'params': model.fc.parameters(), 'lr': 1e-3},       # Головка классификации
    {'params': [p for n, p in model.named_parameters()    # Базовая сеть
                if not n.startswith('fc')],
     'lr': 1e-5},
])

Отсечение градиента

Отсечение градиентов может предотвратить взрыв градиентов и повысить стабильность обучения. Это особенно полезно в глубоких сетях, таких как RNN и LSTM.

Пример

import torch.nn as nn

# Пример отсечения градиентов
# max_norm: максимальная норма градиентов; градиенты, превышающие это значение, будут масштабированы
# norm_type: тип нормы, по умолчанию 2 (норма L2)
nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0)

# Позиция в цикле обучения
for epoch in range(epochs):
    optimizer.zero_grad()
    outputs = model(inputs)
    loss = criterion(outputs, labels)
    loss.backward()

    # Отсечение градиентов после loss.backward() и перед optimizer.step()
    nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0)

    optimizer.step()

# Другой способ отсечения градиентов: отсечение по значению
for param in model.parameters():
    if param.grad is not None:
        param.grad.data.clamp_(min=-1.0, max=1.0)

Отсечение градиентов — распространённая техника обучения глубоких нейронных сетей (особенно RNN, LSTM и т. д.), позволяющая эффективно предотвращать сбои обучения, вызванные взрывом градиентов.

Накопление градиентов

Когда видеопамяти недостаточно, можно использовать накопление градиентов, чтобы имитировать эффект обучения с большим размером батча.

Пример

# Пример накопления градиентов
# Фактический batch_size = batch_size * accumulation_steps
accumulation_steps = 4   # Накопить 4 небольших батча

optimizer.zero_grad()

for i, (inputs, labels) in enumerate(train_loader):
    outputs = model(inputs)
    loss = criterion(outputs, labels)

    # Разделите loss на шаги накопления для усреднения
    loss = loss / accumulation_steps
    loss.backward()

    # Обновлять параметры один раз за заданное число шагов накопления
    if (i + 1) % accumulation_steps == 0:
        nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0)
        optimizer.step()
        optimizer.zero_grad()

# Обработать оставшиеся градиенты
if (i + 1) % accumulation_steps != 0:
    optimizer.step()
    optimizer.zero_grad()

Полный пример обучения

Ниже приведён полный пайплайн обучения, демонстрирующий лучшие практики для оптимизаторов.

Пример

import torch
import torch.nn as nn
import torch.optim as optim
from torch.optim.lr_scheduler import CosineAnnealingLR

# Конфигурация
DEVICE = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
EPOCHS = 100
BATCH_SIZE = 32
LR = 1e-3

# Создание модели и перемещение на устройство
model = SimpleNet().to(DEVICE)

# Функция потерь и оптимизатор
criterion = nn.CrossEntropyLoss()
optimizer = optim.AdamW(model.parameters(), lr=LR, weight_decay=0.01)

# Планировщик скорости обучения (косинусный отжиг)
scheduler = CosineAnnealingLR(optimizer, T_max=EPOCHS, eta_min=1e-6)

# Цикл обучения
best_acc = 0.0
for epoch in range(EPOCHS):
    model.train()
    total_loss = 0.0
    correct = 0

    for inputs, labels in train_loader:
        inputs = inputs.to(DEVICE)
        labels = labels.to(DEVICE)

        # Обнуление градиентов (рекомендуется использовать set_to_none=True)
        optimizer.zero_grad(set_to_none=True)

        # Прямой проход
        outputs = model(inputs)
        loss = criterion(outputs, labels)

        # Обратный проход
        loss.backward()

        # Отсечение градиентов (предотвращение взрыва градиентов)
        nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0)

        # Обновить параметры
        optimizer.step()

        # Статистика
        total_loss += loss.item()
        correct += (outputs.argmax(1) == labels).sum().item()

    # Обновить скорость обучения
    scheduler.step()

    # Вывод информации об обучении
    avg_loss = total_loss / len(train_loader)
    accuracy = correct / len(train_loader.dataset)
    current_lr = scheduler.get_last_lr()[0]
    print(f"Эпоха {epoch+1}/{EPOCHS} | Потеря: {avg_loss:.4f} | "
          f"Точность: {accuracy:.4f} | Скорость обучения: {current_lr:.6f}")

    # Сохранение лучшей модели
    if accuracy > best_acc:
        best_acc = accuracy
        torch.save({
            'epoch': epoch,
            'model_state_dict': model.state_dict(),
            'optimizer_state_dict': optimizer.state_dict(),
            'scheduler_state_dict': scheduler.state_dict(),
            'best_acc': best_acc,
        }, 'best_model.pth')

print(f"Обучение завершено, лучшая точность: {best_acc:.4f}")

Руководство по выбору оптимизатора

Выбор правильного оптимизатора необходимо определять исходя из конкретной задачи, характеристик данных и этапа обучения.

Выбор по задаче

Для разных типов задач рекомендуются разные оптимизаторы.

Тип задачиРекомендуемый оптимизаторРекомендуемая скорость обученияПримечания
Классификация изображений (CNN)SGD + Momentum0.01 ~ 0.1Медленная сходимость, но высокая точность
Классификация изображений (CNN)AdamW0.001Быстрая сходимость
NLP / TransformerAdamW1e-5 ~ 1e-4Меньшая скорость обучения
RNN / LSTMRMSprop / Adam0.001Адаптивная скорость обучения
GANAdam (G) / Adam (D)0.0001Меньшая скорость обучения
Обучение с подкреплениемAdam / RMSprop0.0001 ~ 0.001Зависит от конкретной задачи
Быстрое экспериментированиеAdam / AdamW0.001Быстрая сходимость

Сравнение производительности

ОптимизаторСкорость сходимостиИспользование памятиЧувствительность к гиперпараметрамИтоговая точность
SGD + MomentumМедленнаяНизкаяВысокаяВысокая
AdamВысокаяСредняяНизкаяСредняя
AdamWВысокаяСредняяНизкаяВысокая
RMSpropСредняяСредняяСредняяСредняя
AdagradСредняяВысокаяСредняяНизкая

Распространённые проблемы и решения

  • Нестабильное обучение (колебания потерь): уменьшите скорость обучения, добавьте отсечение градиентов
  • Сходимость слишком медленная: используйте Adam или AdamW, увеличьте скорость обучения
  • Переобучение: увеличьте weight_decay, используйте регуляризацию
  • Недостаточно видеопамяти: уменьшите batch_size, используйте накопление градиентов

Выбор оптимизатора не абсолютен; рекомендуется начать с Adam или AdamW и рассмотреть другие оптимизаторы, если результаты неудовлетворительны. Для конкретных задач может потребоваться проведение экспериментов, чтобы определить оптимальный выбор.

Для более подробной информации обратитесь кофициальной документации PyTorch。

Другие расширения