機器學習之線性迴歸 Linear Regression(二)Python實現

一元線性迴歸

        假設你想計算匹薩的價格。 雖然看看菜單就知道了,不過也可以用機器學習方法建一個線性迴歸模型,通過分析匹薩直徑與價格的線性關係,來預測任意直徑匹薩的價格。假設我們查到了部分匹薩的直徑與價格的數據,這就構成了訓練數據,如下表所示:


import matplotlib.pyplot as plt
def runplt():
	plt.figure()
	plt.title("Cost and diameter")
	plt.xlabel("Diameter/inch")
	plt.ylabel("Cost/dollar")
	plt.axis([0,25,0,30])
	plt.grid(True)
	return plt
plt = runplt()
X = [[6],[8],[10],[14],[18]]
y = [[7],[9],[13],[17.5],[18]]
plt.plot(X,y,'k.')
plt.show()

        上圖中,x軸表示匹薩直徑,y軸表示匹薩價格。 能夠看出,匹薩價格與其直徑正相關,這與我們的日常經驗也比較吻合,自然是越大越貴。

        下面就用 scikit-learn 來建模

#創建並擬合模型
from sklearn.linear_model import LinearRegression
model = LinearRegression()
model.fit(X,y)
print("Predict 12 inch cost:$%.2f" % model.predict([[12]]))

>> Predict 12 inch cost:$13.68
        一元線性迴歸假設輸入變量和輸出變量之間存在線性關係,這個線性模型所構成的空間是一個超平面(hyperplane)。 超平面是 n 維歐氏空間中維度減一的線性子空間,如平面中的直線、空間中的平面,總比包含它的空間少一維。 在一元線性迴歸中,一個維度是輸入變量,另一個維度是輸出變量,總共兩維。 因此,其超平面只有一維,就是一條線。

        上述代碼中 sklearn.linear_model.LinearRegression 類是一個估計器(estimator)。 估計器依據觀測值來預測結果。 在 scikit-learn 裏面,所有的估計器都帶有 fit() 和 predict() 方法。 fit() 用來分析模型參數,predict() 是通過 fit() 算出的模型參數構成的模型,對解釋變量進行預測獲得的值。 因爲所有的估計器都有這兩種方法,所有 scikit-learn 很容易實驗不同的模型。 LinearRegression 類的 fit() 方法學習下面的一元線性迴歸模型:

        y 表示輸出變量的預測值,本例指匹薩價格預測值, x是輸入變量,本例指匹薩直徑。 截距和相關係數是線性迴歸模型最關心的事情。下圖中的直線就是匹薩直徑與價格的線性關係。 用這個模型,可以計算不同直徑的價格。

plt = runplt()
X = [[6],[8],[10],[14],[18]]
y = [[7],[9],[13],[17.5],[18]]
model = LinearRegression()
model.fit(X,y)
X2 = [[0], [10], [14], [25]]
y2 = model.predict(X2)
plt.plot(X, y, 'k.')
plt.plot(X2, y2, 'g-')
plt.show()


帶成本函數的模型擬合評估

        由若干參數生成的迴歸直線。 如何判斷哪一條直線纔是最佳擬合呢?

        一元線性迴歸擬合模型的參數估計常用方法是普通最小二乘法(ordinary least squares )或線性最小二乘法(linear least squares)。 首先,我們定義出擬合成本函數,然後對參數進行數理統計。

        成本函數(cost function)也叫損失函數(loss function),用來定義模型與觀測值的誤差。 模型預測的價格與訓練集數據的差異稱爲殘差(residuals)或訓練誤差(training errors)。 後面會用模型計算測試集,那時模型預測的價格與測試集數據的差異稱爲預測誤差(prediction errors)或訓練誤差(test errors)。模型的殘差是訓練樣本點與線性迴歸模型的縱向距離,如下圖所示:

model = LinearRegression()
model.fit(X,y)
X2 = [[0], [10], [14], [25]]
y2 = model.predict(X2)
plt.plot(X, y, 'k.')
plt.plot(X2, y2, 'g-')
# 殘差預測值
yr = model.predict(X)
for idx, x in enumerate(X):
	plt.plot([x,x], [y[idx], yr[idx]], 'r-')
