1. 程式人生 > >sklearn中SVM與AdaBoost對手寫體數字進行識別

sklearn中SVM與AdaBoost對手寫體數字進行識別

       最近比較忙,自從寫了第一篇部落格之後,好久沒寫部落格。最近自己搗鼓了一下基於SVM與AdaBoost的手寫體數字識別,和大家分享一下這個過程吧。

       首先,資料集的準備,選用的是比較有名的MINIST資料集(資料集可以在這個地方下載點選開啟連結,其實,在學習過程中,要用到的大部分資料集在這個地方都可以下載到的)。

       然後,要做的第一件事就是將要用到的包匯入:

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
        接下來,我們將準備好的資料集匯入,並對訓練集和測試集的輸入輸出進行劃分,這個地方我分別採用了pandas.read_csv和numpy.loadtxt這兩種方法來匯入資料集:
    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
         在接下來,我們先用svm演算法來對手寫體數字進行識別:
    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_estimatorAdaBoostClassifier和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)=fk1(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有著更好的表現。

程式碼和資料集我已經上傳,可以在這個地方進行下載點選開啟連結

大家一起學習,歡迎批評指正。