Что такое градиентный спуск (Gradient Descent) и как он работает?

«Что такое градиентный спуск (Gradient Descent) и как он работает?» — вопрос из категории Нейронные сети и Deep Learning, который задают на 30% собеседований Data Scientist / ML Инженер. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Градиентный спуск — это итеративный алгоритм оптимизации первого порядка, используемый для нахождения локального минимума дифференцируемой функции. В контексте глубокого обучения и машинного обучения он применяется для минимизации функции потерь J(θ) путём обновления параметров модели θ.

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

Формула обновления (для параметра θ_i): θ_i := θ_i - α * ∂J(θ) / ∂θ_i где α — скорость обучения (learning rate), гиперпараметр, контролирующий размер шага.

Основные варианты алгоритма:

Вариант Описание Плюсы Минусы
Batch Gradient Descent Вычисляет градиент по всему обучающему набору за одну итерацию. Стабильное, детерминированное направление к минимуму. Очень медленно на больших датасетах; требует всей памяти данных.
Stochastic Gradient Descent (SGD) Вычисляет градиент и обновляет параметры для одного случайного примера за итерацию. Быстрый; может выпрыгивать из локальных минимумов. Сильно флуктуирует; сходимость может быть нестабильной.
Mini-batch Gradient Descent Компромисс: вычисляет градиент по небольшой случайной подвыборке (mini-batch). Более стабилен, чем SGD; использует аппаратное ускорение (векторизацию). Требует настройки размера батча.

Практический пример реализации Mini-batch SGD для линейной регрессии на NumPy:

import numpy as np

def mini_batch_gradient_descent(X, y, learning_rate=0.01, epochs=100, batch_size=32):
    """
    X: матрица признаков (m samples, n features)
    y: вектор целевых значений (m, )
    """
    m, n = X.shape
    theta = np.random.randn(n)  # Инициализация параметров

    for epoch in range(epochs):
        # Перемешиваем данные в каждую эпоху
        indices = np.random.permutation(m)
        X_shuffled = X[indices]
        y_shuffled = y[indices]

        for i in range(0, m, batch_size):
            X_batch = X_shuffled[i:i+batch_size]
            y_batch = y_shuffled[i:i+batch_size]

            # Вычисление градиента для батча
            predictions = X_batch.dot(theta)
            errors = predictions - y_batch
            gradient = (2 / batch_size) * X_batch.T.dot(errors)

            # Обновление параметров
            theta -= learning_rate * gradient

        # (Опционально) Мониторинг потерь
        if epoch % 10 == 0:
            loss = np.mean((X.dot(theta) - y) ** 2)
            print(f"Epoch {epoch}, Loss: {loss:.4f}")

    return theta

Критические аспекты для успеха:

  1. Скорость обучения (α): Слишком большая — алгоритм расходится; слишком малая — обучение происходит крайне медленно. Часто используют затухание (decay) или адаптивные методы (Adam, RMSprop).
  2. Инициализация параметров: Важна для сложных моделей (например, нейросетей). Используют методы Xavier/Glorot или He.
  3. Момент (Momentum): Техника, которая помогает ускорить сходимость и проходить мелкие локальные минимумы, добавляя инерцию от предыдущих обновлений градиента.