跟我一起學scikit-learn16:線性回歸算法

線性回歸算法是使用線性方程對數據集擬合得算法,是一個非常常見的回歸算法。本章首先從最簡單的單變量線性回歸算法開始介紹,然后介紹了多變量線性回歸算法,其中成本函數以及梯度下降算法的推導過程會用到部分線性代數和偏導數;接著重點介紹了梯度下降算法的求解步驟以及性能優化方面的內容;最后通過一個房價預測模型,介紹了線性回歸算法性能優化的一些常用步驟和方法。

1.單變量線性回歸算法

我們先考慮最簡單的單變量線性回歸算法,即只有一個輸入特征。

1.預測函數

針對數據集x和y,預測函數會根據輸入特征x來計算輸出值h(x)。其輸入和輸出的函數關系如下:
{h}_{\theta}(x)={\theta}_{0}+{\theta}_{1}x
這個方程表達的是一條直線。我們的任務是構造一個{h}_{\theta}函數,來映射數據集中的輸入特征x和輸出值y,使得預測函數{h}_{\theta}計算出來的值與真實值y的整體誤差最小。構造{h}_{\theta}函數的關鍵就是找到合適的{\theta}_{0}{\theta}_{1}的值,{\theta}_{0}{\theta}_{1}成為模型參數。

假設我們有如下的數據集:


NeatReader-1559784236614.png

假設模型參數{\theta}_{0}=1{\theta}_{1}=3,則預測函數為{h}_{\theta}(x)=1+3x。針對數據集中的第一個樣本,輸入為1,根據模型函數預測出來的值是4,與輸出值y是吻合的。針對第二個樣本,輸入為2,根據模型函數預測出來的值是7,與實際輸出值y相差1。模型的求解過程就是找出一組最合適的模型參數{\theta}_{0}{\theta}_{1},以便能最好地擬合數據集。

怎樣來判斷最好地擬合了數據集呢?沒錯,就是使用成本函數(也叫損失函數)。當擬合成本最小時,即找到了最好的擬合參數。

2.成本函數

單變量線性回歸算法的成本函數是:
J(\theta)=J({\theta}_{0},{\theta}_{1})=\frac{1}{2m}\sum_{i=1}^m(h({x}^{(i)})-{y}^{(i)})^2
其中,h({x}^{(i)})-{y}^{(i)}是預測值和真實值之間的誤差,故成本就是預測值和真實值之間誤差平方的平均值,之所以乘以1/2是為了方便計算。這個函數也稱為均方差公式。有了成本函數,就可以精確地測量模型對訓練樣本擬合的好壞程度。

3.梯度下降算法

有了預測函數,也可以精確地測量預測函數對訓練樣本的擬合情況。但怎么求解模型參數{\theta}_{0}{\theta}_{1}的值呢?這時梯度下降算法就排上了用場。

我們的任務是找到合適的{\theta}_{0}{\theta}_{1},使得成本函數J({\theta}_{0},{\theta}_{1})最小。為了便于理解,我們切換到三維空間來描述這個任務。在一個三維空間里,以{\theta}_{0}作為x軸,以{\theta}_{1}作為y軸,以成本函數J({\theta}_{0},{\theta}_{1})作為z軸,那么我們的任務就是要找出當z軸上的值最小的時候所對應的x軸上的值和y軸上的值。

梯度下降算法的原理是:先隨機選擇一組{\theta}_{0}{\theta}_{1},同時選擇一個參數\alpha作為移動的步長。然后,讓x軸上的{\theta}_{0}和y軸上的{\theta}_{1}分別向特定的方向移動一小步,這個步長的大小就由參數\alpha決定。經過多次迭代之后,x軸和y軸上的值決定的點就慢慢靠近z軸上的最小值處,如圖所示。

figure5_1.png

這是個等高線圖,就是說在我們描述的三維空間里,你的視角在正上方,看到一圈一圈z軸值相同的點構成的線。在上圖中,隨機選擇的點在處,經過多次迭代后,慢慢地靠近圓心處,即z軸上最小值附近。

問題來了,{X}_{0}(由[{\theta}_{0},{\theta}_{1}]描述)怎么知道往哪個方向移動,才能靠近z軸上最小值附近?答案是往成本函數逐漸變小的方向移動。怎么表達成本函數逐漸變小的方向呢?答案是偏導數。

