ML--支持向量机SVM

ML–支持向量机SVM

SVM算法专门解决线性不可分

主要涉及的知识点有:

  • 支持向量机的基本原理和构造
  • 支持向量机的核函数
  • 支持向量机的参数调节
  • 支持向量机实例–对波士顿房价进行回归分析

一.支持向量机SVM基本概念

1.支持向量机SVM的原理

# 导入numpy
import numpy as np
# 导入画图工具
import matplotlib.pyplot as plt
# 导入支持向量机SVM
from sklearn import svm
# 导入数据集生成工具
from sklearn.datasets import make_blobs

# 先创建50个数据点,让它们分为两类
X,y=make_blobs(n_samples=50,centers=2,random_state=6)

# 创建一个线性内核的支持向量机模型
clf=svm.SVC(kernel='linear',C=1000)
clf.fit(X,y)

# 把数据点画出来
plt.scatter(X[:,0],X[:,1],c=y,s=30,cmap=plt.cm.Paired)

# 建立图像坐标
ax=plt.gca()
xlim=ax.get_xlim()
ylim=ax.get_ylim()

# 生成两个等差数列
xx=np.linspace(xlim[0],xlim[1],30)
yy=np.linspace(ylim[0],ylim[1],30)
YY,XX=np.meshgrid(yy,xx)
xy=np.vstack([XX.ravel(),YY.ravel()]).T
Z=clf.decision_function(xy).reshape(XX.shape)

# 把分类的决定边界画出来
ax.contour(XX,YY,Z,colors='k',levels=[-1,0,1],alpha=0.5,linestyles=['--','-','--'])

ax.scatter(clf.support_vectors_[:,0],clf.support_vectors_[:,1],s=100,linewidth=1,facecolors='none')

plt.show()

output_5_0.png

[结果分析] 从图中,可以清晰地看到,在分类器两侧分别有两条虚线,那些正好压在虚线上的数据点,就是我们刚刚提到的支持向量。而上例使用的这种方法称为最大边界间隔超平面(Maximum Margin Separating Hyperplane)。指的是说中间这条实线(在高维数据中是一个超平面),和所有支持向量之间的距离,都是最大的

如果我们把SVM的内核换成是RBF,看下结果

# 创建一个RBF内核的支持向量机模型
clf_rbf=svm.SVC(kernel='rbf',C=1000)
clf_rbf.fit(X,y)

# 把数据点画出来
plt.scatter(X[:,0],X[:,1],c=y,s=30,cmap=plt.cm.Paired)

# 建立图像坐标
ax=plt.gca()
xlim=ax.get_xlim()
ylim=ax.get_ylim()

# 生成两个等差数列
xx=np.linspace(xlim[0],xlim[1],30)
yy=np.linspace(ylim[0],ylim[1],30)
YY,XX=np.meshgrid(yy,xx)
xy=np.vstack([XX.ravel(),YY.ravel()]).T
Z=clf_rbf.decision_function(xy).reshape(XX.shape)

# 把分类的决定边界画出来
ax.contour(XX,YY,Z,colors='k',levels=[-1,0,1],alpha=0.5,linestyles=['--','-','--'])

ax.scatter(clf_rbf.support_vectors_[:,0],clf_rbf.support_vectors_[:,1],s=100,linewidth=1,facecolors='none')

plt.show()

output_8_1.png

[结果分析] 我们看到分类器的样子变得完全不一样了,这是因为当我们使用RBF内核的时候,数据点之间的距离是用如下公式来计算的:

公式中的x1和x2代表两个不同的数据点,而|||x1-x2|代表两个点之间的欧几里得距离。γ(gamma)是用来控制RBF内核宽度的参数,也就是图中实线距离两条虚线的距离

二.SVM的核函数与参数选择

1.不同核函数的SVM对比

前面提到的linearSVM就是一种使用了线性内核的SVM算法。不过linearSVM不支持对核函数进行修改,因为它默认只能使用线性内核。为了让大家能够直观体验不同内核的SVM算法在分类中的表现,我们画个图像进行展示

# 导入红酒数据集
from sklearn.datasets import load_wine

# 定义一个函数用来画图
def make_meshgrid(x,y,h=.02):
    x_min,x_max=x.min()-1,x.max()+1
    y_min,y_max=y.min()-1,y.max()+1
    xx,yy=np.meshgrid(np.arange(x_min,x_max,h),np.arange(y_min,y_max,h))
    
    return xx,yy

