支持向量机SVM(Support Vector Machine)

作者:欧新宇(Xinyu OU)

本文档所展示的测试结果,均运行于:Intel Core i7-7700K CPU 4.2GHz

原始SVM算法是由弗拉基米尔·万普尼克和亚历克塞·泽范兰杰斯于1963年发明的。1992年,Bernhard E. Boser、Isabelle M. Guyon和弗拉基米尔·万普尼克提出了一种通过将核技巧应用于最大间隔超平面来创建非线性分类器的方法。当前标准的前身(软间隔)由Corinna Cortes和Vapnik于1993年提出,并于1995年发表。

上个世纪90年代,由于人工神经网络的衰落,SVM在很长一段时间里都是当时的明星算法。被认为是一种理论优美且非常实用的机器学习算法。

在理论方面,SVM算法涉及到了非常多的概念:间隔(margin)、支持向量(support vector)、核函数(kernel)、对偶(duality)、凸优化等。有些概念理解起来比较困难,例如kernel trick和对偶问题。在应用方法,SVM除了可以当做有监督的分类和回归模型来使用外,还可以用在无监督的聚类及异常检测。相对于现在比较流行的深度学习(适用于解决大规模非线性问题),SVM非常擅长解决复杂的具有中小规模训练集的非线性问题,甚至在特征多于训练样本时也能有非常好的表现(深度学习此时容易过拟合)。但是随着样本量m的增加,SVM模型的计算复杂度会呈$m^2$或$m^3$增加。

0. Explain SVM like I am a 5 year old


很久很久以前,有个大侠的爱人被魔鬼抓走了,魔鬼要这位大侠和它玩一个游戏才能放了大侠的爱人。

魔鬼在桌子上似乎很有规律地方了两种颜色的球,然后说到: "你需要用一根棍子将它们分开,并且要求在后续放更多球之后,仍然适用。"

Image

于是乎,大侠这样放下了棍子,看起来还不错。

Image

魔鬼又在桌上放下了更多的球,似乎也还不错,不过有一个球站错阵营了。

Image

于是,大侠祭出了一件神级法宝 —— 支持向量机(Support Vector Machine,SVM)。利用SVM,大侠让棍子再一次完美地充当了一条分割线。

Image


重点:SVM的特性一:试图建立一条完美的分界线,让该分界线处于最佳的位置,让分界线两边与样本间有尽可能大的间隙。

Image


借助神级法宝SVM的第一个trick —— 最大类间间隙,大侠度过了第一关。于是,魔鬼给了大侠一个更困难的挑战。

Image

这个问题让大侠范畴了,似乎一条棍子根本没办法将两种颜色的球进行分隔。此时,大侠想到了法宝SVM的另一个神技 —— 超平面。于是乎,大侠用力一拍桌子,所有的球都飞到了空中。凭借大侠"快准狠"的身手,他迅速将一张纸插入到球的中间。

Image

此时,从魔鬼的角度来看,这些球看起来就像是被一条曲线给分开了。

Image


重点:SVM的特性二:由于样本特征的特性,当样本在原始的特征空间中线性不可分时,我们可以将其转换到高维空间,并利用高维空间中的超平面对样本进行分隔。


因为大侠的聪明才智,他成功解救了他的爱人。之后,神界的无聊众神们认真总结并研究了这个故事。它们把那些带颜色的球称为数据 (data),把棍子称为分类器 (classifier),把最大间隙trick称为优化 (optimization),拍桌子的绝招称为核化 (kernelling),而那张纸就是超平面 (hyperplane).

为了更清晰理解SVM,这里给出一个视频解释。

【Video】http://ouxinyu.cn/Teaching/MachineLearning/Attachments/Ch0709SVMintro.mp4

1. 支持向量机SVM的原理


SVM(Support Vector Machines)支持向量机是在所有知名的数据挖掘及传统机器学习算法中最健壮,最准确的方法之一,它属于二分类算法,可以支持线性和非线性的分类。

当然,SVM也可以支持多分类。

1.1 基本原理

首先,我们来了解一下线性分类器。假设在一个二维线性可分的数据集中,如下图所示,我们需要找一个超平面把两组数据分开。图中的四条直线都可以实现分隔两种数据,然而哪一条直线能够达到更好的泛化能力呢?换句话说,我们需要找到一个能够使两个类的空间最大的超平面

