Python 機器學習實戰 —— 監督學習(上)

前言

近年來AI人工智慧成為社會發展趨勢,在IT行業引起一波熱潮,有關機器學習、深度學習、神經網路等文章多不勝數。從智慧家居、自動駕駛、無人機、智慧機器人到人造衛星、安防軍備,無論是國家級軍事設備還是廣泛的民用設施,都充斥著AI應用的身影。接下來的一系列文章將會由淺入深從不同角度分別介紹機器學習、深度學習之間的關係與區別,通過一系統的常用案例講述它們的應用場景。
本文將會從最常見的機器學習開始介紹相關的知識應用與開發流程。

 

 

目錄

一、淺談機器學習

二、基本概念

三、常用方法介紹

四、線性模型

五、支援向量機

六、k近鄰

七、樸素貝葉斯分類 

八、決策樹

 

 

一、淺談機器學習

1.1 機器學習簡介

其實AI人工智慧並非近代的產物,早在19世紀的50到80年代,科學家們就有著讓電腦演算法代替人腦思考的想法。通過幾十年的努力,到了90年代成為了機器學習蓬勃發展期,很多科技公司在不同領域提供了相關的技術支援。在最初,機器學習只用於垃圾郵件清理,數學公式分析等簡單領域,然而後來其應用場景越來越多,無論是圖片過濾,語音分析,數據清洗等領域都能看到機器學習的身影。到如今無論是智慧手機,航空運輸,智慧駕駛等方方面面都可以看到 AI 的身影。

從不同領域分析,AI人工智慧包含了機器學習與深度學習。

機器學習主要應用數據科學領域,它與普通程式開發的主要區別在於一般程式,數據往往來源於不同的資料庫,通過對數據進行複雜轉化,運算得到最後的結果。
而機器學習目的並不是為了得到最後的運算結果,而是對計算過程進行分析,總結出一套運算的規則。只要數據量足夠多,運算規則就越準確。最後可以根據這套規則對沒有通過驗證的數據進行預算,得到預算後的值。只要使用的規則正確,預算的結果的正確率往往可以達到95%以上。 

而深度學習開始只是機器學習的一分支領域,它更強調從連續的層中進行學習,這種層級結構中的每一層代表不同程式的抽象,層級越高,抽象程度越大。這些層主要通過神經網路的模型學習得到的,最大的模型會有上百層之多。而最簡單的神經網路分為輸入層,中間層(中間層往往會包含多個隱藏層),輸出層。

經過多年的發展,深度學習的應用層面越來越廣,從而誕生出 Tensorflow、Keras、Theano、CNTK 等框架的支援,逐漸發展形成獨立的技術領域。根據不同的應用場景,深度學習又分為多層感知器MLP、深度神經網路DNN、卷積神經網路CNN、遞歸神經網路RNN、生成對抗網路BNN等多個方面。

由於本系統文章主要是對機器學習進行介紹,對深度學習有興趣有朋友可以留意後面的文章,將會對神經網路進行更深入全面的講解。

1.2  機器學習分類

機器學習可分為監督學習與無監督學習兩種

1.2.1  監督學習

從輸入 / 輸出中總結運算規律進行機械學習的演算法叫做監督學習。在監督學習中,每個實例都是由一個輸入對象(通常為矢量)和一個期望的輸出值(也稱為監督訊號)組成。監督學習演算法是分析該數據的輸入對象與輸出值 ,總結當中的運算規律併產生一個推斷的邏輯,並可以把此邏輯用於映射出的新實例,對沒有運算過的數據結果進行預測。
其過程就像讀小學時候的數學應用題,課堂上老師會先講解應用題的公式與計算方法,然後學生可以在做作業時根據此計算規律完成類似的題目。

常見的監督學習包含了線性回歸、k近鄰、樸素貝葉斯分類 、決策樹、隨機森林與梯度提升決策樹、支援向量機等多種演算法,在下面將用不同實例介紹其應用場景。

1.2..2  無監督學習

相比之下無監督學習只有輸入數據,沒有提供輸出結果,只能通過計算評估其中的運行規律。就好比在考試中遇到作業上沒有做過的應用題,只能通過思考總結才能得出答案。它的最大挑戰在於實例沒有包含最終結果,所以無法在學習中評估演算法是否學到了有用的東西。

常見的無監督學習分為聚類、降維兩大類,包含了PCA(主成分分析)、NMF(非負矩陣分解)、t-SNE(流形學習)、k均值聚類、DBSCAN 等多種演算法,將在下一篇文章 《 Python 機器學習實戰 —— 無監督學習》中詳細介紹,敬請留意。

回到目錄

 二、基本概念

在機器學習里,每一行的數據實例被看作是一個樣本,每一列的數據被看作是一個特徵。學過面向對象開發的朋友應該好理解,這相當於一個類與其屬性的關係。而監督學習是通過一系列樣本計算,總結出特徵與計算結果之間的關係,這被稱為特徵提取

2.1 分類與回歸的區別

監督學習分成分類與回歸兩大類:分類與回歸
分類的目標是預測類別標籤,類似於通過人臉識別分出黃種人、白種人、黑種人。
回歸的目標則是預測一個連續值,相當於數學裡面的坐標圖,它可以通過已有的數據計算出坐標的走向,從而對末知值進行預測。類似於氣溫預計,GDP增速預算。
其實區分分類與回歸很簡單,只要判斷輸出值是否具備連續性,具有連續性的就是回歸,只有若干個固定值的就是分類。

2.2 數據劃分

從監督學習的概念可以了解,其學習目標是要從已有的數據中總結出各個特徵與結果之間的關係,然而這個計算模型的泛化能力如何,我們無法從已通過運算過的數據中得知。所以在運算前,一般會先對數據進行劃分,一部分是訓練數據,專門用作模型的學習,另一部分是測試數據,用作模型的測試,其比例一般是75%對25%。
為了方便數據劃分,sklearn 提供 train_test_split 函數方便日常使用,下一節將有介紹。

2.3  過擬合與欠擬合

對於訓練數據最重要的目標是將其泛化,總結出每個特徵的比重,因此並非正確率越高越好。如果在訓練過程中,得到的正確率是100%,然而在測試時候,正常率卻很低,不能泛化到測試數據當中。這往往是因為訓練過程中存在過似合,過度重視某些特徵,而忽略某些關鍵因素。
相反,如果模型過分簡單,考慮的因素太少,甚至導致在訓練過程中的正確率就很低,這可能就是因為模型的欠擬合造成。

 

所以在選擇模型的時候,需要在過擬合與欠擬合之間作出權衡(如圖)。

2.4 損失函數

損失函數一方面跟數學計算原理的應用關係比較密切,另一個方面它也是機器學習的基層原理,此章節想做一個簡單的介紹。由於網上關於這方面的學習材料很多,所以此章節主要是想通過最容易理解的方式去講述計算的原理,至於複雜的計算流程可以在網上查找。
在寫這篇文章的時候,恰好是高考的開考日,在這裡預祝師弟師妹能順利闖關,金榜題名。畢竟最近疫情有複發的情況,學習實在不容易。通過此章節想告訴理工科的學子們,辛勤學習並非一無所用。公式里所蘊藏的原理,他日參加工作時候還是有很廣的應用場景。

在日常對數據模型的測試中,預測值與真實值總會存在一定的偏差,以直線方程 y=a*x+b 為例,如下圖。在預測值 [x1,y1]  [x2,y2]  [x3,y3]  ……. [x(i),y(i)] 中,並非每個點與方程匹配,而是存在一定的誤差。所以在測試中,當誤差值最小時,可以看作這條直線的正確答案。

損失函數就是用來評價模型的預測值和真實值不一樣的程度,當損失值越小,證明預計值越接近真實值,模型的訓練程度就越好。為了讓預測值 y^ (i)  盡量接近於真實值 y(i) ,學者提出了多個方法進行計算損失值,最常見的損失函數有均方誤差(MSE)、交叉熵誤差(CEE)等。
均方誤差(MSE)中,因為每個預測值與真實值大小不同,為了解決差值的正負值之分,所以誤差大小就用差值平方來表示,差值越小,平方值小,誤差越小。
均方誤差計算公式就是求預測值與真實值之間的方差平均值,如果均方誤差越小,那就證明預測值越接近於真實值。

交叉熵誤差(CEE)的公式如下,其中 t=(t_{1},t_{2},...,t_{n}) 為實際的分類結果,y={y_{1},y_{2},...,y_{n}}  為預測的結果

把它用於分類模型時有一個很好的特性,可以真實的反應出真實分類結果和預測結果的誤差,假設 t_{i}=1, 即真實的分類結果是 t_i, 則 交叉熵誤差可以簡化為  E=-logy_{i},那函數影像如下。可以看到,y_{i} 越接近 1,即預測結果和真實分類結果越接近,誤差越接近0, 即誤差越小。

 

關於損失函數有多種演算法,下面介紹兩種最常用也是最容易理解的演算法:最小二乘法(OLS)與梯度下降法(GD)

 2.4.1 最小二乘法