可以簡單地把偏導數理解為斜率。我們要讓{\theta}_{j}不停地迭代,由當前{\theta}_{j}的值,根據J(\theta)的偏導數函數,算出J(\theta){\theta}_{j}上的斜率,然后在乘以學習率\alpha,就可以讓{\theta}_{j}往前J(\theta)變小的方向邁一小步。

用數學來描述上述過程,梯度下降的公式為:
\theta_{j}=\theta_{j}-\alpha\frac{\partial}{\partial\theta_{j}}J(\theta)
公式中,下標j就是參數的序號,針對單變量線性回歸,即0和1。\alpha稱為學習率,它決定每次要移動的幅度大小,它會乘以成本函數對參數\theta_{j}的偏導數,以這個結果作為參數移動的幅度。如果幅度太小,就意味著要計算很多次才能到達目的地;如果幅度太大,可能會直接跨過目的地,從而無法收斂。

把成本函數J(\theta)的定義代入上面的公式中,不難推導出梯度下降算法公式:
{\theta}_{0}={\theta}_{0}-\frac{\alpha}{m}\sum_{i=1}^{m}(h(x^{(i)})-y^{(i)})
{\theta}_{1}={\theta}_{1}-\frac{\alpha}{m}\sum_{i=1}^{m}((h(x^{(i)})-y^{(i)})x_{i})
公式中,\alpha是學習率;m是訓練樣本的個數;h(x^{(i)})-y^{(i)}是模型預測值和真實值的誤差。需要注意的是,針對{\theta}_{0}{\theta}_{1}分別求出了其迭代公式,在{\theta}_{1}的迭代公式里,累加器中還需要乘以x_{i}。對公式推導感興趣的讀者,可以參考本章擴展部分的內容。

2.多變量線性回歸算法

工程應用中往往不止一個輸入特征。熟悉了單變量線性回歸算法后,我們來探討一下多變量線性回歸算法。

1.預測函數

上文介紹的線性回歸模型里只有一個輸入特征,我們推廣到更一般的情況,即多個輸入特征。此時輸出y的值由n個輸入特征x1,x2,x3,...,xn決定。那么預測函數模型可以改寫如下:
{h}_{\theta}(x)={\theta}_{0}+{\theta}_{1}{x}_{1}+{\theta}_{2}{x}_{2}+...+{\theta}_{n}{x}_{n}
假設x0=1,那么上面的公式可以重寫為:
{h}_{\theta}(x)=\sum_{j=0}^{n}{\theta}_{j}{x}_{j}
其中,{\theta}_{0}{\theta}_{1},...,{\theta}_{n}統稱為\theta,是預測函數的參數。即一組\theta值就決定了一個預測函數,記為{h}_{\theta}(x),為了簡便起見,在不引起誤解的情況下可以簡寫為h(x)。理論上,預測函數有無窮多個,我們求解的目標就是找出一個最優的\theta值。

思考:當有n個變量x_{1}x_{2},...,x_{n}決定y值的時候,訓練數據集應該長什么樣呢?

(1)向量形式的預測函數
根據向量乘法運算法則,成本函數可重寫為:
h_{\theta}(x)=\begin{bmatrix}\theta_{0},\theta_{1},\cdots,\theta_{n}\end{bmatrix}\begin{bmatrix}x_{0}\\ x_{1}\\ \vdots\\x_{n}\\\end{bmatrix}=\theta^{T}x
此處,依然假設{x}_{0}=1x_{0}稱為模型偏置(bias)。

為什么要寫成向量形式的預測函數呢?一是因為簡潔,而是因為在實現算法時,要用到數值計算里的矩陣運算來提高效率,比如Numpy庫里的矩陣運算。

(2)向量形式的訓練樣本
假設,輸入特征的個數是n,即x_{1}x_{2},...,x_{n},我們總共有m個訓練樣本,為了書寫方便,假設{x}_{0}=1。這樣訓練樣本可以寫成矩陣的形式,即矩陣里每一行都是一個訓練樣本,總共有m行,每行有n+1列。

思考:為什么不是n列而是n+1列?答案是:把模型偏置{x}_{0}也加入了訓練樣本里。最后把訓練樣本寫成一個矩陣,如下:

X=\begin{bmatrix} x_{0}^{(1)} & x_{1}^{(1)} & x_{2}^{(1)} & \cdots & x_{n}^{(1)}\\ x_{0}^{(2)} & x_{1}^{(2)} & x_{2}^{(2)} & \cdots & x_{n}^{(2)}\\ \vdots & \vdots & \vdots & \ddots & \vdots\\ x_{0}^{(m)} & x_{1}^{(m)} & x_{2}^{(m)} & \cdots & x_{n}^{(m)}\\ \end{bmatrix},\theta=\begin{bmatrix}\theta_{0}\\ \theta_{1}\\\theta_{2}\\ \vdots \\\theta_{n}\\ \end{bmatrix}

理解訓練樣本矩陣的關鍵在于理解這些上標和下標的含義。其中,帶括號的上標表示樣本序號,從1到m;下標表示特征序號,從0到n,其中x_{0}為常數1。比如,x_{j}^{(i)}表示第i個訓練樣本的第j個特征的值。而x^{(i)}只有上標,則表示第i個訓練樣本所構成的列向量。

熟悉矩陣乘法的話不難得出結論,如果要一次性計算出所有訓練樣本的預測值h_{\theta}(X),可以使用下面的矩陣運算公式:
h_{\theta}(X)=X\theta
從這個公式也可以看到矩陣形式表達的優勢。實際上,在scikit-learn里,訓練樣本就是用這種方式表達的,即使用{m}\times{n}維的矩陣來表達訓練樣本,可以回顧一下scikit-learn里模型的fit()函數的參數。

2.成本函數

多變量線性回歸算法的成本函數:
J(\theta)=\frac{1}{2m}\sum_{i=1}^{m}(h(x^{(i)})-y^{(i)})^2
其中,模型參數\theta為n+1維的向量,h(x^{(i)})-y^{(i)}是預測值和實際值的差。這個形式和單變量線性回歸算法的類似。

成本函數有其對應的矩陣形式:
J(\theta)=\frac{1}{2m}(X\theta-\vec{y})^T(X\theta-\vec{y})
其中,X為{m}\times{(n+1)}維的訓練樣本矩陣;上標T表示轉置矩陣;\vec{y}表示由所有的訓練樣本的輸出y^{(i)}構成的向量。這個公式的優勢是:沒有累加器,不需要循環,直接使用矩陣運算,就可以一次性計算出對特定的參數\theta下模型的擬合成本。

思考:矩陣運算真的不需要循環嗎?
這里所說的不需要循環,是指不需要在算法實現層使用循環,但在數值運算庫如Numpy里,實現的矩陣運算還是要用到循環。雖然都是循環,但是有差別,一是在數值運算庫里實現的循環效率更高,而是矩陣運算的循環可以使用分布式來實現。一個大矩陣運算可以拆成多個子矩陣運算,然后在不同的計算機上執行運算,最終再把運算結果匯合起來。這種分布式計算對大型矩陣運算來說是一種必要的手段。

3.梯度下降算法

根據單變量線性回歸算法的介紹,梯度下降的公式為:
\theta_{j}=\theta_{j}-\alpha\frac{\partial}{\partial\theta_{j}}J(\theta)
公式中,下標j是參數的序號,其值從0到n;\alpha為學習率。把成本函數代入上式,利用偏導數計算法則,不難推導出梯度下降算法的參數迭代公式:
{\theta}_{j}={\theta}_{j}-\frac{\alpha}{m}\sum_{i=1}^{m}((h(x^{(i)})-y^{(i)})x_{j}^{(i)})

我們可以對比一下單變量線性回歸函數的參數迭代公式。實際上和多變量線性回歸函數的參數迭代公式是一模一樣的。惟一的區別就是因為x_{0}為常數1,在單變量線性回歸算法的參數迭代公式中省去了。

這個公式怎么樣用編程語言來實現呢?在編寫機器學習算法的時候,一般步驟如下。

(1)確定學習率:\alpha太大可能會使成本函數無法收斂,太小則計算太多,機器學習算法效率就比較低。

(2)確定參數起始點:比如讓所有的參數都以1作為起始點,即\theta_{0}=1\theta_{1}=1,...,\theta_{n}=1。這樣就得到了我們的預測函數h_{\theta}(x)=\sum_{i=1}^{m}x^{(i)}。根據預測值和成本函數,就可以算出在參數起始位置的成本。需要注意的是,參數起始點可以根據實際情況靈活選擇,以便讓機器學習算法的性能更高,比如選擇比較靠近極點的位置。

(3)計算參數的下一組值:根據梯度下降參數迭代公式,分別同時計算出新的\theta_{j}的值。然后用新的\theta值得到新的預測函數h_{\theta}(x)。再根據新的預測函數,代入成本函數就可以算出新的成本。