Image

所谓超平面是什么呢?

在二维空间中,超平面就是一条直线(例如上图中的分割线,或大侠放的棍子);而在三维空间中就是一个平面(例如大侠插入球中间的纸)。我们将这个划分数据的决策边界统称为超平面。距离这个超平面最近的点就叫做支持向量,点到超平面的距离叫做间隔。支持向量机就是要使超平面支持向量之间的间隔尽可能的大,这样超平面才可以更好地将两类样本进行准确分隔。

而保证间隔尽可能大就是保证分类器误差尽可能小模型尽可能健壮


事实上,

【重点】支持向量机的核心任务是:最大化类间距,最小化类内距。


1.2 SVM的数学表达

暂略...

1.3 SVM的核函数

1.3.1 为什么要使用核函数

以上讨论的都是线性可分的情况,但实际中的样本很多时候都是线性不可分的,例如下列图中所示的样本。

Image

对于非线性分布的样本(线性不可分问题),Linear模型很难进行处理,而SVM具有一个优秀的trick —— 核函数K(*,*),它通过将数据映射到高维空间,来解决在原始空间线性不可分的问题。

基于核函数的SVM的基本工作流程是:

  1. 在低维空间中完成特征计算
  2. 通过核函数将输入空间中的特征映射到高维特征空间
  3. 在高维特征空间中构造最优分离超平面

通过以上的操作,SVM可以实现将原始特征空间不好分隔的非线性数据进行最优分隔。

事实上,上面给出的样本集都可以看作是不同半径的圆(或圆环),所以,一个理想的分界面应该是一个"圆圈",而不是一条线。这个圆圈就是我们说的(超平面)。下面,我们将其中两个样本映射到三维空间,可以得到如下的图例.

图一: Image

图二: Image

小结:

  1. SVM的春天在于核函数
  2. SVM不仅仅能用于二分类,也同样可以用于多分类;同时也可以实现回归和聚类;
  3. 核函数的主要功能是将特征映射投射到高维空间以实现"线性"可分;
  4. 相比于简单地将特征映射到高维空间,核函数的价值在于:它所有的计算都是基于原始空间,只是将实质的分类效果表现到高维空间。这种机制避免的维度爬升的不可预见性(维度灾难),以及高维空间计算的复杂性。

1.3.2 核函数的基本用法

在SVM算法中,训练模型的过程可以理解为每个样本对于超平面重要性的判断。换句话说,在训练数据中,只有一部分数据对于边界的确定是有帮助的,这些点刚好位于超平面附近。我们称这些点为"支持向量",这也是"支持向量机"名称的由来。

在SVM中,使用最普遍的核函数包括: 线性核函数(Linear kernel)、多项式核函数(Polynomial kernel)、高斯核函数(又称为径向基核函数核(Radial basis function kernel, RBF))、Sigmoid核函数.

  • 线性核

下面我们给出一个基于最基本的线性核的支持向量机示例,基本过程仍然是我们习惯的步骤:

  1. 导入必须库
  2. 创建/导入数据
  3. 数据预处理,包括训练集、测试集划分,数据正则化,数据清洗等
  4. 构建模型,并进行模型训练(或称为拟合数据)
  5. 输出模型准确率
  6. 可视化分析
In [21]:
# 1.导入必须库
import numpy as np
import matplotlib.pyplot as plt
# 导入支持向量机SVM 
from sklearn import svm
# 导入数据集生成和拆分工具
from sklearn.datasets import make_blobs
from sklearn.model_selection import train_test_split

# 2.创建/导入数据
X, y = make_blobs(n_samples=100, centers=2, random_state=6)

# 3.数据预处理,包括训练集、测试集划分,数据正则化,数据清洗等
X_train, X_test, y_train, y_test = train_test_split(X, y)

# 4.构建模型,并进行模型训练(或称为拟合数据)
clf = svm.SVC(kernel='linear', C=1000)
clf.fit(X_train, y_train)

# 5.输出模型准确率
print("线性SVM训练集评分为: {0:.3f}, 测试集评分为: {1:.3f}".format(clf.score(X_train, y_train), clf.score(X_test, y_test)))

