Die artikel bespreek verskeie maniere om die wiskundige vergelyking van 'n eenvoudige (paar) regressielyn te bepaal.
Alle metodes om die vergelyking wat hier oorweeg word op te los, is gebaseer op die kleinste kwadrate metode. Ons noem die metodes soos volg:
- Analitiese oplossing
- gradiënt afkoms
- Stogastiese Gradiënt Afkoms
Vir elk van die maniere om die vergelyking van 'n reguit lyn op te los, bied die artikel verskeie funksies aan, wat hoofsaaklik verdeel is in dié wat geskryf is sonder om die biblioteek te gebruik Numpy en dié wat vir berekeninge gebruik word Numpy. Daar word geglo dat die vaardige gebruik Numpy sal die koste van rekenaars verminder.
Alle kode in hierdie artikel is geskryf in python 2.7 met Jupyter Notaboek. Die bronkode en die voorbeelddatalêer is beskikbaar by
Die artikel is meer gefokus op beide beginners en diegene wat reeds geleidelik die studie van 'n baie uitgebreide afdeling in kunsmatige intelligensie - masjienleer - begin bemeester het.
Kom ons gebruik 'n baie eenvoudige voorbeeld om die materiaal te illustreer.
Voorbeeld voorwaardes
Ons het vyf waardes wat verslawing kenmerk Y van X (Tabel #1):
Tabel No. 1 "Voorwaardes van die voorbeeld"
Ons sal aanvaar dat die waardes is die maand van die jaar, en - verdienste hierdie maand. Met ander woorde, inkomste hang af van die maand van die jaar, en - die enigste teken waarvan inkomste afhang.
Die voorbeeld is so-so, beide in terme van die voorwaardelike afhanklikheid van inkomste op die maand van die jaar, en in terme van die aantal waardes - daar is baie min van hulle. So 'n vereenvoudiging sal egter toelaat, soos hulle op die vingers sê, om die materiaal wat deur beginners geassimileer word, te verduidelik, nie altyd met gemak nie. En ook die eenvoud van die getalle sal diegene wat die voorbeeld op "papier" wil oplos sonder noemenswaardige arbeidskoste toelaat.
Gestel dat die afhanklikheid wat in die voorbeeld gegee word redelik goed benader kan word deur die wiskundige vergelyking van die lyn van 'n eenvoudige (paar) regressie van die vorm:
waar is die maand waarin die opbrengs ontvang is, - inkomste wat ooreenstem met die maand, и is die regressiekoëffisiënte van die geskatte lyn.
Let daarop dat die koëffisiënt dikwels na verwys as die helling of gradiënt van die geskatte lyn; is die bedrag waarmee die wanneer dit verander .
Dit is duidelik dat ons taak in die voorbeeld is om sulke koëffisiënte in die vergelyking te kies и , waarteen die afwykings van ons beraamde inkomstewaardes per maande vanaf die ware antwoorde, d.w.s. waardes wat in die steekproef aangebied word, sal minimaal wees.
Kleinste vierkante metode
Volgens die kleinste kwadrate-metode moet die afwyking bereken word deur dit te kwadraeer. So 'n tegniek maak dit moontlik om die wedersydse terugbetaling van afwykings te vermy, as hulle teenoorgestelde tekens het. Byvoorbeeld, as in een geval, die afwyking is +5 (plus vyf), en in die ander -5 (minus vyf), dan sal die som van die afwykings wedersyds gekanselleer word en 0 (nul) wees. Dit is moontlik om nie die afwyking te kwadraat nie, maar om die modulus-eienskap te gebruik en dan sal al die afwykings positief wees en sal ophoop. Ons sal nie in detail op hierdie punt stilstaan nie, maar bloot aandui dat dit vir die gerief van berekeninge gebruiklik is om die afwyking te vier.
Dit is hoe die formule lyk, met behulp waarvan ons die kleinste som van kwadraatafwykings (foute) sal bepaal:
waar is 'n funksie van benadering van ware antwoorde (dit is die inkomste wat deur ons bereken word),
is die ware antwoorde (inkomste verskaf in die steekproef),
is die indeks van die steekproef (die nommer van die maand waarin die afwyking bepaal word)
Kom ons differensieer die funksie, definieer die parsiële differensiaalvergelykings, en wees gereed om aan te beweeg na 'n analitiese oplossing. Maar eers, kom ons neem 'n kort afwyking oor wat differensiasie is en onthou die meetkundige betekenis van die afgeleide.
Differensiasie
Differensiasie is die operasie om die afgeleide van 'n funksie te vind.
Waarvoor is die afgeleide? Die afgeleide van 'n funksie kenmerk die tempo van verandering van die funksie en wys vir ons die rigting daarvan. As die afgeleide op 'n gegewe punt positief is, neem die funksie toe; anders neem die funksie af. En hoe groter die waarde van die modulo-afgeleide, hoe hoër is die tempo van verandering van die funksiewaardes, asook hoe steiler die helling van die funksiegrafiek.
Byvoorbeeld, onder die toestande van 'n Cartesiese koördinaatstelsel, is die waarde van die afgeleide by die punt M(0,0) gelyk aan +25 beteken dat op 'n gegewe punt, wanneer die waarde verskuif word na regs deur 'n konvensionele eenheid, waarde verhoog met 25 konvensionele eenhede. Op die grafiek lyk dit na 'n redelik steil hoek van styging in waardes vanaf 'n gegewe punt.
Nog 'n voorbeeld. Die waarde van die afgeleide is -0,1 beteken dat wanneer verskuiwing per een konvensionele eenheid, die waarde verminder met slegs 0,1 konvensionele eenheid. Terselfdertyd, op die grafiek van die funksie, kan ons 'n skaars merkbare afwaartse helling waarneem. As ons 'n analogie met 'n berg trek, is dit asof ons baie stadig met 'n sagte helling van 'n berg afklim, anders as die vorige voorbeeld, waar ons baie steil pieke moes neem :)
Dus, na die differensieer van die funksie per kans и , definieer ons die vergelykings van parsiële afgeleides van die 1ste orde. Nadat ons die vergelykings gedefinieer het, sal ons 'n stelsel van twee vergelykings kry, wat oplos wat ons sulke waardes van die koëffisiënte kan kies и , waarteen die waardes van die ooreenstemmende afgeleides by gegewe punte met 'n baie, baie klein hoeveelheid verander, en in die geval van 'n analitiese oplossing glad nie verander nie. Met ander woorde, die foutfunksie by die gevind koëffisiënte sal 'n minimum bereik, aangesien die waardes van gedeeltelike afgeleides by hierdie punte gelyk aan nul sal wees.
Dus, volgens die reëls van differensiasie, die vergelyking van die parsiële afgeleide van die 1ste orde met betrekking tot die koëffisiënt sal die vorm aanneem:
1ste orde parsiële afgeleide vergelyking met betrekking tot sal die vorm aanneem:
As gevolg hiervan het ons 'n stelsel van vergelykings gekry wat 'n redelik eenvoudige analitiese oplossing het:
begin{vergelyking*}
begin{gevalle}
na + bsumlimits_{i=1}^nx_i - sumlimits_{i=1}^ny_i = 0
sumlimits_{i=1}^nx_i(a +bsumlimits_{i=1}^nx_i - sumlimits_{i=1}^ny_i) = 0
einde{gevalle}
einde{vergelyking*}
Voordat ons die vergelyking oplos, kom ons laai vooraf, kontroleer die korrektheid van die laai en formateer die data.
Laai en formateer data
Daar moet kennis geneem word dat as gevolg van die feit dat ons die kode in twee variasies sal gebruik vir die analitiese oplossing, en in die toekoms vir gradiënt en stogastiese gradiënt afkoms: die gebruik van die biblioteek Numpy en sonder om dit te gebruik, benodig ons die toepaslike dataformatering (sien kode).
Data laai en verwerking kode
# импортируем все нужные нам библиотеки
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import math
import pylab as pl
import random
# графики отобразим в Jupyter
%matplotlib inline
# укажем размер графиков
from pylab import rcParams
rcParams['figure.figsize'] = 12, 6
# отключим предупреждения Anaconda
import warnings
warnings.simplefilter('ignore')
# загрузим значения
table_zero = pd.read_csv('data_example.txt', header=0, sep='t')
# посмотрим информацию о таблице и на саму таблицу
print table_zero.info()
print '********************************************'
print table_zero
print '********************************************'
# подготовим данные без использования NumPy
x_us = []
[x_us.append(float(i)) for i in table_zero['x']]
print x_us
print type(x_us)
print '********************************************'
y_us = []
[y_us.append(float(i)) for i in table_zero['y']]
print y_us
print type(y_us)
print '********************************************'
# подготовим данные с использованием NumPy
x_np = table_zero[['x']].values
print x_np
print type(x_np)
print x_np.shape
print '********************************************'
y_np = table_zero[['y']].values
print y_np
print type(y_np)
print y_np.shape
print '********************************************'
Visualisering
Nou, nadat ons eerstens die data gelaai het, tweedens die korrektheid van die laai nagegaan het en die data uiteindelik geformateer het, sal ons die eerste visualisering uitvoer. Dikwels word hierdie metode gebruik paar plot biblioteke Seebaard. In ons voorbeeld, as gevolg van die beperkte getalle, maak dit geen sin om die biblioteek te gebruik nie Seebaard. Ons sal die gewone biblioteek gebruik matplotlib en kyk net na die verspreidingsdiagram.
Spreidiagram kode
print 'График №1 "Зависимость выручки от месяца года"'
plt.plot(x_us,y_us,'o',color='green',markersize=16)
plt.xlabel('$Months$', size=16)
plt.ylabel('$Sales$', size=16)
plt.show()
Grafiek No. 1 "Afhanklikheid van inkomste op die maand van die jaar"
Analitiese oplossing
Ons gebruik die mees algemene gereedskap in python en los die stelsel vergelykings op:
begin{vergelyking*}
begin{gevalle}
na + bsumlimits_{i=1}^nx_i - sumlimits_{i=1}^ny_i = 0
sumlimits_{i=1}^nx_i(a +bsumlimits_{i=1}^nx_i - sumlimits_{i=1}^ny_i) = 0
einde{gevalle}
einde{vergelyking*}
Volgens Cramer se reël vind 'n gemeenskaplike determinant, sowel as determinante deur en deur , waarna die determinant gedeel word deur na 'n gemeenskaplike determinant - vind die koëffisiënt , insgelyks vind ons die koëffisiënt .
Analitiese oplossing kode
# определим функцию для расчета коэффициентов a и b по правилу Крамера
def Kramer_method (x,y):
# сумма значений (все месяца)
sx = sum(x)
# сумма истинных ответов (выручка за весь период)
sy = sum(y)
# сумма произведения значений на истинные ответы
list_xy = []
[list_xy.append(x[i]*y[i]) for i in range(len(x))]
sxy = sum(list_xy)
# сумма квадратов значений
list_x_sq = []
[list_x_sq.append(x[i]**2) for i in range(len(x))]
sx_sq = sum(list_x_sq)
# количество значений
n = len(x)
# общий определитель
det = sx_sq*n - sx*sx
# определитель по a
det_a = sx_sq*sy - sx*sxy
# искомый параметр a
a = (det_a / det)
# определитель по b
det_b = sxy*n - sy*sx
# искомый параметр b
b = (det_b / det)
# контрольные значения (прооверка)
check1 = (n*b + a*sx - sy)
check2 = (b*sx + a*sx_sq - sxy)
return [round(a,4), round(b,4)]
# запустим функцию и запишем правильные ответы
ab_us = Kramer_method(x_us,y_us)
a_us = ab_us[0]
b_us = ab_us[1]
print ' 33[1m' + ' 33[4m' + "Оптимальные значения коэффициентов a и b:" + ' 33[0m'
print 'a =', a_us
print 'b =', b_us
print
# определим функцию для подсчета суммы квадратов ошибок
def errors_sq_Kramer_method(answers,x,y):
list_errors_sq = []
for i in range(len(x)):
err = (answers[0] + answers[1]*x[i] - y[i])**2
list_errors_sq.append(err)
return sum(list_errors_sq)
# запустим функцию и запишем значение ошибки
error_sq = errors_sq_Kramer_method(ab_us,x_us,y_us)
print ' 33[1m' + ' 33[4m' + "Сумма квадратов отклонений" + ' 33[0m'
print error_sq
print
# замерим время расчета
# print ' 33[1m' + ' 33[4m' + "Время выполнения расчета суммы квадратов отклонений:" + ' 33[0m'
# % timeit error_sq = errors_sq_Kramer_method(ab,x_us,y_us)
Hier is wat ons gekry het:
Dus, die waardes van die koëffisiënte word gevind, die som van die kwadraatafwykings word gestel. Kom ons trek 'n reguit lyn op die verstrooiingshistogram in ooreenstemming met die gevonde koëffisiënte.
Regressielynkode
# определим функцию для формирования массива рассчетных значений выручки
def sales_count(ab,x,y):
line_answers = []
[line_answers.append(ab[0]+ab[1]*x[i]) for i in range(len(x))]
return line_answers
# построим графики
print 'Грфик№2 "Правильные и расчетные ответы"'
plt.plot(x_us,y_us,'o',color='green',markersize=16, label = '$True$ $answers$')
plt.plot(x_us, sales_count(ab_us,x_us,y_us), color='red',lw=4,
label='$Function: a + bx,$ $where$ $a='+str(round(ab_us[0],2))+',$ $b='+str(round(ab_us[1],2))+'$')
plt.xlabel('$Months$', size=16)
plt.ylabel('$Sales$', size=16)
plt.legend(loc=1, prop={'size': 16})
plt.show()
Grafiek No. 2 "Korrekte en berekende antwoorde"
Jy kan na die grafiek van afwykings vir elke maand kyk. In ons geval sal ons geen noemenswaardige praktiese waarde daaruit put nie, maar ons sal nuuskierigheid bevredig in hoe goed die eenvoudige lineêre regressievergelyking die afhanklikheid van inkomste op die maand van die jaar kenmerk.
Afwyking grafiek kode
# определим функцию для формирования массива отклонений в процентах
def error_per_month(ab,x,y):
sales_c = sales_count(ab,x,y)
errors_percent = []
for i in range(len(x)):
errors_percent.append(100*(sales_c[i]-y[i])/y[i])
return errors_percent
# построим график
print 'График№3 "Отклонения по-месячно, %"'
plt.gca().bar(x_us, error_per_month(ab_us,x_us,y_us), color='brown')
plt.xlabel('Months', size=16)
plt.ylabel('Calculation error, %', size=16)
plt.show()
Grafiek No. 3 "Afwykings,%"
Nie perfek nie, maar ons het ons werk gedoen.
Kom ons skryf 'n funksie wat, om die koëffisiënte te bepaal и gebruik die biblioteek Numpy, meer presies, ons sal twee funksies skryf: een deur 'n pseudo-inverse matriks te gebruik (nie in die praktyk aanbeveel nie, aangesien die proses rekenaarmatig kompleks en onstabiel is), die ander met 'n matriksvergelyking.
Analitiese Oplossingskode (NumPy)
# для начала добавим столбец с не изменяющимся значением в 1.
# Данный столбец нужен для того, чтобы не обрабатывать отдельно коэффицент a
vector_1 = np.ones((x_np.shape[0],1))
x_np = table_zero[['x']].values # на всякий случай приведем в первичный формат вектор x_np
x_np = np.hstack((vector_1,x_np))
# проверим то, что все сделали правильно
print vector_1[0:3]
print x_np[0:3]
print '***************************************'
print
# напишем функцию, которая определяет значения коэффициентов a и b с использованием псевдообратной матрицы
def pseudoinverse_matrix(X, y):
# задаем явный формат матрицы признаков
X = np.matrix(X)
# определяем транспонированную матрицу
XT = X.T
# определяем квадратную матрицу
XTX = XT*X
# определяем псевдообратную матрицу
inv = np.linalg.pinv(XTX)
# задаем явный формат матрицы ответов
y = np.matrix(y)
# находим вектор весов
return (inv*XT)*y
# запустим функцию
ab_np = pseudoinverse_matrix(x_np, y_np)
print ab_np
print '***************************************'
print
# напишем функцию, которая использует для решения матричное уравнение
def matrix_equation(X,y):
a = np.dot(X.T, X)
b = np.dot(X.T, y)
return np.linalg.solve(a, b)
# запустим функцию
ab_np = matrix_equation(x_np,y_np)
print ab_np
Vergelyk die tyd wat dit geneem het om die koëffisiënte te bepaal и , volgens die 3 metodes wat aangebied word.
Kode vir Berekening Tyd Berekening
print ' 33[1m' + ' 33[4m' + "Время выполнения расчета коэффициентов без использования библиотеки NumPy:" + ' 33[0m'
% timeit ab_us = Kramer_method(x_us,y_us)
print '***************************************'
print
print ' 33[1m' + ' 33[4m' + "Время выполнения расчета коэффициентов с использованием псевдообратной матрицы:" + ' 33[0m'
%timeit ab_np = pseudoinverse_matrix(x_np, y_np)
print '***************************************'
print
print ' 33[1m' + ' 33[4m' + "Время выполнения расчета коэффициентов с использованием матричного уравнения:" + ' 33[0m'
%timeit ab_np = matrix_equation(x_np, y_np)
Op 'n klein hoeveelheid data kom 'n "selfgeskrewe" funksie na vore, wat die koëffisiënte vind deur die Cramer-metode te gebruik.
Nou kan jy aanbeweeg na ander maniere om die koëffisiënte te vind и .
gradiënt afkoms
Kom ons definieer eers wat 'n gradiënt is. Op 'n eenvoudige manier is die gradiënt 'n segment wat die rigting van maksimum groei van die funksie aandui. Na analogie van opdraande klim, waar die helling lyk, is daar die steilste klim na die top van die berg. Om die bergvoorbeeld te ontwikkel, onthou dat ons eintlik die steilste afdraande nodig het om die laagtepunt so vinnig as moontlik te bereik, dit wil sê die minimum - die plek waar die funksie nie toeneem nie en nie afneem nie. Op hierdie punt sal die afgeleide nul wees. Daarom het ons nie 'n gradiënt nodig nie, maar 'n anti-gradiënt. Om die antigradiënt te vind, moet jy net die gradiënt vermenigvuldig met -1 (minus een).
Laat ons aandag gee aan die feit dat 'n funksie verskeie minima kan hê, en nadat ons na een van hulle gedaal het volgens die algoritme wat hieronder voorgestel word, sal ons nie 'n ander minimum kan vind wat laer as die gevind een kan wees nie. Ontspan, ons is nie in gevaar nie! In ons geval het ons te doen met 'n enkele minimum, aangesien ons funksie op die grafiek is 'n gewone parabool. En soos ons almal baie goed van 'n skoolwiskundekursus behoort te weet, het 'n parabool net een minimum.
Nadat ons uitgevind het hoekom ons 'n gradiënt nodig het, en ook dat die gradiënt 'n segment is, dit wil sê 'n vektor met gegewe koördinate, wat net dieselfde koëffisiënte is и ons kan gradiënt afkoms implementeer.
Voordat ek begin, stel ek voor om net 'n paar sinne oor die afkomsalgoritme te lees:
- Ons bepaal pseudo-lukraak die koördinate van die koëffisiënte и . In ons voorbeeld sal ons koëffisiënte naby nul definieer. Dit is 'n algemene praktyk, maar elke geval kan sy eie praktyk hê.
- Van koördinaat trek die waarde van die gedeeltelike afgeleide van die 1ste orde by die punt af . Dus, as die afgeleide positief is, neem die funksie toe. Daarom, deur die waarde van die afgeleide af te trek, sal ons in die teenoorgestelde rigting van groei beweeg, dit wil sê in die rigting van afkoms. As die afgeleide negatief is, dan is die funksie op hierdie punt besig om af te neem en die waarde van die afgeleide af te trek, beweeg ons na die daling.
- Ons voer 'n soortgelyke operasie met die koördinaat uit : trek die waarde van die gedeeltelike afgeleide by die punt af .
- Om nie oor die minimum te spring nie en nie in die diep ruimte weg te vlieg nie, is dit nodig om die stapgrootte in die rigting van afkoms te stel. Oor die algemeen kan 'n mens 'n hele artikel skryf oor hoe om die stap korrek in te stel en hoe om dit tydens afdaling te verander om die koste van berekeninge te verminder. Maar nou het ons 'n effens ander taak, en ons sal die stapgrootte bepaal deur die wetenskaplike metode van "poke" of, soos hulle in die gewone mense sê, empiries.
- Sodra ons buite die gegewe koördinate is и trek die waardes van die afgeleides af, ons kry nuwe koördinate и . Ons neem die volgende stap (aftrekking), reeds vanaf die berekende koördinate. En so begin die siklus weer en weer, totdat die vereiste konvergensie bereik word.
Almal! Nou is ons gereed om te gaan soek na die diepste kloof van die Mariana-sloot. Laat ons begin.
Gradiënt-afkomskode
# напишем функцию градиентного спуска без использования библиотеки NumPy.
# Функция на вход принимает диапазоны значений x,y, длину шага (по умолчанию=0,1), допустимую погрешность(tolerance)
def gradient_descent_usual(x_us,y_us,l=0.1,tolerance=0.000000000001):
# сумма значений (все месяца)
sx = sum(x_us)
# сумма истинных ответов (выручка за весь период)
sy = sum(y_us)
# сумма произведения значений на истинные ответы
list_xy = []
[list_xy.append(x_us[i]*y_us[i]) for i in range(len(x_us))]
sxy = sum(list_xy)
# сумма квадратов значений
list_x_sq = []
[list_x_sq.append(x_us[i]**2) for i in range(len(x_us))]
sx_sq = sum(list_x_sq)
# количество значений
num = len(x_us)
# начальные значения коэффициентов, определенные псевдослучайным образом
a = float(random.uniform(-0.5, 0.5))
b = float(random.uniform(-0.5, 0.5))
# создаем массив с ошибками, для старта используем значения 1 и 0
# после завершения спуска стартовые значения удалим
errors = [1,0]
# запускаем цикл спуска
# цикл работает до тех пор, пока отклонение последней ошибки суммы квадратов от предыдущей, не будет меньше tolerance
while abs(errors[-1]-errors[-2]) > tolerance:
a_step = a - l*(num*a + b*sx - sy)/num
b_step = b - l*(a*sx + b*sx_sq - sxy)/num
a = a_step
b = b_step
ab = [a,b]
errors.append(errors_sq_Kramer_method(ab,x_us,y_us))
return (ab),(errors[2:])
# запишем массив значений
list_parametres_gradient_descence = gradient_descent_usual(x_us,y_us,l=0.1,tolerance=0.000000000001)
print ' 33[1m' + ' 33[4m' + "Значения коэффициентов a и b:" + ' 33[0m'
print 'a =', round(list_parametres_gradient_descence[0][0],3)
print 'b =', round(list_parametres_gradient_descence[0][1],3)
print
print ' 33[1m' + ' 33[4m' + "Сумма квадратов отклонений:" + ' 33[0m'
print round(list_parametres_gradient_descence[1][-1],3)
print
print ' 33[1m' + ' 33[4m' + "Количество итераций в градиентном спуске:" + ' 33[0m'
print len(list_parametres_gradient_descence[1])
print
Ons het tot onder in die Mariana-sloot geduik en daar het ons almal dieselfde waardes van die koëffisiënte gevind и wat eintlik te verwagte is.
Kom ons maak nog 'n duik, net hierdie keer sal die vulling van ons diepsee voertuig ander tegnologie wees, naamlik die biblioteek Numpy.
Gradient Descent Code (NumPy)
# перед тем определить функцию для градиентного спуска с использованием библиотеки NumPy,
# напишем функцию определения суммы квадратов отклонений также с использованием NumPy
def error_square_numpy(ab,x_np,y_np):
y_pred = np.dot(x_np,ab)
error = y_pred - y_np
return sum((error)**2)
# напишем функцию градиентного спуска с использованием библиотеки NumPy.
# Функция на вход принимает диапазоны значений x,y, длину шага (по умолчанию=0,1), допустимую погрешность(tolerance)
def gradient_descent_numpy(x_np,y_np,l=0.1,tolerance=0.000000000001):
# сумма значений (все месяца)
sx = float(sum(x_np[:,1]))
# сумма истинных ответов (выручка за весь период)
sy = float(sum(y_np))
# сумма произведения значений на истинные ответы
sxy = x_np*y_np
sxy = float(sum(sxy[:,1]))
# сумма квадратов значений
sx_sq = float(sum(x_np[:,1]**2))
# количество значений
num = float(x_np.shape[0])
# начальные значения коэффициентов, определенные псевдослучайным образом
a = float(random.uniform(-0.5, 0.5))
b = float(random.uniform(-0.5, 0.5))
# создаем массив с ошибками, для старта используем значения 1 и 0
# после завершения спуска стартовые значения удалим
errors = [1,0]
# запускаем цикл спуска
# цикл работает до тех пор, пока отклонение последней ошибки суммы квадратов от предыдущей, не будет меньше tolerance
while abs(errors[-1]-errors[-2]) > tolerance:
a_step = a - l*(num*a + b*sx - sy)/num
b_step = b - l*(a*sx + b*sx_sq - sxy)/num
a = a_step
b = b_step
ab = np.array([[a],[b]])
errors.append(error_square_numpy(ab,x_np,y_np))
return (ab),(errors[2:])
# запишем массив значений
list_parametres_gradient_descence = gradient_descent_numpy(x_np,y_np,l=0.1,tolerance=0.000000000001)
print ' 33[1m' + ' 33[4m' + "Значения коэффициентов a и b:" + ' 33[0m'
print 'a =', round(list_parametres_gradient_descence[0][0],3)
print 'b =', round(list_parametres_gradient_descence[0][1],3)
print
print ' 33[1m' + ' 33[4m' + "Сумма квадратов отклонений:" + ' 33[0m'
print round(list_parametres_gradient_descence[1][-1],3)
print
print ' 33[1m' + ' 33[4m' + "Количество итераций в градиентном спуске:" + ' 33[0m'
print len(list_parametres_gradient_descence[1])
print
Koëffisiëntwaardes и is onveranderd.
Kom ons kyk hoe die fout tydens gradiëntdaling verander het, dit wil sê hoe die som van die kwadraatafwykings met elke stap verander het.
Kode vir somkwadraatafwykingplot
print 'График№4 "Сумма квадратов отклонений по-шагово"'
plt.plot(range(len(list_parametres_gradient_descence[1])), list_parametres_gradient_descence[1], color='red', lw=3)
plt.xlabel('Steps (Iteration)', size=16)
plt.ylabel('Sum of squared deviations', size=16)
plt.show()
Grafiek #4 "Som van kwadraatafwykings in gradiëntafkoms"
Op die grafiek sien ons dat die fout met elke stap afneem, en na 'n sekere aantal iterasies neem ons 'n byna horisontale lyn waar.
Laastens, kom ons evalueer die verskil in kode-uitvoeringstyd:
Kode vir tydsberekening van gradiënt afkoms berekening
print ' 33[1m' + ' 33[4m' + "Время выполнения градиентного спуска без использования библиотеки NumPy:" + ' 33[0m'
%timeit list_parametres_gradient_descence = gradient_descent_usual(x_us,y_us,l=0.1,tolerance=0.000000000001)
print '***************************************'
print
print ' 33[1m' + ' 33[4m' + "Время выполнения градиентного спуска с использованием библиотеки NumPy:" + ' 33[0m'
%timeit list_parametres_gradient_descence = gradient_descent_numpy(x_np,y_np,l=0.1,tolerance=0.000000000001)
Miskien doen ons iets verkeerd, maar weer 'n eenvoudige "selfgeskrewe" funksie wat nie die biblioteek gebruik nie Numpy voor die berekeningstyd van die funksie wat die biblioteek gebruik Numpy.
Maar ons staan nie stil nie, maar beweeg na 'n ander opwindende manier om die eenvoudige lineêre regressievergelyking op te los. Ontmoet!
Stogastiese Gradiënt Afkoms
Om vinnig te verstaan hoe stogastiese gradiënt-afkoms werk, is dit beter om die verskille van konvensionele gradiënt-afkoms te definieer. Ons, in die geval van gradiënt afkoms, in die vergelykings van afgeleides van и gebruik die som van die waardes van alle kenmerke en ware antwoorde beskikbaar in die steekproef (dit is die som van alle и ). In stogastiese gradiënt-afkoms sal ons nie al die waardes in die steekproef gebruik nie, maar in plaas daarvan sal ons pseudo-lukraak die sogenaamde steekproefindeks kies en die waardes daarvan gebruik.
Byvoorbeeld, as die indeks gedefinieer word as nommer 3 (drie), dan neem ons die waardes и , dan vervang ons die waardes in die vergelykings van afgeleides en bepaal nuwe koördinate. Dan, nadat ons die koördinate bepaal het, bepaal ons weer die steekproefindeks op 'n pseudo-ewekansige wyse, vervang die waardes wat met die indeks ooreenstem in die parsiële differensiaalvergelykings, en bepaal die koördinate op 'n nuwe manier и ens. tot groen konvergensie. Met die eerste oogopslag kan dit lyk asof dit enigsins kan werk, maar dit werk. Dit is weliswaar opmerklik dat die fout nie met elke stap afneem nie, maar daar is beslis 'n neiging.
Wat is die voordele van stogastiese gradiënt-afkoms bo konvensionele gradiënt-afkoms? As ons steekproefgrootte baie groot is en in tienduisende waardes gemeet word, dan is dit baie makliker om byvoorbeeld 'n ewekansige duisend daarvan te verwerk as die hele steekproef. Dit is waar stogastiese gradiënt-afkoms inskop. In ons geval sal ons natuurlik nie ’n groot verskil agterkom nie.
Kom ons kyk na die kode.
Kode vir Stogastiese Gradiënt Afkoms
# определим функцию стох.град.шага
def stoch_grad_step_usual(vector_init, x_us, ind, y_us, l):
# выбираем значение икс, которое соответствует случайному значению параметра ind
# (см.ф-цию stoch_grad_descent_usual)
x = x_us[ind]
# рассчитывыаем значение y (выручку), которая соответствует выбранному значению x
y_pred = vector_init[0] + vector_init[1]*x_us[ind]
# вычисляем ошибку расчетной выручки относительно представленной в выборке
error = y_pred - y_us[ind]
# определяем первую координату градиента ab
grad_a = error
# определяем вторую координату ab
grad_b = x_us[ind]*error
# вычисляем новый вектор коэффициентов
vector_new = [vector_init[0]-l*grad_a, vector_init[1]-l*grad_b]
return vector_new
# определим функцию стох.град.спуска
def stoch_grad_descent_usual(x_us, y_us, l=0.1, steps = 800):
# для самого начала работы функции зададим начальные значения коэффициентов
vector_init = [float(random.uniform(-0.5, 0.5)), float(random.uniform(-0.5, 0.5))]
errors = []
# запустим цикл спуска
# цикл расчитан на определенное количество шагов (steps)
for i in range(steps):
ind = random.choice(range(len(x_us)))
new_vector = stoch_grad_step_usual(vector_init, x_us, ind, y_us, l)
vector_init = new_vector
errors.append(errors_sq_Kramer_method(vector_init,x_us,y_us))
return (vector_init),(errors)
# запишем массив значений
list_parametres_stoch_gradient_descence = stoch_grad_descent_usual(x_us, y_us, l=0.1, steps = 800)
print ' 33[1m' + ' 33[4m' + "Значения коэффициентов a и b:" + ' 33[0m'
print 'a =', round(list_parametres_stoch_gradient_descence[0][0],3)
print 'b =', round(list_parametres_stoch_gradient_descence[0][1],3)
print
print ' 33[1m' + ' 33[4m' + "Сумма квадратов отклонений:" + ' 33[0m'
print round(list_parametres_stoch_gradient_descence[1][-1],3)
print
print ' 33[1m' + ' 33[4m' + "Количество итераций в стохастическом градиентном спуске:" + ' 33[0m'
print len(list_parametres_stoch_gradient_descence[1])
Ons kyk noukeurig na die koëffisiënte en vang onsself op die vraag "Hoe so?". Ons het ander waardes van die koëffisiënte gekry и . Miskien het stogastiese gradiënt-afkoms meer optimale parameters vir die vergelyking gevind? Ongelukkig nee. Dit is genoeg om na die som van kwadraatafwykings te kyk en te sien dat met nuwe waardes van die koëffisiënte, die fout groter is. Ons is nie haastig om te wanhoop nie. Kom ons bou 'n grafiek van foutverandering.
Kode vir die plot van die som van kwadraatafwykings in stogastiese gradiëntafkoms
print 'График №5 "Сумма квадратов отклонений по-шагово"'
plt.plot(range(len(list_parametres_stoch_gradient_descence[1])), list_parametres_stoch_gradient_descence[1], color='red', lw=2)
plt.xlabel('Steps (Iteration)', size=16)
plt.ylabel('Sum of squared deviations', size=16)
plt.show()
Grafiek #5 "Som van kwadraatafwykings in Stogastiese Gradiënt-afkoms"
Nadat ons na die skedule gekyk het, val alles in plek en nou sal ons alles regmaak.
So wat het gebeur? Die volgende het gebeur. Wanneer ons lukraak 'n maand kies, dan is dit vir die gekose maand wat ons algoritme poog om die fout in die berekening van inkomste te verminder. Dan kies ons nog 'n maand en herhaal die berekening, maar ons verminder die fout vir die tweede gekose maand. En laat ons nou onthou dat ons die eerste twee maande aansienlik afwyk van die lyn van die eenvoudige lineêre regressievergelyking. Dit beteken dat wanneer enige van hierdie twee maande gekies word, deur die fout van elkeen van hulle te verminder, ons algoritme die fout oor die hele steekproef ernstig verhoog. So wat om te doen? Die antwoord is eenvoudig: jy moet die trap van afkoms verminder. Inderdaad, deur die afkomstap te verminder, sal die fout ook ophou om op of af te "spring". Of eerder, die “spring”-fout sal nie ophou nie, maar dit sal dit nie so vinnig doen nie :) Kom ons kyk.
Kode om SGD met 'n kleiner stap te laat loop
# запустим функцию, уменьшив шаг в 100 раз и увеличив количество шагов соответсвующе
list_parametres_stoch_gradient_descence = stoch_grad_descent_usual(x_us, y_us, l=0.001, steps = 80000)
print ' 33[1m' + ' 33[4m' + "Значения коэффициентов a и b:" + ' 33[0m'
print 'a =', round(list_parametres_stoch_gradient_descence[0][0],3)
print 'b =', round(list_parametres_stoch_gradient_descence[0][1],3)
print
print ' 33[1m' + ' 33[4m' + "Сумма квадратов отклонений:" + ' 33[0m'
print round(list_parametres_stoch_gradient_descence[1][-1],3)
print
print ' 33[1m' + ' 33[4m' + "Количество итераций в стохастическом градиентном спуске:" + ' 33[0m'
print len(list_parametres_stoch_gradient_descence[1])
print 'График №6 "Сумма квадратов отклонений по-шагово"'
plt.plot(range(len(list_parametres_stoch_gradient_descence[1])), list_parametres_stoch_gradient_descence[1], color='red', lw=2)
plt.xlabel('Steps (Iteration)', size=16)
plt.ylabel('Sum of squared deviations', size=16)
plt.show()
Grafiek #6 "Som van kwadraatafwykings vir stogastiese gradiënt afkoms (80 XNUMX stappe)"
Die kans het verbeter, maar is steeds nie ideaal nie. Hipoteties kan dit op hierdie manier reggestel word. Ons kies byvoorbeeld op die laaste 1000 iterasies die waardes van die koëffisiënte waarmee die minimum fout gemaak is. Dit is waar, hiervoor sal ons die waardes van die koëffisiënte self moet neerskryf. Ons sal dit nie doen nie, maar eerder aandag gee aan die skedule. Dit lyk glad en dit lyk asof die fout eweredig afneem. Eintlik is dit nie. Kom ons kyk na die eerste 1000 iterasies en vergelyk dit met die laastes.
Kode vir SGD-grafiek (eerste 1000 stappe)
print 'График №7 "Сумма квадратов отклонений по-шагово. Первые 1000 итераций"'
plt.plot(range(len(list_parametres_stoch_gradient_descence[1][:1000])),
list_parametres_stoch_gradient_descence[1][:1000], color='red', lw=2)
plt.xlabel('Steps (Iteration)', size=16)
plt.ylabel('Sum of squared deviations', size=16)
plt.show()
print 'График №7 "Сумма квадратов отклонений по-шагово. Последние 1000 итераций"'
plt.plot(range(len(list_parametres_stoch_gradient_descence[1][-1000:])),
list_parametres_stoch_gradient_descence[1][-1000:], color='red', lw=2)
plt.xlabel('Steps (Iteration)', size=16)
plt.ylabel('Sum of squared deviations', size=16)
plt.show()
Grafiek No. 7 "Som van kwadraatafwykings van SGD (eerste 1000 stappe)"
Grafiek #8 "Som van kwadraatafwykings van SGD (laaste 1000 stappe)"
Heel aan die begin van die afdraande neem ons 'n redelik eenvormige en skerp afname in die fout waar. By die laaste iterasies sien ons dat die fout om en om die waarde van 1,475 gaan en op sommige oomblikke selfs gelyk is aan hierdie optimale waarde, maar dan gaan dit steeds op ... Ek herhaal, jy kan die waardes van die koëffisiënte и , en kies dan diegene waarvoor die fout minimaal is. Ons het egter 'n groter probleem gehad: ons moes 80 duisend stappe neem (sien kode) om waardes naby aan optimaal te kry. En dit weerspreek reeds die idee om berekeningstyd te bespaar in stogastiese gradiënt-afkoms met betrekking tot gradiënt-afkoms. Wat kan reggestel en verbeter word? Dit is nie moeilik om te sien dat ons in die eerste iterasies geleidelik daal en daarom moet ons 'n groot stap in die eerste iterasies laat en die stap verminder soos ons vorentoe beweeg. Ons sal dit nie in hierdie artikel doen nie – dit het reeds gesloer. Die wat wil kan self dink hoe om dit te doen, dis nie moeilik nie 🙂
Kom ons voer nou stogastiese gradiënt-afkoms uit met behulp van die biblioteek Numpy (en laat ons nie struikel oor die rotse wat ons vroeër geïdentifiseer het nie)
Kode vir Stogastiese Gradiënt Afkoms (NumPy)
# для начала напишем функцию градиентного шага
def stoch_grad_step_numpy(vector_init, X, ind, y, l):
x = X[ind]
y_pred = np.dot(x,vector_init)
err = y_pred - y[ind]
grad_a = err
grad_b = x[1]*err
return vector_init - l*np.array([grad_a, grad_b])
# определим функцию стохастического градиентного спуска
def stoch_grad_descent_numpy(X, y, l=0.1, steps = 800):
vector_init = np.array([[np.random.randint(X.shape[0])], [np.random.randint(X.shape[0])]])
errors = []
for i in range(steps):
ind = np.random.randint(X.shape[0])
new_vector = stoch_grad_step_numpy(vector_init, X, ind, y, l)
vector_init = new_vector
errors.append(error_square_numpy(vector_init,X,y))
return (vector_init), (errors)
# запишем массив значений
list_parametres_stoch_gradient_descence = stoch_grad_descent_numpy(x_np, y_np, l=0.001, steps = 80000)
print ' 33[1m' + ' 33[4m' + "Значения коэффициентов a и b:" + ' 33[0m'
print 'a =', round(list_parametres_stoch_gradient_descence[0][0],3)
print 'b =', round(list_parametres_stoch_gradient_descence[0][1],3)
print
print ' 33[1m' + ' 33[4m' + "Сумма квадратов отклонений:" + ' 33[0m'
print round(list_parametres_stoch_gradient_descence[1][-1],3)
print
print ' 33[1m' + ' 33[4m' + "Количество итераций в стохастическом градиентном спуске:" + ' 33[0m'
print len(list_parametres_stoch_gradient_descence[1])
print
Die waardes het geblyk amper dieselfde te wees as wanneer dit gedaal het sonder om te gebruik Numpy. Dit is egter logies.
Kom ons vind uit hoeveel tyd stogastiese gradiënt afdraandes ons geneem het.
Kode om SGD-berekeningstyd te bepaal (80k stappe)
print ' 33[1m' + ' 33[4m' +
"Время выполнения стохастического градиентного спуска без использования библиотеки NumPy:"
+ ' 33[0m'
%timeit list_parametres_stoch_gradient_descence = stoch_grad_descent_usual(x_us, y_us, l=0.001, steps = 80000)
print '***************************************'
print
print ' 33[1m' + ' 33[4m' +
"Время выполнения стохастического градиентного спуска с использованием библиотеки NumPy:"
+ ' 33[0m'
%timeit list_parametres_stoch_gradient_descence = stoch_grad_descent_numpy(x_np, y_np, l=0.001, steps = 80000)
Hoe verder die bos in, hoe donkerder die wolke: weereens wys die "selfgeskrewe" formule die beste resultaat. Dit alles dui daarop dat daar selfs meer subtiele maniere moet wees om die biblioteek te gebruik. Numpy, wat rekenaarbewerkings werklik versnel. In hierdie artikel sal ons nie oor hulle leer nie. Iets om op jou gemak oor na te dink :)
Ons som op
Voordat ek opsom, wil ek graag 'n vraag beantwoord wat heel waarskynlik by ons liewe leser ontstaan het. Hoekom eintlik sulke “torings” met afdraandes, hoekom moet ons op en af met die berg stap (meestal af) om die kosbare laagland te vind, as ons so 'n kragtige en eenvoudige toestel in ons hande het, in die vorm van 'n analitiese oplossing wat ons onmiddellik na die regte plek teleporteer?
Die antwoord op hierdie vraag lê op die oppervlak. Nou het ons 'n baie eenvoudige voorbeeld ontleed waarin die ware antwoord is hang af van een kenmerk . Jy sien dit nie gereeld in die lewe nie, so kom ons verbeel ons dat ons 2, 30, 50 of meer tekens het. Voeg hierby duisende, en selfs tienduisende waardes vir elke kenmerk. In hierdie geval kan die analitiese oplossing die toets druip en misluk. Op sy beurt sal gradiëntafkoms en sy variasies ons stadig maar seker nader aan die doelwit bring - die minimum van die funksie. En moenie bekommerd wees oor die spoed nie – ons sal waarskynlik steeds die maniere ontleed wat ons sal toelaat om die staplengte (dit is die spoed) in te stel en aan te pas.
En nou vir 'n kort opsomming.
Eerstens hoop ek dat die materiaal wat in die artikel aangebied word, beginner "datawetenskaplikes" sal help om te verstaan hoe om eenvoudige (en nie net nie) lineêre regressievergelykings op te los.
Tweedens het ons na verskeie maniere gekyk om die vergelyking op te los. Nou, afhangende van die situasie, kan ons die een kies wat die beste geskik is vir die taak op hande.
Derdens het ons die krag van bykomende instellings gesien, naamlik die staplengte van gradiëntafkoms. Hierdie parameter moet nie verwaarloos word nie. Soos hierbo genoem, moet die staplengte tydens die afdraande verander word om die koste van die uitvoer van berekeninge te verminder.
Vierdens, in ons geval het "selfgeskrewe" funksies die beste tydresultaat van berekeninge getoon. Dit is waarskynlik te wyte aan nie die mees professionele gebruik van die biblioteek se vermoëns nie. Numpy. Maar hoe dit ook al sy, die gevolgtrekking stel homself soos volg voor. Aan die een kant is dit soms die moeite werd om gevestigde menings te bevraagteken, en aan die ander kant is dit nie altyd die moeite werd om alles te kompliseer nie - inteendeel, soms is 'n eenvoudiger manier om 'n probleem op te los meer effektief. En aangesien ons doel was om drie benaderings te ontleed om 'n eenvoudige lineêre regressievergelyking op te los, was die gebruik van "selfgeskrewe" funksies vir ons genoeg.
Letterkunde (of so iets)
1. Lineêre regressie
2. Kleinste vierkante metode
3. Afgeleide
4. helling
5. Gradiënt Afkoms
6. NumPy-biblioteek