Makalede basit (eşleştirilmiş) bir regresyon çizgisinin matematiksel denklemini belirlemenin çeşitli yolları tartışılmaktadır.
Burada tartışılan denklemi çözmenin tüm yöntemleri en küçük kareler yöntemine dayanmaktadır. Yöntemleri şu şekilde belirtelim:
- Analitik çözüm
- Dereceli alçalma
- Stokastik gradyan inişi
Düz bir çizgi denklemini çözmenin her yöntemi için makale, esas olarak kütüphaneyi kullanmadan yazılanlara bölünmüş çeşitli işlevler sağlar. Dizi ve hesaplamalar için kullananlar Dizi. Ustaca kullanıldığına inanılıyor Dizi bilgi işlem maliyetlerini azaltacaktır.
Makalede verilen tüm kodlar dilde yazılmıştır. python 2.7 ile Jupyter Not Defteri. Kaynak kodu ve örnek verileri içeren dosya şu adreste yayınlanmıştır:
Makale daha çok hem yeni başlayanlara hem de yapay zekanın çok geniş bir bölümü olan makine öğrenimi üzerine yavaş yavaş uzmanlaşmaya başlayanlara yöneliktir.
Malzemeyi açıklamak için çok basit bir örnek kullanıyoruz.
Örnek koşullar
Bağımlılığı karakterize eden beş değerimiz var Y itibaren X (Tablo No. 1):
Tablo No. 1 “Örnek koşullar”
Değerlerin olduğunu varsayacağız yılın ayıdır ve — bu ayki gelir. Başka bir deyişle, gelir yılın ayına bağlıdır ve - gelirin bağlı olduğu tek işaret.
Örnek öyle, hem gelirin yılın ayına koşullu bağımlılığı açısından hem de değer sayısı açısından - bunlardan çok azı var. Bununla birlikte, böyle bir basitleştirme, dedikleri gibi, yeni başlayanların özümsediği materyali her zaman kolaylıkla olmasa da açıklamayı mümkün kılacaktır. Ayrıca sayıların basitliği, örneği kağıt üzerinde önemli işçilik maliyetleri olmadan çözmek isteyenlere olanak sağlayacaktır.
Örnekte verilen bağımlılığın, formun basit (eşleştirilmiş) regresyon çizgisinin matematiksel denklemiyle oldukça iyi bir şekilde tahmin edilebileceğini varsayalım:
nerede gelirin alındığı aydır, - aya karşılık gelen gelir, и tahmin edilen doğrunun regresyon katsayılarıdır.
Katsayıya dikkat edin genellikle tahmini çizginin eğimi veya eğimi olarak adlandırılır; miktarı temsil eder değiştiğinde .
Açıkçası, örnekteki görevimiz denklemdeki bu tür katsayıları seçmektir. и , aylara göre hesaplanan gelir değerlerimizin gerçek cevaplardan sapmaları, yani. örnekte sunulan değerler minimum olacaktır.
en küçük kareler yöntemi
En küçük kareler yöntemine göre sapmanın karesi alınarak hesaplanması gerekir. Bu teknik, zıt işaretleri varsa sapmaların karşılıklı olarak iptal edilmesini önlemenizi sağlar. Örneğin, bir durumda sapma şu şekilde ise +5 (artı beş) ve diğerinde -5 (eksi beş), bu durumda sapmaların toplamı birbirini iptal edecek ve 0 (sıfır) olacaktır. Sapmanın karesini almak değil, modülün özelliğini kullanmak mümkündür; o zaman tüm sapmalar pozitif olacak ve birikecektir. Bu nokta üzerinde ayrıntılı olarak durmayacağız, ancak hesaplamaların kolaylığı için sapmanın karesinin alınmasının geleneksel olduğunu belirtmekle yetineceğiz.
Sapmaların (hataların) karelerinin en küçük toplamını belirleyeceğimiz formül şu şekilde görünür:
nerede gerçek cevapların (yani hesapladığımız gelirin) yaklaşık değerinin bir fonksiyonudur,
doğru yanıtlardır (örnekte sağlanan gelir),
örnek indeksidir (sapmanın belirlendiği ayın numarası)
Fonksiyonun türevini alalım, kısmi diferansiyel denklemleri tanımlayalım ve analitik çözüme geçmeye hazır olalım. Ama önce türevin ne olduğuna dair kısa bir gezinti yapalım ve türevin geometrik anlamını hatırlayalım.
Farklılaşma
Türev alma, bir fonksiyonun türevini bulma işlemidir.
Türev ne için kullanılır? Bir fonksiyonun türevi, fonksiyonun değişim hızını karakterize eder ve bize yönünü söyler. Belirli bir noktadaki türev pozitifse fonksiyon artar; aksi takdirde fonksiyon azalır. Mutlak türevin değeri ne kadar büyük olursa, fonksiyon değerlerinin değişim hızı da o kadar yüksek olur ve fonksiyon grafiğinin eğimi de o kadar dik olur.
Örneğin, Kartezyen koordinat sistemi koşulları altında, M(0,0) noktasındaki türevin değeri şuna eşittir: + 25 belirli bir noktada değer kaydırıldığında anlamına gelir geleneksel bir birim tarafından sağa doğru, değer 25 konvansiyonel birim artar. Grafikte değerlerde oldukça dik bir artış görünüyor belirli bir noktadan.
Başka bir örnek. Türev değeri eşittir -0,1 yerinden edildiğinde anlamına gelir bir geleneksel birim başına değer yalnızca 0,1 geleneksel birim azalır. Aynı zamanda fonksiyonun grafiğinde zar zor fark edilen bir aşağı doğru eğim gözlemleyebiliriz. Bir dağ benzetmesi yaparsak, bir önceki örnekten farklı olarak, çok dik zirvelere tırmanmak zorunda kaldığımız gibi, sanki bir dağdan çok yavaş bir eğimle iniyoruz :)
Yani fonksiyonun türevini aldıktan sonra ihtimallere göre и 1. mertebeden kısmi diferansiyel denklemleri tanımlıyoruz. Denklemleri belirledikten sonra, katsayıların bu değerlerini seçebileceğimiz çözerek iki denklemden oluşan bir sistem alacağız. и karşılık gelen türevlerin değerlerinin belirli noktalarda çok çok küçük bir miktarda değiştiği ve analitik bir çözüm durumunda hiç değişmediği. Yani bu noktalardaki kısmi türevlerin değerleri sıfıra eşit olacağından, bulunan katsayılardaki hata fonksiyonu minimuma ulaşacaktır.
Yani, türev alma kurallarına göre, katsayıya göre 1. dereceden kısmi türev denklemi şu şekli alacaktır:
1. mertebeden kısmi türev denklemi şu şekli alacaktır:
Sonuç olarak, oldukça basit bir analitik çözümü olan bir denklem sistemi elde ettik:
başlangıç{denklem*}
başlangıç{vakalar}
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
son {vakalar}
bitiş{denklem*}
Denklemi çözmeden önce ön yükleme yapalım, yüklemenin doğru olup olmadığını kontrol edelim ve verileri formatlayalım.
Verileri yükleme ve biçimlendirme
Analitik çözüm için ve ardından gradyan ve stokastik gradyan inişi için kodu iki varyasyonda kullanacağımız gerçeğinden dolayı not edilmelidir: kütüphaneyi kullanmak Dizi ve onu kullanmadan, uygun veri formatlamasına ihtiyacımız olacak (koda bakın).
Veri yükleme ve işleme kodu
# импортируем все нужные нам библиотеки
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 '********************************************'
Görüntüleme
Şimdi öncelikle verileri yükledikten, ikinci olarak yüklemenin doğruluğunu kontrol ettikten ve son olarak verileri formatladıktan sonra ilk görselleştirmeyi gerçekleştireceğiz. Bunun için sıklıkla kullanılan yöntem çift grafiği Kütüphane deniz doğumu. Örneğimizde sayı sınırlı olduğundan kütüphaneyi kullanmanın bir anlamı yoktur. deniz doğumu. Normal kütüphaneyi kullanacağız matplotlib ve dağılım grafiğine bakın.
Dağılım grafiği kodu
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()
Grafik No. 1 “Gelirin yılın ayına bağımlılığı”
Analitik çözüm
En yaygın araçları kullanalım piton ve denklem sistemini çözelim:
başlangıç{denklem*}
başlangıç{vakalar}
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
son {vakalar}
bitiş{denklem*}
Cramer kuralına göre genel belirleyiciyi ve belirleyicileri şu şekilde bulacağız: ve , bundan sonra determinantı şuna böleriz: genel belirleyiciye - katsayıyı bulun , benzer şekilde katsayıyı buluyoruz .
Analitik çözüm kodu
# определим функцию для расчета коэффициентов 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)
İşte elimizde olanlar:
Böylece katsayıların değerleri bulunmuş, sapmaların kareleri toplamı bulunmuştur. Bulunan katsayılara göre saçılma histogramı üzerine düz bir çizgi çizelim.
Regresyon satırı kodu
# определим функцию для формирования массива рассчетных значений выручки
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()
Tablo No. 2 “Doğru ve hesaplanmış cevaplar”
Her ay için sapma grafiğine bakabilirsiniz. Bizim durumumuzda bundan önemli bir pratik değer elde etmeyeceğiz, ancak basit doğrusal regresyon denkleminin gelirin yılın ayına bağımlılığını ne kadar iyi karakterize ettiği konusundaki merakımızı gidereceğiz.
Sapma grafik kodu
# определим функцию для формирования массива отклонений в процентах
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()
Grafik No. 3 “Sapmalar, %”
Mükemmel değil ama görevimizi tamamladık.
Katsayıları belirleyen bir fonksiyon yazalım. и kütüphaneyi kullanır DiziDaha doğrusu, iki fonksiyon yazacağız: biri sözde ters matris kullanarak (işlem hesaplama açısından karmaşık ve kararsız olduğundan pratikte önerilmez), diğeri ise bir matris denklemi kullanarak.
Analitik Çözüm Kodu (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
Katsayıları belirlemek için harcanan zamanı karşılaştıralım и , sunulan 3 yönteme uygun olarak.
Hesaplama süresini hesaplamak için kod
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)
Az miktarda veriyle, Cramer yöntemini kullanarak katsayıları bulan “kendi kendine yazılan” bir fonksiyon ortaya çıkıyor.
Artık katsayıları bulmanın diğer yollarına geçebilirsiniz и .
Dereceli alçalma
Öncelikle degradenin ne olduğunu tanımlayalım. Basitçe söylemek gerekirse gradyan, bir fonksiyonun maksimum büyüme yönünü gösteren bir segmenttir. Bir dağa tırmanmaya benzer şekilde, eğim yüzleri, dağın tepesine doğru en dik tırmanışın olduğu yerdir. Örneği dağla geliştirerek, ovaya en hızlı şekilde ulaşabilmek için aslında en dik inişe, yani minimum, fonksiyonun artmadığı veya azalmadığı yere ihtiyacımız olduğunu hatırlıyoruz. Bu noktada türev sıfıra eşit olacaktır. Bu nedenle, bir degradeye değil, bir antigradiyente ihtiyacımız var. Antigradient'i bulmak için degradeyi şu şekilde çarpmanız yeterlidir: -1 (eksi bir).
Bir fonksiyonun birden fazla minimuma sahip olabileceğine ve aşağıda önerilen algoritmayı kullanarak bunlardan birine indiğimizde, bulunandan daha düşük olabilecek başka bir minimum bulamayacağımıza dikkat edelim. Rahat olalım, bu bizim için bir tehdit değil! Bizim durumumuzda tek bir minimumla uğraşıyoruz, çünkü fonksiyonumuz Grafikte düzenli bir parabol var. Ve hepimizin okuldaki matematik dersinden çok iyi bildiği gibi, bir parabolün yalnızca bir minimumu vardır.
Neden bir degradeye ihtiyacımız olduğunu ve ayrıca degradenin bir parça olduğunu, yani katsayıları tam olarak aynı olan belirli koordinatlara sahip bir vektör olduğunu öğrendikten sonra и degrade inişini uygulayabiliriz.
Başlamadan önce iniş algoritması hakkında birkaç cümle okumanızı öneririm:
- Katsayıların koordinatlarını sözde rastgele bir şekilde belirliyoruz и . Örneğimizde sıfıra yakın katsayılar tanımlayacağız. Bu yaygın bir uygulamadır ancak her vakanın kendine özgü uygulaması olabilir.
- Koordinattan noktasındaki 1. dereceden kısmi türevin değerini çıkarın . Yani türev pozitifse fonksiyon artar. Dolayısıyla türevin değerini çıkararak büyümenin tersi yönde yani iniş yönünde hareket edeceğiz. Türev negatifse bu noktadaki fonksiyon azalır ve türevin değerini çıkararak iniş yönünde hareket ederiz.
- Koordinatla benzer bir işlem gerçekleştiriyoruz : noktadaki kısmi türevin değerini çıkarın .
- Minimumun üzerinden atlayıp derin uzaya uçmamak için adım boyutunu iniş yönünde ayarlamak gerekir. Genel olarak, hesaplama maliyetlerini azaltmak için adımın nasıl doğru şekilde ayarlanacağı ve iniş süreci sırasında nasıl değiştirileceği hakkında tam bir makale yazabilirsiniz. Ancak şimdi önümüzde biraz farklı bir görev var ve adım boyutunu bilimsel "dürtme" yöntemini kullanarak veya halk dilinde dedikleri gibi ampirik olarak belirleyeceğiz.
- Verilen koordinatlardan geldiğimizde и türevlerin değerlerini çıkarırsak yeni koordinatlar elde ederiz и . Zaten hesaplanan koordinatlardan bir sonraki adımı (çıkarma) atıyoruz. Ve böylece döngü, gerekli yakınsama sağlanana kadar tekrar tekrar başlar.
Tüm! Artık Mariana Çukuru'nun en derin geçidini aramaya hazırız. Başlayalım.
Gradyan iniş kodu
# напишем функцию градиентного спуска без использования библиотеки 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
Mariana Çukuru'nun en dibine daldık ve orada aynı katsayı değerlerini bulduk и , tam da beklenmesi gereken şey buydu.
Bir dalış daha yapalım ama bu sefer derin deniz aracımız başka teknolojilerle, yani kütüphaneyle dolu olacak Dizi.
Degrade iniş kodu (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
Katsayı değerleri и değiştirilemez.
Gradyan inişi sırasında hatanın nasıl değiştiğine, yani her adımda sapmaların karelerinin toplamının nasıl değiştiğine bakalım.
Sapmaların karelerinin toplamının grafiğini çizme kodu
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()
Grafik No. 4 “Degrade iniş sırasında sapmaların karelerinin toplamı”
Grafikte her adımda hatanın azaldığını ve belirli sayıda tekrardan sonra neredeyse yatay bir çizgi gözlemlediğimizi görüyoruz.
Son olarak kod yürütme süresindeki farkı tahmin edelim:
Gradyan iniş hesaplama süresini belirleyen kod
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)
Belki bir şeyleri yanlış yapıyoruz ama yine de bu kütüphaneyi kullanmayan basit bir "evde yazılan" fonksiyondur. Dizi kütüphaneyi kullanan bir fonksiyonun hesaplama süresini aşıyor Dizi.
Ancak hareketsiz kalmıyoruz ve basit doğrusal regresyon denklemini çözmenin heyecan verici başka bir yolunu araştırmaya doğru ilerliyoruz. Tanışmak!
Stokastik gradyan inişi
Stokastik gradyan inişinin çalışma prensibini hızlı bir şekilde anlamak için, sıradan gradyan inişinden farklarını belirlemek daha iyidir. Gradyan iniş durumunda, türevlerin denklemlerinde и Örnekte mevcut olan tüm özelliklerin ve doğru cevapların değerlerinin toplamları (yani tüm özelliklerin toplamları) kullanıldı. и ). Stokastik gradyan inişinde, örnekte mevcut olan tüm değerleri kullanmayacağız, bunun yerine sözde rastgele olarak sözde örnek indeksi seçip değerlerini kullanacağız.
Örneğin indeksin 3 (üç) numara olduğu belirlendiyse değerleri alıyoruz и sonra değerleri türev denklemlerinde yerine koyarız ve yeni koordinatlar belirleriz. Daha sonra, koordinatları belirledikten sonra, örnek indeksi yine rastgele belirleriz, indekse karşılık gelen değerleri kısmi diferansiyel denklemlerde değiştiririz ve koordinatları yeni bir şekilde belirleriz. и vesaire. yakınsama yeşile dönene kadar. İlk bakışta bu hiç işe yarayacakmış gibi görünmeyebilir ama işe yarıyor. Hatanın her adımda azalmadığını belirtmekte fayda var ama bir eğilim olduğu kesin.
Stokastik gradyan inişinin geleneksel olana göre avantajları nelerdir? Eğer örneklem büyüklüğümüz çok büyükse ve onbinlerce değerle ölçülüyorsa, o zaman örneğin rastgele bin tanesini işlemek tüm örneklem yerine çok daha kolaydır. Stokastik gradyan inişinin devreye girdiği yer burasıdır. Bizim durumumuzda elbette çok fazla bir fark görmeyeceğiz.
Hadi koda bakalım.
Stokastik gradyan iniş kodu
# определим функцию стох.град.шага
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])
Katsayılara dikkatlice bakıyoruz ve kendimizi “Bu nasıl olabilir?” sorusunu sorarken buluyoruz. Başka katsayı değerlerimiz de var и . Belki stokastik gradyan inişi denklem için daha uygun parametreler bulmuştur? Ne yazık ki hayır. Sapmaların karelerinin toplamına bakmak ve katsayıların yeni değerlerinde hatanın daha büyük olduğunu görmek yeterlidir. Umutsuzluğa kapılmak için acelemiz yok. Hata değişiminin bir grafiğini oluşturalım.
Stokastik gradyan inişinde sapmaların karelerinin toplamını çizmeye yönelik kod
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()
Grafik No. 5 “Stokastik gradyan inişi sırasında sapmaların karelerinin toplamı”
Programa baktığımızda her şey yerli yerine oturuyor ve şimdi her şeyi düzelteceğiz.
Peki ne oldu? Aşağıdakiler oldu. Rastgele bir ay seçtiğimizde, algoritmamız gelir hesaplamasındaki hatayı seçilen ay için azaltmaya çalışır. Daha sonra başka bir ay seçip hesaplamayı tekrarlıyoruz ancak seçilen ikinci ay için hatayı azaltıyoruz. Şimdi, ilk iki ayın basit doğrusal regresyon denkleminden önemli ölçüde saptığını unutmayın. Yani bu iki aydan herhangi biri seçildiğinde algoritmamız her birinin hatasını azaltarak tüm örneklem için hatayı ciddi oranda artırıyor. Peki ne yapmalı? Cevap basit: iniş adımını azaltmanız gerekiyor. Sonuçta, iniş adımını azaltarak hata, yukarı ve aşağı "zıplamayı" da durduracaktır. Daha doğrusu “atlama” hatası durmayacak ama o kadar çabuk da bitmeyecek :) Hadi kontrol edelim.
SGD'yi daha küçük artışlarla çalıştıracak kod
# запустим функцию, уменьшив шаг в 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()
Grafik No. 6 “Stokastik gradyan inişi sırasında sapmaların karelerinin toplamı (80 bin adım)”
Katsayılar iyileşti ancak hala ideal değil. Varsayımsal olarak bu durum bu şekilde düzeltilebilir. Örneğin son 1000 yinelemede minimum hatanın yapıldığı katsayıların değerlerini seçiyoruz. Doğru, bunun için katsayıların değerlerini de yazmamız gerekecek. Bunu yapmayacağız, bunun yerine programa dikkat edeceğiz. Pürüzsüz görünüyor ve hata eşit şekilde azalıyor gibi görünüyor. Aslında, bu doğru değil. İlk 1000 yinelemeye bakalım ve bunları sonuncuyla karşılaştıralım.
SGD grafiği kodu (ilk 1000 adım)
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()
Grafik No. 7 “SGD sapmalarının karelerinin toplamı (ilk 1000 adım)”
Grafik No. 8 “SGD sapmalarının karelerinin toplamı (son 1000 adım)”
İnişin en başında hatada oldukça düzenli ve dik bir azalma gözlemliyoruz. Son iterasyonlarda hatanın 1,475 civarında dolaştığını ve hatta bazı anlarda bu optimal değere eşitlendiğini ancak sonrasında hala yukarıya çıktığını görüyoruz... Tekrar ediyorum, değerleri yazabilirsiniz. katsayılar и ve ardından hatanın en az olduğu olanları seçin. Ancak daha ciddi bir sorunumuz vardı: Optimuma yakın değerler elde etmek için 80 bin adım atmak zorunda kaldık (kodlara bakın). Ve bu zaten, gradyan inişine göre stokastik gradyan inişiyle hesaplama süresinden tasarruf etme fikriyle çelişiyor. Neler düzeltilebilir ve geliştirilebilir? İlk iterasyonlarda emin adımlarla aşağı indiğimizi ve bu nedenle ilk iterasyonlarda büyük bir adım bırakıp ilerledikçe adımı azaltmamız gerektiğini fark etmek zor değil. Bu makalede bunu yapmayacağız - zaten çok uzun. Dileyenler bunu nasıl yapacaklarını kendileri düşünsünler, hiç de zor değil :)
Şimdi kütüphaneyi kullanarak stokastik gradyan inişini gerçekleştirelim Dizi (ve daha önce tespit ettiğimiz taşlara takılmayalım)
Stokastik Gradyan İniş Kodu (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
Değerlerin, kullanmadan inerken olduğu gibi neredeyse aynı olduğu ortaya çıktı. Dizi. Ancak bu mantıklıdır.
Stokastik gradyan inişlerinin ne kadar sürdüğünü bulalım.
SGD hesaplama süresini belirleme kodu (80 bin adım)
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)
Ormanın derinliklerine doğru bulutlar daha koyu olur: yine "kendi yazdığı" formül en iyi sonucu gösterir. Bütün bunlar kütüphaneyi kullanmanın daha da incelikli yollarının olması gerektiğini gösteriyor Dizi, bu da hesaplama işlemlerini gerçekten hızlandırır. Bu yazıda bunları öğrenmeyeceğiz. Boş zamanınızda düşünecek bir şeyiniz olacak :)
Özetliyoruz
Özetlemeden önce sevgili okurumuzun aklına gelebilecek bir soruya cevap vermek istiyorum. Aslında, neden inişlerle bu kadar “işkence”, eğer elimizde bu kadar güçlü ve basit bir cihaz varsa, değerli ovayı bulmak için neden dağda (çoğunlukla aşağı) inip çıkmamız gerekiyor? Bizi anında Doğru yere ışınlayacak analitik bir çözüm biçimi mi?
Bu sorunun cevabı yüzeyde yatıyor. Şimdi, doğru cevabın verildiği çok basit bir örneğe baktık. bir işarete bağlıdır . Bunu hayatta çok sık görmüyorsunuz, o yüzden 2, 30, 50 veya daha fazla burcumuz olduğunu hayal edelim. Buna her özellik için binlerce hatta onbinlerce değer ekleyelim. Bu durumda analitik çözüm teste dayanamayabilir ve başarısız olabilir. Buna karşılık, gradyan inişi ve onun varyasyonları bizi yavaş ama emin adımlarla hedefe, yani fonksiyonun minimumuna yaklaştıracaktır. Ve hız konusunda endişelenmeyin; muhtemelen adım uzunluğunu (yani hızı) ayarlamamıza ve düzenlememize olanak tanıyacak yollara bakacağız.
Ve şimdi asıl kısa özet.
Öncelikle, makalede sunulan materyalin, yeni başlayan "veri bilimcilerinin" basit (ve sadece değil) doğrusal regresyon denklemlerinin nasıl çözüleceğini anlamalarına yardımcı olacağını umuyorum.
İkinci olarak denklemi çözmenin çeşitli yollarına baktık. Artık duruma göre sorunu çözmeye en uygun olanı seçebiliriz.
Üçüncüsü, ek ayarların gücünü, yani degrade iniş adımı uzunluğunu gördük. Bu parametre ihmal edilemez. Yukarıda belirtildiği gibi hesaplama maliyetini azaltmak için iniş sırasında adım uzunluğunun değiştirilmesi gerekir.
Dördüncüsü, bizim durumumuzda, hesaplamalar için en iyi zaman sonuçlarını "evde yazılan" işlevler gösterdi. Bunun nedeni muhtemelen kütüphanenin yeteneklerinin yeterince profesyonelce kullanılmamasıdır. Dizi. Ancak öyle de olsa, aşağıdaki sonuç kendini gösteriyor. Bir yandan bazen yerleşik fikirleri sorgulamaya değer, diğer yandan her zaman her şeyi karmaşıklaştırmaya değmez - tam tersine, bazen bir sorunu çözmenin daha basit bir yolu daha etkilidir. Amacımız basit bir doğrusal regresyon denklemini çözmeye yönelik üç yaklaşımı analiz etmek olduğundan, "kendi kendine yazılan" fonksiyonların kullanılması bizim için oldukça yeterliydi.
Edebiyat (veya buna benzer bir şey)
1. Doğrusal regresyon
2. En Küçük Kareler Yöntemi
3. Türev
4. Gradyan
5. Gradyan iniş
6. NumPy kütüphanesi
Kaynak: habr.com