最近比较忙,自从写了第一篇博客之后,好久没写博客。最近自己捣鼓了一下基于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
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有着更好的表现。
代码和数据集我已经上传,可以在这个地方进行下载点击打开链接
大家一起学习,欢迎批评指正。