【機(jī)器學(xué)習(xí):理論+實(shí)戰(zhàn)】線性回歸,你還只會(huì)調(diào)包嗎?
自己動(dòng)手實(shí)現(xiàn)線性回歸
我們往往會(huì)用類似sklearn的linear_model模塊直接調(diào)包去使用線性回歸處理一些回歸問題,但我們絕不能只會(huì)調(diào)包!
接下來我們就用面向?qū)ο蟮姆绞?,自己?shí)現(xiàn)一個(gè)線性回歸類,并且在這個(gè)過程中體會(huì)從數(shù)學(xué)公式到python代碼的美妙。
在這里我將不會(huì)講解線性回歸的原理和公式(畢竟這是python為主題的公眾號),而是聚焦于”從公式到代碼“的過程。所以如果對公式原理以及一些線性回歸的相關(guān)術(shù)語不熟悉,可以去看相關(guān)的資料充充電,這并不難,畢竟線性回歸是機(jī)器學(xué)習(xí)里最簡單直觀的算法了
簡述線性回歸模型
線性回歸就是輸入一堆數(shù)據(jù)(假設(shè)有m條,每條數(shù)據(jù)有n個(gè)屬性,也就是m行n列),結(jié)果訓(xùn)練后得到模型參數(shù)(也就是線性回歸方程中與每個(gè)屬性相乘的那些值,有幾n個(gè)屬性,就有對應(yīng)的n個(gè)參數(shù))的過程。
訓(xùn)練的方法有許多,我大致分兩類,一類就是正則方程,也就是直接通過解矩陣方程得到參數(shù)。另一類就是梯度下降,它又可以細(xì)分為批量梯度下降算法、隨機(jī)梯度下降算法以及小批量梯度下降算法。梯度下降更加有通用性,因?yàn)槠渌鼨C(jī)器學(xué)習(xí)模型的損失函數(shù)都極其復(fù)雜,無法獲得最值的解析解,所以可以說正則方程法算是線性回歸模型的”特色“。
設(shè)計(jì)自定義的線性回歸類
初始化函數(shù):
初始化函數(shù)接收參與訓(xùn)練的數(shù)據(jù)集,并且隨機(jī)設(shè)定模型參數(shù)
getNormalEquation函數(shù)
正則方程函數(shù)
BGD函數(shù)
BGD函數(shù)是梯度下降算法的函數(shù)
SGD函數(shù)
SGD函數(shù)是隨機(jī)梯度下降算法函數(shù),接收學(xué)習(xí)率這個(gè)參數(shù)
MBGD函數(shù)
MBGD函數(shù)是小批量梯度下降算法的函數(shù),接收學(xué)習(xí)率這個(gè)參數(shù)
train_BGD函數(shù)
用BGD算法訓(xùn)練,接收迭代率和學(xué)習(xí)率兩個(gè)參數(shù)
train_SGD函數(shù)
用SGD算法訓(xùn)練,接收迭代率和學(xué)習(xí)率兩個(gè)參數(shù)
train_MBGD函數(shù)
用MBGD算法訓(xùn)練,接收迭代率和學(xué)習(xí)率兩個(gè)參數(shù)
Shuffle函數(shù)
這個(gè)函數(shù)主要用于生成與訓(xùn)練數(shù)據(jù)集大小相同的隨機(jī)自然數(shù)序列。主要用于SGD函數(shù)和MBGD函數(shù)中,通過隨機(jī)數(shù)序列打亂訓(xùn)練集和標(biāo)簽集,消除訓(xùn)練集順序?qū)GD算法和MBGD算法的影響
Cost函數(shù)
這個(gè)函數(shù)就是平均訓(xùn)練的損失函數(shù),計(jì)算訓(xùn)練數(shù)據(jù)集在模型參數(shù)下的預(yù)測結(jié)果與實(shí)際值之間的均方誤差。它主要用于三個(gè)迭代訓(xùn)練函數(shù)中,記錄每次迭代后的誤差
predict函數(shù)
這個(gè)函數(shù)是預(yù)測函數(shù),接受一組測試數(shù)據(jù),然后結(jié)合模型參數(shù)對測試數(shù)據(jù)進(jìn)行預(yù)測
test函數(shù)
這個(gè)函數(shù)和預(yù)測函數(shù)很相似,都是結(jié)合模型參數(shù)預(yù)測結(jié)果,不同的是這個(gè)函數(shù)接受的是測試數(shù)據(jù)集,它遍歷整個(gè)測試數(shù)據(jù)集,每次都調(diào)用predict函數(shù)進(jìn)行預(yù)測
自定義線性回歸類的結(jié)構(gòu)
import?numpy?as?np
"""
這是一個(gè)關(guān)于線性回歸的模塊
核心部分就是LinearRegression這個(gè)類
這個(gè)類有以下幾個(gè)方法
構(gòu)造方法、根據(jù)參數(shù)計(jì)算預(yù)測值的方法、計(jì)算損失值的方法、生成隨機(jī)數(shù)序列的方法、使用批量梯度下降、隨機(jī)梯度下降、小批量隨機(jī)梯度下降更新參數(shù)的方法
"""
class?LinearRegress:
???def?__init__(self,?input_data,?real_result,?theta):
???def?Cost(self):
???def?Shuffle(self):
???def?BGD(self,alpha):
???def?SGD(self,alpha):
???def?MBGD(self,alpha,batch_size):
???def?train_BGD(self,iter,alpha):
???def?train_SGD(self,iter,alpha):
???def?train_MBGD(self,iter,batch_size,alpha):
???def?predict(self,data):
???def?test(self,test_data):
初始化函數(shù)的代碼實(shí)現(xiàn)
初始化函數(shù)__init__的參數(shù)是訓(xùn)練數(shù)據(jù)集input_data、訓(xùn)練結(jié)果集real_result以及參數(shù)theta。在初始化函數(shù)內(nèi),主要完成訓(xùn)練數(shù)據(jù)集、結(jié)果集以及模型參數(shù)的初始化,與此同時(shí)還要遍歷每組數(shù)據(jù),并且要對數(shù)據(jù)進(jìn)行拓展,也就是增加一維常數(shù)屬性1(因?yàn)榫€性回歸方程都是z=ax+by+c這種類似形式,c前面的系數(shù)是1) 當(dāng)theta的默認(rèn)值是None,如果是None,那么就用正態(tài)分布隨機(jī)給模型參數(shù)賦值,如若不是,就用theta作為模型參數(shù)
import?numpy?as?np
class?LinearRegress:
?def?__init__(self,?input_data,?real_result,?theta):
??"""
? inputData :輸入數(shù)據(jù)??(m條記錄,n個(gè)屬性,m*n)
? realResult :真實(shí)數(shù)據(jù)??(m條記錄,1個(gè)值,m*1)
? theta :線性回歸的參數(shù),也就是這個(gè)算法不斷更新迭代優(yōu)化得到的核心數(shù)據(jù)?(對應(yīng)n個(gè)屬性,n*1)
??默認(rèn)為None??如果是None就用正態(tài)分布的數(shù)據(jù)來初始化參數(shù),如果不是None,就用傳進(jìn)來的theta初始化參數(shù)
??"""
??#?輸入數(shù)據(jù)集的形狀
??row,?col?=?np.shape(input_data)
??#?通過輸入數(shù)據(jù)集,建立這個(gè)類里面的成員變量Data,先初始化為m個(gè)0組成的列表
??self.Data?=?[0]?*?row
??#?給m條數(shù)據(jù)中的每一條數(shù)據(jù)都添加上一個(gè)常數(shù)項(xiàng)1,這一步是為了與線性回歸模型中的偏置b對應(yīng)起來
??for?(index,?data)?in?enumerate(input_data):
???#?將每一條數(shù)據(jù)構(gòu)成的列表后面都追加一個(gè)1.0,并且根據(jù)序號index將它們存放到成員變量Data相應(yīng)位置中
????self.Data[index]?=?list(data).append(1.0)
??#?轉(zhuǎn)化為numpy的格式更加高效
??self.Data?=?np.array(self.Data)
??#?通過輸入的結(jié)果數(shù)據(jù)集,建立這個(gè)類中的成員變量Result
??self.Result?=?real_result
??#?如果參數(shù)不為None的時(shí)候,利用傳入的theta初始化相應(yīng)參數(shù)
??if?theta?is?not?None:
???self.Theta?=?theta
??#?不然就用正態(tài)分布的數(shù)據(jù)來初始化
??else:
???#?此處選取了(n+1*1)的數(shù)據(jù),也是為了和偏置匹配
???self.Theta?=?np.random.normal((col?+?1,?1))
輔助函數(shù)的編寫
在前面一章我們已經(jīng)提到了這個(gè)線性回歸類的函數(shù)(方法)集合,我們先來看一看一些輔助函數(shù),它們往往運(yùn)用在其它的函數(shù)中,因此先對它們進(jìn)行介紹。
Shuffle函數(shù)
功能:在運(yùn)行SGD算法函數(shù)或者M(jìn)BGD算法函數(shù)前,隨機(jī)打亂原始的數(shù)據(jù)集以及對應(yīng)的標(biāo)簽集。
??def?Shuffle(self):
??????"""
??????這是在運(yùn)行SGD算法或者M(jìn)BGD算法之前,隨機(jī)打亂后原始數(shù)據(jù)集的函數(shù)
??????"""
??????#?首先獲得訓(xùn)練集規(guī)模,之后按照規(guī)模生成自然數(shù)序列
??????length?=?len(self.InputData)
??????sequence?=?list(range(length))
??????#?利用numpy的隨機(jī)打亂函數(shù)打亂訓(xùn)練數(shù)據(jù)下標(biāo)
??????random_sequence?=?np.random.permutation(sequence)
??????#?返回?cái)?shù)據(jù)集隨機(jī)打亂后的數(shù)據(jù)序列
??????return?random_sequence
Cost函數(shù)
功能:計(jì)算訓(xùn)練數(shù)據(jù)集self.InputData在模型參數(shù)self.Theta下的預(yù)測結(jié)果(也就是它們之間進(jìn)行矩陣的乘法)與實(shí)際值之間的均方誤差。主要運(yùn)用在三種梯度下降算法函數(shù)中,記錄每次迭代訓(xùn)練后的均方誤差。
??def?Cost(self):
??????"""
??????這是計(jì)算損失函數(shù)的函數(shù)
??????"""
??????#?在線性回歸里的損失函數(shù)定義為真實(shí)結(jié)果與預(yù)測結(jié)果之間的均方誤差
??????#?首先計(jì)算輸入數(shù)據(jù)的預(yù)測結(jié)果
??????predict?=?self.InputData.dot(self.Theta).T
??????#?計(jì)算真實(shí)結(jié)果與預(yù)測結(jié)果之間的均方誤差
??????cost?=?predict-self.Result.T
??????cost?=?np.average(cost**2)
??????return?cost
predict函數(shù)
功能:對輸入的測試數(shù)據(jù)(1* n+1),結(jié)合模型參數(shù)(n+1* 1),作矩陣乘法獲得預(yù)測結(jié)果
??def?predict(self,data):
??????"""
??????這是對一組測試數(shù)據(jù)預(yù)測的函數(shù)
??????:param?data:?測試數(shù)據(jù)
??????"""
??????#?對測試數(shù)據(jù)加入1維特征,以適應(yīng)矩陣乘法
??????tmp?=?[1.0]
??????tmp.extend(data)
??????data?=?np.array(tmp)
??????#?計(jì)算預(yù)測結(jié)果,計(jì)算結(jié)果形狀為(1,),為了分析數(shù)據(jù)的方便
??????#?這里只返矩陣的第一個(gè)元素
??????predict_result?=?data.dot(self.Theta)[0]
??????return?predict_result
????????
test函數(shù)
功能:循環(huán)調(diào)用predict函數(shù),遍歷整個(gè)測試數(shù)據(jù)集,m條測試數(shù)據(jù)進(jìn)行預(yù)測
??def?test(self,test_data):
??????"""
??????這是對測試數(shù)據(jù)集的線性回歸預(yù)測函數(shù)
??????:param?test_data:?測試數(shù)據(jù)集
??????"""
??????#?定義預(yù)測結(jié)果數(shù)組
??????predict_result?=?[]
??????#?對測試數(shù)據(jù)進(jìn)行遍歷
??????for?data?in?test_data:
??????????#?預(yù)測每組data的結(jié)果
??????????predict_result.append(self.predict(data))
??????predict_result?=?np.array(predict_result)
??????return?predict_result
train_BGD函數(shù)
利用BGD算法函數(shù)進(jìn)行訓(xùn)練的函數(shù),iter代表迭代的次數(shù),alpha代表學(xué)習(xí)率,也就是每次更新模型參數(shù)的幅度大小
??def?train_BGD(self,iter,alpha):
??????"""
??????這是利用BGD算法迭代優(yōu)化的函數(shù)
??????:param?iter:?迭代次數(shù)
??????:param?alpha:?學(xué)習(xí)率
??????"""
??????#?定義訓(xùn)練損失數(shù)組,記錄每輪迭代的訓(xùn)練數(shù)據(jù)集的損失
??????Cost?=?[]
??????#?開始進(jìn)行迭代訓(xùn)練
??????for?i?in?range(iter):
??????????#?利用學(xué)習(xí)率alpha,結(jié)合BGD算法對模型進(jìn)行訓(xùn)練
??????????self.BGD(alpha)
??????????#?記錄每次迭代的訓(xùn)練損失
??????????Cost.append(self.Cost())
??????Cost?=?np.array(Cost)
??????return?Cost
train_SGD函數(shù)
利用SGD算法函數(shù)進(jìn)行訓(xùn)練的函數(shù),參數(shù)說明同上
??def?train_SGD(self,iter,alpha):
??????"""
??????這是利用SGD算法迭代優(yōu)化的函數(shù)
??????:param?iter:?迭代次數(shù)
??????:param?alpha:?學(xué)習(xí)率
??????"""
??????#?定義訓(xùn)練損失數(shù)組,記錄每輪迭代的訓(xùn)練數(shù)據(jù)集的損失
??????Cost?=?[]
??????#?開始進(jìn)行迭代訓(xùn)練
??????for?i?in?range(iter):
??????????#?利用學(xué)習(xí)率alpha,結(jié)合SGD算法對模型進(jìn)行訓(xùn)練
??????????self.SGD(alpha)
??????????#?記錄每次迭代的訓(xùn)練損失
??????????Cost.append(self.Cost())
??????Cost?=?np.array(Cost)
??????return?Cost
train_MBGD函數(shù)
利用MBGD算法函數(shù)進(jìn)行訓(xùn)練的函數(shù),參數(shù)說明同上
??def?train_MBGD(self,iter,batch_size,alpha):
??????"""
??????這是利用MBGD算法迭代優(yōu)化的函數(shù)
??????:param?iter:?迭代次數(shù)
??????:param?batch_size:?小樣本規(guī)模
??????:param?alpha:?學(xué)習(xí)率
??????"""
??????#?定義訓(xùn)練損失數(shù)組,記錄每輪迭代的訓(xùn)練數(shù)據(jù)集的損失
??????Cost?=?[]
??????#?開始進(jìn)行迭代訓(xùn)練
??????for?i?in?range(iter):
??????????#?利用學(xué)習(xí)率alpha,結(jié)合MBGD算法對模型進(jìn)行訓(xùn)練
??????????self.MBGD(alpha,batch_size)
??????????#?記錄每次迭代的訓(xùn)練損失
??????????Cost.append(self.Cost())
??????Cost?=?np.array(Cost)
??????return?Cost
正則方程法
所謂正則方程法就是用矩陣求導(dǎo)的方法,求取損失函數(shù)的最小值 在求導(dǎo)的過程中,會(huì)求解導(dǎo)函數(shù)等于零的方程,最終將會(huì)解得模型參數(shù)值為:訓(xùn)練數(shù)據(jù)集的轉(zhuǎn)置乘以訓(xùn)練數(shù)據(jù)集本身,之后對相乘結(jié)果求一個(gè)逆矩陣,再將整個(gè)逆矩陣乘以訓(xùn)練數(shù)據(jù)集本身,最后與結(jié)果數(shù)據(jù)集相乘。具體的推導(dǎo)過程可以看下圖
值得注意的是,正則方程法需要保證訓(xùn)練數(shù)據(jù)集的轉(zhuǎn)置乘以訓(xùn)練數(shù)據(jù)集本身,記為XT*X可以求得逆矩陣,但是在實(shí)際問題中,數(shù)據(jù)集的特征數(shù)n可能會(huì)大于數(shù)據(jù)集規(guī)模m,這就導(dǎo)致了XTX不可逆。故而在實(shí)際的編程過程中,會(huì)在XTX的基礎(chǔ)上加一個(gè)很小的可逆矩陣,例如0.001E,這樣就可以保證可逆了。
??def?getNormalEquation(self):
??????"""
??????這是利用正規(guī)方程計(jì)算模型參數(shù)Thetha
??????"""
??????"""
????????0.001*np.eye(np.shape(self.InputData.T))是
????????防止出現(xiàn)原始XT的行列式為0,即防止原始XT不可逆
?????"""
??????#?獲得輸入數(shù)據(jù)數(shù)組形狀
??????col,rol?=?np.shape(self.InputData.T)
??????#?計(jì)算輸入數(shù)據(jù)矩陣的轉(zhuǎn)置
??????XT?=?self.InputData.T+0.001*np.eye(col,rol)
??????#?計(jì)算矩陣的逆
??????inv?=?np.linalg.inv(XT.dot(self.InputData))
??????#?計(jì)算模型參數(shù)Thetha
??????self.Theta?=?inv.dot(XT.dot(self.Result))
梯度下降算法
梯度下降算法是求取最值的通法,通過每次的迭代更新,逐步逼近最優(yōu)解,常用于機(jī)器學(xué)習(xí)和人工智能當(dāng)中用來遞歸性地逼近最小偏差模型。
在線性回歸模型中,我們在求解損失函數(shù)的最小值時(shí),可以通過梯度下降法來一步步的迭代求解,得到最小化的損失函數(shù)和模型參數(shù)值。
具體可分為三種:批量梯度下降算法(Batch Gradient Descent)BGD、隨機(jī)梯度下降算法(Stochastic Gradient Descent)SGD 以及 小批量梯度下降算法(Mini Batch Gradient Descent)MBGD,下面我們來依次介紹這些算法的思想以及具體的編程實(shí)現(xiàn)。
批量梯度下降算法
算法流程:每次迭代遍歷數(shù)據(jù)集時(shí),保存每組訓(xùn)練數(shù)據(jù)對應(yīng)的梯度增量,待到遍歷結(jié)束后,計(jì)算所有梯度增量之和,再與當(dāng)前模型參數(shù)相加,更新模型參數(shù),重復(fù)上述過程,直到模型參數(shù)值收斂。顯然,經(jīng)過不斷的優(yōu)化迭代后,BGD算法幾乎可以收斂于全局最優(yōu)解(當(dāng)然還受限于學(xué)習(xí)率這個(gè)超參數(shù),它代表模型參數(shù)更新的幅度)。
??def?BGD(self,alpha):
??????"""
??????這是利用BGD算法進(jìn)行一次迭代調(diào)整參數(shù)的函數(shù)
??????:param?alpha:?學(xué)習(xí)率
??????"""
??????#?定義梯度增量數(shù)組
??????gradient_increasment?=?[]
??????#?對輸入的訓(xùn)練數(shù)據(jù)及其真實(shí)結(jié)果進(jìn)行依次遍歷
??????for?(input_data,real_result)?in?zip(self.InputData,self.Result):
??????????#?計(jì)算每組input_data的梯度增量,并放入梯度增量數(shù)組
??????????g?=?(real_result-input_data.dot(self.Theta))*input_data
??????????gradient_increasment.append(g)
??????#?按列計(jì)算屬性的平均梯度增量
??????avg_g?=?np.average(gradient_increasment,0)
??????#?改變平均梯度增量數(shù)組形狀
??????avg_g?=?avg_g.reshape((len(avg_g),1))
??????#?更新參數(shù)Theta
??????self.Theta?=?self.Theta?+?alpha*avg_g
BGD算法每次迭代都需要遍歷整個(gè)訓(xùn)練數(shù)據(jù)集,保存每組數(shù)據(jù)對應(yīng)的梯度增量,當(dāng)數(shù)據(jù)規(guī)模較大時(shí),會(huì)帶來很大的空間開銷?;诖?,隨機(jī)梯度下降算法 SGD 應(yīng)運(yùn)而生!
隨機(jī)梯度下降算法
BGD算法雖然可以幾乎收斂于全局最優(yōu)解,但是開銷太大,速度慢,適應(yīng)性不強(qiáng)。究其原因,還是每次都要遍歷整個(gè)訓(xùn)練數(shù)據(jù)集這個(gè)過程花銷太大了,所以就想到了每次就用一條數(shù)據(jù)來更新參數(shù)的方法,這就是隨機(jī)梯度下降算法!
算法流程:在規(guī)模為m的數(shù)據(jù)集中,每次取出一條,來計(jì)算對應(yīng)的模型參數(shù)梯度,每次都用這個(gè)梯度來更新模型參數(shù)。也就是說在迭代過程中更新m次。重復(fù)上述過程直到收斂,在這個(gè)過程中會(huì)先講數(shù)據(jù)集打亂。
這個(gè)算法與批量梯度下降的最大不同就是:前者需要累積整個(gè)數(shù)據(jù)集的梯度增量之和,而后再更新;而隨機(jī)梯度下降則是將每條數(shù)據(jù)產(chǎn)生的梯度增量立即更新到模型參數(shù)上。后者的空間開銷顯然要小
?def?SGD(self,alpha):
??????"""
??????這是利用SGD算法進(jìn)行一次迭代調(diào)整參數(shù)的函數(shù)
??????:param?alpha:?學(xué)習(xí)率
??????"""
??????#?首先將數(shù)據(jù)集隨機(jī)打亂,減小數(shù)據(jù)集順序?qū)?shù)調(diào)優(yōu)的影響
??????shuffle_sequence?=?self.Shuffle()
??????self.InputData?=?self.InputData[shuffle_sequence]
??????self.Result?=?self.Result[shuffle_sequence]
??????#?對訓(xùn)練數(shù)據(jù)集進(jìn)行遍歷,利用每組訓(xùn)練數(shù)據(jù)對參數(shù)進(jìn)行調(diào)整
??????for?(input_data,real_result)?in?zip(self.InputData,self.Result):
??????????#?計(jì)算每組input_data的梯度增量
??????????g?=?(real_result-input_data.dot(self.Theta))*input_data
??????????#?調(diào)整每組input_data的梯度增量的形狀
??????????g?=?g.reshape((len(g),1))
??????????#?更新線性回歸模型參數(shù)
??????????self.Theta?=?self.Theta?+?alpha?*?g
然而,SGD也有其缺陷,雖然相比于BGD頻繁調(diào)整了參數(shù),加快了收斂速度,但是每次都用一條數(shù)據(jù)的梯度來更新,不能代表全體數(shù)據(jù)集的梯度方向,屬實(shí)是”以偏概全“了,屬于貪心算法,理論上不能獲得最優(yōu)解,常常只能獲得次優(yōu)解?;赟GD和BGD的優(yōu)點(diǎn)不同,小批量隨機(jī)梯度下降 MBGD 算法應(yīng)運(yùn)而生!
小批量隨機(jī)梯度下降算法
這個(gè)算法的思想就是:首先打亂數(shù)據(jù)集順序,并且劃分為若干批小樣本,而后每次迭代后遍歷一個(gè)批次里的所有樣本,計(jì)算梯度增量平均值,據(jù)此來更新模型參數(shù)。在小批量樣本的規(guī)模足夠大的時(shí)候,小批量樣本的梯度向量平均值也近似等于整個(gè)訓(xùn)練數(shù)據(jù)集的平均值了,這樣就結(jié)合了BGD的”廣泛性“和SGD的”快速性“
??def?MBGD(self,alpha,batch_size):
??????"""
??????這是利用MBGD算法進(jìn)行一次迭代調(diào)整參數(shù)的函數(shù)
??????:param?alpha:?學(xué)習(xí)率
??????:param?batch_size:?小樣本規(guī)模
??????"""
??????#?首先將數(shù)據(jù)集隨機(jī)打亂,減小數(shù)據(jù)集順序?qū)?shù)調(diào)優(yōu)的影響
??????shuffle_sequence?=?self.Shuffle()
??????self.InputData?=?self.InputData[shuffle_sequence]
??????self.Result?=?self.Result[shuffle_sequence]
??????#?遍歷每個(gè)小批量樣本
??????for?start?in?np.arange(0,?len(shuffle_sequence),?batch_size):
??????????#?判斷start+batch_size是否大于數(shù)組長度,
??????????#?防止最后一組小樣本規(guī)??赡苄∮赽atch_size的情況
??????????end?=?np.min([start?+?batch_size,?len(shuffle_sequence)])
??????????#?獲取訓(xùn)練小批量樣本集及其標(biāo)簽
??????????mini_batch?=?shuffle_sequence[start:end]
??????????Mini_Train_Data?=?self.InputData[mini_batch]
??????????Mini_Train_Result?=?self.Result[mini_batch]
??????????#?定義梯度增量數(shù)組
??????????gradient_increasment?=?[]
??????????#?對小樣本訓(xùn)練集進(jìn)行遍歷,利用每個(gè)小樣本的梯度增量的平均值對模型參數(shù)進(jìn)行更新
??????????for?(data,result)?in?zip(Mini_Train_Data,Mini_Train_Result):
??????????????#?計(jì)算每組input_data的梯度增量,并放入梯度增量數(shù)組
??????????????g?=?(result?-?data.dot(self.Theta))?*?data
??????????????gradient_increasment.append(g)
??????????#?按列計(jì)算每組小樣本訓(xùn)練集的梯度增量的平均值,并改變其形狀
??????????avg_g?=?np.average(gradient_increasment,?0)
??????????avg_g?=?avg_g.reshape((len(avg_g),?1))
??????????#?更新模型參數(shù)theta
??????????self.Theta?=?self.Theta?+?alpha?*?avg_g
MBGD兼顧了BGD算法和SGD算法的優(yōu)點(diǎn),是中庸的選擇
訓(xùn)練線性回歸模型
在這個(gè)章節(jié)中,將會(huì)用sklearn內(nèi)置的波士頓房價(jià)數(shù)據(jù)集結(jié)合我們自己實(shí)現(xiàn)的線性回歸去預(yù)測波士頓郊區(qū)的房價(jià)。
波士頓房價(jià)數(shù)據(jù)集共有14個(gè)屬性,其中13個(gè)是輸入屬性,還有1個(gè)屬性是輸出屬性”房屋價(jià)格“。13個(gè)輸入屬性中的第6個(gè)屬性就是”房間數(shù)目“,結(jié)合常識,房間數(shù)目約等于面積,對房價(jià)的影響很大。為了簡化案例,我們就聚焦于”房間數(shù)目“,據(jù)此預(yù)測房屋價(jià)格。
波士頓房價(jià)數(shù)據(jù)集是很知名的數(shù)據(jù)集,如果對這個(gè)數(shù)據(jù)集的結(jié)構(gòu)不了解的同學(xué)可以去搜一下相關(guān)資料。
在本章的代碼中,會(huì)用到numpy、pandas、matplotlib以及sklearn,對這些模塊不熟悉的同學(xué),可以去看看螞蟻老師的相關(guān)課程!
Merge函數(shù):將實(shí)驗(yàn)數(shù)據(jù)轉(zhuǎn)換為結(jié)構(gòu)化數(shù)據(jù)
因?yàn)槲蚁M麑⒌玫降膶?shí)驗(yàn)結(jié)果存入到excel文件中,就先定義了Merge函數(shù),將sklearn中的數(shù)據(jù)轉(zhuǎn)換為pandas的dataframe形式,方便后續(xù)操作。
def?Merge(data,col):
????"""
????這是生成DataFrame數(shù)據(jù)的函數(shù)
????:param?data:輸入數(shù)據(jù)
????:param?col:列名稱數(shù)組
????"""
????Data?=?np.array(data).T
????return?pd.DataFrame(Data,columns=col)
數(shù)據(jù)預(yù)處理
首先導(dǎo)入數(shù)據(jù)集,并且對它進(jìn)行預(yù)處理,用load_boston()函數(shù)加載數(shù)據(jù)集,并且只選取第六個(gè)特征值”平均房間數(shù)目“,通過Merge生成dataframe,而后保存為excel文件。然后將改變一下訓(xùn)練數(shù)據(jù)集和結(jié)果(房價(jià))數(shù)據(jù)集的形狀,最后將這些數(shù)據(jù)集劃分為訓(xùn)練集和測試集。
from?sklearn.model_selection?import?train_test_split
from?sklearn.datasets?import?load_boston
#?導(dǎo)入數(shù)據(jù)以及劃分訓(xùn)練數(shù)據(jù)與測試數(shù)據(jù)
InputData,?Result?=?load_boston(return_X_y=True)
#?為了方便實(shí)驗(yàn),只取第6維特征。第6列為平均房間數(shù)目
InputData?=?np.array(InputData)[:,5]
#?保存原始數(shù)據(jù)集
Data?=?Merge([InputData,Result],['平均房間數(shù)目','房價(jià)'])
Data.to_excel('./原始數(shù)據(jù).xlsx')
#?改變數(shù)據(jù)集與真實(shí)房價(jià)數(shù)組的形狀
InputData?=?InputData.reshape((len(InputData),?1))
Result?=?np.array(Result).reshape((len(Result),?1))
#?把數(shù)據(jù)集分成訓(xùn)練數(shù)據(jù)集和測試數(shù)據(jù)集
train_data,test_data,train_result,test_result?=?\
????train_test_split(InputData,Result,test_size=0.1,random_state=50)
數(shù)據(jù)可視化
我們會(huì)用到matplotlib庫,對這些數(shù)據(jù)進(jìn)行可視化
import?matplotlib?as?mpl
import?matplotlib.pyplot?as?plt
#?解決Matplotlib中的中文亂碼問題,以便于后面實(shí)驗(yàn)結(jié)果可視化
mpl.rcParams['font.sans-serif']?=?[u'simHei']
mpl.rcParams['axes.unicode_minus']?=?False
#?利用散點(diǎn)圖可視化測試數(shù)據(jù)集,并保存可視化結(jié)果
col?=?['真實(shí)房價(jià)']
plt.scatter(test_data,test_result,alpha=0.5,c='b',s=10)
plt.grid(True)
plt.legend(labels?=?col,loc='best')
plt.xlabel("房間數(shù)")
plt.ylabel("真實(shí)房價(jià)")
plt.savefig("./測試集可視化.jpg",bbox_inches='tight')
plt.show()
plt.close()
構(gòu)建模型
首先,隨機(jī)生成模型參數(shù),而后將訓(xùn)練數(shù)據(jù)和模型參數(shù),引入我們的線性回歸類,將訓(xùn)練數(shù)據(jù)和模型參數(shù)傳入我們寫好的線性回歸對象中,每一種算法都實(shí)例化出一個(gè)對象。
from?LinearRegression.LinearRegression?import?LinearRegress
#?開始構(gòu)建線性回歸模型
col?=?np.shape(train_data)[1]+1
#?初始化線性回歸參數(shù)theta
theta?=?np.random.random((col,1))
#?BGD優(yōu)化的線性回歸模型
linearregression_BGD?=?LinearRegression(train_data,?train_result,theta)
#?SGD優(yōu)化的線性回歸模型
linearregression_SGD?=?LinearRegression(train_data,?train_result,theta)
#?MBGD優(yōu)化的線性回歸模型
linearregression_MBGD?=?LinearRegression(train_data,?train_result,theta)
#?正則方程優(yōu)化的線性回歸模型
linearregression_NormalEquation?=?LinearRegression(train_data,?train_result,theta)
之后設(shè)定迭代次數(shù)、學(xué)習(xí)率、小批量樣本個(gè)數(shù)這些超參數(shù),并且傳入相應(yīng)的對象中
#?訓(xùn)練模型
iter?=?30000?????????????#?迭代次數(shù)
alpha?=?0.001????????????#?學(xué)習(xí)率
batch_size?=?64??????????#?小樣本規(guī)模
#?BGD的訓(xùn)練損失
BGD_train_cost?=?linearregression_BGD.train_BGD(iter,alpha)
#?SGD的訓(xùn)練損失
SGD_train_cost?=?linearregression_SGD.train_SGD(iter,alpha)
#?MBGD的訓(xùn)練損失
MBGD_train_cost?=?linearregression_MBGD.train_MBGD(iter,batch_size,alpha)
#?利用正規(guī)方程獲取參數(shù)
linearregression_NormalEquation.getNormalEquation()
記錄迭代訓(xùn)練誤差結(jié)果并進(jìn)行可視化
訓(xùn)練結(jié)束后,就將訓(xùn)練的結(jié)果進(jìn)行可視化,觀察比較算法之間的差異,并且將這些信息保存為excel文件
#?三種梯度下降算法迭代訓(xùn)練誤差結(jié)果可視化,并保存可視化結(jié)果
col?=?['BGD','SGD','MBGD']
iter?=?np.arange(iter)
plt.plot(iter,?BGD_train_cost,?'r-.')
plt.plot(iter,?SGD_train_cost,?'b-')
plt.plot(iter,?MBGD_train_cost,?'k--')
plt.grid(True)
plt.xlabel("迭代次數(shù)")
plt.ylabel("平均訓(xùn)練損失")
plt.legend(labels?=?col,loc?=?'best')
plt.savefig("./三種梯度算法的平均訓(xùn)練損失.jpg",bbox_inches='tight')
plt.show()
plt.close()
#?三種梯度下降算法的訓(xùn)練損失
#?整合三種梯度下降算法的訓(xùn)練損失到DataFrame
train_cost?=?[BGD_train_cost,SGD_train_cost,MBGD_train_cost]
train_cost?=?Merge(train_cost,col)
#?保存三種梯度下降算法的訓(xùn)練損失及其統(tǒng)計(jì)信息
train_cost.to_excel("./三種梯度下降算法的訓(xùn)練訓(xùn)練損失.xlsx")
train_cost.describe().to_excel("./三種梯度下降算法的訓(xùn)練訓(xùn)練損失統(tǒng)計(jì).xlsx")
用訓(xùn)練好的模型擬合曲線
計(jì)算4種算法訓(xùn)練的線性回歸模型的擬合曲線,并可視化
#?計(jì)算4種調(diào)優(yōu)算法下的擬合曲線
x?=?np.arange(int(np.min(test_data)),?int(np.max(test_data)+1))
x?=?x.reshape((len(x),1))
#?BGD算法的擬合曲線
BGD?=?linearregression_BGD.test(x)
#?SGD算法的擬合曲線
SGD?=?linearregression_SGD.test(x)
#?MBGD算法的擬合曲線
MBGD?=?linearregression_MBGD.test(x)
#?正則方程的擬合曲線
NormalEquation?=?linearregression_NormalEquation.test(x)
#?4種模型的擬合直線可視化,并保存可視化結(jié)果
col?=?['BGD',?'SGD',?'MBGD',?'正則方程']
plt.plot(x,?BGD,'r-.')
plt.plot(x,?SGD,?'b-')
plt.plot(x,?MBGD,?'k--')
plt.plot(x,?NormalEquation,?'g:',)
plt.scatter(test_data,test_result,alpha=0.5,c='b',s=10)
plt.grid(True)
plt.xlabel("房間數(shù)")
plt.ylabel("預(yù)測值")
plt.legend(labels?=?col,loc?=?'best')
plt.savefig("./預(yù)測值比較.jpg",bbox_inches='tight')
plt.show()
plt.close()
對模型進(jìn)行驗(yàn)證
前文介紹了模型的訓(xùn)練過程,那如何評判訓(xùn)練好的模型效果呢?方法是用測試集了驗(yàn)證模型預(yù)測值與實(shí)際值的誤差去判斷效果。
在這里,將會(huì)使用4種實(shí)例化對象的test方法得到模型的預(yù)測結(jié)果,然后使用pandas將預(yù)測結(jié)果和統(tǒng)計(jì)信息保存為excel文件
#?利用測試集進(jìn)行線性回歸預(yù)測
#?BGD算法的預(yù)測結(jié)果
BGD_predict?=?linearregression_BGD.test(test_data)
#?SGD算法的預(yù)測結(jié)果
SGD_predict?=?linearregression_SGD.test(test_data)
#?MBGD算法的預(yù)測結(jié)果
MBGD_predict?=?linearregression_MBGD.test(test_data)
#?正則方程的預(yù)測結(jié)果
NormalEquation_predict?=?linearregression_NormalEquation.test(test_data)
#?保存預(yù)測數(shù)據(jù)
#?A.tolist()是將numpy.array轉(zhuǎn)化為python的list類型的函數(shù),是將A的所有元素
#?當(dāng)作一個(gè)整體作為list的一個(gè)元素,因此我們只需要A.tolist()的第一個(gè)元素
data?=?[test_data.T.tolist()[0],test_result.T.tolist()[0],BGD_predict,
????????????SGD_predict,MBGD_predict,NormalEquation_predict]
col?=?["平均房間數(shù)目","真實(shí)房價(jià)",'BGD預(yù)測結(jié)果','SGD預(yù)測結(jié)果','MBGD預(yù)測結(jié)果','正規(guī)方程預(yù)測結(jié)果']
Data?=?Merge(data,col)
Data.to_excel('./測試數(shù)據(jù)與預(yù)測結(jié)果.xlsx')
#?計(jì)算4種算法的均方誤差以及其統(tǒng)計(jì)信息
#?test_result之前的形狀為(num,1),首先計(jì)算其轉(zhuǎn)置后
#?獲得其第一個(gè)元素即可
test_result?=?test_result.T[0]
#?BGD算法的均方誤差
BGD_error?=?((BGD_predict-test_result)**2)
#?SGD算法的均方誤差
SGD_error?=?((SGD_predict-test_result)**2)
#?MBGD算法的均方誤差
MBGD_error?=?((MBGD_predict-test_result)**2)
#?正則方程的均方誤差
NormalEquation_error?=?((NormalEquation_predict-test_result)**2)
#?整合四種算法的均方誤差到DataFrame
error?=?[BGD_error,SGD_error,MBGD_error,NormalEquation_error]
col?=?['BGD',?'SGD',?'MBGD',?'正則方程']
error?=?Merge(error,col)
#?保存四種均方誤差及其統(tǒng)計(jì)信息
error.to_excel("./四種算法的均方預(yù)測誤差原始數(shù)據(jù).xlsx")
error.describe().to_excel("./四種算法的均方預(yù)測誤差統(tǒng)計(jì).xlsx")
分析本次實(shí)驗(yàn)的結(jié)果
原始數(shù)據(jù)集
我們來看看原始數(shù)據(jù)集長什么樣子
可視化測試集
看表格不直觀,我們來看看其中測試集的可視化結(jié)果吧!
可以明顯看到平均房間數(shù)和房價(jià)之間存在正相關(guān)的關(guān)系
對比3種梯度下降算法在訓(xùn)練過程中的平均損失

