Реализация формулы градиентного спуска на Python

Вопрос или проблема

Недавно я начал курс по машинному обучению от Эндрю Нга, и вот формула, которую Эндрю приводит для расчета градиентного спуска на линейной модели.

$$ \theta_j = \theta_j – \alpha \frac{1}{m} \sum_{i=1}^m \left( h_\theta(x^{(i)}) – y^{(i)}\right)x_j^{(i)} \qquad \text{одновременно обновите } \theta_j \text{ для всех } j$$

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

Однако приведенный ниже код не работает, если я применяю np.sum()

def gradientDescent(X, y, theta, alpha, num_iters):

    # Инициализируем некоторые полезные значения
    m = y.shape[0]  # количество обучающих примеров

    # создаем копию theta, чтобы избежать изменения оригинального массива, так как массивы numpy
    # передаются по ссылке в функции
    theta = theta.copy()

    J_history = [] # Используем список Python для сохранения стоимости на каждой итерации

    for i in range(num_iters):
        temp = np.dot(X, theta) - y
        temp = np.dot(X.T, temp)
        theta = theta - ((alpha / m) * np.sum(temp))
        # сохраняем стоимость J на каждой итерации
        J_history.append(computeCost(X, y, theta))

    return theta, J_history

С другой стороны, если я избавлюсь от np.sum(), формула работает идеально.

def gradientDescent(X, y, theta, alpha, num_iters):

# Инициализируем некоторые полезные значения
m = y.shape[0]  # количество обучающих примеров

# создаем копию theta, чтобы избежать изменения оригинального массива, так как массивы numpy
# передаются по ссылке в функции
theta = theta.copy()

J_history = [] # Используем список Python для сохранения стоимости на каждой итерации

for i in range(num_iters):
    temp = np.dot(X, theta) - y
    temp = np.dot(X.T, temp)
    theta = theta - ((alpha / m) * temp)
    # сохраняем стоимость J на каждой итерации
    J_history.append(computeCost(X, y, theta))

return theta, J_history

Может кто-нибудь объяснить это?

Ваша цель – вычислить градиенты для всего вектора theta размером p (количество переменных). Ваш temp также является вектором размером $p$, который содержит значения градиентов функции стоимости относительно каждого из ваших значений theta.

Поэтому вы хотите вычесть векторы поэлементно (с учетом скорости обучения $\alpha$), так что нет причин суммировать вектор.

Комментаторы правы – вы путаете векторные и скалярные операции.

Формула является скалярной, и вот как вы можете ее реализовать:

for n in range(num_iters):
    
    for j in range(len(theta)):
    
        sum_j = 0
        for i in range(len(X)):
            temp = X[i, j]*theta[j] - y[i]
            temp = temp * X[i, j]
            sum_j += temp
        
        sum_j = (alpha / m)*sum_j
        
        theta[j] = theta[j] - sum_j

J_history.append(computeCost(X, y, theta))

Но вы пытаетесь подставить векторы в скалярную формулу, и это вызывает путаницу.

Ответ или решение

Реализация Формулы Градиентного Спуска в Python: Подробное Объяснение

Градиентный спуск является одним из ключевых методов оптимизации, применяемых в машинном обучении, и в частности, в линейной регрессии. Andrew Ng в своем курсе по машинному обучению представил формулу для обновления параметров модели. Давайте рассмотрим эту формулу и объясним, как правильно ее реализовать в Python, избегая типичных ошибок, которые могут возникнуть у начинающих.

Формула Градиентного Спуска

Формула обновления в градиентном спуске записывается следующим образом:

$$
\theta_j = \thetaj – \alpha \frac{1}{m} \sum{i=1}^m \left( h_\theta(x^{(i)}) – y^{(i)}\right) x_j^{(i)}
$$

где:

  • ( \theta_j ) — параметр модели,
  • ( \alpha ) — скорость обучения,
  • ( m ) — количество обучающих примеров,
  • ( h_\theta(x^{(i)}) ) — предсказание модели для каждого примера,
  • ( x_j^{(i)} ) — значение j-го признака для i-го примера,
  • ( y^{(i)} ) — истинное значение целевой переменной.

Ошибки в Реализации

В вашем коде возникла путаница между векторными и скалярными операциями. Основная причина проблемы связана с использованием функции np.sum() в вашем алгоритме обновления параметров:

theta = theta - ((alpha / m) * np.sum(temp))

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

Правильная Реализация в Python

Чтобы корректно реализовать формулу градиентного спуска в Python, вам нужно будет обновлять каждый параметр ( \theta_j ) одновременно, используя векторные операции NumPy. Вы можете сделать это следующим образом:

import numpy as np

def computeCost(X, y, theta):
    m = y.shape[0]
    predictions = X.dot(theta)
    square_errors = (predictions - y) ** 2
    return 1 / (2 * m) * np.sum(square_errors)

def gradientDescent(X, y, theta, alpha, num_iters):
    m = y.shape[0]
    theta = theta.copy()
    J_history = []

    for _ in range(num_iters):
        # Вычисляем предсказания
        predictions = np.dot(X, theta)
        # Ошибки
        errors = predictions - y
        # Обновляем theta
        theta -= (alpha / m) * np.dot(X.T, errors)

        # Сохраняем стоимость в каждом шаге
        J_history.append(computeCost(X, y, theta))

    return theta, J_history

Объяснение Реализации

  1. Инициализация параметров: Мы создаем копию параметров ( \theta ), чтобы сохранить оригинальные значения.

  2. Расчет предсказаний: Мы используем векторное произведение для получения предсказаний predictions.

  3. Выявление ошибок: Сравниваем предсказания и истинные значения для получения ошибки.

  4. Обновление параметров: Обновление выполняется с использованием векторного умножения и без вызова np.sum(), что позволяет обновлять параметры корректно, сохраняя размерность.

  5. Сохранение стоимости: Мы сохраняем значение функции потерь (computeCost) на каждой итерации для анализа эффективности модели.

Эта реализация является более эффективной и соответствует принципам векторизации в NumPy, что значительно снижает время выполнения по сравнению с решением с использованием вложенных циклов.

Заключение

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

Оцените материал
Добавить комментарий

Капча загружается...