Вопрос или проблема
Недавно я начал курс по машинному обучению от Эндрю Нга, и вот формула, которую Эндрю приводит для расчета градиентного спуска на линейной модели.
$$ \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
Объяснение Реализации
-
Инициализация параметров: Мы создаем копию параметров ( \theta ), чтобы сохранить оригинальные значения.
-
Расчет предсказаний: Мы используем векторное произведение для получения предсказаний
predictions
. -
Выявление ошибок: Сравниваем предсказания и истинные значения для получения ошибки.
-
Обновление параметров: Обновление выполняется с использованием векторного умножения и без вызова
np.sum()
, что позволяет обновлять параметры корректно, сохраняя размерность. -
Сохранение стоимости: Мы сохраняем значение функции потерь (
computeCost
) на каждой итерации для анализа эффективности модели.
Эта реализация является более эффективной и соответствует принципам векторизации в NumPy, что значительно снижает время выполнения по сравнению с решением с использованием вложенных циклов.
Заключение
Правильное применение формул градиентного спуска важно для успешного обучения моделей машинного обучения. Понимание различий между скалярными и векторными операциями является ключом к корректной реализации алгоритма. Используйте предложенный пример кода как основу для своей работы и убедитесь, что вы понимаете каждый шаг.