Введение:

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

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

Математика простого градиентного спуска:

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

Предположим, у нас есть функция f(x) = mx + b, где m — наклон, а b — точка пересечения по оси y. Наша цель — найти значения m и b, минимизирующие функцию стоимости или потерь.

Функцию затрат или потерь можно определить как среднеквадратичную ошибку (MSE), которая определяется как:

MSE = (1/n) * Σ(yi - (mx + b))²

Где n — количество точек данных, yi — фактическое значение, а (mx + b) — прогнозируемое значение.

Основная цель простого градиентного спуска — минимизировать функцию стоимости путем итеративного обновления значений m и b, пока мы не достигнем точки минимума. Правило обновления для параметров задается:

m = m - α * d/dm(MSE) 
b = b - α * d/db(MSE)

Где α - скорость обучения, которая определяет размер шага алгоритма градиентного спуска. d/dm(MSE) и d/db(MSE) — частные производные функции стоимости по m и b соответственно.

Реализация простого градиентного спуска в Python:

Теперь давайте реализуем алгоритм простого градиентного спуска на Python. Мы будем использовать библиотеку NumPy для создания набора данных и выполнения необходимых вычислений.

Во-первых, давайте импортируем необходимые библиотеки:

import numpy as np
import matplotlib.pyplot as plt

Далее мы создадим простую линейную функцию с некоторым случайным шумом:

np.random.seed(0)
m = 2
b = 3
X = np.random.rand(100, 1)
y = b + m * X + np.random.randn(100, 1)

Здесь мы создали линейную функцию y = mx + b, где m = 2 и b = 3, и добавили к данным случайный шум.

Теперь давайте определим функцию стоимости (MSE) и алгоритм градиентного спуска:

Функция compute_cost вычисляет стоимость (MSE) с учетом входных признаков X, цели y и весов w. Он определяется как:

def compute_cost(X, y, w):
    n_samples = len(y)
    y_pred = X.dot(w)
    cost = np.sum((y_pred - y) ** 2) / (2 * n_samples)
    return cost

Функция gradient_descent выполняет пакетный градиентный спуск для обновления весов w в направлении отрицательного градиента функции стоимости. Он принимает в качестве входных данных функции X, цель y, начальные веса w_init, скорость обучения lr и количество итераций n_iterations. Он возвращает окончательные веса w и список затрат cost_history на каждой итерации.

def gradient_descent(X, y, w_init, lr, n_iterations):
    n_samples, n_features = X.shape
    w = w_init
    cost_history = []
    for i in range(n_iterations):
        y_pred = X.dot(w)
        grad = X.T.dot(y_pred - y) / n_samples
        w -= lr * grad
        cost = compute_cost(X, y, w)
        cost_history.append(cost)
    return w, cost_history

Давайте теперь применим наш алгоритм градиентного спуска к простой задаче линейной регрессии. Мы будем использовать набор данных Boston Housing, который является встроенным набором данных в sci-kit-learn.

from sklearn.datasets import fetch_california_housing
import numpy as np
import matplotlib.pyplot as plt

# Load the Boston Housing dataset
calif = fetch_california_housing()
# Get the input features X and target y
X = calif.data[:, 5]  # Use only one feature (average number of rooms per dwelling)
y = calif.target
# Add a column of ones to X to account for the intercept term
X = np.c_[np.ones(X.shape[0]), X]
# Initialize the weights to zeros
w_init = np.zeros(X.shape[1])
# Set the hyperparameters
lr = 0.01
n_iterations = 1000
# Run gradient descent
w, cost_history = gradient_descent(X, y, w_init, lr, n_iterations)
# Print the final weights and the first few costs
print("Final weights:", w)
print("First few costs:", cost_history[:5])

Вывод будет:

Final weights: [2.07645362   -0.00263202]
First few costs: [2.614287944294635, 2.574050115411683, 2.538992814628236, 2.5047110583949337, 2.4710605408142827]

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

plt.plot(range(n_iterations), cost_history)
plt.xlabel("Iteration")
plt.ylabel("Cost")
plt.title("Gradient Descent")
plt.show()

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

Как мы видим, наш простой алгоритм градиентного спуска способен найти оптимальные веса для нашей задачи линейной регрессии. Однако этот алгоритм имеет некоторые ограничения. Одним из основных ограничений является то, что требуется, чтобы весь набор данных помещался в память, что может оказаться невыполнимым для очень больших наборов данных. В следующей статье мы обсудим стохастический градиентный спуск, который является расширением градиентного спуска, устраняющим это ограничение.