Sklearn學習筆記

機器學習

若是一個程序能夠在任務T上,隨着經驗E的增長,效果P也能夠隨之增長,則稱這個程序能夠從經驗中學習。html

機器學習分類

機器學習能夠分爲如下兩類node

監督學習(Supervised learning)ios

經過大量已知的輸入和輸出相配對的數據,讓計算機從中學習出規律,從而能針對一個新的輸入做出合理的輸出預測。git

好比,有大量不一樣特徵(面積、地理位置、朝向、開發商)的房子的價格數據,經過學習這些數據,能預測已知特徵的房子價格。這種稱爲迴歸學習(Regression learning)即輸出結果是一個具體的數值,它的預測模型是一個連續的函數。程序員

再好比咱們有大量的郵件,每一個郵件都已經標記是不是垃圾郵件。經過學習這些已標記的郵件數據,最後得出一個模型,這個模型對新的郵件,能準確地判斷出該郵件是不是垃圾郵件,這種稱爲分類學習(Classfication learning),即輸出結果是離散的,即要麼輸出1表示是垃圾郵件,要麼輸出0表示不是垃圾郵件。面試

無監督學習(Unsupervised learning)算法

經過學習大量的無標記數據,去分析出數據自己的內在特色和結構。數組

好比有大量的用戶購物的歷史記錄信息,從數據中去分析用戶的不一樣類別。針對這個問題,最終能劃分幾個類別?每一個類別有哪些特色?這個稱爲聚類(Clustering)。與分類學習不一樣,分類問題是已知哪幾種類別,聚類問題是不知道有哪些類別。答案是未知的,須要利用算法從數據裏挖掘出數據的特色和結構。性能優化

兩類機器學習的區別:網絡

有監督學習的訓練數據裏有已知的結果來監督,無監督學習的訓練數據裏沒有結果監督,不知道到底能分析出什麼樣的結果出來。

機器學習開發步驟

假設,咱們要開發一個房價評估系統,對一個已知特徵的房子價格進行評估預測。那麼須要如下幾個步驟

1. 數據採集和標記:須要大量不一樣特徵的房子和對應的價格信息,如面積、地理位置、朝向、價格等。另外還有房子所在地的學校狀況。這些數據叫作訓練樣本,或者數據集。房子的面積、地理位置等稱爲特徵。數據採集階段,須要收集儘可能多的特徵。特徵越全,數據越多,訓練出來的模型越準確。

經過這個過程能夠感覺到數據採集的成本是很高的。擁有海量數據那麼他自己的估值就會很高。

有時候爲了避稅,房子的評估價格會比房子的真實交易價格低不少。這時,就須要採集房子的實際成交價格,這一過程稱爲:數據標記。標記能夠是人工標記,好比說從房中介打聽價格,也能夠是自動標記,好比分析數據等。數據標記對監督學習方法是必須的。

2. 數據清洗:假設採集到的數據裏,關於房子面積,有按平方米計算的,也有按平方英寸計算的,這時須要對面積單位進行統一。這個過程稱爲數據清洗。包括去掉複雜的數據及噪聲數據,讓數據更具有結構化特徵。

3. 特徵選擇:假設採集到100個房子的特徵,經過逐個分析這些特徵,最終選擇了30個特徵做爲輸入。這個過程稱爲特徵選擇。特徵選擇的方法之一是人工選擇方法,即對逐個特徵進行人員分析,而後選擇合適的特徵集合。另一個方法是經過模型來自動完成的,例如PAC算法

4. 模型選擇:房價評估系統是屬於有監督學習的迴歸學習類型,能夠選擇最簡單的線性方程來模擬。選擇哪些模型,和問題領域、數據量大小、訓練時長、模型的準確度等多方面有關。

5. 模型訓練和測試:把數據集分紅訓練數據集測試數據集。通常按照8:2或7:3來劃分,而後用訓練數據集來訓練模型。訓練出參數後再使用測試數據集來測試模型的準確度。單獨分出一個測試數據集,是由於必須確保測試的準確性,即模型的準確性是要用沒見過的數據來測試,而不能用訓練的數據來測試。理論上更合理的數據集劃分方案是分紅3個,此外還要再加一個交叉驗證數據集

6. 模型性能評估和優化:模型出來後須要對機器學習的算法模型進行性能評估。性能評估包括,訓練時長是指

須要花多長時間來訓練這個模型。對一些海量數據的機器學習應用,可能須要一個月甚至更長的時間來訓練一個模型,這個時候算法的訓練性能就變得很重要了。

另外須要判斷數據集是否足夠多,通常而言,對於複雜特徵的系統,訓練數據集越大越好。而後還須要判斷模型的準確性,即對一個新的數據可否準確地進行預測。最後須要判斷模型可否知足應用場景的性能要求,若是不能知足要求,就須要優化,而後繼續對模型進行訓練和評估,或者更換爲其餘模型。

7. 模型使用:訓練出來的模型能夠把參數保存起來,下次使用時直接加載便可,通常來說,模型訓練須要的計算量是很大的,也須要較長時間的訓練,由於一個好的模型參數,須要對大型數據集進行訓練後才能獲得。而真正使用模型時,其計算量是比較小的,通常是直接把新樣本做爲輸入,而後調用模型便可獲得預測結果。

Scikit-learn簡介

scikit-lear是一個開源的Python語言機器學習工具包,涵蓋了幾乎全部主流機器學習算法的實現,而且提供了一致的調用接口。

給予Numpy和scipy等Python數值計算庫,提供了高效的算法實現。優勢有如下幾點

文檔齊全:官方文檔齊全,更新及時

接口易用:針對全部的算法提供了一致的接口調用規則

算法全面:涵蓋了主流機器學習任務的算法,包括迴歸算法、分類算法、聚類分析、數據降維處理等

scikit-learn不支持分佈式計算,不適用用來處理超大型數據。

數字識別示例

監督學習,數據是標記過的手寫數字圖片。經過採集足夠多的手寫數字樣本,選擇合適的模型,並使用採集到的數據進行模型訓練,,最後驗證識別程序的正確性。

1. 數據採集和標記

scikit-learn自帶了一些數據集,其中一個是手寫數字識別圖片的數據,使用如下代碼來加載並顯示出來

from sklearn import datasets
from matplotlib import pyplot as plt # 加載數據 digits = datasets.load_digits() # 能夠在notebook環境把數據的圖片用Matchplotlib顯示出來 images_and_labels = list(zip(digits.images, digits.target)) plt.figure(figsize=(8, 6), dpi=200) for index, (image, label) in enumerate(images_and_labels[:8]): plt.subplot(2, 4, index+1) plt.axis('off') plt.imshow(image, cmap=plt.cm.gray_r, interpolation='nearest') plt.title('Digit:%i' % label, fontsize=20)

圖片就是一個個手寫的數字

2. 特徵選擇

針對手寫的圖片,最直接的方法是使用圖片的每一個像素點做爲一個特徵。好比圖片是200*200的分辨率,那麼就有40000個特徵了,及特徵向量的長度是40000

實際上,scikit使用Numpy的array對象來表示數據,全部的圖片數據保存在digits.images裏,每一個元素都是一個88尺寸的灰階圖片。咱們在進行機器學習時,須要把數據保存爲樣本個數x特徵個數的array對象,針對手寫數字識別這個案例,scikit已經爲咱們轉換好了,就保存在digits.data數據裏,能夠經過digits.data.shape來查看它們的數據格式

print("shape of raw image data:{0}".format(digits.images.shape))  # (1797, 8, 8)
print("shape of data: {0}".format(digits.data.shape)) # (1797, 64)

總共有1797個訓練樣本,其中原始的數據是8*8的圖片,而尋來你的數據是把圖片的64個像素點都轉換爲特徵。 

3. 數據清洗

人們不可能在8*8這種分辨率上寫數字,在採集的時候是讓用戶在一個大圖片上寫數字。可是若是圖片是200*200,那麼一個訓練樣例就有40000個特徵,計算量將是巨大的。爲了減小計算量,也爲了模型的穩定性,須要把200*200圖片縮小爲8*8。這個過程就是數據清洗,把不適合用來作訓練的數據進行預處理,從而轉換爲適合的數據。

4. 模型選擇

不一樣的機器學習算法針對特定的機器學習應用有不一樣的效率,模型的選擇和驗證留到後面章節詳細介紹。這裏使用支持向量機(SVM) 

5. 模型訓練

首先須要把數據分爲訓練數據集和測試數據集。使用8:2的劃分。接着使用訓練數據集進行訓練,完成後clf就會包含咱們訓練出來的模型參數,使用這個模型對象進行預測便可。 

from sklearn import datasets, svm
from sklearn.model_selection import train_test_split # 加載數據 digits = datasets.load_digits() # 把數據分紅訓練數據集和測試數據集 Xtrain, Xtest, Ytrain, Ytest = train_test_split(digits.data, digits.target, test_size=0.20, random_state=2) # 接着使用訓練數據集來訓練模型 clf = svm.SVC(gamma=0.001, C=100.) clf.fit(Xtrain, Ytrain)

6. 模型測試 

咱們用訓練出來的模型測試一下準確度,能夠直接把預測結果和真實結果作比較。scikit提供了現成的比較方法

from sklearn import datasets, svm
from sklearn.model_selection import train_test_split from sklearn.metrics import accuracy_score from matplotlib import pyplot as plt # 加載數據 digits = datasets.load_digits() # 把數據分紅訓練數據集和測試數據集 Xtrain, Xtest, Ytrain, Ytest = train_test_split(digits.data, digits.target, test_size=0.20, random_state=2) # 接着使用訓練數據集來訓練模型 clf = svm.SVC(gamma=0.001, C=100.) clf.fit(Xtrain, Ytrain) # 評估模型的準確度 Ypred = clf.predict(Xtest) accuracy_score(Ytest, Ypred)

模型的準確率大概97.8%

除此以外,能夠直接把測試數據集裏的部分圖片顯示出來,而且在左下角顯示預測值,右下角顯示真實值。

% matplotlib inline

from sklearn import datasets, svm from sklearn.model_selection import train_test_split from sklearn.metrics import accuracy_score from matplotlib import pyplot as plt # 加載數據 digits = datasets.load_digits() # 把數據分紅訓練數據集和測試數據集 Xtrain, Xtest, Ytrain, Ytest = train_test_split(digits.data, digits.target, test_size=0.20, random_state=2) # 接着使用訓練數據集來訓練模型 clf = svm.SVC(gamma=0.001, C=100.) clf.fit(Xtrain, Ytrain) # 評估模型的準確度 Ypred = clf.predict(Xtest) accuracy_score(Ytest, Ypred)
clf.scroe(Xtest,Ytext) # 兩個同樣 # 查看預測狀況 fig, axes = plt.subplots(4, 4, figsize=(8, 8)) fig.subplots_adjust(hspace=0.1, wspace=0.1) for i, ax in enumerate(axes.flat): ax.imshow(Xtest[i].reshape(8, 8), cmap=plt.cm.gray_r, interpolation='nearest') ax.text(0.05, 0.05, str(Ypred[i]), fontsize=32, transform=ax.transAxes, color="green" if Ypred[i] == Ytest[i] else 'red') ax.text(0.8, 0.05, str(Ytest[i]), fontsize=32, transform=ax.transAxes, color="black") ax.set_xticks([]) ax.set_yticks([])

7. 模型保存與加載

當咱們對模型的準確度感到滿意後,就能夠把模型保存下來。這樣下次須要預測時,能夠直接加載模型進行預測,而不是從新訓練一遍模型。能夠使用下面的代碼來保存模型:

# 保存模型參數
from sklearn.externals import joblib
joblib.dump(clf, 'digits_svm.pk1')

在咱們使用的時候,就能夠直接加載成clf使用

from sklearn import datasets
from sklearn.externals import joblib from sklearn.metrics import accuracy_score from sklearn.model_selection import train_test_split # 加載數據 digits = datasets.load_digits() # 把數據分紅訓練數據集和測試數據集 Xtrain, Xtest, Ytrain, Ytest = train_test_split(digits.data, digits.target, test_size=0.20, random_state=2) clf = joblib.load('digits_svm.pk1') Ypred = clf.predict(Xtest) print(accuracy_score(Ytest, Ypred))

Scikit-Learn通常性原理和通用規則 

scikit包含大部分流行的監督學習算法(分類和迴歸)和無監督學習算法(聚類和數據降維)的實現

1. 評估模型對象

scikit裏的全部算法都以一個評估模型對象來建立對外提供接口。上面的svm.SVC()函數返回的就是一個支持向量機評估模型對象。 

建立評估模型對象時,能夠指定不一樣的參數,這個稱爲評估對象參數,評估對象參數直接影響評估模型訓練時的效率以及準確性。

學習機器學習算法的原理,其中一項很是重要的任務就是了解不一樣的機器學習算法有哪些可調的參數,這些參數表明什麼意思。

對機器學習算法的性能以及準確性有沒有什麼影響。由於在工程應用上,要從頭實現一個機器學習算法的可能性很低,除非是數值計算科學家。更多狀況下,是分析採集到的數據,根據數據特徵選擇合適的算法,而且調整算法的參數,從而實現算法效率和準確度之間的平衡。

2. 模型接口

scikit全部的評估模型對象都有fit()這個接口,是用來訓練模型的接口。針對有監督學習的機器學習,使用fit(X,y)來進行訓練,其中y是標記數據。針對無監督的機器學習算法,使用fit(X)來進行訓練,由於無監督機器學習算法的數據集是沒有標記的,不須要傳入y。

針對全部的監督學習算法,scikit模型對象提供了predict()接口,通過訓練的模型,能夠用這個接口來進行預測。針對分類問題,有些模型還提供了predict_proba()的接口,用來輸出一個待預測的數據,屬於各類類型的可能性,而predict()接口直接返回了可能性最高的那個類別。

幾乎全部的模型都提供了scroe()接口來評價一個模型的好壞,得分越高越好。不是全部的問題都只有準確度這個評價標準,好比異常檢測系統,一些產品不良率能夠控制到10%如下,這個時候最簡單的模型是無條件地所有預測爲合格,即無條件返回1,其準確率將達99.999%以上,但實際上這是一個很差的模型。評價這種模型,就須要使用查準率和召回率來衡量。

針對無監督的機器學習算法,scikit的模型對象也提供了predict()接口,用來對數據進行聚類分析,把新數據納入某個聚類裏。無監督學習算法還有transform()接口,這個接口用來進行轉換,好比PCA算法對數據進行降維處理時,把三維數據降爲二維數據,此時調用transform()算法便可把一個三維數據轉換爲對應的二維數據。

模型接口也是scikit工具包的最大優點之一,即把不一樣的算法抽象出來,對外提供一致的接口調用。

3. 模型檢驗

機器學習應用開發的一個很是重要的方面就是模型檢驗,須要檢測咱們訓練出來的模型,針對陌生數據其預測準確性如何。除了模型提供的score()接口外,在sklearn.metrics包的下面還有一系列用來檢測模型性能的方法。

4. 模型選擇

模型選擇是個很是重要的課題,根據要處理的問題性質,數據是否通過標記。數據規模多大,等等這些問題,能夠對模型有個初步的選擇。下面兩個圖是速查表,能夠快速選擇要給相對合適的模型。

機器學習理論基礎

過擬合和欠擬合

擬合:指已知某函數的若干離散函數值{f1,f2,…,fn},經過調整該函數中若干待定係數f(λ1, λ2,…,λn),使得該函數與已知點集的差異(最小二乘意義)最小。

離散:連續的對應(就是反義詞)就是離散 。離散就是不連續。好比人眼看到的圖片,就是連續的。計算機裏的照片就是離散的二進制比特流,圖像(灰度圖像)像素的灰度值在計算機裏是從0到255(其實是用二進制表示的),即0,1,2,3,...,255,0表明黑色,255表明白色,只有0到255的整數,沒有其餘整數,並且沒有兩個整數之間的小數,即不連續的,這就叫離散。

過擬合是指模型能很好地擬合訓練樣本, 但對新數據的預測準確性不好。欠擬合是指模型不能很好的擬合訓練樣本,且對新數據的預測準確性也很差。

n_dots = 20
x = np.linspace(0, 1, n_dots) y = np.sqrt(x) + 0.2 * np.random.rand(n_dots) - 0.1

訓練樣本是,其中r是[-0.1,0.1] 之間的一個隨機數。

而後分別用一階多項式、三階多項式、十階多項式3個模型來擬合這個數據集,獲得的結果以下

多項式(polynomial)是指由變量係數以及它們之間的加、減、乘、冪運算(非負整數次方)獲得的表達式。

圖中的點是咱們生成的20個訓練樣本。虛線爲實際的模型,實線是用訓練樣本擬合出來的模型

左側是欠擬合(underfitting),也稱高誤差(high bias),由於試圖用一條直線來擬合樣本數據。

右側是過擬合(overfitting),也稱高方差(high variance),用了十階多項式來擬合數據,雖然模型對現有的數據集擬合的很好,但對新數據預測偏差卻很大。

中間的模型較好的擬合了數據集,能夠看到虛線和實線基本重合。

成本函數

成本是衡量模型與訓練樣本符合程度的指標。是針對全部的訓練樣本,模型擬合出來的值與訓練樣本的真實值的偏差平均值。而成本函數就是成本與模型參數的函數關係。模型訓練的過程,就是找出合適的模型參數,使得成本函數的值最小。成本函數記爲J(θ),其中θ表示模型參數

用一階多項式來擬合數據,則獲得的模型是y=θ01x。此時[θ01] 構成的向量就是模型參數。訓練這個模型的目標,就是找出合適的模型參數[θ01] ,使得全部的點到這條直線上的距離最短。

不一樣的模型參數θ對應不一樣的直線,明顯能夠看出L2比L1更好地擬合數據集。根據成本函數的定義,咱們能夠容易地得出模型的成本函數公式

m是訓練樣本個數,20個點,h(x(i)) 就是模型對每一個樣本的預測值,y(i) 是每一個樣本的真實值。這個公式實際上就是線性迴歸算法的成本函數簡化表達式。

一個數據集可能有多個模型能夠用來擬合它,而一個模型有無窮多個模型參數,針對特定的數據集和特定的模型,只有一個模型參數能最好地擬合這個數據集,這就是模型和模型參數的關係

針對一個數據集,能夠選擇不少個模型來擬合數據,一旦選定了某個模型,就須要從這個模型的無窮多個參數裏找出一個最優的參數,使得成本函數的值最小。

多個模型之間怎麼評價好壞呢?在例子中,十階多項式針對訓練樣本的成本最小,由於它的預測曲線幾乎穿過了全部的點,訓練樣本到曲線的距離的平均值最小。可是十階多項式不是最好的模型,由於它過擬合了。

模型準確性

測試數據的成本,Jtest(θ) 是評估模型準確性的最直觀的指標,Jtest(θ) 值越小說明模型預測出來的值與實際值差別越小,對新數據的預測準確性就越好。須要特別注意,用來測試模型準確性的測試數據集,必須是模型沒見過的數據。

這就是須要把數據集分紅訓練數據集和測試數據集的緣由。通常是按照8:2或7:3來劃分,而後用訓練數據集來訓練模型,再用測試數據集來測試模型的準確性,根據模型的準確性來評價模型的性能。

針對上文介紹的線性迴歸算法,能夠用下面公式計算測試數據集的偏差,其中m是測試數據集的個數。

模型性能的不一樣表述方式

在scikit-learn裏,不使用成本函數來表達模型的性能,是使用分數來表達,這個分數老是在[0,1]之間,數值越大說明模型準確性越好。當模型訓練完成後,調用模型的score(X_test,y_test)便可算出模型的分數值,其中X_test和y_test是測試數據集樣本。

模型分數(準確性)與成本成反比。即分數越大,準確性越高,偏差越小,成本越低;反之分數越小、準確性越低、偏差越大、成本越高

交叉驗證數據集

一個更科學的方法是把數據集分紅3份,分別是訓練數據集,交叉驗證數據集和測試數據集。推薦比例是6:2:2

爲何須要交叉驗證數據集呢?以多項式模型選擇爲例,假設咱們用一階多項式、二階多項式、三階多項式...十階多項式的模型參數。這10個模型裏,那個模型更好呢?

這個時候會用測試數據集算出針對測試數據集的成本Jtest(θ) ,看那個模型的測試數據集成本最低(偏差平均值),咱們就選擇這個多項式來擬合數據,但實際上,這是有問題的。測試數據集的最主要功能是測試模型的準確性,須要確保模型沒見過這些數據。如今用測試數據集來選擇多項式的階數d,至關於把測試數據集提早讓模型見過了。這樣選擇出來的多項式階數d自己就是對訓練數據集最友好的一個,這樣模型的準確性測試就失去意義了。

爲了解決這個問題,把數據分紅3部分,隨機選擇60%的數據做爲訓練數據集,其成本記爲J(θ),隨機選擇20%的數據做爲交叉驗證數據集,其成本記爲Jcv(θ),剩下的20%做爲測試數據集,其成本記爲Jtest(θ) 

在模型選擇時,咱們使用訓練數據集來訓練算法參數,用交叉驗證數據集來驗證參數。選擇交叉驗證數據集的成本Jcv(θ)最小的多項式來做爲數據擬合模型,最後再用測試數據集來測試選擇出來的模型針對測試數據集的準確性。

在模型選擇中,使用交叉驗證數據集,因此篩選模型多項式階數d的過程當中,實際上並無使用測試數據集。保證了使用測試數據集來計算成本衡量模型的準確性,測試數據集沒有參與模型選擇的過程。

在實踐過程當中,不少人直接把數據集分紅訓練數據集和測試數據集,而沒有分出交叉驗證數據集。由於不少時候並不須要橫向去對比不一樣的模型。工程上,咱們最主要的工做不是選擇模型,而是獲取更多數據、分析數據、挖掘數據。

學習曲線

咱們能夠把Jtrain(θ)和Jcv(θ) 做爲縱座標,畫出與訓練數據集m的大小關係,這就是學習曲線。經過學習曲線,能夠直觀地觀察到模型的準確性與訓練數據集大小的關係。

若是數據集的大小爲m,則經過下面的流程便可畫出學習曲線:

  • 把數據集分紅訓練數據集和交叉驗證數據集。
  • 取訓練數據集的20%做爲訓練樣本,訓練出模型參數。
  • 使用交叉驗證數據集來計算訓練出來的模型的準確性。
  • 以訓練數據集的準確性,交叉驗證的準確性做爲縱座標,訓練數據集個數爲橫座標,在座標軸上畫出上述步驟計算出來的模型準確性。
  • 訓練數據集增長10%,跳到步驟3繼續執行,直到訓練數據集大小爲100%爲止

學習曲線要表達的內容是,當訓練數據集增長時,模型對訓練數據集擬合的準確性以及對交叉驗證數據集預測的準確性的變化規律。

經過一個例子來看看scikit-learn裏如何畫出模型的學習曲線,從而判斷模型的準確性以及優化方向。

生成一個在附近波動的點來做爲訓練樣本,不過此次要多生成一些點,由於要考慮當訓練樣本數量增長的時候,模型的準確性是怎麼變化的。

實例畫出學習曲線

畫出模擬的學習曲線,從而判斷模型的準確性及優化方向。

使用開頭的例子生成附近波動的點來做爲訓練樣本,要考慮當訓練樣本數量增長的時候,模型的準確性是怎麼變化的。

import matplotlib.pyplot as plt
import numpy as np

n_dots = 200
X = np.linspace(0, 1, n_dots)
y = np.sqrt(X) + 0.2 * np.random.rand(n_dots) - 0.1
# 須要用到n_sample * n_feature的矩陣,因此須要轉化爲矩陣
X = X.reshape(-1, 1)
y = y.reshape(-1, 1)

# 須要構造一個多項式模型。使用Pipeline來構造多項式模型,這個流水線裏能夠包含多個數據處理模型,
# 前一個模型處理完,轉到下一個模型處理
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import PolynomialFeatures
from sklearn.linear_model import LinearRegression


# 該函數生成一個多項式模型,其中degree表示多項式的階數。
def polynomial_model(degree=1):
    polynomial_features = PolynomialFeatures(degree=degree, include_bias=False)
    linear_regression = LinearRegression()
    # 先增長多項式階數,而後用線性迴歸算法來擬合數據
    pipeline = Pipeline([("polynomial_features", polynomial_features), ("linear_regression", linear_regression)])
    return pipeline  # 使用sklearn.model_selection.learning_curve函數來畫出學習曲線,


# 會自動把訓練樣本的數量按照預約的規則逐漸增長,而後畫出不一樣訓練樣本數量時的模型準確性。
# train_sizes參數指定訓練樣本數量的變化規則。train_sizes=np.linspace(. 1,1.0,5)
# 表示把訓練樣本數量從0.1~1分紅五等分,從序列中取出訓練樣本數量百分比,
# 逐個計算在當前訓練樣本數量狀況下訓練出來的模型準確性
from sklearn.model_selection import learning_curve
from sklearn.model_selection import ShuffleSplit