# 6.可视化分析
# c: 色彩或颜色序列; s: 散点尺寸; cmap: Colormap 
plt.scatter(X_train[:,0], X_train[:,1], c=y_train, 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.savefig(r'results/Ch0801LinearSVM.png', dpi=300)
plt.show()
线性SVM训练集评分为: 1.000, 测试集评分为: 1.000

【结果分析】从上图中可以看到, 两条虚线分别位于实线的两侧, 同时经过了至少一个样本数据点. 此时,我们可以将这些落在虚线上的样本称之为支持向量. 在上面的例子中, 我们使用的是SVM中最基本的"最大边界间隔超平面", 它使得图中的实线与所有支持向量之间的距离, 加权和最大. 下面我们尝试使用RBF核函数替代Linear核函数.

  • RBF核

RBF核又称为高斯核,它的数学表达为: $k_{rbf}(x_1, x_2) = exp(\gamma ||x_1 - x_2||^2)$. 公式中,$\gamma$是用来控制RBF核宽度参数, 也就是"超平面"与"支持向量"所在虚线之间的距离; $x_1, x_2$分别代表不同的数据点, $||x_1 - x_2||$代表两点之间的欧几里得距离.

【知识点】 在二维的笛卡尔坐标系中, 点A($x_1, y_1$)和点B($x_2, y_2$)之间的欧几里得距离(欧氏距离) $D=\sqrt{(x_2-x_1)^2 + (y_2-y_1)^2}$.

In [22]:
# 1.导入必须库
import numpy as np
import matplotlib.pyplot as plt
# 导入支持向量机SVM 
from sklearn import svm
# 导入数据集生成和拆分工具
from sklearn.datasets import make_blobs
from sklearn.model_selection import train_test_split

# 2.创建/导入数据
X, y = make_blobs(n_samples=100, centers=2, random_state=6)

# 3.数据预处理,包括训练集、测试集划分,数据正则化,数据清洗等
X_train, X_test, y_train, y_test = train_test_split(X, y)

# 4.构建模型,并进行模型训练(或称为拟合数据)
clf_rbf = svm.SVC(kernel='rbf', C=1000, gamma='auto')
clf_rbf.fit(X_train, y_train)

# 5.输出模型准确率
print("线性SVM训练集评分为: {0:.3f}, 测试集评分为: {1:.3f}".format(clf.score(X_train, y_train), clf.score(X_test, y_test)))

# 6.可视化分析
# c: 色彩或颜色序列; s: 散点尺寸; cmap: Colormap 
plt.scatter(X_train[:,0], X_train[:,1], c=y_train, 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='b')

plt.savefig(r'results/Ch0801LinearSVM.png', dpi=300)
plt.show()
线性SVM训练集评分为: 1.000, 测试集评分为: 1.000

2. SVM核函数和参数选择


2.1 基于不同核函数的SVM的对比

In [23]:
# TODO: 1. 导入必须库 以及 定义必要的函数
import numpy as np
import matplotlib.pyplot as plt
# 导入 Wine酒 数据集
from sklearn import datasets
from sklearn.model_selection import train_test_split
# 导入支持向量机SVM
from sklearn import svm

# 定义一个绘图函数


def make_meshgrid(x, y, h=0.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


# TODO: 2. 创建/导入数据
wine = datasets.load_wine()

# TODO: 3. 数据预处理,包括训练集、测试集划分,数据正则化,数据清洗等
X = wine.data[:, :2]  # 为便于可视化仍然仅使用前两个特征
y = wine.target
X_train, X_test, y_train, y_test = train_test_split(X, y)

# TODO: 4. 构建模型,并进行模型训练(或称为拟合数据)
C = 1.0
models = (svm.LinearSVC(C=C, max_iter=10000),
          svm.SVC(kernel='linear', C=C, gamma='auto'),
          svm.SVC(kernel='rbf', gamma=0.7, C=C),
          svm.SVC(kernel='sigmoid', C=C, gamma='auto'),
          svm.SVC(kernel='poly', degree=3, C=C, gamma='auto'),
          svm.SVC(kernel='poly', degree=4, C=C, gamma='auto'))
# TODO:考虑输出变量models
models = (clf.fit(X_train, y_train) for clf in models)

titles = ('LinearSVC (linear kernel)',
          'SVC with linear kernel',
          'SVC with RBF kernel',
          'SVC with sigmoid kernel',
          'SVC with ploy(degree=3) kernel',
          'SVC with ploy(degree=4) kernel')


# TODO: 5. 输出模型准确率
# for model, title in zip(models, titles):
#     print("模型名称: {0}, 训练集准确率: {1:.3f}, 测试集准确率: {2:.3f}".format(
#         title, model.score(X_train, y_train), model.score(X_test, y_test)))

# TODO: 6. 可视化分析
# 设定一个子图形的个数和排列方式
fig, sub = plt.subplots(3, 2, figsize=(10, 10))
plt.subplots_adjust(wspace=0.4, hspace=0.4)

# 使用自定义的函数进行画图
X0, X1 = X_train[:, 0], X_train[:, 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_train, 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)
    
    # 输出准确率
    print("模型名称: {0}, 训练集准确率: {1:.3f}, 测试集准确率: {2:.3f}".format(
        title, clf.score(X_train, y_train), clf.score(X_test, y_test)))

plt.show()
模型名称: LinearSVC (linear kernel), 训练集准确率: 0.782, 测试集准确率: 0.667
模型名称: SVC with linear kernel, 训练集准确率: 0.805, 测试集准确率: 0.689
模型名称: SVC with RBF kernel, 训练集准确率: 0.872, 测试集准确率: 0.778
模型名称: SVC with sigmoid kernel, 训练集准确率: 0.414, 测试集准确率: 0.356
模型名称: SVC with ploy(degree=3) kernel, 训练集准确率: 0.865, 测试集准确率: 0.733
模型名称: SVC with ploy(degree=4) kernel, 训练集准确率: 0.842, 测试集准确率: 0.711

【结果分析】

  1. LinearSVC 和 SVC with linear核,由于都属于线性方法,所以性能相差不大,细微的差别主要来源于两种方法的正则化算法。linearSVC使用最小化L2范数,线性内核SVC使用最小化L1范数
  2. 高斯核的RBF多项式核性能略微好一点,通过更具有弹性的非线性算法,它们能更好地拟合数据
  3. Sigmoid核的SVM在这个任务中可以说是惨不忍睹,但是Sigmoid确是神经网络的一种重要激活函数
  4. 超参数degree, 正则化参数C对基于多项式核的SVM具有较大影响
  5. 超参数gamma, 正则化参数C对 基于高斯核(RBF核)的SVM具有较大影响

2.2 支持向量机的超参数调节与分析

2.2.1 RBF核的gamma值

In [24]:
# TODO: 1. 导入必须库 以及 定义必要的函数
import numpy as np
import matplotlib.pyplot as plt
# 导入 Wine酒 数据集
from sklearn import datasets
from sklearn.model_selection import train_test_split
# 导入支持向量机SVM
from sklearn import svm

# 定义一个绘图函数


def make_meshgrid(x, y, h=0.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


# TODO: 2. 创建/导入数据
wine = datasets.load_wine()

# TODO: 3. 数据预处理,包括训练集、测试集划分,数据正则化,数据清洗等
X = wine.data[:, :2]  # 为便于可视化仍然仅使用前两个特征
y = wine.target
X_train, X_test, y_train, y_test = train_test_split(X, y)

# TODO: 4. 构建模型,并进行模型训练(或称为拟合数据)
C = 1.0
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),
          svm.SVC(kernel='rbf', gamma=15, C=C),
          svm.SVC(kernel='rbf', gamma=20, C=C),
          svm.SVC(kernel='rbf', gamma=50, C=C))
# TODO:考虑输出变量models
models = (clf.fit(X_train, y_train) for clf in models)

titles = ('gamma=0.1',
          'gamma=1',
          'gamma=10',
          'gamma=15',
          'gamma=20',
          'gamma=50')


# TODO: 5. 输出模型准确率
# for model, title in zip(models, titles):
#     print("RBFSVM({0}): 训练集准确率: {1:.3f}, 测试集准确率: {2:.3f}".format(
#         title, clf.score(X_train, y_train), clf.score(X_test, y_test)))

# TODO: 6. 可视化分析
# 设定一个子图形的个数和排列方式
fig, sub = plt.subplots(3, 2, figsize=(10, 10))
plt.subplots_adjust(wspace=0.4, hspace=0.4)

# 使用自定义的函数进行画图
X0, X1 = X_train[:, 0], X_train[:, 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_train, 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)
    
    # 输出准确率
    print("RBFSVM({0}): 训练集准确率: {1:.3f}, 测试集准确率: {2:.3f}".format(
        title, clf.score(X_train, y_train), clf.score(X_test, y_test)))

plt.show()
RBFSVM(gamma=0.1): 训练集准确率: 0.850, 测试集准确率: 0.756
RBFSVM(gamma=1): 训练集准确率: 0.857, 测试集准确率: 0.756
RBFSVM(gamma=10): 训练集准确率: 0.910, 测试集准确率: 0.756
RBFSVM(gamma=15): 训练集准确率: 0.940, 测试集准确率: 0.733
RBFSVM(gamma=20): 训练集准确率: 0.940, 测试集准确率: 0.667
RBFSVM(gamma=50): 训练集准确率: 0.947, 测试集准确率: 0.578

【结果分析】

  1. 随着gamma值的的增大,模型的非线性现象越来越明显。简单的说, 当gamma → 0时,模型越来越简单,对于训练样本错分的几率越来越大; 当gamma增大时,模型变得越来平滑、复杂,非线性现象也越来越明显,对于训练样本来说分错的几率也越来越小
  2. 可以看到的是,当gamma变大时,测试集的性能并没有随着训练集性能的增加而持续增加,而是出现一个峰值后逐渐降低。
  3. 总结一下:gamma值越小模型越来越趋近于欠拟合,gamma值越大模型越来越趋向于过拟合.

有兴趣的同学,请尝试调整正则化参数C,看看变化趋势.

2.2.2 多项式核的degree值

In [25]:
# TODO: 1. 导入必须库 以及 定义必要的函数
import numpy as np
import matplotlib.pyplot as plt
# 导入 Wine酒 数据集
from sklearn import datasets
from sklearn.model_selection import train_test_split
# 导入支持向量机SVM
from sklearn import svm

# 定义一个绘图函数


def make_meshgrid(x, y, h=0.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


# TODO: 2. 创建/导入数据
wine = datasets.load_wine()

# TODO: 3. 数据预处理,包括训练集、测试集划分,数据正则化,数据清洗等
X = wine.data[:, :2]  # 为便于可视化仍然仅使用前两个特征
y = wine.target
X_train, X_test, y_train, y_test = train_test_split(X, y)

# TODO: 4. 构建模型,并进行模型训练(或称为拟合数据)
C = 1.0
models = (svm.SVC(kernel='poly', degree=1, C=C, gamma='auto'),
          svm.SVC(kernel='poly', degree=2, C=C, gamma='auto'),
          svm.SVC(kernel='poly', degree=3, C=C, gamma='auto'),
          svm.SVC(kernel='poly', degree=4, C=C, gamma='auto'),
          svm.SVC(kernel='poly', degree=5, C=C, gamma='auto'),
          svm.SVC(kernel='poly', degree=6, C=C, gamma='auto'))
# TODO:考虑输出变量models
models = (clf.fit(X_train, y_train) for clf in models)

titles = ('degree=1',
          'degree=2',
          'degree=3',
          'degree=4',
          'degree=5',
          'degree=6')


# TODO: 5. 输出模型准确率
# for model, title in zip(models, titles):
#     print("RBFSVM({0}): 训练集准确率: {1:.3f}, 测试集准确率: {2:.3f}".format(
#         title, clf.score(X_train, y_train), clf.score(X_test, y_test)))

# TODO: 6. 可视化分析
# 设定一个子图形的个数和排列方式
fig, sub = plt.subplots(3, 2, figsize=(10, 10))
plt.subplots_adjust(wspace=0.4, hspace=0.4)

# 使用自定义的函数进行画图
X0, X1 = X_train[:, 0], X_train[:, 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_train, 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)
    
    # 输出准确率
    print("RBFSVM({0}): 训练集准确率: {1:.3f}, 测试集准确率: {2:.3f}".format(
        title, clf.score(X_train, y_train), clf.score(X_test, y_test)))

plt.show()
RBFSVM(degree=1): 训练集准确率: 0.759, 测试集准确率: 0.889
RBFSVM(degree=2): 训练集准确率: 0.812, 测试集准确率: 0.889
RBFSVM(degree=3): 训练集准确率: 0.812, 测试集准确率: 0.889
RBFSVM(degree=4): 训练集准确率: 0.789, 测试集准确率: 0.867
RBFSVM(degree=5): 训练集准确率: 0.797, 测试集准确率: 0.867
RBFSVM(degree=6): 训练集准确率: 0.797, 测试集准确率: 0.822

【结果分析】

  1. 随着degree值的的增大,模型的非线性现象越来越明显
  2. 从现有设置来看,并未观察到模型准确率趋于稳定,原因可能是模型训练迭代次数不足

2.3 SVM算法的优缺点

优点:

  1. 使用核函数可以向高维空间进行映射

  2. 使用核函数可以解决非线性的分类

  3. 分类思想很简单,就是将样本与决策面的间隔最大化

  4. 分类效果较好

缺点:

  1. SVM算法对大规模训练样本难以实施(比如1万以上的样本,特别是特征较多的样本)

  2. 用SVM解决多分类问题存在困难. (但可以通过adaboost、层次化等思想,联合多个SVM实现多分类)

3. SVM实例——波士顿房价回归分析


SVM是重要的分类工具,它也可以用来做回归分析,下面我们使用sklearn数据集中非常适合做回归分析的波士顿房价数据集完成SVM回归分析。

3.1 数据集分析

  • 载入必须库及数据集
In [26]:
# TODO: 1. 导入必须库 以及 定义必要的函数
import matplotlib.pyplot as plt
# 导入 Boston波士顿房价数据集
from sklearn import datasets
from sklearn.model_selection import train_test_split
# 导入支持向量机SVM
from sklearn import svm
# 或使用 from sklearn.svm import SVR, 直接使用 SVR() 进行调用

# TODO: 2. 创建/导入数据
boston = datasets.load_boston()
  • 数据集分析
In [27]:
# 预览数据集中的键
# 数据集中包含5个键,分别是数据、标签、特征名称、数据集描述和文件名
print(boston.keys())
dict_keys(['data', 'target', 'feature_names', 'DESCR', 'filename'])
In [28]:
# 预览某个指定键的值
print(boston['filename'])
C:\Users\oxy\Anaconda3\lib\site-packages\sklearn\datasets\data\boston_house_prices.csv
In [29]:
# 预览某个指定键的值
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.

3.2 数据集预处理

In [30]:
# TODO: 3. 数据预处理,包括训练集、测试集划分,数据正则化,数据清洗等
X, y = boston.data, boston.target

X_train, X_test, y_train, y_test = train_test_split(X, y)

观察一下训练集和测试集的情况

In [31]:
print("训练集的形态: 数据{}, 标签{}".format(X_train.shape, y_train.shape))
print("训练集的形态: 数据{}, 标签{}".format(X_test.shape, y_test.shape))
训练集的形态: 数据(379, 13), 标签(379,)
训练集的形态: 数据(127, 13), 标签(127,)

3.2 使用SVR进行建模

支持向量回归模型(Support Vector Regression, SVR)是使用SVM来拟合曲线,做回归分析。

分类和回归问题是有监督机器学习中最重要的两类任务。与分类的输出是有限个离散的值(例如 ${−1,1}$)不同的是,回归模型的输出在一定范围内是连续的。

In [32]:
# TODO: 4. 构建模型,并进行模型训练(或称为拟合数据)
for kernel in ['linear','rbf', 'sigmoid']:
    svr = svm.SVR(kernel=kernel, gamma='auto')
    svr.fit(X_train, y_train)
    print(kernel, '核函数的模型训练集得分:{:.3f}, 测试集得分:{:.3f}'.format(svr.score(X_train, y_train),svr.score(X_test, y_test)))
linear 核函数的模型训练集得分:0.704, 测试集得分:0.688
rbf 核函数的模型训练集得分:0.160, 测试集得分:-0.040
sigmoid 核函数的模型训练集得分:-0.015, 测试集得分:-0.060

可以看到三种核函数的性能都不尽如人意。使用了"linear核"的SVM得分只有0.7左右,而"RBF核"的SVM更加糟糕,其测试集得分已经为负数。类似的,"Sigmoid核"的SVM也是灾难。

关于负数(官方解释):The best possible score is 1.0 and it can be negative (because the model can be arbitrarily worse)

那么原因呢?

  1. SVM模型本身的性能有限
  2. 样本各个特征之间存在较大的量级差异
  3. 超参数设置不合理
  4. ...

3.3 模型优化

3.3.1 利用正则化优化各个特征之间的量级差

什么是量级差?简而言之,例如:有的观察人的基本信息。其身高的取值范围在1.5米~2.0米,而收入可能是1000元~100000元。当两个特征都同时上升10%,身高只提高了0.05,而收入提高了9900,可以看到身高和收入之间数量级具有非常大的鸿沟。

下面我们先绘制一下各个特征的量级进行比较

In [33]:
# 设置特征最小值的位置和标签
plt.plot(X.min(axis=0),'v',label='min')
# 设置特征最大值的位置和标签
plt.plot(X.max(axis=0),'^',label='max')
# 设置y轴采用对数形式显示
plt.yscale('log')
# 设置图例自动放置到最佳位置
plt.legend(loc='best')
# 设置坐标轴横轴、纵轴的标题
plt.xlabel('features')
plt.ylabel('feature magnitude')
# 显示图形
plt.show()

【结果分析】

每个特征都具有各异性,可以看到有的特征跨度较大,有的特征跨度较小,有的特分布在高量区域,有的分布在低量区域。例如:特征一跨度很大($10^(-2)~10^2$),特征4分布在$10^0$附近,特征10分布在$10^1$附近,而特征11分布在$10^{-1}~10^2$附近。

因此,我们需要对特征进行一定的正则化(规范化、归一化)将其规范到一定的范围,以实现均等(同量级)的对比。

In [34]:
# 导入数据预处理工具
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.figure(dpi=120)
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),'o',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()

输出基于数据预处理后的得分

In [35]:
for kernel in ['linear','rbf', 'sigmoid']:
    svr = svm.SVR(kernel=kernel)
    svr.fit(X_train_scaled, y_train)
    print('数据预处理后',kernel,'核函数的模型训练集得分: {:.3f}, 测试集得分:{:.3f}'.format(
        svr.score(X_train_scaled, y_train), svr.score(X_test_scaled, y_test)))
数据预处理后 linear 核函数的模型训练集得分: 0.705, 测试集得分:0.680
数据预处理后 rbf 核函数的模型训练集得分: 0.679, 测试集得分:0.553
数据预处理后 sigmoid 核函数的模型训练集得分: 0.616, 测试集得分:0.507

【结果分析】

  1. 从得分来看,无论是RBF核还是Sigmoid核,经过数据预处理后,无论是训练集还是测试集性能都有较大的提升
  2. 线性核的SVM并没有太大的改善,说明归一化并不是对多有方法都有效果
  3. 经过数据预处理后,RBF核和Sigmoid核都有显著提升,然而依然不如Linear核。这预示着,数据归一化后,模型仍然有提升的空间
3.3.2 超参数调整

下面我们针对RBF核心的SVM进行超参数修改,以争取进一步提高性能。

In [36]:
kernel='rbf'
svr = svm.SVR(kernel=kernel, C=100, gamma=0.1)
svr.fit(X_train_scaled, y_train)
print('数据预处理后',kernel,'核函数的模型训练集得分: {:.3f}, 测试集得分:{:.3f}'.format(
        svr.score(X_train_scaled, y_train), svr.score(X_test_scaled, y_test)))
数据预处理后 rbf 核函数的模型训练集得分: 0.957, 测试集得分:0.868

非常惊诧!是不是?这说明调节超参数对于性能的提升也有较明显的作用。


【本章小节】

  1. SVM是目前使用比较广泛的机器学习算法,它不但可以实现二分类,也可以是实现多分类
  2. SVM可以同时实现分类和回归分析
  3. SVM最大的trick是核函数,常见的核函数包括线性核、高斯核(RBF)、Sigmoid核、多项式核等
  4. SVM的性能提升主要来源于数据归一化和超参数调节。这也说明SVM对于数据和参数都较为敏感,这也使得要使SVM获得较好的性能需要丰富的调参经验。