Как работает стохастический градиентный спуск?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Стохастический градиентный спуск (SGD)
Стохастический градиентный спуск — это фундаментальный оптимизационный алгоритм, который лежит в основе обучения нейронных сетей и многих ML моделей.
1. Базовый градиентный спуск (GD)
Сначала вспомню классический подход:
import numpy as np
# Стоимостная функция (Loss Function)
def loss_function(X, y, weights):
predictions = X @ weights
errors = predictions - y
return np.sum(errors ** 2) / len(y) # MSE
# Градиент — направление роста функции
def gradient(X, y, weights):
predictions = X @ weights
errors = predictions - y
return (2 / len(y)) * (X.T @ errors)
# Классический градиентный спуск
weights = np.random.randn(n_features)
learning_rate = 0.01
for epoch in range(100):
grad = gradient(X, y, weights) # Считаем градиент на ВСЕХ данных
weights -= learning_rate * grad
Проблема: при миллионах примеров это очень медленно. На каждой итерации нужно проходить все данные.
2. Суть стохастического подхода
SGD идея: обновляй веса на основе одного примера или небольшого batch, а не всего датасета:
# Стохастический градиентный спуск
weights = np.random.randn(n_features)
learning_rate = 0.01
batch_size = 32
for epoch in range(num_epochs):
# Перемешиваем данные
indices = np.random.permutation(len(X))
for i in range(0, len(X), batch_size):
batch_indices = indices[i:i + batch_size]
X_batch = X[batch_indices]
y_batch = y[batch_indices]
# Вычисляем градиент ТОЛЬКО на batch
grad = gradient(X_batch, y_batch, weights)
weights -= learning_rate * grad
3. Три варианта gradient descent
| Вариант | Размер данных | Скорость | Стабильность | Использование памяти |
|---|---|---|---|---|
| Batch GD | Весь датасет | Медленно | Стабильно | Много |
| Stochastic GD | 1 пример | Быстро | Шумный | Мало |
| Mini-batch GD | 32-256 примеров | Быстро | Хороший баланс | Умеренно |
Мини-batch — это лучший выбор на практике:
# На PyTorch
from torch.utils.data import DataLoader
train_loader = DataLoader(dataset, batch_size=32, shuffle=True)
for epoch in range(num_epochs):
for X_batch, y_batch in train_loader: # автоматические batches
optimizer.zero_grad()
output = model(X_batch)
loss = criterion(output, y_batch)
loss.backward()
optimizer.step()
4. Почему SGD работает
Математическое обоснование:
Полный градиент (на всех N примерах):
∇L = (1/N) * Σ ∇L_i
Ожидание SGD градиента (на одном примере):
E[∇L_i] = ∇L
То есть градиент на одном примере — это несмещенная оценка полного градиента! На среднем это работает правильно.
Практические преимущества:
- Скорость: обновления происходят чаще (после каждого batch)
- Память: не нужно загружать все данные в RAM
- Регуляризация: случайный шум помогает избежать локальных минимумов
- Online learning: можно обновлять модель по мере поступления новых данных
5. Проблемы базового SGD
Проблема 1: Высокая волатильность
Без сглаживания SGD может колебаться:
# Визуально: потеря функция
| *
| * * <- шумные обновления
| * *
| * *
|* * <- общее направление к минимуму
Проблема 2: Медленная сходимость в конце обучения
6. Улучшенные алгоритмы
Momentum (Импульс)
Добавляю "инерцию" — копируем приземление мяча с горы:
# Momentum SGD
velocity = 0
momentum = 0.9
for epoch in range(num_epochs):
grad = gradient(X_batch, y_batch, weights)
velocity = momentum * velocity - learning_rate * grad
weights += velocity
Это сглаживает шум и ускоряет сходимость в правильном направлении.
Adam (Adaptive Moment Estimation)
Самый популярный оптимизатор на практике:
from torch.optim import Adam
optimizer = Adam(model.parameters(), lr=0.001, beta1=0.9, beta2=0.999)
for epoch in range(num_epochs):
for X_batch, y_batch in train_loader:
output = model(X_batch)
loss = criterion(output, y_batch)
optimizer.zero_grad()
loss.backward()
optimizer.step() # Adam автоматически
Adam комбинирует momentum и adaptive learning rates (RMSprop):
- m_t = экспоненциальное среднее градиентов (первый момент)
- v_t = экспоненциальное среднее квадратов градиентов (второй момент)
- Adaptive LR = learning_rate / sqrt(v_t + epsilon)
7. Практические советы
# 1. Выбор batch size
batch_sizes = [16, 32, 64, 128] # экспериментируй
# Больше → более стабильно, медленнее
# Меньше → шумнее, быстрее
# 2. Learning rate scheduling
from torch.optim.lr_scheduler import StepLR, ReduceLROnPlateau
scheduler = StepLR(optimizer, step_size=10, gamma=0.1) # уменьшай LR
# 3. Нормализация данных
from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)
# С нормализацией SGD работает стабильнее
# 4. Мониторинг
for epoch in range(num_epochs):
for X_batch, y_batch in train_loader:
loss = train_step(X_batch, y_batch)
if (batch_idx + 1) % 100 == 0:
print(f"Epoch {epoch}, Batch {batch_idx+1}: Loss {loss:.4f}")
Визуализация процесса
Gradient Descent сходимость:
Batch GD: Stochastic GD: Mini-batch GD:
Loss Loss Loss
| | |
100 |* |* |*
50 | * | * * * | *
10 | *---> | * * * | *
1 | ------> | ------> | ---->
|______________|_______________|____________
Iterations
Ключевые выводы
- SGD обновляет веса на основе mini-batch, а не всего датасета
- Импульс улучшает сходимость, сглаживая шум
- Adam — лучший выбор на практике для большинства задач
- Learning rate — критический гиперпараметр
- Batch size влияет на скорость и стабильность