支持向量机主要有两部分组成
Hinge Loss(铰链损失)和 Kernel Method(核方法)。
损失函数
输入的数据
数据的标签是两类,即 +1 和 -1。在这里取模型的函数为
所以分类用的损失函数为:
其中定义当计算出的函数值与标签值不相等的时候取1,相等的时候函数值取0。但是这样的得到的函数有一点不好,它无法进行微分,所以我们采用了另外一种函数作为损失函数,即
对各种损失函数的分析
讨论各种损失函数的特性:
再上图中,横坐标是
y
^
n
f
(
x
n
)
\hat{y}^nf(x^n)
y^nf(xn),如果预测的符号与原始标签的符号是相同的,那么它们的损失值为0,如果符号相反的损失值为1。其中黑色的线是理想损失函数的曲线,但是可以明显看到这是一个不可微分的函数,所以我们使用了近似的损失函数进行替代,这个近似的损失函数可以有多种选择,下面将对每一种可能的选择进行讨论。
其中红色的曲线是二次函数曲线,损失函数的表达式为
l ( f ( x n ) , y ^ n ) = ( y ^ n f ( x n ) − 1 ) 2 l(f(x^n),\hat{y}^n)=(\hat{y}^nf(x^n)-1)^2 l(f(xn),y^n)=(y^nf(xn)−1)2
当 y ^ n = 1 \hat{y}^n=1 y^n=1 代入即 ( f ( x n ) − 1 ) 2 (f(x^n)-1)^2 (f(xn)−1)2
当 y ^ n = − 1 \hat{y}^n=-1 y^n=−1 代入即 ( f ( x n ) + 1 ) 2 (f(x^n)+1)^2 (f(xn)+1)2
我们可以看到当数据的标签值是 +1 的时候,预测值为 +1 可以达到最小的损失;当标签值为 -1 的时候,预测值为 -1 可以达到最小的损失。所以当 y ^ n f ( x n ) = 1 \hat{y}^nf(x^n)=1 y^nf(xn)=1 的时候取到最小的损失函数值,但是在后面这个函数就是不合理的,因为随着预测值逐渐变大,损失函数的取值居然越来越大,这明显是不合理的。
接下来我们考虑使用 sigmoid + square loos 作为损失函数,其函数曲线如蓝色的那一条,具体的损失函数如下
l ( f ( x n ) , y ^ n ) = ( σ ( y ^ n f ( x n ) ) − 1 ) 2 l(f(x^n),\hat{y}^n)=(\sigma(\hat{y}^nf(x^n))-1)^2 l(f(xn),y^n)=(σ(y^nf(xn))−1)2
当 y ^ n = 1 \hat{y}^n=1 y^n=1 代入即 ( σ ( f ( x n ) ) − 1 ) 2 (\sigma(f(x^n))-1)^2 (σ(f(xn))−1)2
当 y ^ n = − 1 \hat{y}^n=-1 y^n=−1 代入即 ( σ ( f ( x n ) ) ) 2 (\sigma(f(x^n)))^2 (σ(f(xn)))2
画出损失函数曲线如上图所示
如果我们使用 sigmoid + cross entropy 作为损失函数,那么具体的损失函数及曲线如下图所示
从表达式可以看出,如果 y ^ n f ( x n ) ) → ∞ \hat{y}^nf(x^n))→∞ y^nf(xn))→∞,那么整体的函数值将趋近于 l n 1 = 0 ln1=0 ln1=0,如果 y ^ n f ( x n ) ) → ∞ \hat{y}^nf(x^n))→∞ y^nf(xn))→∞,那么整体的函数值将趋近于 ∞ ∞ ∞,所以函数曲线如上图。这个函数曲线是合理的,因为随着 y ^ n f ( x n ) ) → ∞ \hat{y}^nf(x^n))→∞ y^nf(xn))→∞ 的增加函数值会逐渐下降。对比 sigmoid + square loos 作为损失函数的方法,我们可以看到,当自变量(横坐标对应的值)取到负无穷的时候, sigmoid + cross entropy 会有很大的梯度值,而 sigmoid + square loos 的梯度值几乎为0,也就是说,前者在梯度下降的过程中,主要努力就会得到回报,而后者没有回报,所以也很有可能不想努力。另一点,在这里我们将交叉熵的函数值除以了 l n 2 ln2 ln2,主要目的是希望可以得到理想损失函数的一个上界。
最后我们来考虑铰链损失函数,具体的表达式及函数曲线如下图所示
如上的损失函数,我们可以看到,对于一个正例,如果 f ( x ) > 1 f(x)>1 f(x)>1,那么便得到了一个完美的分类结果,如果是反例的话,如果 f ( x ) < − 1 f(x)<−1 f(x)<−1,那么便得到了一个完美的分类结果。所以xx 不用太大,因为大了函数值也是相同的。观察函数曲线可以知道,当 y ^ n f ( x n ) > 1 \hat{y}^nf(x^n)>1 y^nf(xn)>1 的时候,就已经够好了,但是它们同向却在 penalty 认为实际上还不够好,虽然可以正确分类了,但是损失函数仍然或让自变量向右移动变得更好,这个区间也就是所谓的边界(margin)。其中损失函数中取 1 的原始,它可以得到理想损失函数的一个紧致的上界。
如果我们对比交叉熵函数和铰链损失函数的话,它们最大的区别在于对待已经正确分类的例子的态度。如果将图中的黑点从1 的位置移动到 2 的位置,我们可以看到交叉熵损失函数的函数值会继续下降,也就是说它在已经得到好的结果之后还希望得到更好的结果;但是折页函数是一种及格就好的函数,当大于margin的时候就好了。在实际的使用中,铰链函数略优于交叉熵损失函数,就是说没有领先的很多。但是铰链损失函数更能够顾全全局,当进行多分类的时候得到的效果会更好。
线性 SVM 分类器
线性的SVM的步骤主要如下图所示分为三步:
第一部分是目标函数,这里使用如上图所示的目标函数,它可以表示为两个向量之间的点积运算,进而可以表示为权重的转置与输入 x 的相乘。在第二步,构建损失函数,这里使用的是铰链损失函数和 L2 的正则项,因为前面的损失函数明显是一个凸函数,后面的正则化项也明显是一个正则化项,所以这些的组合也是一个凸函数;这样在第三步就可以使用梯度下降的方法更新参数。有的人可能会想,这个函数是分段线性的可以使用梯度下降嘛,可以的。想想之前的relu,也是这样的啊,同样可以使用梯度下降的方法进行训练。
在这里我们可以看到,实际上逻辑回归和线性的 SVM 之间的区别就在于 损失函数的不同。另一方面,通过第一步我们可以看出,实际上SVM与神经网络之间是相通的。
1、使用梯度下降训练 SVM
2、使用二次规划训练 SVM
首先我们把损失函数中的铰链损失函数取出,并判断下面两个等式是否相同。
实际上仅仅考虑这两个等式,他们之间是不相等的,但是同时考虑最小化如下的损失函数的话,两者就是相同的了
这个时候我们用
ϵ
\epsilon
ϵ 代替原来的折页损失函数,这里
ϵ
\epsilon
ϵ 满足之前红色框框内的两个不等式,所以在这里我们认为他是松弛变量,而这样的问题可以使用二次规划的方法进行求解。
核方法
1、对偶表示
最后分类函数的权重实际上是输入数据的线性组合,如下图所示
造成这种结果的原始,实际上可以通过之前所讲的利用梯度下降的方法更新参数的角度进行考虑。如上图所示,我们将所有的权重更新的过程合并成一个向量,其中的折页损失函数的导数记为
c
n
(
w
)
c^n(w)
cn(w),他的具体表达式也如上图所示。如果我们将权重的初始值设为0 的话,那么参数 w 实际上就是输入数据的线性组合,另外因为对于折页损失函数来讲,它里面有很多零的值,所以系数
α
α
α 中有很多的 0,这样权重实际上是输入数据的稀疏组合,其中稀疏不为零所对应的数据点称为支持向量。这样的结果导致模型的鲁棒性更强,即使有离群值的点或者不合理的点,只要不选取它们作为支持向量,那么对于分类结果的影响并不大。
由于在上面的部分已经证明了,SVM的权重实际上是通过输入点的线性组合得到,因此它可以表示为下入的形式
这里是将原来的求和转变为了向量相称的形式,这个表达方式在机器学习中是常常使用的。在得到了的权重的对偶表达方式之后,第一步是进行变量替代,如下图所示
将
w
w
w 带入之后可以看到
f
(
x
)
f(x)
f(x) 主要这三部分组成,第一部分是系数
α
α
α ,他是一个行向量,一共有 N 列,后面的两项可以使用矩阵乘法的结合律,得到一个列向量,一共有 N 行。所以
f
(
x
)
f(x)
f(x) 可以表示为上图那种形式,将其中的
(
x
n
⋅
x
)
(x^n⋅x)
(xn⋅x) 可以表示为
K
(
x
n
,
x
)
K(x^n,x)
K(xn,x),这样的表示形式有利于使用后面的核方法。
如下图所示,经目标函数表达为系数与
K
(
x
n
,
x
)
K(x^n,x)
K(xn,x) 的线性组合之后,接下来的任务就是最小化损失函数。
demo
import numpy as np
import pylab as pl #提供Python的画图功能
from sklearn import svm
#创建四十个点
a = np.random.seed(0)#seed()方法,;里面的参数没变,使每次程序生成结果不变
#np.random.randn(20, 2):每个点都是二维的即x点与y点,打印出来的话有二十行,两列,每行组成一个坐标
#[2,2]加上是在下方,减去是在上方
#np.r_是按列连接两个矩阵,就是把两矩阵上下相加,要求列数相等,np.c_则相反
X = np.r_[np.random.randn(20, 2) - [2, 2], np.random.randn(20, 2) + [2, 2]]
Y = [0]*20 +[1]*20#对应的归类标记,前20个为0,后20个为1
#建立SVM模型
clf = svm.SVC(kernel="linear")
clf.fit(X,Y)
#
w = clf.coef_[0]
a = -w[0]/w[1]#斜率
xx = np.linspace(-5, 5)
yy = a*xx - (clf.intercept_[0])/w[1]
b = clf.support_vectors_[0]#取第一个支持向量
yy_down = a*xx + (b[1] - a*b[0])
b = clf.support_vectors_[-1]#取最后一个支持向量
yy_up = a*xx + (b[1] - a*b[0])
# of a line y=a.x +b: the generic w_0x + w_1y +w_3=0 can be rewritten y = -(w_0/w_1) x + (w_3/w_1)
print("w: ",w)
print("a: ",a)
print("support_vectors_: ", clf.support_vectors_)
print("clf.coef_: ", clf.coef_)
pl.plot(xx, yy, 'k-')
pl.plot(xx, yy_down, 'k--')
pl.plot(xx, yy_up, 'k--')
pl.scatter(clf.support_vectors_[:, 0], clf.support_vectors_[:, 1],
s=80, facecolors='none')#画出周围的点
pl.scatter(X[:, 0], X[:, 1], c=Y, cmap=pl.cm.Paired)
pl.axis('tight')
pl.show()
运行输出