(4)確定成本函數是否收斂:拿新的成本和舊的成本進行比較,看成本是不是變得越來越小。如果兩次成本之間的差異小于誤差范圍,即說明已經非常靠近最小成本了,就可以近似地認為我們找到了最小成本。如果兩次成本之間的差異在誤差范圍之外,重復步驟(3)繼續計算下一組參數\theta,直到找到最優解。

3.模型優化

線性回歸模型常用的優化方法,包括增加多項式特征以及數據歸一化處理等。

1.多項式與線性回歸

當線性回歸模型太簡單導致欠擬合時,我們可以增加特征多項式來讓線性回歸模型更好地擬合數據。比如有兩個特征x_{1}x_{2},可以增加兩個特征的乘積{x_{1}}\times{x_{2}}作為新特征x_{3}。同理,我們也可以增加{x}_{1}^{2}{x}_{2}^{2}分別作為新特征x_{4}x_{5}

在scikit-learn里,線性回歸是由類sklearn.learn_model.LinearRegression實現的,多項式由類sklearn.preprocessing.PolynomialFeatures實現。那么要怎樣添加多項式特征呢?我們需要用一個管道把兩個類串起來,即用sklearn.pipeline.Pipeline把這兩個模型串起來。

比如下面的函數就可以創建一個多項式擬合:

from sklearn.linear_model import LinearRegression
from sklearn.preprocessing import PolynomialFeatures
from sklearn.pipeline import Pipeline
def polynomial_model(degree=1):
    polynomial_features = PolynomialFeatures(degree=degree,include_bias=False)
    linear_regression = LinearRegression(normalize=True)
    # 這是一個流水線,先增加多項式階數,然后再用線性回歸算法來擬合數據
    pipeline = Pipeline([("polynomial_features", polynomial_features),
                         ("linear_regression", linear_regression)])
    return pipeline

一個Pipeline可以包含多個處理節點,在scikit-learn里,除了最后一個節點外,其他的節點都必須實現fit()方法和transform()方法,最后一個節點只需要實現fit()方法即可。當訓練樣本數據送進Pipeline里進行處理時,它會逐個調用節點的fit()方法和transform()方法,最后調用最后一個節點的fit()方法來擬合數據。管道的示意圖如下所示:


figure5_2.png
2.數據歸一化

當線性回歸模型有多個輸入特征時,特別是使用多項式添加特征時,需要對數據進行歸一化處理。比如,特征x_{1}的范圍在[1,4]之間,特征x_{2}的范文在[1,2000]之間,這種情況下,可以讓x_{1}除以4來作為新特征x_{1},同時讓x_{2}除以2000來作為新特征x_{2},該過程稱為特征縮放(feature scaling)。可以使用特征縮放來對訓練樣本進行歸一化處理,處理后的特征范圍在[0,1]之間。

為什么要進行數據歸一化處理?以及歸一化處理有哪些注意事項?
歸一化處理的目的是讓算法收斂更快,提升模型擬合過程中的計算效率。進行歸一化處理后,當有個新的樣本需要計算預測值時,也需要先進行歸一化處理,再通過模型來計算預測值,計算出來的預測值要再乘以歸一化處理的系數,這樣得到的數據才是真正的預測數據。

在scikit-learn里,使用LinearRegression進行線性回歸時,可以指定normalize=True來對數據進行歸一化處理。具體可以查閱scikit-learn文檔。

4.示例:使用線性回歸算法擬合正弦函數

本節用線性回歸算法來模擬正弦函數。
首先生成200個在區間[2\pi,2\pi]內的正弦函數上的點,并給這些點加上一些隨機的噪聲。

import numpy as np
n_dots = 200
X = np.linspace(-2 * np.pi, 2 * np.pi, n_dots)
Y = np.sin(X) + 0.2 * np.random.rand(n_dots) - 0.1
X = X.reshape(-1, 1)
Y = Y.reshape(-1, 1);

其中,reshape()函數的作用是把Numpy的數組轉換成符合scikit-learn輸入格式的數組(這里是把一個n維向量轉換成一個n*1維的矩陣),否則scikit-learn會報錯。

接著我們使用PolynomialFeatures和Pipeline創建一個多項式擬合模型:

from sklearn.linear_model import LinearRegression
from sklearn.preprocessing import PolynomialFeatures
from sklearn.pipeline import Pipeline
def polynomial_model(degree=1):
    polynomial_features = PolynomialFeatures(degree=degree,include_bias=False)
    linear_regression = LinearRegression(normalize=True)
    pipeline = Pipeline([("polynomial_features", polynomial_features),
                         ("linear_regression", linear_regression)])
    return pipeline

分別用2/3/5/10階多項式來擬合數據集:

from sklearn.metrics import mean_squared_error
degrees = [2, 3, 5, 10]
results = []
for d in degrees:
    model = polynomial_model(degree=d)
    model.fit(X, Y)
    train_score = model.score(X, Y)
    mse = mean_squared_error(Y, model.predict(X))
    results.append({"model": model, "degree": d, "score": train_score, "mse": mse})
for r in results:
    print("degree: {}; train score: {}; mean squared error: {}"
          .format(r["degree"], r["score"], r["mse"]))

算出每個模型擬合的評分,此外,使用mean_squared_error算出均方根誤差,即實際的點和模型預點之間的距離,均方根誤差越小說明模型擬合效果越好——上述代碼的輸出結果為:

degree: 2; train score: 0.14988484858514306; mean squared error: 0.4346164668605285
degree: 3; train score: 0.27617240286045885; mean squared error: 0.37005268328809543
degree: 5; train score: 0.89946485219823; mean squared error: 0.05139801432804186
degree: 10; train score: 0.9941202722467253; mean squared error: 0.0030059768938090893

從輸出結果可以看出,多項式階數越高,擬合評分越高,均方根誤差越小,擬合效果越好。

最后我們把不同模型的擬合效果在二維坐標上畫出來,可以清楚地看到不同階數的多項式的擬合效果:

import matplotlib.pyplot as plt
from matplotlib.figure import SubplotParams
plt.figure(figsize=(12,6),dpi=200,subplotpars=SubplotParams(hspace=0.3))
for i,r in enumerate(results):
    fig = plt.subplot(2,2,i+1)
    plt.xlim(-8,8)
    plt.title("LinearRegression degree={}".format(r["degree"]))
    plt.scatter(X,Y,s=5,c='b',alpha=0.5)
    plt.plot(X,r["model"].predict(X),'r-')
plt.show()

我們使用SubplotParams調整了子圖的豎直間距,并且使用subplot()函數把4個模型的擬合情況都畫在同一個圖形上。上述代碼的輸出結果如下圖所示:


figure5_3.png

思考:在[-2π,2π]區間內,10階多項式對數據擬合得非常好,我們可以試著畫出這10階模型在[-20,20]的區域內的曲線,觀察一下該模型的曲線和正弦函數的差異。代碼如下:

plt.figure(figsize=(12,6),dpi=200)
X = np.linspace(-20,20,2000).reshape(-1, 1)
Y = np.sin(X).reshape(-1, 1)
model_10 = results[3]["model"]
plt.xlim(-20,20)
plt.ylim(-2,2)
plt.plot(X,Y,'b-')
plt.plot(X,model_10.predict(X),'r-')
dot1 = [-2*np.pi,0]
dot2 = [2*np.pi,0]
plt.scatter(dot1[0],dot1[1],s=50,c='r')
plt.scatter(dot2[0],dot2[1],s=50,c='r')
plt.show()
figure5_4.png

從圖中可以看出,10階多項式模型只有在區間[-2π,2π]之間對正弦曲線擬合較好,在此區間以外,兩者相差甚遠。此案例告訴我們,每個模型都有自己的適用范圍,在滿足適用范圍的基本前提下,要盡可能尋找擬合程度最高的模型來使用。

5.示例:預測房價

本節使用scikit-learn自帶的波士頓房價數據來訓練模型,然后用模型來預測房價。

1.輸入特征

房價和哪些因素有關?很多人可能對這個問題特別敏感,隨時可以列出很多,如房子面子、房子地理位置、周邊教育資源、周邊商業資源、房子朝向、年限、小區情況等。在scikit-learn的波士頓房價數據集里,它總共收集了13個特征,具體如下:

  • CRIM:城鎮人均犯罪率。
  • ZN:城鎮超過25000平方英尺的住宅區域的占地比例。
  • INDUS:城鎮非零售用地占地比例。
  • CHAS:是否靠近河邊,1為靠近,0為遠離。
  • NOX:一氧化氮濃度
  • RM:每套房產的平均房間個數。
  • AGE:在1940年之前就蓋好,且業主自住的房子的比例。
  • DIS:與波士頓市中心的距離。
  • RAD:周邊高速公路的便利性指數。
  • TAX:每10000美元的財產稅率。
  • PTRATIO:小學老師的比例。
  • B:城鎮黑人的比例。
  • LSTAT:地位較低的人口比例。
    從這些指標里可以看到中美指標的一些差異。當然,這個數據是在1993年之前收集的,可能和現在會有差異。不要小看了這些指標,實際上一個模型的好壞和輸入特征的選擇關系密切。大家可以思考一下,如果要在中國預測房價,你會收集哪些特征數據?這些特征數據的可獲得性如何?收集成本多高?

