- 梯度下降法簡(jiǎn)介
- 多元線性回歸中的梯度下降法
- 隨機(jī)梯度下降法
- 梯度下降法 的調(diào)試
1.梯度下降法簡(jiǎn)介
- 不是一個(gè)機(jī)器學(xué)習(xí)算法
- 是一種基于搜索的最優(yōu)化方法
- 作用:最小化損失函數(shù) (最小值的點(diǎn))
- 梯度上升法:最大化一個(gè)效用函數(shù)(最大值的點(diǎn) )
1.1一元線性回歸梯度下降原理分析
首先我們一元線性回歸為例:
一元線性回歸的目標(biāo):
我們的目標(biāo)是求a和b的值先朦,以作到損失函數(shù)最小耗绿。
在上式中X(i)和y(i)為訓(xùn)練集數(shù)據(jù)目胡,我們假設(shè)這個(gè)直線最終結(jié)果是一個(gè)沒(méi)有截距過(guò)原點(diǎn)的直線,也就是將b看作一個(gè)常數(shù)(或者將b直接去掉)亡问,對(duì)公式進(jìn)行變換:
對(duì)于第i個(gè)數(shù)據(jù)的損失函數(shù)表達(dá)式為:
那么這樣看的話這是一個(gè)關(guān)于a的一元二次方程。
總共有m個(gè)數(shù)據(jù),他們每一個(gè)損失函數(shù)的值相加列粪,最后相加的結(jié)果還是一個(gè)一元二次方程福侈。
所以我們的目標(biāo)就是找到該方程的最小值酒来。
參數(shù)theta對(duì)應(yīng)的損失函數(shù)J的值對(duì)應(yīng)的示例圖,我們需要找到使得損失函數(shù)值J取得最小值對(duì)應(yīng)的theta(這里是二維平面肪凛,也就是我們的參數(shù)只有一個(gè))
1.我們要找的就是圖中的theta(m)值役首。
- 在直線方程中,導(dǎo)數(shù)代表斜率显拜。
- 在曲線方程中衡奥,導(dǎo)數(shù)代表切線斜率。斜率越大远荠,變化越快矮固。
- theta越靠近theta(m),J的變化越慢
2.怎么理解圖中的導(dǎo)數(shù)可以代表方向,對(duì)應(yīng)J增大的方向
以及公式譬淳?
我們對(duì)圖中任意一點(diǎn)求導(dǎo)(切線)档址,這個(gè)切線都是有方向的
- 在最小值左側(cè),切線的斜率是負(fù)的邻梆,對(duì)應(yīng)途中的(1)守伸,它的指向?yàn)镴增大的方向
- 在最小值的右側(cè),切線斜率是正的浦妄,對(duì)應(yīng)圖中的(2)尼摹,它的指向也為J增大的方向
- 而我們的目標(biāo)是向最小值的為晃著靠近,所以對(duì)每一個(gè)點(diǎn)求導(dǎo)之后剂娄,取相反數(shù)蠢涝,那么這樣切線就指向J減小的方向。
3.對(duì)于學(xué)習(xí)率n阅懦,接下來(lái)介紹:
η太小和二,會(huì)減慢收斂學(xué)習(xí)速度:
η太大,甚至導(dǎo)致不收斂:
那么這樣的話表達(dá)式:
我們?cè)谥老逻叺脑砭涂梢岳斫饬恕?/p>
尋找最小點(diǎn)theta(m)的原理過(guò)程:
在圖中的線上隨機(jī)尋找一個(gè)點(diǎn)耳胎,我們要不斷的更新theta的值惯吕,而這個(gè)更新值與原值之間的區(qū)間就是上邊的公式(我們暫且將公式值即為p)惕它,那么下一次更新的值就為(theta+p)。由于這個(gè)更新值一定指向J縮小的方向废登,所以theta不斷靠近theta(m)怠缸,直到某一次theta對(duì)應(yīng)的J比上一次的theta值對(duì)應(yīng)的J小于某一個(gè)很小的值(越靠近theta(m),J的變化速度越小),說(shuō)明我們找到了該點(diǎn)theta钳宪。
4.其他注意事項(xiàng)
并不是所有函數(shù)都有唯一的極值點(diǎn)
5.解決方案:
- 多次運(yùn)行揭北,隨機(jī)化初始點(diǎn)
- 梯度下降法的初始點(diǎn)也是一個(gè)超參數(shù)
6.有b時(shí)的情況
在之前的分析中我們把b看作一個(gè)常數(shù)/0。
如果加上b的話吏颖,那么損失函數(shù)就隨著a搔体,b兩個(gè)參數(shù)而做改變,這就是一個(gè)三維的空間半醉,如果做圖的話疚俱,x為theta,y為b缩多,z為J:
那么畫(huà)出來(lái)就類(lèi)似于倒過(guò)來(lái)的大鐘呆奕。
1.2模擬梯度下降法
接下來(lái)我們使用代碼來(lái)實(shí)現(xiàn)上邊的分析過(guò)程。
1.創(chuàng)建數(shù)據(jù)集衬吆,畫(huà)圖:
import numpy as np
import matplotlib.pyplot as plt
# 生成等差數(shù)列[-1,6],個(gè)數(shù)為141個(gè)
plot_x = np.linspace(-1,6,141)
print(plot_x)
# print(len(plot_x))
# 生成一元二次方程
plot_y = (plot_x-2.5)**2 - 1
print(plot_y)
# 畫(huà)圖
plt.plot(plot_x,plot_y)
plt.show()
運(yùn)行:
2.實(shí)現(xiàn)梯度下降:
# 學(xué)習(xí)率n
eta = 0.1
# 最小差距
epsilon = 1e-8
# 定義生成J值得函數(shù)
def J(theta):
return (theta-2.5)**2 - 1
# 定義d(J)/d(theta)得值
def DJ(thate):
return 2*(thate-2.5)
# 設(shè)置初始theta
theta = 0.0
while True:
# 記錄當(dāng)前位置,后邊用于判斷是否跳出
last = theta
# 計(jì)算下一個(gè)點(diǎn)的位置,直接賦給循環(huán)變量theta
theta = theta-eta * DJ(theta)
if abs(J(theta) - J(last)) < epsilon:
break
print(theta)
print(J(theta))
# 2.499891109642585
# -0.99999998814289
分析:
- 首先定義超參數(shù)n和最小差距梁钾,然后定義兩個(gè)函數(shù)J和DJ,計(jì)算某一點(diǎn)的J值逊抡,以及計(jì)算某一點(diǎn)J對(duì)theta的導(dǎo)姆泻。
- 設(shè)置超參數(shù)--初始點(diǎn)的theta,進(jìn)入無(wú)限迭代中冒嫡,先記錄本次點(diǎn)的theta值定義last拇勃,然后計(jì)算下一個(gè)點(diǎn)的theta,判斷本次theta和上一次theta點(diǎn)的J值差距是否小于最小差距孝凌,若是則跳出方咆,否則繼續(xù)循環(huán)。
3.生成梯度下降尋找最小值的過(guò)程展示:
import matplotlib.pyplot as plt
import numpy as np
# 設(shè)置初始theta
theta = 0.0
# 記錄尋找過(guò)程中的點(diǎn)
theta_list = []
while True:
# 記錄當(dāng)前位置,后邊用于判斷是否跳出
last = theta
theta_list.append(theta)
# 計(jì)算下一個(gè)點(diǎn)的位置,直接賦給循環(huán)變量theta
theta = theta-eta * DJ(theta)
if abs(J(theta) - J(last)) < epsilon:
break
print(len(theta_list)) # 45
# 畫(huà)圖
# J函數(shù)的x,y
plot_x = np.linspace(-1,6,141)
plot_y = J(plot_x)
plt.plot(plot_x,plot_y)
plt.plot(theta_list,J(np.array(theta_list)),color='r',marker='+')
plt.show()
運(yùn)行:
由圖:
從左到右的過(guò)程中蟀架,+號(hào)越來(lái)月稀疏瓣赂。
總共循環(huán)了45次,找到了這個(gè)theta最小值辜窑。
這個(gè)循環(huán)次數(shù)是由學(xué)習(xí)率n钩述,初始點(diǎn),和最小差距決定的穆碎。
例如:
1.將學(xué)習(xí)率調(diào)小
將學(xué)習(xí)率n為0.000001
那么,循環(huán)次數(shù)達(dá)到了1956011次
運(yùn)行:
我們可以多次改變初始點(diǎn)的位置以保證準(zhǔn)確性职恳。
2.調(diào)大學(xué)習(xí)率
將學(xué)習(xí)率n為0.8
那么所禀,循環(huán)次數(shù)為21次
運(yùn)行:
學(xué)習(xí)率不能調(diào)的過(guò)大方面,結(jié)果會(huì)過(guò)大,會(huì)報(bào)錯(cuò)色徘。
修改J函數(shù):
def J(theta):
try:
return (theta-2.5)**2 - 1
except:
return float('inf') # 返回一個(gè)很大的float數(shù)
J()在返回值進(jìn)入下方循環(huán)中時(shí)恭金,能保證不報(bào)錯(cuò),但是會(huì)陷入死循環(huán)中褂策,因?yàn)樵趯W(xué)習(xí)率很大時(shí)横腿,在x軸上的跳躍很快,當(dāng)循環(huán)次數(shù)達(dá)到一定程度斤寂,就會(huì)超出范圍耿焊。
所以在下邊我們封裝為函數(shù)后,可以加一個(gè)循環(huán)次數(shù)的參數(shù)遍搞,就可以查看其超出范圍是怎么回事了罗侯。
3.封裝為函數(shù)
def gradient_descent(init_theta,eta,n_iters=1e4,epsilon=1e-8):
'''
:param init_theta: 初始點(diǎn)的theta值
:param eta: 學(xué)習(xí)率
:param n_iters: 循環(huán)次數(shù),默認(rèn)為1e4
:param epsilon: 最小差距值,默認(rèn)為1e-8
:return:
'''
# 將初始化的init_theta賦給theta
theta = init_theta
i_iter = 0 # 初始化循環(huán)次數(shù)
theta_history = [] # 初始化路徑列表
while i_iter < n_iters: # 當(dāng)循環(huán)次數(shù)小于參數(shù)n_iter時(shí),執(zhí)行循環(huán)
last = theta # 記錄本次的theta
theta_history.append(theta) # 傳進(jìn)列表
theta = theta - eta * DJ(theta) # 計(jì)算下次的theta
# 判斷上次的J值和本次的J值差距是否小于最小值
if abs(J(theta) - J(last)) < epsilon:
break
# 循環(huán)次數(shù)+1
i_iter += 1
return theta,theta_history # 最佳theta值和尋找theta值路徑列表
調(diào)用:
# 調(diào)用函數(shù)
eta = 1.1
result,result_list = gradient_descent(init_theta=0.0,eta=eta,n_iters=10)
print(result)
print(result_list)
# 畫(huà)圖
plt.plot(plot_x,plot_y)
plt.plot(result_list,J(np.array(result_list)),color='r')
plt.show()
將eta設(shè)置為1.1后,循環(huán)次數(shù)設(shè)為9次溪猿,運(yùn)行:
開(kāi)始值很小钩杰,慢慢的J值變化越來(lái)越快,就會(huì)溢出诊县。
2. 線性回歸中的梯度下降法
上述的過(guò)程實(shí)現(xiàn)了簡(jiǎn)單的線性回歸關(guān)于y = ax
的實(shí)現(xiàn)讲弄,而y = ax+b
以及多元線性回歸中的y = a(0)+a(1)x(1)+a(2)x(2)+...a(n)x(n)
并沒(méi)有實(shí)現(xiàn)。
來(lái)看看多元線性回歸:
y軸還是J函數(shù)依痊,x軸變?yōu)橐粋€(gè)數(shù)據(jù)集---(theta(0),theta(1),...theta(n))垂睬。為了表達(dá)清除,化成平面圖抗悍,其實(shí)這個(gè)模型圖是一個(gè)n維的模型圖驹饺,我們畫(huà)不出來(lái),但是我們可以畫(huà)一個(gè)三維的來(lái)看一下:
一個(gè)三維空間中的梯度下降法(x,y為系數(shù)缴渊,z為損失函數(shù)):
在這里中theta是一個(gè)向量赏壹,可將y = ax+b中的b看作a(0),那么對(duì)于這個(gè)一元線性回歸來(lái)說(shuō),這個(gè)theta就是一個(gè)有兩行一列的向量衔沼。多元線性方程是多行一列的向量蝌借。,其實(shí)本質(zhì)上都是一樣的指蚁。
原理:
原理與一元線性回歸一樣菩佑,隨即設(shè)置一個(gè)點(diǎn),不斷的迭代更新該點(diǎn)凝化,進(jìn)而找出最優(yōu)點(diǎn)稍坯。
- 這里就牽扯到一個(gè)變量--更新量。也就是一元中的【-n(d(J)/d(theta))】
在多元中,由于theta是一個(gè)向量瞧哟,那么要不斷更新該點(diǎn)的位置混巧,那么這個(gè)更新量也必須是一個(gè)向量,該向量用【-n(J對(duì)每一個(gè)theta的偏導(dǎo))】勤揩。 - 這樣(廣播操作)向量-向量后的結(jié)果還是向量咧党,達(dá)到了移動(dòng)該點(diǎn)的效果。對(duì)應(yīng)圖中的(1)(2)點(diǎn)的效果陨亡。
- 根據(jù)新的theta求出該點(diǎn)的J,比較差距跳出即可傍衡。
1.損失函數(shù)求導(dǎo)的公式推導(dǎo)
對(duì)于多元的損失函數(shù)來(lái)說(shuō),他不想一元的J那樣求導(dǎo)的話一眼能看出來(lái)负蠕,
例如:
J =(theta-2.5)2 -1
DJ = 2(theta-2.5)
多元的J比較復(fù)雜:
不可能看出來(lái)答案蛙埂,所以我們要對(duì)它進(jìn)行推導(dǎo)。
推導(dǎo):
推導(dǎo)思路與線性回歸章節(jié)中的思想一樣虐急,將y_head表達(dá)為兩個(gè)矩陣的點(diǎn)乘進(jìn)行計(jì)算:
y_head = X(b) (*) theta
X(b)和theta的表示:
所以對(duì)J可以表示為:
對(duì)于J求導(dǎo):
求導(dǎo)的依據(jù)【標(biāo)量對(duì)列向量】的求導(dǎo)方法進(jìn)行求導(dǎo)箱残。
但是上面推導(dǎo)出的式子的大小是和樣本數(shù)有關(guān)的,m越大止吁,結(jié)果越大被辑,這是不合理的,我們希望和m無(wú)關(guān)敬惦,所以對(duì)損失函數(shù)J除以m:
那么J導(dǎo)就變?yōu)椋?/p>
這樣的話J函數(shù)對(duì)theta向量求導(dǎo)后的數(shù)據(jù)就表示出來(lái)了盼理。
2.在線性回歸的梯度下降代碼實(shí)現(xiàn)
1.J的函數(shù)定義
損失函數(shù)的公式:
根據(jù)公式得知參數(shù)有 測(cè)試數(shù)據(jù):X_b,對(duì)應(yīng)真實(shí)值y,以及theta值俄删。
# 損失函數(shù)J
def J(theta,X_b,y):
try:
return np.sum((y - X_b.dot(theta))**2)/len(X_b)
except:
return float('inf')
2.DJ的實(shí)現(xiàn)
對(duì)DJ的公式進(jìn)行分析:
DJ的公式結(jié)果是一個(gè)向量宏怔,其中數(shù)據(jù)的個(gè)數(shù)是由theta向量個(gè)數(shù)決定的,因?yàn)樗恳豁?xiàng)都是J/theta的導(dǎo)畴椰。所以y = ax+b其實(shí)就相當(dāng)于只有theta(0)和theta(1)兩個(gè)元素臊诊。
我們將它分為兩個(gè)部分,第一部分是對(duì)theta(0)的導(dǎo)斜脂,其余部分是第二部分抓艳。這樣我們便于計(jì)算。
參數(shù)列表:測(cè)試集數(shù)據(jù)X_b,對(duì)應(yīng)真實(shí)值y帚戳,以及theta向量玷或。
DJ函數(shù):
def DJ(theta,X_b,y):
res = np.empty(len(theta))
# 第一個(gè)元素
res[0] = np.sum(X_b.dot(theta) - y)
# 第二個(gè)往后
for i in range(1,len(theta)):
res[i] = (X_b.dot(theta) - y).dot(X_b[:,i])
return res *2 / len(X_b)
分析:
- 首先創(chuàng)建一個(gè)與theta向量等長(zhǎng)的數(shù)組res,用于填充數(shù)據(jù)片任。這個(gè)原因上邊說(shuō)過(guò)偏友。計(jì)算第一部分?jǐn)?shù)據(jù)的值--計(jì)算括號(hào)內(nèi)數(shù)據(jù)直接求和即可,賦給res[0]对供。
- 其他數(shù)據(jù)項(xiàng)使用for循環(huán)循環(huán)計(jì)算位他,為了便于計(jì)算,我們把它換位向量的點(diǎn)乘。
首先分析這其中一項(xiàng):
-
該數(shù)據(jù)項(xiàng)是m個(gè)數(shù)據(jù)值之和棱诱,他的每一項(xiàng)數(shù)據(jù)值都是標(biāo)量*標(biāo)量泼橘,例如第二行分析:
所以就可以將公式看作(以第二行數(shù)據(jù)為例):
那么用就可以在代碼用for循環(huán)給每一項(xiàng)賦值涝动。
- 最后返回res * 2 / len(X_b)迈勋。
3.梯度下降函數(shù)
與之前的y = ax的函數(shù)大概一致,只是在函數(shù)中調(diào)用J和DJ函數(shù)時(shí)要使用的參數(shù)X_b和y要加在該函數(shù)的參數(shù)中醋粟。
gradient_descent_many:
def gradient_descent_many(X_b,y,init_theta,eta,n_iters=1e4,epsilon=1e-8):
'''
:param X_b: 數(shù)據(jù)集靡菇,經(jīng)過(guò)改造(第0列都為1)
:param y: 數(shù)據(jù)集對(duì)應(yīng)正確值
:param init_theta: 初始點(diǎn)的theta值,一個(gè)向量
:param eta: 學(xué)習(xí)率
:param n_iters: 循環(huán)次數(shù),默認(rèn)為1e4
:param epsilon: 最小差距值,默認(rèn)為1e-8
:return: theta向量
'''
# 將初始化的init_theta賦給theta
theta = init_theta
i_iter = 0 # 初始化循環(huán)次數(shù)
# theta_history = [] # 初始化路徑列表
while i_iter < n_iters: # 當(dāng)循環(huán)次數(shù)小于參數(shù)n_iter時(shí),執(zhí)行循環(huán)
last = theta # 記錄本次的theta
# theta_history.append(theta) # 傳進(jìn)列表
theta = theta - eta * DJ(theta,X_b,y) # 計(jì)算下次的theta
# 判斷上次的J值和本次的J值差距是否小于最小值
if abs(J(theta,X_b,y) - J(last,X_b,y)) < epsilon:
break
# 循環(huán)次數(shù)+1
i_iter += 1
return theta
4.生成數(shù)據(jù)米愿,測(cè)試梯度下降
'''生成數(shù)據(jù)'''
np.random.seed(666)
x = 2 * np.random.random(size=100)
# 修改為100行1列的二維數(shù)組,也就是100個(gè)數(shù)據(jù)厦凤,每個(gè)數(shù)據(jù)1個(gè)特征
X = x.reshape(-1,1)
# 生成100個(gè)靠近y = 3x + 4 直線的點(diǎn)
y = x * 3. + 4. + np.random.normal(size=100)
# 將X修改其中數(shù)據(jù)值為X_b
ones = np.ones((len(X),1))
X_b = np.hstack([ones,X])
print(X_b[:5])
# [[1. 1.40087424]
# [1. 1.68837329]
# [1. 1.35302867]
# [1. 1.45571611]
# [1. 1.90291591]]
# 設(shè)置init_theta,一個(gè)列向量,其長(zhǎng)度與數(shù)據(jù)集X_b的維度(列數(shù))相同
# 不能是X育苟,因?yàn)閠heta向量包含theta(0)
init_theta = np.zeros(X_b.shape[1])
print(init_theta)
# [0. 0.]
eta = 0.01
如果梯度下降法尋找出來(lái)的theta(0)接近4,theta(1)接近3较鼓,那么就說(shuō)明找到了。
theta = gradient_descent_many(X_b,y,init_theta,eta)
print(theta)
# [4.02145786 3.00706277]
成功Nグ亍2├谩!
5.整體代碼
import numpy as np
import matplotlib.pyplot as plt
# 損失函數(shù)J
def J(theta,X_b,y):
try:
return np.sum((y - X_b.dot(theta))**2)/len(X_b)
except:
return float('inf')
def DJ(theta,X_b,y):
res = np.empty(len(theta))
# 第一個(gè)元素
res[0] = np.sum(X_b.dot(theta) - y)
# 第二個(gè)往后
for i in range(1,len(theta)):
res[i] = (X_b.dot(theta) - y).dot(X_b[:,i])
return res *2 / len(X_b)
def gradient_descent_many(X_b,y,init_theta,eta,n_iters=1e4,epsilon=1e-8):
'''
:param X_b: 數(shù)據(jù)集漱竖,經(jīng)過(guò)改造(第0列都為1)
:param y: 數(shù)據(jù)集對(duì)應(yīng)正確值
:param init_theta: 初始點(diǎn)的theta值禽篱,一個(gè)向量
:param eta: 學(xué)習(xí)率
:param n_iters: 循環(huán)次數(shù),默認(rèn)為1e4
:param epsilon: 最小差距值,默認(rèn)為1e-8
:return:
:return:
'''
# 將初始化的init_theta賦給theta
theta = init_theta
i_iter = 0 # 初始化循環(huán)次數(shù)
# theta_history = [] # 初始化路徑列表
while i_iter < n_iters: # 當(dāng)循環(huán)次數(shù)小于參數(shù)n_iter時(shí),執(zhí)行循環(huán)
last = theta # 記錄本次的theta
# theta_history.append(theta) # 傳進(jìn)列表
theta = theta - eta * DJ(theta,X_b,y) # 計(jì)算下次的theta
# 判斷上次的J值和本次的J值差距是否小于最小值
if abs(J(theta,X_b,y) - J(last,X_b,y)) < epsilon:
break
# 循環(huán)次數(shù)+1
i_iter += 1
return theta
'''生成數(shù)據(jù)'''
np.random.seed(666)
x = 2 * np.random.random(size=100)
# 修改為100行1列的二維數(shù)組,也就是100個(gè)數(shù)據(jù),每個(gè)數(shù)據(jù)1個(gè)特征
X = x.reshape(-1,1)
# 生成100個(gè)靠近y = 3x + 4 直線的點(diǎn)
y = x * 3. + 4. + np.random.normal(size=100)
# 將X修改其中數(shù)據(jù)值為X_b
ones = np.ones((len(X),1))
X_b = np.hstack([ones,X])
print(X_b[:5])
# [[1. 1.40087424]
# [1. 1.68837329]
# [1. 1.35302867]
# [1. 1.45571611]
# [1. 1.90291591]]
# 設(shè)置init_theta,一個(gè)列向量馍惹,其長(zhǎng)度與數(shù)據(jù)集X_b的維度(列數(shù))相同
# 不能是X躺率,因?yàn)閠heta向量包含theta(0)
init_theta = np.zeros(X_b.shape[1])
print(init_theta)
# [0. 0.]
eta = 0.01
theta = gradient_descent_many(X_b,y,init_theta,eta)
print(theta)
# [4.02145786 3.00706277]
3. 封裝梯度下降函數(shù)-->線性回歸類(lèi)
在之前我們寫(xiě)的線性回歸的類(lèi)中。我們的fit方法是通過(guò)公式推導(dǎo)的方式計(jì)算theta的万矾,而梯度下降法是通過(guò)不斷迭代尋找最好的theta的悼吱。
fit_gd():梯度下降法函數(shù),在該函數(shù)中封裝了三個(gè)函數(shù):
- J():計(jì)算損失函數(shù)
- DJ():計(jì)算損失函數(shù)的導(dǎo)
- gradient_descent():尋找最佳theta
- fit_gd()的參數(shù)為原始訓(xùn)練數(shù)據(jù)良狈,而這三個(gè)內(nèi)部函數(shù)的參數(shù)數(shù)據(jù)為經(jīng)過(guò)處理的數(shù)據(jù)后添,所以在內(nèi)部函數(shù)外要進(jìn)行數(shù)據(jù)整理(X_b)以及參數(shù)(init_theta,eta)的設(shè)置。
class LinearRegression:
def __init__(self):
"""初始化Linear Regression模型"""
## 系數(shù)向量(θ1,θ2,.....θn)
self.coef_ = None
## 截距 (θ0)
self.interception_ = None
## θ向量
self._theta = None
'''線性回歸公式訓(xùn)練模型们颜,計(jì)算theta'''
def fit_normal(self, X_train, y_train):
# 拼接為X(b)格式的數(shù)據(jù)吕朵,-----在每行的第一列之前加上1.
ones_vector = np.ones((len(X_train), 1))
X_b = np.hstack([ones_vector, X_train])
# 根據(jù)X_b帶入公式計(jì)算w
# arr.dot(arr):點(diǎn)乘
# np.linalg.inv(arr):矩陣的逆
self._theta = np.linalg.inv(X_b.T.dot(X_b)).dot(X_b.T).dot(y_train)
self.coef_ = self._theta[1:]
self.interception_ = self._theta[0]
return self
'''使用梯度下降尋找theta'''
def fit_gd(self, X_train, y_train, eta=0.01, n_iters=1e4):
"""根據(jù)訓(xùn)練數(shù)據(jù)集X_train, y_train, 使用梯度下降法訓(xùn)練Linear Regression模型"""
assert X_train.shape[0] == y_train.shape[0], \
"the size of X_train must be equal to the size of y_train"
def J(theta, X_b, y):
try:
return np.sum((y - X_b.dot(theta)) ** 2) / len(y)
except:
return float('inf')
def dJ(theta, X_b, y):
res = np.empty(len(theta))
res[0] = np.sum(X_b.dot(theta) - y)
for i in range(1, len(theta)):
res[i] = (X_b.dot(theta) - y).dot(X_b[:, i])
return res * 2 / len(X_b)
def gradient_descent(X_b, y, initial_theta, eta, n_iters=1e4, epsilon=1e-8):
theta = initial_theta
cur_iter = 0
while cur_iter < n_iters:
gradient = dJ(theta, X_b, y)
last_theta = theta
theta = theta - eta * gradient
if (abs(J(theta, X_b, y) - J(last_theta, X_b, y)) < epsilon):
break
cur_iter += 1
return theta
X_b = np.hstack([np.ones((len(X_train), 1)), X_train])
initial_theta = np.zeros(X_b.shape[1])
self._theta = gradient_descent(X_b, y_train, initial_theta, eta, n_iters)
self.interception_ = self._theta[0]
self.coef_ = self._theta[1:]
return self
'''測(cè)試集'''
def perdict(self, X_perdict):
ones_vector = np.ones((len(X_perdict), 1))
X_b = np.hstack([ones_vector, X_perdict])
y_head = X_b.dot(self._theta)
return y_head
'''MSE'''
def mean_squared_error(self, y_true, y_predict):
"""計(jì)算y_true和y_predict之間的MSE"""
assert len(y_true) == len(y_predict), \
"the size of y_true must be equal to the size of y_predict"
return np.sum((y_true - y_predict) ** 2) / len(y_true)
'''R^2'''
def r2_score(self, y_true, y_perdict):
r2_score = 1 - self.mean_squared_error(y_true, y_perdict) / np.var(y_true)
return r2_score
def __repr__(self):
return 'LinearRegression'
調(diào)用:
'''生成數(shù)據(jù)'''
np.random.seed(666)
x = 2 * np.random.random(size=100)
# 修改為100行1列的二維數(shù)組,也就是100個(gè)數(shù)據(jù),每個(gè)數(shù)據(jù)1個(gè)特征
X = x.reshape(-1,1)
# 生成100個(gè)靠近y = 3x + 4 直線的點(diǎn)
y = x * 3. + 4. + np.random.normal(size=100)
# 實(shí)例化算法模型
lin = LinearRegression()
lin.fit_gd(X,y)
print(lin.coef_) # [3.00706277]
print(lin.interception_) # 4.021457858204859
print(lin._theta) # [4.02145786 3.00706277]
訓(xùn)練結(jié)果大概正確窥突。
4.向量化
我們?cè)谟?jì)算多元線性回歸的的損失函數(shù)J對(duì)每個(gè)theta值的導(dǎo)DJ時(shí)略顯麻煩努溃,先計(jì)算第一個(gè),在循環(huán)計(jì)算其他得數(shù)據(jù)項(xiàng)阻问,如果把這個(gè)DJ用向量的點(diǎn)乘表示出來(lái)梧税,那么就輕松了。
推導(dǎo):
由于X(b)的第0列的所有數(shù)據(jù)都是1,所以上式成立第队。
- 觀察上式右邊的式子,先不看 2/m:
它是一個(gè)n+1 * 1 格式的向量哮塞,其每一項(xiàng)都是m個(gè)標(biāo)量求和,那么去掉求和符號(hào)的話:所以其每一項(xiàng)必然是一個(gè)行向量(1 * m)(點(diǎn)乘)一個(gè)列向量(m * 1),并且他們每一個(gè)行式子的區(qū)別都是右邊向量X_b的列數(shù)變化
- 那么我們呢可以將左邊看作一個(gè)列向量(m * 1)凳谦,右邊看作一個(gè)矩陣忆畅,該矩陣就是X_b矩陣的轉(zhuǎn)置矩陣(n+1 * m)。
展開(kāi)來(lái)寫(xiě):
這樣計(jì)算出來(lái)就是DJ了尸执。
修改DJ函數(shù):
def DJ(theta,X_b,y):
return X_b.T.dot(X_b.dot(theta) - y)* 2. / len(X_b)
使用boston房?jī)r(jià)進(jìn)行測(cè)試:
from sklearn import datasets
from KNN.knn_iris import train_test_split
from Tidudown.manyTidu.tuduClass import LinearRegression
data = datasets.load_boston()
X = data.data
y = data.target
X = X[y<50]
y = y[y<50]
X_train,y_train,x_test,y_test = train_test_split(X,y)
lr = LinearRegression()
lr.fit_gd(X_train,y_train,eta = 0.000001,n_iters=1e6)
y_perdict = lr.perdict(x_test)
z = lr.r2_score(y_test,y_perdict)
print(z)
# 0.6908063505166206
使用公式推導(dǎo)的方式計(jì)算theta訓(xùn)練模型:
# 使用公式推導(dǎo)的方式訓(xùn)練模型
lr.fit_normal(X_train,y_train)
y_perdict = lr.perdict(x_test)
z = lr.r2_score(y_test,y_perdict)
print(z)
# 0.784704968177089
在使用真實(shí)的數(shù)據(jù)家凯,調(diào)整eta和n_iters,
- 要么由于eta太小導(dǎo)致無(wú)法得出真實(shí)的結(jié)果如失,導(dǎo)致學(xué)習(xí)不好绊诲,正確率低
- 要么由于eta太大導(dǎo)致訓(xùn)練時(shí)間加長(zhǎng),這是由于數(shù)據(jù)的規(guī)模在不同的特征上不同褪贵,所以我們需要對(duì)數(shù)據(jù)進(jìn)行歸一化掂之。
5.數(shù)據(jù)歸一化
為了防止數(shù)據(jù)中某一列數(shù)據(jù)的值過(guò)大導(dǎo)致結(jié)果發(fā)生偏差,要進(jìn)行數(shù)據(jù)歸一化操作脆丁。
from sklearn import datasets
from KNN.knn_iris import train_test_split
from Tidudown.manyTidu.tuduClass import LinearRegression
from sklearn.preprocessing import StandardScaler
data = datasets.load_boston()
X = data.data
y = data.target
X = X[y<50]
y = y[y<50]
# 分割數(shù)據(jù)
X_train,y_train,x_test,y_test = train_test_split(X,y)
# 數(shù)據(jù)歸一化
ss = StandardScaler()
ss.fit(X_train)
X_train_standard = ss.transform(X_train)
x_test_standard = ss.transform(x_test)
# 開(kāi)始學(xué)習(xí)
lin = LinearRegression()
# 梯度下降
lin.fit_gd(X_train_standard,y_train)
y_perdict = lin.perdict(x_test_standard)
re = lin.r2_score(y_test,y_perdict)
print(re)
# 0.7876525859547368
# 公式推導(dǎo)
lin.fit_normal(X_train,y_train)
y_perdict = lin.perdict(x_test)
z = lin.r2_score(y_test,y_perdict)
print(z)
# 0.7876600935022398
可以看到使用兩種方式學(xué)習(xí)后的正確率基本都差不多了世舰。
6.梯度下降法的優(yōu)勢(shì)
自己定義一個(gè)訓(xùn)練集,theta向量偎快,以及他們之間的線值y冯乘。
m = 1000
n = 5000
big_X = np.random.normal(size=(m, n))
true_theta = np.random.uniform(0.0, 100.0, size=n+1)
big_y = big_X.dot(true_theta[1:]) + true_theta[0] + np.random.normal(0., 10., size=m)
big_reg1 = LinearRegression()
%time big_reg1.fit_normal(big_X, big_y)
# CPU times: user 18.8 s, sys: 899 ms, total: 19.7 s
# Wall time: 10.9
big_reg2 = LinearRegression()
%time big_reg2.fit_gd(big_X, big_y)
# CPU times: user 9.51 s, sys: 121 ms, total: 9.63 s
# Wall time: 5.76 s
如果樣本數(shù)非常多,那么即使使用梯度下降法也會(huì)導(dǎo)致速度比較慢晒夹,因?yàn)樵谔荻认陆捣ㄖ旭陕琂的值需要每一個(gè)樣本都要參與運(yùn)算。這時(shí)候需要采用隨機(jī)梯度下降法丐怯。