SVM解读(2)

非线性SVM与核函数

SVC在非线性数据上的推广

为了能够找出非线性数据的线性决策边界,我们需要将数据从原始的空间x投射到新空间Φ(x)中,Φ是一个映射函数,它代表了某种非线性的变换,如同我们之前所做过的使用r来升维一样,这种非线性变换看起来是一种非常有效的方式。使用这种变换,线性SVM的原理可以被很容易推广到非线性情况下,其推到过程核逻辑都与线性SVM一摸一样,只不过在第一决策边界之前,我们必须先对数据进行升维,即将原始的x转换成Φ。

重要参数kernel

这种变换非常巧妙,但也带有一些实现问题。首先,我们可能不清楚应该什么样的数据应该使用什么类型的映射函数来确保可以在变换空间中找出线性决策边界。极端情况下,数据可能会被映射到无限维度的空间中,这种高维空间可能不是那么友好,维度越多,推导和计算的难度都会随之暴增。其次,已知适当的映射函数,我们想要计算类似于Φ(x)i* Φ(xtest)这样的点积,计算量无法巨大。要找出超平面所付出的代价是非常昂贵的。

关键概念:核函数

而解决上面这些问题的数学方式,叫做”核技巧”。是一种能够使用数据原始空间中的向量计算来表示升维后的空间中的点积结果的数学方式。具体体现为,K(u,v)= Φ(u)*Φ(v)。而这个原始空间中的点积函数K(u,v),就被叫做“核函数”。

核函数能够帮助我们解决三个问题:

第一,有了核函数之后,我们无需去担心$\Phi$究竟应该是什么样,因为非线性SVM中的核函数都是正定核函数(positive definite kernel functions),他们都满足美世定律(Mercer’s theorem),确保了高维空间中任意两个向量的点积一定可以被低维空间中的这两个向量的某种计算来表示。

第二,使用核函数计算低维度中的向量关系比计算原来的Φ(xi) * Φ(xtest)要简单太多了。

第三,因为计算是在原始空间中进行,所以避免了维度诅咒的问题。

选用不同的核函数,就可以解决不同数据分布下的寻找超平面问题。在SVC中,这个功能由参数“kernel”和一系列与核函数相关的参数来进行控制。之前的代码中我们一直使用这个参数并输入”linear”,但却没有给大家详细讲解,也是因为如果不先理解核函数本身,很难说明这个参数到底在做什么。参数“kernel”在sklearn中可选以下几种选项:

可以看出,除了选项”linear”之外,其他核函数都可以处理非线性问题。多项式核函数有次数d,当d为1的时候它就是再处理线性问题,当d为更高次项的时候它就是在处理非线性问题。我们之前画图时使用的是选项“linear”,自然不能处理环形数据这样非线性的状况。而刚才我们使用的计算r的方法,其实是高斯径向基核函数所对应的功能,在参数”kernel“中输入”rbf“就可以使用这种核函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
from sklearn.svm import SVC
import matplotlib.pyplot as plt
import numpy as np
from sklearn.datasets import make_circles
X,y = make_circles(100, factor=0.1, noise=.1)
plt.scatter(X[:,0],X[:,1],c=y,s=50,cmap="rainbow")
plt.show()
def plot_svc_decision_function(model,ax=None):
if ax is None:
ax = plt.gca()
xlim = ax.get_xlim()
ylim = ax.get_ylim()
x = np.linspace(xlim[0],xlim[1],30)
y = np.linspace(ylim[0],ylim[1],30)
Y,X = np.meshgrid(y,x)
xy = np.vstack([X.ravel(), Y.ravel()]).T
P = model.decision_function(xy).reshape(X.shape)
ax.contour(X, Y, P,colors="k",levels=[-1,0,1],alpha=0.5,linestyles=["--","-","--"])
ax.set_xlim(xlim)
ax.set_ylim(ylim)
plt.show()
clf = SVC(kernel = "linear").fit(X,y)
plt.scatter(X[:,0],X[:,1],c=y,s=50,cmap="rainbow")
r = np.exp(-(X**2).sum(1))
rlim = np.linspace(min(r),max(r),0.2)
from mpl_toolkits import mplot3d
def plot_3D(elev=30,azim=30,X=X,y=y):
ax = plt.subplot(projection="3d")
ax.scatter3D(X[:,0],X[:,1],r,c=y,s=50,cmap='rainbow')
ax.view_init(elev=elev,azim=azim)
ax.set_xlabel("x")
ax.set_ylabel("y")
ax.set_zlabel("r")
plt.show()
plot_3D()
clf = SVC(kernel = "rbf").fit(X,y)
plt.scatter(X[:,0],X[:,1],c=y,s=50,cmap="rainbow")
plot_svc_decision_function(clf)