最小二乘法是基於 MSE 均方誤差而來,它的目標就是求出 n 個 y^ (i) 與 y(i) 誤差值平方和最小時各常量參數的值。還是以上面提到的直線方程 y=a*x+b 為例,均方誤差越小,證明預測值越接近於真實值,這時候只要求出最小均方誤差時 a,b的值,也就可以得出此直線的方程。因為 n 是個常數,所以求最小均方誤差的公式可以轉化為求預測值 y^ (i) 與真實值之 y(i) 間的最小方差,這就是最小二乘法的由來。

 代入  y=a*x+b  後,計算公式可得到轉換成下圖,由於計算的是平方和,所以表達式必然存在最小值,通過偏導數為 0 時得極值的原理,可得到計算公式:

此時好辦,最小二乘法已經變成高考的導數題,要了解求解過程的程式猿建議去複習一下高考時偏導數求解的方法(若怕麻煩相信通過幾條簡單的語法計算就已經可以最終結果)。根據以下得出的公式,代入已知的數據點  [x1,y1]  [x2,y2]  [x3,y3]  ……. [x(i),y(i)]  便可得到最後的 a、b 參數值,最後把 a、b 值代入直線方程就是最終的答案。

第四節介紹的線性回歸模型中,有部分就是使用最小二乘法來實現的,敬請留意。
當然,現實情形下數據模型是複雜的,不可能完全按照直線模型來走,所以複雜的線性模型還可以通過多次方程式,基函數,線性分割等多種方法來處理,在後面章節將會詳細講述。

2.4.2 梯度下降法

上面提到損失函數就是用來評價模型的預測值和真實值不一樣的程度,損失值越小證明參數的預測值越接近於真實值。梯度下降法就是用於求損失值最小時的參數值,相對於最小二乘法複雜的數學公式,它可以通過偏導數斜率的變化更形象地描述解決的過程。

先介紹梯度下降法用到的基本概念:
切線斜率:從數學的角度可知,一維函數在點 x 的導數叫做函數在點 x 的切線斜率,二維函數在點(x0,x1) 的偏導數稱為點(x0,x1) 的切線斜率,如此類推。
鞍點:函數的極小值(最小值)被稱為鞍點,從數學原理可知當到達鞍點時,切線斜率接近於 0。
梯度:以常用的二元方程 f(x0,x1)=x02+x12  為例子,把全部變數的偏導數(切線斜率)匯總成的向量稱為梯度

下面以一個二次方程 f(x)=x2+1 為例,介紹一下如何通過梯度下降法求極值
首先,切線的斜率可以通過導數原理求得,例如 f(x) 分別經過點 A(- 4,17) ,B(- 5,26),此時會發現,兩點之間的距離越小,它們所組成的直線就越接近於該點的切線。兩點距離無限接近於0時,此時可認為這條直線就是該點的切線,這也是微積分的基本原理。

下面的例子就是實現上述的原理,畫出與點(- 4,17)  間 x 軸的距離分別為 0.3、0.2、0.001,0.0001 的直線。調用 tangent()可以返回該直線的斜率,當 h 使用默認值 0.0001 值,該直線斜率已經無限接近於導數值(即切線斜率)。

 1 class gradinet_drop():
 2     def __init__(self):
 3         return
 4 
 5     def f(self, x):
 6         return x*x+1
 7 
 8     def polt_line(self):
 9         # 畫出 y=x*x+1 圖
10         x = np.linspace(-5, 5, 100)
11         plt.plot(x.reshape(-1, 1), self.f(x))
12         plt.xlabel('x')
13         plt.ylabel('f(x)')
14 
15     def tangent(seft,x,h=0.0001):
16         #根據 y=ax+b 直線公式
17         #求導數,即切線斜率
18         a=(seft.f(x+h)-seft.f(x))/h
19         #求截距
20         b=seft.f(x+h)-a*x+h
21         #划出切線
22         seft.polt_tangent(a,b)
23         return a
24 
25     def polt_tangent(self,a,b):
26         #划出直線 y=ax+b
27         x = np.linspace(-5, -1, 100)
28         y=a*x+b
29         plt.plot(x.reshape(-1,1),y,'--')
30 
31 gradientdrop=gradinet_drop()
32 gradientdrop.polt_line()
33 # 分別以 h 等於 -0.3,-0.2,-0.001 ,0.0001求斜率
34 # 將發現 h 越小,所得斜率越接於近切線斜率
35 for h in [-0.3,-0.2,0.001,0.0001]:
36     print('h={0}, tangent={1}'.format(h,gradientdrop.tangent(-4,h)))
37 plt.show()

運行結果

梯度下降法最終的目標是求得切線斜率接近無限於0時的數據點(鞍點), 所以函數可以使用最簡單的方法,使取值沿著當前梯度方向不斷前進,然後重複計算梯度。通過此遞歸方式,切線斜率就是無限接近於0。
用數學公式表達如下圖,η 表示學習率,學習率可以根據實際需要而改變,將學習率乘以該點的切線斜率代表學習量。

根據此公式,可以在上面的程式碼中加入一個簡單的方法gradient(),以 0.01 的學習率,重複1000次進行計算。隨著梯度不斷地下降,x 坐標就會根據學習量不斷接近鞍點,在重複1000後可以看到在數據點(0,1)處的切線斜率 tangent 為 -1.0098588631990424e-08,已無限接近於 0 ,此時鞍點的函數值 f(x)=1 就是此函數的最小值。

 1 class gradinet_drop():
 2     def __init__(self):
 3         return
 4 
 5     def f(self, x):
 6         return x*x+1
 7 
 8     def polt_line(self):
 9         # 畫出 y=x*x+1 圖
10         x = np.linspace(-5, 5, 100)
11         plt.plot(x.reshape(-1, 1), self.f(x))
12         plt.xlabel('x')
13         plt.ylabel('f(x)')
14 
15     def tangent(seft,x,h=0.0001):
16         #根據 y=ax+b 直線公式
17         #求導數(切線斜率)
18         a=(seft.f(x+h)-seft.f(x))/h
19         #求截距
20         b=seft.f(x+h)-a*x+h
21         #隨機畫出切線
22         n=np.random.randint(100)
23         if(n==5):
24             seft.polt_tangent(a,b)
25         return a
26 
27     def polt_tangent(self,a,b):
28         #划出切線 y=ax+b
29         x = np.linspace(-5, 1, 100)
30         y=a*x+b
31         plt.plot(x.reshape(-1,1),y,'--')
32 
33     def gradinet(seft,x,rate=0.01,n=1000):
34         #學習率默認為0.01,默認重複1000次
35         for i in range(n):
36             x-=seft.tangent(x)*rate
37             print('x={0},f(x)={1},tangent slope={2}'.format(x,seft.f(x),seft.tangent(x)))
38 
39 gradientdrop=gradinet_drop()
40 gradientdrop.polt_line()
41 #使用學習率默認0.01,默認重複1000次求出鞍點
42 gradientdrop.gradinet(-3)
43 plt.show()

運行結果

 

到此不防思考一下使用梯度下降法找到鞍點的目標是什麼,其實只要把簡單的二次方程 f(x) 替換成為損失函數便會豁然開朗。以均方誤差 MSE 為例,當找到鞍點,意味著找到函數最小值,即在該點時均方誤差最小,在這點得到的參數值就是該函數的常量。
同樣以最小二乘法中的直線 y=a*x+b 為例,把程式碼稍微修改一下。首先把方程改為均方誤差公式,通過 sympy 包中的函數 diff 分別對 a,b 求偏導數,把學習率設置為0.01,訓練1000次,a,b的默認初始值均為 1。使用 sklearn 中的測試數據 make_regression 進行訓練,最後畫出該直線並輸出斜率和截距。

 1 class gradinet_drop():
 2     def __init__(self,train_x,train_y):
 3         #定義測試數據
 4         self.x=train_x
 5         self.y=train_y
 6         return
 7 
 8     def mes(self,a,b):
 9         #均方誤差計算公式
10         return mean((self.y-a*self.x-b)**2)
11 
12     def partial_derivative(seft):
13         #求 mes(a,b)對 a,b 的偏導數
14         partial_derivative_a=sp.diff(seft.mes(a,b), a)
15         partial_derivative_b=sp.diff(seft.mes(a,b),b)
16         return [partial_derivative_a,partial_derivative_b]
17 
18     def gradinet(seft,rate=0.01,n=1000):
19         #學習率默認為0.01,默認重複1000次
20         #把 y=a*x+b 參數 a,b 的初始值設為 1,1
21         a1=1
22         b1=1
23         #默認訓練1000次,找到最小均方誤差時的 a,b 值
24         for i in range(n):
25             deri=seft.partial_derivative()
26             a1-=deri[0].subs({a:a1,b:b1})*rate
27             b1-=deri[1].subs({a:a1,b:b1})*rate
28         #輸出直線參數 a,b 值
29         print('y=a*x=b\n  a={0},b={1}'.format(a1,b1))
30         return [a1,b1]
31 
32     def polt_line(self,param):
33         #根據a,b參數值划出直線 y=a*x+b
34         x = np.linspace(-3, 3, 100)
35         y=param[0]*x+param[1]
36         plt.plot(x,y,20)
37         plt.legend(['train data','line'])
38 
39 #輸入測試數據
40 X,y=dataset.make_regression(n_features=1,noise=5)
41 gradient_drop=gradinet_drop(np.squeeze(X),y)
42 #畫出數據點
43 plt.plot(X,y,'.')
44 #訓練數據找出最小均方誤差時的參數 a,b 值
45 param=gradient_drop.gradinet()
46 #畫出訓練後的直線
47 gradient_drop.polt_line(param)
48 plt.show()