# 定义一个绘制等高线的函数
def plot_contours(ax,clf,xx,yy,**params):
    Z=clf.predict(np.c_[xx.ravel(),yy.ravel()])
    Z=Z.reshape(xx.shape)
    out=ax.contourf(xx,yy,Z,**params)
    return out

# 使用酒的数据集
wine=load_wine()

# 选取数据集的前两个特征
X=wine.data[:,:2]
y=wine.target

C=1.0   # 选取数据集的前两个特征
models=(svm.SVC(kernel='linear',C=C),svm.LinearSVC(C=C),svm.SVC(kernel='rbf',gamma=0.7,C=C),svm.SVC(kernel='poly',degree=3,C=C))

models=(clf.fit(X,y) for clf in models)

# 设定图题
titles=('SVC with linear kernel','LinearSVC (linear kernel)','SVC with RBF kernel','SVC with polynomial (degree 3) kernel')

# 设定一个字图形的个数和排列方式
fit,sub=plt.subplots(2,2)
plt.subplots_adjust(wspace=0.4,hspace=0.4)

# 使用前面定义的函数进行画图
X0,X1=X[:,0],X[:,1]
xx,yy=make_meshgrid(X0,X1)

for clf,title,ax in zip(models,titles,sub.flatten()):
    plot_contours(ax,clf,xx,yy,cmap=plt.cm.plasma,alpha=0.8)
    ax.scatter(X0,X1,c=y,cmap=plt.cm.plasma,s=20,edgecolors='k')
    ax.set_xlim(xx.min(),xx.max())
    ax.set_ylim(yy.min(),yy.max())
    
    ax.set_xlabel('Feature 0')
    ax.set_ylabel('Feature 1')
    ax.set_xticks(())
    ax.set_yticks(())
    ax.set_title(title)
    
plt.show()

output_13_2.png

[结果分析] 我们可以看到线性内核的SVClinearSVC得到的结果非常近似,但仍然有一点点差别。其中一个原因是linearSVC对L2范数进行最小化,而线性内核的SVC是对L1范数进行最小化。无论如何,linearSVC和线性内核的SVC生成的决定边界都是线性的,在更高维数据集中将会是相交的超平面。而RBF内核的SVCpolynomial内核的SVC分类器的决定边界则完全不是线性的,它们更加弹性。而决定了它们决定边界形状的,就是它们的参数。在polynomial内核的SVC中,起决定性作用的参数就是degree和正则化参数C,在本例中我们使用的degree为3,也就是对原始数据集的特征进行乘3次方操作。而在RBF内核的SVC中,其起决定作用的是正则化参数C和参数gamma

接下来我们重点介绍一下RBF内核SVCgamma参数调节

2.支持向量机的gamma参数调节

首先让我们看一下不同的gamma值对于RBF内核的SVC分类器有什么影响

C=1.0 # SVM正则化参数
models=(svm.SVC(kernel='rbf',gamma=0.1,C=C),svm.SVC(kernel='rbf',gamma=1,C=C),svm.SVC(kernel='rbf',gamma=10,C=C))

models=(clf.fit(X,y) for clf in models)

# 设定图题
titles=('gamma=0.1','gamma=1','gamma=10')

# 设置子图形个数和排列
fig,sub=plt.subplots(1,3,figsize=(10,3))

X0,X1=X[:,0],X[:,1]
xx,yy=make_meshgrid(X0,X1)

# 使用定义好的函数进行画图
for clf,title,ax in zip(models,titles,sub.flatten()):
    plot_contours(ax,clf,xx,yy,cmap=plt.cm.plasma,alpha=0.8)
    
    ax.scatter(X0,X1,c=y,cmap=plt.cm.plasma,s=20,edgecolors='k')
    ax.set_xlim(xx.min(),xx.max())
    ax.set_ylim(yy.min(),yy.max())
    
    ax.set_xlabel('Feature 0')
    ax.set_ylabel('Feature 1')
    
    ax.set_xticks(())
    ax.set_yticks(())
    ax.set_title(title)
    
plt.show()

output_17_0.png

[结果分析] 从图中可以看出,自左到右gamma值从0.1增加到10,gamma值越小,则RBF内核的直径越大,这样就会有更多的点被模型圈进决定边界中,所以决定边界也就越平滑,这时的模型也就越简单;而随着参数的增加,模型则更倾向于把每个点都放到相应的决定边界中,这时模型的复杂度也相应提高了。所以gamma值越小模型越倾向于欠拟合,而gamma值越大,则模型越倾向于出现过拟合的问题

而至于正则化参数CC值越小,模型就越受限,也就是说单个数据点对模型的影响越小,模型就越简单;而C值越大,每个数据点对模型的影响就越大,模型也会更加复杂

