введение
Линейная регрессия — один из самых популярных и используемых алгоритмов в области машинного обучения. этот алгоритм будет соответствовать линейной линии между предикторами и переменными отклика. уравнение подобранной линии будет использоваться для интерпретации взаимосвязи между переменными, а также для прогнозирования.
вот уравнение линейной регрессии предположим, что у вас есть переменные x и y
мы попытаемся найти наклон (m) и точку пересечения (b), чтобы уравнение работало.
Я уже написал учебник о том, как найти наклон и точку пересечения случая линейной регрессии, используя обычный метод наименьших квадратов, который вы можете увидеть здесь. на этот раз мы вычислим m и b методом градиентного спуска.
градиентный спуск — это средство, которое помогает алгоритму машинного обучения находить минимальную ошибку посредством нескольких испытаний или итераций оценок и обновлений весов.
процедура градиентного спуска
давайте разберемся, как работает процедура. сначала нам нужно инициализировать значение для m и b, чтобы начать. пусть 0 для m и b. используйте эти значения, чтобы предсказать результат для первой итерации.
после этого мы оцениваем, насколько была допущена ошибка. есть много функций ошибок, которые мы можем использовать для задачи регрессии. здесь мы собираемся использовать SSE (сумма квадратов ошибок).
градиентный спуск идет после этой части. мы будем использовать его для обновления наклона и точки пересечения. это скажет алгоритму, следует ли добавить или вычесть. вот процедура обновления
lopeGradient = производная (ошибка) относительно m (наклон)
interceptGradient = производная (ошибка) относительно b (пересечение)
LearningRate = контролирует, насколько будет обновляться
затем мы делаем новый прогноз, используя новый наклон и точку пересечения, а также вычисляем ошибку. мы делаем это столько раз, сколько нужно модели, пока она не найдет минимальную ошибку.
Python реализация градиентного спуска
давайте посмотрим, как мы реализуем это в python. Вот библиотеки, которые нам понадобятся.
import pandas as pd import numpy as np import sympy as sy import scipy as sp import matplotlib.pyplot as plt from sklearn.datasets import load_diabetes
мы будем использовать набор данных о диабете из scikit-learn.
data = load_diabetes() diab = pd.DataFrame(data.data,columns = data.feature_names) diab['target'] = data.target
чтобы все было просто, мы возьмем только одну переменную x. я возьму ИМТ из набора данных. хотя вы можете выбрать то, что вы предпочитаете.
x_var = diab.bmi y_var = diab.target
давайте посмотрим на наши данные
plt.plot(x_var,y_var,'.') plt.xlabel('bmi') plt.ylabel('diabetes');
похоже, что ИМТ и диабет имеют положительную линейную зависимость. для начала мы инициируем наклон и перехват до нуля
slope, intercept = 0, 0
подключите наклон и точку пересечения к уравнению, чтобы предсказать результат
yhat = slope*x_var+intercept
теперь давайте посмотрим, как наша первая модель выглядит визуально
plt.plot(x_var,y_var,'.') plt.plot(x_var,yhat,label = 'y = 0x+0') plt.xlabel('bmi') plt.ylabel('diabetes') plt.legend();
так выглядит наша исходная линия регрессии. давайте оценим нашу модель, используя функцию ошибки SSE
error = sum((y_var-yhat)**2) print(error) 12850921.0
12 миллионов это наша первоначальная ошибка
теперь мы хотим минимизировать ошибку, обновив наклон и точку пересечения.
Процедура обновления следующая:
Скорость обучения здесь будет фильтровать, насколько сильно мы хотим изменить наклон или точку пересечения. обычно между 0–1.
градиент наклона — это градиент нашей функции ошибок по отношению к текущему наклону.
Градиент точки пересечения — это градиент нашей функции ошибок по отношению к текущей точке пересечения.
мы можем найти градиенты, используя библиотеку sympy
чтобы использовать эту библиотеку, нам нужно указать все обозначения/символы в нашей функции ошибок. для лучшего понимания вы можете посмотреть мой туториал Как использовать sympy для производных операций.
убедитесь, что вы не перекрываете имена обозначений с другими именами переменных, которые у вас есть. потому что позже он выдаст ошибку.
сначала мы возьмем производную/градиент квадратов ошибок/остатков (y-yhat)², а затем просуммируем все это позже.
x,y,m,b = sy.symbols('x y m b') errorFunc = (y-(m*x+b))**2
вот как мы получаем уравнение нашей errorFunc.
квадрат ошибки/остатки = (y−yhat)² (без суммы)
if:
yhat = mx+b
затем:
квадрат ошибки/остатки = (y−(mx+b))2
чтобы обновить наклон или точку пересечения, нам нужен градиент нашей функции ошибок. градиент скажет нам, является ли крутизна ошибки положительной (означает, что ошибка увеличивается, становится больше) или отрицательной (означает, что ошибка уменьшается или становится меньше).
градиент функции ошибки относительно точки пересечения (b)
sy.diff(errorFunc,b)
градиент функции ошибок по наклону (м)
sy.diff(errorFunc,m)
мы создадим две разные функции Python, содержащие эти функции градиента ошибок.
#gradient w.r.t slope def gradSlope(x,y,m,b): gradient = -2*x*(-b-m*x+y) gradient = sum(gradient) return gradient #gradient w.r.t intercept def gradIntercept(x,y,m,b): gradient = 2*b+2*m*x-2*y gradient = sum(gradient) return gradient
прежде чем мы выполним итерацию градиентного спуска, я предлагаю задокументировать все наклоны, точки пересечения и ошибки. это поможет нам позже при создании графиков
#create lists of iteration results for plotting purpose slopes = [] intercepts = [] errors = [] #add the very first slope, intercept, and error slopes.append(slope) intercepts.append(intercept) errors.append(error)
для первого испытания давайте сделаем 3 итерации, просто чтобы мы поняли, как это работает, прежде чем поместить все это в цикл.
#iteration 1 prevSlope = slope prevIntercept = intercept newSlope = prevSlope-(0.001*gradSlope(x_var,y_var,prevSlope,prevIntercept)) newIntercept = prevIntercept-(0.001*gradIntercept(x_var,y_var,prevSlope,prevIntercept)) yhat = x_var*newSlope+newIntercept error = sum((y_var-yhat)**2) slopes.append(newSlope) intercepts.append(newIntercept) errors.append(error) #------------------------------------------------------------------ #iteration 2 prevSlope = newSlope prevIntercept = newIntercept newSlope = prevSlope-(0.001*gradSlope(x_var,y_var,prevSlope,prevIntercept)) newIntercept = prevIntercept-(0.001*gradIntercept(x_var,y_var,prevSlope,prevIntercept)) yhat = x_var*newSlope+newIntercept error = sum((y_var-yhat)**2) slopes.append(newSlope) intercepts.append(newIntercept) errors.append(error) #------------------------------------------------------------------ #iteration 3 prevSlope = newSlope prevIntercept = newIntercept newSlope = prevSlope-(0.001*gradSlope(x_var,y_var,prevSlope,prevIntercept)) newIntercept = prevIntercept-(0.001*gradIntercept(x_var,y_var,prevSlope,prevIntercept)) yhat = x_var*newSlope+newIntercept error = sum((y_var-yhat)**2) slopes.append(newSlope) intercepts.append(newIntercept) errors.append(error)
теперь мы можем построить результаты, чтобы увидеть, как выглядят первые три итерации.
plt.figure(figsize = (20,7)) plt.subplot(1,2,1) plt.plot(errors) plt.xlabel('iteration') plt.ylabel('sum of squared errors (SSE)') plt.subplot(1,2,2) plt.plot(x_var,y_var,'.') for i in range(len(slopes)-1): plt.plot(x_var,x_var*slopes[i]+intercepts[i],label = f'iteration {i}') plt.xlabel('bmi') plt.ylabel('diabetes') plt.legend()
хороший! мы видим снижение ошибки, и линия регрессии пытается подогнать себя и двигаться к нашим точкам данных
ниже приведена функция, которая реализует оптимизацию градиентного спуска. это в основном то же самое, но немного отличается от кода, который мы использовали в предыдущих 3 итерациях.
def gradient_descent(x_var,y_var,init_slope=0,init_intercept=0,learning_rate = 0.01,n_iter = 100): #document the results slopes = [] intercepts = [] errors = [] #add the first initial slope and intercept slopes.append(init_slope) intercepts.append(init_intercept) #calculate the initial error and append it to errors list yhat = x_var*init_slope+init_intercept first_error = sum((y_var-yhat)**2) errors.append(first_error) #loop through n_iter for i in range(n_iter): #define previous slope and intercept prevSlope = slopes[-1] prevIntercept = intercepts[-1] #update the slope and intercept newSlope = prevSlope-(learning_rate*gradSlope(x_var,y_var,prevSlope,prevIntercept)) newIntercept = prevIntercept-(learning_rate*gradIntercept(x_var,y_var,prevSlope,prevIntercept)) #predict data using the new slope and intercept yhat = x_var*newSlope+newIntercept error = sum((y_var-yhat)**2) #append new slope, intercept, and error slopes.append(newSlope) intercepts.append(newIntercept) errors.append(error) #return the results return slopes,intercepts,errors
давайте проверим нашу функцию градиентного спуска
slopes,intercepts,errors = gradient_descent(x_var,y_var, init_slope = 0, init_intercept = 0, learning_rate = 0.001, n_iter = 3000)
наша первая ошибка была 12 миллионов. давайте посмотрим наименьшую ошибку, которую мы получили после реализации градиентного спуска.
min(errors) 1719587.283181484
с 12 миллионов до 1,7 миллиона
давайте визуализируем результаты
plt.figure(figsize = (20,7)) plt.subplot(1,2,1) plt.plot(errors) plt.xlabel('iteration') plt.ylabel('sum of squared errors (SSE)') plt.subplot(1,2,2) plt.plot(x_var,y_var,'.') plt.plot(x_var,x_var*slopes[0]+intercepts[0],label = 'iteration 0') plt.plot(x_var,x_var*slopes[10]+intercepts[10],label = 'iteration 10') plt.plot(x_var,x_var*slopes[1000]+intercepts[1000],label = 'iteration 1000') plt.plot(x_var,x_var*slopes[3000]+intercepts[3000],label = 'iteration 3000') plt.xlabel('diabetes') plt.ylabel('bmi') plt.legend();
если вы хотите получить доступ к наклону и перехватить с наименьшей ошибкой, вы можете сделать следующее
lowest_error = min(errors) lowest_error_index = errors.index(lowest_error) slope_lowest_error = slopes[lowest_error_index] intercept_lowest_error = intercepts[lowest_error_index] print(f'slope = {slope_lowest_error}\nintercept = {intercept_lowest_error}\nerror = {lowest_error}')
заключение
это почти все, что я знаю о том, как реализовать градиентный спуск с нуля в python. вы можете поэкспериментировать с параметрами, например, попробуйте изменить learning_rate, чтобы увидеть, как это повлияет на результаты.
Надеюсь, поможет. если вы нашли какие-либо ошибки в этой статье, предложите исправление. Спасибо!