# 這個函數實現的功能就是畫出模型的學習曲線。當計算模型的準確性時,
# 是隨機從數據集中分配出訓練樣本和交叉驗證樣本,這樣會致使數據分佈不均勻。
# 一樣訓練樣本數量的模型,因爲隨機分配,致使每次計算出來的準確性都不同。
# 咱們在計算模型的準確性是屢次計算,並求準確性的平均值和方差。
# plt.fill_between()函數會把模型準確性的平均值的上下方差的空間裏用顏色填充。
# 而後用plt.plot()函數畫出模型準確性的平均值
def plot_learning_curve(estimator, title, X, y, ylim=None, cv=None, n_jobs=1, train_sizes=np.linspace(.1, 1.0, 5)):
    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="Trainning score")
        plt.plot(train_sizes, test_scores_mean, 'o-', color="g", label="Cross-validation score")
        plt.legend(loc="best")
        return plt


# 使用ploynomial_model函數構造出3個模型,分別是一階多項式、三階多項式、十階多項式
# 爲了讓學習曲線更平滑,計算10次交叉驗證數據集的分數
cv = ShuffleSplit(n_splits=10, test_size=0.2, random_state=0)
titles = ['Learning Curves(Under Fitting)', 'Learning Curves', 'Learning Curves(Over Fitting)']
degrees = [1, 3, 10]
plt.figure(figsize=(18, 4), dpi=200)
for i in range(len(degrees)):
    plt.subplot(1, 3, i + 1)
    plot_learning_curve(polynomial_model(degrees[i]), titles[i], X, y, ylim=(0.75, 1.01), cv=cv)

plt.show()

 

左圖:一階多項式,欠擬合;中圖:三階多項式,較好地擬合了數據集;右圖:十階多項式,過擬合。

虛線:針對訓練數據集計算出來的分數,即針對訓練數據集擬合的準確性

實線:針對交叉驗證數據集計算出來的分數,即針對交叉驗證數據集預測的準確性

從左圖咱們能夠觀察到,當模型欠擬合時,隨着訓練數據集的增長,交叉驗證數據集的準確性(實線)逐漸增大,逐漸和訓練數據集的準確性(虛線)靠近,但其整體水平比較低,收斂在0.88左右。其訓練數據集的準確性也比較低,收斂在0.90左右。這就是過擬合的表現。從這個關係能夠看出來,當發生高誤差時,增長訓練樣本數量不會對算法準確性有較大的改善。

從右圖能夠觀察到,當模型過擬合時,隨着訓練數據集的增長,交叉驗證數據集的準確性(實線)也在增長,逐漸和訓練數據集的準確性(虛線)靠近,但二者之間的間隙比較大。訓練數據集的準確性很高,收斂在0.95左右是三者中最高的(高方差),但其交叉驗證數據集的準確性值卻較低,最終收斂在0.91左右。

中圖,咱們選擇的三階多項式較好地擬合了數據,最終訓練數據集的準確性(虛線)和交叉驗證數據集的準確性(實線)靠的很近,最終交叉驗證數據集收斂在0.93附近,訓練數據集的準確性收斂在0.94附近。3個模型對比,這個模型的準確性最好。

當須要改進學習算法時,能夠畫出學習曲線,以便判斷算法是處在高誤差仍是高方差的問題

高誤差:訓練偏差很大,訓練偏差與測試偏差差距小,隨着樣本數據增多,訓練偏差增大。解決方法:

1.尋找更好的特徵(具備表明性的)

2.用更多的特徵(增大輸入向量的維度)

高方差:訓練偏差小,訓練偏差與測試偏差差距大,能夠經過增大樣本集合來減少差距。隨着樣本數據增多,測試偏差會減少。解決方案:

1.增大數據集合(使用更多的數據)

2.減小數據特徵(減少數據維度)

學習曲線是診斷模型算法準確性的一個很是重要的工具。

過擬合和欠擬合的特徵

過擬合:模型對訓練數據集的準確性比較高,其成本Jtrain(θ)比較低,對交叉驗證數據集的準確性比較低,其成本Jcv(θ)比較高。

欠擬合:模型對訓練數據集的準確性比較低,其成本Jtrain(θ)比較高,對交叉驗證數據集的準確性比較低,其成本Jcv(θ)比較高

一個好的機器學習算法應該是對訓練數據集準確性高、成本低,即較準確地擬合數據,同時對交叉驗證數據集準確性高、成本低、偏差小,即對未知數據有良好的預測性。

算法模型性能優化

若是咱們的機器學習算法不能很好地預測新數據時,須要先判斷這個算法模型是欠擬合仍是過擬合

若是是過擬合,能夠採起的措施以下

1. 獲取更多的訓練數據:從學習曲線的規律來看,更多的數據有助於改善過擬合問題

2. 減小輸入的特徵數量:好比,針對書寫識別系統,原來使用200*200的圖片,總共4萬個特徵。優化後,咱們能夠把圖片縮小爲10*10的圖片,總共100個特徵。這樣能夠大大減小模型的計算量,同時也減小模型的複雜度,改善過擬合問題。

若是是欠擬合,說明模型太簡單了,須要增長模型的複雜度。

1. 增長有價值的特徵:從新解讀並理解訓練數據,好比針對房產價格預測的機器學習任務,原來只根據房子面積來預測價格,結果模型出現了欠擬合。優化後,咱們增長其餘的特徵,好比房子的朝向、戶型、年代、房子旁邊的學校的質量、房子的開發商、房子周邊商業街個數、房子周邊公園個數等。

2. 增長多項式特徵:有時候,從已知數據裏挖掘出更多特徵不是件容易的事情,這個時候,能夠用純數學的方法,增長多項式特徵。好比原來的輸入特徵只有x1,x2,優化後能夠增長特徵,變成x1,x2,x1x2,x12,x22。這樣也能夠增長模型複雜度,從而改善欠擬合的問題。當用一階多項式擬合數據集時,使用的只有一個特徵,而最終咱們用三階多項式來擬合數據時,用的其實就是增長多項式特徵這個方法。

查準率和召回率

模型準確性並不能評價一個算法的好壞。好比針對癌症篩查算法,根據統計,普通腫瘤中癌症的機率是0.5%。有個機器學習算法,測試得出的準確率是99.2%,錯誤率是0.8%。這個算法究竟是好仍是壞呢?若是努力改進算法,最終獲得準確率是99.5%,錯誤率是0.5%,模型究竟是變好了仍是變壞了呢?

若是單純從模型準確性的指標上很難判斷究竟是變好了仍是變壞了。由於這個事情的先驗機率過低了,加入寫一個預測函數,老是返回0,即老是認爲不會得癌症,那麼咱們這個預測函數的準確率是99.5%,錯誤率是0.5%。由於整體而言,只有那0.5%真正得癌症的卻被咱們誤判了。

那麼怎麼來評價這類問題的模型好壞呢?引入了另外兩個概念,查準率(Precision)和召回率(Recall)。仍是以癌症篩選爲例:

在處理先驗機率低的問題時,咱們老是把機率較低的事件定義爲1,而且把y=1做爲Positive的預測結果。對老是返回0的超級簡單的腫瘤篩查預測函數,使用查準率和召回率來驗證模型性能時,會發現查準率和召回率都是0,這是由於他永遠沒法正確地預測出惡性腫瘤,即TruePositive永遠爲0.

在scikit-learn裏,評估模型性能的算法都在sklearn.metrics包裏。其中計算查找率和召回率的API分別爲sklearn.metrics.precision_score() 和 sklearn.metrics.recall_score()

F1 Score

如今有兩個指標,若是一個算法的查準率是0.5,召回率是0.4。另一個算法查準率是0.02,召回率是1.0;那麼兩個算法到底哪一個好呢?

咱們引入了F1 Score的概念

其中P是查找率,R是召回率。這樣就能夠用一個數值直接判斷哪一個算法性能更好。典型地,若是查找率或召回率有一個爲0,那麼F1 Score將會爲0。而理想的狀況下,查準率和召回率都爲1,則算出來的F1 Score爲1.

在scikit-learn裏,計算F1 Score的函數是sklean.metrics.f1_score()

K-近鄰算法

它是一個有監督的機器學習算法,k-近鄰算法也稱爲knn算法,能夠解決分類問題,也能夠解決迴歸問題。

k-近鄰算法的核心思想是未標記樣本的類別,由距離其最近的k個鄰居來投票決定。

假設,咱們有一個已經標記的數據集,即已經知道了數據集中每一個樣本所屬的類別。此時,有一個未標記的數據樣本,咱們的任務是預測出這個數據樣本所屬的類別。k-近鄰算法的原理是,計算待標記的數據樣本和數據集中每一個樣本的距離,取距離最近的k個樣本。待標記的數據樣本所屬的類別,就由這k個距離最近的樣本投票產生。

假設X_test爲待標記的數據樣本,X_train爲已標記的數據集,須要以下步驟

1. 遍歷X_train中的全部樣本,計算每一個樣本與X_test的距離,並把距離保存在Distance數組中

2. 對Distance數組進行排序,取距離最近的k個點,記爲X_knn

3. 在X_knn中統計每一個類別的個數,即class()在X_knn中有幾個樣本,class1在X_knn中有幾個樣本等

4. 待標記樣本的類別,就是在X_knn中樣本個數最多的那個類別

算法優缺點

優勢:準確性高,對異常值和噪聲有較高的容忍度。缺點:計算量較大,對內存的需求也較大。從算法原理能夠看出來,每次對一個未標記樣本進行分類時,都須要所有計算一遍距離。

算法參數 

其算法參數是k,參數選擇須要根據數據來決定。k值越大,模型的誤差越大,對噪聲數據越不敏感,當k值很大時,可能形成模型欠擬合; k值越小,模仿的方差就會越大,當k值過小,就會形成模型過擬合

算法的變種

k-近鄰算法有一些變種,其中之一就是能夠增長鄰居的權重。默認狀況下,計算距離時,都是使用相同權重。實際上,能夠針對不一樣的鄰居指定不一樣的距離權重,如距離越近權重越高。能夠經過指定算法的weights參數來實現

另一個變種是,使用必定半徑內的點取代距離最近的k個點。RadiusNeighborsClassifier類實現了這個算法的變種。當數據採樣不均勻時,該算法變種能夠取得更好的性能。

示例:使用k-近鄰算法進行分類 

使用KNN進行分類處理的是sklearn.neighbors.KNeighbors Classifier類

1. 生成已標記的數據集

from sklearn.datasets.samples_generator import make_blobs

centers = [[-2, 2], [2, 2], [0, 4]]
X, y = make_blobs(n_samples=60, centers=centers, random_state=0, cluster_std=0.60)

咱們使用sklearn.datasets.samples_generator包下的make_blobs()函數來生成數據集,上面代碼中,生成60個訓練樣本,這60個樣本分佈在以centers參數指定中心點周圍。cluster_std是標準差,用來指明生成的點分佈的鬆散程度。生成的訓練數據集放在變量X裏面,數據集的類別標記在y裏面。

把生成的點畫出來

plt.figure(figsize=(16, 10), dpi=144)
c = np.array(centers)
plt.scatter(X[:, 0], X[:, 1], c=y, s=100, cmap='cool')
plt.scatter(c[:, 0], c[:, 1], s=100, marker='^', c='orange')

這些點的分佈狀況在座標軸上一目瞭然,其中三角形的點即各個類別的中心節點

2. 使用KNeighborsClassifier來對算法進行訓練,咱們選擇的參數是k=5

from sklearn.neighbors import KNeighborsClassifier

k = 5
clf = KNeighborsClassifier(n_neighbors=k)
clf.fit(X, y)

3. 對一個新的樣本內進行預測

X_sample = [0, 2]
temp = np.array(X_sample).reshape((1, -1))
y_sample = clf.predict(temp)
neighbors = clf.kneighbors(temp, return_distance=False)

咱們要預測的樣本是[0,2],使用kneighbors方法,把這個樣本週圍距離最近的5個點取出來。取出來的點是訓練樣本X裏的索引,從0開始計算

4. 把待預測的樣本以及和其最近的5個點標記出來

plt.figure(figsize=(16, 10), dpi=144)
plt.scatter(X[:, 0], X[:, 1], c=y, s=100, cmap='cool') # 樣本
plt.scatter(c[:, 0], c[:, 1], s=100, marker='^', c='k') # 中心點
plt.scatter(X_sample[0], X_sample[1], marker="x", c=y_sample[0], s=100, cmap='cool')
# 待預測的點

for i in neighbors[0]:
plt.plot([X[i][0], X_sample[0]], [X[i][1], X_sample[1]], 'k--', linewidth=0.6)

示例:使用k-近鄰算法進行迴歸擬合

分類問題的預測值是離散的,能夠用k-近鄰算法在連續區間內對數值進行預測,進行迴歸擬合。 

使用k-近鄰算法進行迴歸擬合的算法是sklearn.neighbors.KNeighborsRegressor

1. 生成數據集,它在餘弦曲線的基礎上加入噪聲

n_dots = 40
X = 5 * np.random.rand(n_dots, 1)
y = np.cos(X).ravel()
y += 0.2 * np.random.rand(n_dots) - 0.1

2. 使用KNeighborsRegressor來訓練模型

from sklearn.neighbors import KNeighborsRegressor
k = 5
knn = KNeighborsRegressor(k)
knn.fit(X, y)

須要如何進行迴歸擬合呢?

一個方法是,在X軸上的指定區間內生成足夠多的點,針對這些足夠密集的點,使用訓練出來的模型進行預測,獲得預測值y_pred,而後在座標軸上,把全部的預測點連接起來,這樣就畫出了擬合曲線

針對足夠密集的點進行預測

T = np.linspace(0, 5, 500)[:, np.newaxis]
y_pred = knn.predict(T)
knn.score(X, y)

能夠用score() 方法計算擬合曲線針對訓練樣本的擬合準確性

3. 把這些預測點連起來,構成擬合曲線

plt.figure(figsize=(16, 10), dpi=144)
plt.scatter(X, y, c='g', label='data', s=100)
plt.plot(T, y_pred, c='k', label='prediction', lw=4)
plt.axis('tight')
plt.title('KNeighborsRegressor (k=%i)' % k)
plt.show()

最終生成的擬合曲線以及訓練樣本數據以下

實例:糖尿病預測

加載數據

data = pd.read_csv('pima-indians-diabetes/diabetes.csv')

總共有768個樣本,8個特徵,Outcome爲標記值,0表示沒有糖尿病,1表示有糖尿病

Pregnancies:懷孕次數

Glucose:血漿葡萄糖濃度,採用2小時口服葡萄糖耐量實驗測得

BloodPressure:舒張壓(毫米汞柱)

SkinThickness:肱三頭肌皮膚褶皺厚度(毫米)

Insulin:兩小時血清胰島素

BMI:身體質量指數,體重除以身高的平方

Diabetes Pedigree Function:糖尿病血統指數,糖尿病和家庭遺傳相關

Age:年齡

data.groupby("Outcome").size()

進一步觀察數據集中陽性和陰性樣本的個數

其中陰性樣本500例,陽性樣本268例。須要對數據集進行簡單處理,把8個特徵值分離出來,做爲訓練數據集,把Outcome列分離出來做爲目標值。而後把數據集劃分爲訓練數據集和測試數據集

X = data.iloc[:, 0:8]
Y = data.iloc[:, 8]
print('shape of X {}; shape of Y {}'.format(X.shape, Y.shape))

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)

模型比較

使用普通的k-均值算法、帶權重的k-均值算法以及制定半徑的k-均值算法分別對數據集進行擬合併計算評分

from sklearn.neighbors import KNeighborsClassifier, RadiusNeighborsClassifier

models = []
models.append(('KNN', KNeighborsClassifier(n_neighbors=2)))
models.append(('KNN with weights', KNeighborsClassifier(n_neighbors=2, weights='distance')))
models.append(('Radius Neighbors', RadiusNeighborsClassifier(n_neighbors=2, radius=500)))

results = []
for name, model in models:
model.fit(X_train, Y_train)
results.append((name, model.score(X_test, Y_test)))
for i in range(len(results)):
print('name:{}; score:{}'.format(results[i][0], results[i][1]))

輸出內容以下:

name:KNN; score:0.6753246753246753
name:KNN with weights; score:0.6233766233766234
name:Radius Neighbors; score:0.6428571428571429

權重算法,咱們選擇了距離越近,權重越高。

RadiusNeighborsClassifier模型的半徑,選擇了500。從輸出能夠看出,普通的k-均值算法性能仍是最好。

可是這個答案是不許確的,由於咱們的訓練樣本和測試樣本是隨機分配的,不一樣的訓練樣本和測試樣本組合可能致使計算出來的算法準確性是有差別的。

更準確的對比算法準確性,一個方法是屢次隨機分配訓練數據集和交叉驗證數據集,而後求模型準確性評分的平均值。scikit-learn提供了KFold和cross_val_score函數來處理這種問題

from sklearn.model_selection import KFold, cross_val_score

results = []
for name, model in models:
kfold = KFold(n_splits=10)
cv_result = cross_val_score(model, X, Y, cv=kfold)
results.append((name, cv_result))
for i in range(len(results)):
print('name:{},cross val score:{}'.format(results[i][0], results[i][1].mean()))

經過KFold把數據集分紅10份,其中1份會做爲交叉驗證數據集來計算模型準確性,剩餘的9份做爲訓練數據集。cross_val_score函數總共計算出10次不一樣訓練數據集和交叉驗證數據集組合獲得的模型準確性評分,最後求平均值。

輸出結果爲:

name:KNN,cross val score:0.7147641831852358
name:KNN with weights,cross val score:0.6770505809979495
name:Radius Neighbors,cross val score:0.6497265892002735

模型訓練及分析

普通的k-均值算法性能更優一些。使用普通的k-均值算法模型對數據集進行訓練,並查看對訓練樣本的擬合狀況以及對測試樣本的預測準確性狀況

knn = KNeighborsClassifier(n_neighbors=2)
knn.fit(X_train, Y_train)
train_score = knn.score(X_train, Y_train)
test_score = knn.score(X_test, Y_test)
print('train score:{}; test score:{}'.format(train_score, test_score))

輸出以下

train score:0.8257328990228013; test score:0.7012987012987013

從這個輸出中能夠看到兩個問題,1是對訓練樣本的擬合狀況不佳,評分才0.84多一些,說明算法模型太簡單了,沒法很好地擬合訓練樣本。2是模型的準確性欠佳,不到73%的預測準確性。

能夠進一步畫出學習曲線,證明結論

from sklearn.model_selection import ShuffleSplit
from LearningCurve import plot_learning_curve

knn = KNeighborsClassifier(n_neighbors=2)
cv = ShuffleSplit(n_splits=10, test_size=0.2, random_state=0)
plt.figure(figsize=(10, 6), dpi=200)
plot_learning_curve(knn, "Learn Curve for KNN Diabetes", X, Y, ylim=(0.0, 1.01), cv=cv)

從圖中能夠看出,訓練樣本評分較低,且測試樣本與訓練樣本距離較大,這是欠擬合現象。

k-均值算法沒有更好的措施來解決欠擬合問題,能夠試着用邏輯迴歸算法、支持向量機等來對比不一樣模型的準確性狀況

特徵選擇及數據可視化

只選擇2個與輸出值相關性最大的特徵,在二維平面上畫出輸入特徵與輸出值的關係

scikit-learn在sklearn.feature_selection包裏提供了豐富的特徵選擇方法。使用SelectKBest來選擇相關性最大的兩個特徵

from sklearn.feature_selection import SelectKBest
selector = SelectKBest(k=2)
X_new = selector.fit_transform(X,Y)
X_new[0:5]

把相關性最大的兩個特徵放在X_new變量裏,同時輸出了前5個數據樣本。SelectKBest會自動選擇出2個相關性最高的特徵

能夠將提取到的2個特徵,在二維座標上畫出全部的訓練樣本

selector = SelectKBest(k=2)
X_new = selector.fit_transform(X, Y)
X_new[0:5]
plt.figure(figsize=(10, 6), dpi=200)
plt.ylabel("BMI")
plt.xlabel("Glucose")
plt.scatter(X_new[Y == 0][:, 0], X_new[Y == 0][:, 1], c='r', s=20, marker='o')
plt.scatter(X_new[Y == 1][:, 0], X_new[Y == 1][:, 1], c='g', s=20, marker='^')
plt.show()

橫座標是血糖值,縱座標是BMI值,反映身體肥胖狀況。在中間數據集密集的區域,陽性樣本和陰性樣本幾乎重疊在一塊兒了。假設如今有一個待預測的樣本在中間密集區域,它的陽性鄰居多仍是陰性鄰居多呢?

如何提升k-近鄰算法的運算效率

根據算法原理,每次須要預測一個點時,都須要計算訓練數據集裏每一個點到這個點的距離,而後選出距離最近的k個點進行投票。當數據集很大時,這個算法成本很是高。針對N個樣本,D個特徵的數據集,其算法複雜度爲O(DN2) 

爲了解決這個問題,K-D Tree的數據結構被髮明出來。爲了不每次都從新計算一遍距離,算法會把距離信息保存在一棵樹裏,這樣在計算以前從樹裏查詢距離信息,儘可能避免從新計算。其基本原理是,若是A和B距離很遠,B和C距離很近,那麼A和C的距離也很遠。

有了這個信息,就能夠在合適的時候跳過距離遠的點。這樣優化後的算法複雜度能夠下降到O(DNlog(N))

相關性測試

經過一個簡單的例子來看假設檢驗問題,判斷假設的結論是否成立或成立的機率有多高。

假設,在一個城市隨機採樣到程序員和性別的關係的數據

假設,咱們的結論是程序員和性別無關,這個假設稱爲原假設。經過咱們隨機採樣觀測到的數據,原假設是否成立,或者說原假設成立的機率有多高?

卡方檢驗(chi-squared test) 是檢測假設成立與否的一個經常使用的工具。計算公式是

卡方檢測的值標記爲x2,Oi是觀測值,Ei是指望值。針對咱們的例子,若是原假設成立,即程序員職業和性別無關,那麼咱們指望的男程序員數量應該爲(14/489)*242=6.928

 根據卡方檢驗的工時,能夠算出卡方值爲:

算出卡方值後,怎麼判斷原假設成立的機率呢?涉及到自由度和卡方分佈的概念。

自由度是(r-1)*(c-1),其中r是行數,c是列數。針對咱們的問題,其自由度爲1。卡方分佈是指,若n個互相獨立的隨機變量均服從正態分佈,則這n個隨機變量的平方和構成一新的隨機變量,其分佈規律稱爲卡方分佈。

卡方分佈的密度函數和自由度相關,知道自由度和目標機率,就能求出卡方值。

針對咱們的問題,能夠查表獲得,自由度爲1的卡方分佈。在99%處的卡方值爲6.63。咱們計算出來的卡方值爲7.670。因爲7.67>6.63,固有99%的把握能夠推翻原假設。換個說話,若是原假設成立,即程序員職業和性別無關,那麼咱們隨機採樣到的數據出現的機率將低於1%。

卡方值的大小能夠反映變量與目標值的相關性,值越大,相關性越大。利用這個特性,SelectKBest() 函數就能夠計算不一樣特徵的卡方值來判斷特徵與輸出值的相關性大小,從而完成特徵選擇。

計算卡方值的函數是sklearn.feature_selection.chi2() 

計算F值校驗算法的函數是sklearn.feature_selection.f_classif()

線性迴歸算法

線性迴歸算法是使用線性方程對數據集進行擬合的算法,是一個很是常見的迴歸算法。

算法原理

考慮最簡單的單變量線性迴歸算法,只有一個輸入特徵。

預測函數 

針對數據集x和y,預測函數會根據輸入特徵x來計算輸出值h(x)。其輸入和輸出的函數關係以下。

這個方程表達的是一條直線。咱們須要構造一個hθ計算出來的值與真實值y的總體偏差最小。

構造hθ函數的關鍵就是找到合適θ0,θ1的值,稱爲模型參數。

假設咱們有以下數據集:

假設模型參數θ0=1,θ1=3,則模型函數爲hθ(x) = 1+3x。

針對數據集中的第一個樣本,輸入爲1,根據模型函數預測出來的值是4,與輸出值y是吻合的。

針對第二個樣本,輸入爲2,根據模型函數預測出來的值是7,與實際輸出值y相差1.

模型的求解過程,就是找出一組最合適的模型參數θ0,θ1,以便能最好地擬合數據集

當擬合成本最小時,就是找到了最好的擬合參數

成本函數

單變量線程迴歸算法的成本函數是

其中h(x(i))-y(i) 是預測值和實際值的差,故成本就是預測值和實際值的差的平方的平均值,之因此乘以1/2是爲了計算方便。

這個函數也稱爲均方差方程,有了成本函數,就能夠精確地測量模型對訓練樣本擬合的好壞程度。

梯度降低算法

有了預測函數,能夠精確地測量預測函數對訓練樣本的擬合狀況。咱們要怎麼求解模型參數θ0,θ1的值呢?

這個時候梯度降低算法就派上用場了。

咱們的任務是找到合適的θ0,θ1,使得成本函數J(θ0,θ1) 最小。爲了便於理解,咱們切換到三維空間來描述這個任務。在一個三維空間裏,以θ0做爲x軸,以θ1做爲y軸,以成本函數J(θ0,θ1)爲z軸,咱們須要找出當z軸上的值最小的時候所對應的x軸上的值和y軸上的值。

梯度降低算法的原理是,先隨機選擇一組θ0,θ1,同時選擇一個參數a做爲移動的步幅。而後讓x軸上的θ0和y軸上的θ1分別向特定的方向移動一小步,這個步幅的大小就由參數a指定。通過屢次迭代以後,x軸和y軸上的值決定的點就慢慢地靠近z軸上的最小值處 