我們先導入數據:

from sklearn.datasets import load_boston
boston = load_boston()
X = boston.data
y = boston.target
X.shape

輸出如下:

(506, 13)

表明這個數據集有506個樣本,每個樣本有13個特征。整個訓練樣本放在一個506*13的矩陣里。可以通過X[0]來查看一個樣本數據:

X[0]

輸出如下:

array([6.320e-03, 1.800e+01, 2.310e+00, 0.000e+00, 5.380e-01, 6.575e+00,
       6.520e+01, 4.090e+00, 1.000e+00, 2.960e+02, 1.530e+01, 3.969e+02,
       4.980e+00])

還可以通過boston.features_names來查看這些特征的標簽:

boston.feature_names

輸出如下:

array(['CRIM', 'ZN', 'INDUS', 'CHAS', 'NOX', 'RM', 'AGE', 'DIS', 'RAD',
       'TAX', 'PTRATIO', 'B', 'LSTAT'], dtype='<U7')

我們可以把特征和數值對應起來,觀察一下數據。

2.模型訓練

在scikit-learn里,LinearRegression類實現了線性回歸算法。在對模型進行訓練之前,我們需要先把數據集分成兩份,以便評估算法的準確性。

from sklearn.model_selection import train_test_split
X_train,X_test,y_train,y_test=train_test_split(X,y,test_size=0.2,random_state=3)

由于數據量比較小,我們只選了20%的樣本來作為測試數據集。接著,訓練模型并測試模型的準確性評分:

import time
from sklearn.linear_model import LinearRegression
model = LinearRegression()
start = time.process_time()
model.fit(X_train,y_train)
train_score = model.score(X_train,y_train)
cv_score = model.score(X_test,y_test)
print("elaspe:{0:.6f};train_score:{1:0.6f};cv_score:{2:.6f}"
      .format(time.process_time()-start,train_score,cv_score))

我們順便統計了模型的訓練時間,除此之外,統計模型對訓練樣本的準確性得分(即對訓練樣本擬合的好壞程度)train_score,還測試了模型對測試樣本的得分sv_score。運行結果如下:

elaspe:0.000000;train_score:0.723941;cv_score:0.794958

從得分情況來看,模型的擬合效果一般,還有沒有辦法來優化模型的擬合效果呢?

3.模型優化

首先觀察一下數據,特征數據的范圍相差比較大,最小的在{10}^{-3}級別,而最大的在{10}^{2}級別,看來我們需要先把數據進行歸一化處理。歸一化處理最簡單的方式是,創建線性回歸模型時增加normalize=True參數:

model = LinearRegression(normalize=True)

當然,數據歸一化處理只會加快算法收斂速度,優化算法訓練的效率,無法提升算法的準確性。

怎么樣優化模型的準確性呢?我們回到訓練分數上來,可以觀察到模型針對訓練樣本的評分比較低(train_score:0.723941),即模型對訓練樣本的擬合成本比較高,這是一個典型的欠擬合現象。回憶我們之前介紹的優化欠擬合模型的方法,一是挖掘更多的輸入特征,而是增加多項式特征。在我們這個例子里,通過使用低成本的方案——即增加多項式特征來看能否優化模型的性能。增加多項式特征,其實就是增加模型的復雜度。

我們編寫創建多項式模型的函數:

from sklearn.linear_model import LinearRegression
from sklearn.preprocessing import PolynomialFeatures
from sklearn.pipeline import Pipeline
def polynomial_model(degress=1):
    polynomial_features = PolynomialFeatures(degree=degree,include_bias=False)
    linear_regression = LinearRegression(normalize=True)
    pipeline = Pipeline([("polynomial_features",polynomial_features),
                         ("linear_regression",linear_regression)])
    return pipeline

接著,我們使用二階多項式來擬合數據:

model = polynomial_model(degree=2)
start = time.process_time()
model.fit(X_train,y_train)
train_score = model.score(X_train,y_train)
cv_score = model.score(X_test,y_test)
print("elaspe:{0:.6f};train_score:{1:0.6f};cv_score:{2:.6f}"
      .format(time.process_time()-start,train_score,cv_score))

輸出結果是:

elaspe:0.437500;train_score:0.930547;cv_score:0.860465

訓練樣本分數和測試分數都提高了,看來模型確實得到了優化。我們可以把多項式改為3階看一下效果:

elaspe:0.343750;train_score:1.000000;cv_score:-105.483692

改為3階多項式后,針對訓練樣本的分數達到了1,而針對測試樣本的分數確實負數,說明這個模型過擬合了。

思考:我們總共有13個輸入特征,從一階多項式變為二階多項式,輸入特征個數增加了多少個?
參考:二階多項式共有:13個單一的特征,C_{13}^{2}=78個兩兩配對的特征,13個各自平方的特征,共計104個特征。比一階多項式的13個特征增加了91個特征。(如果有其他答案,歡迎在評論區留言)

4.學習曲線

更好的方法是畫出學習曲線,這樣對模型的狀態以及優化的方向就一目了然。

import matplotlib.pyplot as plt
from common.utils import plot_learning_curve
from sklearn.model_selection import ShuffleSplit
cv = ShuffleSplit(n_splits=10,test_size=0.2,random_state=0)
plt.figure(figsize=(18,4),dpi=200)
title = 'Learning Curves (degree={0})'
degrees = [1,2,3]
start = time.process_time()
for i in range(len(degrees)):
    plt.subplot(1,3,i+1)
    plot_learning_curve(plt,polynomial_model(degrees[i]),title.format(degrees[i]),
                        X,y,ylim=(0.01,1.01),cv=cv)
    print('elaspe:{0:.6f}'.format(time.process_time()-start))

其中,common.utils包里的plot_learning_curve()函數是對sklearn.model_selection.learning_curve()函數的封裝,代碼如下:

from sklearn.model_selection import learning_curve
import numpy as np

def plot_learning_curve(plt, estimator, title, X, y, ylim=None, cv=None,
                        n_jobs=1, train_sizes=np.linspace(.1, 1.0, 5)):
    """
    Generate a simple plot of the test and training learning curve.

    Parameters
    ----------
    estimator : object type that implements the "fit" and "predict" methods
        An object of that type which is cloned for each validation.

    title : string
        Title for the chart.

    X : array-like, shape (n_samples, n_features)
        Training vector, where n_samples is the number of samples and
        n_features is the number of features.

    y : array-like, shape (n_samples) or (n_samples, n_features), optional
        Target relative to X for classification or regression;
        None for unsupervised learning.

    ylim : tuple, shape (ymin, ymax), optional
        Defines minimum and maximum yvalues plotted.

    cv : int, cross-validation generator or an iterable, optional
        Determines the cross-validation splitting strategy.
        Possible inputs for cv are:
          - None, to use the default 3-fold cross-validation,
          - integer, to specify the number of folds.
          - An object to be used as a cross-validation generator.
          - An iterable yielding train/test splits.

        For integer/None inputs, if ``y`` is binary or multiclass,
        :class:`StratifiedKFold` used. If the estimator is not a classifier
        or if ``y`` is neither binary nor multiclass, :class:`KFold` is used.

        Refer :ref:`User Guide <cross_validation>` for the various
        cross-validators that can be used here.

    n_jobs : integer, optional
        Number of jobs to run in parallel (default 1).
    """
    plt.title(title)
    if ylim is not None:
        plt.ylim(*ylim)
    plt.xlabel("Training examples")
    plt.ylabel("Score")
    train_sizes, train_scores, test_scores = learning_curve(
        estimator, X, y, cv=cv, n_jobs=n_jobs, train_sizes=train_sizes)
    train_scores_mean = np.mean(train_scores, axis=1)
    train_scores_std = np.std(train_scores, axis=1)
    test_scores_mean = np.mean(test_scores, axis=1)
    test_scores_std = np.std(test_scores, axis=1)
    plt.grid()

    plt.fill_between(train_sizes, train_scores_mean - train_scores_std,
                     train_scores_mean + train_scores_std, alpha=0.1,
                     color="r")
    plt.fill_between(train_sizes, test_scores_mean - test_scores_std,
                     test_scores_mean + test_scores_std, alpha=0.1, color="g")
    plt.plot(train_sizes, train_scores_mean, 'o--', color="r",
             label="Training score")
    plt.plot(train_sizes, test_scores_mean, 'o-', color="g",
             label="Cross-validation score")

    plt.legend(loc="best")
    return plt

