ML: SVM

支持向量机

  • 1. 基于最大间隔分隔数据
  • 2. 寻找最大间隔
    • 2.1分类器求解的优化问题:
    • 2.2 拉格朗日乘子法
  • 3. SMO 高效优化算法
    • 3.1 Platt 的 SMO 算法
    • 3.2 求解步骤
    • 3.3 应用简化版 SMO 算法处理小规模数据集
  • 4. 利用完整 Platt SMO 算法加速优化
    • 4.1 完整版 Platt SMO 的支持函数
    • 4.2 完整 Platt SMO 算法中的优化例程
  • 5. 在复杂数据上应用核函数
    • 5.1 利用核函数将数据映射到高维空间
    • 5.2 径向基核函数
    • 5.3 在测试中使用核函数

  • Support Vector Machines
  • 本文使用最流行的一种实现方法完成 SVM,该算法为序列最小优化(Sequential Minimal Optimization, SMO)算法
  • 优点:泛化错误率低,计算开销不大,结果易解释
  • 缺点:对参数调节和核函数的选择敏感,原始分类器不加修改仅适用于处理二类问题
  • 适用数据类型:数值型和标称型数据

1. 基于最大间隔分隔数据

  • 超平面(hyperplane):对于二维数据,需要用一条直线来分隔,三维数据用一个平面来分隔,以此类推,N 维数据需要 N-1 维的对象来分隔,该对象为超平面
  • 间隔(margin):间隔指离分隔超平面最近的点与分隔面的距离,该距离越大,分类器越好,或者说越健壮
  • 支持向量(support vector):即离分隔超平面最近的那些点

2. 寻找最大间隔

  • 假设分隔超平面为 w T x + b = 0 {\bf {w}}^T{\bf {x}} + b = 0 wTx+b=0,则点 A {\bf {A}} A 到该超平面的距离为 ∣ w T A + b ∣ ∣ ∣ w ∣ ∣ \frac{|{\bf {w}}^T{\bf {A}} + b|}{||{\bf {w}}||} wwTA+b

