5.4 SVM工作原理¶
5.4.1 决策函数和预测¶
线性SVM分类器通过简单地计算决策函数来 \(\mathbf{w^T} \mathbf{x} +b = w_1 x_1 + \cdots + w_n x_n + b\) 预测新实例 \(x\) 的分类。如果结果为正,则预测类别是正类(1),否则预测其为负类(0),见公式5-2。
[1]:
%matplotlib inline
import matplotlib
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
from sklearn import datasets
[2]:
iris = datasets.load_iris()
X = iris['data'][:, (2, 3)]
y = (iris['target']== 2).astype(np.float64)
X.shape, y.shape
[2]:
((150, 2), (150,))
[3]:
from mpl_toolkits.mplot3d import Axes3D
[4]:
def plot_3D_decision_function(ax, w, b, x1_lim=[4, 6], x2_lim=[0.8, 2.8]):
x1_in_bounds = (X[:, 0] > x1_lim[0]) & (X[:, 0] < x1_lim[1])
X_crop = X[x1_in_bounds]
y_crop = y[x1_in_bounds]
x1s = np.linspace(x1_lim[0], x1_lim[1], 20)
x2s = np.linspace(x2_lim[0], x2_lim[1], 20)
x1, x2 = np.meshgrid(x1s, x2s)
xs = np.c_[x1.ravel(), x2.ravel()]
df = (xs.dot(w) + b).reshape(x1.shape)
m = 1 / np.linalg.norm(w)
boundary_x2s = -x1s*(w[0]/w[1])-b/w[1]
margin_x2s_1 = -x1s*(w[0]/w[1])-(b-1)/w[1]
margin_x2s_2 = -x1s*(w[0]/w[1])-(b+1)/w[1]
ax.plot_surface(x1s, x2, np.zeros_like(x1),
color="b", alpha=0.2, cstride=100, rstride=100)
ax.plot(x1s, boundary_x2s, 0, "k-", linewidth=2, label=r"$h=0$")
ax.plot(x1s, margin_x2s_1, 0, "k--", linewidth=2, label=r"$h=\pm 1$")
ax.plot(x1s, margin_x2s_2, 0, "k--", linewidth=2)
ax.plot(X_crop[:, 0][y_crop==1], X_crop[:, 1][y_crop==1], 0, "g^")
ax.plot_wireframe(x1, x2, df, alpha=0.3, color="k")
ax.plot(X_crop[:, 0][y_crop==0], X_crop[:, 1][y_crop==0], 0, "bs")
ax.axis(x1_lim + x2_lim)
ax.text(4.5, 2.5, 3.8, "Decision function $h$", fontsize=15)
ax.set_xlabel(r"Petal length", fontsize=15)
ax.set_ylabel(r"Petal width", fontsize=15)
ax.set_zlabel(r"$h = \mathbf{w}^T \mathbf{x} + b$", fontsize=18)
ax.legend(loc="upper left", fontsize=16)
[5]:
from sklearn.svm import LinearSVC
from sklearn.preprocessing import StandardScaler
svc_clf2 = LinearSVC(C=100, loss="hinge", random_state=42)
svc_clf2.fit(X, y)
[5]:
LinearSVC(C=100, loss='hinge', random_state=42)
[6]:
fig = plt.figure(figsize=(11, 6))
ax1 = fig.add_subplot(111, projection='3d')
plot_3D_decision_function(ax1, w=svc_clf2.coef_[0], b=svc_clf2.intercept_[0])
plt.show()
5.4.2 训练目标¶
决策函数的斜率:它等于权重向量的范数,即 \(||\mathbf{w}||\) 。如果我们将斜率除以2,那么决策函数等于±1的点也将变得离决策函数两倍远。也就是说,将斜率除以2,将会使间隔乘以2。也许2D图更容易将其可视化,见图5-13。
权重向量w越小,间隔越大
[7]:
def plot_2D_decision_function(w, b, ylabel=True, x1_lim=[-3, 3]):
x1 = np.linspace(x1_lim[0], x1_lim[1], 200)
y = w * x1 + b
m = 1 / w
plt.plot(x1, y)
plt.plot(x1_lim, [1, 1], "k:")
plt.plot(x1_lim, [-1, -1], "k:")
plt.axhline(y=0, color='k')
plt.axvline(x=0, color='k')
plt.plot([m, m], [0, 1], "k--")
plt.plot([-m, -m], [0, -1], "k--")
plt.plot([-m, m], [0, 0], "k-o", linewidth=3)
plt.axis(x1_lim + [-2, 2])
plt.xlabel(r"$x_1$", fontsize=16)
if ylabel:
plt.ylabel(r"$w_1 x_1$ ", rotation=0, fontsize=16)
plt.title(r"$w_1 = {}$".format(w), fontsize=16)
plt.figure(figsize=(12, 3.2))
plt.subplot(121)
plot_2D_decision_function(1, 0)
plt.subplot(122)
plot_2D_decision_function(0.5, 0, ylabel=False)
plt.show()
我们要最小化 \(||\mathbf{w}||\) 来得到尽可能大的间隔。但是,如果我们想避免任何间隔违例(硬间隔),那么就要使所有正类训练集的决策函数大于1,负类训练集的决策函数小于-1. 如果我们定义实例为负类(如果 \(y^{(i)}=0\) )时,\(t^{(i)}=-1\) ;实例为正类(如果\(y^{(i)}=1\))时,\(t^{(i)}=1\)。那么就可以将这个约束条件表示为:对所有实例来说,\(t^{(i)}(\mathbf{w}^T \cdot \mathbf{x}^{(i)} + b) \ge 1\)
因此,我们可以将硬间隔线性SVM分类器的目标看作一个约束优化问题:
$$ \underset{\mathbf{w}, b}{\text{minimize}} \frac{1}{2} \mathbf{w}`^T :nbsphinx-math:mathbf{w}`
$$
$$
\text{使得}`t\ :sup:`{(i)}(:nbsphinx-math:mathbf{w}``T \cdot `:nbsphinx-math:mathbf{x}`^{(i)} + b) \ge `1, i= 1, 2, :nbsphinx-math:cdots`, m $$
我们要最小化 \(\frac{1}{2} w^T w\) (这等于 \(\frac{1}{2} {||w||}^2\) ), 而不是最小化\(||w||\), 的确, \(\frac{1}{2} {||w||}^2\)有一个很好的简单的导数(是\(w\)),而\(||w||\)在\(w=0\)时不可微。
要达到软间隔的目标,我们需要为每一个实例引入一个松弛变量\(\zeta^{(i)} \ge 0\), \(\zeta^{(i)}\)衡量的是第\(i\)个实例多大程度上允许间隔违例。那么现在我们有了两个互相冲突的目标:使松弛变量越小越好从而减少间隔违例,同时还要使\(w^T \cdot w/2\)最小化以增大间隔。这正是超参数\(C\)的用武之地:允许我们在两个目标之间权衡.
软间隔线性SVM分类器目标:
$$ \underset{\mathbf{w}, b, \zeta}{\text{minimize}} \frac{1}{2} \mathbf{w}`^T :nbsphinx-math:mathbf{w}` + C \sum_{i=1}^m \zeta`^{(i)} :nbsphinx-math:mathbf{w}`
$$
$$
\text{使得}`t\ :sup:`{(i)}(:nbsphinx-math:mathbf{w}``T \cdot `:nbsphinx-math:mathbf{x}`^{(i)} + b) \ge `1 - :nbsphinx-math:zeta`^{(i)} \text{和} \zeta`^{(i)} :nbsphinx-math:ge 0, i= 1, 2, :nbsphinx-math:cdots`, m $$
5.4.3 二次规划¶
硬间隔和软间隔问题都属于线性约束的凸二次优化问题。这类问题被称为二次规划(QP)问题
公式5-5:二次规划问题
\(\mathbf{p}\)是一个\(n_p\)维向量(\(n_p\)是参数数量)
\(\mathbf{H}\)是一个\(n_p \times n_p\)的矩阵
\(\mathbf{f}\)是一个\(n_p\)维的向量
\(\mathbf{A}\)是一个\(n_c \times n_p\)的矩阵(\(n_c\)是约束的数量)
\(\mathbf{b}\)是一个\(n_c\)维的向量
请注意,表达式\(\mathbf{A} \mathbf{p} \le \mathbf{b}^{(i)}\)定义了\(n_c\)个约束:\(p^T a^{(i)} \le b^{(i)}\),其中\(i=1, 2, \cdots ,n_c\),其中\(a^{(i)}\)是包含\(\mathbf{A}\)的第\(i\)行元素的向量,\(b^{(i)}\)是\(\mathbf{b}\)的第\(i\)个元素。
你可以轻松地验证,如果用以下方式设置QP参数,就可以得到硬间隔线性SVM分类器的目标:
\(n_p = n+1\), 其中n是特征数量(+1是偏置项)
\(n_c = m\), 其中m是训练的实例的数量
\(H\)是\(n_p \times n_p\)单位矩阵,除了在左上方的元素为零(忽略偏置项)
\(f=0\)是一个全零的\(n_p\)维向量
\(b=-1\)是一个全是-1的\(n_c\)维向量
\(a^{(i)} = t^{(i)} \cdot \dot{x}^{(i)}\), 其中\(\dot{x}^{(i)}\)等于\(x^{(i)}\), 并且具有额外的偏差\(\dot{x}_0 = 1\)
所以,要训练硬间隔线性SVM分类器,有一种办法是直接将上面的参数用在一个现成的二次规划求解器上。得到的向量\(p\)将会包括偏置项\(b=p_0\),以及特征权重\(w_i=p_i, i=1, 2, \cdots ,m\)。类似地,你也可以用二次规划求解器来解决软间隔问题.
5.4.4 对偶问题¶
针对一个给定的约束优化问题,称之为原始问题,我们常常可以用另一个不同的,但是与之密切相关的问题来表达,这个问题我们称之为对偶问题。通常来说,对偶问题的解只能算是原始问题的解的下限,但是在某些情况下,它也可能跟原始问题的解完全相同。幸运的是,SVM问题刚好就满足这些条件(目标函数是凸函数,不等式约束是连续可微和凸函数),所以你可以选择是解决原始问题还是对偶问题,二者解相同.
公式5-6给出了线性SVM目标的对偶形式:
使得\(\alpha^{(i)} \ge 0, i=1, 2, \cdots, m\)
公式5-7:从对偶问题到原始问题
$$ \begin{aligned}
\hat{\mathbf{w}} &= \sum_{i=1}^m \hat{\alpha}`^{(i)} t^{(i)} {:nbsphinx-math:mathbf{x}`}^{(i)} \ \hat{b} &= \frac{1}{n_s} \underset{\hat{\alpha}^{(i)} > 0}{\sum_{i=1}^m} (t^{(i)} - \hat{\mathbf{w}}`^T :nbsphinx-math:mathbf{x}`^{(i)})
\end{aligned} $$
当训练实例的数量小于特征数量时,解决对偶问题比原始问题更快速。更重的是,它能够实现核技巧,而原始问题不可能实现。
5.4.5 内核化SVM¶
假设二阶多项式的映射函数\(\phi\)如公式5-8所示:
注意转换后的向量是三维的而不是二维的。现在我们来看看,如果应用这个二阶多项式映射,两个二维向量a和b会发生什么变化,然后计算转换后两个向量的点积(在机器学习中,向量经常表示为列向量,由此点积可以通过计算\(a^T b\)获得)
公式5-9:二阶多项式映射的核技巧:
转换后向量的点集等于原始向量的点积的平方:
关键点:如果将转换映射\(\phi\)应用于所有训练实例,那么对于对偶问题(见公式5-6)将包含点积:
的计算。如果\(\phi\)是公式5-8所定义的二阶多项式转换,那么可以直接用:
来替换这个转换向量的点积。所以你根本不需要转换训练实例,只需要将公式5-6里的点积转换成点积的平方即可。这个技巧大大调高了整个过程的计算效率,这就是核技巧的本质。
函数\(K(a, b) = (a^T \cdot b)^2\)被称为二阶多项式核,在机器学习里,核是能够仅基于原始向量a和b来计算点积\(\phi(a)^T\phi(b)\)的函数,它不需要计算(甚至不需要知道)转换函数。
公式5-10是一些常用的核函数:
根据Mercer定理,如果函数\(K(a, b)\)符合几个数学条件:也就是Mercer条件(K必须是连续的,并且在其参数上对称,所以\(K(a, b)=K(b, a)\), 等等),则存在函数\(\phi\)将a和b映射到另一个空间(可能是更高维度的空间),使得\(K(a, b) = \phi(a)^T \phi(b)\). 所以你可以将K用作核函数,因为你知道\(\phi\)是存在的。
注意,也有一些常用的核函数(如S型核函数)不符合Mercer条件的所有条件,但是他们在实践中通常也表现不存。
还有一个未了解的问题需要说明。公式5-7显示了用线性SVM分类器如何从对偶解走向原始解,但是如果你应用了核技巧,最终得到的是包含\(\phi(x^{(i)})\)的方程。而\(\hat{\mathbf{w}}\)的维度数量必须与\(\phi(x^{(i)})\)相同,后者很有可能是巨大甚至是无穷大的,所以你根本无法计算,可是不知道\(\hat{\mathbf{w}}\)该如何做出预测呢?你可以将公式5-7中的\(\hat{\mathbf{w}}\)的公式插入新实例\(x^{(n)}\)的决策函数中,这样就得到了一个只能包含输入向量之间点积的公式。这时你就可以再次运用核技巧了:
公式5-11: 使用核化SVM做出预测:
注意,因为仅对于支持向量才有\(\alpha^{(i)} \ne 0\), 所以在预测时,计算新输入向量\(\mathbf{x}(n)\)的点积,使用的仅仅是支持向量而不是全部训练实例。当然,你还需要使用同样的技巧计算偏置项\(\hat{b}\).
公式5-12:使用核技巧来计算偏置项:
$$ \begin{aligned} \hat{b} &= \frac{1}{n_s} \underset{\hat{\alpha}^{(i)}>0}{\sum_{i=1}^m} (t^{(i)} - \hat{w}`^T :nbsphinx-math:phi`(\mathbf{x}`^{(i)})) \\ &= :nbsphinx-math:frac{1}{n_s}` \underset{\hat{\alpha}^{(i)}>0}{\sum_{i=1}^m} \Bigg`( t^{(i)} - :nbsphinx-math:Bigg`( \sum_{j=1}^m \hat{\alpha}`^{(j)} t^{(j)} :nbsphinx-math:phi`(\mathbf{x}`^{(j)}) :nbsphinx-math:Bigg`)^T \phi`(:nbsphinx-math:mathbf{x}`^{(i)}) \Bigg) \ &= \frac{1}{n_s} \underset{\hat{\alpha}^{(i)}>0}{\sum_{i=1}^m} \Bigg`( t^{(i)} - :nbsphinx-math:underset{hat{alpha}^{(j)}>0}{sum_{j=1}^m}` \hat{\alpha}`^{(j)} t^{(j)} K(:nbsphinx-math:mathbf{x}`^{(i)}, \mathbf{x}`^{(j)}) :nbsphinx-math:Bigg`)
\end{aligned} $$
如果你现在觉得开始头痛,完全正常:这正是核技巧的副作用
5.4.6 在线SVM¶
对于线性SVM分类器,一种实现在线SVM分类器的方法是使用梯度下降(例如,使用SGDClassifier)来最小化源自原始问题的公式5-13的成本函数。不幸的是,梯度下降比基于QP的方法收敛慢得多。
公式5-13:线性SVM分类器成本函数:
成本函数是的第一项会推动模型得到一个较小的权重向量\(\mathbf{w}\), 从而使间隔更大。第二项则计算全部的间隔违例。如果没有一个示例位于街道之上,并且都在街道正确的一边,那么各个实例的间隔违例为0;否则,该实例的违例大小与其到街道正确一边的距离成正比。所以将这个项最小化,能够保证模型使间隔违例尽可能小,也尽可能少。
Hinge损失函数
函数\(\max(0, 1-t)\)被称为hinge损失函数。
[8]:
X = np.linspace(-2, 4, 40)
y = np.maximum(X, 0)
plt.figure(figsize=(9, 4))
plt.xlabel('t')
plt.axis([-2, 4, -1, 2])
plt.grid(True)
plt.axvline(x=0, color="k")
plt.axhline(y=0, color="k")
plt.plot(1-X, y, "b-",label=r"$\max(0, 1-t)$")
plt.legend(loc="upper right")
plt.show()
在线SVM也可是实现核技巧,可参考“Incremental and Decremental SVM Learning”, 以及“Fast Kernel Classifier with Online and Active Learning”。但是这些核SVM都是在Matlab和C++上实现的,对于大规模非线性问题,你可能需要使用神经网络。