這個是等高線圖,在描述的三維空間裏,視角在正上方,看到一圈一圈z軸值相同的點構成的線。

隨機選擇的點在x0處,通過屢次迭代後,慢慢地靠近圓心處,即z軸上最小值附近。

那麼怎麼知道往哪一個方向移動,才能靠近z軸上最小值附近呢?

答案是往成本函數逐漸變小的方向移動。使用偏導數,能夠表達成本函數逐漸變小的方向。

能夠簡單的把偏導數理解爲斜率,咱們要讓θj不停地迭代,由當前θj的值,根據J(θ)的偏導數函數,算出J(θ)在θj上的斜率,而後再乘以學習率a,就可讓θj 往前J(θ) 變小的方向邁一小步。

用數學來描述上述過程,梯度降低的公式爲:

公式中,下標j就是參數的序號,針對單變量線性迴歸,即0和1。a稱爲學習率,決定每次要移動的幅度大小,會乘以成本函數對參數θj的偏導數,以這個結果做爲參數移動的幅度。

若是幅度過小,意味着要計算不少次才能到達目的地,若是幅度太大,可能會直接跨過目的地,從而沒法收斂。

把成本函數J(θ)的定義代入上面的公式中,不難推導出梯度降低算法公式

公式中,a是學習率;m是訓練樣本的個數;h(x(i))-y(i)是模型預測值和真實值的偏差。須要注意的是,針對θ0和θ1分別求出了其迭代公式,在θ1的迭代公式裏,累加器中還須要乘以xi。

多變量線性迴歸算法

工程應用中每每不止一個輸入特徵。熟悉了單變量線性迴歸算法後,來講一下多變量線性迴歸算法

預測函數

多個輸入特徵。此時輸出y的值由n個輸入特徵x1,x2,x3,...,xn決定。那麼預測函數模型能夠改寫以下:

若x0爲常數1,用累加器運算符重寫上面的預測函數

 

θ0,θ1...θn統稱爲θ,是預測函數的參數。即一組θ值就決定了一個預測函數,記做hθ(x),簡寫爲h(x)

理論上,預測函數有無窮多個,咱們求解的目標就是找出一個最優的θ值

1. 向量形式的預測函數

根據向量乘法運算法則,成本函數能夠重寫爲:

此處,依然假設x0=1,x0稱爲模型偏置(bias)

寫成向量形式的預測函數,是由於在實現算法時,能夠使用矩陣計算來提升效率

2. 向量形式的訓練樣本

假設,輸入特徵個數是n,即x1,x2,x3...xn,咱們總共有m個訓練樣本,爲了書寫方便,假設x0=1。這樣訓練樣本能夠寫成矩陣的形式,即矩陣裏每一行都是一個訓練樣本,總共有m行,每行有n+1列

最後,把訓練樣本寫成一個矩陣,把預測函數的參數θ寫成列向量,其樣式以下

理解訓練樣本矩陣的關鍵在於理解這些上標和下標的含義。其中帶括號上標表示樣本序號,從1到m;

下標表示特徵序號,從0到n,其中x0爲常數1。

好比 x(i)j 表示第i個訓練樣本的第j個特徵的值。而x(i) 只有上標,則表示第i個訓練樣本所構成的列向量。

若是要一次性計算出全部訓練樣本的預測值hθ(X),能夠使用下面的矩陣運算公式

這個公式能夠看到矩陣形式表達的優點。在scikit-learn裏,訓練樣本就是用這個方式表達的,使用m*n維的矩陣來表達訓練樣本。

成本函數

多變量線性迴歸算法的成本函數:

其中,模型參數θ爲n+1維的向量,h(x(i))-y(i)是預測值和實際值的差。這個形式和單變量線性迴歸算法相似

成本函數有其對應的矩陣樣式的版本

其中,X爲m*(n+1)維的訓練樣本矩陣;上標T表示轉置矩陣;y表示由全部的訓練樣本的輸出y(i)構成的向量。

這個公式的優點是:沒有累加器,不須要循環,直接使用矩陣運算,就能夠一次性計算出針對特定的參數θ下模型的擬合成本

梯度降低算法

根據單變量線性迴歸算法裏的介紹,梯度降低的公式爲:

公式中,下標j是參數的序號,其值從0到n;a學習率。把成本函數代入上式,利用偏導數計算法則,推導出梯度降低算法的參數迭代工時

編寫機器學習算法的時候,通常步驟以下

1. 肯定學習率:a太大可能會使成本函數沒法收斂,過小則計算太多,機器學習算法效率就比較低。

2. 肯定參數起始點:讓全部的參數都爲1做爲起點,即θ0=1,θ1=1。這樣就獲得了預測函數。根據預測值和成本函數,就能夠算出在參數起始位置的成本。須要注意的是,參數起始點能夠根據實際狀況靈活選擇,以便讓機器學習算法的性能更高,如選擇比較靠近極點的位置。

3. 計算參數的下一組值:根據梯度降低參數迭代公式,分別同時算出新的θj的值。而後用新的θ值獲得新的預測函數hθ(x),再根據新的預測函數,代入成本函數就能夠算出新的成本。

4. 確認成本函數是否收斂:拿新的成本和舊的成本進行比較,當作本是否是變得愈來愈小。若是兩次成本之間的差別小於偏差範圍,即說明已經很是靠近最小成本了,就能夠近似地認爲咱們找到了最小成本。若是兩次成本之間的差別在偏差範圍以外,重複步驟3繼續計算下一組參數,直到找到最優解。

模型優化    

多項式與線型迴歸

當線型迴歸模型太簡單致使欠擬合時,咱們能夠增長特徵多項式來讓線型迴歸模型更好地擬合數據。好比有兩個特徵x1,x2,能夠增長兩特徵的乘積x1,x2做爲新特徵x3.還能夠增長x12 做爲另外一個新特徵x4。

線型迴歸是由類sklearn.linear_model.LinearRegression實現,多項式由類sklearn.preprocessing.PolynomialFeatures實現。那麼要怎麼添加多項式特徵呢?

須要用一個管道把兩個類串起來,即用sklearn.pipeline.Pipeline把兩個模型串起來

好比下面的函數能夠建立一個多項式擬合

from sklearn.preprocessing import PolynomialFeatures
from sklearn.linear_model import LinearRegression
from sklearn.pipeline import Pipeline


def polynomial_model(degree=1):
    polynomial_features = PolynomialFeatures(degree=degree, include_bias=False)
    linear_regression = LinearRegression()
    # 流水線,先增長多項式階數,而後再用線性迴歸算法來擬合數據
    pipeline = Pipeline([("polynomial_features", polynomial_features), ("linear_regression", linear_regression)])
    return pipeline

一個Pipeline 能夠包含多個處理節點,在scikit-learn裏除了最後一個節點外,其餘的節點都必須實現fit()和transform()方法,最後一個節點只須要實現fit()方法便可。當訓練樣本數據送進Pipeline裏進行處理時,會逐個調用節點的fit()和transform()方法,而後調用最後一個節點的fit()方法來擬合數據。

數據歸一化

當線型迴歸模型有多個輸入特徵時,特別是使用多項式添加特徵時,須要對數據進行歸一化處理。好比特徵x1的範圍在[1,4]之間,特徵x2的範圍在[1,2000]之間,這種狀況下,可讓x1除以4來做爲新特徵x1,同時讓x2除以2000來做爲新特徵x2.

該過程稱爲特徵縮放,能夠使用特徵縮放來對訓練樣本進行歸一化處理,處理後的特徵值範圍在[0,1]之間

歸一化處理的目的是讓算法收斂更快,提高模型擬合過程當中的計算效率。進行歸一化處理後,當有個新的樣本須要計算預測值時,也須要先進行歸一化處理,再經過模型來計算預測值,計算出來的預測值要再乘以歸一化處理的係數,這樣獲得的數據纔是真實的預測數據

使用LinearRegression進行線性迴歸時,能夠指定normalize=True來對數據進行歸一化處理。

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

首先生成200個在區間內的正弦函數點,並加上一些隨機的噪音

import numpy as np
import matplotlib.pyplot as plt

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輸入格式的數組,不然會報錯。

接着使用PolynomialFeatures 和 Pipeline建立一個多項式擬合模型

from sklearn.pipeline import Pipeline
from sklearn.linear_model import LinearRegression
from sklearn.preprocessing import PolynomialFeatures

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.10468112529330831; mean squared error:0.006269846500589429
degree:3; train score:0.19281625288786197; mean squared error:0.00565264324827472
degree:5; train score:0.664659360758771; mean squared error:0.002348363686782361
degree:10; train score:0.7606029623390451; mean squared error:0.001676478315417776

從輸出結果能夠看出,多項式的階數越高,擬合評分越高,均方差偏差越小,擬合效果越好。最後把不一樣模型的擬合效果在二維座標上畫出來,能夠清楚看到不一樣階數的多項式的擬合效果

使用SubplotParams調整子圖的豎直間隔,並用subplot把4個模型擬合狀況都畫在同一個圖形上。

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()

在[-2π,2π]區間內,10階多項式數據擬合很是好,可是其餘區間的曲線,就很通常了。

示例:測算房價

使用scikit-learn自帶的波士頓房價數據集來訓練模型,而後用模型來測算房價

輸入特徵

房價和那些因素有關?不少人可能對這個問題特別敏感,隨時能夠列出不少,如房子面積、地理位置、周邊教育資源、周邊商業資源、朝向、年限、小區狀況等。

在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

數據集有506個樣本,每一個樣本有13個特徵。整個訓練樣本放在一個50613的矩陣裏。

模型訓練

在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.clock()
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.clock() - start, train_score, cv_score))

除了統計模型的訓練時間外,還針對訓練樣本的準確性得分train_score進行了統計,還統計了模型針對測試樣本的得分cv_score

elaspe:0.063575; train_score:0.723941;cv_score:0.794958

模型優化

首先觀察一下數據,特徵數據的範圍相差比較大,最小的在10-3級別,而最大的在102級別,看來咱們須要把數據進行歸一化處理,歸一化處理最簡單的方式是,建立線性迴歸模型時增長normalize=True參數:

model = LinearRegression(normalize=True)

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

怎麼優化模型準確性呢?回到訓練分數上,能夠觀察到數據針對訓練樣本的評分比較低(train_score:0.7239)

即數據對訓練數據的擬合成本比較高,這是個典型的欠擬合現象。解決欠擬合一是挖掘更多輸入特徵,二是增長多項式特徵。

這個例子中,經過使用低成本的方案,即增長多項多特徵來看看可否優化模型的性能。增長多項式特徵,其實就是增長模型的複雜度

編寫建立多項式模型的函數

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

 接着,使用二階多項式來擬合數據

model = polynomial_model(degree=2)
start = time.clock()
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.clock() - start, train_score, cv_score))

輸出結果是:

elaspe:0.009711; train_score:0.930547; cv_score:0.860465 

訓練樣本分數和測試分數都提升了,看來模型確實獲得了優化。能夠把多項式改成三階看一下結果

elaspe:0.178630; train_score:1.000000; cv_score:-105.517016

改成三階多項式後,針對訓練樣本的分數達到了1,而針對測試樣本的分數倒是負數,說明這個模型過擬合了

學習曲線

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

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]
plt.figure(figsize=(18, 4), dpi=200)
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.clock() - start))
plt.show()

輸出以下圖

從圖中能夠看出,一階多項式欠擬合,由於針對訓練樣本的分數比較低。三階多項式過擬合,由於針對訓練樣本的分數達到1,卻看不到針對交叉驗證數據集的分數。針對二階多項式擬合的狀況,雖然比一階多項式效果好,但從圖中能夠明顯看出來,針對訓練數據集的分數和針對交叉驗證數據集的分數之間的間隙比較大,這特徵說明訓練樣本數量不夠,咱們應該去採集更多的數據,以便提升模型的準確性

隨機梯度降低算法

上面介紹的梯度降低迭代公式稱爲批量梯度降低算法(Batch Gradient Descent),用它對參數進行一次迭代運算,須要遍歷全部的訓練數據集。當訓練數據集比較大時,其算法的效率會很低。

考慮另一個算法

這個算法的關鍵點是把累加器去掉,不去遍歷全部的訓練數據集,而是改爲每次隨機從訓練數據集取一個數據進行參數迭代計算,這就是隨機梯度降低算法(stochastic gradient descent)。隨機梯度降低算法能夠大大提升模型訓練效率

從數學上證實批量梯度降低算法和隨機梯度降低算法的等價性涉及到複雜的數學知識。這裏有個直觀的解釋能夠幫助讀者理解二者的等價性。回到成本函數

這裏累加後除以2是爲了方便計算,那麼除以m是什麼意思呢?

答案是平均值,即全部訓練數據集上的點到預測函數的距離的平均值。再回到隨機選取訓練數據集裏的一個數據這個作法來看,若是計算次數足夠多,而且是真正隨機,那麼隨機選取出來的這組數據從機率的角度來看,和平均值是至關的。

打個比方,存錢罐裏有1角的硬幣10個,5角的2個,1元的1個,總計3元、13個硬幣。隨機從裏面取1000次,把每次取出來的硬幣幣值記錄下來,而後將硬幣放回存錢罐。這樣最後去算着1000次取出來的錢的平均值(1000次取出來的幣值總和除以1000)和存錢罐裏每一個硬幣的平均值應該是近似相等的。

標準方程

梯度降低算法經過不停地迭代,從而不停地逼近成本函數的最小值來求解模型參數。另一個方法是從計算成本函數的微分,令微分算子爲0,求解這個方程,便可獲得線性迴歸的解。

線性迴歸算法的成本函數:

成本函數的斜率爲數的點,即爲模型參數的解。即令求解這個方程最終能夠獲得的模型參數:

這就是咱們的標準方程。經過矩陣運算,直接從訓練樣本里求出參數θ的值。其中X爲訓練樣本的矩陣形式,是m*n的矩陣,y是訓練樣本的結果數據,是個m維列向量。

邏輯迴歸算法

邏輯迴歸算法是用來解決分類問題的算法。

算法原理

假設有一場足球賽,擁有兩支球隊的全部出場球員信息,歷史交鋒成績、比賽時間、主客場、裁判和天氣等信息,根據這些信息預測球隊的輸贏。 

假設比賽結果記爲y,贏球標記爲1,輸出標記爲0,這個就是典型的二元分類,能夠用邏輯迴歸算法來解決。

預測函數

須要找出一個預測函數模型,使其值輸出在[0,1]之間。而後選擇一個基準值,如0.5,若是算出來的預測值大於0.5,就認爲其預測值爲1,反之則其預測值爲0

做爲預測函數,其中e是天然對數的底數。函數g(z) 稱爲Sigmoid函數,也稱爲Logistic函數。以z爲橫座標,以g(z)爲縱座標,畫出的圖形以下

從圖中能夠看出,當z=0時,g(z)=0.5,當z>0時,g(z)>0.5。當z愈來愈大時g(z)無限接近於1。當z<0時,g(z)<0.5,當z愈來愈小時,g(z)無限接近於0。這正是咱們想要的針對二元分類算法的預測函數。

結合線性迴歸函數的預測函數hθ(x) = θTx,假設令z(x)=θTx,則邏輯迴歸算法的預測函數以下:

下面來解讀預測函數

hθ(x) 表示在輸入值爲x,參數爲θ的前提條件下y=1的機率。用機率論的公式能夠寫成:

上面的機率公式能夠讀做:在輸入x及參數θ條件下y=1的機率。這是個條件機率公式。

由機率論的知識能夠推導出

對二元分類法來講,這是個非黑即白的世界

斷定邊界

邏輯迴歸算法預測函數由下面兩個公式給出的

假定y=1的斷定條件是hθ(x) >= 0.5,y=0的斷定條件是hθ(x)<0.5,則能夠推導出y=1的斷定條件就是θTx>=0,y=0的斷定條件就是θTx<0。因此,θTx=0便是咱們的斷定邊界

假定有兩個變量x1,x2,其邏輯迴歸預測函數是hθ(x)=g(θ0+θ1x1+θ2x2)

假設給定參數

那麼能夠獲得斷定邊界-3+x1+x2=0,即x1+x2=3,若是以x1爲橫座標,x2爲縱座標,則這個函數畫出來的就是一個經過(0,3)和(3,0) 兩個點的斜線。這條線就是斷定邊界

其中,直線左下角爲y=0,直線右上解爲y=1。橫座標爲x1,縱座標爲x2

若是預測函數是多項式 且給定

則能夠獲得斷定邊界函數x12+x22=1。仍是以x1爲橫座標,x2爲縱座標,則這是一個半徑爲1的圓。圓內部是y=0,圓外部是y=1

成本函數 

爲了容易求出成本函數的最小值,咱們分紅y=1和y=0兩種狀況來分別考慮其預測值與真實值的偏差。咱們先考慮最簡單的狀況,即計算某個樣本x,y其預測值與真實值的偏差,咱們選擇的成本公式以下

其中,hθ(x) 表示預測爲1的機率,log(x)爲天然對數。咱們以hθ(x)爲橫座標,以成本值Gost(hθ(x),y)爲縱座標,把上述兩個公式分別畫在二維平面上

成本是預測值與真實值的差別。當差別越大時,成本越大,模型收到的懲罰也越嚴重

在左圖中,當y=1時,隨着hθ(x)的值(預測爲1的機率)愈來愈大,預測值愈來愈接近真實值,其成本愈來愈小。當y=0時,隨着hθ(x)的值(預測爲1的機率)愈來愈大,預測值愈來愈偏離真實值,其成本愈來愈大。

邏輯迴歸模型的預測函數是Sigmoid函數,而Sigmoid函數裏有e的n次方運算,天然對數恰好是其逆運算,好比log(en)=n。選擇天然對數,最終會推導出形式優美的邏輯迴歸模型參數的迭代函數,而不須要去涉及對數運算和指數函數運算。這就是選擇天然對數函數來做爲成本函數的緣由。

把輸入值x從負無窮大到正無窮大映射到[0,1]區間的模型不少,邏輯迴歸算法不必定要選擇Sigmoid函數做爲預測函數,可是若是不選擇它,就須要從新選擇性質接近的成本函數。

成本函數爲

梯度降低算法

和線型迴歸相似,使用梯度降低算法來求解邏輯迴歸模型參數。根據梯度降低算法的定義,能夠得出

這裏的關鍵是求解成本函數的偏導數。最終推導出來的梯度降低算法公式爲:

多元分類

邏輯迴歸模型能夠解決二元分類問題,即y={0,1},能不能用來解決多元分類問題呢?答案是確定的

針對多元分類問題,y={0,1,2,...,n},總共有n+1個類別 

首先把問題轉化爲二元分類問題,即y=0是一個類別,y={1,2,3...,n}做爲另一個類別,而後計算這兩個類別的機率。接着把y=1做爲一個類別,把y={0,2,...n}做爲另一個類別,再計算這兩個類別的機率。

總共須要n+1個預測函數:

預測出來的機率最高的那個類別,就是樣本所屬的類別

正則化

過擬合是指模型很好地擬合了訓練樣本,但對新數據預測的準確性不好,這是由於模型太複雜了。解決辦法是減小輸入特徵的個數,或者獲取更多的訓練樣本。

還有一種解決過擬合的方法:正則化

1. 保留全部的特徵,減小特徵的權重θj的值。確保全部特徵對預測值都有少許的貢獻

2. 當每一個特徵xi對預測值y都有少許的貢獻時,這樣的模型能夠良好地工做,這就是正則化的目的,能夠用它來解決特徵過多時的過擬合問題

線性迴歸模型正則化

先來看看線性迴歸模型的成本函數是如何正則化的

公式中前半部分就是線性迴歸模型的成本函數,也稱爲預測值與實際值的偏差。後半部分爲加入的正則項。

其中λ的值由兩個目的,即要維持對訓練樣本的擬合,又要避免對訓練樣本的過擬合。若是λ值太大,則能確保不出現過擬合,但可能會致使對現有訓練樣本出現欠擬合。

從數學角度來看,成本函數增長了一個正則項後,成本函數再也不惟一地由預測值與真實值的偏差所決定,還和參數θ的大小有關。有了這個限制,要實現成本函數最小的目的,θ就不能隨便取值了。好比某個比較大的θ值可能會讓預測值與真實值的偏差(hθ(x(i))-y(i))2值很小,但會致使θ很大,最終的結果是成本函數太大。

這樣經過調節參數λ,就能夠控制正則項的權重,從而避免線性迴歸算法過擬合

利用正則化的成本函數,能夠推導出正則化後的參數迭代函數

因子在每次迭代式都將把θj收縮一點點。由於a和λ是正數,而m是訓練樣例的個數,是個比較大的正整數。

邏輯迴歸模型正則化

使用相同的思路,能夠對邏輯迴歸模型的成本函數進行正則化,其方法也是在原來的成本函數基礎上加上正則項

正則化後的參數迭代公式爲:

上式中j>=1,由於θ0沒有參與正則化。邏輯迴歸和線型迴歸的參數迭代算法看起來形式是同樣的,但其實它們的算法不同,由於兩個式子的預測函數hθ(x)不同。針對線型迴歸hθ(x)=θTx,而針對邏輯迴歸

算法參數

在scikit-learn裏,邏輯迴歸模型由類sklearn.linear_model.LogisticRegression實現

1. 正則項權重

上面介紹的正則項權重λ,在LogisticRegression裏有個參數C與此對應,但成反比。即C值越大,正則項的權重越小,模型容易出現過擬合;C值越小,正則項權重越大,模型容易出現欠擬合。

2. L1/L2範數

建立邏輯迴歸模型時,有個參數penalty,其取值有'11'或'12',這是什麼意思呢?

這個實際上就是指定咱們前面介紹的正則項的形式。在成本函數裏添加的正則項爲

這個其實是個L2正則項,即把L2範數做爲正則項。也能夠添加L1範數來做爲正則項。

L1範數做爲正則項,會讓模型參數θ稀疏化,即讓模型參數向量裏爲0的元素儘可能多。而L2範數做爲正則項,則是讓模型參數儘可能小,但不會爲0,儘可能讓每一個特徵對預測值都有一些小的貢獻。

首先了解一下L1範數和L2範數的概念,他們都是針對向量的一種運算。爲了簡單起見,假設模型只有兩個參數,它們構成一個二維向量θ=[θ1,θ2],則L1範數爲

即L1範數是向量裏元素的絕對值之和,L2範數爲元素的平方和的開方根。

定義清楚了以後,來介紹它們做爲正則項的效果有什麼不一樣。梯度降低算法在參數迭代的過程當中,其實是在成本函數的等高線上跳躍,並最終收斂在偏差最小的點上。

正則項的本質是懲罰。模型在訓練的過程當中,若是沒有遵照正則項所表達的規則,那麼其成本會變大,即受到了懲罰,從而往正則項所表達的規則處收斂。成本函數在這兩項規則的綜合做用下,正則化後的模型參數應該收斂在偏差等值線與正則項等值線相切的點上。

把L1範數和L2範數在二維座標軸上畫出其圖形,便可直觀地看到它們所表達的規則的不一樣

def L1(x):
    return 1 - np.abs(x)

def L2(x):
    return np.sqrt(1 - np.power(x, 2))

def contour(v, x):
    return 5 - np.sqrt(v - np.power(x + 2, 2))    # 4x1^2 + 9x2^2 = v

def format_spines(title):    
    ax = plt.gca()                                  # gca 表明當前座標軸,即 'get current axis'
    ax.spines['right'].set_color('none')            # 隱藏座標軸
    ax.spines['top'].set_color('none')
    ax.xaxis.set_ticks_position('bottom')           # 設置刻度顯示位置
    ax.spines['bottom'].set_position(('data',0))    # 設置下方座標軸位置
    ax.yaxis.set_ticks_position('left')
    ax.spines['left'].set_position(('data',0))      # 設置左側座標軸位置

    plt.title(title)
    plt.xlim(-4, 4)
    plt.ylim(-4, 4)

plt.figure(figsize=(8.4, 4), dpi=144)

x = np.linspace(-1, 1, 100)
cx = np.linspace(-3, 1, 100)

plt.subplot(1, 2, 1)
format_spines('L1 norm')
plt.plot(x, L1(x), 'r-', x, -L1(x), 'r-')
plt.plot(cx, contour(20, cx), 'r--', cx, contour(15, cx), 'r--', cx, contour(10, cx), 'r--')

plt.subplot(1, 2, 2)
format_spines('L2 norm')
plt.plot(x, L2(x), 'b-', x, -L2(x), 'b-')
plt.plot(cx, contour(19, cx), 'b--', cx, contour(15, cx), 'b--', cx, contour(10, cx), 'b--')

 

左圖中,用的是L1範數來做爲正則項,L1範數表示的是元素的絕對值之和,圖中L1範數的值爲1,其在θ1,θ2座標軸上的等值線是個正方形,虛線表示的是偏差等值線。能夠看到,偏差等值線和L1範數等值線相切的點位於座標軸上。

右圖中,用的是L2範數來做爲正則項,L2範數的值爲1,在θ1,θ2座標軸上,它的等值線是一個圓。和模型偏差等值線相切的點,通常不在座標軸上。

L1範數做爲正則項,會讓模型參數稀疏化,而L2範數做爲正則項,則會使模型的特徵對預測值都有少許的貢獻,避免模型過擬合

做爲推論,L1範數做爲正則項,有如下幾個用途

特徵選擇