運行結果

 

可見計算後的直線與訓練庫中的點已相當接近,使用梯度下降法只需要牢記一點:計算目標是求出鞍點,在損失值最低時的參數值就是該函數的常量。下面介紹到的 SGD 模型正是使用梯度下降法進行計算,後面將有詳細說明。
對損失函數就介紹到這裡,希望對各位的理解有所幫助。

回到目錄

三、常用方法介紹

Scikit-Learn 是目錄最常用的機器學習庫,所以在本文當中大部實例都是運用當中的方法完成。
在 sklearn.datasets 中包含了大量的數據集,可為運算提供測試案例。例如鳶尾花數據集 load_iris()、癌症數據集 load_breast_cancer()、波士頓放假數據集 load_boston() 這些都是各大技術文章里常用的數據集,為了方便閱讀下面大部分的例子中都會用到這些數據作為例子進行解說。

3.1  train_test_split 方法

上面曾經提起,為了方便區分訓練數據與測試數據,sklearn 提供了 train_test_split 方法去劃分訓練數據與測試數據

 train_test_split( * arrays,  test_size=None,   train_size=None, random_state=None, shuffle=True,stratify=None)

  • *arrays:可以是列表、numpy數組、scipy稀疏矩陣或pandas的數據框
  • test_size:可以為浮點、整數或None,默認為None

    ①若為浮點時,表示測試集佔總樣本的百分比

    ②若為整數時,表示測試樣本樣本數

    ③若為None時,test size自動設置成0.25

  • train_size:可以為浮點、整數或None,默認為None

    ①若為浮點時,表示訓練集佔總樣本的百分比

    ②若為整數時,表示訓練樣本的樣本數

    ③若為None時,train_size自動被設置成0.75

  • random_state:可以為整數、RandomState實例或None,默認為None

    ①若為None時,每次生成的數據都是隨機,可能不一樣

    ②若為整數時,每次生成的數據都相同

  • stratify可以為類似數組或None

    ①若為None時,劃分出來的測試集或訓練集中,其類標籤的比例也是隨機的

    ②若不為None時,劃分出來的測試集或訓練集中,其類標籤的比例同輸入的數組中類標籤的比例相同,用於處理不均衡數據集

常用例子:

把1000個數據集按 75% 與 25% 的比例劃分為訓練數據與測試數據

1     X,y=make_wave(1000)
2     X_train,X_test,y_train,y_test=train_test_split(X,y)

 train_size 與 test_size 默認值為75% 與 25% ,所以上面的例子與下面例子得出結果相同

1     X,y=make_wave(1000)
2     X_train,X_test,y_train,y_test=train_test_split(X,y,train_size=0.75,test_size=0.25)

為了每次測試得到相同的數據集,可以把 RandomState 設置為相同值,只要RandomState相同,則產生的數據集相同。
因為 RandomState 默認為空,因此在不填寫的情況下,每次產生的數據集都是隨機數據

1     X,y=make_wave(1000)
2     X_train,X_test,y_train,y_test=train_test_split(X,y,random_state=0)

 

3.2 predict 方法與 accuracy_score 方法

在分類模型中,通常使用 predict(selt, X:any)方法預測新數據的標籤,通常在完成訓練後,就是通過此方法輸入測試數據,把計算結果與測試結果進行對比。
accuracy_score 主要用於對比預測結果的準備率
下面的程式碼就是最簡單的流程,先劃分訓練數據與測試數據,然後選擇模型,輸入訓練數據進行泛化,輸入測試數據求出計算結果,最後把計算結果與已有的測試結果進行對比,查看其正確率。

 1     X,y=make_blobs(n_samples=150,n_features=2)
 2     #劃分訓練數據與測試數據
 3     X_train,X_test,y_train,y_test=train_test_split(X,y)
 4     #綁定模型
 5     knn_classifier=KNeighborsClassifier(n_neighbors=7)
 6     #輸入訓練數據
 7     knn_classifier.fit(X_train,y_train)
 8     #運行測試數據
 9     y_model=knn_classifier.predict(X_test)
10     #把運行結果與測試結果進行對比
11     print(accuracy_score(y_test,y_model))        

 

3.3 score 方法

在回歸模型當中,由於測試結果並非固定值,所以一般通過使用 score(self, X, y, sample_weight=None) 方法,通過對 R^2(判定係數)來衡量與目標均值的對比結果。
R^2=1,則表示模型與數據完成吻合,如果R^2為負值,側表示模型性能非常差。

1     X,y=make_wave(1000)
2     X_train,X_test,y_train,y_test=train_test_split(X,y,random_state=0)
3     linearReg=LinearRegression()
4     linearReg.fit(X_train,y_train)
5     print(linearReg.score(X_train,y_train))

 

3.4 validation_curve 驗證曲線

上一節曾經講過,要提高模型的品質,應該在過擬合跟欠擬合當中,選擇一個平衡點。
有見及此,為了更直觀地了解模型的品質,sklearn 特意準備了 validation_curve 方法 ,可以更直觀與觀察到驗證曲線的示意圖。

validation_curve(estimator, X, y, *, param_name, param_range, groups=None, 
     cv=None, scoring=None, n_jobs=None, pre_dispatch=”all”,
     verbose=0, error_score=np.nan, fit_params=None)

  • estimator:實現了fit 和 predict 方法的對象
  • X : 訓練的向量
  • y : 目標相對於X分類或回歸
  • param_name:將被改變的變數名稱
  • param_range:param_name對應的變數的取值
  • cv:如果傳入整數,測試數據將分成對應的分數,其中一份作為cv集,其餘n-1作為traning(默認為3份)

只需要簡單地綁定模型,輸入數據,便可得到模型的準確率變化曲線

 1     # validation_curve 綁定 SVC
 2     X, y = load_digits(return_X_y=True)
 3     param_range = np.logspace(-6, -1, 5)
 4     train_scores, test_scores = validation_curve(
 5         SVC(), X, y, param_name="gamma", param_range=param_range,
 6         scoring="accuracy", n_jobs=1)
 7     #訓練數據與測試數據的平均得分
 8     train_scores_mean = np.mean(train_scores, axis=1)
 9     test_scores_mean = np.mean(test_scores, axis=1)
10     #顯示數據
11     plt.title("Validation Curve with SVM")
12     plt.xlabel(r"$\gamma$")
13     plt.ylabel("Score")
14     plt.ylim(0.0, 1.1)
15     lw = 2
16     plt.semilogx(param_range, train_scores_mean, label="Training score",
17                  color="darkorange", lw=lw)
18     plt.semilogx(param_range, test_scores_mean, label="Cross-validation score",
19                  color="navy", lw=lw)
20     plt.legend(loc="best")
21     plt.show()

運行結果

下面的章節開始介紹  sklearn 中監督學習的一些常用模型,可能模型的使用方法基本一致,看起來似乎千篇一律。實則不然,因為機器學習與一般的開發不一樣,主要是了解不同模型的運算規則和適用場景。從原理中理解實質,希望讀者能夠明白。

回到目錄

四、線性模型

線性模型是實踐中應用最為廣泛的模型之一,它支援分類與回歸,最常用的線性分類演算法有 LogisticRegression , LinearSVC、SGDClassifier,常用的線性回歸演算法有 LinearRegression、Ridge、Lasso 、SGDRegressor 等,後面將一一講解。前幾節,將從最常用的線性回歸開始入手,介紹幾個最常用的線性模型。

從最簡單的直線函數 y = w * x + k 開始介紹,這就是線性模型當中最簡單的單一特徵模型,模型訓練的目標是通過數據計算出最接近數據點的模型參數 w 與 k。
其中 x 是輸入變數,也是唯一的特徵;
w 為斜率,也被稱為權重被保存在 coef_ 屬性當中;
k 為載矩也稱偏移量,被保存於 intercept_ 屬性當中。
線性函數的運算結果類似於下圖,是一條 斜率為 w,偏移量為 k 的直線

 

模型看似簡單,然而在機器學習中模型往往是由淺入深,當輸入特徵由一個變為兩個時,模型變會從一條直線變為一個平面,當特徵變為三個時,模型將變成一個立體三維空間 ……

由此可得,線性模型的最終公式如下,模型會有 n+1 個特徵

y = w[0] * x[0] + w[1] * x[1] + w[2] * x[2] + w[3] * x[3] + …… + w[n] * x[n] + k

為了更好地理解線性模型,下面先由最簡單的 「線性回歸」 開始講起

 

4.1 線性回歸 LinearRegression

