sklearn中SVM與AdaBoost對手寫體數字進行識別
最近比較忙,自從寫了第一篇部落格之後,好久沒寫部落格。最近自己搗鼓了一下基於SVM與AdaBoost的手寫體數字識別,和大家分享一下這個過程吧。
首先,資料集的準備,選用的是比較有名的MINIST資料集(資料集可以在這個地方下載點選開啟連結,其實,在學習過程中,要用到的大部分資料集在這個地方都可以下載到的)。
然後,要做的第一件事就是將要用到的包匯入:
接下來,我們將準備好的資料集匯入,並對訓練集和測試集的輸入輸出進行劃分,這個地方我分別採用了pandas.read_csv和numpy.loadtxt這兩種方法來匯入資料集:import numpy as np import pandas as pd from sklearn import svm import matplotlib.colors import matplotlib.pyplot as plt from sklearn.metrics import accuracy_score from sklearn.model_selection import GridSearchCV from time import time from sklearn.ensemble import AdaBoostClassifier from sklearn.linear_model import LogisticRegression from sklearn.tree import DecisionTreeClassifier
在接下來,我們先用svm演算法來對手寫體數字進行識別:data = pd.read_csv('optdigits.tra', header=None)#使用pandas載入訓練樣本 x, y = data[range(64)].values, data[64].values#劃分訓練集的輸入與輸出 images_tra = x.reshape(-1, 8, 8)#其實每一個8x8的小矩陣表示一個數字,為下面將錯誤樣本畫出做準備 y_tra = y.ravel().astype(np.int)#將陣列變為1維陣列 #load test dataset datatest = np.loadtxt('optdigits.tes',dtype=np.float, delimiter=',')#其實這個地方也可以採用pandas讀入資料 x_test, y_test = np.split(datatest, (-1,), axis=1)#劃分測試樣本集的輸入與輸出 print y_test.shape images_tes = x_test.reshape(-1, 8, 8)#同上 y_test = y_test.ravel().astype(np.int) print images_tes.shape
model = svm.SVC(C=10.0, kernel='rbf', gamma=0.001)#設定模型引數
tt1 = time() model.fit(x, y_tra)#訓練模型 tt2 = time() delta_tt = tt2 - tt1 print 'SVMxun訓練模型耗時:%d分%.3f秒' % ((int)(delta_tt / 60), delta_tt - 60*((int)(delta_tt/60))) y_hat = model.predict(x_test)#做預測 print 'SVC訓練集準確率:', accuracy_score(y_tra, model.predict(x)) print 'SVC測試集準確率:', accuracy_score(y_test, y_hat) print y_test print y_hat num = [] tmp = [] for index in range(len(y_test)): if (y_test[index] != y_hat[index]):#打印出模型分類的測試樣本對應的真實值與模型預測的實際值,以及對應的index print y_test[index], y_hat[index], index tmp.append(index)#將錯誤的index記錄下來 num.append(y_test[index])#將這些錯誤預測結果對應的正確數字儲存下來 print '*************************' # print tmp # print num
在識別的過程中,我將測試集中,識別錯誤的樣本下標以及它所對應的正確數字值分別用num陣列和tmp陣列進行了儲存進行了儲存。在svm模型引數的設定上,我主要考慮了C,kernel和gamma這三個引數。
C:C-SVC的懲罰引數C,預設值是1.0。C越大,相當於懲罰鬆弛變數,希望鬆弛變數接近0,即對誤分類的懲罰增大,趨向於對訓練集全分對的情
況,這樣對訓練集測試時準確率很高,但泛化能力弱。C值小,對誤分類的懲罰減小,允許容錯,將他們當成噪聲點,泛化能力較強。
kernel:核函式,預設是rbf,可以是‘linear’, ‘poly’, ‘rbf’, ‘sigmoid’, ‘precomputed’
0 – 線性:u'v
1 – 多項式:(gamma*u'*v + coef0)^degree
2 – RBF函式:exp(-gamma|u-v|^2)
3 –sigmoid:tanh(gamma*u'*v + coef0)
我這裡就選用了'rbf'核函式,其他的核函式可以自己嘗試一下。
gamma: ‘rbf’,‘poly’ 和‘sigmoid’的核函式引數。預設是’auto’,則會選擇1/n_features
之後,我又採用了AdaBoost演算法進行了訓練和識別。AdaBoost是一種迭代演算法,它有兩種形式分別為AdaBoostClassifier和AdaBoostRegressor。
它的核心思想是針對一個訓練集訓練不同的分類器(弱分類器),然後把這些弱分類器集合起來,構成一個更強的最終分類器(強分類器)。
在實際的模型引數設定過程中,我們要設定的引數包括:
base_estimator:AdaBoostClassifier和AdaBoostRegressor都有,即我們的弱分類學習器或者弱迴歸學習器。理論上可以選擇任何一個分類
或者回歸學習器,不過需要支援樣本權重。我們常用的一般是CART決策樹或者神經網路MLP。預設是決策樹,即AdaBoostClassifier預設使用CART分
類樹DecisionTreeClassifier,而AdaBoostRegressor預設使用CART迴歸樹DecisionTreeRegressor。另外有一個要注意的點是,如果我們選擇的
AdaBoostClassifier演算法是SAMME.R,則我們的弱分類學習器還需要支援概率預測,也就是在scikit-learn中弱分類學習器對應的預測方法除了predict
還需要有predict_proba。
algorithm:這個引數只有AdaBoostClassifier有。主要原因是scikit-learn實現了兩種Adaboost分類演算法,SAMME和SAMME.R。兩者的主要
區別是弱學習器權重的度量,SAMME使用了和我們的原理篇裡二元分類Adaboost演算法的擴充套件,即用對樣本集分類效果作為弱學習器權重,而SAMME.R
使用了對樣本集分類的預測概率大小來作為弱學習器權重。由於SAMME.R使用了概率度量的連續值,迭代一般比SAMME快,因此AdaBoostClassifier
的預設演算法algorithm的值也是SAMME.R。我們一般使用預設的SAMME.R就夠了,但是要注意的是使用了SAMME.R, 則弱分類學習器引數
base_estimator必須限制使用支援概率預測的分類器。SAMME演算法則沒有這個限制。
loss:這個引數只有AdaBoostRegressor有,Adaboost.R2演算法需要用到。有線性‘linear’, 平方‘square’和指數 ‘exponential’三種選擇, 預設
是線性,一般使用線性就足夠了,除非你懷疑這個引數導致擬合程度不好。
n_estimators: AdaBoostClassifier和AdaBoostRegressor都有,就是我們的弱學習器的最大迭代次數,或者說最大的弱學習器的個數。一般
來說n_estimators太小,容易欠擬合,n_estimators太大,又容易過擬合,一般選擇一個適中的數值。預設是50。在實際調參的過程中,我們常常將
n_estimators和下面介紹的引數learning_rate一起考慮。
learning_rate: AdaBoostClassifier和AdaBoostRegressor都有,即每個弱學習器的權重縮減係數νν,在原理篇的正則化章節我們也講到
了,加上了正則化項,我們的強學習器的迭代公式為fk(x)=fk−1(x)+ναkGk(x)fk(x)=fk−1(x)+ναkGk(x)。νν的取值範圍
為0<ν≤10<ν≤1。對於同樣的訓練集擬合效果,較小的νν意味著我們需要更多的弱學習器的迭代次數。通常我們用步長和迭代最大次數一起來決
定演算法的擬合效果。所以這兩個引數n_estimators和learning_rate要一起調參。一般來說,可以從一個小一點的νν開始調參,預設是1。
在訓練過程中,對於AdaBoost中的弱分類器,我這裡分別選擇了DT和LR。首先來看一下選擇DT的情況:
clf = AdaBoostClassifier(DecisionTreeClassifier(max_depth=5, min_samples_split=5, min_samples_leaf=5), n_estimators=200, learning_rate=0.05, algorithm='SAMME.R')
t1 = time()
clf.fit(x, y_tra)
t2 = time()
t = t2 - t1
print 'AdaBoost-DT訓練模型耗時:%d分%.3f秒' % ((int)(t/60), t-60*(int)(t/60))
y_hat1 = clf.predict(x_test)
print 'AdaBoost-DT訓練集準確率:', accuracy_score(y_tra, clf.predict(x))
print 'AdaBoost-DT測試集準確率:', accuracy_score(y_test, y_hat1)
print '*************************'
在決策樹引數的選擇上,決策樹最大深度max_depth我選取5,內部節點再劃分所需最小樣本數min_samples_split我選取5,葉子節點最少樣本數
min_samples_leaf我選在5,其他引數我採用預設值。對於這些引數,我個人覺得,如果給的太大,那麼必然會產生過擬合的情況,如果給的太小,
那麼就會產生欠你和的情況。
再來看一下選用邏輯迴歸的情況:
clf1 = AdaBoostClassifier(LogisticRegression(penalty='l2'), n_estimators=200, learning_rate=0.05, algorithm='SAMME', random_state=None)
t2 = time()
clf1.fit(x, y_tra)
t3 = time()
delta_t = t3 - t2
y_hat2 = clf1.predict(x_test)
print 'AdaBoost-LR訓練集準確率:', accuracy_score(y_tra, clf1.predict(x))
print 'AdaBoost-LR測試集準確率:', accuracy_score(y_test, y_hat2)
print 'AdaBoost-LR訓練模型耗時:%d分%.3f秒' % ((int)(delta_t/60), delta_t-60*(int)(delta_t/60))
這裡,在LR引數的選擇上,我主要就改了penalty這個引數,具體為啥改成這個引數,我也沒有具體的理論依據,只是自己嘗試著玩一下的。。。
最後,我們來看一下模型的識別效果:
從模型的表現上來看,SVM有著更好的表現。
程式碼和資料集我已經上傳,可以在這個地方進行下載點選開啟連結
大家一起學習,歡迎批評指正。