會讓模型參數向量裏的元素爲0的點儘可能多。所以能夠排除掉那些對預測值沒有什麼影響的特徵。從而簡化問題,因此L1範數解決過擬合的措施,其實是減小特徵數量

可解釋性

模型參數向量稀疏化後,只會留下那些對預測值有重要影響的特徵。這樣咱們就容易解釋模型的因果關係。針對某種癌症的篩查,若是有100個特徵,那麼咱們無從解釋到底那些特徵對陽性呈關鍵做用。稀疏化後,只留下幾個關鍵的特徵,就容易看到因果關係

L1範數做爲正則項,更多的是一個分析工具,而適合用來對模型求解。由於它會把不重要的特徵直接去除。大部分狀況下,咱們解決過擬合問題,仍是選擇L2範數做爲正則項,這也是scikit-learn裏的默認值

實例:乳腺癌檢測

使用邏輯迴歸算法解決乳腺癌檢測問題。

首先採集腫瘤病竈造影圖片,而後對圖片進行分析,從圖片中提取特徵,再根據特徵來訓練模型。

最終使用模型來檢測新採集到的腫瘤病竈造影,以便判斷腫瘤是良性的仍是惡性的。這是典型的二元分類問題

數據採集及特徵提取 

直接加載scikit-learn自帶的乳腺癌數據集

from sklearn.datasets import load_breast_cancer

cancer = load_breast_cancer()
X = cancer.data
y = cancer.target
print('data shape:{0}; no.positive:{1}; no.negative:{2}'
      .format(X.shape, y[y == 1].shape[0], y[y == 0].shape[0]))
print(cancer.data[0])

數據集總共有569個樣本,其中陽性357個,212個陰性。每一個樣本有30個特徵

這個數據集總共提取了如下10個關鍵屬性

radius:半徑,即病竈中心點離邊界的平均距離

texture:紋理,灰度值的標準誤差

perimeter:周長,即病竈的大小

area:面積,反映病竈大小的一個指標

smoothness:平滑度,即半徑的變化幅度

compactness:密實度,周長的平方除以面積的熵,再減一

concavity:凹度,凹陷部分輪廓的嚴重程度

concave points:凹點,凹陷輪廓的數量

symmetry:對稱性

fractal dimension:分形維度

從這些指標裏,能夠看出,有些指標屬於複合指標,即由其餘的指標通過運算獲得的。好比密實度,是由周長和麪積計算出來的。這種運算構建出來的新特徵,是事物內在邏輯關係的體現

舉個例子,須要監控數據中心中每臺物理主機的運行狀況,其中CPU佔用率、內存佔用率,網絡吞吐量是幾個重要指標。

問:有臺主機CPU佔用率80%,這個主機狀態是否正常?要不要發佈告警?答:看狀況,僅從CPU佔用率來看還不能判斷主機是否正常,還要看內存佔用狀況和網絡吞吐量狀況。若是此時內存佔用也成比例上升,且網絡吞吐量狀況。那麼形成這一狀態的多是用戶訪問的流量過大,致使主機負荷增長,不須要告警。

但若是內存佔用,網絡吞量和CPU佔用不在同一量級,那麼這臺主機就可能處於不正常的狀態。

因此,咱們須要構建一個複合特徵,如CPU佔用率和內存佔用率的比值,以及CPU佔用率和網絡吞吐量的比值,這樣構造出來的特徵更真實地體現出了現實問題中的內在規則。

提取特徵時,不妨從事物的內在邏輯關係入手,分析已有特徵之間的關係,從而構造出新的特徵。

乳腺癌數據集的特徵問題,實際上就只有10個特徵,而後構造出了每一個特徵的標準差及最大值。這樣每一個特徵就又衍生了兩個特徵,總共就30個特徵。能夠經過cancer.feature_names變量來查看這些特徵的名稱

模型訓練

scikit-learn提供了一致的接口調用,使用起來很是方便

首先把數據集分紅訓練數據集和測試數據集8:2

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)

而後,使用LogisticRegression模型來訓練,並計算訓練數據集的評分數據和測試數據集的評分數據

from sklearn.linear_model import LogisticRegression

model = LogisticRegression()
model.fit(X_train, y_train)
train_score = model.score(X_train, y_train)
test_score = model.score(X_test, y_test)
print(
'train score:{train_score:.6f}; test score:{test_score:.6f}'
.format(train_score=train_score, test_score=test_score))

輸出的內容以下:

train score:0.962637; test score:0.938596

還能夠看一下測試樣本中,有幾個是預測正確的:

y_pred = model.predict(X_test)
print('matchs:{0}/{1}'.format(np.equal(y_pred, y_test).shape[0], y_test.shape[0]))

輸出以下

matchs:114/114

總共114個測試樣本,所有測試正確。這裏所有都預測正確,而testscore卻只有0.973684,而不是1呢?

由於scikir-learn不是使用這個數據來計算分數,由於這個數據不能徹底反映偏差狀況,而是使用預測機率數據來計算模型評分

針對二元分類問題,LogisticRegression模型會針對每個樣本輸出兩個機率, 即爲0的機率和爲1的機率,哪一個機率高就預測爲哪一個類別

能夠找出針對測試數據集,模型預測的自信度低於90%的樣本。

首先計算出測試數據集裏每一個樣本的預測機率數據,針對每一個樣本,會有兩個數據,一是預測其爲陽性的機率,另一個是預測其爲陰性的機率。接着找出預測爲陰性機率大於0.1的樣本,而後在結果裏,找出預測爲陽性的機率也大於0.1的樣本,這樣就找出了模型預測自信度低於90%的樣本。

y_pred_proba = model.predict_proba(X_test)
print('sample of predict probability:{0}'.format(y_pred_proba[0]))
y_pred_proba_0 = y_pred_proba[:, 0] > 0.1
result = y_pred_proba[y_pred_proba_0]
y_pred_proba_1 = result[:, 1] > 0.1
print(result[y_pred_proba_1])

看一下輸出結果

[0.35747299 0.64252701]

使用model.predict_proba() 來計算機率,同時找出那些預測自信度低於90%的樣本。能夠看到最沒有把握的是這個0.64機率的

模型優化

使用LogisticRegression模型的默認參數訓練出來的模型,準確性看起來仍是挺高的。問題是,有沒有優化空間呢?若是有,往哪一個方向優化呢?

先嚐試增長多項式特徵,實際上,多項式特徵和上文介紹的人爲添加的複合特徵相似,都是從已有特徵通過數學運算得來的。只是這裏的邏輯關係沒有那麼明顯。

雖然咱們不能直觀地理解多項式特徵的邏輯關係,可是有一些方法和工具能夠用來過濾那些對模型準確性有幫助的特徵

首先使用Pipeline來增長多項式特徵,

from sklearn.linear_model import LogisticRegression
from sklearn.preprocessing import PolynomialFeatures
from sklearn.pipeline import Pipeline

def polynomial_model(degree=1, **kwarg):
polynomial_features = PolynomialFeatures(degree=degree, include_bias=False)
logistic_regression = LogisticRegression(**kwarg)
pipeline = Pipeline([("polynomial_features", polynomial_features)
, ("logistic_regression", logistic_regression)])
return pipeline

接着,增長二階多項式特徵,建立並訓練模型

import time

model = polynomial_model(degree=2, penalty='l1')
start = time.clock()
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:.6f}; cv_score:{2:.6f}'
.format(time.clock() - start, train_score, cv_score))

使用L1範數做爲正則項(參數penalty='l1')輸出以下

elaspe:0.374181; train_score:1.000000; cv_score:0.956140

能夠看到,訓練數據集評分和測試數據集評分都增長了。爲何使用L1範數做爲正則項呢?

前面介紹過,L1範數做爲正則項,能夠實現參數的稀疏化,即自動幫助咱們選擇出那些對模型有關聯的特徵。

能夠觀察一下有多少個特徵沒有被丟棄,即其對應的模型參數θj非0:

logistic_regression = model.named_steps['logistic_regression']
print('model parameters shape:{0}; count of non-zero element:{1}'
.format(logistic_regression.coef_.shape,np.count_nonzero(logistic_regression.coef_)))

輸出以下

model parameters shape:(1, 495); count of non-zero element:116

邏輯迴歸模型的coef_屬性裏保存的就是模型參數。從輸出結果能夠看到,增長二階多項式特徵後,輸入特徵由原來的30個增長到了495個,最終大多數特徵都被丟棄,只保留了94個有效特徵

學習曲線

怎麼知道使用L1=範數做爲正則項能提升算法的準確性?答案是:畫出學習曲線。

學習曲線是有效的診斷工具之一,也是以前章節一直強調的內容

首先畫出使用L1範數做爲正則項所對應的一階和二階多項式的學習曲線

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)
title = 'Learning Curves (degree={0},penalty={1})'
degrees = [1, 2]
penalty = 'l1'
start = time.clock()
plt.figure(figsize=(12, 4), dpi=144)
for i in range(len(degrees)):
    plt.subplot(1, len(degrees), i + 1)
    plot_learning_curve(plt, polynomial_model(degree=degrees[i], penalty=penalty),
                        title.format(degrees[i], penalty), X, y, ylim=(0.8, 1.01), cv=cv)
print('eleaspe:{0:.6f}'.format(time.clock() - start))
plt.show()

決策樹

決策樹是最經典的機器學習模型之一。它的預測結果容易理解,易於向業務部門解釋,預測速度快,能夠處理類別型數據和連續型數據。在機器學習的數據挖掘類求職面試中,決策樹是面試官最喜歡的面試題之一。

算法原理

決策樹是一個相似於流程圖的樹結構,分支節點表示對一個特徵進行測試,根據測試結果進行分類,樹葉節點表明一個類別。下圖用決策樹來決定下班後的安排 

咱們分別對精力指數、情緒指數兩個特徵進行測試,並根據測試結果決定行爲的類別。每選擇一個特徵進行測試,數據集就被劃分紅多個子數據集。接着繼續在子數據集上選擇特徵,並進行數據集劃分,直到建立出一個完整的決策樹。

建立好決策樹模型後,只要根據下班後的精力和情緒情況,從根節點一路往下便可預測出下班後的行爲

在建立決策樹的時候,要先對哪一個特徵進行分裂?針對上圖的例子,先判斷精力指數進行分裂仍是先判斷情緒指數進行分裂。須要從信息的量化談起

信息增益

1948年,香農在他註明的《通訊的數學原理》中提出了信息熵(Entropy)的概念,從而解決了信息的量化問題。香農認爲,一條信息的信息量和它的不肯定性有直接關係。一個問題不明肯定性越大,要搞清楚這個問題,須要瞭解的信息就越多,其信息熵就越大。信息熵的計算公式爲:

其中,P(x)表示事件x出現的機率。例如,一個盒子裏分別有5個白球和5個紅球,隨機取出一個球。問:這個球是紅色的仍是白色的?這個問題的信息量多大呢?因爲紅球和白球出現的機率都是1/2,代入信息熵公式,能夠獲得其信息熵爲

這個問題的信息量是1bit,信息量的單位就是比特。須要肯定這個球是紅色的仍是白色的,只須要1比特的信息就夠了。

再舉一個例子,一個盒子裏有10個白球,隨機取出一個球,這個球是什麼顏色的。這個問題的信息量是0,由於這是一個肯定的事件,其機率P(x)=1,咱們代入香農的信息熵公式,便可獲得其信息熵爲0。即不須要再獲取任何新的信息,便可知道這個球必定是白色

回到決策的構建問題上,當咱們要構建一個決策樹時。應該遍歷全部的特徵,分別計算,使用這個特徵劃分數據集先後的信息熵的變化值,而後選擇信息熵變化幅度最大的那個特徵,來優先做爲數據集劃分依據。

則選擇信息增益最大的特徵做爲分裂節點

好比,一個盒子裏共有紅、白、黑、藍4種顏色的球共計16個,其中紅球2個,白球2個,黑球4個,籃球8個。

紅球和黑球的體積同樣,都爲1個單位。白球和籃球的體積同樣,都是2個單位。紅球、白球和黑球的質量同樣,都是1個單位,藍球的質量爲2個單位。

應該優先選擇哪一個特徵呢?先計算基礎信息熵,即劃分數據集前的信息熵。從已知信息容易知道,紅球、白球、黑球、藍球出現的機率分別爲2/1六、2/1六、4/1六、8/16,所以基礎信息熵爲

接着使用體積來劃分數據集,此時會劃分出兩個數據集,第一個子數據集裏是紅球和黑球,第二個子數據集裏是白球和藍球,咱們計算這種劃分方式的信息熵。

其中第一個子數據集裏,紅球2個,黑球4個,其機率分別爲2/6和4/6,所以第一個子數據集的信息熵爲

第二個子數據集裏,白球2個,藍球8個,其機率分別爲2/10和8/10,所以第二個子數據集的信息熵爲

所以,使用體積來劃分數據集後,其信息熵爲H(D1) = H(D1sub1)+H(D1sub2)=1.640224

其信息增益爲H(Dbase)-H(D1)=1.75-1.640224=0.109776

若是咱們使用質量來劃分數據集,也會劃分出兩個數據集,第一個子數據集是紅球、白球、黑球。第二個子數據集只有藍球。咱們計算這種劃分方式的信息熵。針對第一個子數據集,紅球、白球、黑球出現的機率分別是2/八、2/八、4/8其信息熵爲

第二個子數據集裏只有藍球,其機率爲1,所以其信息熵H(D2sub2)=0。咱們得出使用使用質量來劃分數據集時的信息熵爲1.5,其信息增益爲1.75-1.5=0.25.因爲使用質量劃分數據集比使用體積劃分數據集獲得了更高的信息增益,因此咱們優先選擇質量這個特徵來劃分數據集。

下面來討論信息增益的物理意義

以機率P(x)爲橫座標,以信息熵Entropy爲縱座標,把信息熵和機率的函數關係Entropy=-P(x)log2P(x)在二維座標軸上畫出來

import numpy as np
from matplotlib import pyplot as plt


def entropy(px):
    return -px * np.log2(px)

x = np.linspace(0.001, 1, 1000)
plt.title('$Entropy(x) = - P(x) * log_2(P(x))$')
plt.xlim(0, 1)
plt.ylim(0, 0.6)
plt.xlabel('P(x)')
plt.ylabel('Entropy')
plt.plot(x, entropy(x), 'r-')
plt.show()

從這個函數關係能夠看出來,當機率P(x)月接近0或月越接近1時,信息熵的值越小,其不肯定性越小,即數據越「純」。典型地,當機率爲1時,此時數據是最純淨的,由於只有一種類別的數據,已經消除了不肯定性,其信息熵爲0.

咱們在特徵選擇時,選擇信息增益最大的特徵,在物理上,即讓數據儘可能往更純淨的方向上變換。所以,信息增益是用來衡量數據變得更有序、更純淨的程度的指標

熵是熱力學中表徵物質狀態的參量之一,其物理意義是體系混亂程度的度量,被香農借用過來,做爲信息量的度量。註明的熵增原理是這樣描述的:

熵增原理就是孤立熱力學系統的熵不減小,老是增大或者不變。一個孤立系統不可能朝低熵的狀態發展,即不會變的有序

用白話講就是,若是沒有外力的做用,這個世界將是愈來愈無序的。人活着,在於儘可能讓熵變低,即讓世界變的更有序,下降不肯定性。咱們在消費資源時,是一個增熵的過程。咱們把有序的食物變成了無序的垃圾。

決策樹的建立

決策樹的構建過程,就是從訓練數據集中概括出一組分類規則,使它與訓練數據矛盾較小的同時具備較強的泛化能力。有了信息增益來量化地選擇數據集的劃分特徵,使決策樹的建立過程變得容易了。決策樹的建立基本上分爲如下幾步:

1. 計算數據集劃分前的信息熵

2. 遍歷全部未做爲劃分條件的特徵,分別計算根據每一個特徵劃分數據集後的信息熵

3. 選擇信息增益最大的特徵,並使用這個特徵做爲數據劃分節點來劃分數據

4. 遞歸地處理被劃分後的全部子數據集,從未被選擇的特徵裏繼續選擇最優數據劃分特徵來劃分子數據集

遞歸的結束,通常來說有兩個終止條件。

一是全部的特徵都用完了,即沒有新的特徵能夠用來進一步劃分數據集。

二是劃分後的信息增益足夠小了,這個時候就能夠中止遞歸劃分了。

針對這個中止條件,須要事先選擇信息增益的門限值來做爲結束遞歸的條件。

使用信息增益做爲特徵選擇指標的決策樹構建算法,稱爲ID3算法

1. 離散化

若是一個特徵是連續值怎麼辦呢?假設有一個精力測試儀器,測出來的是一個0~100的數字,這個是連續值,這個時候要怎麼用決策樹來建模呢?

答案是:離散化

咱們須要對數據進行離散化處理。例如當精力指數小於等於40時標識爲低,當大於40且小於等於70時標識爲中,當大於70時標識爲高。

通過離散處理後,就能夠用來構建決策樹了。要離散化成幾個類別,這個每每和具體的業務相關。

2.正則項

最大化信息增益來選擇特徵,決策樹的構建過程當中,容易形成優先選擇類別最多的特徵來進行分類。舉一個極端的例子,把某個產品的惟一標識符ID做爲特徵之一加入到數據集中,那麼構建決策樹時,就會優先選擇產品ID來做爲劃分特徵,由於這樣劃分出來的數據,每一個葉子節點只有一個樣本,劃分後的子數據最純淨,其信息增益最大

計算劃分後的子數據集的信息熵時,加上一個與類別個數成正比的正則項,來做爲最後的信息熵。這樣,當算法選擇的某個類別較多的特徵,使信息熵較小時,因爲受到類別個數的正則項懲罰,致使最終的信息熵也比較大。這樣經過合適的參數,能夠使算法訓練獲得某種程度的平衡。

另一個解決辦法是使用信息增益比來做爲特徵選擇的標準

3.基尼不純度

信息熵是衡量信息不肯定性的指標,實際上也是衡量信息純度的指標。除此以外,基尼不純度(GIni impurity)也是衡量信息不純度的指標,其計算工時以下

其中,P(x)是樣本屬於這個類別的機率。若是全部的樣本都屬於一個類別,此時P(x) = 1,則Gini(D)=0,即數據不純度最低,純度最高。咱們以機率P(x) 做爲橫座標,以這個類別的基尼不純度Gini(D)=P(x)(1-P(x)) 做爲縱座標,在座標軸上畫出其函數關係

def gini_impurity(px):
    return px * (1 - px)

x = np.linspace(0.01, 1, 100)
plt.figure(figsize=(5, 3), dpi=200)
plt.title('$Gini(x) = P(x) (1 - P(x))$')
plt.xlim(0, 1)
plt.ylim(0, 0.6)
plt.xlabel('P(x)')
plt.ylabel('Gini Impurity')
plt.plot(x, entropy(x), 'r-');

CART算法使用基尼不純度來做爲特徵選擇標準,CART也是一種決策樹構建算法。

剪枝算法

使用決策樹模型擬合數據時,容易形成過擬合。解決過擬合的方法是對決策樹進行剪枝處理。決策樹的剪枝有兩種思路:前剪枝(Pre-Pruning)和後剪枝(Post-Pruning)

1.前剪枝

前剪枝是在構造決策樹的同時進行剪枝。在決策樹的構建過程當中,若是沒法進一步下降信息熵的狀況下,就會中止建立分支,爲了不過擬合,能夠設定一個閾值,信息熵減少的數量小於這個閾值,即便還能夠繼續下降熵,也中止繼續建立分支。這種方法成爲前剪枝。有一些簡單的前剪枝方法,如限制葉子節點的樣本個數,當樣本個數小於必定的閾值時,既再也不繼續建立分支

2.後剪枝

後剪枝是指決策樹構造完成以後進行剪枝。剪枝的過程是對擁有一樣父節點的一組節點進行檢查,判斷若是將其合併,信息熵的增長量是否小於某一閾值。若是小於閾值,則這一組節點能夠合併一個節點。後剪枝是目前較廣泛的作法。後剪枝的過程是刪除一些子樹,而後用子樹的根節點代替,來做爲新的葉子節點。這個新的葉子節點所標識的類別經過大多數原則來肯定,即把這個葉子節點裏樣本最多的類別,做爲這個葉子節點的類別。

後剪枝算法有不少種,其中經常使用一種稱爲下降錯誤率剪枝法(Reduced-Error Pruning)其思路是,自底向上,從已經構建好的徹底決策樹中找出一個子樹,而後用子樹的根節點代替這課子樹,做爲新的葉子節點。葉子節點鎖標識的類別經過大多數原則來肯定。這樣就構建了一個新的簡化版的決策樹。而後使用交叉驗證數據集來測試簡化版本的決策樹,看看其錯誤率是否是下降了。若是錯誤率下降了,則能夠使用這個簡化版的決策樹代替徹底決策樹,不然仍是採用原來的決策樹。經過遍歷全部子樹,直到針對交叉驗證數據集,沒法進一步下降錯誤率爲止。

算法參數

scikit-learn使用sklearn.tree.DecisionTreeClassifier類來實現決策樹分類算法。其中幾個典型的參數解釋以下

criterion:特徵選擇算法。一種是基於信息熵,另一種是基於基尼不純度。這兩種算法的差別性不大,對模型的準確值沒有太大的影響。相對而言,信息熵運算效率會低一些,由於它有對數運算。

splitter:建立決策樹分支的選擇,一種是選擇最優分支建立原則,另一種是從排名靠前的特徵中,隨機選擇一個特徵來建立分支,這個方法和正則項的效果相似,能夠避免過擬合問題。

max_depth:指定決策樹的最大深度。經過指定該參數,用來解決模型過擬合問題

min_samples_split:這個參數指定能建立分支的數據集的大小,默認是2。若是一個節點的數據樣本個數小於這個數值,則再也不建立分支。這也是一種前剪枝方法

min_samples_leaf:建立分支後的節點樣本數量必須大小等於這個數值,不然再也不建立分支。這也是一種前剪枝的方法

max_leaf_nodes:除了限制最小的樣本節點個數,改參數能夠限制最大的樣本節點個數

min_impurity_split:能夠使用該參數來指定信息增益的閾值。決策樹在建立分支時,信息增益必須大於這個閾值,不然不建立分支

從這些參數能夠看到,scikit-learn有一系列的參數用來控制決策樹生成的過程,從而解決過擬合問題。

實例:預測泰坦尼克號倖存者

經過決策樹來預測泰坦尼克號那些人可能成爲倖存者

數據集總共兩個文件。train.csv是訓練數據集,包含已標註的訓練樣本數據。test.csv是咱們模型要進行幸存者預測的數據。咱們的任務就是根據train.csv裏的數據訓練模型,使用這個模型預測test.csv裏的數據

數據分析

train.csv是一個892行、12列的數據表格。意味着咱們有891個訓練樣本,每一個樣本有12個特徵,須要先分析這些特徵,以便決定哪一個特徵能夠用來進行模型訓練

PassengerId:乘客的ID號,這是個順序編號,用來惟一地標識一名乘客。這個特徵和倖存與否無關,不使用這個特徵。

Survived:1 表示倖存,0 表示遇難。這是咱們標註的數據

Pclass:倉位等級,是很重要的特徵。高倉位等級的乘客能更快地到達甲板,從而更容易獲救

Name:乘客名字,這個特徵和倖存與否無關,丟棄

Sex:乘客性別,船長讓婦女和兒童先上,很重要的特徵

Age:乘客年齡,兒童會優先上船

SibSp:兄弟姐妹同在船上的數量

Parch:同船的父輩人員數量

Ticket:乘客票號,不使用這個特徵

Fare:乘客體熱指標

Cabin:乘客所在的船艙號。實際上這個特徵和倖存與否有必定關係,好比最先被水淹沒的船艙位置,其乘客的倖存機率要低一些。但因爲這個特徵由大量丟失數據,因此丟棄這個特徵

Embarked:乘客登船的港口,須要把港口數據轉換爲數值型數據

咱們須要加載csv數據,並作一些預處理,包括:

1. 提取Survived列的數據做爲模型的標註數據

2. 丟棄不須要的特徵數據

3. 對數據進行轉換,以便模型處理。好比性別數據,咱們須要轉換爲0和1

4. 處理缺失數據,好比年齡、有不少確實的數據

使用pandas完成這些任務

def read_dataset(fname):
    # 指定第一列爲行索引
    data = pd.read_csv(fname, index_col=0)
    # 丟棄無用的數據,axis=1列
    data.drop(['Name', 'Ticket', 'Cabin'], axis=1, inplace=True)
    # 處理性別數據
    data['Sex'] = (data['Sex'] == 'male').astype('int')
    # 處理登船港口數據
    labels = data['Embarked'].unique().tolist()
    data['Embarked'] = data['Embarked'].apply(lambda n: labels.index(n))
    # 處理缺失數據
    data = data.fillna(0)
    data.head()
    return data

train = read_dataset('titanic/train.csv')

模型訓練

首先須要把Survived列提取出來做爲標籤,而後在原數據集中將其丟棄。同時把數據集分紅訓練數據集和交叉驗證數據集

from sklearn.model_selection import train_test_split

y = train['Survived'].values
x = train.drop(['Survived'], axis=1).values
X_train, X_test, y_train, y_test = train_test_split(x, y, test_size=0.2)
print('train dataset:{0}; test dataset:{1}'.format(X_train.shape, X_test.shape))

輸出內容以下

train dataset:(712, 7); test dataset:(179, 7)