2.1分类器求解的优化问题:

  1. 超平面首先要满足能够正确分类,即:
    { w T x i + b > 0 y i = 1 w T x i + b < 0 y i = − 1 \left\{ \begin{array}{l} {\bf {w}}^T{\bf {x_i}} + b>0\qquad y_i=1\\ {\bf {w}}^T{\bf {x_i}} + b<0\qquad y_i=-1 \end{array} \right. {wTxi+b>0yi=1wTxi+b<0yi=1
  2. 其次,我们要找的超平面,应该是间隔区域的中轴线,假设支持向量与超平面的距离为 d,则:
    { w T x i + b ∣ ∣ w ∣ ∣ ≥ d ∀ y i = 1 w T x i + b ∣ ∣ w ∣ ∣ ≤ − d ∀ y i = − 1 \left\{ \begin{array}{l} \frac{{\bf {w}}^T{\bf {x_i}} + b}{||{\bf {w}}||} \geq d \qquad \forall y_i=1\\[2ex] \frac{{\bf {w}}^T{\bf {x_i}} + b}{||{\bf {w}}||} \leq -d \qquad \forall y_i=-1 \end{array} \right. wwTxi+bdyi=1wwTxi+bdyi=1
    两边除以 d,得到:
    { w d T x i + b d ≥ 1 ∀ y i = 1 w d T x i + b d ≤ − 1 ∀ y i = − 1 \left\{ \begin{array}{l} {\bf {w}}_d^T{\bf {x_i}} + b_d \geq 1 \qquad \forall y_i=1\\[2ex] {\bf {w}}_d^T{\bf {x_i}} + b_d \leq -1 \qquad \forall y_i=-1 \end{array} \right. {wdTxi+bd1yi=1wdTxi+bd1yi=1
    其中 w d T = w T ∣ ∣ w ∣ ∣ d , b d = b ∣ ∣ w ∣ ∣ d {\bf {w}}_d^T = \frac{{\bf {w}}^T}{||{\bf {w}}||d},\quad b_d = \frac{b}{||{\bf {w}}||d} wdT=wdwT,bd=wdb
    其实是在原 w {\bf {w}} w b b b 的基础上除以了一个标量,因此 w d T x i + b d = 0 {\bf {w}}_d^T{\bf {x_i}} + b_d=0 wdTxi+bd=0 w T x i + b = 0 {\bf {w}}^T{\bf {x_i}} + b=0 wTxi+b=0 为同一个平面,因此可以直接表示为:
    { w T x i + b ≥ 1 ∀ y i = 1 w T x i + b ≤ − 1 ∀ y i = − 1 \left\{ \begin{array}{l} {\bf {w}}^T{\bf {x_i}} + b \geq 1 \qquad \forall y_i=1\\[2ex] {\bf {w}}^T{\bf {x_i}} + b \leq -1 \qquad \forall y_i=-1 \end{array} \right. {wTxi+b1yi=1wTxi+b1yi=1
  3. 假设我们的输出类别分别为 -1 和 +1,则上式可以表示为:
    y i ( w T x i + b ) ≥ 1 ∀ x i y_i({\bf {w}}^T{\bf {x_i}} + b)\geq 1 \qquad \forall {\bf {x_i}} yi(wTxi+b)1xi
  4. 对于我们的支持向量(离超平面最近的点),有: ∣ w T x i + b ∣ = 1 |{\bf {w}}^T{\bf {x_i}} + b| = 1 wTxi+b=1
    所以我们要最大化的距离: d = 1 ∣ ∣ w ∣ ∣ d = \frac{1}{||{\bf {w}}||} d=w1
    即求 ∣ ∣ w ∣ ∣ ||{\bf {w}}|| w 的最小值,为了方便求导,可等效于求
    m i n 1 2 ∣ ∣ w ∣ ∣ 2 s . t y i ( w T x i + b ) ≥ 1 i = 1 , 2 , . . , n \begin{array}{l}min \frac{1}{2}||{\bf {w}}||^2\\[2ex] s.t\quad y_i({\bf {w}}^T{\bf {x_i}} + b)\geq 1 \quad i=1,2,..,n \end{array} min21w2s.tyi(wTxi+b)1i=1,2,..,n
    以上即为支持向量机的基本数学模型

2.2 拉格朗日乘子法

  1. 上述问题为具有约束条件的最小化问题,且约束条件为不等式,对于这种问题,一般采用拉格朗日乘子法求解,其基本思想为构造一个函数,使得该函数在可行解区域内与原目标函数完全一致,而在可行解区域外的数值非常大,甚至是无穷大,这样的话就将其转换为与原问题等价的无约束条件最小化问题。构造的函数如下:
    L ( x , α ) = f ( x ) + α g ( x ) L(x, \alpha) =f(x) + \alpha g(x) L(x,α)=f(x)+αg(x)
    其中 f ( x ) f(x) f(x) 为原函数, g ( x ) g(x) g(x) 为满足约束条件的函数,约束条件为 g ( x ) ≤ 0 g(x)\leq 0 g(x)0,对于支持向量机的基本数学模型,构造函数如下:
    L ( w , α , b ) = 1 2 ∣ ∣ w ∣ ∣ 2 − ∑ i = 1 n α i ( y i ( w T x i + b ) − 1 ) L({\bf {w}}, \alpha, b) =\frac{1}{2}||{\bf {w}}||^2 - \sum_{i=1}^n\alpha_i(y_i({\bf {w}}^T{\bf {x_i}} + b)-1) L(w,α,b)=21w2i=1nαi(yi(wTxi+b)1)
    其中 α i ≥ 0 \alpha_i \geq 0 αi0。设:
    θ ( w ) = m a x α i ≥ 0 L ( w , α , b ) \theta({\bf {w}}) = \mathop{max}\limits_{\alpha_i \geq 0} L({\bf {w}}, \alpha, b) θ(w)=αi0maxL(w,α,b)
    y i ( w T x i + b ) ≥ 1 y_i({\bf {w}}^T{\bf {x_i}} + b)\geq 1 yi(wTxi+b)1 时,显然该函数等于原函数,当 y i ( w T x i + b ) ≤ 1 y_i({\bf {w}}^T{\bf {x_i}} + b)\leq 1 yi(wTxi+b)1 时,我们将 α \alpha α 设置为无穷大,那么该函数值也为无穷大,于是就满足了上面提到的转换为无约束最小化问题的条件。于是我们的问题转换为求解:
    m i n w , b m a x α i ≥ 0 L ( w , α , b ) = p ∗ \mathop{min} \limits_{{\bf {w}},b}\mathop{max}\limits_{\alpha_i \geq 0} L({\bf {w}}, \alpha, b) = p* w,bminαi0maxL(w,α,b)=p
  2. 上式求解起来比较困难,因为首先要面对关于 w , b {\bf {w}},b w,b 的方程,而 α i \alpha_i αi 又是不等式约束。于是我们利用拉格朗日函数的对偶性,将最小和最大的位置交换一下:
    m a x α i ≥ 0 m i n w , b L ( w , α , b ) = d ∗ \mathop{max}\limits_{\alpha_i \geq 0}\mathop{min} \limits_{{\bf {w}},b} L({\bf {w}}, \alpha, b) = d* αi0maxw,bminL(w,α,b)=d
    满足 p ∗ = d ∗ p*=d* p=d 的条件一是求取最小值的函数是凸函数(这一点很明显满足),二是满足 KKT 条件,全称是 Karush-Kuhn-Tucker 条件,它表示我们的最优值必须满足:
    • 经过拉格朗日函数处理之后的新目标函数 L ( w , b , α ) L({\bf {w}},b,α) L(w,b,α) w , b {\bf {w}},b w,b 求导为零;
    • h ( x ) = 0 h(x) = 0 h(x)=0 h ( x ) h(x) h(x) 为等式约束,在我们的问题中显然满足该条件);
    • α ∗ g ( x ) = 0 α*g(x) = 0 αg(x)=0
      针对第一个条件,对 w , b {\bf {w}},b w,b 分别求偏导:
      ∂ L ∂ w = 0    ⟺    w = ∑ i = 1 n α i y i x i \frac{\partial L}{\partial {\bf {w}}} = 0\iff{\bf {w}}=\sum_{i=1}^n\alpha_iy_i{\bf {x}}_i wL=0w=i=1nαiyixi
      ∂ L ∂ b = 0    ⟺    ∑ i = 1 n α i y i = 0 \frac{\partial L}{\partial b} = 0\iff\sum_{i=1}^n\alpha_iy_i=0 bL=0i=1nαiyi=0
      代回原式得到:
      L ( w , b , α ) = ∑ i = 1 n α i − 1 2 ∑ i , j = 1 n ( α i α j y i y j x i T x j ) L({\bf {w}},b,α) = \sum_{i=1}^n\alpha_i - \frac{1}{2}\sum_{i,j=1}^n(\alpha_i \alpha_j y_i y_j {\bf {x}}_i^T{\bf {x}}_j) L(w,b,α)=i=1nαi21i,j=1n(αiαjyiyjxiTxj)
      于是上述问题转换为:
      m a x α [ ∑ i = 1 n α i − 1 2 ∑ i , j = 1 n ( α i α j y i y j x i T x j ) ] \mathop{max}\limits_{\alpha}\left[\sum_{i=1}^n\alpha_i - \frac{1}{2}\sum_{i,j=1}^n(\alpha_i \alpha_j y_i y_j {\bf {x}}_i^T{\bf {x}}_j)\right] αmax[i=1nαi21i,j=1n(αiαjyiyjxiTxj)] s . t . α i ≥ 0 , i = 1 , 2 , 3 , . . . , n s.t.\qquad \alpha_i\geq 0, i=1,2,3,...,n s.t.αi0,i=1,2,3,...,n ∑ i = 1 n α i ⋅ y i = 0 \sum_{i=1}^n\alpha_i\cdot y_i = 0 i=1nαiyi=0

3. SMO 高效优化算法

3.1 Platt 的 SMO 算法

  • 将大优化问题分解为多个小优化问题来求解小优化问题往往很容易求解,并且对它们进行顺序求解的结果与将它们作为整体来求解的结果完全一致的。在结果完全相同的同时,SMO算法的求解时间短很多。
  • SMO算法的目标是求出一系列 α {\alpha} α 和 b,一旦求出了这些 α {\alpha} α,就很容易计算出权重向量 w {\bf {w}} w 并得到分隔超平面。
  • SMO算法的工作原理是:每次循环中选择两个 α {\alpha} α 进行优化处理。一旦找到了一对合适的 α {\alpha} α ,那么就增大其中一个同时减小另一个。这里所谓的“合适”就是指两个 α {\alpha} α 必须符合以下两个条件,条件之一就是两个 α {\alpha} α 必须要在间隔边界之外,而且第二个条件则是这两个 α {\alpha} α 还没有进行过区间化处理或者不在边界上。
  • u = w T x + b = ∑ i = 1 n α i y i x i T x + b u={\bf {w}}^T{\bf {x}} + b = \sum_{i=1}^n\alpha_i y_i{\bf {x}}_i^T{\bf {x}}+b u=wTx+b=i=1nαiyixiTx+b
    我们的目标函数:
    m a x α [ ∑ i = 1 n α i − 1 2 ∑ i , j = 1 n ( α i α j y i y j x i T x j ) ] \mathop{max}\limits_{\alpha}\left[\sum_{i=1}^n\alpha_i - \frac{1}{2}\sum_{i,j=1}^n(\alpha_i \alpha_j y_i y_j {\bf {x}}_i^T{\bf {x}}_j)\right] αmax[i=1nαi21i,j=1n(αiαjyiyjxiTxj)] s . t . α i ≥ 0 , i = 1 , 2 , 3 , . . . , n s.t.\qquad \alpha_i\geq 0, i=1,2,3,...,n s.t.αi0,i=1,2,3,...,n ∑ i = 1 n α i ⋅ y i = 0 \sum_{i=1}^n\alpha_i\cdot y_i = 0 i=1nαiyi=0
    变换为求最小值:
    m i n α [ 1 2 ∑ i = 1 n ∑ j = 1 n ( α i α j y i y j x i T x j ) − ∑ i = 1 n α i ] \mathop{min}\limits_{\alpha}\left[\frac{1}{2}\sum_{i=1}^n\sum_{j=1}^n(\alpha_i \alpha_j y_i y_j {\bf {x}}_i^T{\bf {x}}_j) - \sum_{i=1}^n\alpha_i\right] αmin[21i=1nj=1n(αiαjyiyjxiTxj)i=1nαi] s . t . α i ≥ 0 , i = 1 , 2 , 3 , . . . , n s.t.\qquad \alpha_i\geq 0, i=1,2,3,...,n s.t.αi0,i=1,2,3,...,n ∑ i = 1 n α i ⋅ y i = 0 \sum_{i=1}^n\alpha_i\cdot y_i = 0 i=1nαiyi=0
    实际上我们的数据并非完全线性可分,所以可以引入一个松弛变量(slack variable),来允许有些数据点可以处于分隔面的错误一侧,此时的约束条件变为:
    s . t . C ≥ α i ≥ 0 , i = 1 , 2 , 3 , . . . , n s.t.\qquad C \geq\alpha_i\geq 0, i=1,2,3,...,n s.t.Cαi0,i=1,2,3,...,n ∑ i = 1 n α i ⋅ y i = 0 \sum_{i=1}^n\alpha_i\cdot y_i = 0 i=1nαiyi=0
    根据 KKT条件以及松弛变量定义(具体可参考知乎,尤其是第三个理解起来比较困难,个人任务该问答比较有帮助)可以得出其中 α i {\alpha}_i αi 取值的意义为:
    α i = 0    ⟹    y i u i ≥ 1    ⟺    该 点 在 边 界 内 部 或 边 界 上 \alpha_i = 0\implies y_iu_i\geq1 \iff 该点在边界内部或边界上 αi=0yiui1 0 < α i < C    ⟹    y i u i = 1    ⟺    该 点 在 边 界 上 0< \alpha_i < C\implies y_iu_i = 1 \iff 该点在边界上 0<αi<Cyiui=1 α i = C    ⟹    y i u i ≤ 1    ⟺    该 点 在 边 界 外 或 边 界 上 ( 允 许 错 分 的 样 本 点 ) \alpha_i = C\implies y_iu_i \leq 1 \iff 该点在边界外或边界上(允许错分的样本点) αi=Cyiui1()
    α i {\alpha}_i αi 不满足上面的条件时,需要更新 α i {\alpha}_i αi,与此同时它还应满足第二个约束条件 ∑ i = 1 n α i ⋅ y i = 0 \sum_{i=1}^n\alpha_i\cdot y_i = 0 i=1nαiyi=0,因此我们同时更新两个 α {\alpha} α 值,因为只有成对更新,才能保证更新之后的值仍然满足和为 0 的约束,假设我们选择的两个乘子为 α 1 {\alpha}_1 α1 α 2 {\alpha}_2 α2
    α 1 n e w y 1 + α 2 n e w y 2 = α 1 o l d y 1 + α 2 o l d y 2 = ζ \alpha_1^{new}y_1 + \alpha_2^{new}y_2 = \alpha_1^{old}y_1 + \alpha_2^{old}y_2 = \zeta α1newy1+α2newy2=α1oldy1+α2oldy2=ζ
    其中 ζ \zeta ζ 为常数,假设: L ≤ α 2 n e w ≤ H L\leq \alpha_2^{new}\leq H Lα2newH
    由: C ≥ α i ≥ 0 , i = 1 , 2 , 3 , . . . , n C \geq\alpha_i\geq 0, i=1,2,3,...,n Cαi0,i=1,2,3,...,n α 1 n e w y 1 + α 2 n e w y 2 = α 1 o l d y 1 + α 2 o l d y 2 = ζ \alpha_1^{new}y_1 + \alpha_2^{new}y_2 = \alpha_1^{old}y_1 + \alpha_2^{old}y_2 = \zeta α1newy1+α2newy2=α1oldy1+α2oldy2=ζ
    y 1 y_1 y1 y 2 y_2 y2 不同时: α 1 n e w − α 2 n e w = α 1 o l d − α 2 o l d = ζ \alpha_1^{new} - \alpha_2^{new} = \alpha_1^{old} - \alpha_2^{old} = \zeta α1newα2new=α1oldα2old=ζ
    y 1 y_1 y1 y 2 y_2 y2 相同时: α 1 n e w + α 2 n e w = α 1 o l d + α 2 o l d = ζ \alpha_1^{new} + \alpha_2^{new} = \alpha_1^{old} + \alpha_2^{old} = \zeta α1new+α2new=α1old+α2old=ζ
    得到 { H = m a x ( 0 , − ζ ) ,    L = m i n ( C , C − ζ ) y 1 ≠ y 2 H = m a x ( 0 , ζ − C ) ,    L = m i n ( C , ζ ) y 1 = y 2 \left\{\begin{array}{l}H = max(0, -\zeta), \; L = min(C, C-\zeta)\qquad y_1\neq y_2\\H = max(0, \zeta - C), \; L = min(C, \zeta)\qquad y_1= y_2 \end{array}\right. {H=max(0,ζ),L=min(C,Cζ)y1=y2H=max(0,ζC),L=min(C,ζ)y1=y2
    这个界限就是编程的时候需要用到的。已经确定了边界,接下来,就是推导迭代式,用于更新 α值。
    对于目标函数,将 α 1 {\alpha}_1 α1 α 2 {\alpha}_2 α2 提取出来:
    W ( α 2 ) = ∑ i = 1 n α i − 1 2 ∑ i = 1 n ∑ j = 1 n ( α i α j y i y j x i T x j ) W({\alpha}_2) = \sum_{i=1}^n\alpha_i - \frac{1}{2}\sum_{i=1}^n\sum_{j=1}^n(\alpha_i \alpha_j y_i y_j {\bf {x}}_i^T{\bf {x}}_j) W(α2)=i=1nαi21i=1nj=1n(αiαjyiyjxiTxj) = α 1 + α 2 + ∑ i = 3 n α i − 1 2 ∑ i = 1 n [ ∑ j = 1 2 ( α i α j y i y j x i T x j ) + ∑ j = 3 n ( α i α j y i y j x i T x j ) ] = \alpha_1+\alpha_2+\sum_{i=3}^n\alpha_i - \frac{1}{2}\sum_{i=1}^n\left[\sum_{j=1}^2(\alpha_i \alpha_j y_i y_j {\bf {x}}_i^T{\bf {x}}_j)+\sum_{j=3}^n(\alpha_i \alpha_j y_i y_j {\bf {x}}_i^T{\bf {x}}_j)\right] =α1+α2+i=3nαi21i=1n[j=12(αiαjyiyjxiTxj)+j=3n(αiαjyiyjxiTxj)] = α 1 + α 2 + ∑ i = 3 n α i − 1 2 ∑ i = 1 2 [ ∑ j = 1 2 ( α i α j y i y j x i T x j ) + ∑ j = 3 n ( α i α j y i y j x i T x j ) ] =\alpha_1+\alpha_2+\sum_{i=3}^n\alpha_i - \frac{1}{2}\sum_{i=1}^2\left[\sum_{j=1}^2(\alpha_i \alpha_j y_i y_j {\bf {x}}_i^T{\bf {x}}_j)+\sum_{j=3}^n(\alpha_i \alpha_j y_i y_j {\bf {x}}_i^T{\bf {x}}_j)\right] =α1+α2+i=3nαi21i=12[j=12(αiαjyiyjxiTxj)+j=3n(αiαjyiyjxiTxj)] − 1 2 ∑ i = 3 n [ ∑ j = 1 2 ( α i α j y i y j x i T x j ) + ∑ j = 3 n ( α i α j y i y j x i T x j ) ] - \frac{1}{2}\sum_{i=3}^n\left[\sum_{j=1}^2(\alpha_i \alpha_j y_i y_j {\bf {x}}_i^T{\bf {x}}_j)+\sum_{j=3}^n(\alpha_i \alpha_j y_i y_j {\bf {x}}_i^T{\bf {x}}_j)\right] 21i=3n[j=12(αiαjyiyjxiTxj)+j=3n(αiαjyiyjxiTxj)] = α 1 + α 2 + ∑ i = 3 n α i − 1 2 ∑ i = 1 2 ∑ j = 1 2 ( α i α j y i y j x i T x j ) − ∑ i = 1 2 ∑ j = 3 n ( α i α j y i y j x i T x j ) =\alpha_1+\alpha_2+\sum_{i=3}^n\alpha_i - \frac{1}{2}\sum_{i=1}^2\sum_{j=1}^2(\alpha_i \alpha_j y_i y_j {\bf {x}}_i^T{\bf {x}}_j)-\sum_{i=1}^2\sum_{j=3}^n(\alpha_i \alpha_j y_i y_j {\bf {x}}_i^T{\bf {x}}_j) =α1+α2+i=3nαi21i=12j=12(αiαjyiyjxiTxj)i=12j=3n(αiαjyiyjxiTxj) − 1 2 ∑ i = 3 n ∑ j = 3 n ( α i α j y i y j x i T x j ) - \frac{1}{2}\sum_{i=3}^n\sum_{j=3}^n(\alpha_i \alpha_j y_i y_j {\bf {x}}_i^T{\bf {x}}_j) 21i=3nj=3n(αiαjyiyjxiTxj) = α 1 + α 2 − 1 2 ( α 1 2 x 1 T x 1 ) − 1 2 ( α 2 2 x 2 T x 2 ) − α 1 α 2 y 1 y 2 x 1 T x 2 − α 1 y 1 ∑ j = 3 n ( α j y j x 1 T x j ) =\alpha_1+\alpha_2 - \frac{1}{2}(\alpha_1^2{\bf {x}}_1^T{\bf {x}}_1) - \frac{1}{2}(\alpha_2^2{\bf {x}}_2^T{\bf {x}}_2) - \alpha_1\alpha_2y_1y_2{\bf {x}}_1^T{\bf {x}}_2 - \alpha_1y_1\sum_{j=3}^n(\alpha_j y_j {\bf {x}}_1^T{\bf {x}}_j) =α1+α221(α12x1Tx1)21(α22x2Tx2)α1α2y1y2x1Tx2α1y1j=3n(αjyjx1Txj) − α 2 y 2 ∑ j = 3 n ( α j y j x 2 T x j ) − ∑ i = 3 n α i − 1 2 ∑ i = 3 n ∑ j = 3 n ( α i α j y i y j x i T x j ) - \alpha_2y_2\sum_{j=3}^n(\alpha_j y_j {\bf {x}}_2^T{\bf {x}}_j) - \sum_{i=3}^n\alpha_i - \frac{1}{2}\sum_{i=3}^n\sum_{j=3}^n(\alpha_i \alpha_j y_i y_j {\bf {x}}_i^T{\bf {x}}_j) α2y2j=3n(αjyjx2Txj)i=3nαi21i=3nj=3n(αiαjyiyjxiTxj)
    定义:
    f ( x i ) = ∑ j = 1 n α j y j x i T x j + b f({\bf {x}}_i) = \sum_{j=1}^n\alpha_jy_j{\bf {x}}_i^T{\bf {x}}_j+b f(xi)=j=1nαjyjxiTxj+b v i = ∑ j = 3 n α j y j x i T x j = f ( x i ) − ∑ j = 1 2 α j y j x i T x j − b v_i = \sum_{j=3}^n\alpha_jy_j{\bf {x}}_i^T{\bf {x}}_j=f({\bf {x}}_i) - \sum_{j=1}^2\alpha_jy_j{\bf {x}}_i^T{\bf {x}}_j-b vi=j=3nαjyjxiTxj=f(xi)j=12αjyjxiTxjb
    则目标函数为:
    W ( α 2 ) = α 1 + α 2 − 1 2 ( α 1 2 x 1 T x 1 ) − 1 2 ( α 2 2 x 2 T x 2 ) − α 1 α 2 y 1 y 2 x 1 T x 2 − α 1 y 1 v 1 − α 2 y 2 v 2 + c o n s t a n t W({\alpha}_2) = \alpha_1+\alpha_2 - \frac{1}{2}(\alpha_1^2{\bf {x}}_1^T{\bf {x}}_1) - \frac{1}{2}(\alpha_2^2{\bf {x}}_2^T{\bf {x}}_2) - \alpha_1\alpha_2y_1y_2{\bf {x}}_1^T{\bf {x}}_2 - \alpha_1y_1v_1- \alpha_2y_2v_2 +constant W(α2)=α1+α221(α12x1Tx1)21(α22x2Tx2)α1α2y1y2x1Tx2α1y1v1α2y2v2+constant
    由于 ∑ i = 1 n α i ⋅ y i = 0 \sum_{i=1}^n\alpha_i\cdot y_i = 0 i=1nαiyi=0
    所以 α 1 y 1 + α 2 y 2 = − ∑ i = 3 n α i y i = B \alpha_1y_1+\alpha_2y_2 = -\sum_{i=3}^n\alpha_iy_i = B α1y1+α2y2=i=3nαiyi=B同时乘以 y 1 y_1 y1
    α 1 = B y 1 − y 1 y 2 α 2 = γ − s α 2 \alpha_1 = By_1-y_1y_2\alpha_2=\gamma-s\alpha_2 α1=By1y1y2α2=γsα2
    代入目标函数:
    W ( α 2 ) = γ − s α 2 + α 2 − 1 2 ( γ − s α 2 ) 2 x 1 T x 1 − 1 2 ( α 2 2 x 2 T x 2 ) − ( γ − s α 2 ) α 2 s x 1 T x 2 W({\alpha}_2) = \gamma-s\alpha_2+\alpha_2 - \frac{1}{2}(\gamma-s\alpha_2)^2{\bf {x}}_1^T{\bf {x}}_1 - \frac{1}{2}(\alpha_2^2{\bf {x}}_2^T{\bf {x}}_2)- (\gamma-s\alpha_2)\alpha_2s{\bf {x}}_1^T{\bf {x}}_2 W(α2)=γsα2+α221(γsα2)2x1Tx121(α22x2Tx2)(γsα2)α2sx1Tx2 − ( γ − s α 2 ) y 1 v 1 − α 2 y 2 v 2 + c o n s t a n t - (\gamma-s\alpha_2)y_1v_1- \alpha_2y_2v_2 +constant (γsα2)y1v1α2y2v2+constant
    α 2 \alpha_2 α2求导:
    ∂ W ( α 2 ) α 2 = − s + 1 + γ s x 1 T x 1 − α 2 x 1 T x 1 − α 2 x 2 T x 2 − γ s x 1 T x 2 + 2 α 2 x 1 T x 2 + y 2 v 1 − y 2 v 2 = 0 \frac{\partial W(\alpha_2)}{\alpha_2} = -s+1+\gamma s{\bf {x}}_1^T{\bf {x}}_1-\alpha_2{\bf {x}}_1^T{\bf {x}}_1- \alpha_2{\bf {x}}_2^T{\bf {x}}_2 -\gamma s {\bf {x}}_1^T{\bf {x}}_2+2\alpha_2{\bf {x}}_1^T{\bf {x}}_2+y_2v_1-y_2v_2=0 α2W(α2)=s+1+γsx1Tx1α2x1Tx1α2x2Tx2γsx1Tx2+2α2x1Tx2+y2v1y2v2=0
    s = y 1 y 2 s=y_1y_2 s=y1y2 代入上式,得到:
    α 2 n e w = y 2 [ y 2 − y 1 + γ y 1 ( x 1 T x 1 − x 1 T x 2 ) + v 1 − v 2 ] x 1 T x 1 + x 2 T x 2 − 2 x 1 T x 2 \alpha_2^{new} = \frac{y_2[y_2-y_1+\gamma y_1({\bf {x}}_1^T{\bf {x}}_1-{\bf {x}}_1^T{\bf {x}}_2)+v_1-v_2]}{{\bf {x}}_1^T{\bf {x}}_1+{\bf {x}}_2^T{\bf {x}}_2-2{\bf {x}}_1^T{\bf {x}}_2} α2new=x1Tx1+x2Tx22x1Tx2y2[y2y1+γy1(x1Tx1x1Tx2)+v1v2]
    令:
    E i = f ( x i ) − y i E_i = f(x_i) - y_i Ei=f(xi)yi η = x 1 T x 1 + x 2 T x 2 − 2 x 1 T x 2 \eta = {\bf {x}}_1^T{\bf {x}}_1+{\bf {x}}_2^T{\bf {x}}_2-2{\bf {x}}_1^T{\bf {x}}_2 η=x1Tx1+x2Tx22x1Tx2
    E i E_i Ei 为误差项, η \eta η 为学习速率
    已知:
    γ = α 1 o l d + s α 2 o l d \gamma = \alpha_1^{old} + s\alpha_2^{old} γ=α1old+sα2old v i = ∑ j = 3 n α j y j x i T x j = f ( x i ) − ∑ j = 1 2 α j y j x i T x j − b v_i = \sum_{j=3}^n\alpha_jy_j{\bf {x}}_i^T{\bf {x}}_j=f({\bf {x}}_i) - \sum_{j=1}^2\alpha_jy_j{\bf {x}}_i^T{\bf {x}}_j-b vi=j=3nαjyjxiTxj=f(xi)j=12αjyjxiTxjb
    代入化简最终得到:
    α 2 n e w = α 2 o l d + y 2 ( E 1 − E 2 ) η \alpha_2^{new} = \alpha_2^{old}+\frac{y_2(E_1-E_2)}{\eta} α2new=α2old+ηy2(E1E2)
    根据之前推导的 α 2 \alpha_2 α2 的范围,得到修剪后的 α 2 \alpha_2 α2
    α 2 n e w , c l i p p e d = { H ,    α 2 n e w > H α 2 n e w ,    L ≤ α 2 n e w ≤ H L ,    α 2 n e w < L \alpha_2^{new,clipped} = \left\{\begin{array}{l} H,\;\alpha_2^{new} > H\\[2ex] \alpha_2^{new},\;L\leq\alpha_2^{new} \leq H\\[2ex] L,\;\alpha_2^{new} α2new,clipped=H,α2new>Hα2new,Lα2newHL,α2new<L
    对于 α 1 \alpha_1 α1,由 α 1 \alpha_1 α1 α 2 \alpha_2 α2 的关系,可以得到:
    α 1 n e w = α 1 o l d + y 1 y 2 ( α 2 o l d − α 2 n e w , c l i p p e d ) \alpha_1^{new} = \alpha_1^{old} + y_1y_2(\alpha_2^{old} - \alpha_2^{new,clipped}) α1new=α1old+y1y2(α2oldα2new,clipped)
    由此我们得到了 α \alpha α 的求解公式,接下来看常数 b,我们要根据 α \alpha α 的取值范围,去更正 b 的值,使间隔最大化。当 α 1 n e w \alpha_1^{new} α1new 在 0 和 C 之间的时候,根据 KKT 条件可知,这个点是支持向量上的点。因此,满足下列公式:
    y 1 ( w T x 1 + b ) = 1 y_1({\bf {w}}^T{\bf {x}}_1+b)=1 y1(wTx1+b)=1
    公式两边同时乘以y1得(y1y1=1):
    w T x 1 + b = y 1 {\bf {w}}^T{\bf {x}}_1+b=y_1 wTx1+b=y1 ∑ i = 1 n α i y i x i T x 1 + b = y 1 \sum_{i=1}^{n}\alpha_iy_i{\bf {x}}_i^T{\bf {x}}_1 + b = y_1 i=1nαiyixiTx1+b=y1    ⟹    b 1 n e w = y 1 − α 1 n e w y 1 x 1 T x 1 − α 2 n e w y 2 x 2 T x 1 − ∑ i = 3 n α i y i x i T x 1 \implies b_1^{new}=y_1 - \alpha_1^{new}y_1{\bf {x}}_1^T{\bf {x}}_1-\alpha_2^{new}y_2{\bf {x}}_2^T{\bf {x}}_1-\sum_{i=3}^{n}\alpha_iy_i{\bf {x}}_i^T{\bf {x}}_1 b1new=y1α1newy1x1Tx1α2newy2x2Tx1i=3nαiyixiTx1 = − E 1 + α 1 o l d y 1 x 1 T x 1 + α 2 o l d y 1 x 2 T x 1 + b o l d − α 1 n e w y 1 x 1 T x 1 − α 2 n e w y 2 x 2 T x 1 =-E_1+ \alpha_1^{old}y_1{\bf {x}}_1^T{\bf {x}}_1+ \alpha_2^{old}y_1{\bf {x}}_2^T{\bf {x}}_1 +b^{old}- \alpha_1^{new}y_1{\bf {x}}_1^T{\bf {x}}_1-\alpha_2^{new}y_2{\bf {x}}_2^T{\bf {x}}_1 =E1+α1oldy1x1Tx1+α2oldy1x2Tx1+boldα1newy1x1Tx1α2newy2x2Tx1 = b o l d − E 1 + y 1 x 1 T x 1 ( α 1 o l d − α 1 n e w ) + y 2 x 2 T x 1 ( α 2 o l d − α 2 n e w ) =b^{old}-E_1+y_1{\bf {x}}_1^T{\bf {x}}_1(\alpha_1^{old}-\alpha_1^{new})+y_2{\bf {x}}_2^T{\bf {x}}_1(\alpha_2^{old}-\alpha_2^{new}) =boldE1+y1x1Tx1(α1oldα1new)+y2x2Tx1(α2oldα2new)
    同理可得:
    b 2 n e w = b o l d − E 2 + y 1 x 1 T x 2 ( α 1 o l d − α 1 n e w ) + y 2 x 2 T x 2 ( α 2 o l d − α 2 n e w ) b_2^{new}=b^{old}-E_2+y_1{\bf {x}}_1^T{\bf {x}}_2(\alpha_1^{old}-\alpha_1^{new})+y_2{\bf {x}}_2^T{\bf {x}}_2(\alpha_2^{old}-\alpha_2^{new}) b2new=boldE2+y1x1Tx2(α1oldα1new)+y2x2Tx2(α2oldα2new)
    b 1 b_1 b1 b 2 b_2 b2 都有效的时候,它们是相等的,即:
    b n e w = b 1 n e w = b 2 n e w b^{new} = b_1^{new} = b_2^{new} bnew=b1new=b2new
    当两个乘子都在边界上,则 b b b 阈值和 KKT 条件一致。当不满足的时候,SMO 算法选择他们的中点作为新的阈值:
    b = { b 1 ,    0 < α 1 n e w < C b 2 ,    0 < α 2 n e w < C b 1 + b 2 2 ,    o t h e r w i s e b=\left\{\begin{array}{l} b_1,\; 0<\alpha_1^{new}b=b1,0<α1new<Cb2,0<α2new<C2b1+b2,otherwise

3.2 求解步骤

  1. 计算误差: E i = f ( x i ) − y i = ∑ j = 1 n α j y j x i T x j + b − y i E_i =f({\bf {x}}_i)-y_i = \sum_{j=1}^n\alpha_jy_j{\bf {x}}_i^T{\bf {x}}_j+b - y_i Ei=f(xi)yi=j=1nαjyjxiTxj+byi
  2. 计算上下界 L L L H H H { H = m a x ( 0 , α j o l d − α i o l d ) ,    L = m i n ( C , C + α j o l d − α i o l d ) y 1 ≠ y 2 H = m a x ( 0 , α j o l d + α i o l d − C ) ,    L = m i n ( C , α j o l d + α i o l d ) y 1 = y 2 \left\{\begin{array}{l}H = max(0, \alpha_j^{old}-\alpha_i^{old}), \; L = min(C, C+\alpha_j^{old}-\alpha_i^{old})\qquad y_1\neq y_2\\H = max(0, \alpha_j^{old}+\alpha_i^{old} - C), \; L = min(C, \alpha_j^{old}+\alpha_i^{old})\qquad y_1= y_2 \end{array}\right. {H=max(0,αjoldαiold),L=min(C,C+αjoldαiold)y1=y2H=max(0,αjold+αioldC),L=min(C,αjold+αiold)y1=y2
  3. 计算 η \eta η η = x i T x i + x j T x j − 2 x i T x j \eta = {\bf {x}}_i^T{\bf {x}}_i+{\bf {x}}_j^T{\bf {x}}_j-2{\bf {x}}_i^T{\bf {x}}_j η=xiTxi+xjTxj2xiTxj
  4. 更新 α j \alpha_j αj α j n e w , c l i p p e d = { H ,    α j n e w > H α j o l d + y j ( E i − E j ) η ,    L ≤ α j n e w ≤ H L ,    α j n e w < L \alpha_j^{new,clipped} = \left\{\begin{array}{l} H,\;\alpha_j^{new} > H\\[2ex] \alpha_j^{old}+\frac{y_j(E_i-E_j)}{\eta},\;L\leq\alpha_j^{new} \leq H\\[2ex] L,\;\alpha_j^{new} αjnew,clipped=H,αjnew>Hαjold+ηyj(EiEj),LαjnewHL,αjnew<L
  5. 更新 α i \alpha_i αi α i n e w = α i o l d + y i y j ( α j o l d − α j n e w , c l i p p e d ) \alpha_i^{new} = \alpha_i^{old} + y_iy_j(\alpha_j^{old} - \alpha_j^{new,clipped}) αinew=αiold+yiyj(αjoldαjnew,clipped)
  6. 更新 b 1 b_1 b1 b 2 b_2 b2 b 1 n e w = b o l d − E i + y i x i T x i ( α i o l d − α i n e w ) + y j x j T x i ( α j o l d − α j n e w ) b_1^{new}=b^{old}-E_i+y_i{\bf {x}}_i^T{\bf {x}}_i(\alpha_i^{old}-\alpha_i^{new})+y_j{\bf {x}}_j^T{\bf {x}}_i(\alpha_j^{old}-\alpha_j^{new}) b1new=boldEi+yixiTxi(αioldαinew)+yjxjTxi(αjoldαjnew) b 2 n e w = b o l d − E j + y i x i T x j ( α i o l d − α i n e w ) + y j x j T x j ( α j o l d − α j n e w ) b_2^{new}=b^{old}-E_j+y_i{\bf {x}}_i^T{\bf {x}}_j(\alpha_i^{old}-\alpha_i^{new})+y_j{\bf {x}}_j^T{\bf {x}}_j(\alpha_j^{old}-\alpha_j^{new}) b2new=boldEj+yixiTxj(αioldαinew)+yjxjTxj(αjoldαjnew)
  7. 根据 b 1 b_1 b1 b 2 b_2 b2 更新 b b b b = { b 1 ,    0 < α 1 n e w < C b 2 ,    0 < α 2 n e w < C b 1 + b 2 2 ,    o t h e r w i s e b=\left\{\begin{array}{l} b_1,\; 0<\alpha_1^{new}b=b1,0<α1new<Cb2,0<α2new<C2b1+b2,otherwise

以上推导过程转载自大神的文章:Python3《机器学习实战》学习笔记(八):支持向量机原理篇之手撕线性SVM

3.3 应用简化版 SMO 算法处理小规模数据集

'''SMO 算法中的辅助函数'''

import numpy as np

def loadDataSet(filename):
    dataMat = []
    labelMat = []
    with open(filename) as f:
        for line in f.readlines():
            lineArr = line.strip().split('\t')
            dataMat.append([float(lineArr[0]), float(lineArr[1])])
            labelMat.append(float(lineArr[2]))
    return dataMat, labelMat

def selectJrand(i, m):
    '''
    i: 第一个 alpha 的下标
    m: 所有 alpha 的数目
    '''
    j = i    # j: 输出随机值,但该随机值不等于 i
    while (j==i):
        j = int(np.random.uniform(0, m))
    return j

def clipAlpha(aj, H, L):
    '''
    用于调整 alpha 值,使其小于 H,且大于 L
    '''
    if aj > H:
        aj = H
    if L > aj:
        aj = L
    return aj
'''简化 SMO 算法'''

def smoSimple(dataMatIn, classLabels, C, toler, maxIter):
    '''
    dataMatIn: 数据集
    classLabels: 类别标签
    C: 常数
    toler: 松弛变量
    maxIter: 最大循环次数
    '''
    dataMatrix = np.mat(dataMatIn)    # 数据集转换为矩阵
    labelMat = np.mat(classLabels).T    # 类别标签转换为矩阵
    b = 0    # b 初始化为 0
    m, n = np.shape(dataMatrix)    # 数据集大小
    alphas = np.mat(np.zeros((m, 1)))    # 初始化 alpha 为 0
    iternum = 0    # 在没有任何 alpha 改变的情况下遍历数据集的次数
    while (iternum < maxIter):    # iternum 达到 maxIter 时函数允许结束并退出
        alphaPairsChanged = 0    # 用于记录 alpha 对是否变化
        for i in range(m):    # 第 i 个样本
            y_i = float(np.multiply(alphas, labelMat).T * dataMatrix * dataMatrix[i, :].T) + b    
            # yi = w.T*xi + b, w = sum(aj*yj*xj)
            # (1,1) = (m,1)dot(m,1).T * (m,n) * (1,n).T + (1,1)
            e_i = y_i - float(labelMat[i])    # 步骤1:预测值 i 与实际值 i 的误差
            if ((labelMat[i]*e_i < -toler) and (alphas[i] < C)) or ((labelMat[i]*e_i > toler) and (alphas[i] > 0)):
                # 检查是否满足容错率以及是否在边界上,若在边界上则不需要优化
                j = selectJrand(i, m)    # 随机选择一个 j,与 i 成对更新
                y_j = float(np.multiply(alphas, labelMat).T * (dataMatrix * dataMatrix[j, :].T)) + b    # 计算 yj
                e_j = y_j - float(labelMat[j])    # 步骤1:预测值 j 与实际值 j 的误差
                alpha_i_old = alphas[i].copy()    # 深拷贝得到更新前的值
                alpha_j_old = alphas[j].copy()
                if (labelMat[i] != labelMat[j]):    # 步骤2:计算上下界
                    L = max(0, alphas[j] - alphas[i])
                    H = min(C, C + alphas[j] - alphas[i])
                else:
                    L = max(0, alphas[j] + alphas[i] - C)
                    H = min(C, alphas[j] + alphas[i])
                if L == H:
                    print('L==H')
                    continue
                # 步骤3:计算 eta
                eta = 2.0 * dataMatrix[i,:] * dataMatrix[j,:].T - \
                      dataMatrix[i,:] * dataMatrix[i,:].T - dataMatrix[j,:] * dataMatrix[j,:].T
                if eta >= 0:
                    print('eta>=0')
                    continue
                # 步骤4:更新 alpha_j
                alphas[j] -= labelMat[j] * (e_i - e_j) / eta
                alphas[j] = clipAlpha(alphas[j], H, L)
                if (abs(alphas[j] - alpha_j_old) < 0.00001):
                    print('j not moving enough')
                    continue
                # 步骤5:更新 alpha_i
                alphas[i] += labelMat[i] * labelMat[j] * (alpha_j_old - alphas[j])
                # 步骤6:更新 b1 和 b2
                b1 = b - e_i - labelMat[i] * (alphas[i] - alpha_i_old) * dataMatrix[i,:] * dataMatrix[i,:].T -\
                     labelMat[j] * (alphas[j] - alpha_j_old) * dataMatrix[i,:] * dataMatrix[j,:].T
                b2 = b - e_j - labelMat[i] * (alphas[i] - alpha_i_old) * dataMatrix[i,:] * dataMatrix[j,:].T -\
                     labelMat[j] * (alphas[j] - alpha_j_old) * dataMatrix[j,:] * dataMatrix[j,:].T
                # 步骤7:根据 b1 和 b2 更新 b:
                if (0 < alphas[i]) and (C > alphas[i]):
                    b = b1
                elif (0 < alphas[j]) and (C > alphas[j]):
                    b = b2
                else:
                    b = (b1 + b2) / 2.0
                alphaPairsChanged += 1    # 改变次数 +1
                print('iter: %d i: %d, pairs changed %d' % (iternum, i, alphaPairsChanged))
        if (alphaPairsChanged == 0):    # alpha 未更新,iternum + 1
            iternum += 1
        else:
            iternum = 0    # alpha 更新,iternum = 0
        print('iteration number: %d' % iternum)
    return b, alphas

[In]: dataMat, labelMat = loadDataSet('Ch06/testSet.txt')
[In]: b, alphas = smoSimple(dataMat, labelMat, 0.6, 0.001, 40)
[In]: b
[Out]: matrix([[-3.73041522]])
[In]: alphas[alphas>0]
[Out]: matrix([[0.12876353, 0.23408636, 0.36284989]])

'''计算 w'''
def get_w(dataMat, labelMat, alphas):
    dataMat, labelMat = np.array(dataMat), np.array(labelMat)
    # w = sum(alpha_i*y_i*x_i)
    w = np.multiply(alphas,(labelMat.reshape(100,1))).T * dataMat
    return w

[In]: w = get_w(dataMat, labelMat, alphas)
[Out]: matrix([[ 0.7972711 , -0.28038632]])

划分结果如下图所示:
ML: SVM_第1张图片

4. 利用完整 Platt SMO 算法加速优化

  • 完整的 Platt SMO 算法在速度上会优于简化版
  • 两者唯一的不同点在于 alpha 的选择方式
  • Platt SMO 算法是通过一个外循环来选择第一个 alpha 值的,并且其选择过程会在两种方式之间进行交替:一种方式是在所有数据集上进行单遍扫描,另一种方式则是在非边界 alpha (不等于 0 或 C)中实现单遍扫描。
  • 在选择第一个 alpha 值后,算法会通过一个内循环来选择第二个 alpha 值。在优化过程中,会通过最大化步长的方式来获得第二个 alpha 值。根据 alpha 的更新公式,我们选择使得 Ei-Ej 最大的 alpha 值。

4.1 完整版 Platt SMO 的支持函数

  1. 构建一个仅包含 init 方法的 optStruct 类,将其作为一个数据结构来使用,方便我们对于重要数据的维护
class optStruct:
    def __init__(self, dataMatIn, classLabels, C, toler):
        self.X = dataMatIn
        self.labelMat = classLabels
        self.C = C
        self.tol = toler
        self.m = np.shape(dataMatIn)[0]
        self.alphas = np.mat(np.zeros((self.m, 1)))
        self.b = 0
        self.eCache = np.mat(np.zeros((self.m, 2)))    # 用于缓存误差
  1. 对于给定的 alpha 值,计算 E 值并返回
'''给定 alpha,计算 E'''
def calcEk(oS, k):
    yk = float(np.multiply(oS.alphas, oS.labelMat).T * oS.X * oS.X[k, :].T) + oS.b    
    Ek = yk - float(oS.labelMat[k])
    return Ek
  1. 使用启发式方法选择第二个 alpha
'''使用启发式方法选择第二个 alpha'''
def selectJ(i, oS, Ei):
    maxK = -1
    maxDeltaE = 0
    Ej = 0
    os.eCache[i] = [1, Ei]    # 将 Ei 在缓存中设置为有效
    validEcacheList = np.nonzero(oS.eCache[:,0].A)[0]    # np.nonzero(a)返回数组a中非零元素的索引值元组,这里返回非零 E 对应的索引值
    if len(validEcacheList) > 1:    # 存在有效的 Ei
        for k in validEcacheList:
            if k == i:
                continue
            Ek = calcEk(oS, k)
            deltaE = abs(Ei - Ek)
            if (deltaE > maxDeltaE):
                maxK = k
                maxDeltaE = deltaE
                Ej = Ek
        return maxK, Ej
    else:    # 第一次循环,随机选择一个 alpha
        j = selectJrand(i, oS.m)
        Ej = calcEk(oS, j)
    return j, Ej
  1. 计算误差值并存入缓存,在对 alpha 进行优化后会用到该值
'''计算误差值并存入缓存'''
def updateEk(oS, k):
    Ek = calcEk(oS, k)
    oS.eCache[k] = [1, Ek]

4.2 完整 Platt SMO 算法中的优化例程

  1. 内循环
def innerL(i, oS):
    Ei = calcEk(oS, i)
    if ((oS.labelMat[i] * Ei < -oS.tol) and (oS.alphas[i] < oS.C)) or ((oS.labelMat[i]*Ei > oS.tol) and (oS.alphas[i] > 0)):
        j, Ej = selectJ(i, oS, Ei)    # 使用启发式方法选择第二个 alpha
        alpha_i_old = oS.alphas[i].copy()
        alpha_j_old = oS.alphas[j].copy()
        if (oS.labelMat[i] != oS.labelMat[j]):
            L = max(0, oS.alphas[j] - oS.alphas[i])
            H = min(oS.C, oS.C + oS.alphas[j] - oS.alphas[i])
        else:
            L = max(0, oS.alphas[j] + oS.alphas[i] - oS.C)
            H = min(oS.C, oS.alphas[j] + oS.alphas[i])
        if L == H:
            print('L==H')
            return 0
        eta = 2.0 * oS.X[i,:] * oS.X[j,:].T - \
                oS.X[i,:] * oS.X[i,:].T - oS.X[j,:] * oS.X[j,:].T
        if eta >= 0:
            print('eta>=0')
            return 0
        oS.alphas[j] -= oS.labelMat[j] * (Ei - Ej) / eta
        oS.alphas[j] = clipAlpha(oS.alphas[j], H, L)
        updateEk(oS, j)    # 更新误差缓存
        if (abs(oS.alphas[j] - alpha_j_old) < 0.00001):
            print('j not moving enough')
            return 0
        oS.alphas[i] += oS.labelMat[i] * oS.labelMat[j] * (alpha_j_old - oS.alphas[j])
        updateEk(oS, i)    # 更新误差缓存
        b1 = oS.b - Ei - oS.labelMat[i] * (oS.alphas[i] - alpha_i_old) * oS.X[i,:] * oS.X[i,:].T -\
                oS.labelMat[j] * (oS.alphas[j] - alpha_j_old) * oS.X[i,:] * oS.X[j,:].T
        b2 = oS.b - Ej - oS.labelMat[i] * (oS.alphas[i] - alpha_i_old) * oS.X[i,:] * oS.X[j,:].T -\
                oS.labelMat[j] * (oS.alphas[j] - alpha_j_old) * oS.X[j,:] * oS.X[j,:].T
        if (0 < oS.alphas[i]) and (oS.C > oS.alphas[i]):
            oS.b = b1
        elif (0 < oS.alphas[j]) and (oS.C > oS.alphas[j]):
            oS.b = b2
        else:
            oS.b = (b1 + b2) / 2.0
        return 1
    else:
        return 0
  1. 外循环
'''完整版 Platt SMO 的外循环代码'''

def smoP(dataMatIn, classLabels, C, toler, maxIter, kTup=('lin', 0)):
    oS = optStruct(np.mat(dataMatIn), np.mat(classLabels).T, C, toler)
    iter_num = 0
    entireSet = True
    alphaPairsChanged = 0
    # 迭代次数超过指定最大值,或者遍历整个集合都未对任何 alpha 对进行修改时,退出循环
    while (iter_num < maxIter) and ((alphaPairsChanged > 0) or (entireSet)):
        alphaPairsChanged = 0
        if entireSet:    # 全集遍历
            for i in range(oS.m):
                alphaPairsChanged += innerL(i, oS)
                print('fullSet, iter: %d i: %d, pairs changed %d' % (iter_num, i, alphaPairsChanged))
            iter_num += 1
        else:    # 遍历非边界
            nonBoundIs = np.nonzero((oS.alphas.A > 0) * (oS.alphas.A < C))[0]
            for i in nonBoundIs:
                alphaPairsChanged += innerL(i, oS)
                print('non-bound, iter: %d i: %d, pairs changed %d' % (iter_num, i, alphaPairsChanged))
            iter_num += 1
        if entireSet:    # 一次全集遍历后变为非边界遍历
            entireSet = False
        elif (alphaPairsChanged == 0):    # 如果非边界遍历 alpha 没有更新,重新转换为全集遍历
            entireSet = True
        print('iteration number: %d' % iter_num)
    return oS.b, oS.alphas

[In]: dataArr, labelArr = loadDataSet('Ch06/testSet.txt')
[In]: b, alphas = smoP(dataArr, labelArr, 0.6, 0.001, 40)
[In]: b
[Out]: matrix([[-2.89901748]])
[In]: alphas[alphas > 0]
[Out]: matrix([[0.06961952, 0.0169055 , 0.0169055 , 0.0272699 , 0.04522972,
         0.0272699 , 0.0243898 , 0.06140181, 0.06140181]])

两种方法对比图如下,左图为完整版,右图为简化版
ML: SVM_第2张图片ML: SVM_第3张图片

5. 在复杂数据上应用核函数

  • 前面使用的数据都是线性可分的情况。对于某些非线性可分,但是存在某种可识别的方式,比如分类边界为一个圆,圆内部为一类,圆外部为另一类,对于人眼来说这种情况很容易分辨,但对于分类器,就无法直接识别了。
  • 这种情况下,我们需要使用一种称为核函数的工具将数据转换成易于分类器理解的形式。

5.1 利用核函数将数据映射到高维空间

  • 上述问题的解决方法为将数据从一个特征空间映射到另一个特征空间。一般情况下是从低维特征空间映射到高维空间,该过程通过核函数完成,这里核函数可以看做是一个包装器或者是接口
  • SVM 中的所有运算都可以写成内积,我们可以把内积运算替换成核函数,而不必做简化处理,该方式称为核技巧或者核变电。其含义具体就是:对于核函数 k k k,对所有 x i , x j ∈ X x_i,x_j \in X xi,xjX,满足 k ( x i , x j ) = ⟨ ϕ ( x i ) , ϕ ( x j ) ⟩ k(x_i,x_j)=\langle \phi(x_i),\phi(x_j)\rangle k(xi,xj)=ϕ(xi),ϕ(xj),其中 ϕ ( x ) \phi(x) ϕ(x) 为映射函数,这样避免了先计算映射值,再计算内积这种复杂运算。

5.2 径向基核函数

  • 对于简单的例子,我们可以手动构造对应映射的核函数,如果对于任意一个映射,要构造出对应的核函数就很困难了。因此,通常,人们会从一些常用的核函数中进行选择,根据问题和数据的不同,选择不同的参数,得到不同的核函数。其中一个非常流行的核函数就是径向基核函数。径向基函数的高斯版本如下:
    k ( x , y ) = e x p ( − ∣ ∣ x − y ∣ ∣ 2 2 σ 2 ) k(x, y) = exp\left(\frac{-||x-y||^2}{2\sigma^2}\right) k(x,y)=exp(2σ2xy2)其中, σ \sigma σ 是用户自定义的用于确定到达率(reach)或者说函数值跌落到0的速度参数。
  • 核转换函数:该函数定义为计算单条数据的核函数值,然后在数据结构类中添加核函数值属性,并且在该类中计算每条数据的核函数值。最终的核函数值矩阵大小为(m, m)
'''核转换函数'''
import numpy as np

def kernelTrans(X, A, kTup):
    '''
    X: 输入矩阵
    A: 单个数据的向量
    kTup: 核函数的信息,为二元元组,第一个元素为核函数类型,第二个元素为核函数的可选参数
    
    Return 转换结果 K
    '''
    m, n = np.shape(X)
    K = np.mat(np.zeros((m, 1)))
    if kTup[0] == 'lin':    # 线性核函数,只进行内积。
        K = X * A.T
    elif kTup[0] == 'rbf':    # 高斯核函数,根据高斯核函数公式进行计算
        for j in range(m):
            deltaRow = X[j,:] - A
            K[j] = deltaRow * deltaRow.T
        K = np.exp(K / (-1*kTup[1]**2))
    else:
        print('raise NameError(''Houston We Have a Problem -- That Kernel is not recognized'')')
    return K

class optStruct:
    def __init__(self, dataMatIn, classLabels, C, toler, kTup):
        self.X = dataMatIn
        self.labelMat = classLabels
        self.C = C
        self.tol = toler
        self.m = np.shape(dataMatIn)[0]
        self.alphas = np.mat(np.zeros((self.m, 1)))
        self.b = 0
        self.eCache = np.mat(np.zeros((self.m, 2)))    # 用于缓存误差
        self.K = np.mat(np.zeros((self.m, self.m)))    # 初始化核 K
        for i in range(self.m):    # 计算所有数据的核 K
            self.K[:, i] = kernelTrans(self.X, self.X[i, :], kTup)
  • 使用了核转换函数后,之前有针对 X 的内积运算都需要修改,包括两个函数,内循环函数和 Ek 计算函数:
'''使用核函数的 innerL() 和 calcEk()'''

def innerL(i, oS):
    Ei = calcEk(oS, i)
    if ((oS.labelMat[i] * Ei < -oS.tol) and (oS.alphas[i] < oS.C)) or ((oS.labelMat[i]*Ei > oS.tol) and (oS.alphas[i] > 0)):
        j, Ej = selectJ(i, oS, Ei)    # 使用启发式方法选择第二个 alpha
        alpha_i_old = oS.alphas[i].copy()
        alpha_j_old = oS.alphas[j].copy()
        if (oS.labelMat[i] != oS.labelMat[j]):
            L = max(0, oS.alphas[j] - oS.alphas[i])
            H = min(oS.C, oS.C + oS.alphas[j] - oS.alphas[i])
        else:
            L = max(0, oS.alphas[j] + oS.alphas[i] - oS.C)
            H = min(oS.C, oS.alphas[j] + oS.alphas[i])
        if L == H:
            print('L==H')
            return 0
        # 原来为eta = 2.0 * oS.X[i,:] * oS.X[j,:].T - oS.X[i,:] * oS.X[i,:].T - oS.X[j,:] * oS.X[j,:].T
        eta = 2.0 * oS.K[i,j] - oS.K[i,i] - oS.K[j,j]
        if eta >= 0:
            print('eta>=0')
            return 0
        oS.alphas[j] -= oS.labelMat[j] * (Ei - Ej) / eta
        oS.alphas[j] = clipAlpha(oS.alphas[j], H, L)
        updateEk(oS, j)    # 更新误差缓存
        if (abs(oS.alphas[j] - alpha_j_old) < 0.00001):
            print('j not moving enough')
            return 0
        oS.alphas[i] += oS.labelMat[i] * oS.labelMat[j] * (alpha_j_old - oS.alphas[j])
        updateEk(oS, i)    # 更新误差缓存
        # b1 = oS.b - Ei - oS.labelMat[i] * (oS.alphas[i] - alpha_i_old) * oS.X[i,:] * oS.X[i,:].T -\
                # oS.labelMat[j] * (oS.alphas[j] - alpha_j_old) * oS.X[i,:] * oS.X[j,:].T
        # b2 = oS.b - Ej - oS.labelMat[i] * (oS.alphas[i] - alpha_i_old) * oS.X[i,:] * oS.X[j,:].T -\
                # oS.labelMat[j] * (oS.alphas[j] - alpha_j_old) * oS.X[j,:] * oS.X[j,:].T
        b1 = oS.b - Ei - oS.labelMat[i] * (oS.alphas[i] - alpha_i_old) * oS.K[i,i] -\
                oS.labelMat[j] * (oS.alphas[j] - alpha_j_old) * oS.K[i,j]
        b2 = oS.b - Ej - oS.labelMat[i] * (oS.alphas[i] - alpha_i_old) * oS.K[i,j] -\
                oS.labelMat[j] * (oS.alphas[j] - alpha_j_old) * oS.K[j,j]
        if (0 < oS.alphas[i]) and (oS.C > oS.alphas[i]):
            oS.b = b1
        elif (0 < oS.alphas[j]) and (oS.C > oS.alphas[j]):
            oS.b = b2
        else:
            oS.b = (b1 + b2) / 2.0
        return 1
    else:
        return 0
    
def calcEk(oS, k):
    # yk = float(np.multiply(oS.alphas, oS.labelMat).T * oS.X * oS.X[k, :].T) + oS.b
    yk = float(np.multiply(oS.alphas, oS.labelMat).T * oS.K[:, k]) + oS.b
    Ek = yk - float(oS.labelMat[k])
    return Ek

5.3 在测试中使用核函数

'''利用核函数进行分类的径向基测试函数'''
def testRbf(k1=1.3):
    dataArr, labelArr = loadDataSet('Ch06/testSetRBF.txt')
    b, alphas = smoP(dataArr, labelArr, 200, 0.0001, 10000, ('rbf', k1))
    dataMat = np.mat(dataArr)
    labelMat = np.mat(labelArr).T
    svInd = np.nonzero(alphas.A > 0)[0]
    sVs = dataMat[svInd]
    labelSV = labelMat[svInd]
    print('there are %d Support Vectors' % np.shape(sVs)[0])
    m, n = np.shape(dataMat)
    errorCount = 0
    # 利用核函数进行分类
    for i in range(m):
        kernelEval = kernelTrans(sVs, dataMat[i,:], ('rbf', k1))    # 得到转换后的数据
        predict = kernelEval.T * np.multiply(labelSV, alphas[svInd]) + b    # 计算预测值
        if np.sign(predict) != np.sign(labelArr[i]):
            errorCount += 1
    print('the training error rate is :%f' % (float(errorCount) / m))
    # 对测试集进行分类
    dataArr, labelArr = loadDataSet('Ch06/testSetRBF2.txt')
    errorCount = 0
    dataMat = np.mat(dataArr)
    labelMat = np.mat(labelArr).T
    m, n = np.shape(dataMat)
    for i in range(m):
        kernelEval = kernelTrans(sVs, dataMat[i,:], ('rbf', k1))    # 得到转换后的数据
        predict = kernelEval.T * np.multiply(labelSV, alphas[svInd]) + b    # 计算预测值
        if np.sign(predict) != np.sign(labelArr[i]):
            errorCount += 1
    print('the test error rate is :%f' % (float(errorCount) / m))

[In]: testRbf()
[Out]: there are 26 Support Vectors
[Out]: the training error rate is :0.090000
[Out]: the test error rate is :0.180000
  • 改变 k1 的值,可以发现 k1 越小,支持向量越多,训练错误率越低,但测试错误率升高,即出现过拟合。k1 存在一个最优值,对于不同类型的数据集,该值可能差异很大

参考资料:

  • [1] 机器学习实战第六章
  • [2] Python3《机器学习实战》学习笔记(八):支持向量机原理篇之手撕线性SVM
  • [3] Python3《机器学习实战》学习笔记(九):支持向量机实战篇之再撕非线性SVM

你可能感兴趣的:(机器学习)