运行后,从效果图可以看到,决策边界被完美的找了出来。

探索核函数在不同数据集上的表现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.colors import ListedColormap
from sklearn import svm
from sklearn.datasets import make_circles, make_moons, make_blobs,make_classification
n_samples = 100
datasets = [
make_moons(n_samples=n_samples, noise=0.2, random_state=0),
make_circles(n_samples=n_samples, noise=0.2, factor=0.5, random_state=1),
make_blobs(n_samples=n_samples, centers=2, random_state=5),
make_classification(n_samples=n_samples,n_features =
2,n_informative=2,n_redundant=0, random_state=5)
]
Kernel = ["linear","poly","rbf","sigmoid"]
#四个数据集分别是什么样子呢?
for X,Y in datasets:
plt.figure(figsize=(5,4))
plt.scatter(X[:,0],X[:,1],c=Y,s=50,cmap="rainbow")
nrows=len(datasets)
ncols=len(Kernel) + 1
fig, axes = plt.subplots(nrows, ncols,figsize=(20,16))
for ds_cnt, (X,Y) in enumerate(datasets):
#在图像中的第一列,放置原数据的分布
ax = axes[ds_cnt, 0]
if ds_cnt == 0:
ax.set_title("Input data")
ax.scatter(X[:, 0], X[:, 1], c=Y, zorder=10, cmap=plt.cm.Paired,edgecolors='k')
ax.set_xticks(())
ax.set_yticks(())
#第二层循环:在不同的核函数中循环
#从图像的第二列开始,一个个填充分类结果
for est_idx, kernel in enumerate(Kernel):
#定义子图位置
ax = axes[ds_cnt, est_idx + 1]
#建模
clf = svm.SVC(kernel=kernel, gamma=2).fit(X, Y)
score = clf.score(X, Y)
#绘制图像本身分布的散点图
ax.scatter(X[:, 0], X[:, 1], c=Y
,zorder=10
,cmap=plt.cm.Paired,edgecolors='k')
#绘制支持向量
ax.scatter(clf.support_vectors_[:, 0], clf.support_vectors_[:, 1], s=50,
facecolors='none', zorder=10, edgecolors='k')
#绘制决策边界
x_min, x_max = X[:, 0].min() - .5, X[:, 0].max() + .5
y_min, y_max = X[:, 1].min() - .5, X[:, 1].max() + .5
#np.mgrid,合并了我们之前使用的np.linspace和np.meshgrid的用法
#一次性使用最大值和最小值来生成网格
#表示为[起始值:结束值:步长]
#如果步长是复数,则其整数部分就是起始值和结束值之间创建的点的数量,并且结束值被包含在内
XX, YY = np.mgrid[x_min:x_max:200j, y_min:y_max:200j]
#np.c_,类似于np.vstack的功能
Z = clf.decision_function(np.c_[XX.ravel(), YY.ravel()]).reshape(XX.shape)
#填充等高线不同区域的颜色
ax.pcolormesh(XX, YY, Z > 0, cmap=plt.cm.Paired)
#绘制等高线
ax.contour(XX, YY, Z, colors=['k', 'k', 'k'], linestyles=['--', '-', '--'],
levels=[-1, 0, 1])
#设定坐标轴为不显示
ax.set_xticks(())
ax.set_yticks(())
#将标题放在第一行的顶上
if ds_cnt == 0:
ax.set_title(kernel)
#为每张图添加分类的分数
ax.text(0.95, 0.06, ('%.2f' % score).lstrip('0')
, size=15
, bbox=dict(boxstyle='round', alpha=0.8, facecolor='white')
#为分数添加一个白色的格子作为底色
, transform=ax.transAxes #确定文字所对应的坐标轴,就是ax子图的坐标轴本身
, horizontalalignment='right' #位于坐标轴的什么方向
)
plt.tight_layout()
plt.show()