3.SVM算法的优势与不足

SVM应对高维数据集和低维数据集都还算是得心应手。但是,前提条件是数据集的规模不太大。如果数据集中的样本数量在1万以内,SVM都能驾驭得了,但如果样本数量超过10万的话,SVM就会非常耗费时间和内存

SVM还有一个短板,就是对于数据预处理和参数调节要求非常高

SVM算法中,有3个参数是比较重要的:第一个是核函数的选择;第二个是核函数的参数,例如RBFgamma值;第三个是正则化参数CRBF内核的gamma值是用来调节内核宽度的,gamma值和C值一起控制模型的复杂度,数值越大模型越复杂,而数值越小模型越简单

三.SVM实例–波士顿房价回归分析

scikit-learn中,内置了一个非常适合做回归分析的数据集–波士顿房价数据集。我们将使用该数据集讲解SVM中用于回归分析的SVR的用法

1.初步了解数据集

# 导入波士顿房价数据集
from sklearn.datasets import load_boston
boston=load_boston()

# 打印数据集中的键
print(boston.keys())
dict_keys(['data', 'target', 'feature_names', 'DESCR', 'filename'])

[结果分析] 从结果中可以看出,波士顿房价数据集中有5个键,分别是数据,目标,特征名称,描述和文件名。我们可能有疑问,波士顿房价数据集比红酒数据集少了一个键,就是目标名称(target_names),这是为什么?我们查看一下

print(boston['DESCR'])
.. _boston_dataset:

Boston house prices dataset
---------------------------

**Data Set Characteristics:**  

    :Number of Instances: 506 

    :Number of Attributes: 13 numeric/categorical predictive. Median Value (attribute 14) is usually the target.

    :Attribute Information (in order):
        - CRIM     per capita crime rate by town
        - ZN       proportion of residential land zoned for lots over 25,000 sq.ft.
        - INDUS    proportion of non-retail business acres per town
        - CHAS     Charles River dummy variable (= 1 if tract bounds river; 0 otherwise)
        - NOX      nitric oxides concentration (parts per 10 million)
        - RM       average number of rooms per dwelling
        - AGE      proportion of owner-occupied units built prior to 1940
        - DIS      weighted distances to five Boston employment centres
        - RAD      index of accessibility to radial highways
        - TAX      full-value property-tax rate per $10,000
        - PTRATIO  pupil-teacher ratio by town
        - B        1000(Bk - 0.63)^2 where Bk is the proportion of blacks by town
        - LSTAT    % lower status of the population
        - MEDV     Median value of owner-occupied homes in $1000's

    :Missing Attribute Values: None

    :Creator: Harrison, D. and Rubinfeld, D.L.

This is a copy of UCI ML housing dataset.
https://archive.ics.uci.edu/ml/machine-learning-databases/housing/


This dataset was taken from the StatLib library which is maintained at Carnegie Mellon University.

The Boston house-price data of Harrison, D. and Rubinfeld, D.L. 'Hedonic
prices and the demand for clean air', J. Environ. Economics & Management,
vol.5, 81-102, 1978.   Used in Belsley, Kuh & Welsch, 'Regression diagnostics
...', Wiley, 1980.   N.B. Various transformations are used in the table on
pages 244-261 of the latter.

The Boston house-price data has been used in many machine learning papers that address regression
problems.   
     
.. topic:: References

   - Belsley, Kuh & Welsch, 'Regression diagnostics: Identifying Influential Data and Sources of Collinearity', Wiley, 1980. 244-261.
   - Quinlan,R. (1993). Combining Instance-Based and Model-Based Learning. In Proceedings on the Tenth International Conference of Machine Learning, 236-243, University of Massachusetts, Amherst. Morgan Kaufmann.

[结果分析] 从上面的这段描述可以看出,数据集中共有506个样本,每个样本有13个特征变量。而后面还有一个叫作中位数的第14个变量,这个变量就是该数据集中的target

2.使用SVR进行建模

我们要先制作训练数据集和测试数据集,代码如下:

# 导入数据集拆分工具
from sklearn.model_selection import train_test_split
# 建立训练数据集和测试数据集
X,y=boston.data,boston.target
X_train,X_test,y_train,y_test=train_test_split(X,y,random_state=8)

print(X_train.shape)
print(X_test.shape)
(379, 13)
(127, 13)

下面开始用SVR进行建模,我们在前面介绍了SVM的两种核函数:Linearrbf,不过我们不知道这两种核函数哪一个会让模型表现得更好,那么分别尝试一下:

# 导入支持向量机回归模型
from sklearn.svm import SVR
# 分别测试sklearn核函数和rbf核函数
for kernel in ['linear','rbf']:
    svr=SVR(kernel=kernel)
    svr.fit(X_train,y_train)
    print(kernel,'核函数的模型训练集得分:{:.3f}'.format(svr.score(X_train,y_train)))
    print(kernel,'核函数的模型测试集得分:{:.3f}'.format(svr.score(X_test,y_test)))
linear 核函数的模型训练集得分:0.709
linear 核函数的模型测试集得分:0.696
rbf 核函数的模型训练集得分:0.145
rbf 核函数的模型测试集得分:0.001

[结果分析] 两种核函数的模型得分都不能令人满意。使用rbf核函数的模型糟糕透了,在训练数据集的分只有0.145,而在测试数据集的得分完全可以用"灾难"来形容了—居然只有0.001分

这是什么原因呢?想想,会不会是数据集的各个特征之间的量级差的比较远?由于SVM算法对于数据预处理的要求是比较高的,如果数据特征量差异较大,我们需要对数据进行预处理。所以我们用可视化的方法看一看数据集中各个特征的数量级是什么情况

# 将特征数值中的最小值和最大值用散点图画出来
plt.plot(X.min(axis=0),'v',label='min')
plt.plot(X.max(axis=0),'^',label='max')

# 设定纵坐标为对数形式
plt.yscale('log')

# 设置图注位置为最佳
plt.legend(loc='best')

# 设定横纵轴标题
plt.xlabel('features')
plt.ylabel('feature magnitude')

# 显示图形
plt.show()

output_37_0.png

看来为了能够让SVM算法能够更好地对数据进行拟合,我们必须对数据集进行预处理

# 导入数据预处理工具
from sklearn.preprocessing import StandardScaler
# 对训练集和测试集进行数据预处理
scaler=StandardScaler()
scaler.fit(X_train)
X_train_scaled=scaler.transform(X_train)
X_test_scaled=scaler.transform(X_test)

# 将预处理后的数据特征最大值和最小值用散点图表示出来
plt.plot(X_train_scaled.min(axis=0),'v',label='train set min')
plt.plot(X_train_scaled.max(axis=0),'^',label='train set max')
plt.plot(X_test_scaled.min(axis=0),'v',label='test set min')
plt.plot(X_test_scaled.max(axis=0),'^',label='test set max')
plt.yscale('log')

# 设置图注位置
plt.legend(loc='best')

# 设置横纵轴标题
plt.xlabel('scaled features')
plt.ylabel('scaled feature magnitude')

plt.show()

output_39_0.png

[结果分析] 经过了我们的预处理,不管是训练集还是测试集,基本上所有的特征最大值都不会超过10,而最小值也都趋于0,以至于在图中我们看不到它们了

现在我们在试试用经过预处理的数据来训练模型,看看结果会有什么不同

# 导入支持向量机回归模型
from sklearn.svm import SVR
# 分别测试sklearn核函数和rbf核函数
for kernel in ['linear','rbf']:
    svr=SVR(kernel=kernel)
    svr.fit(X_train_scaled,y_train)
    print(kernel,'核函数的模型训练集得分:{:.3f}'.format(svr.score(X_train_scaled,y_train)))
    print(kernel,'核函数的模型测试集得分:{:.3f}'.format(svr.score(X_test_scaled,y_test)))
linear 核函数的模型训练集得分:0.706
linear 核函数的模型测试集得分:0.698
rbf 核函数的模型训练集得分:0.665
rbf 核函数的模型测试集得分:0.695

[结果分析] 经过预处理之后,linear内核的SVR得分变化不大,而rbf内核的SVR得分有了巨大的提升

SVC一样,SVR模型也有gammaC两个参数,接下来我们试着对两个参数进行修改

# 设置模型的C参数和gamma参数
svr=SVR(C=100,gamma=0.1)
svr.fit(X_train_scaled,y_train)

print('调节参数后的模型在训练集得分:{:.3f}'.format(svr.score(X_train_scaled,y_train)))
print('调节参数后的模型在测试集得分:{:.3f}'.format(svr.score(X_test_scaled,y_test)))
调节参数后的模型在训练集得分:0.966
调节参数后的模型在测试集得分:0.894

[结果分析] 这是一个比较不错的结果,我们看到通过参数调节,rbf内核的SVR模型在训练集的得分已经高达0.966,而在测试数据集的得分也达到了0.894,可以说现在模型的表现已经是可以接受的