接下來,使用scikit-learn的決策樹模型對數據進行擬合

from sklearn.tree import DecisionTreeClassifier

clf = DecisionTreeClassifier()
clf.fit(X_train, y_train)
train_score = clf.score(X_train, y_train)
test_score = clf.score(X_test, y_test)
print('train score: {0}; test score: {1}'.format(train_score, test_score))

輸出內容以下

train score: 0.9803370786516854; test score: 0.7932960893854749

能夠看出,針對訓練樣本評分很高,但針對交叉驗證數據集評分比較低,二者差距較大。

很明顯的過擬合的特徵。解決決策樹過擬合的方法是剪枝,包括前剪枝和後剪枝。可是scikit-learn不支持後剪枝,但提供一系列的模型參數進行前剪枝。

優化模型參數

一個最直觀的解決辦法是選擇一系列參數的值,而後分別計算用指定參數訓練出來的模型和評分數據。還能夠把二者的關係畫出來,直觀地看到參數值與模型準確度的關係 

以模型深度max_depth爲例,咱們先建立一個函數,使用不一樣的模型深度訓練,並計算評分數據

# 參數選擇 max_depth
def cv_score(d):
clf = DecisionTreeClassifier(max_depth=d)
clf.fit(X_train, y_train)
tr_score = clf.score(X_train, y_train)
cv_score = clf.score(X_test, y_test)
return (tr_score, cv_score)

接着構造參數範圍,在這個範圍內分別計算模型評分,並找出評分最高的模型所對應的參數

depths = range(2, 15)
scores = [cv_score(d) for d in depths]
tr_scores = [s[0] for s in scores]
cv_scores = [s[1] for s in scores]
# 找出交叉驗證數據集評分最高的索引
best_score_index = np.argmax(cv_score)
best_score = cv_scores[best_score_index]
best_param = depths[best_score_index]  # 找出對應的參數
print('best param:{0}; best score:{1}'.format(best_param, best_score))

輸出結果以下

best param:2; best score:0.7486033519553073 

針對模型深度這個參數,最優的值是2,其對應的交叉驗證數據集評分爲0.74.能夠把模型參數和模型評分畫出來,更直觀地觀察齊變化規律

plt.figure(figsize=(6, 4), dpi=144)
plt.grid()
plt.xlabel('max depth of decision tree')
plt.ylabel('score')
plt.plot(depths, cv_scores, '.g-', label='cross-validation score')
plt.plot(depths, tr_scores, '.r--', label='training score')
plt.legend()
plt.show()

使用一樣的方法,也能夠考察參數min_impurity_split。這個參數用來指定信息熵或基尼不純度的閾值,當決策樹分裂後,其信息增益低於這個閾值時,則再也不分裂

def cv_score(val):
    clf = DecisionTreeClassifier(criterion='gini', min_impurity_split=val)
    clf.fit(X_train, y_train)
    tr_score = clf.score(X_train, y_train)
    cv_score = clf.score(X_test, y_test)
    return (tr_score, cv_score)


# 指定參數範圍,分別訓練模型並計算評分
values = np.linspace(0, 0.5, 50)
scores = [cv_score(v) for v in values]
tr_scores = [s[0] for s in scores]
cv_scores = [s[1] for s in scores]

# 找出評分最高的模型參數
best_score_index = np.argmax(cv_scores)
best_score = cv_scores[best_score_index]
best_param = values[best_score_index]
print('best param:{0}; best score:{1}'.format(best_param, best_score))

# 畫出模型參數與模型評分的關係
plt.figure(figsize=(6, 4), dpi=144)
plt.grid()
plt.xlabel('threshold of entropy')
plt.ylabel('score')
plt.plot(values, cv_scores, '.g-', label='cross-validation score')
plt.plot(values, tr_scores, '.r--', label='training score')
plt.legend()
plt.show()

模型參數選擇工具包

介紹的模型參數優化方法有兩個問題。

1. 數據不穩定,每次從新把數據集劃分紅訓練數據集和交叉驗證數據集後,選擇出來的模型參數就不是最優的了 

2. 不能一次選擇多個參數

問題1的緣由是每次把數據集劃分都是隨機劃分,這樣致使每次的訓練數據集是有差別的,訓練出來的模型也是有差別的。解決這個問題的方法是屢次計算,求平均值

scikit-learn在sklearn.model_selection包裏提供了大量的模型選擇和評估工具供咱們使用

針對上面的問題能夠使用GridSearchCV類來解決。

from sklearn.model_selection import GridSearchCV

thresholds = np.linspace(0, 0.5, 50)
param_grid = {'min_impurity_split': thresholds}
clf = GridSearchCV(DecisionTreeClassifier(), param_grid, cv=5)
clf.fit(x, y)
print('best param:{0}; best score:{1}'.format(clf.best_params_, clf.best_score_))

輸出結果以下

best param:{'min_impurity_split': 0.21428571428571427}; best score:0.8226711560044894

其中關鍵的參數是param_grid,是一個字典,字典關鍵字所對應的值是一個列表。GridSearchCV會枚舉列表裏全部的值來構建模型,屢次計算訓練模型,並計算模型評分,最終得出指定參數值的平均評分及標準差。

另一個關鍵的參數是cv,它用來指定交叉驗證數據集的生成規則,代碼中的cv=5表示每次計算都把數據集分紅5份,拿其中一份做爲交叉驗證數據集,其餘的做爲訓練數據集。

最終得出的最優參數及最優評分保存在clf.best_params和clf_best_score_裏。

此外clf.cv_results保存了計算過程當中的全部中間結果。能夠拿這個數據來畫出模型參數與模型評分的關係圖

def plot_curve(train_sizes, cv_results, xlabel):
    train_scores_mean = cv_results['mean_train_score']
    train_scores_std = cv_results['std_train_score']
    test_scores_mean = cv_results['mean_test_score']
    test_scores_std = cv_results['std_test_score']
    plt.figure(figsize=(6, 4), dpi=144)
    plt.title('parameters turning')
    plt.grid()
    plt.xlabel(xlabel)
    plt.ylabel('score')
    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, '.--', color='r', label='Training score')
    plt.plot(train_sizes, test_scores_mean, '.-', color='g', label='Cross-validation score')
    plt.legend(loc='best')
    plt.show()

plot_curve(thresholds, clf.cv_results_, xlabel='gini thresholds')

 

接下來看一下如何在多組參數之間選擇最優的參數

entropy_thresholds = np.linspace(0, 1, 50)
gini_thresholds = np.linspace(0, 0.5, 50)
# 設置參數矩陣
param_grid = [{'criterion': ['entropy'], 'min_impurity_split': entropy_thresholds},
              {'criterion': ['gini'], 'min_impurity_split': gini_thresholds},
              {'max_depth': range(2, 10)},
              {'min_samples_split': range(2, 30, 2)}]
clf = GridSearchCV(DecisionTreeClassifier(), param_grid, cv=5)
clf.fit(x, y)
print('best param:{0}; best score:{1}'.format(clf.best_params_, clf.best_score_))

輸出結果

best param:{'criterion': 'entropy', 'min_impurity_split': 0.5306122448979591}; best score:0.8271604938271605 

關鍵部分仍是param_grid參數,是一個列表,列表中的每個元素都是一個字典。

針對列表中的第一個字典,選擇信息熵做爲決策樹特徵的判斷標準,同時其閾值範圍是[0,1]之間分了50等分。

GridSearchCV會針對列表中的每一個字典進行迭代,最終比較列表中每一個字典所對應的參數組合,選擇出最優的參數

支持向量機

支持向量機簡稱SVM,是Support Vector Machine的縮寫。SVM是一種分類算法,在工業界和學術界都有普遍的應用。特別是針對數據集較小的狀況下,每每其分類效果比神經網絡好

算法原理

SVM的最大特色是能構造出最大間距的決策邊界,從而提升分類算法的魯棒性

大間距分類算法 

假設要對一個數據集進行分類,能夠構造一個分割線把圓形的點和方形的點分開。這個分隔線稱爲分隔超平面(Separating hyperplance)

從上圖中能夠明顯看出,實現的分隔線比虛線的分隔線更好,由於使用實線的分隔線進行分類時,離分隔線最近的點到分隔線上的距離更大,即margin2>margin1。這段距離的兩倍,稱爲間距(margin)。那些離分隔超平面最近的點,稱爲支持向量(support vector)。爲了達到最好的分類效果,SVM的算法原理就是找到一個分隔超平面,能把數據集正確分類,而且間距最大

首先來看怎麼計算間距,二維空間裏,能夠使用方程w1x1+w2x2+b=0來表示分隔超平面。針對高維度空間,可寫成通常化的向量形式,即wTx+b=0。畫出與分隔超平面平行的兩條直線,分別穿過兩個類別的支持向量(離分隔超平面距離最近的點)。這兩條直線的方程分別爲wTx+b=-1和wTx+b=1

根據點到直線的距離公式,能夠容易地計算出支持向量A到分隔超平面的距離爲

因爲點A在直線wTx+b=1上,所以wTA+b=1,代入便可得,支持向量A到分隔超平面的距離爲

爲了使間距最大,咱們只須要找到合適的參數w和b,使最大便可。|| w || 是向量w的L2範數,其計算公式爲:

由此可得,求的最大值,等價於求|| w || 2最小值

其中n爲向量w的維度。除了間距最大外,選出來的分隔超平面還要能正確地把數據集分類。

針對方形的點,一定知足wTx+b>=1的約束條件。針對圓形的點x,一定知足wTx+b<=-1的約束條件。類別是離散的值,咱們使用-1來表示圓形的類別,用1來表示方形的類別,即y∈{-1,1}。針對數據集中的全部樣本x(i),y(i),只要他們都知足如下的約束條件,則由參數w和b定義的分隔超平面便可正確地把數據集分類

技巧在於使用1和-1來定義類別標籤。針對y(i)=1的狀況,因爲其知足wTx(i)=b>=1的約束,兩邊都乘以y(i)後,大於號保持不變。針對y(i)=-1的狀況,因爲其知足wTx(i)+b<=-1的約束,兩邊都乘以y(i)後,負負得正,而且小於號變成了大於號。這樣,就能夠用一個公式來表達針對兩個不一樣類別的約束函數。

邏輯迴歸算法裏,使用0和1做爲類別標籤,而在這裏咱們使用-1和1做爲類別標籤。其目的都是爲了讓數學表達儘可能簡潔。

總結:求解SVM算法,就是在知足約束條件y(i)(wTx(i)+b)>=1的前提下,求解||w||2的最小值。

鬆弛係數

針對線性不可分的數據集,上面介紹的方法就失靈了,由於沒法找到最大間距的分隔超平面

解決這個問題的辦法是引入一個參數ε,稱爲鬆弛係數。而後把優化的目標函數變爲

其中m爲數據集的個數,R爲算法參數。其約束條件相應地變爲

怎麼理解鬆弛係數呢?能夠把εi理解爲數據樣本x(i)違反最大間距規則的程度,如上圖右側所示,針對大部分正常的樣本,即知足約束條件的樣本ε=0。而對部分違反最大間距規則的樣本ε>0。而參數R則表示對違反最大間距規則的樣本的懲罰力度。

當R選擇一個很大的值時,咱們的目標函數對違反最大間距規則的點的懲罰力度將變得很大。

當R選擇一個比較小的值時,針對那些違反最大間距規則的樣本,其付出的代價不是特別大,咱們的規模就會傾向於容許部分點違反最大間距規則。能夠把y(i)(wTx(i)+b)做爲橫座標,把樣本因爲違反約束條件所付出的代價Ji做爲縱座標。能夠畫出下圖

能夠清楚地看出來,針對那些沒有違反約束條件y(i)(wTx(i)+b)>=1的樣本,其成本爲0。而針對那些違反了約束條件的樣本y(i)(wTx(i)+b)>=1-εi,其成本與ε成正比,如圖中的斜線所示,斜線的斜率爲R。

從這裏的描述可知,引入鬆弛係數相似於邏輯迴歸算法裏的成本函數引入正則項,目的都是爲了糾正過擬合問題,讓支持向量機對噪聲數據有更強的適用性。

當出現一些違反大間距規則的噪聲樣本時,仍然但願咱們的分隔超平面是原來的樣子,這就是鬆弛係數的做用

核函數

核函數是特徵轉換函數。是很是抽象的描述

咱們的任務是找出合適的參數w,b使得由它們決定的分隔超平面、間距最大,且能正確地對數據集進行分類。間距最大是咱們的優化目標,正確地對數據集進行分類是約束條件。用數學來表達,在知足約束條件y(i)(wTx(i)+b)>=1,即y(i)(wTx(i)+b)-1>=0的前提下求0.5||w||2的最小值

拉格朗日乘子法是解決約束條件下,求函數極值的理想方法。其方法是引入非負係數a來做爲約束條件的權重

公式中,針對數據集中的每一個樣本x(i),y(i)都有一個係數ai與之對應。學習過微積分的都知道,極值處的偏導數爲0。咱們先求L對w的偏導數

從而獲得w和a的關係

把求的最小值,目的是爲了使w的數學表達式儘可能簡潔優美。接着咱們繼續先求L對b的偏導數

經過代數運算可得

m是數據集的個數,a是拉格朗日乘子法引入的一個係數,針對數據集中的每一個樣本x^(i),都有對應的ai。x^(i)是數據集中第i個樣本的輸入,它是一個向量,y^(i)是數據集第i個樣本的輸出標籤,其值爲y^(i)∈{-1,1}

使用數值分析能夠求公式的最小值,這是一個典型的二次規劃問題。目前普遍應用的是一個稱爲SMO(序列最小優化)的算法

最後求解出來的a有個明顯的特色,即大部分ai=0,這個結論背後的緣由很直觀,由於只有那些支持向量所對應的樣本,直接決定了間隙的大小,其餘離分隔超平面太遠的樣本,對間隙大小根本沒有影響。

L裏的x^(i)T x^(j)部分,其中x^(i)是一個特徵向量,因此x^(i)T x^(j)是一個數值,它是兩個輸入特徵向量的內積。咱們的預測函數爲

當y>0咱們預測爲類別1,當y<0時,咱們預測爲類別-1。注意到預測函數裏也包含式子x^(i)Tx。咱們把K(x^(i),x^(j)) = x^(i)^Tx^(j)稱爲核函數。x^(i)^Tx^(j)是兩個向量內積,物理含義是衡量兩個向量的類似性,當着兩個向量互相垂直時,即徹底線性無關,此時x^(i)^Tx^(j)=0.引入核函數後,咱們的預測函數就變成

類似性函數

假設咱們有一個數據集,只有一個輸入特徵,要對這個數據集進行分類。因爲只有一個輸入特徵,這些訓練樣本分佈在一條直線上,此時咱們很難找出一個分隔超平面來分隔這個數據集

爲了解決這個問題,能夠想辦法用必定的規則把這些沒法進行線性分隔的樣本,映射到更高維度的空間裏,而後在高維度空間裏找出分隔超平面。

把一維空間上的樣本映射到二維空間,這樣很容易就能找出一個分隔超平面把這些樣本分離開

SVM的核函數就是爲了實現這種類似性映射。最簡單的核函數是K(x^(i),x^(j))=x^(i)^Tx^(j),它衡量的是兩個輸入特徵向量的類似性。能夠經過核函數K(x^(i),x^(j))來從新定義類似性,從而獲得想要的映射。

怎麼把低維度的空間映射到高維度的空間呢?能夠使用多項式來增長特徵數,這個本質上就是從低維度映射到高維度。針對上圖的例子,咱們的輸入特徵是一維的,即只有[x1]變量,若是咱們要變成二維的,一個方法是把輸入特徵變爲[x1,2x1^2],此時的輸入特徵就變成了一個二維向量。定義這種特徵映射的哈函數爲Φ(x),稱之爲類似性函數。針對輸入特徵向量x,通過Φ(x)做用後,會變成一個新的、更高維度的輸入特徵向量。這樣在原來低維度計算類似性的運算x^(i)^Tx^(j),就能夠轉換爲高維度空間裏進行類似性運算Φ(x^(i))^TΦ(x^(j)) 

類似性函數是特徵映射函數,好比針對二維的特徵向量[x1,x2],咱們能夠定義類似性函數

通過類似性函數轉換後,二維的特徵向量就變成了五維的特徵向量。而核函數定義爲特徵向量的內積,通過類似性函數Φ(x)轉換後,核函數即變爲兩個五維特徵向量的內積,即K(x^(i),x^(j))=Φ(x^(i))^TΦ(x^(j))

經常使用的核函數

核函數通常和應用場景相關,好比在基因測序領域和文本處理領域,核函數多是不同的,有專門針對特定應用領域進行核函數開發和建模的科研人員在從事這方面的研究。雖然核函數和應用場景相關,但實際上仍是有一些通用的萬金油式的核函數。經常使用的有兩種

1. 多項式核函數,是對輸入特徵向量增長多項式的一種類似性映射函數,其數學表達式爲 

其中y爲正數,c爲非負數。介紹過的線性核函數是多項式核函數在n=1,y=1,c=0處的一種特例。在二維空間裏K(x^(i),x^(j))=x^(i)^Tx^(j)只能表達直線的分隔超平面,而多項式核函數K(x^(i),x^(j))=(yx^(i)^Tx^(j)+c)^n在n>1時,能夠表達更復雜的、非直線的分隔超平面

2. 高斯核函數,其數學表達式爲

若是咱們的輸入特徵是一維的標量,那麼高斯核函數對應的形狀就是一個反鐘形的曲線,其參數σ控制反鐘形的寬度

因爲K(x^(i),x^(j))=Φ(x^(i))^TΦ(x^(j)),通過合適的數學變換,可得高斯核函數對應的特徵轉換函數爲

注意前面無限多項的累加器,其物理意義就是把特徵向量轉換到無限多維向量空間裏,即高斯核函數能夠把輸入特徵向量擴展到無限維空間裏。公式的推到過程會用到泰勒展開式

接下來看一下高斯核函數對應的預測函數

其中K(x^(i),x)是高斯核函數,而ai只是支持向量對應的樣本處不爲0,其餘的樣本爲0

預測函數是中心點在支持向量處的高斯函數的線性組合,其線性組合的係數爲aiy^(i)。所以,高斯函數也稱爲RBF(Radial Basis Function)核函數,即反鐘形函數的線性組合

核函數的對比

線性函數

接觸到底最簡單的核函數,直接計算兩個輸入特徵向量的內積。優勢是簡單、運算效率高。由於不涉及複雜的變換;結果容易解釋,由於老是能生成一個最簡潔的線性分隔超平面。缺點是對線性不可分的數據集沒有很好的辦法

多項式核函數

經過多項式來做爲特徵映射函數,優勢是能夠擬合出複雜的分隔超平面。缺點是可選的參數太多,有Y,c,n這3個參數要選擇,實踐過程當中,選擇一組合適的參數會變得比較困難。另一個缺點是多項式的階數n不宜過高,不然會給模型求解帶來一些計算的困難。

當x^(i)^Tx^(j)<1時,通過n次方運算後,會接近於0,而x^(i)^Tx^(j)>1時,通過n次方運算後,又會變得很是大,這樣核函數就會變得不夠穩定

高斯核函數

高斯核函數能夠把輸入特徵映射到無限多維,因此會比線性核函數功能上要強大不少,而且沒有多項式核函數的數值計算那麼困難,由於它的核函數計算出來的值永遠在[0,1]之間。高斯核函數還有一個優勢是參數容易選擇,由於只有一個參數σ。缺點是不容易解釋,由於映射到無限多維向量空間這個事情顯得不太直觀,計算速度比較慢,容易過擬合。緣由是映射到無限維向量空間,這個是很是複雜的模型,會試圖去擬合全部的樣本,從而形成過擬合

在實踐中怎麼選擇核函數呢?邏輯迴歸算法也能夠用來解決分類問題,究竟是用邏輯迴歸仍是SVM算法呢?

假設n是特徵個數,m是訓練數據集的樣本個數,通常能夠按照下面的規則來選擇算法

若是n相對m來講比較大,例如n=10000,m=10~1000,如文本處理問題,這個時候使用邏輯迴歸或線性函數的SVM算法均可以。

若是n比較小,m中等大小,例如n=1~1000,m=10~10000,那麼能夠使用高斯核函數的SVM算法

若是n比較小,m比較大,例如n=1~1000,m=50000+,那麼通常須要添加特徵,此時須要使用多項式核函數或高斯核函數的SVM算法

更通常性的算法選擇原則是,針對數據量很大的問題,能夠選擇複雜一點的模型。雖然複雜模型容易形成過擬合,但因爲數據量和好難打,能夠有效地彌補過擬合問題。若是數據量比較小,通常須要選擇簡單一點的模型,不然很容易形成過擬合,此時須要注意模型是否欠擬合,若是出現了欠擬合,能夠使用增長多項式特徵的方法糾正欠擬合問題。

scikit-learn裏的SVM

scikit-learn裏對SVM的算法實現都在包sklearn.svm下面,其中SVC類是用來進行分類的任務,SVR是用來進行數值迴歸任務的。

以SVC爲例,首先須要選擇SVM的核函數,由參數kernel指定,其中linear表示本章介紹的線性函數,只能產生直線形狀的分隔超平面;poly表示本章介紹的多項式核函數,用它能夠構建出複雜形狀的分隔超平面,rbf表示高斯核函數

不一樣的核函數須要指定不一樣的參數。針對線性函數,只須要指定參數C,它表示對不符合最大間距規則的樣本的懲罰力度。針對多項式核函數,除了參數C外,還須要指定degree,表示多項式的階數

針對高斯核函數,除了參數C外,還須要指定gamma值,這個值對應的是高斯核函數公式裏的

看一個最簡單的例子,咱們生成一個有兩個特徵,包含兩種類別的數據集,而後用線性核函數的SVM算法進行分類

import numpy as np
import matplotlib.pyplot as plt
from sklearn import svm
from sklearn.datasets import make_blobs


def plot_hyperplance(clf, X, y, h=0.02, draw_sv=True, title='hyperplan'):
    x_min, x_max = X[:, 0].min() - 1, X[:, 0].max() + 1
    y_min, y_max = X[:, 1].min() - 1, X[:, 1].max() + 1
    xx, yy = np.meshgrid(np.arange(x_min, x_max, h), np.arange(y_min, y_max, h))
    plt.title(title)
    plt.xlim(xx.min(), xx.max())
    plt.ylim(yy.min(), yy.max())
    plt.xticks(())
    plt.yticks(())

    Z = clf.predict(np.c_[xx.ravel(), yy.ravel()])
    Z = Z.reshape(xx.shape)
    plt.contourf(xx, yy, Z, cmap='hot', alpha=0.5)

    makers = ['o', 's', '^']
    colors = ['b', 'r', 'c']
    labels = np.unique(y)
    for label in labels:
        plt.scatter(X[y == label][:, 0], X[y == label][:, 1], 
              c=colors[label], marker=makers[label]) if draw_sv: sv = clf.support_vectors_ plt.scatter(sv[:, 0], sv[:, 1], c='y', marker='x') X, y = make_blobs(n_samples=100, centers=2, random_state=0, cluster_std=0.3) clf = svm.SVC(C=1.0, kernel='linear') clf.fit(X, y) plt.figure(figsize=(12, 4), dpi=144) plot_hyperplance(clf, X, y, h=0.01, title='Maximum Margin Hyperplan') plt.show()

輸出圖形以下

帶有X標記的點即爲支持向量,,保存在模型的support_vectors_裏

接着來看另一個例子,生成一個有兩個特徵、包含三種類別的數據集,而後分別構造4個SVM算法來擬合數據集,分別是線性核函數、三階多項式核函數,y=0.5的高斯核函數,y=0.1的高斯核函數

最後把這4個SVM算法擬合出來的分隔超平面畫出來

X, y = make_blobs(n_samples=100, centers=3, random_state=0, cluster_std=0.8)
clf_linear = svm.SVC(C=1.0, kernel='linear')
clf_poly = svm.SVC(C=1.0, kernel='poly', degree=3)
clf_rbf = svm.SVC(C=1.0, kernel='rbf', gamma=0.5)
clf_rbf2 = svm.SVC(C=1.0, kernel='rbf', gamma=0.1)

plt.figure(figsize=(10, 10), dpi=144)
clfs = [clf_linear, clf_poly, clf_rbf, clf_rbf2]
titles = ['Linear Kernel', 'Polynomial Kernel with Degree=3',
          'Gaussian Kernel with $\gamma=0.5$',
          'Gaussian Kernel with $\gamma=0.1$']
for clf, i in zip(clfs, range(len(clfs))):
    clf.fit(X, y)
    plt.subplot(2, 2, i + 1)
    plot_hyperplance(clf, X, y, title=titles[i])

plt.show()

輸出圖形以下,其中帶有標記的點即爲支持向量

左上角是線性核函數,只能擬合出直線分隔超平面。

右上角是三階多項式核函數,能擬合出複雜曲線分隔超平面

左下角是y=0.5的高斯核函數,右下角是y=0.1的高斯核函數

經過調整參數y的值,能夠調整分隔超平面的形狀。y值太大,容易形成過擬合,y值過小,高斯核函數會退化成線性核函數。

實例:乳腺癌檢測

使用邏輯迴歸算法進行了乳腺癌檢測模型的學習和訓練。此次使用支持向量機來解決這個問題

from sklearn.datasets import load_breast_cancer
from sklearn.model_selection import train_test_split