可以观察到,线性核函数和多项式核函数在非线性数据上表现会浮动,如果数据相对线性可分,则表现不错,如果是像环形数据那样彻底不可分的,则表现糟糕。在线性数据集上,线性核函数和多项式核函数即便有扰动项也可以表现不错,可见多项式核函数是虽然也可以处理非线性情况,但更偏向于线性的功能。Sigmoid核函数就比较尴尬了,它在非线性数据上强于两个线性核函数,但效果明显不如rbf,它在线性数据上完全比不上线性的核函数们,对扰动项的抵抗也比较弱,所以它功能比较弱小,很少被用到。rbf,高斯径向基核函数基本在任何数据集上都表现不错,属于比较万能的核函数。我个人的经验是,无论如何先试试看高斯径向基核函数,它适用于核转换到很高的空间的情况,在各种情况下往往效果都很不错,如果rbf效果不好,那我们再试试看其他的核函数。另外,多项式核函数多被用于图像处理之中。

选取与核函数相关的参数

在知道如何选取核函数后,我们还要观察一下除了kernel之外的核函数相关的参数。对于线性核函数,”kernel”是唯一能够影响它的参数,但是对于其他三种非线性核函数,他们还受到参数gamma,degree以及coef0的影响。参数gamma就是表达式中的γ,degree就是多项式核函数的次数d,参数coef0就是常数项γ。其中,高斯径向基核函数受到gamma的影响,而多项式核函数受到全部三个参数的影响。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#使用交叉验证得出最好的参数和准确率
from sklearn.model_selection import StratifiedShuffleSplit
from sklearn.model_selection import GridSearchCV
from sklearn.datasets import load_breast_cancer
from sklearn import svm
import numpy as np
data = load_breast_cancer()
X = data.data
y = data.target
gamma_range = np.logspace(-10,1,20)
coef0_range = np.linspace(0,5,10)
param_grid = dict(gamma = gamma_range
,coef0 = coef0_range)
cv = StratifiedShuffleSplit(n_splits=5, test_size=0.3, random_state=420)
grid = GridSearchCV(svm.SVC(kernel = "poly",degree=1,cache_size=5000),
param_grid=param_grid, cv=cv)
grid.fit(X, y)
print("The best parameters are %s with a score of %0.5f" % (grid.best_params_,
grid.best_score_))

重要参数C

关键概念:硬件隔与软件隔

当两组数据是完全线性可分,我们可以找出一个决策边界使得训练集上的分类误差为0,这两种数据就被称为是存在“硬间隔“的。当两组数据几乎是完全线性可分的,但决策边界在训练集上存在较小的训练误差,这两种数据就被称为是存在”软间隔“。

看上图,原来的决策边界ωx+b=0,原本的平行于决策边界的两个虚线超平面ω\x+b=1和ω*x+b=-1都依然有效。我们的原始判别函数是:

不过,这些超平面现在无法让数据上的训练误差等于0了,因为此时存在了一个混杂在红色点中的紫色点。于是,我们需要放松我们原始判别函数中的不等条件,来让决策边界能够适用于我们的异常点。于是我们引入松弛系数Φ来帮助我们优化原始的判别函数:

其中ζi>0。可以看的出,这其实是将原来的虚线超平面向图像的上方和下方平移。松弛系数其实蛮好理解的,看上图,在红色点附近的蓝色点在原本的判别函数中必定会被分为红色,所有一定会判断错。我们现在做一条与决策边界平行,且过蓝色点的直线ωxi+b=1- ζi(图中的蓝色虚线)。这条直线是由ω\xi+b=1平移得到,所以两条直线在纵坐标上的差异就是ζi。而点ωxi+b=1的距离就可以表示为 ζi*ω/||ω||,即ζi在ω方向上的投影。由于单位向量是固定的,所以ζi可以作为蓝色点在原始的决策边界上的分类错误的程度的表示。隔得越远,分的越错。注意:ζi并不是点到决策超平面的距离本身。

不难注意到,我们让ω*xi+b>=1-ζi作为我们的新决策超平面,是有一定的问题的。虽然我们把异常的蓝色点分类正确了,但我们同时也分错了一系列红色的点。所以我们必须在我们求解最大边际的损失函数中加上一个惩罚项,用来惩罚我们具有巨大松弛系数的决策超平面。我们的拉格朗日函数,拉格朗日对偶函数,也因此都被松弛系数改变。现在,我们的损失函数为

C是用来控制惩罚力度的系数

我们的拉格朗日函数为(其中$\mu$是第二个拉格朗日乘数)

需要满足的KKT条件为

拉格朗日对偶函数为

sklearn类SVC背后使用的最终公式。公式中现在唯一的新变量,松弛系数的惩罚力度C,由我们的参数C来进行控制。

在实际使用中,C和核函数的相关参数(gamma,degree等等)们搭配,往往是SVM调参的重点。与gamma不同,C没有在对偶函数中出现,并且是明确了调参目标的,所以我们可以明确我们究竟是否需要训练集上的高精确度来调整C的方向。默认情况下C为1,通常来说这都是一个合理的参数。 如果我们的数据很嘈杂,那我们往往减小C。当然,我们也可以使用网格搜索或者学习曲线来调整C的值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
from sklearn.model_selection import StratifiedShuffleSplit
from sklearn.model_selection import GridSearchCV
from sklearn.datasets import load_breast_cancer
import matplotlib.pyplot as plt
from sklearn.model_selection import train_test_split
from sklearn import svm
import numpy as np
data = load_breast_cancer()
X = data.data
y = data.target
C1_range = np.linspace(0.01,30,50)
C2_range=np.linspace(0.01,30,50)
C3_range=np.linspace(5,7,50)
param_grid1 = dict(C=C1_range)
cv = StratifiedShuffleSplit(n_splits=5, test_size=0.3, random_state=420)
grid1 = GridSearchCV(svm.SVC(kernel = "linear",gamma=
0.012742749857031322,cache_size=5000),
param_grid=param_grid1, cv=cv)
grid1.fit(X, y)
print("linear The best parameters are %s with a score of %0.5f" % (grid1.best_params_,
grid1.best_score_))
param_grid2=dict(C=C2_range)
grid2 = GridSearchCV(svm.SVC(kernel = "rbf",gamma=
0.012742749857031322,cache_size=5000),
param_grid=param_grid2, cv=cv)
grid2.fit(X, y)
print("rbf(0.01,30,50) The best parameters are %s with a score of %0.5f" % (grid2.best_params_,
grid2.best_score_))
param_grid3=dict(C=C3_range)
grid3=GridSearchCV(svm.SVC(kernel="rbf",gamma=0.012742749857031322,cache_size=5000),param_grid=param_grid3)
grid3.fit(X,y)
print("rbf(5,7,50) ",grid3.best_params_)
print("rbf(5,7,50) ",grid3.best_score_)

SVC的参数、属性和接口

参数

属性

接口

----本文结束,感谢您的阅读。如有错,请指正。----
大哥大嫂过年好!支持我一下呗
0%