線性回歸是最簡單的的線性模型,此模型就是通過第二節介紹的最小二乘法,找出參數 w 與 k,使得訓練集與預測集的均方誤差 MSE 最小,最終確定 w 與 k 值。
下面就是一個單一特徵的數據集進行測試,用 100 條數據計算出斜率和偏移量,再划出此直線。 

 1 def linear_regression_test():
 2     #測試數據
 3     line=np.linspace(-3,3,50)
 4     datasets.make_regression(n_features=1,noise=35,random_state=1)
 5     X_train,X_test,y_train,y_test=train_test_split(X,y)
 6     #線性回歸模型訓練
 7     linear=LinearRegression()
 8     linear.fit(X_train,y_train)
 9     #準確率
10     print('train data prec:'+str(linear.score(X_train,y_train)))
11     print('test data prec:'+str(linear.score(X_test,y_test)))
12     #斜率與截距
13     print('coef:'+str(linear.coef_))
14     print('intercept:'+str(linear.intercept_))
15     #圖形顯示
16     plt.plot(X_train,y_train,'*','')
17     result=linear.predict(line.reshape(-1,1))
18     plt.plot(line,result,'r')
19     plt.legend(['training data','model'])
20     plt.show()

輸出結果

線性模型

LinearRegression 模型比較簡單,不需要任何參數,但因此也無法調節模型的複雜程度,可以看到訓練數據與測試數據的準確都不高。在實際應用中,LinearRegression 模型的應用場景並不多。

 

4.2 Ridge 嶺回歸

為了提高準確率,sklearn 設計了 Ridge 嶺回歸模型來代替 LinearRegression。嶺回歸也是使用最小二乘法進行計算,然而與 LinearRegression 不同的是嶺回歸使用了正則化 L2,它會讓 w 的元素盡量偏向於0。
Ridge 構造函數

1 class Ridge(MultiOutputMixin, RegressorMixin, _BaseRidge):
2     @_deprecate_positional_args
3     def __init__(self, alpha=1.0, *, fit_intercept=True, normalize=False,
4                  copy_X=True, max_iter=None, tol=1e-3, solver="auto",
5                  random_state=None):
6     ......

參數說明

  • alpha 是正則項係數,初始值為1,數值越大,則對複雜模型的懲罰力度越大。
  • fit_intercept:bool類型,默認為True,表示是否計算截距 ( 即 y=wx+k 中的 k )。
  • normalize:bool類型,默認為False,表示是否對各個特徵進行標準化(默認方法是:減去均值併除以L2範數),推薦設置為True。如果設置為False,則建議在輸入模型之前,手動進行標準化。當fit_intercept設置為False時,將忽略此參數。
  • copy_X:默認值為True,代表 x 將被複制,為 False 時,則 x 有可能被覆蓋。
  • max_iter:默認值為None,部分求解器需要通過迭代實現,這個參數指定了模型優化的最大迭代次數。
  • tol:默認為小數點後3位,代表求解方法精度
  • solver:求解優化問題的演算法,默認值 auto,可以根據數據類型選擇最合適的演算法。可選的演算法有:

1).svd:採用奇異值分解的方法來計算。

2).cholesky:採用scipy.linalg.solve函數求得閉式解。

3).sparse_cg:採用scipy.sparse.linalg.cg函數來求取最優解。

4).lsqr:使用scipy.sparse.linalg.lsqr求解,它是最快的。

5).sag:使用隨機平均梯度下降,當n_samples和n_features都較大時,通常比其他求解器更快。

  • random_state:隨機數種子,推薦設置一個任意整數,同一個隨機值,模型可以復現。

Ridge 使用了 L2 正則化規範,對模型係數 w 進行約束,使每個特徵的 w 儘可能的小,避免過度擬合。 相比 LinearRegression ,可見 Ridge 使用 L2 正則化規範後,分數有進一步的提升。

 1 def ridge_test():
 2     #測試數據
 3     line=np.linspace(-3,3,100)
 4     X,y=datasets.make_regression(n_features=1,noise=35,random_state=1)
 5     X_train,X_test,y_train,y_test=train_test_split(X,y)
 6     #嶺回歸模型
 7     ridge=Ridge(alpha=1)
 8     ridge.fit(X_train,y_train)
 9     #計算準確率
10     print('train data prec:'+str(ridge.score(X_train,y_train)))
11     print('test data prec:'+str(ridge.score(X_test,y_test)))
12     #斜率與截距
13     print('coef:'+str(ridge.coef_))
14     print('intercept:'+str(ridge.intercept_))
15     # 圖形顯示
16     plt.plot(X_train, y_train, '*', '')
17     plt.legend(['training data', 'model'])
18     result = ridge.predict(line.reshape(-1, 1))
19     plt.plot(line, result, 'r')
20     plt.show()

運算結果

 Ridge 模型

除此以外,Ridge 模型還提供了一個參數 alpha 用於控制其泛化性能,alpha 默認值為 1。
alpha 越大,w 會更趨向於零,因此泛化性能越高,但訓練集上的性能會降低,在數據量較大的時候需要權衡利弊。
alpha 越小,w 的限制就會越小,訓練集上的性能就會越高。要注意的是如果把 alpha 調到接近於 0,那 L2 正則化基本不起作用,Ridge 的計算結果將接近於 LinearRegression。
下面試著把 alpha 值調為 0.2 ,可以看到測試集上的分數有一定提升。 

 

 4.3 Lasso 模型

Lasso 跟 Ridge 一樣都是線性回歸模型,兩者的主要區別在 Ridge 默認是使用 L2 正則化,而 Lasso 使用的是 L1 正則化規範。L1 會把參數控制趨向於0,從而抽取更有關鍵代表性的幾個特徵。
Lasso的構造函數

 1 class Lasso(ElasticNet):
 2     @_deprecate_positional_args
 3     def __init__(self, alpha=1.0, *, fit_intercept=True, normalize=False,
 4                  precompute=False, copy_X=True, max_iter=1000,
 5                  tol=1e-4, warm_start=False, positive=False,
 6                  random_state=None, selection='cyclic'):       ......

參數說明

  • alpha 是正則項係數,初始值為1,數值越大,則對複雜模型的懲罰力度越大。
  • fit_intercept:bool類型,默認為True,表示是否計算截距(即y=wx+k中的k)。
  • normalize:bool類型,默認為False,表示是否對各個特徵進行標準化(默認方法是:減去均值併除以L2範數),推薦設置為True。如果設置為False,則建議在輸入模型之前,手動進行標準化。當fit_intercept設置為False時,將忽略此參數。
  • precompute:默認值為 Falise,表示是否使用預計算的 Gram 矩陣來加速計算。如果設置為 auto 代表讓電腦來決定。Gram 矩陣也可以作為參數傳遞。對於稀疏輸入這個選項總是正確的,用於保持稀疏性。
  • copy_X:默認值為True,代表 x 將被複制,為 False 時,則 x 有可能被覆蓋。
  • max_iter:默認值為10000,部分求解器需要通過迭代實現,這個參數指定了模型優化的最大迭代次數。
  • tol:默認為小數點後 4 位,代表求解方法精度。
  • warm_start:默認值為 False,當設置為True時,重用之前調用的解決方案作為初始化,否則,只需要刪除前面的解決方案。
  • positive:默認值為 False,當設置為 True 時,則係數總是為正數。
  • random_state:隨機數種子,推薦設置一個任意整數,同一個隨機值,模型可以復現。
  • selection:默認值為 cyclic ,如果設置為 random,則每一次迭代都會更新一個隨機係數,而不是在默認情況下按順序循環,這樣做通常會導致更快的收斂速度,尤其是當tol大於1e-4時。

Lasso 模型當中包含兩個主要參數:
alpha 默認值為 1 ,按照 L1的正則化規範,alpha 值越大,特徵提取數越少,更多的權重會接近於0,alpha 越小,特徵提取越多,當alpha 接近於0時,Lasso 模型與 Ridge 類似,所有特徵權重都不為 0
max_iter  默認值為10000, 它標誌著運行迭代的最大次數,有需要時可以增加 max_iter 提高準確率。

下面嘗試對 100 特徵的數據進行測試,把 alpha 值設置為 0.2,可以看到測試數據的準確率比較低,只有 0.82,有效的特徵有72個。

 1 def lasso_test():
 2     # 測試數據
 3     X,y=datasets.make_regression(n_features=100,noise=40,random_state=1)
 4     X_train,X_test,y_train,y_test=train_test_split(X,y,train_size=0.75,test_size=0.25)
 5     # 把alpha設置為30
 6     lasso=Lasso(alpha=0.2,max_iter=10000)
 7     lasso.fit(X_train,y_train)
 8     #輸出正確率
 9     print('lasso\n train data:{0}'.format(lasso.score(X_train,y_train)))
10     print(' test data:{0}'.format(lasso.score(X_test,y_test)))
11     #有效特徵數與總數
12     print(' used features: {0}'.format(sum(lasso.coef_!=0)))
13     print(' total features: {0}'.format(lasso.n_features_in_))
14     plt.plot(lasso.coef_,'o',color='red')
15     plt.legend(['feature'])
16     plt.show()