對3種梯度下降算法的均方誤差進(jìn)行統(tǒng)計(jì)

顯然可以發(fā)現(xiàn),BGD算法的斂速度最慢,且在訓(xùn)練結(jié)束后,BGD算法的訓(xùn)練均方誤差最終在48.533,SGD的收斂速度最快(最快到達(dá)拐點(diǎn)),但由于貪心算法的特性(每條數(shù)據(jù)的梯度增量都作用于模型參數(shù))它會(huì)在最小值附近震蕩,而MBGD收斂速度居中,且沒有震蕩,誤差基本上都是一路下跌
這些結(jié)果和我們的前期判斷一致
驗(yàn)證模型
接下來我們看看4中算法訓(xùn)練出來的模型應(yīng)用在測試集的數(shù)據(jù)上是什么效果吧!
預(yù)測結(jié)果可視化

在上圖中,圓點(diǎn)代表真實(shí)數(shù)據(jù),從上到下4條線形不一的直線分別代表BGD、SGD、MBGD以及正則方程算法訓(xùn)練出來的線性回歸模型的預(yù)測擬合直線。從圖中來看,除了少量離群點(diǎn),絕大多數(shù)點(diǎn)都均勻分布在4條擬合直線的左右,大致可以判定,效果都還不錯(cuò)。
預(yù)測結(jié)果量化分析
為了更好地對比4中算法的優(yōu)缺點(diǎn),下圖給出測試集的預(yù)測房價(jià)與真實(shí)房價(jià)之間的均方誤差統(tǒng)計(jì)信息
可以看出來,MBGD的均方誤差在測試集上是最小的,正則方程緊隨其后,BGD第三,SGD誤差最大 再次驗(yàn)證了我們之前的判斷。
總結(jié)
線性回歸是機(jī)器學(xué)習(xí)回歸問題中最簡單的模型,其模型參數(shù)通常用梯度下降和正則方程求解。在三種梯度下降算法中,BGD能近似獲得全局最優(yōu)解,穩(wěn)定性強(qiáng),但是收斂速度慢;SGD則是收斂速度快,但無法獲得全局最優(yōu)解,且穩(wěn)定性欠佳;MBGD則是中庸方案。至于正則方程,則適用于大規(guī)模的數(shù)據(jù)集,但其時(shí)間復(fù)雜度較大(涉及大量矩陣相乘)。
推薦我的機(jī)器學(xué)習(xí)實(shí)戰(zhàn)課程
一個(gè)機(jī)器學(xué)習(xí)系列,從模型訓(xùn)練到在線預(yù)估,到Linux線上部署,包含大量的工程細(xì)節(jié),推薦給大家我自己的機(jī)器學(xué)習(xí)實(shí)戰(zhàn)課程:
掃碼購買
《機(jī)器學(xué)習(xí) Sklearn二手車價(jià)格預(yù)估》
課程介紹

購買后加老師微信:ant_learn_python
會(huì)提供微信答疑,拉VIP會(huì)員交流群
