本文主要实现sklearn库中的支持向量机SVM模型,希望能够帮助到大家。
线性模型在低维空间中可能非常受限,因为线和平面的灵活度十分有限。
有一种方法可以让线性模型更加灵活,就是添加更多的特征,例如添加特征的交互项或者多项式,专业术语即维度升级。
先看一下如下的数据集
from sklearn.datasets import make_blobs #导入斑点(blobs)数据集
import matplotlib.pyplot as plt
import mglearn
X, y = make_blobs(centers=4, random_state=8) #生成4团斑点数据
y = y%2 #通过取余操作,将原来的4个类别(0,1,2,3)转化为2个类别(0,1)
mglearn.discrete_scatter(X[:,0], X[:,1], y) #用画布的形式展示所有数据的位置信息,包含三个参数(特征1、2,标签)
plt.xlabel('Feature 0')
plt.ylabel('Feature 1') #特征名称
Text(0, 0.5, 'Feature 1')
用于分类的线性模型只能用一条直线来划分数据点,对整个数据集无法给出较好的结果,如下图
from sklearn.svm import LinearSVC #引入线性分类支持向量机
linear_svm = LinearSVC().fit(X, y) #训练数据
mglearn.plots.plot_2d_separator(linear_svm, X) #绘图画出模型给出的数据集切分线
mglearn.discrete_scatter(X[:,0], X[:,1], y) #绘图所有的数据
plt.xlabel('Feature 0')
plt.ylabel('Feature 1') #特征名称
plt.legend() #标出说明标签
可以看到,对于该数据集,无法使用往常的线性模型对数据集进行有效分类。
现在对输入特征进行扩展,比如说添加第二个特征的平方 f e a t u r e 1 2 feature_1^2 feature12作为一个新特征。
现将每个数据点表示为三维数据( f e a t u r e 0 , f e a t u r e 1 , f e a t u r e 1 2 feature_0,feature_1,feature_1^2 feature0,feature1,feature12),而不是二维数据点( f e a t u r e 0 , f e a t u r e 1 feature_0, feature_1 feature0,feature1),这个新的表示可以化成下图:
import numpy as np #导入numpy数组
#导入3d图形的绘制工具
from mpl_toolkits.mplot3d import Axes3D, axes3d
import matplotlib.pyplot as plt
X_new = np.hstack([X, X[:,1:]**2]) #hstack合并数据,在X的数据集上再追加一列,为X[:,1:]的平方,此时数据为三维数据
figure = plt.figure() #绘制面板对象
ax = Axes3D(figure, elev=-152, azim=-26) #3d可视化
mask = y == 0 #把数据集中所有 y = 0的点赋值给 mask变量,返回值是bool类型的列表
ax.scatter(X_new[mask,0], X_new[mask,1], X_new[mask,2], c='b', cmap=mglearn.cm2, s = 60) #绘制所有类别为0的数据点
ax.scatter(X_new[~mask,0], X_new[~mask,1], X_new[~mask,2], c='r', marker='^',cmap=mglearn.cm2, s = 60) #绘制所有类别为1的数据点
ax.set_xlabel('feature 0')
ax.set_ylabel('feature 1')
ax.set_zlabel('feature 1 ** 2') #绘制坐标轴标签
可以看到,我们已经将数据集中的二维数据成功转化为了三维数据。
在数据的新表示中,现在可以用线性模型(三维空间中的平面)将这两个类别分开。我们可以用线性模型拟合扩展后的数据来验证这一点,如下
linear_svm_3d = LinearSVC().fit(X_new, y) #再次用线性分类支持向量机训练数据
coef, intercept = linear_svm_3d.coef_.ravel(), linear_svm_3d.intercept_ #获取训练数据集后的参数
figure = plt.figure() #定义显示面板
ax = Axes3D(figure, elev=-152, azim=-26) #3d显示
#numpy.linspace()为均分函数, 用于绘制超平面需要,即把n到m的距离均分s份
xx = np.linspace(X_new[:,0].min()-2, X_new[:,0].max()+2, 50) #将第一个维度从最小值到最大值均分为50份
yy = np.linspace(X_new[:,1].min()-2, X_new[:,1].max()+2, 50) #将第二个维度从最小值到最大值均分为50份
'''
X, Y = np.meshgrid(x, y) 代表的是将x中每一个数据和y中每一个数据组合生成很多点,
然后将这些点的x坐标放入到X中,y坐标放入Y中,并且相应位置是对应的
'''
XX, YY = np.meshgrid(xx, yy) #生成了较大密度的XX, YY,分别代表数据集的第一纬度、第二维度数据
ZZ = (coef[0] * XX + coef[1] * YY + intercept)/-coef[2] #生成数据集的第三维度z的所有数据点
ax.plot_surface(XX, YY, ZZ, rstride=8, cstride=8, alpha=0.3) #根据所有数据集的数据的第一、第二、第三维度数据进行绘制决策超平面
ax.scatter(X_new[mask,0], X_new[mask,1], X_new[mask,2], c='b', cmap=mglearn.cm2, s = 60) #绘制所有类别为0的数据点
ax.scatter(X_new[~mask,0], X_new[~mask,1], X_new[~mask,2], c='r', marker='^',cmap=mglearn.cm2, s = 60) #绘制所有类别为1的数据点
ax.set_xlabel('feature 0')
ax.set_ylabel('feature 1')
ax.set_zlabel('feature 1 ** 2') #绘制坐标轴标签
如果将线性SVM模型看作原始特征的函数,那么它实际上已经不是线性的了。
它不是一条直线,而是一个椭圆,如下图
ZZ = YY ** 2
# ravel() 函数将数组多维度拉成一维数组
dec = linear_svm_3d.decision_function(np.c_[XX.ravel(), YY.ravel(), ZZ.ravel()]) #决策函数
#绘制三维等高线图,不同点在于contour() 是绘制轮廓线,contourf()会填充轮廓
plt.contourf(XX, YY, dec.reshape(XX.shape), levels=[dec.min(), 0, dec.max()], cmap=mglearn.cm2, alpha=0.5)
mglearn.discrete_scatter(X[:,0], X[:,1], y) #绘制所有数据点
plt.xlabel('Feature 0') #绘制坐标轴的特征标签
plt.ylabel('Feature 1')
Text(0, 0.5, 'Feature 1')
支持向量一般为距离决策超平面较近的一些数据点,如下图
from sklearn.svm import SVC #导入支持向量机分类器
X, y = mglearn.tools.make_handcrafted_dataset() #导入数据集
svm = SVC(kernel='rbf', C=10, gamma=0.1).fit(X, y) #初始化支持向量机分类器,核函数为高斯核、C正则程度为10、gamma初始化0.1
mglearn.plots.plot_2d_separator(svm, X, eps=.5) #绘图分界线
mglearn.discrete_scatter(X[:,0], X[:,1], y) #绘制所有数据点
sv = svm.support_vectors_ #画出支持向量
#dual_coef_.ravel()内存放的是一个列表(lambda * y_i)
sv_labels = svm.dual_coef_.ravel() > 0
#绘制支持向量
mglearn.discrete_scatter(sv[:,0], sv[:,1], sv_labels, s=15, markeredgewidth=3)
plt.xlabel('Feature 0') #绘制坐标轴的特征标签
plt.ylabel('Feature 1')
Text(0, 0.5, 'Feature 1')
gamma 参数是控制高斯核宽度的参数(如有不解,请看本章)。它决定了点与点"靠近"是指多大的距离。
C参数是正则化参数,与线性模型中用到的类似,它限制每个点的重要性。
fig, axes = plt.subplots(3, 3, figsize=(15,10))
for ax, C in zip(axes, [-1, 0, 3]):
for a, gamma in zip(ax, range(-1, 2)):
mglearn.plots.plot_svm(log_C=C, log_gamma=gamma, ax=a)
axes[0,0].legend(['class0', 'class1', 'sv class0', 'sv class1'], ncol=4, loc=(.9, 1.2))
如上图,可以发现以下规律
从左到右,参数gamma的值从0.1增加到10。gamma值较小,说明高斯核的半径较大,许多点都被看做比较靠近。这一点可以在图中看出,左侧的图决策边界非常平滑,越向右的点决策边界更关注单个点。小的gamma值表示决策边界变化很慢,生成的是复杂度较低的模型,而大的gamma值则会生成更为复杂的模型。
从上到下,将参数C的值从0.1增加到1000。与线性模型相同,C值很小,说明模型非常受限,每个数据点的影响范围非常有限。而增大C之后这些点对模型的影响变大,使得决策边界发生弯曲来将这些点正确分类。
from sklearn.datasets import load_breast_cancer
from sklearn.model_selection import train_test_split
cancer = load_breast_cancer() #加载数据集
X_train, X_test, y_train, y_test = train_test_split(cancer.data, cancer.target, random_state=0)
svc = SVC() #定义SVC支持向量机分类器
svc.fit(X_train, y_train) #训练数据集
print(svc.score(X_train, y_train)) #输出预测的训练集与测试集评分
print(svc.score(X_test, y_test))
0.903755868544601
0.9370629370629371
预测并不是特别完美。
虽然SVM的表现通常很好,但它对参数的设定和数据的缩放非常敏感。特别地,它要求所有特征具有相似的变化范围。
现在来看下每个特征的最小值与最大值,绘制在对数坐标上
#加入axis参数,当axis=0时会分别取每一列的最大值或最小值,axis=1时,
#会分别取每一行的最大值或最小值,且将所有取到的数据放在一个一维数组中。
#X_train.min(axis=0)即找出每一列的最小值,X_train.min(axis=1)即找出每一行的最小值
plt.plot(X_train.min(axis=0), 'o', label='min')
plt.plot(X_train.min(axis=1), '^', label='max')
plt.legend(loc=4)
plt.xlabel('Feature index')
plt.xlabel('Feature magnitude')
plt.yscale('log')
从这张图中可看出,特征的最小值具有完全不同的数量级,这对于其它模型来说可能是小问题,但对核SVM却有极大影响。
现在对数据集进行预处理。
解决这个问题的一种办法就是对每个特征就行缩放,使其大致位于同一范围。
核SVM常用的缩放方法就是将所有特征缩放到0和1之间,现在来进行缩放
min_on_training = X_train.min(axis=0) #计算训练集中每个特征的最小值
range_on_training = (X_train - min_on_training).max(axis=0) #计算训练集中每个特征的范围(最大值-最小值)
#减去最小值,然后除以范围,这样数据集的每个特征都会在0~1之间
X_train_scaled = (X_train - min_on_training)/range_on_training
#输出所有进行缩放后的数据
X_train_scaled
array([[0.23044157, 0.32157676, 0.21940433, ..., 0.31484671, 0.30277942,
0.09858323],
[0.20062473, 0.42116183, 0.19452699, ..., 0.06965208, 0.34042973,
0.06677161],
[0.62232003, 0.76929461, 0.60403566, ..., 0.56079917, 0.19850187,
0.07431457],
...,
[0.11619102, 0.35726141, 0.11077327, ..., 0.17402687, 0.17524147,
0.17263545],
[0.12963226, 0.35311203, 0.11706171, ..., 0. , 0.06780997,
0.06919848],
[0.21434995, 0.59004149, 0.21235575, ..., 0.33251808, 0.10782574,
0.21172767]])
可以看到,它们均位于0~1之间,再输出每个特征的最大值与最小值
print('Minimum for each feature\n{}'.format(X_train_scaled.min(axis=0))) #输出每个特征的最小值
print('Maximum for each feature\n{}'.format(X_train_scaled.max(axis=0))) #输出每个特征的最大值
Minimum for each feature
[0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
0. 0. 0. 0. 0. 0.]
Maximum for each feature
[1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1.
1. 1. 1. 1. 1. 1.]
现在同样对测试集的数据进行缩放
X_test_scaled = (X_test - min_on_training)/range_on_training #利用训练集的最小值和范围对数据集做相同的变换
svc = SVC() #定义SVC支持向量机分类器
svc.fit(X_train_scaled, y_train) #训练进行缩放后的数据集
print(svc.score(X_train_scaled, y_train)) #输出预测的训练集与测试集评分
print(svc.score(X_test_scaled, y_test))
0.9835680751173709
0.972027972027972
可以看到,无论是训练集还是测试集,预测的准确度有了明显的提高。
但是训练集与测试集的预测结果较接近,可能出现了欠拟合的状态。
现在尝试调节正则化参数C来拟合更为复杂的模型。
svc = SVC(C=30) #定义SVC支持向量机分类器,增大参数C,提高模型的复杂度(但要适度,过度增加会导致过拟合)
svc.fit(X_train_scaled, y_train) #训练进行缩放后的数据集
print(svc.score(X_train_scaled, y_train)) #输出预测的训练集与测试集评分
print(svc.score(X_test_scaled, y_test))
0.9906103286384976
0.986013986013986
可以发现,在本次测试中,增加参数C明显地提高了预测地准确度,得到了98.6%的准确度。
核支持向量机是非常强大的模型,在各种数据集上的表现均较好。
但对样本个数的缩放表现不好,当预测值较低时,可以尝试对数据进行缩放再进行预测,另外在调节参数C和gamma时也应谨慎小心。