SVM(support vector machine)算法属于机器学习算法里面(不包括深度学习算法)理论比较复杂同时十分有效的分类算法,因此在学习过程中也花了不少心思,尤其是一些证明过程。网上也已经有许多整理好的博文,动笔过程中在学习这些前辈的博文的基础上加上自己对这些算法的理解以及一些实际的利用。另外,因为是站在自己理解的基础上写这篇文章,所以可能很多部分的具体过程不是特别详细,需要更加详细的可以去文末链接,欢迎大家不吝赐教。
话不多说,开撸。
不同于logical分类器,SVM关心的是距离平面最近的样本距离平面的距离,总是希望这个距离尽可能的大。这样做的原因是因为在进行logical分类的时候,较远的样本点的存在使得分类平面距离某些样本点特别近。从概率上来讲,在进行样本分类预测的时候,这些预测结果并不可靠(概率分布略大于0.5)。另外,SVM采用这样的方式(关心局部样本点)可以在较少的样本点的情况下就可以拟合出分类器,但并不意味着不需要大数据集,自然训练数据集合越大训练效果越好。
因为网上关于两种不同间隔的定义已经和详细,因此我只做简单定义(详细过程可以看文末链接),主要进行两者不同点说明和适用情况。
函数间隔 几何间隔
函数间隔的值小于任意样本到分类平面的函数距离;几何间隔将进行归一化处理,这样当缩放w和b的时候的值不会改变。通常情况下提到的间隔指的都是几何间隔。
于是最大间隔分类器(maximum margin classifier)的目标函数可以定义为:
回顾下几何间隔的定义,令函数间隔等于1,则有 = 1 / ||w||且,从而上述目标函数转化成了
相当于在相应的约束条件下,最大化这个1/||w||值,而1/||w||便是几何间隔。
如下图所示,中间的实线便是寻找到的最优超平面(Optimal Hyper Plane),其到两条虚线边界的距离相等,这个距离便是几何间隔,两条虚线间隔边界之间的距离等于2,而虚线间隔边界上的点则是支持向量。由于这些支持向量刚好在虚线间隔边界上,所以它们满足,而对于所有不是支持向量的点,则显然有。
目标函数:
由于这个问题的特殊结构,还可以通过拉格朗日对偶性(Lagrange Duality)变换到对偶变量 (dual variable) 的优化问题,即通过求解与原问题等价的对偶问题(dual problem)得到原始问题的最优解,这就是线性可分条件下支持向量机的对偶算法,这样做的优点在于:一者对偶问题往往更容易求解;二者可以自然的引入核函数,进而推广到非线性分类问题。
那什么是拉格朗日对偶性呢?简单来讲,通过给每一个约束条件加上一个拉格朗日乘子(Lagrange multiplier),定义拉格朗日函数(通过拉格朗日函数将约束条件融合到目标函数里去,从而只用一个函数表达式便能清楚的表达出我们的问题):
容易验证,当某个约束条件不满足时,例如,那么显然有(只要令即可)。而当所有约束条件都满足时,则最优值为,亦即最初要最小化的量。
因此,在要求约束条件得到满足的情况下最小化,实际上等价于直接最小化(当然,这里也有约束条件,就是) ,因为如果约束条件没有得到满足,会等于无穷大,自然不会是我们所要求的最小值。
具体写出来,目标函数变成了:
这里用表示这个问题的最优值,且和最初的问题是等价的。如果直接求解,那么一上来便得面对w和b两个参数,而又是不等式约束,这个求解过程不好做。不妨把最小和最大的位置交换一下,变成:
交换以后的新问题是原始问题的对偶问题,这个新问题的最优值用来表示。而且有≤,在满足某些条件的情况下,这两者相等,这个时候就可以通过求解对偶问题来间接地求解原始问题。
换言之,之所以从minmax的原始问题,转化为maxmin的对偶问题,一者因为是的近似解,二者,转化为对偶问题后,更容易求解。
下面可以先求L 对w、b的极小,再求L 对的极大。
具体计算过程如图:这样,求出了a,即可求出w,然后通过,即可求出b,最终得出分离超平面和分类决策函数。
KKT条件是用来求取最优化时候的理论,用来判断和确定最优化点的位置。
不等式约束条件:
设目标函数f(x),不等式约束为g(x),有的教程还会添加上等式约束条件h(x)。此时的约束优化问题描述如下:
则我们定义不等式约束下的拉格朗日函数L,则L表达式为:
其中f(x)是原目标函数,hj(x)是第j个等式约束条件,λj是对应的约束系数,gk(X)是不等式约束,uk是对应的约束系数。
此时若要求解上述优化问题,必须满足下述条件(也是我们的求解条件):
这些求解条件就是KKT条件。(1)是对拉格朗日函数取极值时候带来的一个必要条件,(2)是拉格朗日系数约束(同等式情况),(3)是不等式约束情况,(4)是互补松弛条件,(5)、(6)是原约束条件。
Note:
从KKT条件中我们其实可以看到,对于非支持向量上的数据点,他们的系数必定为0.一方面说明在整个超平面的求解过程非支持向量上的点没有起到对平面参数的约束作用;同时,说明我们在进行新的样本点的分类预测的时候,只需要和支持向量上的点进行內积即可。
另外,我们又注意到:
到这里我们先停一下,回想一下我们进行SVM的样本预测的时候是不是其实就是预测样本和支持向量进行內积运算。同样,如果将空间映射到高维空间之后,进行內积运算的时候是不是增大了内存量。这时候这个公式派上用场了,可以发现先进性內积运算然后带入特定的函数可以发现,內积结果不改变。
数据有噪音。对于这种偏离正常位置很远的数据点,我们称之为 outlier ,在我们原来的 SVM 模型里,outlier 的存在有可能造成很大的影响,因为超平面本身就是只有少数几个 support vector 组成的,如果这些 support vector 里又存在 outlier 的话,其影响就很大了。例如下图:
用黑圈圈起来的那个蓝点是一个 outlier ,它偏离了自己原本所应该在的那个半空间,如果直接忽略掉它的话,原来的分隔超平面还是挺好的,但是由于这个 outlier 的出现,导致分隔超平面不得不被挤歪了,变成途中黑色虚线所示(这只是一个示意图,并没有严格计算精确坐标),同时 margin 也相应变小了。当然,更严重的情况是,如果这个 outlier 再往右上移动一些距离的话,我们将无法构造出能将数据分开的超平面来。
为了处理这种情况,SVM 允许数据点在一定程度上偏离一下超平面。也就是说再进行超平面的最优化参数求取的时候,这些噪声点也是支持向量,只是他们的拉格朗日参数是通过我们手动设定的。
我们,原来的约束条件为:
现在考虑到outlier问题,约束条件变成了:
其中称为松弛变量 (slack variable) ,对应数据点允许偏离的 functional margin 的量。当然,如果我们运行任意大的话,那任意的超平面都是符合条件的了。所以,我们在原来的目标函数后面加上一项,使得这些的总和也要最小:
其中 是一个参数,用于控制目标函数中两项(“寻找 margin 最大的超平面”和“保证数据点偏差量最小”)之间的权重。注意,其中 是需要优化的变量(之一),而 是一个事先确定好的常量。完整地写出来是这个样子:
分析方法和前面一样,转换为另一个问题之后,我们先让针对、和最小化:
将 带回 并化简,得到和原来一样的目标函数:
不过,由于我们得到而又有(作为 Lagrange multiplier 的条件),因此有,所以整个 dual 问题现在写作:
人脸识别:https://github.com/coneypo/ML_handwritten_number
# -*- coding: utf-8 -*-
"""
Created on Fri Apr 27 15:54:58 2018
@author: 王乾
"""
import matplotlib.pyplot as plt
import numpy as np
from sklearn.svm import SVC # "Support vector classifier"
from sklearn.datasets import make_classification,make_blobs
model = SVC(kernel='linear')
X = np.array([[0,0], [1,1]])
y = np.array([0,1])
model.fit(X,y)
'''
from sklearn.svm import LinearSVC
from sklearn.datasets import make_classification
X, y = make_classification(n_features=4, random_state=0)
clf = LinearSVC(random_state=0)
clf.fit(X, y)
#print(clf.coef_)
'''
def plot_svc_decision_function(model, ax=None, plot_support=True):
if ax is None:
ax = plt.gca()#Get Current Axes获取当前轴线
xlim = ax.get_xlim()
ylim = ax.get_ylim()
# create grid to evaluate model
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)
# plot decision boundary and margins
ax.contour(X, Y, P, colors='k',
levels=[-1, 0, 1], alpha=0.5,
linestyles=['--', '-', '--'])
# plot support vectors
if plot_support:
ax.scatter(model.support_vectors_[:, 0],
model.support_vectors_[:, 1],
s=300, linewidth=1, facecolors='none');
ax.set_xlim(xlim)
ax.set_ylim(ylim)
plt.scatter(X[:, 0], X[:, 1], c=y, s=50, cmap='autumn')
plot_svc_decision_function(model);
model.support_vectors_
#加入径向基函数, 核设为 rbf
clf = SVC(kernel='rbf', C=1E6)
clf.fit(X, y)
X, y = make_blobs(n_samples=100, centers=2,
random_state=0, cluster_std=0.8)
fig, ax = plt.subplots(1, 2, figsize=(16, 6))
fig.subplots_adjust(left=0.0625, right=0.95, wspace=0.1)
for axi, C in zip(ax, [10.0, 0.1]):
model = SVC(kernel='linear', C=C).fit(X, y)
axi.scatter(X[:, 0], X[:, 1], c=y, s=50, cmap='autumn')
plot_svc_decision_function(model, axi)
axi.scatter(model.support_vectors_[:, 0],
model.support_vectors_[:, 1],
s=300, lw=1, facecolors='none');
axi.set_title('C = {0:.1f}'.format(C), size=14)
X, y = make_blobs(n_samples=100, centers=2,
random_state=0, cluster_std=1.1)
fig, ax = plt.subplots(1, 2, figsize=(16, 6))
fig.subplots_adjust(left=0.0625, right=0.95, wspace=0.1)
# wspace调整图像边框,使得各个图之间的间距为0
for axi, gamma in zip(ax, [10.0, 0.1]):
model = SVC(kernel='rbf', gamma=gamma).fit(X, y)
axi.scatter(X[:, 0], X[:, 1], c=y, s=50, cmap='autumn')
plot_svc_decision_function(model, axi)
axi.scatter(model.support_vectors_[:, 0],
model.support_vectors_[:, 1],
s=300, lw=1, facecolors='none');
axi.set_title('gamma = {0:.1f}'.format(gamma), size=14)