Как работает Adam оптимизатор?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Как работает Adam оптимизатор
Введение
Adam (Adaptive Moment Estimation) — это адаптивный алгоритм оптимизации, который является одним из наиболее популярных методов для обучения нейросетей. Он комбинирует идеи из Momentum и RMSprop.
История: от SGD к Adam
-
SGD (Stochastic Gradient Descent): простой градиентный спуск
- Минусы: медленная сходимость, зависит от learning rate
-
Momentum: добавляет "инерцию" к обновлениям
- Плюс: быстрее сходится в плоских регионах
- Минус: может "проскочить" оптимум
-
RMSprop: адаптивная скорость обучения для каждого параметра
- Плюс: хорошо работает с разреженными градиентами
- Минус: требует корректировки параметров
-
Adam: комбинирует Momentum и RMSprop
- Плюс: хорошо работает на практике, мало нужно подстраивать
Математика Adam
Adam вычисляет экспоненциальное скользящее среднее (exponential moving average) градиентов и их квадратов:
m_t = beta1 * m_{t-1} + (1 - beta1) * g_t # first moment (mean)
v_t = beta2 * v_{t-1} + (1 - beta2) * g_t^2 # second moment (variance)
Далее, делается bias correction (коррекция смещения), потому что в начале обучения m и v близки к нулю:
m_hat_t = m_t / (1 - beta1^t)
v_hat_t = v_t / (1 - beta2^t)
Наконец, обновление параметров:
theta_t = theta_{t-1} - learning_rate * m_hat_t / (sqrt(v_hat_t) + epsilon)
Реализация Adam с нуля
import numpy as np
class Adam:
def __init__(self, learning_rate=0.001, beta1=0.9, beta2=0.999, epsilon=1e-8):
"""
Инициализация Adam оптимизатора.
learning_rate: скорость обучения (обычно 0.001)
beta1: экспоненциальный decay для first moment (обычно 0.9)
beta2: экспоненциальный decay для second moment (обычно 0.999)
epsilon: малое число для численной стабильности
"""
self.lr = learning_rate
self.beta1 = beta1
self.beta2 = beta2
self.epsilon = epsilon
self.m = {} # first moment
self.v = {} # second moment
self.t = 0 # timestep
def update(self, params, grads):
"""
Обновить параметры на основе градиентов.
params: dict с параметрами
grads: dict с градиентами (такой же ключ как params)
"""
self.t += 1
for key in params:
if key not in self.m:
self.m[key] = np.zeros_like(params[key])
self.v[key] = np.zeros_like(params[key])
g = grads[key]
# Update biased first moment estimate
self.m[key] = self.beta1 * self.m[key] + (1 - self.beta1) * g
# Update biased second raw moment estimate
self.v[key] = self.beta2 * self.v[key] + (1 - self.beta2) * g**2
# Compute bias-corrected first moment estimate
m_hat = self.m[key] / (1 - self.beta1 ** self.t)
# Compute bias-corrected second raw moment estimate
v_hat = self.v[key] / (1 - self.beta2 ** self.t)
# Update parameters
params[key] -= self.lr * m_hat / (np.sqrt(v_hat) + self.epsilon)
# Пример использования
params = {'w': np.array([1.0, 2.0]), 'b': np.array([0.5])}
grads = {'w': np.array([0.1, 0.2]), 'b': np.array([0.05])}
optimizer = Adam(learning_rate=0.01)
for step in range(5):
# Симулируем градиенты
grads['w'] = np.random.randn(2) * 0.1
grads['b'] = np.random.randn(1) * 0.05
print(f"Step {step}: w = {params['w']}, b = {params['b']}")
optimizer.update(params, grads)
Пошаговый пример
import numpy as np
# Инициализация
lr = 0.01
beta1, beta2 = 0.9, 0.999
epsilon = 1e-8
w = 2.0 # параметр
m = 0.0 # first moment
v = 0.0 # second moment
print("Пошаговое обновление параметра w:")
print()
for t in range(1, 4):
# Градиент (симулируем)
g = 0.5 # dL/dw
# Update first moment
m = beta1 * m + (1 - beta1) * g
print(f"Step {t}: m_t = {beta1} * {m - (1-beta1)*g:.4f} + {(1-beta1)*g:.4f} = {m:.4f}")
# Update second moment
v = beta2 * v + (1 - beta2) * g**2
print(f" v_t = {beta2} * {v - (1-beta2)*g**2:.4f} + {(1-beta2)*g**2:.4f} = {v:.4f}")
# Bias correction
m_hat = m / (1 - beta1**t)
v_hat = v / (1 - beta2**t)
print(f" m_hat = {m:.4f} / (1 - {beta1}^{t}) = {m_hat:.4f}")
print(f" v_hat = {v:.4f} / (1 - {beta2}^{t}) = {v_hat:.4f}")
# Update parameter
w_old = w
w = w - lr * m_hat / (np.sqrt(v_hat) + epsilon)
print(f" w = {w_old:.4f} - {lr} * {m_hat:.4f} / sqrt({v_hat:.4f}) = {w:.4f}")
print()
Использование в PyTorch
import torch
import torch.nn as nn
import torch.optim as optim
# Создание модели
model = nn.Sequential(
nn.Linear(10, 64),
nn.ReLU(),
nn.Linear(64, 1)
)
# Создание Adam оптимизатора
optimizer = optim.Adam(
model.parameters(),
lr=0.001, # learning rate
betas=(0.9, 0.999), # (beta1, beta2)
eps=1e-8,
weight_decay=1e-5 # L2 регуляризация (опционально)
)
# Цикл обучения
criterion = nn.MSELoss()
for epoch in range(10):
# Forward pass
X = torch.randn(32, 10)
y = torch.randn(32, 1)
outputs = model(X)
loss = criterion(outputs, y)
# Backward pass
optimizer.zero_grad() # обнуляем градиенты
loss.backward() # вычисляем градиенты
optimizer.step() # обновляем параметры
print(f"Epoch {epoch}: loss = {loss.item():.4f}")
Сравнение с другими оптимизаторами
# SGD vs Momentum vs Adam
import torch.optim as optim
# SGD
optimizer_sgd = optim.SGD(model.parameters(), lr=0.01, momentum=0.9)
# Adam (рекомендуется для большинства задач)
optimizer_adam = optim.Adam(model.parameters(), lr=0.001)
# RMSprop
optimizer_rmsprop = optim.RMSprop(model.parameters(), lr=0.001)
# AdamW (улучшенная версия Adam с лучшей weight decay)
optimizer_adamw = optim.AdamW(model.parameters(), lr=0.001)
# Сравнение скорости сходимости:
# SGD: медленная, может застрять в локальных минимумах
# Momentum: быстрее SGD, но может перескочить
# Adam: быстро, хорошо адаптируется (РЕКОМЕНДУЕТСЯ)
# RMSprop: похож на Adam, но без bias correction
# AdamW: лучше Adam для регуляризации
Ключевые параметры Adam
| Параметр | Значение | Описание |
|---|---|---|
| lr | 0.001 | Скорость обучения (важнейший параметр!) |
| beta1 | 0.9 | Decay rate для first moment (обычно 0.9) |
| beta2 | 0.999 | Decay rate для second moment (обычно 0.999) |
| epsilon | 1e-8 | Численная стабильность (редко менять) |
| weight_decay | 0 | L2 регуляризация |
Когда использовать Adam
Используй Adam если:
- Обучаешь нейросеть (default выбор)
- У тебя есть ограниченное время (Adam сходится быстро)
- Не уверен в выборе оптимизатора
Используй SGD с Momentum если:
- Нужна хорошая обобщаемость на тестовых данных
- У тебя много computational ресурсов
- Работаешь с классификацией изображений (часто дает лучшие результаты)
Используй AdamW если:
- Нужна регуляризация (weight decay работает корректнее)
- Работаешь с современными трансформерами
Визуализация сходимости
import matplotlib.pyplot as plt
# Сравнение оптимизаторов на простой функции
def rastrigin(x, y):
"""Функция Растригина - многомерная функция оптимизации"""
return 10*2 + (x**2 - 10*np.cos(2*np.pi*x)) + (y**2 - 10*np.cos(2*np.pi*y))
# Стартовая точка
x, y = 5.0, 5.0
# Обучение с Adam
history_adam = []
lr = 0.01
for step in range(100):
grad_x = 2*x + 20*np.pi*np.sin(2*np.pi*x)
grad_y = 2*y + 20*np.pi*np.sin(2*np.pi*y)
x -= lr * grad_x
y -= lr * grad_y
history_adam.append(rastrigin(x, y))
plt.plot(history_adam, label='Adam (lr=0.01)')
plt.xlabel('Шаг оптимизации')
plt.ylabel('Loss')
plt.legend()
plt.show()
Практические советы
- Начни с lr=0.001 — это часто хороший стартовый learning rate
- Используй learning rate schedule — уменьшай lr во время обучения
- Комбинируй с gradient clipping — для стабильности при работе с RNN
- Проверяй weight decay — может улучшить обобщение
- Экспериментируй с beta1 и beta2 — редко требуется, но иногда помогает
Adam стал стандартом в современном deep learning и рекомендуется для большинства задач.