輸出的學習曲線如下圖所示:


figure5_5.png

從學習曲線中可以看出,一階多項式欠擬合,因為針對訓練樣本的分數比較低;而三階多項式過擬合,因為針對訓練樣本的分數達到1,卻看不到交叉驗證數據集的分數。針對二階多項式擬合的情況,雖然比一階多項式的效果好,但從圖中可以明顯地看出來,針對訓練數據集的分數和針對交叉驗證數據集的分數之間的間隔比較大,這說明訓練樣本數量不夠,我們應該去采集更多的數據,以提高模型的準確性。

6.拓展閱讀

本節內容涉及到較多的數學知識,特別是矩陣和偏導數運算法則。如果閱讀起來有困難,可以先跳過。如果有一定數學基礎,這些知識對理解算法的實現細節及算法的效率有較大的幫助。

1.梯度下降迭代公式推導

關于梯度下降算法迭代公式的推導過程,可以參考博客:http://blog.kamidox.com/gradient-descent.html,或者直接搜索“線性回歸算法kamidox.com”。博客里詳細介紹了公式推導過程中用到的偏導數運算法則。

2.隨機梯度下降算法

本章介紹的梯度下降算法迭代公式稱為批量梯度下降算法(Batch Gradient Descent,簡稱BGD),用它對參數進行一次迭代運算,需要遍歷所有的訓練數據集。當訓練數據集比較大時,其算法的效率會比較低。考慮另外一個算法:
\theta_{j}=\theta_{j}-\alpha((h(x^{(i)})-y^{(i)})x_{j}^{(i)})
這個算法的關鍵點是把累加器去掉,不去遍歷所有的數據集,而是改成每次隨機地從訓練數據集中取一個數據進行參數迭代計算,這就是隨機梯度下降算法(Stochastic Gradient Descent,簡稱SGD)。隨機梯度下降算法可以大大提高模型訓練的效率。

思考:為什么隨機取一個樣本進行參數迭代是可行的?
從數學上證明批量梯度下降算法和隨機梯度下降算法的等價性涉及到復雜的數學知識。這里有個直觀的解釋可以幫助理解兩者的等價性。回到成本函數的定義:
J(\theta)=\frac{1}{2m}\sum_{i=1}^{m}(h(x^{(i)})-y^{(i)})^2
我們說過,這里累加后除以2是為了計算方便,那么除以m是什么意思呢?答案是平均值,即所有訓練數據集上的點到預測函數的距離的平均值。再回到隨機選取訓練數據集里的一個數據這個做法來看,如果計算次數足夠多,并且是真正隨機,那么隨機選取出來的這組數據從概率的角度來看,和平均值是相當的。打個比方,儲錢罐里有1角的硬幣10枚,5角的硬幣2枚,1元的硬幣1枚,總計3元、13枚硬幣。隨機從里面取1000次,把每次取出來的硬幣幣值記錄下來,然后將硬幣放回儲錢罐里。這樣最后去算這1000次取出來的錢的平均值(1000次取出來的幣值總和除以1000)和儲錢罐里每枚硬幣的平均值(3/13元)應該是近似相等的。

3.標準方程

梯度下降算法通過不斷地迭代,從而不停地逼近成本函數的最小值來求解模型的參數。另外一個方法是直接計算成本函數的微分,令微分算子為0,求解這個方程,即可得到線性回歸的解。
線性回歸算法的成本函數:
J(\theta)=\frac{1}{2m}\sum_{i=0}^{n}(h_{\theta}(x^{(i)})-y^{(i)})^2
成本函數的“斜率”為0的點,即為模型參數的解。令\frac{\partial}{\partial{\theta}}J(\theta)=0,求解這個方程最終可以得到模型參數:
\theta=(X^{T}X)^{-1}X^{T}y
方程求解過程可參閱https://en.wikipedia.org/wiki/Linear_least_squares_(mathematics)#Derivation_of_the_normal_equations
這就是我們的標準方程。它通過矩陣運算,直接從訓練樣本里求出參數θ的值。其中X為訓練樣本的矩陣形式,它是m×n的矩陣,y是訓練樣本的結果數據,它是個m維列向量。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容