cancer = load_breast_cancer()
X = cancer.data
y = cancer.target
print('data shape:{0}; no. positive:{1}; no. negative:{2}'
.format(X.shape, y[y == 1].shape[0], y[y == 0].shape[0]))
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2)

輸出結果以下

data shape:(569, 30); no. positive:357; no. negative:212 

能夠看出來,咱們的數據集很小。高斯核函數太複雜,容易形成過擬合,模型效果應該不會很好。

先使用高斯核函數試一下

from sklearn.svm import SVC

clf = SVC(C=1.0, kernel='rbf', gamma=0.1)
clf.fit(X_train, y_train)
train_score = clf.score(X_train, y_train)
test_score = clf.score(X_test, y_test)
print('train score:{0};test score:{1}'.format(train_score, test_score))

輸出結果以下

train score:1.0;test score:0.7280701754385965 

訓練數據集分數接近滿分,而交叉驗證數據集的評分很低,這是典型的過擬合現象。代碼中選擇了gamma參數爲0.1,這個值相對已經比較小了

固然,能夠自動來選擇參數。使用GridSearchCV來自動選擇參數。來看看若是使用高斯模型,最優的gamma參數值是多少

def plot_param_curve(plt, train_sizes, cv_results, xlabel):
    train_scores_mean = cv_results['mean_train_score']
    train_scores_std = cv_results['std_train_score']
    test_scores_mean = cv_results['mean_test_score']
    test_scores_std = cv_results['std_test_score']
    plt.title('parameters turning')
    plt.grid()
    plt.xlabel(xlabel)
    plt.ylabel('score')
    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, '.--', color="r",
             label="Training score")
    plt.plot(train_sizes, test_scores_mean, '.-', color="g",
             label="Cross-validation score")

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

from sklearn.svm import SVC
from sklearn.model_selection import GridSearchCV

gammas = np.linspace(0,0.0003,30)
param_grid = {'gamma':gammas}
clf = GridSearchCV(SVC(),param_grid,cv=5)
clf.fit(X,y)
print('best param :{0}; best score:{1}'.format(clf.best_params_,clf.best_score_))
plt.figure(figsize=(10,4),dpi=144)
plot_param_curve(plt,gammas,clf.cv_results_,xlabel='gamma')
plt.show()

輸出結果以下

best param :{'gamma': 0.00011379310344827585}; best score:0.9367311072056239 

因而可知,即便最好的gamma參數下,平均最優得分也只是0.93, 咱們選擇在gamma爲0.01時,畫出學習曲線

import time
from sklearn.svm import SVC
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)
title = 'Learning Curves for Gaussian Kernel'
start = time.clock()
plt.figure(figsize=(10, 4), dpi=144)
plot_learning_curve(plt, SVC(C=1.0, kernel='rbf', gamma=0.01),
                    title, X, y, ylim=(0.5, 1.01), cv=cv)
print('elaspe:{0:.6f}'.format(time.clock() - start))
plt.show()

畫出來的圖形以下

這是明顯的過擬合現象,交叉驗證數據集的評分很是低,且離訓練數據集評分很是遠

接下來換一個模型,使用二階多項式核函數來擬合模型

from sklearn.svm import SVC

clf = SVC(C=1.0, kernel='poly', degree=2)
clf.fit(X_train, y_train)
train_score = clf.score(X_train, y_train)
test_score = clf.score(X_test, y_test)
print('train score:{0}; test score:{1};'.format(train_score, test_score))

輸出結果以下

train score:0.9758241758241758; test score:0.956140350877193; 

結果好多了,做爲對比,咱們畫出一階多項式和二階多項式的學習曲線

cv = ShuffleSplit(n_splits=5, test_size=0.2, random_state=0)
    title = 'Learning Curves with degree={0}'
    degrees = [1, 2]

    start = time.clock()
    plt.figure(figsize=(12, 4), dpi=144)
    for i in range(len(degrees)):
        plt.subplot(1, len(degrees), i + 1)
        plot_learning_curve(plt, SVC(C=1.0, kernel='poly', degree=degrees[i]),
                            title.format(degrees[i]), X, y, 
                  ylim
=(0.8, 1.01), cv=cv, n_jobs=4) print('elaspe: {0:.6f}'.format(time.clock()-start)) plt.show()

注意,須要使用if __name__ == '__main__' 來說代碼括起來

上圖中能夠看出,二階多項式核函數的擬合效果更好。平均交叉驗證數據集評分可達0.950,最高時候達到0.975。二階多項式核函數計算代價很高

咱們使用邏輯迴歸算法來處理乳腺癌檢測問題時,使用二階多項式增長特徵,同時使用L1範數做爲正則項,其擬合效果比這裏的支持向量機效果好。

更重要的是邏輯迴歸算法的運算效率遠遠高於二階多項式核函數的支持向量機算法。這裏的支持向量機算法的效果仍是比使用L2範數做爲正則項的邏輯迴歸算法好的。

樸素貝葉斯算法

樸素貝葉斯是一種基於機率統計分類方法。在條件獨立假設的基礎上,使用貝葉斯定理構建算法在文本處理領域有普遍的應用。

貝葉斯定理

某警察使用一個假冒僞劣的呼吸測試儀來測試司機是否醉駕。假設這個儀器有5%的機率會把一個正常的司機判斷爲醉駕,但對真正醉駕的司機其測試結果是100%準確的。從過往的統計的值,大概有0.1%的司機爲醉駕。假設該警察隨機攔下一個司機,讓他作呼氣測試,儀器測試結果爲醉駕。僅憑這一結果判斷,這位司機真的是醉駕的機率有多高?

真實的結果是不到2%,若是沒有其餘方式,僅憑這個儀器的測試結果來判斷,其實準確率是很是低的。

假設咱們的樣本里有1000人,這1000人裏面有0.1%的機率爲醉駕,既有1位是真正醉駕的司機,999位正常。這999位司機,有5%的機率會被誤判,因此醉駕的機率是1/(1+999*5%)=1.96%

貝葉斯定理是計算這類條件機率問題的絕佳方法。記P(A|B) 表示觀察到事件B發生時事件A發生的機率,則貝葉斯定理的數學表達式爲

回到例子裏,記事件A爲司機真正醉駕事件B爲儀器顯示司機醉駕。則例子裏要求解的問題即爲P(A|B),觀察到儀器顯示實際醉駕時,司機真正醉駕時的機率是多少。

P(A) 表示司機真正醉駕的機率,這是先驗機率,例子裏的數值是0.1%。P(B|A) 表示當司機真正醉駕時,儀器顯示司機醉駕的機率是多少,從例子裏的數據得知是100%。P(B)表示儀器顯示司機醉駕的機率,這裏有兩部分數據,針對真正醉駕的司機(0.1%),儀器能100%檢測出來,所以這部分的數值爲0.1%*100%

針對正常的司機(1-0.1%),儀器顯示醉駕的機率爲(1-0.1%)*5%,代入貝葉斯定理便可得

P(A|B)=0.1%*100%/[0.1%*100%+(1-0.1%)*5%]=1.96%

樸素貝葉斯分類法

假設有一個已標記的數據集[x^i,y^i],其中y^i∈[C1,C2,.....Cb],即數據集總共有b個類別;x^i∈[x1,x2,....,xn],即總共有n個輸入特徵。針對一個新的樣本x,咱們要預測y的值,即對x進行分類。這是典型的機器學習裏的分類問題。

使用統計學的語言能夠描述爲:當觀察到輸入樣本是x時,其所屬於的類別y=Ck的機率,使用條件機率公式表示爲

其中,Ck∈[C1,C2,....,Cb],只須要分別求出全部b個類別的機率,而後取機率最大的那個Ck便是x所屬的類別。直接求解上述公式比較困難,能夠應用貝葉斯定理進行一次變換

對於一個肯定的數據集,Ck,P(x)都是固定的值。所以

其中,∞表示成正比的意思。所以,只須要求解,針對不一樣的Ck的狀況下,p(Ck)P(x|Ck)的最大值便可知道,x屬於哪一個類別。根據聯合機率公式可得

聯合機率表示的是一種機率疊加。例如,你走在路上碰見美女的一個隨機事件,美女對你一見傾心是另外一個隨機事件,那麼你在路上遇到美女且對你一見傾心的機率要怎麼計算呢?

使用機率疊加來計算,遇到美女的機率乘以美女對你一見傾心的機率(條件機率)

由於x是有n個特徵向量,即x=[x1,x2,...,xn]可得

根據鏈式法則及條件機率的定義,能夠進一步推導公式

樸素指的是條件獨立假設,即事件之間沒有關聯關係。例如,擲一個質地均勻的骰子兩次,先後之間出現的數字是獨立、不相關,咱們稱這兩個事件是條件獨立。樸素貝葉斯算法的前提是,輸入特徵須要知足條件獨立假設。即當i != j時,xi和xj是不相關的,通俗來講就是xi事件是否發生和xj不要緊。根據條件獨立的原則

其中∏是連乘符號。P(Ck) 表示每種類別出現的機率,這個值能夠很容易地從數據集裏統計出來。P(xi|Ck) 表示當類別爲Ck時,特徵xi出現的機率,這個值也能夠從數據集中統計出來。這就是樸素貝葉斯分類法的數學原理。

簡單的例子

假設有如下關於駕齡、平均車速和性別的統計數據

如今觀察到一個駕齡爲2年的人,平均車速爲80。問這我的的性別是什麼?

假設C0表示女,C1表示男,x0表示駕齡,x1表示平均車速。先計算這我的爲女性的機率相對值。根據統計數據,女司機的機率P(C0)=5/10=0.5。駕齡爲2年的女司機機率即P(x0|C0)=1/10=0.1。平均車速爲80的女性司機機率P(x1|C0)=1/10=0.1.根據樸素貝葉斯分類法的數學公式

接着計算這我的爲男性的機率相對值。根據統計數據,不可貴出男性司機的機率P(C1)=5/10=0.5。駕齡爲2年的男性司機的機率P(x0|C1)=2/10=0.2。平均車速爲80的男性司機機率P(x1|C1)=3/10=0.3。根據樸素貝葉斯分類法的數學公式

從相對機率來看,這我的是男性的機率是女性的6倍,據此判斷這我的是男性。咱們也能夠從相對機率裏算出絕對機率,這我的是男性的機率是0.03/(0.02+0.005)=0.857

機率分佈

樸素貝葉斯分類法是根據數據集裏的數據,計算出絕對機率來進行求解。在看一遍數學公式

其中,P(xi|Ck) 表示在類別Ck裏特徵xi出現的機率。這裏有個最大的問題,若是數據集過小,那麼從數據集裏計算出來的機率誤差將很是嚴重。

例如,觀察一個質地均勻的骰子投擲6次的結果是[1,3,1,5,3,3]。每一個點的出現機率都是1/6,若是根據觀察到的數據集取計算每一個點的機率,和真實的機率相差將是很是大的。

能夠使用機率分佈來計算機率,而不是從數據集裏計算機率

機率統計的基本概念 

人的身高是一個連續的隨機變量,而投擲一個骰子獲得的點數則是一個離散隨機變量。

若是咱們隨便找一我的,那麼這我的身高170的可能性是多大呢?若是能描述人類身高的可能性,那麼直接把170代入便可求出這個可能性。這個函數就是機率密度函數,也稱爲PDF(Probability Density Function)。

典型的機率密度函數是高斯分佈函數,如人類的身高就知足高斯分佈的規律

好比,投擲一個骰子,獲得6的機率是多少呢?1/6

假若有一個函數f(x),能描述骰子出現x點數([x∈[1,6]])的機率,那麼把x代入便可獲得機率,這個函數稱爲機率質量函數,即PMF(Probability Mass Function)。

那麼爲何還要使用機率質量函數呢?

一是在數據上追求統一性,二是並非全部的離散隨機變量的機率分佈都像投擲骰子那麼直觀。

總結一下:隨機變量分紅兩種,一種是連續隨機變量,另外一種是離散隨機變量。機率密度函數描述的是連續隨機變量在某個特定值的可能性,機率質量函數描述的是離散隨機變量在某個特定值的可能性。而機率分佈則是描述隨機變量取值的機率規律。

多項式分佈

拋一個硬幣,要麼正面朝上,要麼反面朝上。假如出現正面的機率是p,則出現反面的機率就是1-p。

符合這種規律的機率分佈,稱爲伯努利分佈(Bernoulli Distribution),其機率質量函數爲

其中,k∈[0,1],p是出現1的機率。一枚質地均勻的硬幣被拋一次,獲得正面的機率爲0.5.代入上述公式,也能夠獲得相同的結果,即f(1;0.5)=0.51*(1-0.5)0=0.5*1=0.5

更通常的狀況,即不止兩種可能性時,假設每種可能性時pi,則知足Σpi=1條件的機率分佈,稱爲類別分佈(Categorical Distributtion)

例如,投擲一個骰子,則會出現6種可能性,全部的可能性加起來的機率爲1.類別分佈的機率質量函數爲:

其中,π是連乘符號,k是類別的數量,pi是第i種類別的機率,xi當且僅當類別x爲類別i時,其值爲1,其餘狀況其值爲0。例如,針對質地均勻的骰子,k的值爲6,pi的值爲1/6.

問:投擲這個骰子獲得3的機率是多少,答案是1/6。代入機率質量函數驗算一下

針對全部i!=3的狀況,xi=0,針對i=3的狀況,xi=1,因此f(3|p)=1/6

問:一枚硬幣被拋10次,出現3次正面的機率是多少。這是典型的二項式分佈問題。二項式分佈指的是把符合伯努利分佈的實驗作了n次,結果1出現0次,1次...n次的機率分別是多少,機率質量函數爲:

其中,k是結果1出現的次數,k∈[0,1,....,n],n是實驗的總次數,p是在一次實驗中結果1出現的機率。怎麼理解這個公式呢?

總共進行了n次實驗,那麼出現k次結果1的機率爲pk,剩下的一定是結果0的次數,即出現了n-k次,其機率爲(1-p)n-k。公式前面的係數表示的是組合,即k次結果1能夠是任意的組合,好比多是前k次是結果1,也多是後k次出現的結果是1.

那麼硬幣被拋10次,出現3次正面的機率是多少呢?代入二項式分佈的機率質量函數,獲得

再看一個更簡單的例子。問:一枚硬幣被拋出1次,出現0次正面的機率是多少?代入二項式分佈的機率質量函數,獲得:

其中,0的階乘爲1,即0!=1。結果跟咱們預期的相符,當實驗只作一次時,二項式分佈退化爲伯努利分佈

多項式分佈是指知足類別分佈的實驗,連續作n次後,每種類別出現的特定次數組合的機率分佈狀況。

假設xi表示類別i出現的次數,pi表示類別i在單次實驗中出現的機率。當知足前提條件

時,由隨機變量xi構成的隨機向量X=[x1,...,xk] 知足如下分佈函數

其中,P是由各個類別的機率構成的向量,即P=[p1,...,pk],k表示類別的總數,n表示實驗進行的總次數。

按照特定順序,全部類別出現的某個特定的次數組合的機率,例如投6次骰子,出現(1,2,3,4,5,6)這樣特定順序組合的機率。前面的係數表示組合的個數,如投6次骰子,每一個點數都出現一次,能夠是(1,2,3,4,5,6),也能夠是1,3,2,4,5,6

再看一個例子,同時投擲6個骰子,出現1,2,3,4,5,6這種組合的機率是多少?能夠把這個問題轉換成連續6次投擲骰子,每一個類別都出現一次的機率。這是典型的多項式分佈問題,其中隨機向量X=[1,1,1,1,1,1],代入多項式分佈的機率質量函數可得

將質地均勻的骰子投擲6次,獲得4個4的機率是多少?把這個問題轉換爲二項式分佈問題,投擲1次骰子時,獲得4的機率是1/6,獲得其餘點數的機率是5/6.如今須要計算投擲6次骰子獲得4個4的機率,代入二項式分佈機率質量函數可得

再來算一下同時投擲6個質地均勻的骰子,出現5個1的機率是多少?仍是轉換爲二項式分佈問題:

總結一下,二項式分佈描述的是屢次伯努利實驗中,某個結果出現次數的機率。多項式分佈描述的是屢次進行知足類別分佈的實驗中,全部類別出現的次數組合的分佈

二項式分佈和多項式分佈組合樸素貝葉斯算法,常常被用來實現文章分類算法。

例如:有一個論壇須要對用戶的評論進行過濾,屏蔽不文明的評論。首先須要一個通過標記的數據集,稱爲語料庫。假設使用人工標記的方法對評論進行人工標記,標記爲1表示包含不文明用語的評論,標記爲0表示正常評論。

假設咱們的詞庫大小爲k,則文章中出現的某個詞能夠當作是一次知足k個類別的類別分佈實驗。咱們知道一篇評論是由n個詞組成的,所以一篇文章能夠當作是進行n次符合類別分佈的試驗後的產物。由此得知,一篇評論文章服從多項式分佈,它是詞庫裏的全部詞語出現的次數組合構成的隨機向量。

通常狀況下,詞庫比較大,評論文章只是由少許詞組成,因此這個隨機向量是很稀疏的,即大部分元素爲0.經過分析語料庫,容易統計出每一個詞出現不文明評論及正常評論文章裏的機率,即pi的值。同時針對帶預測的評論文章,能夠統計出詞庫裏的全部詞在這篇文章裏的出現次數,即xi的值及評論文章的詞語個數n。代入多項式分佈的機率質量函數

能夠求出,待預測的評論文章構成的隨即向量X,其爲不文明評論的相對機率。同理也可求出其爲正常評論的相對機率,經過比較兩個相對機率,就能夠對這篇文章輸出一個預測值。固然,實際應用中,涉及大量的天然語言處理的手段,包括中文分詞技術,詞的數學表示等。

高斯分佈

連續值怎麼用樸素貝葉斯算法來處理呢?

能夠用區間把連續值轉換爲離散值。例如把[0,40]之間的平均車速做爲一個級別,把[40,80]之間的平均車速做爲一個級別,再把80以上的車速做爲另一個級別。這樣就能夠把連續的值變成離散的值,從而使用樸素貝葉斯分類法進行處理。另一個方法,是使用連續隨機變量的機率密度函數,把數值轉換爲一個相對機率。

高斯分佈(Gaussian Distribution)也稱爲正態分佈(Normal Distribution)是天然界最多見的一種機率密度函數。人的身高知足高斯分佈,特別高和特別矮的人出現的相對機率都比較低。人的智商也符合高斯分佈,特別聰明和特別笨的人出現的相對機率都比較低。高斯分佈的機率密度函數爲:

其中x爲隨機變量的值,f(x)爲隨機變量的相對機率,μ爲樣本的平均值,其決定了高斯分佈曲線的位置,σ爲標準差,其決定了高斯分佈的幅度,σ值越大,分佈越分散,值越小,分佈越集中。典型的高斯分佈

這裏須要提醒讀者注意高斯分佈的機率密度函數和支持向量機裏的高斯核函數的區別。兩者的核心數學模型是相同的,但目的是不一樣的

連續值的處理

假設有一組身體特徵的統計數據以下

假設某人身高6英尺、體重130英鎊、腳掌8英寸,請問此人的性別是什麼?

根據樸素貝葉斯公式

針對帶預測的這我的的數據x,咱們只須要分別求出男性和女性的相對機率

而後去相對機率較高的性別爲預測值便可。

這裏的困難在於,全部的特徵都是連續變量,沒法根據統計數據計算機率。咱們能夠使用區間法,把連續變量轉換爲離散變量,而後再計算機率。可是因爲數據量較小,這不是一個好辦法。

因爲人類身高、體重、腳掌尺寸知足高斯分佈,所以更好的辦法是使用高斯分佈的機率密度函數來求相對機率。

首先針對男性和女性,分別求出每一個特徵的平均值和方差

接着利用高斯分佈的機率密度函數,來求解男性身高6英尺的相對機率

這裏的關鍵是把連續值(身高)做爲輸入,經過高斯分佈的機率密度函數的處理,直接轉換爲相對機率。這裏是相對機率,因此其值大於1併爲違反機率論規則

使用相同的方法,能夠算出如下數值

因爲p(Male)=0.5,所以這我的是男性的相對機率爲:

使用相同的辦法,能夠算出這我的爲女性的相對機率爲5.3778*10-4.從數據可知,這我的爲女性的機率比男性的機率高了5個數量級,所以判斷這我的爲女性。

實例:文檔分類

在scikit-lerarn裏,樸素貝葉斯算法在sklearn.naive_bayes包裏實現,包含了本章介紹的幾種典型的機率分佈算法。

其中GussianNB實現了高斯分佈的樸素貝葉斯算法,

MultinomialNB實現了多項式分佈的樸素貝葉斯算法,

BernoulliNB實現了伯努利分佈的樸素貝葉斯算法。 

樸素貝葉斯算法在天然語言領域有普遍的應用,本節咱們用MultinomialNB來實現文檔自動分類

獲取數據集 

datasets/mlcomp/dataset-379-20news-18828.zip,解壓到當前目錄下,會生成一個379的目錄

使用train子目錄下的文檔進行模型訓練,而後使用test子目錄下的文檔進行模型預測。

文檔的數學表達

怎麼把一個文檔表達爲計算機能夠理解並處理的信息,是天然語言處理中的一個重要課題,完整的內容能夠寫成鴻篇鉅著

TF-IDF是一種統計方法,用以評估一個詞語對一份文檔的重要程度。TF表示詞頻(Term Frequency),對一份文檔而言,詞頻是特定詞語,在這篇文檔裏出現的次數除以文檔的詞語總數。

例如:一篇文檔總共有1000個詞,其中樸素貝葉斯出現了5次,的 出現了25次,應用出現了12次,那麼他們的詞頻分別是0.005,0.025,0.012

IDF表示一個詞的逆向文檔頻率指數(Inverse Document Frequency),能夠由總文檔數目除以包含該詞語的文檔的數目,再將獲得的商取對數獲得,它表達的是詞語的權重指數。

例如,咱們的數據集總共有1萬篇文檔,其中樸素貝葉斯 只出如今10篇文檔中,則其權重指數

的 在全部的文檔中都出現過,則其權重指數IDF=log(1)=0.應用 在1000篇文檔中出現,則其權重指數

計算出每一個詞的詞頻和權重指數後,二者相乘,便可獲得這個詞在文檔中的重要程度。詞語的重要性隨着它在文檔中出現的次數呈正比例增長,但同時會隨着它在語料庫中出現的頻率呈反比降低。

有了TF-IDF這個工具,能夠把一篇文檔轉換爲一個向量。能夠從數據集(天然語言處理領域也稱爲corpus,即語料庫)裏提取出全部出現的詞語,咱們稱爲詞典。假設詞典裏總共有10000個詞語,則每一個文檔均可以轉化爲一個10000維的向量。其次,針對咱們要轉換的文檔裏出現的每一個詞語,都去計算器TF-IDF的值,並把這個值填入文檔向量裏這個詞所對應的元素上。這樣就完成了把一篇文檔轉換爲一個向量的過程。一個文檔每每只會由詞典裏的一小部分詞語構成,這就意味着這個向量裏的大部分元素都是0.

scikit-learn軟件包裏實現了把文檔轉換爲向量的過程。

首先把訓練用的語料庫讀入內存

from time import time
from sklearn.datasets import load_files

print('loading train dataset ...')
t = time()
news_train = load_files('mlcomp/379/train')
print('summary:{0} documents in {1} categories.'
      .format(len(news_train.data), len(news_train.target_names)))
print('done in {0} seconds'.format(time() - t))

其中379/train目錄下放的就是咱們的語料庫,其中包含20個子目錄,每一個子目錄的名字表示的是文檔的類別,子目錄下包含這種類別的全部文檔。load_files() 函數會從這個目錄裏把全部的文檔都讀入內存,而且自動根據所在的子目錄名稱打上標籤。其中news_train.data是一個數組,裏面包含了全部文檔的文本信息。new_train.target也是一個數組,包含了全部文檔所屬的類別,而news_train.target_names則是類別的名稱,所以,若是咱們想知道第一篇文檔所屬的類別名稱,只須要經過代碼news_train.target_names[news_tarin.target[0]] 便可獲得 

輸出以下

summary:13180 documents in 20 categories.
done in 1.1021101474761963 seconds

語料庫裏總共有13180個文檔,其中分紅20個類別。須要吧這些文檔所有轉換爲由TF-IDF表達的權重信息構成的向量

from sklearn.feature_extraction.text import TfidfVectorizer

print('vectorizing train dataset ...')
t = time()
vectorizer = TfidfVectorizer(encoding='latin-1')
X_train = vectorizer.fit_transform((d for d in news_train.data))
print('n_samples: %d, n_features: %d' % X_train.shape)
print('number of non-zero features in sample [{0}]:{1}'
      .format(news_train.filenames[0], X_train[0].getnnz()))
print("done in {0} seconds".format(time() - t))

其中,TfidfVectorizer類是用來把全部的文檔轉換爲矩陣,該矩陣每行都表明一個文檔,一行中的每一個元素表明一個對應的詞語的重要性,詞語的重要性由TF-IDF來表示。其fit_transform() 方法是fit() 和 transform() 合併起來。其中fit()會先完成語料庫分析、提取詞典等操做,transform()會把對每篇文檔轉換爲向量,最終構成一個矩陣,保存在X_train變量裏。

輸出內容以下

