Линейная регрессия — решение линейной регрессии с помощью алгоритма градиентного спуска

искусственный интеллект

Введение в алгоритмы

Линейная регрессия — один из самых простых алгоритмов регрессии. Простое введение в алгоритм: приведите пример двумерной плоскости. В математике средней школы можно найти прямую линию, если известны две точки. Ну и что, если дали 100 баллов. Как получить прямую? Невозможно уложить прямую линию в 100 точек. Должны быть ошибки, поэтому цель линейного программирования — уложить эти 100 точек прямой линией. минимизировать общую ошибку. Регрессия часто используется для изучения линейных отношений между целями и функциями. В основном используется для предиктивной аналитики.

Алгоритмическая сцена

Используем код для построения сценария использования алгоритма.Сначала определим n точек. Посмотрите на распределение этих n точек.

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

X = np.arange(0,10,0.05).reshape(-1, 1)
np.random.seed(100)
y = 0.5 * np.arange(0,10,0.05) + 3 + np.random.rand(len(X))

plt.scatter(X.reshape(1, -1), y)

Результат показан ниже:

Можно видеть, что n точек, которые мы намеренно построили, вероятно, находятся околоy=θ*x+by = \theta * x + bЭта прямая распределена.

Так что теперь есть проблема, если дана новая точка, абсциссаx0x_0, пожалуйста, предскажите ординатуy0y_0.

Модель алгоритма

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

Сначала предположим, что образец составляет около

y^=θx+b\шляпа у = \тета · х + б

Эта линия распределена.y^\hat yпредставляет прогнозируемое значение. Тогда задача решения модели линейной регрессии трансформируется в решение параметровθ\thetaиbb.

Как упоминалось на предыдущем шаге, цель линейной регрессии — найти такую ​​линию, которая минимизирует ошибку во всех точках. Какие критерии мы используем для выражения «ошибки», чтобы найти наилучшие параметры?

Теперь мы знаем, что характеристики определенного образцаxix_i. Одновременные образцыxix_iсоответствующее целевое значениеyiy_iтакже известно.

Мы используем параметрθ\thetaиbbРассчитать прогнозируемое значениеy^i\hat y_i:

y^i=θx+b\шляпа y_i = \тета х + б

Затем определите потери для одной точки

Costi=(y^iyi)2Cost_i =(\hat y_i - y_i)^2

То есть квадрат расстояния между истинным значением и прогнозируемым значением. Тогда сумма ошибок n точек будет следующей:

L=1ni=1n(y^iyi)2L = \frac {1} {n} \sum _{i=1} ^{n} {(\hat y_i - y_i)^2}

Подставив параметры, получим:

L(θ,b)=1ni=1n(θxi+byi)2L(\theta,b) = \frac {1} {n} \sum _{i=1} ^{n} (\theta ·x_i + b - y_i)^2

Это функция потерь, которую мы собираемся определить. Итак, теперь проблема трансформировалась в

(θ*,b*)=argmin(θ,b)i=1n(θxi+byi)2(\theta^*, b^*) = arg \min _{(\theta, b)} \sum _{i=1} ^{n} (\theta · x_i + b - y_i)^2

то есть решитьθ\thetaи ${b} заставляют функцию потерь иметь минимальное значение.

решение модели

Существует два распространенных способа решения моделей линейной регрессии: (1) метод наименьших квадратов (2) алгоритм градиентного спуска.

Метод наименьших квадратов прост для решения, идея состоит в том, чтобы вычислитьLправильноθиbL для \ тета и bк производной, так что формула равна 0. В этом случае формула имеет минимальное значение. Детали пока не обсуждаются.

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

Алгоритм градиентного спуска решает процесс линейной регрессии:

(1) Инициализацияθ\thetaиbbявляется небольшим значением, не равным 0.

(2) с токомθ\thetaиbbРешение для предсказанных значенийy^\hat y.

(3) Если прогнозируемое значениеy^\hat yи фактическое значение прогнозируемое значениеyyошибка больше, чемϵ\epsilon, перейдите к (4); если прогнозируемое значениеy^\hat yи фактическое значение прогнозируемое значениеyyошибка меньше чемϵ\epsilon, возвращает текущийθ\thetaиbb. Решение заканчивается. (4) Решите частную производную функции потерь и обновите параметрыθ\thetaиbb:

θθнLθ\theta \leftarrow \theta - \eta \frac {\partial L} {\partial \theta}
bbнLbb \leftarrow b - \eta \frac {\partial L} {\partial b}

Реализуйте алгоритм градиентного спуска самостоятельно

