Модуль оптимизатора torch.optim в PyTorch
Оптимизаторы — ключевые компоненты в глубоком обучении, отвечающие за настройку параметров модели на основе градиента функции потерь, что позволяет модели постепенно приближаться к оптимальному решению.
В PyTorch модуль torch.optim предоставляет реализации различных алгоритмов оптимизации и является незаменимым инструментом для обучения нейронных сетей.
Зачем нужны оптимизаторы
Оптимизаторы играют жизненно важную роль в глубоком обучении, решая утомительную проблему ручного обновления параметров.
- Автоматическое обновление параметров: Ручное вычисление и обновление каждого параметра очень утомительно, а оптимизатор делает это автоматически.
- Ускорение сходимости: Использование алгоритмов оптимизации позволяет быстрее найти оптимальное решение, чем обычный градиентный спуск.
- Избежание локальных оптимумов: Некоторые оптимизаторы способны выходить из локальных оптимумов.
Распространённые типы оптимизаторов
Разные оптимизаторы подходят для разных сценариев. Выбор правильного оптимизатора может значительно улучшить результаты обучения.
| Название оптимизатора | Основные возможности | Применимые сценарии |
|---|---|---|
| SGD | Простой и базовый, поддерживает импульс | Базовое обучение, простые модели, CNN |
| Adam | Адаптивная скорость обучения | Большинство задач глубокого обучения |
| AdamW | Adam + развязанное затухание веса | Задачи, требующие L2-регуляризации |
| RMSprop | Адаптивная скорость обучения | RNN-сети, распознавание речи |
| Adagrad | Скорость обучения для каждого параметра | Разреженные данные, обработка текста |
| Adadelta | Адаптивная скорость обучения | Долгосрочные задачи обучения |
Базовый API оптимизатора
Освоение базового процесса использования оптимизаторов — первый шаг в глубоком обучении.
Базовый процесс использования
Использование оптимизатора следует фиксированному шаблону: создать экземпляр → обнулить градиенты → обратный проход → обновить параметры.
Пример
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.
Пример
# 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 — один из наиболее часто используемых оптимизаторов, сочетающий в себе преимущества моментума и адаптивных скоростей обучения. Он адаптивно настраивает скорость обучения для каждого параметра, вычисляя оценки первого и второго моментов градиентов.
Пример
# 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.
Пример
# Основное отличие от 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 — это оптимизатор с адаптивной скоростью обучения, особенно подходящий для обработки нестационарных целевых функций и рекуррентных нейронных сетей.
Пример
# Нормализует скорость обучения путем деления на экспоненциально взвешенное среднее градиентов
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 подходит для обработки разреженных данных; он адаптивно настраивает скорость обучения для каждого параметра.
Пример
# Подходит для оптимизации разреженных данных, использует меньшую скорость обучения для часто обновляемых параметров
optimizer = optim.Adagrad(
params=model.parameters(),
lr=0.01, # Скорость обучения
lr_decay=0, # Затухание скорости обучения
weight_decay=0, # L2-регуляризация
initial_accumulator_value=0 # Начальное значение аккумулятора
)
Продвинутые методы оптимизации
Планирование скорости обучения
Планирование скорости обучения позволяет динамически корректировать скорость обучения во время обучения, что часто может значительно улучшить сходимость модели.
Пример: несколько планировщиков скорости обучения
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.
Пример
# Пример отсечения градиентов
# 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.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 + Momentum | 0.01 ~ 0.1 | Медленная сходимость, но высокая точность |
| Классификация изображений (CNN) | AdamW | 0.001 | Быстрая сходимость |
| NLP / Transformer | AdamW | 1e-5 ~ 1e-4 | Меньшая скорость обучения |
| RNN / LSTM | RMSprop / Adam | 0.001 | Адаптивная скорость обучения |
| GAN | Adam (G) / Adam (D) | 0.0001 | Меньшая скорость обучения |
| Обучение с подкреплением | Adam / RMSprop | 0.0001 ~ 0.001 | Зависит от конкретной задачи |
| Быстрое экспериментирование | Adam / AdamW | 0.001 | Быстрая сходимость |
Сравнение производительности
| Оптимизатор | Скорость сходимости | Использование памяти | Чувствительность к гиперпараметрам | Итоговая точность |
|---|---|---|---|---|
| SGD + Momentum | Медленная | Низкая | Высокая | Высокая |
| Adam | Высокая | Средняя | Низкая | Средняя |
| AdamW | Высокая | Средняя | Низкая | Высокая |
| RMSprop | Средняя | Средняя | Средняя | Средняя |
| Adagrad | Средняя | Высокая | Средняя | Низкая |
Распространённые проблемы и решения
- Нестабильное обучение (колебания потерь): уменьшите скорость обучения, добавьте отсечение градиентов
- Сходимость слишком медленная: используйте Adam или AdamW, увеличьте скорость обучения
- Переобучение: увеличьте weight_decay, используйте регуляризацию
- Недостаточно видеопамяти: уменьшите batch_size, используйте накопление градиентов
Выбор оптимизатора не абсолютен; рекомендуется начать с Adam или AdamW и рассмотреть другие оптимизаторы, если результаты неудовлетворительны. Для конкретных задач может потребоваться проведение экспериментов, чтобы определить оптимальный выбор.
Для более подробной информации обратитесь кофициальной документации PyTorch。
Другие расширения