運行結果

 

 試著把 alpha 調為2,此時有效特徵下降到 45,而測試數據的分數達到 0.94

 

 

4.4 淺談正則化概念

通過 Ridge 與 Lasso 的例子,相信大家對正則化概念會有一定的了解。在這裡簡單介紹一下L1、L2 正則化的區別。
正則化(Regularization)是機器學習中一種常用的技術,其主要目的是控制模型複雜度,減小過擬合。最基本的正則化方法是在原目標函數 中添加懲罰項,對複雜度高的模型進行「懲罰」。其數學表達形式為:

[公式]

式中 [公式] 、 [公式]為訓練樣本和相應標籤, [公式] 為權重係數向量; [公式] 為目標函數, [公式] 即為懲罰項,可理解為模型「規模」的某種度量;參數[公式] 控制控制正則化強弱。不同的 [公式] 函數對權重 [公式] 的最優解有不同的偏好,因而會產生不同的正則化效果。最常用的 [公式] 函數有兩種,即 [公式] 範數和 [公式] 範數,相應稱之為 [公式] 正則化和 [公式] 正則化。

[公式]

[公式]

由表達式可以看出,L1 正則化則是以累加絕對值來計算懲罰項,因此使用 L1 會讓 W(i) 元素產生不同量的偏移,使某些元素為0,從而產生稀疏性,提取最有效的特徵進行計算。
L2 正則化則是使用累加 W 平方值計算懲罰項,使用 L2 時 W(i) 的權重都不會為0,而是對每個元素進行不同比例的放縮。 

通過 Ridge 與 Lasso 對比 L1 與 L2 的區別

 1 def ridge_test():
 2     #測試數據
 3     line=np.linspace(-3,3,100)
 4     X,y=datasets.make_regression(n_features=100,noise=40,random_state=1)
 5     X_train,X_test,y_train,y_test=train_test_split(X,y)
 6     #嶺回歸模型
 7     ridge=Ridge()
 8     ridge.fit(X_train,y_train)
 9     #計算準確率
10     print('ridge\n  train data:{0}'.format(ridge.score(X_train,y_train)))
11     print('  test data:'.format(ridge.score(X_test,y_test)))
12     print('  used features: {0}'.format(sum(ridge.coef_!=0)))
13     print('  total features: {0}'.format(ridge.n_features_in_))
14     plt.plot(ridge.coef_,'^',color='g')
15 
16 
17 def lasso_test():
18     # 測試數據
19     X,y=datasets.make_regression(n_features=100,noise=40,random_state=1)
20     X_train,X_test,y_train,y_test=train_test_split(X,y)
21     # 把alpha設置為2
22     lasso=Lasso(alpha=2,max_iter=10000)
23     lasso.fit(X_train,y_train)
24     #輸出正確率
25     print('lasso\n  train data:{0}'.format(lasso.score(X_train,y_train)))
26     print('  test data:{0}'.format(lasso.score(X_test,y_test)))
27     #斜率與截距
28     print('  used features: {0}'.format(sum(lasso.coef_!=0)))
29     print('  total features: {0}'.format(lasso.n_features_in_))
30     plt.plot(lasso.coef_,'o',color='red')
31     plt.legend(['feature'])
32 
33 ridge_test()
34 lasso_test()
35 plt.show()

運行結果

可見 Ridge 模型中有效特徵仍為100,而 Lasso 的有效特徵僅為 48。在一般運算中,往往會優先使用 Ridge 模型。但當數據的特徵數太多時,Lasso 更顯出其優勢。  

 

4.5 SGDRegressor 模型

前面介紹的 LinearRegression、Ridge、Lasso 等幾個模型,都是使用最小二乘法和求逆運算來計算參數的,下面介紹的 SGDRegressor 模型是使用梯度下降法進行計算的。
在第二節已經使用基礎的 Python 程式碼實現最簡單的梯度下降法演算法,但其實在 sklearn 模型中早已準備了 SGDRegressor 模型支援梯度下降計算,運算時它比最小二乘法和求逆運算更加快節,當模型比較複雜,測試數據較大時,可以考慮使用 SGDRegressor 模型。
SGDRegressor 構造函數

1 class SGDRegressor(BaseSGDRegressor):
2     @_deprecate_positional_args
3     def __init__(self, loss="squared_loss", *, penalty="l2", alpha=0.0001,
4                  l1_ratio=0.15, fit_intercept=True, max_iter=1000, tol=1e-3,
5                  shuffle=True, verbose=0, epsilon=DEFAULT_EPSILON,
6                  random_state=None, learning_rate="invscaling", eta0=0.01,
7                  power_t=0.25, early_stopping=False, validation_fraction=0.1,
8                  n_iter_no_change=5, warm_start=False, average=False):
9      ......
  • loss:默認為「squared_loss」, 選擇要使用的損失函數。可用的回歸損失函數有:’squared_loss’、’huber’、epsilon_unsensitive’或’squared_epsilon_unsensitive’。
  • penalty:默認為 L2,用於指定懲罰項中使用的規範,可選參數為 L1 、 L2、elasticnet
  • alpha:默認值為 0.0001 乘以正則項的常數。值越大,正則化越強。當學習率設為「optimal」時,用於計算學習率。
  • l1_ratio:默認值為 0.15 彈性凈混合參數,0 <= l1_ratio <= 1. l1_ratio=0對應於L2懲罰,l1_ratio=1到 l1。僅當 penalty 為 elasticnet 時使用。
  • fit_intercept:bool類型,默認為True,表示是否計算截距 ( 即 y=wx+k 中的 k )。
  • max_iter:默認值為 1000,部分求解器需要通過迭代實現,這個參數指定了模型優化的最大迭代次數。
  • tol:默認值為1e-3,默認為小數點後 3 位,代表求解方法精度
  • shuffle:默認值為 True ,是否在每個epoch之後對訓練數據進行洗牌。
  • verbose:默認值為 0 詳細程度。
  • epsilon:當 loss 選擇 「huber」 時,它決定了一個閾值,在這個閾值下,預測值將被忽略。若選擇 「epsilon-insensitive」 表示若當前預測和正確標籤之間的差異小於此閾值,將被忽略。
  • random_state:默認值為None 隨機數種子,推薦設置一個任意整數,同一個隨機值,模型可以復現。
  • learning_rate:學習率,默認值為 』invscaling』 ,可選 constant、optimal、invscaling、adaptive

    1)『constant』: eta = eta0;

    2)『optimal: eta = 1.0 / (alpha * (t + t0)) ;

    3)『invscaling』: eta = eta0 / pow(t, power_t);

    4)『adaptive』: eta = eta0

  • eta0:默認值為0.01,初始學習速率。
  • power_t:默認值為0.25 反向縮放學習速率的指數
  • early_stopping:默認值為 False 驗證分數沒有提高時,是否使用提前停止終止培訓。如果設置為True,它將自動將訓練數據的分層部分作為驗證,並且當分數方法返回的驗證分數對 n_iter_no_change 連續時間段沒有至少提高tol時終止訓練。
  • validation_fraction:默認值為 0.1 作為早期停機驗證設置的培訓數據的比例。必須介於0和1之間。僅在「早停」為真時使用。
  • n_iter_no_change:默認值為 5 在提前停止之前沒有改進的迭代次數。
  • warm_start:bool, 默認值為 False 當設置為True時,將上一個調用的解決方案重用為fit作為初始化,否則,只需刪除以前的解決方案。
  • average:默認值為 False 當設置為True時,計算所有更新的 averaged SGD權重,並將結果存儲在coef_ 屬性中。如果設置為大於1的整數,則當看到的樣本總數達到平均值時,將開始平均。所以average=10將在看到10個樣本後開始平均。

SGDRegressor 模型與 Ridge 類似,默認使用 L2 正則化,所有特徵權重都不為 0 而是對每個元素進行不同比例的放縮。 
max_iter  默認值為1000, 它標誌著運行迭代的最大次數,有需要時可以增加 max_iter 提高準確率。
從第二節的例子可以理解,SGD默認使用 squared_loss 均方誤差作為損失函數,參數 eta0 =0.01 是初始的學習速率,當 learning_rate 為 constant 時,學習速率則恆定為 0.01,若使用默認值 invscaling,學習速率則通過公式 eta = eta0 / pow(t, power_t) 計算。
在下面的例子,把學習率設為恆定的 0.001,然後使用二個特徵的數據進行測試,最後使用三維圖形把計算出來的結果進行顯示

 1 def sgd_regressor_test():
 2     # 測試數據
 3     X,y=dataset.make_regression(n_samples=100,n_features=2)
 4     X_train, X_test, y_train, y_test = train_test_split(X, y)
 5     # SGD 模型
 6     sgd = SGDRegressor(learning_rate='constant',eta0=0.001,average=True)
 7     sgd.fit(X_train,y_train)
 8     # 準確率
 9     print('SGD:\n  train data:{0}\n  test data:{1}'
10         .format(sgd.score(X_train,y_train),sgd.score(X_test,y_test)))
11     ax=plt.axes(projection='3d')
12     print('  coef:{0}   intercept:{1}'.format(sgd.coef_,sgd.intercept_))
13     # 生成3維圖
14     ax.scatter3D(X[:,0],X[:,1],y,color='red')
15     # 生成格網矩陣
16     x0, x1 = np.meshgrid(X[:,0], X[:,1])
17     z = sgd.coef_[0] * x0 + sgd.coef_[1] * x1+sgd.intercept_
18     # 繪製3d
19     ax.plot_surface(x0, x1, z,color='white',alpha=0.01)
20     plt.show()