class LinearRegression:
    def __init__(self, eta=0.02, n_iters = 1e4, epsilon = 1e-3):
        
        self._theta = None
        self._eta = eta
        self._n_iters = n_iters
        self._epsilon = epsilon
        
    def Cost(self, theta, X_b, y):
        try:
            return np.sum(np.square(y - X_b.dot(theta))) / len(X_b)
        except:
            return float('inf')

    def dCost(self, theta, X_b, y):
        return (X_b.dot(theta) - y).dot(X_b) * 2 / len(X_b)

    def gradient_descent(self, X_b, y, initial_theta, eta, n_iters, epsilon):
        theta = initial_theta
        eta = self._eta
        n_iters = self._n_iters
        epsilon = self._epsilon
        
        i_iter = 0
        try:
            while i_iter < n_iters:
                gradient = self.dCost(theta, X_b, y)
                last_theta = theta
                theta = theta - eta * gradient

                diff = abs(self.Cost(theta, X_b, y) - self.Cost(last_theta, X_b, y))
                if (diff > 1e100):
                    print('eta is too large!')
                    break
                if (diff  < epsilon):
                    break
                i_iter += 1

            self._theta = theta
        except:
            self._theta = float('inf')
        return self

    def fit(self, X_train, y_train):
        assert X_train.shape[0] == y_train.shape[0], 'train data has wrong dimenssion'
        self._X_train = X_train
        self._y_train = y_train
        
        X_b = np.hstack([np.ones((len(self._X_train), 1)), self._X_train])
        initial_theta = np.zeros(X_b.shape[1])
        eta = 0.01
        
        self.gradient_descent(X_b, y_train, initial_theta, self._eta, self._n_iters, self._epsilon)
        
        return self
    
    def predict(self, X_test):
        X_b = np.hstack([np.ones((len(X_test), 1)), X_test])
        return np.dot(X_b, self._theta)

Приведенный выше код сохраняется в виде отдельного файла, на который можно ссылаться и вызывать его позже. Уведомление в нашей модели

X_train=[X_train[0][0]X_train[0][1]X_train[0][2]...X_train[0][m]X_train[1][0]X_train[1][1]X_train[1][2]...X_train[1][m]...X_train[n][0]X_train[0][1]X_train[0][2]...X_train[n][m]]X\_train = \left [ \begin{matrix} X\_train[0][0] & X\_train[0][1] & X\_train[0][2] & ... & X\_train[0][m] \\ X\_train[1][0] & X\_train[1][1] & X\_train[1][2] & ... & X\_train[1][m] \\ ... \\ X\_train[n][0] & X\_train[0][1] & X\_train[0][2] & ... & X\_train[n][m] \\ \end{matrix} \right]

в соответствующем коде

X_b=[1X_train[0][0]X_train[0][1]X_train[0][2]...X_train[0][m]1X_train[1][0]X_train[1][1]X_train[1][2]...X_train[1][m]...1X_train[n][0]X_train[0][1]X_train[0][2]...X_train[n][m]]X\_b = \left [ \begin{matrix} 1 & X\_train[0][0] & X\_train[0][1] & X\_train[0][2] & ... & X\_train[0][m] \\ 1 & X\_train[1][0] & X\_train[1][1] & X\_train[1][2] & ... & X\_train[1][m] \\ ... \\ 1 & X\_train[n][0] & X\_train[0][1] & X\_train[0][2] & ... & X\_train[n][m] \\ \end{matrix} \right]

В нашей модели с одной функцией это на самом деле:

X_b=[1X_train[0]1X_train[1]...1X_train[n]]X\_b = \left [\begin{matrix} 1 & X\_train[0]\\ 1 & X\_train[1]\\ ... \\ 1 & X\_train[n]\\ \end{matrix} \right]
_theta=[bθ]\_theta = \left [\begin{matrix} b & \theta\\ \end{matrix} \right]

После матричных операций можно найти, что

X_trainθ+b==X_b_thetaX\_train ·\theta + b == X\_b ·\_theta

Левая часть уравнения представляет собой представление в модели, а правая часть уравнения представляет собой представление в коде Это сделано для облегчения матричных операций.

Вызов модели линейного программирования, написанной вами

linear_regression = LinearRegression(
    epsilon = 1e-6,
    eta = 0.02
)

linear_regression.fit(X_train, y_train)
y_predict = linear_regression.predict(X_test)
params = linear_regression._theta

Результат выглядит следующим образом:

array([3.45402168, 0.51257892])

То есть решение получается:

θ=0.51257892\theta = 0.51257892
b=3.45402168b = 3.45402168

На самом деле, она очень близка к кривой распределения при первоначальном построении этой партии образцов. Нарисуем точки и линии на одном графике и посмотрим на эффект:

plt.scatter(X.reshape(1, -1), y)

theta = params[1]
b = params[0]

X = np.arange(0,10,0.05).reshape(-1, 1)
y_predict = theta * X + b

plt.scatter(X, y_predict)

Результат выглядит следующим образом:

На самом деле эта партия образцов была хорошо подогнана.

Уведомление:

Значение eta должно быть соответствующим. Если значение слишком мало, результат будет слишком медленным для сходимости. После выполнения заданного количества итераций он не сошелся к нужному нам значению; если он слишком велик, он не только не сойдётся, но и произойдёт взрывное расхождение.

эпсилон - это диапазон ошибки, который мы даем, когда ошибка эпсилон меньше определенного значения, мы думаем, что прямая линия удовлетворила точность подгонки.

Вызов модели линейного программирования в sklearn

Далее вызовем модель в sklearn, чтобы увидеть эффект:

from sklearn.linear_model import LinearRegression
linear_regression = LinearRegression()

linear_regression.fit(X_train, y_train)
y_predict = linear_regression.predict(X_test)
linear_regression.coef_, linear_regression.intercept_

Результат выглядит следующим образом:

(array([0.51042252]), 3.4682654875517605)

То есть через линейную модель в sklearn получается решение:

θ=0.51042252\theta = 0.51042252
b=3.4682654875517605b = 3.4682654875517605

Взгляните на график подгонки:

Результаты оказались неразличимы невооруженным глазом.

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

Суммировать

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