作者:欧新宇(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$增加。
很久很久以前,有个大侠的爱人被魔鬼抓走了,魔鬼要这位大侠和它玩一个游戏才能放了大侠的爱人。
魔鬼在桌子上似乎很有规律地方了两种颜色的球,然后说到: "你需要用一根棍子将它们分开,并且要求在后续放更多球之后,仍然适用。"
于是乎,大侠这样放下了棍子,看起来还不错。
魔鬼又在桌上放下了更多的球,似乎也还不错,不过有一个球站错阵营了。
于是,大侠祭出了一件神级法宝 —— 支持向量机(Support Vector Machine,SVM)。利用SVM,大侠让棍子再一次完美地充当了一条分割线。
重点:SVM的特性一:试图建立一条完美的分界线,让该分界线处于最佳的位置,让分界线两边与样本间有尽可能大的间隙。
借助神级法宝SVM的第一个trick —— 最大类间间隙,大侠度过了第一关。于是,魔鬼给了大侠一个更困难的挑战。
这个问题让大侠范畴了,似乎一条棍子根本没办法将两种颜色的球进行分隔。此时,大侠想到了法宝SVM的另一个神技 —— 超平面
。于是乎,大侠用力一拍桌子,所有的球都飞到了空中。凭借大侠"快准狠"的身手,他迅速将一张纸插入到球的中间。
此时,从魔鬼的角度来看,这些球看起来就像是被一条曲线给分开了。
重点:SVM的特性二:由于样本特征的特性,当样本在原始的特征空间中线性不可分时,我们可以将其转换到高维空间,并利用高维空间中的超平面对样本进行分隔。
因为大侠的聪明才智,他成功解救了他的爱人。之后,神界的无聊众神们认真总结并研究了这个故事。它们把那些带颜色的球称为数据 (data)
,把棍子称为分类器 (classifier)
,把最大间隙trick称为优化 (optimization)
,拍桌子的绝招称为核化 (kernelling)
,而那张纸就是超平面 (hyperplane)
.
为了更清晰理解SVM,这里给出一个视频解释。
【Video】http://ouxinyu.cn/Teaching/MachineLearning/Attachments/Ch0709SVMintro.mp4
SVM(Support Vector Machines)支持向量机是在所有知名的数据挖掘及传统机器学习算法中最健壮,最准确的方法之一,它属于二分类算法,可以支持线性和非线性的分类。
当然,SVM也可以支持多分类。
首先,我们来了解一下线性分类器。假设在一个二维线性可分
的数据集中,如下图所示,我们需要找一个超平面把两组数据分开。图中的四条直线都可以实现分隔两种数据,然而哪一条直线能够达到更好的泛化能力
呢?换句话说,我们需要找到一个能够使两个类的空间最大的超平面。
所谓
超平面
是什么呢?
在二维空间中,超平面就是一条直线(例如上图中的分割线,或大侠放的棍子);而在三维空间中就是一个平面(例如大侠插入球中间的纸)。我们将这个划分数据的决策边界统称为超平面。距离这个超平面最近的点就叫做支持向量
,点到超平面的距离叫做间隔
。支持向量机就是要使超平面和支持向量之间的间隔尽可能的大,这样超平面才可以更好地将两类样本进行准确分隔。
而保证间隔尽可能大就是保证分类器误差尽可能小,模型尽可能健壮。
事实上,
【重点】支持向量机的核心任务是:最大化类间距,最小化类内距。
暂略...
以上讨论的都是线性可分的情况,但实际中的样本很多时候都是线性不可分的,例如下列图中所示的样本。
对于非线性分布的样本(线性不可分问题),Linear模型很难进行处理,而SVM具有一个优秀的trick —— 核函数K(*,*)
,它通过将数据映射到高维空间,来解决在原始空间线性不可分的问题。
基于核函数的SVM
的基本工作流程是:
通过以上的操作,SVM可以实现将原始特征空间不好分隔的非线性数据进行最优分隔。
事实上,上面给出的样本集都可以看作是不同半径的圆(或圆环),所以,一个理想的分界面应该是一个"圆圈",而不是一条线。这个圆圈就是我们说的(超平面)。下面,我们将其中两个样本映射到三维空间,可以得到如下的图例.
图一:
图二:
小结:
核函数
;核函数
的主要功能是将特征映射投射到高维空间以实现"线性"可分;核函数
的价值在于:它所有的计算都是基于原始空间,只是将实质的分类效果表现到高维空间。这种机制避免的维度爬升的不可预见性(维度灾难),以及高维空间计算的复杂性。在SVM算法中,训练模型的过程可以理解为每个样本对于超平面重要性的判断。换句话说,在训练数据中,只有一部分数据对于边界的确定是有帮助的,这些点刚好位于超平面附近。我们称这些点为"支持向量",这也是"支持向量机"名称的由来。
在SVM中,使用最普遍的核函数包括: 线性核函数(Linear kernel)、多项式核函数(Polynomial kernel)、高斯核函数(又称为径向基核函数核(Radial basis function kernel, RBF))、Sigmoid核函数.
下面我们给出一个基于最基本的线性核的支持向量机示例,基本过程仍然是我们习惯的步骤:
# 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中最基本的"最大边界间隔超平面", 它使得图中的实线
与所有支持向量
之间的距离, 加权和最大. 下面我们尝试使用RBF核函数替代Linear核函数.
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}$.
# 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()
# 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使用最小化L2范数
,线性内核SVC使用最小化L1范数
高斯核的RBF
和多项式核
性能略微好一点,通过更具有弹性的非线性算法,它们能更好地拟合数据Sigmoid核的SVM
在这个任务中可以说是惨不忍睹,但是Sigmoid确是神经网络的一种重要激活函数多项式核的SVM
具有较大影响高斯核(RBF核)的SVM
具有较大影响# 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()
【结果分析】
gamma
值的的增大,模型的非线性现象越来越明显。简单的说, 当gamma → 0时,模型越来越简单,对于训练样本错分的几率越来越大; 当gamma增大时,模型变得越来平滑、复杂,非线性现象也越来越明显,对于训练样本来说分错的几率也越来越小欠拟合
,gamma值越大模型越来越趋向于过拟合
.有兴趣的同学,请尝试调整
正则化参数C
,看看变化趋势.
# 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()
【结果分析】
degree
值的的增大,模型的非线性现象越来越明显优点:
使用核函数可以向高维空间进行映射
使用核函数可以解决非线性的分类
分类思想很简单,就是将样本与决策面的间隔最大化
分类效果较好
缺点:
SVM算法对大规模训练样本难以实施(比如1万以上的样本,特别是特征较多的样本)
用SVM解决多分类问题存在困难. (但可以通过adaboost、层次化等思想,联合多个SVM实现多分类)
# 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()
# 预览数据集中的键
# 数据集中包含5个键,分别是数据、标签、特征名称、数据集描述和文件名
print(boston.keys())
# 预览某个指定键的值
print(boston['filename'])
# 预览某个指定键的值
print(boston['DESCR'])
# TODO: 3. 数据预处理,包括训练集、测试集划分,数据正则化,数据清洗等
X, y = boston.data, boston.target
X_train, X_test, y_train, y_test = train_test_split(X, y)
观察一下训练集和测试集的情况
print("训练集的形态: 数据{}, 标签{}".format(X_train.shape, y_train.shape))
print("训练集的形态: 数据{}, 标签{}".format(X_test.shape, y_test.shape))
支持向量回归模型(Support Vector Regression, SVR)是使用SVM来拟合曲线,做回归分析。
分类和回归问题是有监督机器学习中最重要的两类任务。与分类的输出是有限个离散的值(例如 ${−1,1}$)不同的是,回归模型的输出在一定范围内是连续的。
# 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核"的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.5米~2.0米,而收入可能是1000元~100000元。当两个特征都同时上升10%,身高只提高了0.05,而收入提高了9900,可以看到身高和收入之间数量级具有非常大的鸿沟。
下面我们先绘制一下各个特征的量级进行比较
# 设置特征最小值的位置和标签
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$附近。
因此,我们需要对特征进行一定的正则化(规范化、归一化)将其规范到一定的范围,以实现均等(同量级)的对比。
# 导入数据预处理工具
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()
输出基于数据预处理
后的得分
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)))
【结果分析】
数据预处理
后,无论是训练集还是测试集性能都有较大的提升数据预处理
后,RBF核和Sigmoid核都有显著提升,然而依然不如Linear核。这预示着,数据归一化后,模型仍然有提升的空间下面我们针对RBF核心的SVM进行超参数修改,以争取进一步提高性能。
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)))
非常惊诧!是不是?这说明调节超参数对于性能的提升也有较明显的作用。
【本章小节】