運行結果

 

對大數據進行測試時,相比起最小二乘法,使用梯度下降法效率會更高。下面的例子就是分別使用 LinearRegrssion 與 SGDRegressor 對 100 個特徵的 100000 條數據進行測試,結果 SGD 節省了大約 30%的時間。

 1 def linear_regression_test():
 2     # 測試數據
 3     X, y = dataset.make_regression(n_samples=100000,n_features=100, random_state=2)
 4     X_train, X_test, y_train, y_test = train_test_split(X, y)
 5     # 線性回歸模型訓練
 6     linear = LinearRegression()
 7     linear.fit(X_train, y_train)
 8     # 準確率
 9     print('Linear:\n  train data:{0}\n  test data:{1}'
10           .format(linear.score(X_train,y_train),linear.score(X_test,y_test)))
11 
12 def sgd_regressor_test():
13     # 測試數據
14     X,y=dataset.make_regression(n_samples=100000,n_features=100,random_state=2)
15     X_train, X_test, y_train, y_test = train_test_split(X, y)
16     # SGD 模型
17     sgd = SGDRegressor(learning_rate='constant',eta0=0.01)
18     sgd.fit(X_train,y_train)
19     # 準確率
20     print('SGD:\n  train data:{0}\n  test data:{1}'
21         .format(sgd.score(X_train,y_train),sgd.score(X_test,y_test)))
22 
23 print('  Utime:{0}'.format(timeit.timeit(stmt=linear_regression_test, number=1)))
24 print('  Utime:{0}'.format(timeit.timeit(stmt=sgd_regressor_test, number=1)))

運行結果

 

4.6 LogisticRegression 模型

上面幾個例子,都是講述線性回歸,下面將開始介紹線性分類的模型。LogisticRegression 模型雖然名稱里包含了 Regression ,但其實它是一個線性分類模型。
LogisticRegression 的構造函數

 1 class LogisticRegression(LinearClassifierMixin,
 2                          SparseCoefMixin,
 3                          BaseEstimator):
 4      @_deprecate_positional_args   
 5      def __init__(self, penalty='l2', *, dual=False, tol=1e-4, C=1.0,
 6                  fit_intercept=True, intercept_scaling=1, class_weight=None,
 7                  random_state=None, solver='lbfgs', max_iter=100,
 8                  multi_class='auto', verbose=0, warm_start=False, n_jobs=None,
 9                  l1_ratio=None):
10         ......

參數說明

  • penalty:默認為 L2,用於指定懲罰項中使用的規範,可選參數為 L1 和 L2。
  • dual:默認為False,對偶或原始方法。對偶方法只用在求解線性多核(liblinear)的L2懲罰項上。當樣本數量>樣本特徵的時候,dual通常設置為False。
  • tol:默認為小數點後 4 位,代表求解方法精度。
  • C:正則化係數 λ 的倒數,float類型,默認為1.0,越小的數值表示越強的正則化。
  • fit_intercept:bool類型,默認為True,表示是否計算截距 ( 即 y=wx+k 中的 k )。
  • intercept_scaling:float類型,默認為1,僅在 solver 為 」liblinear」,且 fit_intercept設置為True時有用 。
  • class_weight:用於標示分類模型中各種類型的權重,默認值為None,即不考慮權重。也可選擇balanced 讓類庫自己計算類型權重,此時類庫會根據訓練樣本量來計算權重,某種類型樣本量越多,則權重越低,樣本量越少,則權重越高。或者輸入類型的權重比,例如 class_weight={0:0.9, 1:0.1},此時類型0的權重為90%,而類型1的權重為10%。
  • random_state:隨機數種子,推薦設置一個任意整數,同一個隨機值,模型可以復現。
  • solver:求解優化演算法,默認值 lbfgs,可以根據數據類型選擇最合適的演算法。可選的演算法有:

    1)liblinear:使用了開源的liblinear庫實現,內部使用了坐標軸下降法來迭代優化損失函數。

    2)lbfgs:利用損失函數二階導數矩陣即海森矩陣來迭代優化損失函數。

    3)newton-cg:利用損失函數二階導數矩陣即海森矩陣來迭代優化損失函數。

    4)sag:即隨機平均梯度下降,是梯度下降法的變種,每次迭代僅僅用一部分的樣本來計算梯度,適合數據量較大時使用。

    5)saga:線性收斂的隨機優化演算法的的變重。

  • max_iter:默認值為 100,部分求解器需要通過迭代實現,這個參數指定了模型優化的最大迭代次數。
  • multi_class:分類方式選擇參數,str類型,可選參數為ovr和multinomial,默認為ovr。如果是二元邏輯回歸,ovr和multinomial並沒有任何區別,區別主要在多元邏輯回歸上。 
  • verbose:日誌冗長度,int類型。默認為0。就是不輸出訓練過程,1的時候偶爾輸出結果,大於1,對於每個子模型都輸出。
  • warm_start:默認值為 False,當設置為True時,重用之前調用的解決方案作為初始化,否則,只需要刪除前面的解決方案。
  • n_jobs:CPU 並行數,默認為None,代表1。若設置為 -1 的時候,則用所有 CPU 的內核運行程式。
  • l1_ratios : 默認為None,表示彈性網混合參數列表。

LogisticRegression 包含兩個最常用的參數 penalty 正則化規則、C 正則化程度,由構造函數可以看到,一般情況下 LogisticRegression 使用的是 L2 正則規範。此時決定正則化規範強度的參數是由 C 值決定,C 越大,對應的正則化越弱,數據集的擬合度會更高,C 越小,則模型的權重系統 w 更趨向於 0。

最典型的例子就是使用 make_forge 庫里的例子進行測試

 1 def logistic():
 2     #生成數據集
 3     X,y=datasets.make_forge()
 4     X_train,X_test,y_train,y_test=train_test_split(X,y)
 5     #對Logistic模型進行訓練
 6     logistic=LogisticRegression(C=1.0,random_state=1)
 7     logistic.fit(X_train,y_train)
 8     #輸入正確率
 9     print('logistic\n  train data:{0}'.format(logistic.score(X_train,y_train)))
10     print('  test data:{0}'.format(logistic.score(X_test,y_test)))
11     #輸出模型決策邊界
12     line = np.linspace(7, 13, 100)
13     plt.scatter(X[:,0], X[:,1],c=y,s=100)
14     plt.legend(['model','data'])
15     plt.show()

運行結果

 

 

此時,雖然測試數據集比較簡單,但仍可見類型為0和1的數據仍有產生一定的錯位。此時,嘗試將參數 C 調整為 100,得出的結果如下圖。
可見,當C值越大時,正則化規則越弱,數據集的擬合度會更高

 

 相反,若將 C 調整為 0.01 時,權重係數 w 會越趨向於0,它會讓公式適用於更多的數據點

 

 

LogisticRegression 除了可以通過 C 控制其正則化強度外,還可以通過 penalty 參數選擇正則化規則,penalty 默認值為 L2,但是當數據集的特徵較多時,可以嘗試通過設置 penalty 讓其使用 L1 正則化。但需要注意的是,當使用 L1 正則化時 solver 必須使用 liblinear 演算法,否則系統會報錯。

 1 def logistic(penalty,c):
 2     #生成數據集
 3     X,y=dataset.make_classification(n_samples=1000,n_features=100,random_state=1)
 4     X_train,X_test,y_train,y_test=train_test_split(X,y)
 5     #對Logistic模型進行訓練
 6     if(penalty=='l1'):
 7         solver='liblinear'
 8     else:
 9         solver='lbfgs'
10     logistic=LogisticRegression(penalty=penalty,C=c,solver=solver,random_state=1)
11     logistic.fit(X_train,y_train)
12     #輸入正確率
13     print('{0}\n  train data:{1}'.format(penalty,logistic.score(X_train,y_train)))
14     print('  test data:{0}'.format(logistic.score(X_test,y_test)))
15 
16     #輸出模型決策邊界
17     print('  total features:{0}'.format(logistic.n_features_in_))
18     print('  used features:{0}'.format(sum(logistic.coef_[0]!=0)))
19     plt.plot(logistic.coef_[0],'^')
20 logistic('l1',0.2)
21 logistic('l2',0.2)
22 plt.show()

運行結果

可以看到當使用 L1 規則時,系統只使用了 4 個特徵,此方法更適用於特徵數量比較多的數據集。
使用 L2 時 C 值為 0.2,系統使用了100個特徵,由特徵圖形可見,其權重係數 w 都接近於 0

此時可嘗試調節一下參數,對比一下運行 L2 ,輸入不同 C 值時的變化。

1 logistic("l2", 0.01)
2 logistic('l2',100)
3 plt.legend(['c=0.01','c=100'])
4 plt.show()