plt.show()

        我們可以通過殘差之和最小化實現最佳擬合,也就是說模型預測的值與訓練集的數據最接近就是最佳擬合。對模型的擬合度進行評估的函數稱爲殘差平方和(residual sum of squares)成本函數。 就是讓所有訓練數據與模型的殘差的平方之和最小化,如下所示:

殘差平方和計算如下:
import numpy as np
print("殘差平方和:%.2f" %np.mean((model.predict(X) - y)**2))

解一元線性迴歸的最小二乘法

        通過成本函數最小化獲得參數,先求相關係數b。 按照頻率論的觀點,首先需要計算 x 的方差和 x 與 y 的協方差。
        方差是用來衡量樣本分散程度的。 如果樣本全部相等,那麼方差爲 0。 方差越小,表示樣本越集中,反正則樣本越分散。 方差計算公式如下:
        協方差表示兩個變量的總體的變化趨勢。 如果兩個變量的變化趨勢一致,也就是說如果其中一個大於自身的期望值,另外一個也大於自身的期望值,那麼兩個變量之間的協方差就是正值。 如果兩個變量的變化趨勢相反,即其中一個大於自身的期望值,另外一個卻小於自身的期望值,那麼兩個變量之間的協方差就是負值。 如果兩個變量不相關,則協方差爲 0,變量線性無關不表示一定沒有其他相關性。 協方差公式如下:
        有了方差和協方差,就可以計算相關係統b了


        將前面的數據帶入公式就可以求出阿爾法了:
       α = 12.9 − 0.9762931034482758 × 11.2 = 1.9655172413793114
        這樣就通過最小化成本函數求出模型參數了。 把匹薩直徑帶入方程就可以求出對應的價格了,如 11 英寸直徑價格 $12.70,18 英寸直徑價格 $19.54

        Numpy 裏面有 var 方法可以直接計算方差,ddof 參數是貝塞爾 (無偏估計) 校正係數(Bessel'scorrection),設置爲 1,可得樣本方差無偏估計量
print(np.var(X, ddof=1))

模型評估

        前面用學習算法對訓練集進行估計,得出了模型的參數。 如何評價模型在現實中的表現呢?現在假設有另一組數據,作爲測試集進行評估
        有些度量方法可以用來評估預測效果,我們用 R 方(r-squared)評估匹薩價格預測的效果。 R 方也叫確定係數(coefficient of determination),表示模型對現實數據擬合的程度。 計算 R 方的方法有幾種。 一元線性迴歸中 R 方等於皮爾遜積矩相關係數(Pearson product moment correlation coefficient 或 Pearson's r)的平方。這種方法計算的 R 方一定介於 0~1 之間的正數。 其他計算方法,包括 scikit-learn 中的方法,不是用皮爾遜積矩相關係數的平方計算的,因此當模型擬合效果很差的時候 R 方會是負值。 下面用 scikitlearn 方法來計算 R 方



        R 方是 0.6620 說明測試集裏面過半數的價格都可以通過模型解釋。 現在,用 scikit-learn 來驗證一下。 LinearRegression 的 score 方法可以計算 R 方:
model = LinearRegression()
model.fit(X,y)
X_test = [[8], [9], [11], [16], [12]]
y_test = [[11], [8.5], [15], [18], [11]]
SStot = np.sum((y_test-np.mean(y_test))**2)
SSres = np.sum((y_test-model.predict(X_test))**2)
print("SStot=",SStot)
print("SSres=",SSres)
print("R2=", 1-SSres/SStot)
print("model score=", model.score(X_test, y_test))

>> SStot=56.8
SSres=19.1980
R2=0.66200
model score=0.66200

多元線性迴歸

        可以看出匹薩價格預測的模型 R 方值並不顯著。 如何改進呢?
        匹薩的價格其實還會受到其他因素的影響。 比如,匹薩的價格還與上面的輔料有關。 讓我們再爲模型增加一個輸入變量。 用一元線性迴歸已經無法解決了,我們可以用更具一般性的模型來表示,即多元線性迴歸
        寫成矩陣形式如下:

        增加輔料的匹薩價格預測模型訓練集如下表所示:


        同時要升級測試集數據:


        學習算法評估三個參數的值:兩個相關因子和一個截距。可以通過矩陣運算來實現,因爲矩陣沒有除法運算,所以用矩陣的轉置運算和逆運算來實現:


from numpy.linalg import inv
from numpy import dot, transpose
X = [[1,6,2],[1,8,1],[1,10,0],[1,14,2],[1,18,0]]
y = [[7],[9],[13],[17.5],[18]]
print(dot(inv(dot(transpose(X),X)),dot(transpose(X),y)))

>> [[1.1875][1.01041667][0.39583333]]

        利用矩陣的方法實現,可以得到同樣的結果:

from numpy import *
xMat = mat(X)
yMat = mat(y)
xTx = xMat.T*xMat
print(xTx)
if linalg.det(xTx)==0.0:
	print("The matrix is singular")
else:
	ws = xTx.I * (xMat.T*yMat)
	print(ws)

        更新價格預測模型

from numpy.linalg import inv
from numpy import dot, transpose
import numpy as np
from sklearn.linear_model import LinearRegression
X = [[6,2],[8,1],[10,0],[14,2],[18,0]]
y = [[7],[9],[13],[17.5],[18]]
model = LinearRegression()
model.fit(X,y)

X_test = [[8,2], [9,0], [11,2], [16,2], [12,0]]
y_test = [[11], [8.5], [15], [18], [11]]
predictions = model.predict(X_test)
for i, prediction in enumerate(predictions):
	print("Predicted: %s, Target: %s" %(prediction, y_test[i]))
print("R2=%.2f"%model.score(X_test, y_test))

>> Predicted: [10.06250019], Target: [11]
Predicted: [10.28125019], Target: [8.5]
Predicted: [13.09375019], Target: [15]
Predicted: [18.14583353], Target: [18]
Predicted: [13.31250019], Target: [11]
R2=0.77
        增加輸入變量讓模型擬合效果更好了。 爲什麼只用一個測試集評估一個模型的效果是不準確的,如何通過將測試集數據分塊的方法來測試,讓模型的測試效果更可靠。 不過現在至少可以認爲匹薩價格預測問題,多元迴歸確實比一元迴歸效果更好。 假如輸入變量和輸出變量的關係不是線性的呢?下面來研究一個特別的多元線性迴歸的情況,可以用來構建非線性關係模型。

多項式迴歸

        下面用多項式迴歸,一種特殊的多元線性迴歸方法,增加了指數項( 次數大於 1)。 現實世界中的曲線關係全都是通過增加多項式實現的,其實現方式和多元線性迴歸類似。 本例還用一個輸入變量,匹薩直徑。 用下面的數據對兩種模型做個比較:

        二次迴歸(Quadratic Regression),即迴歸方程有個二次項,公式如下:

        只用一個輸入變量,但是模型有三項,通過第三項(二次項)來實現曲線關係。 PolynomialFeatures 轉換器可以用來解決這個問題。 代碼如下:
model = LinearRegression()
model.fit(X_train,y_train)
xx = np.linspace(0,26,100)
yy = model.predict(xx.reshape(-1,1))
plt = runplt()
plt.plot(X_train, y_train, 'k.')
plt.plot(xx,yy)

quadratic_featurizer = PolynomialFeatures(degree=2)
X_train_quadratic = quadratic_featurizer.fit_transform(X_train)
X_test_quadratic = quadratic_featurizer.transform(X_test)
model_quadratic = LinearRegression()
model_quadratic.fit(X_train_quadratic, y_train)
xx_quadratic = quadratic_featurizer.transform(xx.reshape(-1,1))
plt.plot(xx, model_quadratic.predict(xx_quadratic), 'r-')

cubic_featurizer = PolynomialFeatures(degree=3)
X_train_cubic = cubic_featurizer.fit_transform(X_train)
X_test_cubic = cubic_featurizer.transform(X_test)
model_cubic = LinearRegression()
model_cubic.fit(X_train_cubic, y_train)
xx_cubic = cubic_featurizer.transform(xx.reshape(-1,1))
plt.plot(xx, model_cubic.predict(xx_cubic))

plt.show()
print("一元線性迴歸R2=", model.score(X_test, y_test))
print("二次迴歸    R2=", model_quadratic.score(X_test_quadratic, y_test))
print("三次迴歸    R2=", model_cubic.score(X_test_cubic, y_test))

>> 一元線性迴歸R2=0.8097
二次迴歸R2=0.8675
三次迴歸R2=0.8357

        可以看出,三次擬合的 R 方值更低,雖然其圖形經過了更多的點, 可以認爲這是擬合過度(overfitting)的情況。 這種模型並沒有從輸入和輸出中推導出一般的規律,而是記憶訓練集的結果,這樣在測試集的測試效果就不好了。

正則化

        正則化(Regularization)是用來防止擬合過度的一堆方法。 正則化向模型中增加信息,經常是一種對抗複雜性的手段。 與奧卡姆剃刀原理(Occam's razor)所說的具有最少假設的論點是最好的觀點類似。 正則化就是用最簡單的模型解釋數據。
        scikit-learn 提供了一些方法來使線性迴歸模型正則化。 其中之一是嶺迴歸 (Ridge Regression,RR,也叫 Tikhonov regularization),通過放棄最小二乘法的無偏性,以損失部分信息、降低精度爲代價獲得迴歸係數更爲符合實際、更可靠的迴歸方法。 嶺迴歸增加 L2 範數項(相關係數向量平方和的平方根)來調整成本函數(殘差平方和):

        scikit-learn 也提供了最小收縮和選擇算子 (Least absolute shrinkage and selection operator,LASSO),增加 L1 範數項來調整成本函數(殘差平方和):


        LASSO 方法會產生稀疏參數,大多數相關係數會變成 0,模型只會保留一小部分特徵。 而嶺迴歸還是會保留大多數儘可能小的相關係數。 當兩個變量相關時,LASSO 方法會讓其中一個變量的相關係數會變成 0,而嶺迴歸是將兩個係數同時縮小。
        scikit-learn 還提供了彈性網(elastic net)正則化方法,通過線性組合 L1 和 L2 兼具 LASSO 和嶺迴歸的內容。 可以認爲這兩種方法是彈性網正則化的特例。

        下面對三次擬合的數據加入Lasso和嶺迴歸正則化項

# LASSO迴歸
from sklearn.linear_model import Lasso
lasso = Lasso(alpha=0.05, normalize=True)
lasso.fit(X_train_cubic, y_train)
print("Lasso R2=", lasso.score(X_test_cubic, y_test))
#嶺迴歸
from sklearn.linear_model import Ridge
ridge = Ridge(alpha=0.05, normalize=True)
ridge.fit(X_train_cubic, y_train)
print("Ridge R2=", ridge.score(X_test_cubic, y_test))

>>三次迴歸R2=0.8357
Lasso R2=0.8482
Ridge R2=0.8358

梯度下降法擬合模型

        前面的內容全都是通過最小化成本函數來計算參數的:


        這裏 X 是輸入變量矩陣,當變量很多(上萬個)的時候, 右邊第一項計算量會非常大。 另外,如果右邊第一項行列式爲 0,即奇異矩陣,那麼就無法求逆矩陣了。 這裏我們介紹另一種參數估計的方法,梯度下降法(gradient descent)。 擬合的目標並沒有變,我們還是用成本函數最小化來進行參數估計。

        梯度下降法被比喻成一種方法,一個人蒙着眼睛去找從山坡到溪谷最深處的路。 他看不到地形圖,所以只能沿着最陡峭的方向一步一步往前走。 每一步的大小與地勢陡峭的程度成正比。 如果地勢很陡峭,他就走一大步,因爲他相信他仍在高處,還沒有錯過溪谷的最低點。 如果地勢比較平坦,他就走一小步。 這時如果再走大步,可能會與最低點失之交臂。 如果真那樣,他就需要改變方向,重新朝着溪谷的最低點前進。 他就這樣一步一步的走啊走,直到有一個點走不動了,因爲路是平的了,於是他卸下眼罩,已經到了谷底深處。
        通常,梯度下降算法是用來評估函數的局部最小值的。 我們前面用的成本函數如下:

        可以用梯度下降法來找出成本函數最小的模型參數值。 梯度下降法會在每一步走完後,計算對應位置的導數,然後沿着梯度(變化最快的方向)相反的方向前進, 總是垂直於等高線。需要注意的是,梯度下降法來找出成本函數的局部最小值。 一個三維凸(convex)函數所有點構成的圖像一個碗,碗底就是唯一局部最小值。 非凸函數可能有若干個局部最小值,也就是說整個圖形看着像是有多個波峯和波谷。 梯度下降法只能保證找到的是局部最小值,並非全局最小值。 殘差平方和構成的成本函數是凸函數,所以梯度下降法可以找到全局最小值。

       梯度下降法的一個重要超參數是步長(learning rate),用來控制矇眼人步子的大小,就是下降幅度。 如果步長足夠小,那麼成本函數每次迭代都會縮小,直到梯度下降法找到了最優參數爲止。 但是,步長縮小的過程中,計算的時間就會不斷增加。 如果步長太大,這個人可能會重複越過谷底,也就是梯度下降法可能在最優值附近搖擺不定。

       如果按照每次迭代後用於更新模型參數的訓練樣本數量劃分,有兩種梯度下降法。批量梯度下降(Batch gradient descent)每次迭代都用所有訓練樣本。 隨機梯度下降(Stochastic gradientdescent,SGD)每次迭代都用一個訓練樣本,這個訓練樣本是隨機選擇的。 當訓練樣本較多的時候,隨機梯度下降法比批量梯度下降法更快找到最優參數。 批量梯度下降法一個訓練集只能產生一個結果。 而 SGD 每次運行都會產生不同的結果。 SGD 也可能找不到最小值,因爲升級權重的時候只用一個訓練樣本。 它的近似值通常足夠接近最小值,尤其是處理殘差平方和這類凸函數的時候。

        下面用 scikit-learn 的 SGDRegressor 類來計算模型參數。 它可以通過優化不同的成本函數來擬合線性模型,默認成本函數爲殘差平方和。 本例中,我們用波士頓住房數據的 13 個解釋變量來預測房屋價格:

import numpy as np
from sklearn.datasets import load_boston
from sklearn.linear_model import SGDRegressor
from sklearn.preprocessing import StandardScaler
from sklearn.cross_validation import cross_val_score
from sklearn.cross_validation import train_test_split
data = load_boston()
X_train, X_test, y_train, y_test = train_test_split(data.data, data.target)
X_scaler = StandardScaler()
y_scaler = StandardScaler()
X_train = X_scaler.fit_transform(X_train)
y_train = y_scaler.fit_transform(y_train.reshape(-1,1))
X_test = X_scaler.transform(X_test)
y_test = y_scaler.transform(y_test.reshape(-1,1))

model = SGDRegressor(loss='squared_loss')
scores = cross_val_score(model, X_train, y_train, cv=5)
print("交叉驗證R方值:", scores)
print("交叉驗證R方均值:", np.mean(scores))
model.fit(X_train,y_train)
print("測試集R方值:", model.score(X_test, y_test))

總結

        本文介紹了三類線性迴歸模型。 首先,通過匹薩價格預測的例子介紹了一元線性迴歸,一個輸入變量和一個輸出變量的線性擬合。 然後,討論了多元線性迴歸,具有更一般形式的若干輸入變量和一個輸出變量的問題。 最後,討論了多項式迴歸,一種特殊的多元線性模型,體現了輸入變量和輸出變量的非線性特徵。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章