vectorizing train dataset ...
n_samples: 13180, n_features: 130274
number of non-zero features in sample [mlcomp/379/train\talk.politics.misc\17860-178992]:108
done in 6.095609426498413 seconds

詞典總共有130274個詞語,即每篇文檔均可以轉換爲一個130274維的向量。第一篇文檔中,只有108個非零元素,即這篇文檔總共由108個不重複的單詞組成,在這篇文檔中出現的這108個單詞的TF-IDF值會被計算出來,並保存在向量中的指定位置上。X_train是一個維度爲13180130274的稀疏矩陣。

模型訓練

矩陣的每一行表示一個數據樣本,矩陣的每一列表示一個特徵。能夠直接使用MultinomialNB對數據集進行訓練

from sklearn.naive_bayes import MultinomialNB

print('traning models ...'.format(time() - t))
t = time()
y_train = news_train.target
clf = MultinomialNB(alpha=0.0001)
clf.fit(X_train, y_train)
train_score = clf.score(X_train, y_train)
print('train score:{0}'.format(train_score))
print("done in {0} seconds".format(time() - t))

其中alpha表示平滑參數,其值越小,越容易形成過擬合,值太大,容易形成欠擬合。

輸出以下

train score:0.9978755690440061
done in 0.49102783203125 seconds

接着,加載測試數據集,並用一篇文檔來預測是否準確。測試數據集在379/test目錄下

print('loading test dataset ...')
t = time()
news_test = load_files('mlcomp/379/test')
print('summary:{0} documents in {1} categories.'
      .format(len(news_test.data), len(news_test.target_names)))
print('done in {0} seconds'.format(time() - t))

輸出結果以下

summary:5648 documents in 20 categories.
done in 61.90554094314575 seconds

咱們的測試數據集共有5648篇文檔。接着,咱們把文檔向量化

print('vectorizing test dataset ...')
t = time()
X_test = vectorizer.transform((d for d in news_test.data))
y_test = news_test.target
print('n_samples: %d, n_features: %d' % X_test.shape)
print('number of non-zero features in sample [{0}]: {1}'
      .format(news_test.filenames[0], X_test[0].getnnz()))
print('done in %fs' % (time() - t))

 vectorizer變量是咱們處理訓練數據集時用到的向量化的類的實例,此處咱們只須要調用transform()進行TF-DF數值計算便可,不須要再調用fit()進行語料庫分析了。

輸出內容以下

n_samples: 5648, n_features: 130274
number of non-zero features in sample [mlcomp/379/test\rec.autos\7429-103268]: 61
done in 1.642094s

這樣測試數據集也轉換爲一個維度爲5648130274的稀疏矩陣。能夠取測試數據集裏的第一篇文檔初步驗證一下,看訓練出來的模型可否正確地預測這個文檔所屬類別

pred = clf.predict(X_test[0])
print('predict:{0} is in category {1}'
      .format(news_test.filenames[0], news_test.target_names[pred[0]]))
print('actually:{0} is in category {1}'
      .format(news_test.filenames[0], news_test.target_names[news_test.target[0]]))

輸出以下

predict:mlcomp/379/test\rec.autos\7429-103268 is in category rec.autos
actually:mlcomp/379/test\rec.autos\7429-103268 is in category rec.autos

預測的結果和實際結果是相符的

模型評價

雖然經過驗證,說明訓練的模型是可用的,可是不能經過一個樣本的預測來評價模型的準確性。須要對模型有個全方位的評價,scikit-learn軟件包提供了全方位的模型評價工具

首先對測試數據集進行預測

print('predicting test dataset ...')
t0 = time()
pred = clf.predict(X_test)
print('done in %fs' % (time() - t0))

 輸出以下

done in 0.040971s

接着使用classification_report()函數來查看一下針對每一個類別的預測準確性:

from sklearn.metrics import classification_report

print('classification report on test set for classifier: ')
print(clf)
print(classification_report(y_test, pred, target_names=news_test.target_names))

從輸出結果來看,針對每種類別都統計了查準率、召回率和F1-Score。此外,還能夠經過confusion_matrix()函數生成混淆矩陣,觀察每種類別被錯誤分類的狀況。例如,這些被錯誤分類的文檔是被錯誤分類到那些類別裏的

from sklearn.metrics import confusion_matrix

cm = confusion_matrix(y_test,pred)
print('confusion matrix:')
print(cm)

能夠把混淆矩陣進行數據可視化處理

plt.figure(figsize=(8, 8), dpi=144)
plt.title('Confusion matrix of the classifier')
ax = plt.gca()
ax.spines['right'].set_color('none')
ax.spines['top'].set_color('none')
ax.spines['bottom'].set_color('none')
ax.spines['left'].set_color('none')
ax.xaxis.set_ticks_position('none')
ax.yaxis.set_ticks_position('none')
ax.set_xticklabels([])
ax.set_yticklabels([])
plt.matshow(cm, fignum=1, cmap='Greens')
plt.show()

除對角線外,其餘地方顏色越淺,說明此處錯誤越多。經過這些數據,能夠詳細分析樣本數據,找出爲何某種類別會被錯誤地分類到另外一種類別裏,從而進一步優化模型

PCA算法

PCA是Principal Component Analysis的縮寫,中文稱爲主成分分析法。它是一種維數約減(Dimensionality Reduction)算法,即把高維度數據在損失最小的狀況下轉換爲低維度數據的算法。顯然,PCA能夠用來對數據進行壓縮,能夠在可控的失真範圍內提升運算速度。

算法原理

假設須要把一個二維數據減爲一維數據。能夠想辦法找出一個向量u(1)以便讓二維數據的點(方形點)到這個向量所在的直線上的平均距離最短,即投射偏差最小。

這樣就能夠在失真最小的狀況下,把二維數據轉換爲向量u^(1)所在直線上的一維數據。加入須要把三維數據降爲二維數據時,須要找出兩個向量u^(1),u^(2),以便讓三維數據的點在這兩個向量所決定的平面上的投射偏差最小。

若是從數學角度來描述PCA算法就是,當須要從n維數據降爲k維數據時,須要找出k個向量u^(1),u^(2),...u^(k),把n維的數據投射到這k個向量決定的線性空間裏,最終使投射偏差最小化的過程。

假設有一個數據集,用m*n維的矩陣A表示。矩陣中每一行表示一個樣本,每一列表示一個特徵,總共有m個樣本,每一個樣本有n個特徵。目標是減小特徵個數,只保留最重要的k個特徵

數據歸一化和縮放

數據歸一化和縮放是一種數學技巧,提升PCA運算時的效率。數據歸一化的目標是使特徵的均值爲0。數據歸一化公式爲:

其中 aj^(i)是指i個樣本的第j個特徵的值,μj表示的是第j個特徵的均值。當不一樣的特徵值不在同一個數量級上的時候,還須要對數據進行縮放。數據歸一化再縮放的公式爲:

sj表示第j個特徵的範圍,即sj=max(aj^i)-min(aj^i)

計算協方差矩陣的特徵向量

針對預處理後的矩陣X,先計算其協方差矩陣(Covariance Matrix)

其中,Σ表示協方差矩陣,用大寫的Sigma表示,大寫的Sigma和累加運算符看起來幾乎同樣,但這裏實際上是一個數學符號而已,不是累加運算。計算結果Σ將是一個n*n的矩陣。

接着經過奇異值分解來計算協方差舉着你的特徵向量(eigenvectors)

其中,svd是奇異值分解(Singular Value Decomposition)運算,是高級線性代數的內容。通過奇異值分解後,有3個返回值,其中矩陣U是個n*n的矩陣,若是咱們選擇U的列做爲向量,那麼咱們將獲得n個列向量u^(1),u^(2),...,u^(n),這些向量就是協方差矩陣的特徵向量。表示的物理意義是,協方差矩陣Σ能夠由這些特徵向量進行線性組合獲得。

數據降維和恢復

獲得特徵矩陣後,就能夠對數據進行降維處理了。假設降維前的值爲x^(i),降維後爲z^(i),那麼

其中,Ureduct=[u(1),u(2),...,u(k)],它選取自矩陣U的前k個變量,Ureduce稱爲主成分特徵矩陣,它是數據降維和恢復的關鍵中間變量。看一下數據維度,Ureduce是n*k的矩陣,所以是k*n的矩陣,x^(i)是n*1的向量,所以z^(i)是k*1的向量。這樣即完成了數據的降維操做。

也能夠用矩陣運算一次性轉換多個向量,提升效率。假設X是行向量x^(i)組成的矩陣,則

其中,X是m*n的矩陣,所以降維後的矩陣Z也是一個m*k的矩陣。

Z^(i)就是x^(i)在Ureduct構成的線性空間投射,而且其投射偏差最小。

數據降維後,怎麼恢復呢?降維的數據計算公式。因此,若是要還原數據,能夠使用下面的公式

其中,Ureduct是n*k維矩陣,Z^(i)是k維列向量。這樣算出來的x^(i)就是n維列向量。

矩陣化數據恢復運算公式爲:

其中,Xapprox是還原回來的數據,是一個m*n的矩陣,每行表示一個訓練樣例。Z是一個m*k的矩陣,是降維後的數據。

PCA算法示例

假設咱們的數據集總共有5個記錄,每一個記錄有2個特徵,這樣構成的矩陣A爲:

咱們的目標是把二維數據降爲一維數據。爲了更好地理解PCA的計算過程,分別使用Numpy和sklearn對同一個數據進行PCA降維處理。

使用Numpy模擬PCA計算過程

下面用Numpy來模擬PCA降維的過程。首先對數據進行預處理

import numpy as np

A = np.array([[3, 2000], [2, 3000], [4, 5000], [5, 8000], [1, 2000]], dtype='float')
# 數據歸一化
mean = np.mean(A, axis=0)
norm = A - mean
# 數據縮放
scope = np.max(norm, axis=0) - np.min(norm, axis=0)
norm = norm / scope
print(norm)

兩個特徵的均值不在同一個數量級,同時對數據進行了縮放。輸出以下

[[ 0.     -0.33333333]
[-0.25    -0.16666667]
[ 0.25   0.16666667]
[ 0.5     0.66666667]
[-0.5    -0.33333333]]

接着對協方差矩陣進行奇異值分解,求解其特徵向量:

U, S, V = np.linalg.svd(np.dot(norm.T, norm))

輸出以下

[[-0.67710949 -0.73588229]
[-0.73588229 0.67710949]] 

因爲須要把二維數組降爲一維,所以只取特徵矩陣的第一列來構造出Ureduce

U_reduce = U[:, 0].reshape(2, 1)

輸出以下

[[-0.67710949]
[-0.73588229]]

有了主成分特徵矩陣,就能夠對數據進行降維了

R = np.dot(norm, U_reduce)

其輸出以下

[[ 0.2452941 ]
[ 0.29192442]
[-0.29192442]
[-0.82914294]
[ 0.58384884]]

這樣就把二維的數據降維成一維的數據了。若是須要還原數據,依照PCA數據恢復的計算公式,可得:

Z = np.dot(R, U_reduce.T)

輸出結果以下

[[-0.16609096 -0.18050758]
[-0.19766479 -0.21482201]
[ 0.19766479 0.21482201]
[ 0.56142055 0.6101516 ]
[-0.39532959 -0.42964402]]

在數據預處理階段對數據進行了歸一化,而且作了縮放處理,因此須要進一步還原才能獲得原始數據,這一步是數據預處理的逆運算

B = np.multiply(Z, scope) + mean

輸出結果以下

[[2.33563616e+00 2.91695452e+03]
[2.20934082e+00 2.71106794e+03]
[3.79065918e+00 5.28893206e+03]
[5.24568220e+00 7.66090960e+03]
[1.41868164e+00 1.42213588e+03]]

其中np.multiply是矩陣的點乘運算,即對應的元素相乘。對矩陣基礎不熟悉的讀者,能夠搜索矩陣點乘和叉乘的區別。

與原始矩陣A相比,恢復後數據仍是存在必定程序的失真,這種失真是不可避免的。e+0.3表示的是10的3次方

點乘:向量的內積

叉乘:向量的外積

例如:點乘的結果是一個實數 a·b=|a|·|b|·cos<a,b <a,b表示a,b的夾角
叉乘:叉乘的結果是一個向量
當向量a和b不平行的時候
其模的大小爲 |a×b|=|a|·|b|·sin<a,b (其實是ab所構成的平行四邊形的面積) 方向爲 a×b和a,b都垂直 且a,b,a×b成右手系。當a和b平行的時候,結果爲0向量

使用sklearn進行PCA降維運算

sklearn.decomposition.PCA實現了PCA算法,使用方便,不須要了解具體的PCA運算步驟。

但須要注意的是,數據的預處理須要本身完成,其PCA算法實現自己不進行數據預處理(歸一化和縮放)

咱們選擇MinMaxScaler類進行數據預處理

import numpy as np
from sklearn.decomposition import PCA
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import MinMaxScaler

def std_PCA(**argv):
    scaler = MinMaxScaler()
    pca = PCA(**argv)
    pipeline = Pipeline([('scaler', scaler), ('pca', pca)])
    return pipeline

A = np.array([[3, 2000], [2, 3000], [4, 5000], [5, 8000], [1, 2000]], dtype='float')
pca = std_PCA(n_components=1)
R2 = pca.fit_transform(A)
print(R2)

Pipeline做用是把數據預處理和PCA算法組成一個串行流水線。輸出以下

[[-0.2452941 ]
[-0.29192442]
[ 0.29192442]
[ 0.82914294]
[-0.58384884]]

這個輸出值就是矩陣A通過預處理及PCA降維後的數值。

接着把數據恢復回來

R2_R = pca.inverse_transform(R2)

這裏的pca是一個Pipeline實例,其逆運算inverse_transform()是逐級進行的,即先進行PCA還原,再執行預處理的逆運算。具體來講就是先調用PCA.inverse_transform(),而後再調用MinMaxScaler,inverse_transform()

PCA的物理含義

咱們能夠把前面的例子在一個座標軸上所有畫出來,從而觀察PCA降維過程的物理含義

圖中正方形的點是原始數據通過預處理後(歸一化、縮放)的數據,圓形的點是從一維恢復到二維後的數據。同時,咱們畫出主成分特徵向量u^(1),u^(2),根據上圖直觀印象,介紹幾個有意思的結論

首先,圓點實際上就是放點在向量u^(1)所在直線的投射點,所謂的降維,實際上就是方形的點在主成分特徵向量u^(1)上的投影。所謂的PCA數據恢復,並非真正的恢復,只是把降維後的座標轉換爲原座標系中的座標而已。

針對咱們的例子,只是把向量u^(1)決定的一維座標系中的座標轉換爲原始二維座標系中的座標。其次,主成分特徵向量u^(1),u^(2)是互相垂直的。再次,方形點和圓形點之間的距離,就是PCA數據降維後的偏差。

PCA的數據還原率及應用

PCA算法能夠用來對數據進行壓縮,能夠在可控的失真範圍內提升運算速度。

數據還原率 

使用PCA對數據進行壓縮時,涉及失真的度量問題,即壓縮後的數據能在多大程度上還原原數據,稱這一指標爲數據還原率,用百分比表示。假設咱們要求失真度不超過1%,即數據還原率達到99%,怎麼來實現這個要求呢?

k是主成分分析法中主成分的個數。用下面的公式做爲約束條件,從而選擇合適的偏差範圍下,最合適的k值

其中,分子部分表示平均投射偏差的平方;分母部分表示全部訓練樣例到原點距離的平均值。這裏的物理意義用術語能夠描述爲99%的數據真實性被保留下來了。簡單地理解爲壓縮後的數據還原出元數據的準確度爲99%。

另外,經常使用的比率還有0.05,這個時候數據還原率就是95%。實際應用中,能夠根據要解決問題的場景來決定這個比率

假設,咱們的還原率要求是99%,那麼用下面的算法來選擇參數k

1. 讓k=1

2. 運行PCA算法,計算出

3. 利用計算投射偏差率,並判斷是否知足要求,若是不知足要求,k=k+1,繼續步驟2。若是知足要求,k便是咱們選擇的參數

這個算法較容易理解,但實際上效率很是低,由於沒作一次循環都須要運行一遍PCA算法。另外一個更高效的方法是,利用協方差矩陣進行奇異值分解返回的S矩陣:[U,S,V]=svd(Σ)。其中S是個nx n對角矩陣,即只有對角線上值非零時其餘元素均爲0

從數學上能夠證實,投射偏差率也能夠使用下面的公式計算

這樣運算效率大大提升了,只須要進行一次svd運算便可。

加快監督機器學習算法的運算速度

PCA的一個典型應用是用來加快監督學習的速度

例如,有m個訓練數據(x^1,y^1),(x^2,y^2),... ,(x^m,y^m)。其中,x^1是10000維的數據,想象一下,若是這是個圖片分類問題,若是輸入的圖片是100*100分辨率的,那麼咱們就有10000維的輸入數據

使用PCA來加快算法運算速度時,把輸入的數據分解出來x^1,x^2,...,x^m,而後運用PCA算法對輸入數據進行降維壓縮,獲得降維後的數據z^1,z^2,...,z^m,最後獲得新的訓練樣例,利用新的訓練樣例訓練出關於壓縮後的變量z的預測函數hΘ(z)

須要注意,PCA算法只用來處理訓練樣例,運算PCA算法獲得的轉換參數Ureduce能夠用來對交叉驗證數據集

及測試數據集進行轉換。固然,還須要相應地對數據進行歸一化處理或對數據進行縮放

實例:人臉識別

使用自拍的一組照片,來開發一個特定的人臉識別系統。人臉識別,本質上是一個分類問題,須要把人臉圖片當成訓練數據集,對模型進行訓練。訓練好的模型,就能夠對新的人臉照片進行類別預測,這就是人臉識別系統的原理。

加載數據集

數據集總共包含40位人員的照片,每一個人10張照片。datasets/olivetti.pkz

使用下面的代碼來加載這些照片

import logging
from sklearn.datasets import fetch_olivetti_faces

logging.basicConfig(level=logging.INFO, format='%(asctime)s %(message)s')
data_home = 'datasets/'
logging.info('Start to load dataset')
faces = fetch_olivetti_faces(data_home=data_home)
logging.info('Done with load dataset')

加載的圖片數據集保存在faces變量裏,scikit-learn已經替咱們把每張照片作了初步的處理,剪裁成64*64大小且人臉居中顯示。這一步相當重要,不然咱們的模型將被大量的噪聲數據,即圖片背景干擾。由於人臉識別的關鍵是五官紋理和特徵,每張照片的背景都不一樣,人的髮型也可能常常變化, 這些特徵都應該儘可能排隊在輸入特徵以外,最後要成功加載數據集,還須要安裝Python的圖片處理工具包Pillow

成功加載數據後,其data裏保存的就是按照scikit-learn要求的訓練數據集,target裏保存的就是類別目標索引。經過下面的代碼,將數據集的概要信息顯示出來

X = faces.data
y = faces.target
targets = np.unique(faces.target)
target_names = np.array(['c%d' % t for t in targets])
n_targets = target_names.shape[0]
n_samples, h, w = faces.images.shape
print('Sample count:{}; Target count:{}'.format(n_samples, n_targets))
print('Images size:{} x {} Dataset shape: {}'.format(w, h, X.shape))

輸出內容以下

Sample count:400; Target count:40
Images size:64 x 64 Dataset shape: (400, 4096)

從輸出可知,總共有40位人物的照片,圖片總數是400張,輸入特徵由4096。爲了後續區分不一樣人物,用索引號給目標人物命名,並保存在變量target_names裏。爲了更直觀地觀察數據,從每一個人物的照片裏隨機選擇一張顯示出來。先定義一個函數來顯示照片陣列

def plot_gallery(images, titles, h, w, n_row=2, n_col=5):
    plt.figure(figsize=(2 * n_col, 2.2 * n_row), dpi=144)
    plt.subplots_adjust(bottom=0, left=.01, right=.99, top=.90, hspace=.01)
    for i in range(n_row * n_col):
        plt.subplot(n_row, n_col, i + 1)
        plt.imshow(images[i].reshape((h, w)), cmap=plt.cm.gray)
        plt.title(titles[i])
        plt.axis('off')

輸入參數images是一個二維數據,每一行都是一個圖片數據。加載數據時,fetch_olivetti_faces()函數已經幫咱們作了預處理,圖片的每一個像素的RGB值都轉換成了[0,1]的浮點數。所以,咱們畫出來的照片將是黑白的,而不是彩色的。在圖片識別領域,通常狀況下用黑白照片就能夠了,能夠減小計算量,也會讓模型更準確。

接着分紅兩行顯示這些人物的照片:

n_row = 2
n_col = 6
sample_images = None
sample_titles = []
for i in range(n_targets):
    people_images = X[y == i]
    people_sample_index = np.random.randint(0, people_images.shape[0], 1)
    people_sample_image = people_images[people_sample_index, :]
    if sample_images is not None:
        sample_images = np.concatenate((sample_images, people_sample_image), axis=0)
    else:
        sample_images = people_sample_image
    sample_titles.append(target_names[i])

plot_gallery(sample_images, sample_titles, h, w, n_row, n_col)
plt.show()

代碼中,X[y==1] 能夠選擇出屬於特定人物的全部照片,隨機選擇出來的照片都放在sample_images數組對象裏,最後使用咱們以前定義的函數plot_gallery()把照片畫出來

從圖中能夠看到,fetch_olivetti_faces()函數幫咱們剪裁了中間部分,只留下臉部特徵。

最後把數據集劃分紅訓練數據集和測試數據集

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=4)

失敗的嘗試

使用支持向量機SVM來實現人臉識別

from sklearn.svm import SVC

start = time.clock()
print('Fitting train datasets ...')
clf = SVC(class_weight='balanced')
clf.fit(X_train, y_train)
print('Done in {0:.2f}s'.format(time.clock() - start))

指定SVC的class_weight參數,讓SVC模型能根據訓練樣本的數量來均衡地調整權重,這對不均勻的數據集,即目標人物的照片數量相差較大的狀況是很是有幫助的。因爲總共只有400張照片,數據規模較小,模型運行時間不長

接着,針對測試數據集進行預測

start = time.clock()
print('Predicting test dataset ...')
y_pred = clf.predict(X_test)
print('Done in {0:.2f}s'.format(time.clock() - start))

 最後,分別使用confusion_matrix和classification_report來查看模型分類的準確性

from sklearn.metrics import confusion_matrix

cm = confusion_matrix(y_test, y_pred, labels=range(n_targets))
print('confusion matrix')
np.set_printoptions(threshold=np.nan)
print(cm)

np.set_printoptions()是爲了確保完整地輸出cm數組的內容,這是由於這個數組是40*40的,默認狀況下不會所有輸出。

confusion matrix理想的輸出,是矩陣的對角線上有數字,其餘地方都沒有數字。但咱們的結果顯示不是這樣的。能夠明顯看出不少圖片都被預測成索引爲12的類別了。結果看起來徹底不對。再看一下classification_report的結果

from sklearn.metrics import classification_report
print(classification_report(y_test, y_pred, target_names=target_names))

輸出結果以下

40個類別離,查準率、召回率、F1 Score全爲0,不能有更差的預測結果了。爲何這麼差

由於,咱們吧每一個像素都做爲要給輸入特徵來處理,這樣的數據噪聲太嚴重了,模型根本沒有辦法對訓練數據集進行擬合。總共有4096個特徵,但是數據集大小才400個,比特徵個數還少,並且咱們還須要吧數據集分出20%來做爲測試數據集,這樣訓練數據集更小了。這樣的情況下,模型根本沒法進行準確地訓練和預測。

使用PCA來處理數據集

解決上述問題的一個辦法是使用PCA來給數據降維,只選擇前k個最重要的特徵。選擇多少個特徵合適呢?怎麼肯定k的值呢?PCA算法能夠經過下面的公式來計算失真幅度

在scikit-learn裏,能夠從PCA模型的explained_variance_ration_變量裏獲取PCA處理後的數據還原率。這是一個數組,全部元素求和便可知道咱們選擇的k值的數據還原率,數值越大說明失真越小,隨着k值的增大,數值會無限接近於1.

利用這一特徵,可讓k取值10~300之間,每隔30進行一次取樣。在全部的k值樣本下,計算通過PCA算法處理後的數據還原率。而後根據數據還原率要求,來肯定合理的k值。針對咱們的狀況,選擇失真度小於5%,即PCA處理後能保留95%的原數據信息。

from sklearn.decomposition import PCA

print('Exploring explained variance ratio for dataset ...')
candidate_components = range(10, 300, 30)
explained_ratios = []
start = time.clock()
for c in candidate_components:
    pca = PCA(n_components=c)
    X_pca = pca.fit_transform(X)
    explained_ratios.append(np.sum(pca.explained_variance_ratio_))
print('Done in {0:.2f}s'.format(time.clock() - start))

根據不一樣的k值,構建PCA模型,而後調用fit_transform函數來處理數據集,再把模型處理後數據還原率,放入explained_ratios數組。接着吧這個數組畫出來

plt.figure(figsize=(10, 6), dpi=144)
plt.grid()
plt.plot(candidate_components, explained_ratios)
plt.xlabel('Number of PCA Components')
plt.ylabel('Explained variance ratio for PCA')
plt.title('Explained variance ratio for PCA')
plt.yticks(np.arange(0.5, 1.05, .05))
plt.xticks(np.arange(0, 300, 20))
plt.show()