運行結果

 可見 C 值越小,w 就會越趨向於0,C 值越大,w 就會越分散且受正則化的約束越小

 

4.7 LinearSVC 線性支援向量機

上面的例子所看到的線性分類模型,大部分的例子都只二分類的,下面介紹一下可用於多分類的線性模型 LinearSVC 
LinearSVC 的構造函數

1 class LinearSVC(LinearClassifierMixin,
2                 SparseCoefMixin,
3                 BaseEstimator):
4     @_deprecate_positional_args
5     def __init__(self, penalty='l2', loss='squared_hinge', *, dual=True,
6                  tol=1e-4, C=1.0, multi_class='ovr', fit_intercept=True,
7                  intercept_scaling=1, class_weight=None, verbose=0,
8                  random_state=None, max_iter=1000):
9         ......

參數說明

  • penalty:默認為 L2,用於指定懲罰項中使用的規範,可選參數為 L1 和 L2。當使用 L1 準則時,參數 loss 必須為 squared_hinge , dual 必須為 False。
  • loss : 指定損失函數,默認值為 squared_hinge,可選擇 『hinge』 或 『squared_hinge』 。
  • dual:默認為False,對偶或原始方法。對偶方法只用在求解線性多核(liblinear)的L2懲罰項上。當樣本數量>樣本特徵的時候,dual通常設置為False。
  • tol:默認為小數點後 4 位,代表求解方法精度。
  • C:正則化係數 λ 的倒數,float類型,默認為1.0,越小的數值表示越強的正則化。
  • multi_class : 默認值為 ovr,可選擇 『ovr』 或 『crammer_singer』 ,用於確定多類策略。 「ovr」 訓練n_classes one-vs-rest 分類器,而 「crammer_singer」 優化所有類的聯合目標。  如果選擇「crammer_singer」,則將忽略選項 loss,penalty 和 dual 參數。
  • fit_intercept:bool類型,默認為True,表示是否計算截距 ( 即 y=wx+k 中的 k )。
  • intercept_scaling:float類型,默認為1,僅在 fit_intercept設置為True時有用 。
  • class_weight:用於標示分類模型中各種類型的權重,默認值為None,即不考慮權重。也可選擇balanced 讓類庫自己計算類型權重,此時類庫會根據訓練樣本量來計算權重,某種類型樣本量越多,則權重越低,樣本量越少,則權重越高。或者輸入類型的權重比,例如 class_weight={0:0.9, 1:0.1},此時類型0的權重為90%,而類型1的權重為10%。
  • verbose:日誌冗長度,int類型。默認為0。就是不輸出訓練過程,1的時候偶爾輸出結果,大於1,對於每個子模型都輸出
  • random_state:隨機數種子,推薦設置一個任意整數,同一個隨機值,模型可以復現。
  • max_iter:默認值為 10000,部分求解器需要通過迭代實現,這個參數指定了模型優化的最大迭代次數。

與LogisticRegression相似,LinearSVC 默認也是使用 L2 準則,同樣可以通過 C 控制其正則化強度,C 越大,對應的正則化越弱,數據集的擬合度會更高,C 越小,則模型的權重系統 w 更趨向於 0。
還可以通過 penalty 參數選擇正則化規則,當使用 L1 準則時,參數 loss 必須為 squared_hinge , dual 必須為 False。

當用於多個類別時,LinearSVC 會使用一對其餘的方式,每次學習都會使用一個二分類模型,然後把餘下的數據集再次進行二分類,不斷循環最後得出預測結果。

 1 def linearSVC():
 2     #生成數據集    
 3     X,y=mglearn.datasets.make_blobs(n_samples=200,random_state=23,centers=3)
 4     X_train,X_test,y_train,y_test=train_test_split(X,y)
 5     #使用 LinearSVC 模型,使用 L1 準則
 6     linearSVC=LinearSVC(penalty='l1',loss='squared_hinge', dual=False)
 7     linearSVC.fit(X_train,y_train)
 8     #輸出準確率
 9     print('LinearSVC\n  train data:{0}'.format(linearSVC.score(X_train,y_train)))
10     print('  test data:{0}'.format(linearSVC.score(X_test,y_test)))
11     #划出圖形分隔線
12     plt.scatter(X[:,0], X[:,1],c=y,s=100,cmap='autumn',marker='*')
13     n=np.linspace(-8,8,100)
14     value0=(-n*linearSVC.coef_[0][0] - linearSVC.intercept_[0]) / linearSVC.coef_[0][1]
15     value1=(-n*linearSVC.coef_[1][0] - linearSVC.intercept_[1]) / linearSVC.coef_[1][1]
16     value2=(-n*linearSVC.coef_[2][0] - linearSVC.intercept_[2]) / linearSVC.coef_[2][1]
17     plt.plot(n.reshape(-1, 1), value0,'-')
18     plt.plot(n.reshape(-1, 1), value1,'--')
19     plt.plot(n.reshape(-1, 1), value2,'+')
20     plt.legend(['class0','class1','class2'])
21     plt.show()

運行結果

 

 4.8 SGDClassifier 分類模型

SGDClassifier 與 SGDRegressor 類似,都是使用梯度下降法進行分類計算,由於SGD是以一次一個的方式獨立處理訓練實例,所以它能夠有效處理大型的數據集。SDGClassifier 默認也是使用 L2 準則,注意與SGDRegressor不同的是它的學習率 learning_rate 默認使用 optimal,此時 eta0 無效,若要使用 eta 0 需要提前修改 learning_rate 參數。

構造函數

 1 class SGDClassifier(BaseSGDClassifier):
 2     @_deprecate_positional_args
 3     def __init__(self, loss="hinge", *, penalty='l2', alpha=0.0001,
 4                  l1_ratio=0.15,
 5                  fit_intercept=True, max_iter=1000, tol=1e-3, shuffle=True,
 6                  verbose=0, epsilon=DEFAULT_EPSILON, n_jobs=None,
 7                  random_state=None, learning_rate="optimal", eta0=0.0,
 8                  power_t=0.5, early_stopping=False, validation_fraction=0.1,
 9                  n_iter_no_change=5, class_weight=None, warm_start=False,
10                  average=False):
11     ......
  • loss:默認為「hinge」, 選擇要使用的損失函數。可用的損失函數有:’hinge’, ‘log’, ‘modified_huber’,’squared_hinge’, ‘perceptron’。log 損失使邏輯回歸成為概率分類器。 ‘modified_huber’是另一個平滑的損失,它使異常值和概率估計具有一定的容忍度。「 squared_hinge」與hinge類似,但會受到二次懲罰。「perceptron」是感知器演算法使用的線性損失。
  • penalty:默認為 L2,用於指定懲罰項中使用的規範,可選參數為 L1 、 L2、elasticnet
  • alpha:默認值為 0.0001 乘以正則項的常數。值越大,正則化越強。當學習率設為「optimal」時,用於計算學習率。
  • l1_ratio:默認值為 0.15 彈性凈混合參數,0 <= l1_ratio <= 1. l1_ratio=0對應於L2懲罰,l1_ratio=1到 l1。僅當 penalty 為 elasticnet 時使用。
  • fit_intercept:bool類型,默認為True,表示是否計算截距 ( 即 y=wx+k 中的 k )。
  • max_iter:默認值為 1000,部分求解器需要通過迭代實現,這個參數指定了模型優化的最大迭代次數。
  • tol:默認值為1e-3,默認為小數點後 3 位,代表求解方法精度
  • shuffle:默認值為 True ,是否在每個epoch之後對訓練數據進行洗牌。
  • verbose:默認值為 0 詳細程度。
  • epsilon:默認值為0.1  loss 選擇 「huber」 時,它決定了一個閾值,在這個閾值下,預測值將被忽略。若選擇 「epsilon-insensitive」 表示若當前預測和正確標籤之間的差異小於此閾值,將被忽略。
  • n_job:CPU 並行數,默認為None,代表1。若設置為 -1 的時候,則用所有 CPU 的內核運行程式。
  • random_state:默認值為None 隨機數種子,推薦設置一個任意整數,同一個隨機值,模型可以復現。
  • learning_rate:學習率,默認值為 』optimal』 ,可選 constant、optimal、invscaling、adaptive

    1)『constant』: eta = eta0;

    2)『optimal: eta = 1.0 / (alpha * (t + t0)) ;

    3)『invscaling』: eta = eta0 / pow(t, power_t);

    4)『adaptive』: eta = eta0

  • eta0:默認值為0.0,初始學習速率。當 learning_rate 為 optimal 時,此值無效。
  • power_t:默認值為0.5 反向縮放學習速率的指數
  • early_stopping:默認值為 False 驗證分數沒有提高時,是否使用提前停止終止培訓。如果設置為True,它將自動將訓練數據的分層部分作為驗證,並且當分數方法返回的驗證分數對 n_iter_no_change 連續時間段沒有至少提高tol時終止訓練。
  • validation_fraction:默認值為 0.1 作為早期停機驗證設置的培訓數據的比例。必須介於0和1之間。僅在「早停」為真時使用。
  • n_iter_no_change:默認值為 5 在提前停止之前沒有改進的迭代次數。
  • class_weight:  類別關聯的權重,使用字典格式,默認值 {class_label: None} 也可選擇balanced 讓類庫自己計算類型權重,此時類庫會根據訓練樣本量來計算權重,某種類型樣本量越多,則權重越低,樣本量越少,則權重越高。或者輸入類型的權重比,例如 class_weight={0:0.9, 1:0.1},此時類型0的權重為90%,而類型1的權重為10%。
  • warm_start:bool, 默認值為 False 當設置為True時,將上一個調用的解決方案重用為fit作為初始化,否則,只需刪除以前的解決方案。
  • average:默認值為 False 當設置為True時,計算所有更新的 averaged SGD權重,並將結果存儲在coef_ 屬性中。如果設置為大於1的整數,則當看到的樣本總數達到平均值時,將開始平均。所以average=10將在看到10個樣本後開始平均。

