本节将继续介绍监督学习中的一个强力的分类器——支持向量机(Support Vector Machine,SVM),其通过「间隔最大化」的思想来学习最优的决策边界,比逻辑回归算法更为鲁棒。
一、支持向量机
首先来直观地理解一下决策边界,考虑一个线性可分的二分类问题:

图中有三条直线分别代表三个分类器的决策边界,可以直观地感受到 H 3 H_3 H3 应该会好于 H 1 H_1 H1 和 H 2 H_2 H2 。显然, H 1 H_1 H1 甚至不能把类别正确分开,肯定不能考虑; H 2 H_2 H2 虽然可以,但是边界距离最近的数据点只有很小的间隔,如果测试数据有一些噪声的话,很可能会被 H 2 H_2 H2 错误地分到另一类——即对噪声敏感、泛化能力弱。
而 H 3 H_3 H3 似乎距离两侧都有较大的间隔,可以容忍一些微小的噪声。如何能学习到这样一个分类器呢?
1、优化目标
回忆逻辑回归的优化目标:
min
θ
1
m
[
∑
i
=
1
m
y
(
i
)
(
−
ln
h
θ
(
x
(
i
)
)
)
+
(
1
−
y
(
i
)
)
(
−
ln
(
1
−
h
θ
(
x
(
i
)
)
)
)
]
+
λ
2
m
∑
j
=
1
n
θ
j
2
\min_{\theta} \frac{1}{m} \left[ \sum_{i=1}^{m} y^{(i)} \left( -\ln h_{\theta} \left( x^{(i)} \right) \right) + \left( 1 - y^{(i)} \right) \left( -\ln \left( 1 - h_{\theta} \left( x^{(i)} \right) \right) \right) \right] + \frac{\lambda}{2m} \sum_{j=1}^{n} \theta_{j}^{2}
θminm1[i=1∑my(i)(−lnhθ(x(i)))+(1−y(i))(−ln(1−hθ(x(i))))]+2mλj=1∑nθj2
其中的
h
θ
(
x
(
i
)
)
h_{\theta}\left(x^{(i)}\right)
hθ(x(i)) 使用 sigmoid 函数,因此对于一个
y
=
1
y = 1
y=1 的样本,我们希望
h
θ
(
x
)
h_{\theta}(x)
hθ(x) 也趋近于 1,也就是
θ
T
x
\theta^{T}x
θTx 远大于 0,反之同理。进一步地,我们只考虑优化目标中的代价函数:
Cost
(
h
θ
(
x
)
,
y
)
=
{
−
ln
(
h
θ
(
x
)
)
y
=
1
−
ln
(
1
−
h
θ
(
x
)
)
y
=
0
\text{Cost}\left(h_{\theta}(x), y\right)= \begin{cases} -\ln \left(h_{\theta}(x)\right) & y = 1 \\ -\ln \left(1 - h_{\theta}(x)\right) & y = 0 \end{cases}
Cost(hθ(x),y)={−ln(hθ(x))−ln(1−hθ(x))y=1y=0
画出两种情况下的代价函数关于 θ T x \theta^{T}x θTx 的曲线:

记上图中左图为 cost 1 ( θ T x ) \text{cost}_1(\theta^T x) cost1(θTx),即分类为 1 时采用的代价;右图为 cost 0 ( θ T x ) \text{cost}_0(\theta^T x) cost0(θTx),即分类为 0 时采用的代价。在支持向量机中,我们不再采用 sigmoid 函数,而是将其修改为一条非常接近的折线:

其中,折线的拐点位于
z
=
±
1
z = \pm 1
z=±1 的位置,先不考虑斜率的大小,则代价函数可以表示为:
cost
1
(
z
)
=
{
0
z
⩾
1
k
1
(
z
−
1
)
z
<
1
cost
0
(
z
)
=
{
0
z
⩽
−
1
k
0
(
z
+
1
)
z
>
−
1
\text{cost}_1(z) = \begin{cases} 0 & z \geqslant 1 \\ k_1(z - 1) & z < 1 \end{cases} \quad \text{cost}_0(z) = \begin{cases} 0 & z \leqslant -1 \\ k_0(z + 1) & z > -1 \end{cases}
cost1(z)={0k1(z−1)z⩾1z<1cost0(z)={0k0(z+1)z⩽−1z>−1
此代价函数也被称为折页损失(Hinge Loss),非常形象,常用于凸优化任务中,也被写作:
L
(
y
)
=
max
(
0
,
1
−
y
^
⋅
y
)
L(y) = \max(0, 1 - \hat{y} \cdot y)
L(y)=max(0,1−y^⋅y)
其中 y = ± 1 y = \pm 1 y=±1 为正确的标签, y ^ \hat{y} y^ 为预测输出,通常是软结果,即取 [ − 1 , 1 ] [-1, 1] [−1,1] 区间中的值。
另外,SVM 中习惯不除以样本大小
m
m
m,这不会影响最终的优化目标。并将正则化参数放在第一项而非第二项,即支持向量机的优化目标为:
min
θ
C
∑
i
=
1
m
[
y
(
i
)
cost
1
(
θ
T
x
(
i
)
)
+
(
1
−
y
(
i
)
)
cost
0
(
θ
T
x
(
i
)
)
]
+
1
2
∑
j
=
1
n
θ
j
2
\min_{\theta} C \sum_{i=1}^{m} \left[ y^{(i)} \text{cost}_1 \left( \theta^T x^{(i)} \right) + \left( 1 - y^{(i)} \right) \text{cost}_0 \left( \theta^T x^{(i)} \right) \right] + \frac{1}{2} \sum_{j=1}^{n} \theta_j^2
θminCi=1∑m[y(i)cost1(θTx(i))+(1−y(i))cost0(θTx(i))]+21j=1∑nθj2
其中, C C C 就是正则化参数,类比逻辑回归中 λ \lambda λ 的作用。并且当 C C C 取到 1 λ \frac{1}{\lambda} λ1 时两者的优化目标一致。
2、大间隔思想
显然,新的代价函数会在计算速度上有一定的优势,但它是如何最大化间隔(Margin) 的呢?我们首先观察第一项代价
y
1
(
θ
T
x
)
+
(
1
−
y
)
0
(
θ
T
x
)
y_1(\theta^T x) + (1 - y)_0(\theta^T x)
y1(θTx)+(1−y)0(θTx),欲使之最小,最理想的情况就是对于每个样本,都有
cost
y
(
θ
T
x
)
=
0
\text{cost}_y(\theta^T x) = 0
costy(θTx)=0,对应着:
{
θ
T
x
⩾
1
if
y
=
1
θ
T
x
⩽
−
1
if
y
=
0
\begin{cases} \theta^T x \geqslant 1 & \text{if } y = 1 \\ \theta^T x \leqslant -1 & \text{if } y = 0 \end{cases}
{θTx⩾1θTx⩽−1if y=1if y=0
而在逻辑回归中,我们只需要使误分类的样本最少,也就是:
{
θ
T
x
⩾
0
if
y
=
1
θ
T
x
⩽
0
if
y
=
0
\begin{cases} \theta^T x \geqslant 0 & \text{if } y = 1 \\ \theta^T x \leqslant 0 & \text{if } y = 0 \end{cases}
{θTx⩾0θTx⩽0if y=1if y=0
因此 SVM 相当于将判别条件变得更苛刻,以文章开篇提到的三条决策边界为例,直线 H 2 H_2 H2 和 H 3 H_3 H3 以及它俩的任意线性组合,都能完美分开两类,也就是能有无穷多个解满足逻辑回归的优化目标。但对于支持向量机而言,可选的解会相对少很多。而这也使 SVM 能相对更具有鲁棒性,因为我们留出了一个「安全边界」来应对噪声样本。
接下来假设一种极端的情况,我们将正则化参数
C
C
C 取一个非常大的值,此时模型会倾向于将第一项收敛为 0。那么模型的优化目标可以改写为:
min
θ
1
2
∑
j
=
1
n
θ
j
2
s.t
{
θ
T
x
(
i
)
⩾
1
if
y
(
i
)
=
1
θ
T
x
(
i
)
⩽
−
1
if
y
(
i
)
=
0
\min_{\theta} \frac{1}{2} \sum_{j=1}^{n} \theta_j^2 \text{ s.t } \begin{cases} \theta^T x^{(i)} \geqslant 1 & \text{if } y^{(i)} = 1 \\ \theta^T x^{(i)} \leqslant -1 & \text{if } y^{(i)} = 0 \end{cases}
θmin21j=1∑nθj2 s.t {θTx(i)⩾1θTx(i)⩽−1if y(i)=1if y(i)=0
上式也被称为硬间隔(Hard Margin) 优化目标,用于在线性可分的数据集中找到唯一的最优边界。但在现实情况中,数据样本通常更加复杂,即使真的线性可分,也可能会存在部分离群点,而模型为了得到完美的边界,会学习到偏差很大的边界。因此在实际中通常不将 C C C 设置得过大,采用软间隔(Soft Margin)。

回顾我们前面说到的,可以将 C C C 类比为 1 λ \frac{1}{\lambda} λ1,因此:
- C C C 较大时,相当于 λ \lambda λ 较小,可能会导致过拟合、高方差,出现如上图红线那样的情形。
- C C C 较小时,相当于 λ \lambda λ 较大,可能会导致欠拟合、高偏差,甚至无法分类。
3、几何角度理解大间隔
在前文改写的优化目标的基础上,我们注意到 θ T x \theta^T x θTx 其实是参数 θ \theta θ 和 x x x 两个向量的点积(内积),可以视作 x x x 向 θ \theta θ 的投影长度乘上 θ \theta θ 的长度 ∥ θ ∥ \|\theta\| ∥θ∥。
为了更好理解,我们忽略掉截距,令
θ
0
=
0
\theta_0 = 0
θ0=0,将特征数
n
n
n 置为 2,于是仅有两个特征
{
x
1
,
x
2
}
\{x_1, x_2\}
{x1,x2} 和两个参数
{
θ
1
,
θ
2
}
\{\theta_1, \theta_2\}
{θ1,θ2}。则此时有:
1
2
∑
j
=
1
n
θ
j
2
=
1
2
(
θ
1
2
+
θ
2
2
)
=
1
2
(
θ
1
2
+
θ
2
2
)
2
=
1
2
∥
θ
∥
2
\frac{1}{2} \sum_{j=1}^{n} \theta_j^2 = \frac{1}{2} \left( \theta_1^2 + \theta_2^2 \right) = \frac{1}{2} \left( \sqrt{\theta_1^2 + \theta_2^2} \right)^2 = \frac{1}{2} \|\theta\|^2
21j=1∑nθj2=21(θ12+θ22)=21(θ12+θ22)2=21∥θ∥2
从几何角度理解,参数 θ \theta θ 其实是决策边界的法向量,因此会和边界本身呈现垂直关系。而特征 x x x 就是样本点在(特征空间)坐标系上的向量,起点为原点。绘制出如下样例:

其中
p
(
i
)
p^{(i)}
p(i) 为投影轴的长度(可正可负),于是可以得到:
θ
T
x
(
i
)
=
θ
1
x
1
(
i
)
+
θ
2
x
2
(
i
)
=
p
(
i
)
∥
θ
∥
\theta^T x^{(i)} = \theta_1 x_1^{(i)} + \theta_2 x_2^{(i)} = p^{(i)} \|\theta\|
θTx(i)=θ1x1(i)+θ2x2(i)=p(i)∥θ∥
因此优化目标可以从几何角度再次改写为:
min
θ
1
2
∥
θ
∥
2
s. t
{
p
(
i
)
∥
θ
∥
⩾
1
if
y
(
i
)
=
1
p
(
i
)
∥
θ
∥
⩽
−
1
if
y
(
i
)
=
0
\min_{\theta} \frac{1}{2} \|\theta\|^2 \text{ s. t } \begin{cases} p^{(i)} \|\theta\| \geqslant 1 & \text{if } y^{(i)} = 1 \\ p^{(i)} \|\theta\| \leqslant -1 & \text{if } y^{(i)} = 0 \end{cases}
θmin21∥θ∥2 s. t {p(i)∥θ∥⩾1p(i)∥θ∥⩽−1if y(i)=1if y(i)=0
显然,为了让法向量的范数 ∥ θ ∥ 2 \|\theta\|^2 ∥θ∥2 最小化,我们必须增大样本在法向量上的投影 p ( i ) p^{(i)} p(i) 长度,如下图所示:

以上就是为什么支持向量机最终会找到最大间隔的原因。当然,在推导的过程中我们使用了简化的假设,例如 θ 0 = 0 \theta_0 = 0 θ0=0,这是为了让决策平面通过原点。
二、实际使用
1、软间隔
当训练数据不能线性可分但是可以近似线性可分时,可以允许 SVM 在少量样本上出错,即将之前的硬间隔最大化条件放宽一点,为此引入软间隔(Soft Margin)的概念。
此时,我们就不再不将正则化参数
C
C
C 设置得过大,则优化目标就变成:
min
θ
,
θ
0
1
2
∥
θ
∥
2
+
C
∑
i
=
1
n
max
(
0
,
1
−
y
(
i
)
(
θ
T
x
(
i
)
+
θ
0
)
)
\min_{\theta, \theta_0} \frac{1}{2} \|\theta\|^2 + C \sum_{i=1}^{n} \max \left( 0, 1 - y^{(i)} \left( \theta^T x^{(i)} + \theta_0 \right) \right)
θ,θ0min21∥θ∥2+Ci=1∑nmax(0,1−y(i)(θTx(i)+θ0))
2、高斯核函数
对于非线性可分的数据,在之前的文章中我们知道可以引入高阶特征来进行多项式回归,而问题就是如何选择这些高阶特征?除了通过组合原有特征,在这里我们引入核函数 ϕ : x ↦ ϕ ( x ) \phi: x \mapsto \phi(x) ϕ:x↦ϕ(x),它将原来的 n n n 维向量映射为更高维的向量,使得这些向量在该更高维空间中线性可分。

在之前的例子中,可以认为是使用了线性核
ϕ
(
x
)
=
x
\phi(x) = x
ϕ(x)=x,即不做任何改变;而接下来我们将介绍一种最常用的核函数——高斯核(Gaussian Kernel):
ϕ
i
(
x
)
=
Sim
(
x
,
l
(
i
)
)
=
exp
{
−
∥
x
−
l
(
i
)
∥
2
2
σ
2
}
\phi_{i}(x) = \text{Sim} \left( x, l^{(i)} \right) = \exp \left\{ -\frac{\left\| x - l^{(i)} \right\|^2}{2\sigma^2} \right\}
ϕi(x)=Sim(x,l(i))=exp{−2σ2
x−l(i)
2}
其中 σ \sigma σ 是可调节参数, l ( i ) l^{(i)} l(i) 被称为地标(Landmarks),是原始空间中选取的某些点,具有和样本特征 x x x 相同的维度。 ∥ x − l ( i ) ∥ \left\| x - l^{(i)} \right\| x−l(i) 即为样本到地标的欧氏距离。
可以看出,高斯核与正态分布的形式有些许类似,绘制出函数图像:

显然, x x x 距离 l ( i ) l^{(i)} l(i) 越远时,分子越大,函数值越接近 0 0 0;距离越近时,分子越接近 0 0 0,函数值越接近 1 1 1;当 x = l ( i ) x = l^{(i)} x=l(i) 时,函数值取到最大值。因此,我们自然而然地想到可以取训练集中的样本点作为地标,有 m m m 个样本就取 m m m 个地标。这里不需要区分正负样本点,因为在核函数 ϕ ( i ) ( x ) \phi^{(i)}(x) ϕ(i)(x) 前面还有一个可学习的 θ ( i ) \theta^{(i)} θ(i),对于不同样本会自动区分。注意到 σ \sigma σ 较大时,变化也较为平缓,模型可能会欠拟合,造成高偏差;反之则可能会造成过拟合。
假设我们原来用
x
(
i
)
=
{
1
,
x
1
(
i
)
,
x
2
(
i
)
,
⋯
,
x
n
(
i
)
}
∈
R
n
+
1
x^{(i)} = \left\{ 1, x_1^{(i)}, x_2^{(i)}, \cdots, x_n^{(i)} \right\} \in \mathbb{R}^{n + 1}
x(i)={1,x1(i),x2(i),⋯,xn(i)}∈Rn+1 来描述第
j
j
j 个样本的特征向量,则现在就是使用:
f
(
x
(
i
)
)
=
[
1
ϕ
1
(
x
(
i
)
)
ϕ
2
(
x
(
i
)
)
⋮
ϕ
i
(
x
(
i
)
)
⋮
ϕ
m
(
x
(
i
)
)
]
=
[
1
Sim
(
x
(
i
)
,
l
(
1
)
)
Sim
(
x
(
i
)
,
l
(
2
)
)
⋮
Sim
(
x
(
i
)
,
l
(
i
)
)
=
1
⋮
Sim
(
x
(
i
)
,
l
(
m
)
)
]
∈
R
m
+
1
f \left( x^{(i)} \right) = \begin{bmatrix} 1 \\ \phi_1 \left( x^{(i)} \right) \\ \phi_2 \left( x^{(i)} \right) \\ \vdots \\ \phi_i \left( x^{(i)} \right) \\ \vdots \\ \phi_m \left( x^{(i)} \right) \end{bmatrix} = \begin{bmatrix} 1 \\ \text{Sim} \left( x^{(i)}, l^{(1)} \right) \\ \text{Sim} \left( x^{(i)}, l^{(2)} \right) \\ \vdots \\ \text{Sim} \left( x^{(i)}, l^{(i)} \right) = 1 \\ \vdots \\ \text{Sim} \left( x^{(i)}, l^{(m)} \right) \end{bmatrix} \in \mathbb{R}^{m + 1}
f(x(i))=
1ϕ1(x(i))ϕ2(x(i))⋮ϕi(x(i))⋮ϕm(x(i))
=
1Sim(x(i),l(1))Sim(x(i),l(2))⋮Sim(x(i),l(i))=1⋮Sim(x(i),l(m))
∈Rm+1
三、代码实现
在实际运用 SVM 时,我们通常会调用现成的机器学习第三方库,比较经典的就有 Python 中的 Scikit-learn,其调用支持向量分类器的函数如下:
sklearn.svm.SVC(C=1.0, kernel='rbf', gamma='auto', probability=False)
该函数的实现基于 libsvm,其中二次规划问题的解决算法是 SMO,通常需要调节的参数如下:
C:正则化参数,取值越大,对松弛变量(误分类的代价函数)的惩罚越大,适用于训练集几乎可分的情况,此时训练集上的准确率较高,但泛化能力弱(过拟合)。kernel:默认取rbf径向基函数(高斯核),可选linear线性核、poly多项式核、sigmod等。gamma:核函数系数,默认为auto,取样本特征数的倒数。probability:是否启用概率估计,即最后的输出会包含属于每个类的概率值。
下面以 Coursera 上的数据集 ex6data1.mat 为例,这是一个线性可分的数据集:

使用如下代码:
import numpy as np
from scipy.io import loadmat
from sklearn import svm
import matplotlib.pyplot as plt
# load data
data = loadmat('ex6data1.mat')
X = data['X'] # (51, 2)
y = data['y'].flatten() # (51, )
# classifier define
clf = svm.SVC(C=1, kernel='linear')
clf.fit(X, y)
# dicision boundary
x1, x2 = np.meshgrid(np.linspace(0, 4.5, 100), np.linspace(1.3, 4.5, 100))
f = np.concatenate((x1.reshape(10000, 1), x2.reshape(10000, 1)), axis = 1)
res = clf.decision_function(f).reshape((100,100))
# draw
plt.xlabel('x1')
plt.ylabel('x2')
plt.title('C=1')
plt.plot(X[y==1][:, 0], X[y==1][:, 1], '+', color='black')
plt.plot(X[y==0][:, 0], X[y==0][:, 1], 'o', color='gold')
plt.contour(x1, x2, res, [0])
plt.show()
训练结果如下:

此外还有一个非线性可分的数据集 ex6data2.mat:

使用如下代码:
import numpy as np
from scipy.io import loadmat
from sklearn import svm
import matplotlib.pyplot as plt
# load data
data = loadmat('ex6data2.mat')
X = data['X'] # (863, 2)
y = data['y'].flatten() # (863, )
# classifier define
clf = svm.SVC(C=100, kernel='rbf', gamma=10, probability=True)
clf.fit(X, y)
print(clf.score(X, y))
# dicision boundary
x1, x2 = np.meshgrid(np.linspace(0, 1, 100), np.linspace(0.38, 1, 100))
f = np.concatenate((x1.reshape(10000, 1), x2.reshape(10000, 1)), axis = 1)
res = clf.decision_function(f).reshape((100,100))
# draw
plt.xlabel('x1')
plt.ylabel('x2')
plt.title('C=100, gamma=10')
plt.plot(X[y==1][:, 0], X[y==1][:, 1], '+', color='black')
plt.plot(X[y==0][:, 0], X[y==0][:, 1], 'o', color='gold')
plt.contour(x1, x2, res, [0])
plt.show()
训练结果如下:

415

被折叠的 条评论
为什么被折叠?