圖形以下

橫座標表示k值,縱座標表示數據還原率。要保留95%以上的數據還原率,k值選擇140便可。也很是容易地找出不一樣的數據還原率對應的k值。爲了更直觀地觀察和對比在不一樣數據還原率下的數據,咱們選擇數據還原率分別在95%、90%、80%、70%、60%的狀況下,畫出經PCA處理後的圖片,對應的k值分別是140、7五、3七、1九、8

爲了方便,這裏直接選擇在上圖裏畫出的人物前5位做爲咱們的樣本圖片。每行畫出5個圖片,先畫出原圖,接着再畫出每行在不一樣數據還原率下對應的圖片

def title_prefix(prefix, title):
    return "{}: {}".format(prefix, title)

n_row, n_col = 1, 5
sample_images = sample_images[0:5]
sample_titles = sample_titles[0:5]
plotting_images = sample_images
plotting_titles = [title_prefix('orig', t) for t in sample_titles]
candidate_components = [140, 75, 37, 19, 8]
for c in candidate_components:
    print('Fitting and projecting on PCA(n_components={}) ...'.format(c))
    start = time.clock()
    pca = PCA(n_components=c)
    pca.fit(X)
    X_sample_pca = pca.transform(sample_images)
    X_sample_inv = pca.inverse_transform(X_sample_pca)
    plotting_images = np.concatenate((plotting_images, X_sample_inv), axis=0)
    sample_title_pca = [title_prefix('{}'.format(c), t) for t in sample_titles]
    plotting_titles = np.concatenate((plotting_titles, sample_title_pca), axis=0)
    print('Done in {0:.2f}s'.format(time.clock() - start))

print('Plotting sample images with different number of PCA conpoments ...')
plot_gallery(plotting_images, plotting_titles, h, w, n_row * (len(candidate_components) + 1), n_col)

代碼裏,把全部的圖片收集金plotting_images數組,而後調用前面定義的plot_gallery()函數一次性地畫出來。 

代碼裏,把全部的圖片收集進plotting_images數組,而後調用前面定義的plot_gallery()函數一次性地畫出來。

第一行顯示的原圖,第二行顯示的是數據還原度在95%處,即k=140的圖片。第三行顯示的是數據還原度在90%,即k=90的圖片;

即便在k=8時,圖片依然能比較清楚地反映出人物的臉部特徵輪廓

最終結果

選擇k=140做爲PCA參數,對訓練數據集和測試數據集進行特徵提取

n_components = 140
print('Fitting PCA by using training data ...')
start = time.clock()
pca = PCA(n_components=n_components, svd_solver='randomized', whiten=True).fit(X_train)
print('Done in {0:.2f}s'.format(time.clock() - start))
print('Projecting input data for PCA ...')
start = time.clock()
X_train_pca = pca.transform(X_train)
X_test_pca = pca.transform(X_test)
print('Done in {0:.2f}s'.format(time.clock() - start))

接着使用GridSearchCV來選擇一個最佳的SVC模型參數,而後使用最佳參數對模型,而後使用最佳參數模型進行訓練。

from sklearn.svm import SVC
    from sklearn.model_selection import GridSearchCV

    print('Searching the best parameters for SVC ...')
    param_grid = {'C': [1, 5, 10, 50, 100],
                  'gamma': [0.0001, 0.0005, 0.001, 0.005, 0.01]}
    clf = GridSearchCV(SVC(kernel='rbf', class_weight='balanced'), param_grid, verbose=2, n_jobs=4)
    clf = clf.fit(X_train_pca, y_train)
    print('Best parameters found by grid search:')
    print(clf.best_params_)

經過設置n_jobs=4來啓動4個線程併發執行,同時設置verbose=2來輸出一些過程信息。最終選擇出來的最佳模型參數以下

Best parameters found by grid search:
{'C': 10, 'gamma': 0.001}

接着使用這一模型對測試樣本進行預測,而且使用confusion_matrix輸出預測準確性信息。

start = time.clock()
    print('Predict test dataset ...')
    y_pred = clf.best_estimator_.predict(X_test_pca)
    cm = confusion_matrix(y_test,y_pred,labels=range(n_targets))
    print('Done in {0:.2f}s'.format(time.clock() - start))
    print('confusion matrix:')
    np.set_printoptions(threshold=np.nan)
    print(cm)

大部分預測結果都正確。再使用classification_report輸出分類報告,查看測準率、召回率、F1 Score

print(classification_report(y_test,y_pred,target_names=target_names))

總共有400張圖片,每位目標人物只有10張圖片的狀況下,測準率和召回率平均達到了0.95以上。

k-均值算法

k-均值算法是一種典型的無監督機器學習算法,用來解決聚類問題(Clustering)。因爲數據標記須要耗費巨大的資源,無監督或半監督的學習算法嘴來逐漸受到了學者青睞,由於不須要對數據進行標記,能夠大大減小工做量。

算法原理

針對監督式學習算法,如k-近鄰算法,其輸入數據是已經標記的,目標是找出分類邊界,而後對新的數據進行分類。而無監督式學習算法,如k-均值算法,值給出一組無標記的數據集x^1,x^2,...,x^m,目標是找出這組數據的模型特徵,如那些數據是同一種類型的,那些數據是另一種類型。典型的無監督學習包括市場細分,即經過分析用戶數據,把一個產品的市場進行細分, 找出細分人羣。另一個是社交網絡分析,分析社交網絡中參與人員的不一樣特色,根據特色區分出不一樣羣體。這些都是無監督式學習力的聚類(Clustering)問題

k-均值算法包含如下兩個步驟:

1. 給聚類中心分配點。計算全部的訓練樣例,把每一個訓練樣例分配到距離其最近的聚類中心所在的類別裏。

2. 移動聚類中心。新的聚類中心移動到這個聚類全部的點的平均值處

一直重複作上面的動做,直到聚類中心再也不移動爲止,這時就探索出了數據集的結構了。

也能夠用數學方式來描述k-均值算法。算法有兩個輸入信息,一是k,表示選取的聚類個數;另外一個是訓練數據集x^1,x^2,...,x^m

1. 隨機選擇k個聚類中心,u1,u2,...,uk

2. 從1~m中遍歷全部的數據集,計算x^i分別到u1,u2,...,uk的距離,記錄距離最短的聚類中心點uj(1<=j<=k),而後把x^i這個點分配給這個聚類。即令c^i=j。計算距離時,通常使用 ||x^i-uj|| 來計算。

3. 從1~k中遍歷全部的聚類中心,移動聚類中心的新位置到這個聚類的均值處。即

4. 重複步驟2,直到聚類中心再也不移動爲止

k-均值算法成本函數

根據成本函數的定義,成本即模型預測值與實際值的偏差,據此不可貴出k-均值算法的成本函數:

其中,c^i是訓練樣例x^i分配的聚類序號;是x^i所屬聚類的中心點。k-均值算法的成本函數的物理意義就是,訓練樣例到其所屬的聚類中心點的距離的平均值。

隨機初始化聚類中心點

假設k是聚類的個數,m是訓練樣本的個數,那麼一定有k<m。在隨機初始化時,隨機從m個訓練數據集裏選擇k個樣本做爲聚類中心點。這是正式推薦的隨機初始化聚類中心的作法。

最終聚類結果會和隨機初始化的聚類中心點有關。即不一樣的隨機初始化的聚類中心點可能獲得不一樣的最終聚類結果。由於成本函數可能會收斂在一個局部最優解,而不是全局最優解上。有一個解決方法就是多作幾回隨機初始化的動做,而後訓練出不一樣的聚類中心點和聚類節點分配方案,而後用這些值算出成本函數,從中選擇成本最小的那個函數。

假設咱們作1000次運算,步驟以下

1. 隨機選擇k個聚類中心點

2. 運行k-均值算法,算出c^1,c^2,...,c^m和u1,u2,...,uk

3. 使用c^1,c^2,...,c^m和u1,u2,...,uk算出最終的成本值

4. 記錄最小的成本值,而後調回步驟1,直到達到最大運算次數

這樣就能夠適當加大運算次數,從而求出全局最優解

選擇聚類的個數

怎麼選擇合適的聚類個數呢?實際上聚類個數和業務有緊密的關聯。例如咱們要對運動鞋的尺碼大小進行聚類分析,那麼分紅5個尺寸等級好仍是分紅10個尺寸等級好呢?這是業務問題

從技術角度來說,也有一些方法能夠用來作一些判斷的。能夠把聚類個數做爲橫座標,成本函數做爲縱座標,把成本和聚類個數的數據畫出來。大致的趨勢是隨着k值愈來愈大,成本會愈來愈低。找出一個拐點,在這個拐點以前成本降低比較快,在這個拐點以後,成本降低比較慢,那麼頗有可能這個拐點所在的k值就是尋求的最優解。

scikit-learn裏的k-均值算法

由sklearn.cluster.KMeans類實現。下面來看一下如何使用

from sklearn.datasets import make_blobs

X, y = make_blobs(n_samples=200, n_features=2, centers=4,
                  cluster_std=1, center_box=(-10.0, 10.0), shuffle=True,
                  random_state=1)

而後把樣本畫出來

plt.figure(figsize=(6, 4), dpi=144)
plt.xticks(())
plt.yticks(())
plt.scatter(X[:, 0], X[:, 1], s=20, marker='o')
plt.show()

接着使用KMeans模型來擬合。設置類別數量爲3,並計算出其擬合後的成本

from sklearn.cluster import KMeans

n_clusters = 3
kmean = KMeans(n_clusters=n_clusters)
kmean.fit(X)
print('kmean:k={},cost={}'.format(n_clusters, int(kmean.score(X))))

輸出結果以下

kmean:k=3,cost=-668

KMeans.score()函數計算k-均值算法擬合後的成本,用負數表示,其絕對值越大,說明成本越高。

k-均值算法成本的物理意義爲訓練樣例到其所屬的聚類中心點的距離的平均值,在scikit-learn裏,其計算成本的方法略有不一樣,是計算訓練樣例到其所屬的聚類中心點的距離的總和。

固然,還能夠把分類後的樣本及所屬的聚類中心都畫出來,這樣能夠更直觀地觀察算法的擬合結果。

labels = kmean.labels_
centers = kmean.cluster_centers_
markers = ['o', '^', '*']
colors = ['r', 'b', 'y']
plt.figure(figsize=(6, 4), dpi=144)
plt.xticks(())
plt.yticks(())
for c in range(n_clusters):
    cluster = X[labels == c]
    plt.scatter(cluster[:, 0], cluster[:, 1], marker=markers[c], s=20, c=colors[c])
plt.scatter(centers[:, 0], centers[:, 1], marker='o', c='white', alpha=0.9, s=300)
for i, c in enumerate(centers):
    plt.scatter(c[0], c[1], marker='$%d$' % i, s=50, c=colors[i])

plt.show()

k-均值算法的一個關鍵參數是k,即聚類個數。從技術角度來說,k值越大,算法成本越低,這個很容易理解。

但從業務角度來看,不是k值越大越好。針對這個例子,分別選擇k2,3,4這三種不一樣的聚類個數,來觀察一下k-均值算法最終擬合的結果及其成本。

畫出K-均值聚類結果的代碼稍稍改造一下,變成一個函數。這個函數會使用k-均值算法來進行聚類擬合,同時會畫出按照這個聚類個數擬合後的分類狀況

from sklearn.cluster import KMeans
def fit_plot_kmean_model(n_clusters, X):
    plt.xticks(())
    plt.yticks(())
    # 使用k-均值算法進行擬合
    kmean = KMeans(n_clusters=n_clusters)
    kmean.fit_predict(X)
    labels = kmean.labels_
    centers = kmean.cluster_centers_
    markers = ['o', '^,', '*', 's']
    colors = ['r', 'b', 'y', 'k']
    # 計算成本
    score = kmean.score(X)
    plt.title('K={},score={}'.format(n_clusters, int(score)))
    # 畫樣本
    for c in range(n_clusters):
        cluster = X[labels == c]
        plt.scatter(cluster[:, 0], cluster[:, 1], marker=markers[c], s=20, c=colors[c])
    # 畫出中心點
    plt.scatter(centers[:, 0], centers[:, 1], marker='o', c='white', alpha=0.9, s=300)
    for i, c in enumerate(centers):
        plt.scatter(c[0], c[1], marker='$%d$' % i, s=50, c=colors[i])

函數接受兩個參數,一個是聚類個數,即K的值。另外一個是數據樣本。有了這個函數,就能夠分別對3中不一樣的k值進行聚類分析了,而且把聚類結果可視化

n_cluster = [2, 3, 4]
plt.figure(figsize=(10, 3), dpi=144)
for i, c in enumerate(n_cluster):
    plt.subplot(1, 3, i + 1)
    fit_plot_kmean_model(c, X)

使用k-均值對文檔進行聚類分析

假設有一個博客平臺,用戶在平臺上發佈博客,如何對博客進行聚類分析,以方便展現不一樣類別下的熱門文章呢?

準備數據集 

爲了簡化問題,避免進行中文分詞,仍是使用PCA的語料庫。

只選擇語料庫裏的部份內容來進行聚類分析。假設選擇sci.crypt、sci.electronics、sci.med、sci.space4個類別的文檔進行聚類分析。咱們複製這4個文件夾的內容。

加載數據集

準備好數據集有,把目錄下的文檔進行聚類分析。

首先導入數據

from time import time
from sklearn.datasets import load_files

print('loading document ...')
t = time()
docs = load_files('clustering/data')
print('summary:{0} documents in {1} categories.'
      .format(len(docs.data), len(docs.target_names)))
print('done in {0} seconds'.format(time() - t))

輸出內容以下

loading document ...
summary:3949 documents in 4 categories.
done in 0.28702831268310547 seconds

總共有3949篇文檔,人工標記在4個類別裏。接着把文檔轉化爲TF-IDF向量

from sklearn.feature_extraction.text import TfidfVectorizer

max_features = 20000
print('vectorizing documents ...')
t = time()
vectorizer = TfidfVectorizer(max_df=0.4, min_df=2,
                             max_features=max_features, encoding='latin-1')
X = vectorizer.fit_transform((d for d in docs.data))
print('n_samples:%d,n_features:%d' % X.shape)
print('number of non-zero features in sample [{0}]:{1}'
      .format(docs.filenames[0], X[0].getnnz()))
print('done in {0} seconds'.format(time() - t))

須要注意TfidfVectorizer的幾個參數的選擇,其中max_df=0.4表示若是一個單詞在40%的文檔裏都出現過,則認爲這是一個高頻詞,對文檔聚類沒有幫助,在生成詞典時就會剔除這個詞。min_df=2表示,若是一個單詞的詞頻過低,只在兩個一下(包含兩個)的文檔裏吃醋縣,則也把這個單詞從詞典裏剔除。max_features能夠進一步過濾詞典的大小,會根據TF-IDF權重從高到低進行排序,而後去前面權重高的單詞構成詞典

vectorizing documents ...
n_samples:3949,n_features:20000
number of non-zero features in sample [clustering/data\sci.electronics\11902-54322]:56
done in 1.5791575908660889 seconds

一篇文章構成的向量是一個稀疏向量,其大部分元素都爲0。詞典大小爲20000個,而實例文章中不重複的單詞卻只有56個

文本聚類分析

下面使用KMeans算法對文檔進行聚類分析

from sklearn.cluster import KMeans

print('clustering documents ...')
t = time()
n_clusters = 4
kmean = KMeans(n_clusters=n_clusters, max_iter=100, tol=0.01, verbose=1, n_init=3)
kmean.fit(X)
print('kmean:k={},cost={}'.format(n_clusters,int(kmean.inertia_)))

選擇的聚類個數爲4,max_iter=100表示最多進行100次k-均值迭代。tol=0.1表示中心點移動距離小於0.1時就認爲算法已經收斂,中止迭代。verbose=1表示輸出迭代的過程信息。n_init=3表示進行3次k-均值運算後求平均值,在算法剛開始迭代時,會隨機選擇聚類中心點,不一樣的中心點可能致使不一樣的收斂效果,所以屢次運算求平均值的方法能夠提供算法的穩定性。

clustering documents ...
Initialization complete
Iteration 0, inertia 7599.134
Converged at iteration 43: center shift 0.000000e+00 within tolerance 4.896692e-07
Initialization complete
Iteration 0, inertia 7520.830
Iteration 14, inertia 3822.959
Converged at iteration 14: center shift 0.000000e+00 within tolerance 4.896692e-07
Initialization complete
Iteration 0, inertia 7542.723
Iteration 17, inertia 3817.601
Converged at iteration 17: center shift 0.000000e+00 within tolerance 4.896692e-07
kmean:k=4,cost=3817

輸出信息能夠看到,總共進行了3次k-均值聚類分析,分別做了195424次迭代後收斂。這樣就把3949個文檔自動分類了。kmean.labels_裏保存的就是這些文檔的類別信息。len(kmean.labels_)的值是3949,還能夠經過kmean.labels_[:100]查看前100個文檔的分類狀況。

print(kmean.labels_[:10])

還能夠查看文檔對應的文件名

print(docs.filenames[:10])

聚類分析過程當中,那些單詞的權重最高,從而較容易地決定一個文章的類別?

能夠查看每種類別文檔中,其權限最高的10個單詞分別是什麼

from __future__ import print_function
print('Top terms per cluster:')
order_centroids = kmean.cluster_centers_.argsort()[:,:: -1]
terms = vectorizer.get_feature_names()
for i in range(n_clusters):
    print('Cluster %d:' % i,end='')
    for ind in order_centroids[i,:10]:
        print(' %s ' % terms[ind],end='')
    print()

關鍵在於argsort函數,做用是把一個Numpy數組進行升序排列,返回的是排序後的索引。

因爲kmean.cluster_centers是一個二維數組,所以[:,:: -1]語句的含義就是把聚類中心點的不一樣份量,按照從大到小的順序進行排序,而且把排序後的元素索引保存在二維數組order_centroids裏。

vectorizer.get_feature_names()將獲得咱們的詞典單詞,根據索引便可獲得每一個類別裏權重最高的那些單詞了

聚類算法性能評估 

聚類性能評估比較複雜。針對分類問題,能夠直接計算被錯誤分類的樣本數量,這樣能夠直接算出分類算法的準確率。聚類問題不能使用絕對數量的方法進行性能評估,直接的緣由是,聚類分析後的類別與原標記的類別之間不存在必然的一一對應關係。針對k-均值算法,能夠選擇k的數值不等於已標記的類別個數。

信息論是最重要的基礎概念。熵表示一個系統的有序程度,而聚類問題的性能評估,就是對比通過聚類算法處理後的數據的有序程度,與人工標記的累唄的有序程度之間的差別。

Adjust Rand Index

一種衡量兩個序列類似性的算法。優勢是,針對兩個隨機序列,它的值爲負數或接近於0,而針對兩個結構相同的序列,它的值接近於1.並且對類別標籤不敏感。

from sklearn import metrics

label_true = np.random.randint(1, 4, 6)
label_pred = np.random.randint(1, 4, 6)
print('Adjusted Rand-Index for random sample: %.3f'
      % metrics.adjusted_rand_score(label_true, label_pred))
label_true = [1, 1, 3, 3, 2, 2]
label_pred = [3, 3, 2, 2, 1, 1]
print('Adjusted Rand-Index for same structure sample:%.3f'
      % metrics.adjusted_rand_score(label_true, label_pred))

輸出內容以下

Adjusted Rand-Index for random sample: -0.176
Adjusted Rand-Index for same structure sample:1.000

齊次性和完整性

根據條件熵分析,能夠獲得另外兩個衡量聚類算法性能的指標,分別是齊次性(homogeneity)和完整性(completeness)。齊次性表示一個聚類元素只由一個鐘類別的元素組成。完整性表示給定的已標記的類別,所有分配到一個聚類裏。值均介於[0,1]之間。

label_true = [1, 1, 2, 2]
label_pred = [2, 2, 1, 1]
print('Homogeneity score for same structure sample: %.3f'
      % metrics.homogeneity_score(label_true, label_pred))
label_true = [1, 1, 2, 2]
label_pred = [0, 1, 2, 3]
print('Homogeneity score for each clulster come from only one class: %.3f'
      % metrics.homogeneity_score(label_true, label_pred))
label_true = [1, 1, 2, 2]
label_pred = [0, 1, 2, 3]
print('Homogeneity score for each cluster come from two class : %.3f'
      % metrics.homogeneity_score(label_true, label_pred))
label_true = np.random.randint(1,4,6)
label_pred = np.random.randint(1,4,6)
print('Homogeneity score for each cluster come from two class : %.3f'
      % metrics.homogeneity_score(label_true, label_pred))

輸出以下

Homogeneity score for same structure sample: 1.000
Homogeneity score for each clulster come from only one class: 1.000
Homogeneity score for each cluster come from two class : 1.000
Homogeneity score for each cluster come from two class : 0.159

針對第一組序列,其結構相同,所以齊次性輸出1.

可是第二組樣本爲何也是輸出1呢?

齊次性的定義,聚類元素只由一種已標記的類別元素組成時,其值爲1。

例子中,已標記爲2個類別,而輸出了4個聚類,這樣就知足每一個聚類元素均來自一種已標記的類別元素這一條件。針對第三組樣本,因爲每一個聚類元素都來自2個類別的元素,所以其值爲0;而針對隨機元素序列,不爲0,這是與Adjust Rand Index不一樣的地方。

接着看一組完整性的例子

label_true = [1, 1, 2, 2]
label_pred = [2, 2, 1, 1]
print('Completeness score for same structure sample: %.3f'
      % metrics.completeness_score(label_true, label_pred))
label_true = [1, 1, 2, 2]
label_pred = [0, 1, 2, 3]
print('Completeness score for each class assign to only one cluster: %.3f'
      % metrics.completeness_score(label_true, label_pred))
label_true = [1, 1, 2, 2]
label_pred = [1, 2, 1, 2]
print('Completeness score for each class assign to two class : %.3f'
      % metrics.completeness_score(label_true, label_pred))
label_true = np.random.randint(1,4,6)
label_pred = np.random.randint(1,4,6)
print('Completeness score for random sample : %.3f'
      % metrics.completeness_score(label_true, label_pred))

輸出以下

Completeness score for same structure sample: 1.000
Completeness score for each class assign to only one cluster: 0.500
Completeness score for each class assign to two class : 0.000
Completeness score for random sample : 0.457

針對第一組序列,其結構相同,輸出爲1。針對第二組序列,因爲符合完整性定義,即每一個類別的元素都被分配進了同一個聚類裏,所以其完整性也爲1.針對第三組序列,每一個類別的元素都被分配進兩個不一樣的聚類裏,所以其完整性爲0.和齊次性同樣,對隨機類別的判斷能力也比較弱

齊次性和完整性是一組互補的關係,能夠把兩個指標綜合起來,稱爲V-measure分數

label_true = [1, 1, 2, 2]
label_pred = [2, 2, 1, 1]
print('V-measure score for same structure sample: %.3f'
      % metrics.v_measure_score(label_true, label_pred))
label_true = [0, 1, 2, 3]
label_pred = [1, 1, 2, 2]
print('V-measure score for each class assign to only one cluster: %.3f'
      % metrics.v_measure_score(label_true, label_pred))
label_true = [1, 1, 2, 2]
label_pred = [1, 2, 1, 2]
print('V-measure score for each class assign to two class : %.3f'
      % metrics.v_measure_score(label_true, label_pred))

輸出內容以下

V-measure score for same structure sample: 1.000
V-measure score for each class assign to only one cluster: 0.667
V-measure score for each class assign to two class : 0.000

針對第一組序列,其結構相同,V-measure輸出的值也爲1,表示同時知足齊次性和完整性。第二行和第三行的輸出,代表V-measure符合對稱性法則。

輪廓係數

聚類性能評估方法都須要有已標記的類別數據,這個很難作到。若是已經標記了數據,就會直接用有監督的學習算法,而無監督學習算法的最大優勢就是不須要對數據集進行標記。輪廓係數能夠在不須要已標記的數據集的前提下,對聚類算法的性能進行評估。

輪廓係數由如下兩個指標構成

a:一個樣本與其所在相同聚類的平均距離

b:一個樣本與其距離最近的下一個聚類裏的點的平均距離

則針對這個樣本,其輪廓係數S的值爲:

針對一個數據集,其輪廓係數s爲其全部樣本的輪廓係數的平均值。輪廓係數的數值介於[-1,1]之間,-1表示徹底錯誤的聚類,1表示完美的聚類,0表示聚類重疊

針對前面的例子,能夠分別計算本節介紹的幾個聚類算法性能評估指標,綜合來看聚類算法的性能

labels = docs.target
print('Homogeneity:%0.3f' % metrics.homogeneity_score(labels, kmean.labels_))
print('Completeness:%0.3f' % metrics.completeness_score(labels, kmean.labels_))
print('V-measure:%0.3f' % metrics.v_measure_score(labels, kmean.labels_))
print('Adjusted Rand-Index:%.3f' % metrics.adjusted_rand_score(labels, kmean.labels_))
print('Silhouette Coefficient: %0.3f'
      % metrics.silhouette_score(X, kmean.labels_, sample_size=1000))

輸出內容以下

Homogeneity:0.389
Completeness:0.547
V-measure:0.455
Adjusted Rand-Index:0.253
Silhouette Coefficient: 0.005

 

#

相關文章
相關標籤/搜索