下面嘗試用 SDGClassifier 區分MNIST的數字圖片,由於圖片有70000張,運行可能較慢,嘗試使用多核運算,把 n_job 設置為 -1,把學習率設置為固定值 0.01,可以看到準確率可以達到將近 90%

 1 def sgd_classifier_test():
 2     # 輸入入數據
 3     (X_train, y_train), (X_test, y_test)=keras.datasets.mnist.load_data()
 4     # 把28*28影像數據進行轉換
 5     X_train=X_train.reshape(-1,784)
 6     X_test=X_test.reshape(-1,784)
 7     #使用SGDClassfier模式,使用多核計算,學習率為0.01
 8     sgd_classifier=SGDClassifier(learning_rate='constant',eta0=0.01,n_jobs=-1)
 9     sgd_classifier.fit(X_train,y_train)
10     #查看準確率
11     print('SGDClassfier\n  train data:{0}\n  test data:{1}'.format(
12         sgd_classifier.score(X_train,y_train)
13         ,sgd_classifier.score(X_test,y_test)))
14     #查看測試數量第256幅圖
15     data=X_test[256].reshape(28,28)
16     plt.imshow(data,cmap='binary')
17     plt.show()
18     print('  test number is:{0}'.format(y_test[256]))

運行結果

 

 

4.9 多項式回歸與管道

4.9.1 PolynomialFeatures 多項式回歸模型 

到此以上所有的例子用的都是純線性的實例,然而現實場景中並非如此,比如說一個簡單的二元一次的方程  y = a*x*x+b*x+c 所構成的數據,就不可能通過直線進行進行分割。為此,sklearn 準備了多項式回歸模型 PolynomialFeatures 來解決此問題。前面提到普通的線性模型每個特徵都是符合單次方規則:y = w[0] * x[0] + w[1] * x[1] + w[2] * x[2] + w[3] * x[3] + …… + w[n] * x[n] + k,每個模型會有 n+1 個特徵。而PolynomialFeatures 模型每個特徵可以有多次方關係: 當 degree=N 時,每個特徵都會符合關係式 y = w[0]+w[1]*x+w[2]*x2+w[3]*x3+….+w[n]*xn。如此類推如果有多個特徵,則

構造函數

1 class PolynomialFeatures():
2     @_deprecate_positional_args
3     def __init__(self, degree=2, *, interaction_only=False, include_bias=True,
4                  order='C'):
5         self.degree = degree
6         self.interaction_only = interaction_only
7         self.include_bias = include_bias
8         self.order = order
9         ......
  • degree:默認值為2,控制多項式的次數;
  • interaction_only:默認為 False,如果指定為 True,那麼就不會有特徵自己和自己結合的項,組合的特徵中沒有 X12X1 * X23
  • include_bias:默認為 True 。如果為 True 的話,那麼結果中就會有 0 次冪項,即全為 1 這一列。
  • order:  默認為”C” ,可選擇 “F” 。「C」 表示是在密集情況(dense case)下的輸出array的順序,「F」 可以加快操作但可能使得subsequent estimators變慢。

用一個二元一次的方程  y = a*x*x+b*x+c 作為例子,首先生成100 個點的測試數據畫在圖上,然後使用多項式回歸 PolynomialFeatures 模式,把 degree 設置為默認值 2,最後使用 LinearRegression 模式根據斜率和變數畫出曲線。

 1 # 測試數據,根據 y=3*x*x+2*x+1 生成
 2 def getData():
 3     x=np.linspace(-3.5,3,100)
 4     y=3*x*x+2*x+1
 5     d=np.random.random(100)*2
 6     y=y-d
 7     plt.plot(x,y,'.')
 8     return [x,y]
 9 
10 def polynomial_test():
11     # 獲取測試數據
12     data=getData()
13     X=data[0].reshape(-1,1)
14     y=data[1]
15     # 生成多項式回歸模型
16     polynomial=PolynomialFeatures(degree=2)
17     X_poly=polynomial.fit_transform(X)
18     # 把運算過的數據放到 LinearRegression 進行運算
19     linearRegression=LinearRegression()
20     
21     linearRegression.fit(X_poly,y)
22     # 列印數據
23     print('PolynomialFeature:\n  coef:{0}\n  intercept:{1}\n  score:{2}'
24           .format(linearRegression.coef_,linearRegression.intercept_,
25                   linearRegression.score(X_poly,y)))
26     # 根據斜率和截距畫出圖
27     x=np.linspace(-3.5,3,100)
28     y=linearRegression.coef_[2]*x*x+linearRegression.coef_[1]*x+linearRegression.intercept_
29     plt.plot(x,y)
30     plt.legend(['data','model'])
31     plt.show()

運行結果

 

 

4.9.2 Pipeline 管道

正如上一章節的例子,如果繁雜的模型每次都需要經過多個步驟運算,那將是一個耗時費力的操作,有見及此,sklearn 中有一個 Pipeline 類可以按工作流程分步驟執行模型訓練。在上一章節數據先經過 PolynomialFeatures 模型訓練再進行 LinearRegression 訓練可寫為 pipe=Pipeline([(‘polynomial’,PolynomialFeatures()),(‘linearRegression’,LinearRegression())]) ,在 Pipeline 參數是以字典的形式輸入,先輸入名稱,再輸入類型。如果覺得每次都要為模型對象定義參數名稱比較麻煩,sklearn 還有一個更簡單的方法 make_pipeline ,使用此方法只需要直接把模型的類按順序輸入即可 pipe=make_pipeline(PolynomialFeatures(),LinearRegression()) ,事實上這種寫法也是管道最常用的方法。
使用 Pipeline 管道,可以把上一節的例子簡化成下面的程式碼,輸出完全一樣的結果。

 1 # 測試數據根據 y=3*x*x+2*x+1 生成
 2 def getData():
 3     x=np.linspace(-3.5,3,100)
 4     y=3*x*x+2*x+1
 5     d=np.random.random(100)*2
 6     y=y-d
 7     plt.plot(x,y,'.')
 8     return [x,y]
 9 
10 def polynomial_test():
11     # 獲取測試數據
12     data=getData()
13     X=data[0].reshape(-1,1)
14     y=data[1]
15     # 生成管道先執行 PolynomialFeatures 再執行 LinearRegression
16     pipe=make_pipeline(PolynomialFeatures(degree=2),LinearRegression())
17     # 訓練數據
18     pipe.fit(X,y)
19     # 獲取執行對象
20     linearRegression=pipe.steps[1][1]
21     # 列印數據
22     print('PolynomialFeature:\n  coef:{0}\n  intercept:{1}\n  score:{2}'
23           .format(linearRegression.coef_,linearRegression.intercept_,
24                   pipe.score(X,y)))
25     # 根據斜率和截距畫出圖
26     x=np.linspace(-3.5,3,100)
27     y=linearRegression.coef_[2]*x*x+linearRegression.coef_[1]*x+linearRegression.intercept_
28     plt.plot(x,y)
29     plt.legend(['data','model'])
30     plt.show()

由於篇幅關係,線性模型的使用就先介紹到這裡,關於 支援向量機、k近鄰、樸素貝葉斯分類 、決策樹 等章節將在 《 Python 機器學習實戰 —— 監督學習(下)》中詳細講述,敬請留意。

回到目錄

 

本篇總結

本文主要講述了機械學習的相關概念與基礎知識,監督學習的主要流程。對損失函數進行了基礎的介紹,並對常用的均方誤差與遞度下降法的計算過程進行演示,希望能幫助大家更好地理解。
在線性模型方法,對常用的 LogisticRegression , LinearSVC、SGDClassifier、 LinearRegression、Ridge、Lasso 、SGDRegressor  等線性模型進行了介紹。最後對非線性的 PolynomialFeatures 多項式回歸模型進行介紹,講解一管道 Pipe 的基本用法。
希望本篇文章對相關的開發人員有所幫助,由於時間倉促,錯漏之處敬請點評。

對 .Python  開發有興趣的朋友歡迎加入QQ群:790518786 共同探討 !
對 JAVA 開發有興趣的朋友歡迎加入QQ群:174850571 共同探討!
對 .NET  開發有興趣的朋友歡迎加入QQ群:162338858 共同探討 !