введение

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

вот уравнение линейной регрессии предположим, что у вас есть переменные 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, чтобы увидеть, как это повлияет на результаты.

Надеюсь, поможет. если вы нашли какие-либо ошибки в этой статье, предложите исправление. Спасибо!

ресурс