python机器学习案例系列教程——支持向量机SVM、核函数

时间:2021-11-18 13:50:33

全栈工程师开发手册 (作者:栾鹏)

python数据挖掘系列教程

线性函数、线性回归、线性分类

参考:http://blog.csdn.net/luanpeng825485697/article/details/78933084

线性回归:

线性回归就是计算回归系数,通过回归系数线性组合属性预测结果值。

f ( x ) = w T x + b

二分类的线性分类:

只不过在线性回归的基础上条件了大于和小于0的判断。

y = { 1 , f(x) 0 1 , f(x)<0

使用线性分类时存在线性可分和线性不可分。

比如:下图为线性可分的

python机器学习案例系列教程——支持向量机SVM、核函数

当然,也存在着许多线性不可分的情况,例如下图所示

python机器学习案例系列教程——支持向量机SVM、核函数

即使是线性可分的,分类边界也不一定是确定的。比如下面的几个分类边界都是可以实现分类的,哪个更好呢。

python机器学习案例系列教程——支持向量机SVM、核函数

这就需要用到支持向量机SVM算法了。

支持向量机/SVM

SVM(Support Vector Machines)是分类算法中应用广泛、效果不错的一类。由简至繁SVM可分类为三类:线性可分(linear SVM in linearly separable case)的线性SVM、线性不可分的线性SVM、非线性(nonlinear)SVM。

1. 线性可分

对于二类分类问题,训练集 T = ( x 1 , y 1 ) , ( x 2 , y 2 ) , , ( x N , y N ) ,其中 x i 为输入样本向量,类别 y i 1 , 1 ,线性SVM通过学习得到分离超平面(hyperplane):

w x + b = 0

以及相应的分类决策函数:

f ( x ) = s i g n ( w x + b )

其中 s i g n 为符号函数,将正数映射为1,负数映射为-1。

有如下图所示的分离超平面,哪一个超平面的分类效果更好呢?

python机器学习案例系列教程——支持向量机SVM、核函数

直观上,超平面B1的分类效果更好一些。将距离分离超平面最近的两个不同类别的样本点称为支持向量(support vector)的,构成了两条平行于分离超平面的长带,二者之间的距离称之为margin。显然,margin更大,则分类正确的确信度更高(与超平面的距离表示分类的确信度,距离越远则分类正确的确信度越高)。通过计算得到(计算过程参考《统计学习方法》115页)

m a r g i n = 2 w

从上图中可观察到:margin以外的样本点对于确定分离超平面没有贡献,换句话说,SVM是有很重要的训练样本(支持向量)所确定的。至此,SVM分类问题可描述为在全部分类正确的情况下,最大化 2 w (等价于最小化 0.5 w 2 );线性分类的约束最优化问题转化为如下的凸优化问题。

min w , b 1 2 | | w | | 2
s . t . y i ( w x i + b ) 1 0

在线性可分情况下,训练数据集的样本点中与分离超平面距离最近的样本点的实例称为支持向量。

支持向量满足

y i ( w x i + b ) 1 = 0
,即正样本点满足
w x i + b = 1
,负样本点满足
w x i + b = 1

2. 线性支持向量机

线性可分是理想情形,大多数情况下,由于噪声或特异点等各种原因,训练样本是线性不可分的。因此,需要更一般化的学习算法。

这时我们就可以通过引入所谓的松弛变量(slack variable),来允许有些数据点可以处于超平面的错误的一侧。这样我们的优化目标就能保持仍然不变,但是此时我们的约束条件有所改变。

凸优化问题

(a)无约束优化问题,可以写为:

m i n f ( x )

(b)有等式约束的优化问题,可以写为:

m i n f ( x ) s . t . h i ( x ) = 0 , i = 0 , 1 , 2... n

(c)有不等式约束的优化问题,可以写为:

m i n f ( x ) s . t . g i ( x ) 0 , i = 0 , 1 , 2... n h j ( x ) = 0 , j = 0 , 1 , 2... m

对于第(a)类的优化问题,尝尝使用的方法就是费马大定理(Fermat),即使用求取函数f(x)的导数,然后令其为零,可以求得候选最优值,再在这些候选值中验证;如果是凸函数,可以保证是最优解。这也就是我们高中经常使用的求函数的极值的方法。

对于第(b)类的优化问题,常常使用的方法就是拉格朗日乘子法(Lagrange Multiplier) ,即把等式约束h_i(x)用一个系数与f(x)写为一个式子,称为拉格朗日函数,而系数称为拉格朗日乘子。通过拉格朗日函数对各个变量求导,令其为零,可以求得候选值集合,然后验证求得最优值。

对于第(c)类的优化问题,常常使用的方法就是KKT条件。同样地,我们把所有的等式、不等式约束与f(x)写为一个式子,也叫拉格朗日函数,系数也称拉格朗日乘子,通过一些条件,可以求出最优值的必要条件,这个条件称为KKT条件。

必要条件和充要条件如果不理解,可以看下面这句话:

  • 事件M的必要条件就是M可以推出的结论
  • 事件M的充分条件就是可以推出M的前提

了解到这些,现在让我们再看一下我们的最优化问题:

min w 1 2 | | w | | 2 s . t . y i ( w T x i + b ) 1 , i = 0 , 1 , 2... n

现在,我们的这个对优化问题属于哪一类?很显然,它属于第(c)类问题。因为,在学习求解最优化问题之前,我们还要学习两个东西:拉格朗日函数和KKT条件。

拉格朗日函数

首先,我们先要从宏观的视野上了解一下拉格朗日对偶问题出现的原因和背景。

我们知道我们要求解的是最小化问题,所以一个直观的想法是如果我能够构造一个函数,使得该函数在可行解区域内与原目标函数完全一致,而在可行解区域外的数值非常大,甚至是无穷大,那么这个没有约束条件的新目标函数的优化问题就与原来有约束条件的原始目标函数的优化问题是等价的问题。这就是使用拉格朗日方程的目的,它将约束条件放到目标函数中,从而将有约束优化问题转换为无约束优化问题。

随后,人们又发现,使用拉格朗日获得的函数,使用求导的方法求解依然困难。进而,需要对问题再进行一次转换,即使用一个数学技巧:拉格朗日对偶。
所以,显而易见的是,我们在拉格朗日优化我们的问题这个道路上,需要进行下面二个步骤:

  • 将有约束的原始目标函数转换为无约束的新构造的拉格朗日目标函数

  • 使用拉格朗日对偶性,将不易求解的优化问题转化为易求解的优化

下面是拉格朗日对偶问题的公式,不想推导的记住就行了。

想看推导的参考:https://www.zhihu.com/question/58584814


我们考虑优化问题如下,记作问题(P)。

z = min x f ( x ) s . t . g i ( x ) 0 ,     i = 1 , , m , x X

问题(P)的拉格朗日对偶问题(D)写作

v = max α 0 min x X f ( x ) + α T g ( x ) L ( x , α )

其中的函数L(x,α)就是我们熟知的拉格朗日函数。


下面,先将有约束的原始目标函数转换为无约束的新构造的拉格朗日目标函数

公式变形如下:

L ( w , b , α ) = 1 2 | | w | | 2 i = 1 n α i ( y i ( w T x i + b ) 1 )

其中 α i 是拉格朗日乘子, α i 大于等于0,是我们构造新目标函数时引入的系数变量。

其中 α i 是拉格朗日乘子, α i 大于等于0,是我们构造新目标函数时引入的系数变量(我们自己设置)。现在我们令:

θ ( w ) = max α i 0 L ( w , b , α )

当样本点不满足约束条件时,即在可行解区域外:

y i ( w T x i + b ) < 1

此时,我们将 α i 设置为正无穷,此时 θ ( w ) 显然也是正无穷。

当样本点满足约束条件时,即在可行解区域内:

y i ( w T x i + b ) 1

此时,我们设在 y i ( w T x i + b ) > 1 时的 α i 为0,在 y i ( w T x i + b ) = 1 时(也就是支持向量点)的 α i 为大于0的数,那么显然 θ ( w ) 为原目标函数本身。我们将上述两种情况结合一下,就得到了新的目标函数:

θ ( w ) = { 1 2 | | w | | 2 , x在可行区域 + x在非可行区域

此时,再看我们的初衷,就是为了建立一个在可行解区域内与原目标函数相同,在可行解区域外函数值趋近于无穷大的新函数,现在我们做到了。

现在,我们的问题变成了求新目标函数的最小值,即:

p = min w , b θ ( w ) = min w , b max α i 0 L ( w , b , α )

这里用p*表示这个问题的最优值,且和最初的问题是等价的。

接下来,我们进行第二步:将不易求解的优化问题转化为易求解的优化

我们看一下我们的新目标函数,先求最大值,再求最小值。这样的话,我们首先就要面对带有需要求解的参数w和b的方程,而 α i 又是不等式约束,这个求解过程不好做。所以,我们需要使用拉格朗日函数对偶性,将最小和最大的位置交换一下,这样就变成了:

d = max α 0 min w , b L ( w , b , α )

交换以后的新问题是原始问题的对偶问题,这个新问题的最优值用 d 来表示。而且 d p 。我们关心的是 d = p 的时候,这才是我们要的解。需要什么条件才能让 d = p 呢?

  • 首先必须满足这个优化问题是凸优化问题。
  • 其次,需要满足KKT条件。

凸优化问题的定义是:求取最小值的目标函数为凸函数的一类优化问题。目标函数是凸函数我们已经知道,这个优化问题又是求最小值。所以我们的最优化问题就是凸优化问题。

接下里,就是探讨是否满足KKT条件了。

KKT条件

我们已经使用拉格朗日函数对我们的目标函数进行了处理,生成了一个新的目标函数。通过一些条件,可以求出最优值的必要条件,这个条件就是接下来要说的KKT条件。一个最优化模型能够表示成下列标准形式:

min x f ( x ) s . t . h j ( x ) = 0 , j = 1 , 2 , 3... p , s . t . g k ( x ) 0 , k = 1 , 2 , . . . . , q , x X R n

KKT条件的全称是Karush-Kuhn-Tucker条件,KKT条件是说最优值条件必须满足以下条件:

  • 条件一:经过拉格朗日函数处理之后的新目标函数 L ( w , b , α ) 对x求导为零:
  • 条件二: h ( x ) = 0
  • 条件三: α g ( x ) = 0

对于我们的优化问题:

min w 1 2 | | w | | 2 s . t . y i ( w T x i + b ) 1 , i = 0 , 1 , 2... n

显然,条件二已经满足了。另外两个条件为啥也满足呢?

这里原谅我省略一系列证明步骤,感兴趣的可以移步这里:点我查看

这里已经给出了很好的解释。现在,凸优化问题和KKT都满足了,问题转换成了对偶问题。而求解这个对偶学习问题,可以分为三个步骤:首先要让L(w,b,α)关于w和b最小化,然后求对α的极大,最后利用SMO算法求解对偶问题中的拉格朗日乘子。现在,我们继续推导。

对偶问题求解

第一步:根据上述推导已知:

L ( w , b , α ) = 1 2 | | w | | 2 i = 1 n α i ( y i ( w T x i + b ) 1 )

首先固定 α ,要让 L ( w , b , α ) 关于 w b 最小化,我们分别对 w b 偏导数,令其等于0,即:

d L d w = 0 w = i = 1 n α i y i x i

d L d b = 0 i = 1 n α i y i = 0

将上述结果带回 L ( w , b , α ) 得到

L ( w , b , α ) = i = 1 n α i 0.5 i , j = 1 n α i α j y i y j x i T x j

从上面的最后一个式子,我们可以看出,此时的 L ( w , b , α ) 函数只含有一个变量,即 α i

第二步:

现在内侧的最小值求解完成,我们求解外侧的最大值,从上面的式子得到

max α i = 1 n α i 1 2 i , j = 1 n α i α j y i y j x i T x j s . t . α i 0 , i = 1 , 2... n , i = 1 n α i y i = 0

现在我们的优化问题变成了如上的形式。对于这个问题,我们有更高效的优化算法,即序列最小优化(SMO)算法。我们通过这个优化算法能得到α,再根据α,我们就可以求解出w和b,进而求得我们最初的目的:找到超平面,即”决策平面”。

而上式可以等价于

min α 1 2 i , j = 1 n α i α j y i y j x i T x j i = 1 n α i s . t . α i 0 , i = 1 , 2... n , i = 1 n α i y i = 0

总结一句话:我们为啥使出吃奶的劲儿进行推导?因为我们要将最初的原始问题,转换到可以使用SMO算法求解的问题,这是一种最流行的求解方法。

SMO算法

SMO算法的目标是求出一系列 α i b ,一旦求出了这些 α i ,就很容易计算出权重向量w并得到分隔超平面。

SMO算法的工作原理是:每次循环中选择两个 α i 进行优化处理。一旦找到了一对合适的 α i ,那么就增大其中一个同时减小另一个。这里所谓的”合适”就是指两个 α i 必须符合以下两个条件,条件之一就是两个 α i 必须要在间隔边界之外,而且第二个条件则是这两个 α i 还没有进行过区间化处理或者不在边界上。

SMO算法的解法

先来定义特征到结果的输出函数为:

u = w T x + b

接着,我们回忆一下原始优化问题,如下:

min w 1 2 | | w | | 2 s . t . y i ( w T x i + b ) 1 , i = 0 , 1 , 2... n

求导得:

w = i = 1 n α i y i x i

将上述公式带入输出函数中:

u = i = 1 n α i y i x i T x + b

与此同时,拉格朗日对偶后得到最终的目标化函数:

min α 1 2 i , j = 1 n α i α j y i y j x i T x j i = 1 n α i s . t . α i 0 , i = 1 , 2... n , i = 1 n α i y i = 0

线性支持下的SMO

实际上,对于上述目标函数,是存在一个假设的,即数据100%线性可分。但是,目前为止,我们知道几乎所有数据都不那么”干净”。这时我们就可以通过引入所谓的松弛变量(slack variable),来允许有些数据点可以处于超平面的错误的一侧。这样我们的优化目标就能保持仍然不变,但是此时我们的约束条件有所改变:

s . t . C α i 0 , i = 1 , 2... n , i = 1 n α i y i = 0

根据KKT条件可以得出其中 α i 取值的意义为:

α i = 0 y i u i 1

0 < α i < C y i u i = 1

α i = C y i u i 1

  • 对于第1种情况,表明 α i 是正常分类,在边界内部;
  • 对于第2种情况,表明 α i 是支持向量,在边界上;
  • 对于第3种情况,表明 α i 是在两条边界之间。

而最优解需要满足KKT条件,即上述3个条件都得满足,以下几种情况出现将会不满足

y i u i 1 α i < C

y i u i 1 α i > 0

y i u i = 1 α i = 0 α i = C

也就是说,如果存在不能满足KKT条件的 α i ,那么需要更新这些 α i ,这是第一个约束条件。此外,更新的同时还要受到第二个约束条件的限制,即:

i = 1 n α i y i = 0

因为这个条件,我们同时更新两个 α 值,因为只有成对更新,才能保证更新之后的值仍然满足和为0的约束,假设我们选择的两个乘子为 α 1 α 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 = ζ

其中, ζ 为常数。因为两个因子不好同时求解,所以可以先求第二个乘子 α 2 的解 ( α 2 n e w ) ,得到 α 2 的解 ( α 2 n e w ) 之后,再用 α 2 的解 ( α 2 n e w ) 表示 α 1 的解 ( α 1 n e w ) 。为了求解 ( α 2 n e w ) ,得先确定 ( α 2 n e w ) 的取值范围。假设它的上下边界分别为H和L,那么有:

L α 2 n e w H

接下来,综合下面两个条件:

C α i 0 , i = 1 , 2... 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 = ζ

当y1不等于y2时,即一个为正1,一个为负1的时候,可以得到:

α 1 o l d α 2 o l d = ζ

所以有:

L = m a x ( 0 , ζ ) , H = m i n ( C , C ζ )

此时,取值范围如下图所示:

python机器学习案例系列教程——支持向量机SVM、核函数

当y1等于y2时,即两个都为正1或者都为负1,可以得到:

α 1 o l d + α 2 o l d = ζ

所以有:

L = m a x ( 0 , ζ C ) , H = m i n ( C , ζ )

此时,取值范围如下图所示:

python机器学习案例系列教程——支持向量机SVM、核函数

如此,根据y1和y2异号或同号,可以得出 α 2 n e w 的上下界分别为:

L = m a x ( 0 , α 2 o l d α 1 o l d ) , H = m i n ( C , C + α 2 o l d α 1 o l d ) i f y 1 y 2

L = m a x ( 0 , α 1 o l d + α 2 o l d C ) , H = m i n ( C , α 1 o l d + α 2 o l d ) i f y 1 = y 2

这个界限就是编程的时候需要用到的。已经确定了边界,接下来,就是推导迭代式,用于更新 α 值。

我们已经知道,更新 α 的边界,接下来就是讨论如何更新 α 值。我们依然假设选择的两个乘子为 α 1 α 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

α 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 )

这样,我们就知道了怎样计算 α 1 α 2 了,也就是如何对选择的α进行更新。

我们要根据 α 的取值范围,去更正b的值,使间隔最大化。当 α 1 n e w 在0和C之间的时候,根据KKT条件可知,这个点是支持向量上的点。因此,满足下列公式:

y 1 ( w T x 1 + b ) = 1

公式两边同时乘以 y 1 得( y 1 × y 1 = 1 ):

i = 1 n α i y i x i x 1 + b = y 1

因为我们是根据 α 1 α 2 的值去更新b,所以单独提出i=1和i=2的时候,整理可得:

b 1 n e w = y 1 i = 3 n α i y i x i T x 1 α 1 n e w y 1 x 1 T x 1 α 2 n e w y 2 x 2 T x 1

其中前两项为:

y 1 i = 3 n α i y i x i T x 1 = E 1 + α 1 o l d y 1 x 1 T x 1 + α 2 o l d y 2 x 2 T x 1 + b o l d

将上述两个公式,整理得:

b 1 n e w = b o l d E 1 y 1 ( α 1 n e w α 1 o l d ) x 1 T x 1 y 2 ( α 2 n e w α 2 o l d ) x 2 T x 1

同理可得 b 2 n e w 为:

b 2 n e w = b o l d E 2 y 1 ( α 1 n e w α 1 o l d ) x 1 T x 2 y 2 ( α 2 n e w α 2 o l d ) x 2 T x 2

当我们更新了 α 1 α 2 之后,需要重新计算阈值b,因为b关系到了我们 f ( x ) 的计算,也就关系到了误差Ei的计算。

b = { b 1 , 0 < α 1 n e w < C b 2 , 0 < α 2 n e w < C H ( b 1 + b 2 ) / 2 , o t h e r w i s e

现在,让我们梳理下SMO算法的步骤:

步骤1:计算误差:

E i = f ( x i ) y i = j = 1 n α j y j x i T x j + b y i

步骤2:计算上下界L和H:

L = m a x ( 0 , α j o l d α i o l d ) , H = m i n ( C , C + α j o l d α i o l d ) i f y i y j

L = m a x ( 0 , α j o l d + α i o l d C ) , H = m i n ( C , α j o l d + α i o l d ) i f y j = y i

步骤3:计算 η

η = x i T x i + x j T x j 2 x i T x j

步骤4:更新 α j

α j n e w = α j o l d + y j ( E i E j ) η

步骤5:根据取值范围修剪 α j

α j n e w , c l i p p e d = { H , α j n e w > H α j n e w , L α j n e w H L , α j n e w < L

步骤6:更新 α 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 )

步骤7:更新b1和b2:

b 1 n e w = b o l d E i y i ( α i n e w α i o l d ) x i T x i y j ( α j n e w α j o l d ) x j T x i

b 2 n e w = b o l d E j y i ( α i n e w α i o l d ) x i T x j y j ( α j n e w α j o l d ) x j T x j

步骤8:根据b1和b2更新b:

b = { b 1 , 0 < α 1 n e w < C b 2 , 0 < α 2 n e w < C H ( b 1 + b 2 ) / 2 , o t h e r w i s e

python实现

样本集下载:https://github.com/626626cdllp/data-mining/blob/master/SVM/testSet.txt

# -*- coding:UTF-8 -*-
import matplotlib.pyplot as plt
import numpy as np
import random


# 简化版smo


# 函数说明:读取数据
def loadDataSet(fileName):
    alldata = np.loadtxt(fileName)
    dataMat = alldata[:,0:2]   #添加数据
    labelMat = alldata[:,2]   #.astype(int).reshape(-1,1) #添加标签
    return dataMat,labelMat


""" 函数说明:随机选择alpha Parameters: i - alpha_i的索引值 m - alpha参数个数 Returns: j - alpha_j的索引值 """
def selectJrand(i, m):
    j = i                                 #选择一个不等于i的j
    while (j == i):
        j = int(random.uniform(0, m))
    return j

""" 函数说明:修剪alpha Parameters: aj - alpha_j值 H - alpha上限 L - alpha下限 Returns: aj - alpah值 """
def clipAlpha(aj,H,L):
    if aj > H:
        aj = H
    if L > aj:
        aj = L
    return aj


# 函数说明:数据可视化
def showDataSet(dataMat, labelMat):

    place_plus = np.where(labelMat==1)[0]   # 正样本的位置
    place_minus = np.where(labelMat==-1)[0]  # 负样本的位置
    data_plus = dataMat[place_plus]    #正样本
    data_minus = dataMat[place_minus]  #负样本

    plt.scatter(np.transpose(data_plus)[0], np.transpose(data_plus)[1])   #正样本散点图
    plt.scatter(np.transpose(data_minus)[0], np.transpose(data_minus)[1]) #负样本散点图
    plt.show()


""" 函数说明:简化版SMO算法 Parameters: dataMatIn - 数据矩阵 classLabels - 数据标签 C - 松弛变量 toler - 容错率 maxIter - 最大迭代次数 """
def smoSimple(dataMatIn, classLabels, C, toler, maxIter):
    #转换为numpy的mat存储
    dataMatrix = np.mat(dataMatIn)
    labelMat = np.mat(classLabels).transpose()
    #初始化b参数,统计dataMatrix的维度
    b = 0; m,n = np.shape(dataMatrix)
    #初始化alpha参数,设为0
    alphas = np.mat(np.zeros((m,1)))
    #初始化迭代次数
    iter_num = 0
    #最多迭代matIter次
    while (iter_num < maxIter):
        alphaPairsChanged = 0
        for i in range(m):
            #步骤1:计算误差Ei
            fXi = float(np.multiply(alphas,labelMat).T*(dataMatrix*dataMatrix[i,:].T)) + b
            Ei = fXi - float(labelMat[i])
            #优化alpha,设定一定的容错率。
            if ((labelMat[i]*Ei < -toler) and (alphas[i] < C)) or ((labelMat[i]*Ei > toler) and (alphas[i] > 0)):
                #随机选择另一个与alpha_i成对优化的alpha_j
                j = selectJrand(i,m)
                #步骤1:计算误差Ej
                fXj = float(np.multiply(alphas,labelMat).T*(dataMatrix*dataMatrix[j,:].T)) + b
                Ej = fXj - float(labelMat[j])
                #保存更新前的aplpha值,使用深拷贝
                alphaIold = alphas[i].copy(); alphaJold = alphas[j].copy();
                #步骤2:计算上下界L和H
                if (labelMat[i] != labelMat[j]):
                    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]*(Ei - Ej)/eta
                #步骤5:修剪alpha_j
                alphas[j] = clipAlpha(alphas[j],H,L)
                if (abs(alphas[j] - alphaJold) < 0.00001): print("alpha_j变化太小"); continue
                #步骤6:更新alpha_i
                alphas[i] += labelMat[j]*labelMat[i]*(alphaJold - alphas[j])
                #步骤7:更新b_1和b_2
                b1 = b - Ei- labelMat[i]*(alphas[i]-alphaIold)*dataMatrix[i,:]*dataMatrix[i,:].T - labelMat[j]*(alphas[j]-alphaJold)*dataMatrix[i,:]*dataMatrix[j,:].T
                b2 = b - Ej- labelMat[i]*(alphas[i]-alphaIold)*dataMatrix[i,:]*dataMatrix[j,:].T - labelMat[j]*(alphas[j]-alphaJold)*dataMatrix[j,:]*dataMatrix[j,:].T
                #步骤8:根据b_1和b_2更新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
                #打印统计信息
                print("第%d次迭代 样本:%d, alpha优化次数:%d" % (iter_num,i,alphaPairsChanged))
        #更新迭代次数
        if (alphaPairsChanged == 0): iter_num += 1
        else: iter_num = 0
        print("迭代次数: %d" % iter_num)
    return b,alphas

""" 函数说明:分类结果可视化 Parameters: dataMat - 数据矩阵 w - 直线法向量 b - 直线解决 """
def showClassifer(dataMat, w, b):
    # 绘制样本点
    place_plus = np.where(labelMat==1)[0]   # 正样本的位置
    place_minus = np.where(labelMat==-1)[0]  # 负样本的位置

    data_plus = dataMat[place_plus]    #正样本
    data_minus = dataMat[place_minus]  #负样本

    plt.scatter(np.transpose(data_plus)[0], np.transpose(data_plus)[1],s=30, alpha=0.7)   #正样本散点图
    plt.scatter(np.transpose(data_minus)[0], np.transpose(data_minus)[1], s=30, alpha=0.7) #负样本散点图


    #绘制直线
    x1 = max(dataMat[:,0])  # 第一个属性的最大值
    x2 = min(dataMat[:,0])  # 第一个属性的最小值
    a1, a2 = w
    b = float(b)
    a1 = float(a1[0])
    a2 = float(a2[0])
    y1, y2 = (-b- a1*x1)/a2, (-b - a1*x2)/a2
    plt.plot([x1, x2], [y1, y2])
    #找出支持向量点
    for i, alpha in enumerate(alphas):
        if abs(alpha) > 0:
            x, y = dataMat[i]
            plt.scatter([x], [y], s=150, c='none', alpha=0.7, linewidth=1.5, edgecolor='red')
    plt.show()


""" 函数说明:计算w Parameters: dataMat - 数据矩阵 labelMat - 数据标签 alphas - alphas值 """
def get_w(dataMat, labelMat, alphas):
    alphas, dataMat, labelMat = np.array(alphas), np.array(dataMat), np.array(labelMat)
    w = np.dot((np.tile(labelMat.reshape(1, -1).T, (1, 2)) * dataMat).T, alphas)
    return w.tolist()


if __name__ == '__main__':
    dataMat, labelMat = loadDataSet('testSet.txt')
    showDataSet(dataMat,labelMat)
    b,alphas = smoSimple(dataMat, labelMat, 0.6, 0.001, 40)
    w = get_w(dataMat, labelMat, alphas)
    showClassifer(dataMat, w, b)

3、非线性向量机

1 核技巧

我们已经了解到,SVM如何处理线性可分的情况,而对于非线性的情况,SVM的处理方式就是选择一个核函数。简而言之:在线性不可分的情况下,SVM通过某种事先选择的非线性映射(核函数)将输入变量映到一个高维特征空间,将其变成在高维空间线性可分,在这个高维空间中构造最优分类超平面。

线性可分的情况下,可知最终的超平面方程为:

f ( x ) = i = 1 n α i y i x i T x + b

将上述公式用内积来表示:

f ( x ) = i = 1 n α i y i < x i , x > + b

对于线性不可分,我们使用一个非线性映射,将数据映射到特征空间,在特征空间中使用线性学习器,分类函数变形如下:

f ( x ) = i = 1 n α i y i < ϕ ( x i ) , ϕ ( x ) > + b

其中 ϕ 从输入空间(X)到某个特征空间(F)的映射,这意味着建立非线性学习器分为两步:

  • 首先使用一个非线性映射将数据变换到一个特征空间F;
  • 然后在特征空间使用线性学习器分类。

如果有一种方法可以在特征空间中直接计算内积 < ϕ ( x i ) , ϕ ( x ) > ,就像在原始输入点的函数中一样,就有可能将两个步骤融合到一起建立一个分线性的学习器,这样直接计算的方法称为核函数方法。

这里直接给出一个定义:核是一个函数k,对所有 x , z X ,满足 k ( x , z ) =< ϕ ( x i ) , ϕ ( x ) > ,这里 ϕ ( · ) 是从原始输入空间X到内积空间F的映射。

简而言之:如果不是用核技术,就会先计算线性映 ϕ ( x 1 ) ϕ ( x 2 ) ,然后计算这它们的内积,使用了核技术之后,先把 ϕ ( x 1 ) ϕ ( x 2 ) 的一般表达式 < ϕ ( x 1 ) , ϕ ( x 2 ) >= k ( < ϕ ( x 1 ) , ϕ ( x 2 ) > ) 计算出来,这里的 < · · > 表示内积, k ( · · ) 就是对应的核函数,这个表达式往往非常简单,所以计算非常方便。

这种将内积替换成核函数的方式被称为核技巧(kernel trick)。

常用核函数:

多项式核函数:

K ( x , z ) = ( x z + 1 ) p
对应的支持向量机为p次多项式分类器。

高斯核函数:

K ( x , z ) = e x p ( | | x z | | 2 2 σ 2 )
对应的支持向量机为高斯径向基函数分类器。

总结

SVM的优缺点

优点

可用于线性/非线性分类,也可以用于回归,泛化错误率低,也就是说具有良好的学习能力,且学到的结果具有很好的推广性。
可以解决小样本情况下的机器学习问题,可以解决高维问题,可以避免神经网络结构选择和局部极小点问题。
SVM是最好的现成的分类器,现成是指不加修改可直接使用。并且能够得到较低的错误率,SVM可以对训练集之外的数据点做很好的分类决策。

缺点

对参数调节和和函数的选择敏感。