实验名称*
SVM实现鸢尾花分类
实验目的
使用SVM模型通过线性分类器训练数据,熟悉线性的可分性和不可分性;
对三个支持向量机分类器进行训练,并分别评估模型在训练集和测试集上的性能表现;
了解线性可分与线性不可分,之间的一种现象:软间隔;
应用GridSearchCV方法寻找最优SVM模型;
实验背景
1995年Cortes和Vapnik首先提出了支持向量机(Support Vector Machine),由于其能够适应小样本的分类,分类速度快等特点,性能不差于人工神经网络,所以在这之后,人们将SVM应用于各个领域。大量使用SVM模型的论文不断涌现,包括国内和国外。
支持向量机建立在坚实的数学理论基础上,是在所有知名的数据挖掘算法中最健壮、最准确的方法之一,具有很好的学习能力和泛化能力。
实验原理
SVM是面向数据的分类算法,其基本思想是在训练样本空间中寻找一个超平面(Hyper Plane),使模型在数据上的分类误差接近于0,尤其是在未知数据集上的分类误差(泛化误差)尽量小。
在二维空间中,超平面表现为直线的形式,如图7-1-1描述了二维空间中存在两组样本 +和-,可寻找出若干个直线将两组样本完美分开,理想的状态是寻找众多超平面中鲁棒性(即泛化能力)最好的那个超平面。直观地看,在图7-1-1中加粗的直线可能会是鲁棒性更好的超平面,因为该分割超平面对训练样本局部扰动的“容忍”性最好。
如何寻找最优超平面?图中引入支持向量(Support Vector)与间隔(Margin)的概念,通过支持向量来确定决策边界,图中的两条虚线,样本离决策边界越远,其预测结果 可信度越高,即可以最大化间隔来提高模型的泛化能力,所以支持向量的确定是非常重要的因素。
如图所示,要间隔γ最大化,则要最小化 ||ω||,所以寻找最大化间隔的分隔超平面问题,最终转化为一个凸二次规划问题来求解:
实验环境
建议课时
2课时
实验步骤
一、项目准备
打开一个Terminal终端,输入jupyter notebook
jupyter notebook
点击new,新建一个python3文件
在实际任务中,原始样本空间往往找不出一个超平面可以完美的划分样本,如图所示,在当前空间中,样本x和样本●找不到一个超平面,这种现象称为线性不可分。
为了能够找出非线性数据的线性决策边界,可以采用升维的思想,将数据从原始的空间投射到高维空间中。
举个例子, 桌面上有铺满了黄豆和绿豆, 正常情况下我们无法用一支木棍将它们分开, 这时候你用力拍一下桌子, 让这些黄豆和绿豆弹跳起来, 在某个时刻就有了在空间中用一张纸分开它们的机会了。
如左图所示,在当前空间中,样本o和样本●找不到一个超平面,这种现象称为线性不可分。
针对线性不可分问题,如右图所示,对此可以引入二次多项式核函数解决线性不可分问题。
二、线性可分与线性不可分
导入实验所需的python库准备数据:
- 颜色映射类ListedColormap是为了画等高线做准备;
- train_test_split是数据集划分的一个快捷函数;
- StandardScaler是为了对数据做标准化引入的;
- make_circles是datasets模块提供的一个构建圆形数据集的快捷函数;
- sklearn中的支持向量机算法在svm模块中,其中SVC是一个常用的非线性多维支持向量分类算法。
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.colors import ListedColormap
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.datasets import make_circles
from sklearn.svm import SVC
数据准备:
- 本示例的数据集采用datasets模块的构建函数自行构建,在构建数据中过程中,加入了noise参数用来产生高斯噪声数据;
- factor参数用来指定内外圆的伸缩因子;random_state指定随机状态,使用复现。
- 使用StandardScaler对生成的样本数据进行标准化,又称Z-score标准化。
- train_test_split函数是一个快速数据集分割函数,test_size指定测试集的比例,同样指定了随机状态以便于复现。
- plt.scatter将内圈的数据标识成●,将外圈数据标识成o。
X, y = make_circles(noise=0.2, factor=0.5, random_state=1)
X_ = StandardScaler().fit_transform(X)
X_train, X_test, y_train, y_test = \
train_test_split(X_, y, test_size=.4, random_state=42)
plt.figure(figsize=(8,6),dpi=100)
plt.scatter(X[y==0,0], X[y==0,1], marker='o',s=120, c='w',edgecolors='k')
plt.scatter(X[y==1,0], X[y==1,1], marker='o',s=120, c='k')
模型构建,分别用线性核、高斯径向基、多项式核和双曲正切核构建了4个支持向量机分类器,其中kernel参数指定核函数,不指定时默认为高斯径向基。
names = ["Linear SVM", "RBF SVM", "Poly SVM",'Sigmoid SVM']
classifiers = [
SVC(kernel="linear", C=0.025),
SVC(gamma=2, C=1),
SVC(kernel='poly', degree=4),
SVC(kernel='sigmoid'),
]
模型训练、评估、可视化,生成网格数据,并将训练数据绘制在底图上
x_min, x_max = X_[:, 0].min() - .5, X_[:, 0].max() + .5
y_min, y_max = X_[:, 1].min() - .5, X_[:, 1].max() + .5
xx, yy = np.meshgrid(np.arange(x_min, x_max, 0.2),
np.arange(y_min, y_max, 0.2))
figure = plt.figure(figsize=(36, 9))
def plt_scatter(ax, X,y):
ax.scatter(X[y==0,0], X[y==0,1], marker='o',s=120, c='w',edgecolors='k')
ax.scatter(X[y==1,0], X[y==1,1], marker='o',s=120,c='k')
ax.set_xticks(())
ax.set_yticks(())
#迭代每个分类器
for i,v in enumerate(zip(names, classifiers)):
name, clf = v
#创建子图
ax = plt.subplot(1, len(classifiers) , i+1)
#模型训练
clf.fit(X_train, y_train)
#模型打分,默认打分方式为准确率
score = clf.score(X_test, y_test)
Z = clf.decision_function(np.c_[xx.ravel(), yy.ravel()])
Z = Z.reshape(xx.shape)
#画带填充的等高线
ax.contourf(xx, yy, Z, cmap='RdBu', alpha=.7)
#画样本散点图
plt_scatter(ax, X_,y)
#设置每个子图的名称
ax.set_title(name)
#显示每个模型的准确率
ax.text(xx.max() - .3, yy.min() + .3,
('%.2f' % score).lstrip('0'),
size=20,
horizontalalignment='right')
结果图展示了线性核不能很好的分类时,部分核函数在一定程度上解决了这个问题,它将原始样本空间映射到更高维的特征空间,在高维的特征空间中寻找合适的超平面,从而完成分类任务。针对本例的数据集,高斯径向基和多项式核表现较好。
三、鸢尾花分类
同上节一样,导入sklearn中的支持向量机算法在svm模块中,其中SVC是一个常用的非线性多维支持向量分类算法。
#包导入
from sklearn.svm import SVC
from sklearn import metrics
from sklearn import datasets
from sklearn.model_selection import train_test_split
from sklearn.datasets import load_iris
import pandas as pd
数据准备,使用sklearn的datasets模块导入鸢尾花数据。执行以下代码
df = load_iris()
X=df.data
Y=df.target
X_train,X_test,y_train,y_test = train_test_split(X,Y, random_state=10,test_size=0.4)
print("X_train.shape ",X_train.shape)
print("X_test.shape ",X_test.shape)
print("y_train.shape ",y_train.shape)
print("y_test.shape ",y_test.shape)
输出如下
X_train.shape (90, 4)
X_test.shape (60, 4)
y_train.shape (90,)
y_test.shape (60,)
模型构建,分别使用高斯径向基、线性核和多项式核构建了三个支持向量机分类器,在进行SVC的构建时,参数gamma、degree和coef0是关于核函数的参数,但这三个参数并不是对所有的核都有效,表中列出了关于kernel参数的相关说明。
clfs = [
SVC(C=15, kernel='rbf', gamma=10),
SVC(C=0.1, kernel='linear' ),
SVC(C=0.1, kernel='poly',degree=5 )
]
模型训练及评估,对三个支持向量机分类器进行训练,并分别评估模型在训练集和测试集上的性能表现,这样做的好处是可以观察模型是否有过拟合现象。
for clf in clfs:
print('-'*20)
clf.fit(X_train, y_train)
print('分类器:', clf)
#训练集准确率
train_accscore = clf.score(X_train, y_train)
print('训练集准确率:', train_accscore)
#测试集准确率
test_accscore = clf.score(X_test, y_test)
print('测试集准确率:', test_accscore)
print('支持向量数目:', clf.n_support_)
输出如下:
通过结果可以看出高斯径向基分类器有过拟合现象,因为训练集上的表现远远高于测试集上的表现。
四、软间隔
有了对数据的了解后,可以根据分析需求提取相应的数据,并对数据进行分割。
x12 = X[np.isin(Y, [1,2]), 2:]
y12 = Y[np.isin(Y, [1,2])]
print("X ",X[0:2])
print("Y ",Y[0:2])
print("x.shape ",x12.shape)
print("X[0:2] ",x12[0:2])
print("y.shape ",y12.shape)
print("y[0:2] ",y12[0:2])
输出如下:
选择高斯核函数,调整不同的超参数 C 和 gamma值构造不同的的SVC分类器。
from sklearn import svm
clfs = [
svm.SVC(kernel='linear', C=0.1),
svm.SVC(kernel='rbf', C=1, gamma=0.1),
svm.SVC(kernel='rbf', C=5, gamma=5),
svm.SVC(kernel='rbf', C=10, gamma=10),
]
模型训练评估,使用了metrics模块的accuracy_score方法,与直接clf.score(X,y)等价。
from sklearn import metrics
for clf in clfs:
clf.fit(x12, y12)
y_predict = clf.predict(x12)
accuracy_score = metrics.accuracy_score(y12, y_predict)
print('模型:', clf)
print('准确率:', accuracy_score)
print('支持向量的数目:', clf.n_support_)
输出如下
调用matplotlib进行绘图,以展示不同模型的分类决策面和分类效果。
import matplotlib.pyplot as plt
import matplotlib as mpl
x1_min,x2_min = np.min(x12, axis=0)-0.1
x1_max,x2_max = np.max(x12, axis=0)+0.1
x1,x2 = np.meshgrid(np.arange(x1_min, x1_max, 0.01),
np.arange(x2_min, x2_max, 0.01))
grid_test = np.stack((x1.flat, x2.flat), axis=1)
mpl.rcParams['axes.unicode_minus'] = False
figure = plt.figure(figsize=(12, 4),dpi=100)
titles = ["linear/C=1",
"rbf/C=1/gamma=0.1",
"rbf/C=5/gamma=5",
"rbf/C=10/gamma=10"]
for i,clf in enumerate(clfs):
grid_hat = clf.predict(grid_test)
grid_hat = grid_hat.reshape(x1.shape)
z = clf.decision_function(grid_test)
z = z.reshape(x1.shape)
ax = plt.subplot(1, 4, i+1)
ax.contour(x1,x2,z, colors=list('kbrbk'),
linestyles=['--', '--', '-', '--', '--'],
linewidths=[1,0.5,1.5,0.5,1],
levels=[-1,-0.5,0,0.5,1])
ax.contourf(x1,x2,z, cmap='RdBu', alpha=.8)
ax.scatter(x12[:,0], x12[:,1],
c=y, edgecolors='k',
s=40, cmap='binary')
ax.scatter(x12[clf.support_,0], x12[clf.support_,1],
edgecolors='k', facecolors='none',
s=100, marker='o')
plt.title(titles[i])
运行结果表明,当高斯径向基的参数C和γ取值越大时,其在训练集上的准确率越高,这时特别需要注意过拟合问题。参数C是训练样本正确分类与决策边界最大化之间的权衡,较大的C会接受较小的余量,这要求决策功能较复杂。相反,较小的C则鼓励更大的余量,这时的决策功能会比较简单。换言之,C在SVM中充当正则化系数。
五、GridSearch确定超参数
通过前面的学习,发现一个模型的不同参数取值,会带来不同的模型效果。对于SVC模型,参数C可以对训练样本的误分类进行有价转换。较小的C值会使决策边界更简单,同时较高的C值旨在正确进分类所有训练样本。gamma的定义会影响样本分布,gamma值越大,支持向量越少,支持向量的个数 影响训练和预测的速度。选择合适的C和gamma会对模型的性能起到关键作用,一旦选择不合适,可能会出现欠拟合或者过拟合。
为了方便超参数的确定,sklearn的model_selection模块提供了网格搜索GridSearchCV类,用来查找一定范围内的超参数对结果的影响,以便选择最佳的参数值。下面针对鸢尾花数据集,应用GridSearchCV方法寻找最优SVM模型。
from sklearn.svm import SVC
from sklearn.model_selection import GridSearchCV
from sklearn import datasets
import numpy as np
df = load_iris()
X=df.data
Y=df.target
# X_train,X_test,y_train,y_test = train_test_split(X,Y, random_state=10,test_size=0.4)
#参数范围设定
param_grid = {
'C':np.logspace(-2,10,13),
'gamma':np.logspace(-9,3,13)
}
grid = GridSearchCV(SVC(), param_grid=param_grid, cv=5)
grid.fit(X, Y)
print('最佳参数:', grid.best_params_)
print('最佳分数:', grid.best_score_)
输出入下:
通过结果可看出,对于鸢尾花数据分类问题,如果kernel参数为高斯径向基时,最佳的参数C为1.0,最佳的gamma值为0.1,此时得到的5折交叉验证的准确率平均分可以达到0.98。
import matplotlib.pyplot as plt
plt.figure(figsize=(12, 4),dpi=100)
plt.subplots_adjust(left=.2, right=0.95, bottom=0.15, top=0.95)
plt.imshow(grid.cv_results_['mean_test_score'].reshape(13,13), interpolation='nearest', cmap=plt.cm.hot)
plt.xlabel('gamma')
plt.ylabel('C')
plt.colorbar()
plt.xticks(range(13),np.logspace(-9,3,13), rotation=45)
plt.yticks(range(13),np.logspace(-2,10,13) )
plt.title('Validation accuracy')
plt.show()
输出如下:
可以观察到,对于gamma的某些中间值,当C变得非常大时,得到的模型性能基本相同,不必通过强制较大的余量来进行正则化,仅高斯内核的半径就可以充当良好的结构调整器。此外,当gamma取值大于100时,模型的性能将急剧下降。
实验总结
主要介绍了SVM的基本原理、并结合简单的案例,使用SVM模型通过线性分类器训练数据,熟悉线性的可分性和不可分性,展现了线性可分与不可分时SVM的处理过程。结合鸢尾花分类问题学习了软间隔的概念,此外,针对超参数的确定,讲解了网格搜索的相关知识,应用GridSearchCV方法寻找最优SVM模型。希望同学们课下多加练习。