机器学习支持向量机

目录

支持向量机SVM、核函数

支持向量机/SVM

[1. 线性可分](#1. 线性可分)

[2. 线性支持向量机](#2. 线性支持向量机)

[3. 凸优化问题](#3. 凸优化问题)

[4. 非线性向量机](#4. 非线性向量机)

hard_margin_svm.ipynb

生成模拟数据

cvxopt用法

线性可分支持向量机

soft_margin_svm.ipynb

non-linear_svm.ipynb


支持向量机SVM、核函数

线性回归:

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

f(x)=w\^Tx+b

二分类的线性分类:

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

y= \\begin{cases} 1, \& \\text {f(x)$\\geq$0} \\\\ -1, \& \\text{f(x)\<0} \\end{cases}

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

比如:下图为线性可分的

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

即使是线性可分的,分类边界也不一定是确定的。

比如下面的几个分类边界都是可以实现分类的,哪个更好呢。

这就需要用到支持向量机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)=sign(w⋅x+b)

其中sign为符号函数,将正数映射为1,负数映射为-1。

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

直观上,超平面B1的分类效果更好一些。将距离分离超平面最近的两个不同类别的样本点称为支持向量(support vector)的,构成了两条平行于分离超平面的长带,二者之间的距离称之为margin。

显然,margin更大,则分类正确的确信度更高(与超平面的距离表示分类的确信度,距离越远则分类正确的确信度越高)。

通过计算得到(计算过程参考《统计学习方法》115页)

margin=\\frac{2}{∥w∥}

从上图中可观察到:margin以外的样本点对于确定分离超平面没有贡献,换句话说,SVM是有很重要的训练样本(支持向量)所确定的。

至此,SVM分类问题可描述为在全部分类正确的情况下,最大化\\frac{2}{∥w∥}(等价于最小化0.5∥w∥\^2);

线性分类的约束最优化问题转化为如下的凸优化问题。

\\min_{w,b}\\frac{1}{2}\|\|w\|\|\^2 s.t.\\quad \\quad y_i(w\\cdot x_i+b)-1 \\geq 0

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

支持向量满足 y_i(w\\cdot x_i+b)-1 =0

即正样本点满足 w\\cdot x_i+b=1

负样本点满足 w\\cdot x_i+b=-1

2. 线性支持向量机

线性可分是理想情形,大多数情况下,由于噪声或特异点等各种原因,训练样本是线性不可分的。

因此,需要更一般化的学习算法。

这时我们就可以通过引入所谓的松弛变量(slack variable),来允许有些数据点可以处于超平面的错误的一侧。

这样我们的优化目标就能保持仍然不变,但是此时我们的约束条件有所改变。

3. 凸优化问题

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

min\\quad f(x)

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

$$min\quad f(x) \\

s.t. \quad h_i(x)=0 ,i=0,1,2...n$$

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

$$min\quad f(x) \\

s.t. \quad g_i(x) \leq 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 \quad \frac{1}{2} ||w||^2 \\

s.t. \quad y_i(w^Tx_i+b) \geq 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)\leq 0 ,~\forall~ i=1,\ldots,m,x\in X$$

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

v\^\* = \\max_{α\\geq 0} \\min_{x\\in X} \\underbrace{f(x)+α\^T g(x)}_{L(x,α)}

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


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

公式变形如下:

L(w,b,α) = \\frac{1}{2}\|\|w\|\|\^2-\\sum_{i=1}\^nα_i(y_i(w\^Tx_i+b)-1)

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

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

现在我们令:

\\theta(w) = \\max_{α_i≥0} L(w,b,α)

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

y_i(w\^Tx_i+b)\<1

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

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

y_i(w\^Tx_i+b)≥1

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

θ(w) = \\begin{cases} \\frac{1}{2}\|\|w\|\|\^2, \& \\text {x在可行区域} \\\\ +\\infty\& \\text{x在非可行区域} \\end{cases}

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

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

p\^\* = \\min_{w,b}θ(w) = \\min_{w,b} \\max_{αi≥0}L(w,b,α)

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

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

我们看一下我们的新目标函数,先求最大值,再求最小值。

这样的话,我们首先就要面对带有需要求解的参数w和b的方程,而αi又是不等式约束,这个求解过程不好做。

所以,我们需要使用拉格朗日函数对偶性,将最小和最大的位置交换一下,这样就变成了:

d\^\* = \\max_{α\\geq 0} \\min_{w,b} L(w,b,α)

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

  • 首先必须满足这个优化问题是凸优化问题。

  • 其次,需要满足KKT条件。

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

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

KKT 条件

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

一个最优化模型能够表示成下列标准形式:

$$\min f(x) \\

s.t. \,\,\, h_j(x)=0, \,\,\,\,\,\, j=1,2,3...p, \\

s.t. \,\,\,\, g_k(x)\leq 0 ,k=1,2,....,q, \\

x \in X \subset R^n $$

优化模型的拉格朗日函数为

L(X,λ,μ)=f(X)+\\sum_{j=1}\^p λ_jh_j(X)+\\sum_{k=1}\^qμ_kg_k(X)

其中f(x)是原目标函数,h_j(x)是第j个等式约束条件,λ_j是对应的约束系数,g_k是不等式约束,u_k是对应的约束系数。

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

其中X\^\*表示样本点。这些求解条件就是KKT条件。

(1)是对拉格朗日函数取极值时候带来的一个必要条件

(2)是拉格朗日系数约束(同等式情况)

(3)是不等式约束情况

(4)是互补松弛条件

(5)、(6)是原约束条件。

对于一般的任意问题而言,KKT条件是使一组解成为最优解的必要条件,当原问题是凸问题的时候,KKT条件也是充分条件。

对于我们的优化问题:

$$\min_w \quad \frac{1}{2} ||w||^2 \\

s.t. \quad y_i(w^Tx_i+b) \geq 1 ,i=0,1,2...n $$

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

这里原谅我省略一系列证明步骤,感兴趣的可以移步这里:点我查看(https://blog.csdn.net/xianlingmao/article/details/7919597)

这里已经给出了很好的解释。现在,凸优化问题和KKT都满足了,问题转换成了对偶问题。而求解这个对偶学习问题,可以分为三个步骤:

首先要让L(w,b,α)关于w和b最小化

然后求对α的极大

最后利用SMO算法求解对偶问题中的拉格朗日乘子。

现在,我们继续推导。

对偶问题求解

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

L(w,b,α) = \\frac{1}{2}\|\|w\|\|\^2-\\sum_{i=1}\^nα_i(y_i(w\^Tx_i+b)-1)

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

\\frac{d L}{d w} = 0 \\implies w = \\sum_{i=1}\^n α_iy_ix_i

\\frac{d L}{d b} = 0 \\implies \\sum_{i=1}\^n α_iy_i=0

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

L(w,b,α) = \\sum_{i=1}\^n α_i-0.5\*\\sum_{i,j=1}\^n α_iα_jy_iy_jx_i\^Tx_j

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

第二步:

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

$$\max_α\sum_{i=1}^n α_i-\frac{1}{2}\sum_{i,j=1}^n α_iα_jy_iy_jx_i^Tx_j \\

s.t. \,\,\,\, α_i\geq0,i=1,2...n, \\

\sum_{i=1}^n α_iy_i=0$$

现在我们的优化问题变成了如上的形式。对于这个问题,我们有更高效的优化算法,即序列最小优化(SMO)算法。

我们通过这个优化算法能得到α,再根据α,我们就可以求解出w和b,进而求得我们最初的目的:找到超平面,即"决策平面"。

而上式可以等价于

$$ \min_α \frac{1}{2}\sum_{i,j=1}^n α_iα_jy_iy_jx_i^Tx_j -\sum_{i=1}^n α_i\\

s.t. \,\,\,\, α_i\geq0,i=1,2...n, \\

\sum_{i=1}^n α_iy_i=0$$

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

SMO 算法

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

SMO算法的工作原理是:

每次循环中选择两个α_i进行优化处理。一旦找到了一对合适的α_i,那么就增大其中一个同时减小另一个。

这里所谓的"合适"就是指两个α_i必须符合以下两个条件,条件之一就是两个α_i必须要在间隔边界之外,而且第二个条件则是这两个α_i还没有进行过区间化处理或者不在边界上。

SMO 算法的解法

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

u=w\^Tx+b

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

$$\min_w \quad \frac{1}{2} ||w||^2 \\

s.t. \quad y_i(w^Tx_i+b) \geq 1 ,i=0,1,2...n $$

求导得:

w = \\sum_{i=1}\^n α_iy_ix_i

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

u = \\sum_{i=1}\^n α_iy_ix_i\^Tx+b

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

$$ \min_α \frac{1}{2}\sum_{i,j=1}^n α_iα_jy_iy_jx_i^Tx_j -\sum_{i=1}^n α_i\\

s.t. \,\,\,\, α_i\geq0,i=1,2...n, \\

\sum_{i=1}^n α_iy_i=0$$

线性支持下的 SMO

实际上,对于上述目标函数,是存在一个假设的,即数据100%线性可分。但是,目前为止,我们知道几乎所有数据都不那么"干净"。

这时我们就可以通过引入所谓的松弛变量(slack variable),来允许有些数据点可以处于超平面的错误的一侧。

这样我们的优化目标就能保持仍然不变,但是此时我们的约束条件有所改变:

$$ s.t.\quad C\geqα_i\geq0,i=1,2...n, \\

\sum_{i=1}^n α_iy_i=0$$

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

α_i = 0 \\iff y_iu_i\\geq1

0\<α_i \

α_i = C \\iff y_iu_i\\leq1

  • 对于第1种情况,表明αi是正常分类,在边界内部;

  • 对于第2种情况,表明αi是支持向量,在边界上;

  • 对于第3种情况,表明αi是在两条边界之间。

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

y_iu_i\\leq1 \\quad α_i \

y_iu_i\\geq1 \\quad α_i \> 0

y_iu_i=1 \\quad α_i = 0 或者 α_i =C

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

\\sum_{i=1}\^n α_iy_i=0

因为这个条件,我们同时更新两个α值,因为只有成对更新,才能保证更新之后的值仍然满足和为0的约束,假设我们选择的两个乘子为α_1α_2

α_1\^{new}y_1+α_2\^{new}y_2 = α_1\^{old}y_1+α_2\^{old}y_2 = \\zeta

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

L\\leqα_2\^{new}\\leq H

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

$$ C\geqα_i\geq0,i=1,2...n, \\

α_1^{new}y_1+α_2^{new}y_2 = α_1^{old}y_1+α_2^{old}y_2 = \zeta$$

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

α_1\^{old}-α_2\^{old}= \\zeta

所以有:

L=max(0, \\zeta),H=min(C,C-\\zeta)

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

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

α_1\^{old}+α_2\^{old}= \\zeta

所以有:

L=max(0, \\zeta-C),H=min(C,\\zeta)

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

如此,根据y1和y2异号或同号,可以得出α_2\^{new}的上下界分别为:

L=max(0, α_2\^{old}-α_1\^{old}),H=min(C,C+α_2\^{old}-α_1\^{old}) \\qquad if \\quad y_1 \\neq y_2

L=max(0, α_1\^{old}+α_2\^{old}-C),H=min(C,α_1\^{old}+α_2\^{old}) \\qquad if \\quad y_1=y_2

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

我们已经知道,更新α的边界,接下来就是讨论如何更新α值。我们依然假设选择的两个乘子为α_1α_2。(推导略)

α_2\^{new,clipped}= \\begin{cases} H, \& α_2\^{new}\>H \\\\ α_2\^{new}, \& L\\leq α_2\^{new}\\leq H \\\\ L, \& α_2\^{new}\

α_1\^{new}= α_1\^{old}+y_1y_2(α_2\^{old}-α_2\^{new,clipped})

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

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

y_1(w\^Tx_1+b)= 1

公式两边同时乘以y_1得(y_1\\times y_1=1):

\\sum_{i=1}\^nα_iy_ix_ix_1+b=y_1

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

b_1\^{new} = y_1-\\sum_{i=3}\^nα_iy_ix_i\^Tx_1-α_1\^{new}y_1x_1\^Tx_1-α_2\^{new}y_2x_2\^Tx_1

其中前两项为:

y_1-\\sum_{i=3}\^nα_iy_ix_i\^Tx_1=-E_1+α_1\^{old}y_1x_1\^Tx_1+α_2\^{old}y_2x_2\^Tx_1+b\^{old}

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

b_1\^{new} =b\^{old}-E_1-y_1(α_1\^{new}-α_1\^{old})x_1\^Tx_1-y_2(α_2\^{new}-α_2\^{old})x_2\^Tx_1

同理可得b_2\^{new}为:

b_2\^{new} =b\^{old}-E_2-y_1(α_1\^{new}-α_1\^{old})x_1\^Tx_2-y_2(α_2\^{new}-α_2\^{old})x_2\^Tx_2

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

b= \\begin{cases} b_1, \& 0\<α_1\^{new}\

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

步骤1:计算误差:

E_i=f(x_i)-y_i=\\sum_{j=1}\^nα_jy_jx_i\^Tx_j+b-y_i

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

L=max(0, α_j\^{old}-α_i\^{old}),H=min(C,C+α_j\^{old}-α_i\^{old}) \\qquad if \\quad y_i \\neq y_j

L=max(0, α_j\^{old}+α_i\^{old}-C),H=min(C,α_j\^{old}+α_i\^{old}) \\qquad if \\quad y_j=y_i

步骤3:计算η

η = x_i\^Tx_i+x_j\^Tx_j-2x_i\^Tx_j

步骤4:更新α_j

α_j\^{new}=α_j\^{old}+\\frac{y_j(E_i-E_j)}{η }

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

α_j\^{new,clipped}= \\begin{cases} H, \& α_j\^{new}\>H \\\\ α_j\^{new}, \& L\\leq α_j\^{new}\\leq H \\\\ L, \& α_j\^{new}\

步骤6:更新α_i

α_i\^{new}= α_i\^{old}+y_iy_j(α_j\^{old}-α_j\^{new,clipped})

步骤7:更新b1和b2:

b_1\^{new} =b\^{old}-E_i-y_i(α_i\^{new}-α_i\^{old})x_i\^Tx_i-y_j(α_j\^{new}-α_j\^{old})x_j\^Tx_i

b_2\^{new} =b\^{old}-E_j-y_i(α_i\^{new}-α_i\^{old})x_i\^Tx_j-y_j(α_j\^{new}-α_j\^{old})x_j\^Tx_j

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

b= \\begin{cases} b_1, \& 0\<α_1\^{new}\

** **代码** **

复制代码
# python实现

样本集下载:

** **代码** **

复制代码
!wget https://ghproxy.com/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)

4. 非线性向量机

1 核技巧

我们已经了解到,SVM如何处理线性可分的情况,而对于非线性的情况,SVM的处理方式就是选择一个核函数。

简而言之:在线性不可分的情况下,SVM通过某种事先选择的非线性映射(核函数)将输入变量映到一个高维特征空间,将其变成在高维空间线性可分,在这个高维空间中构造最优分类超平面。

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

f(x)=\\sum_{i=1}\^nα_iy_ix_i\^Tx+b

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

f(x)=\\sum_{i=1}\^nα_iy_i\+b

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

f(x)=\\sum_{i=1}\^nα_iy_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)是正定核的充要条件是:K(x,z)对应的Gram矩阵实半正定矩阵。

Gram矩阵:矩阵对应点的内积。KTK, KKT

半正定矩阵:设A是实对称矩阵。如果对任意的实非零列矩阵X有XTAX≥0,就称A为半正定矩阵。

当检验一个K是否为正定核函数,要对任意有限输入集{xi...}验证K对应的Gram矩阵实是否为半正定矩阵。

LIBSVM 中提供的核函数

**线性核函数:**

K(x,z)=x\\cdot z

线性核,主要用于线性可分的情况,我们可以看到特征空间到输入空间的维度是一样的,其参数少速度快,对于线性可分数据,其分类效果很理想,因此我们通常首先尝试用线性核函数来做分类,看看效果如何,如果不行再换别的

**多项式核函数:**

K(x,z)=(x\\cdot z+1)\^p

对应的支持向量机为p次多项式分类器。

多项式核函数可以实现将低维的输入空间映射到高纬的特征空间,但是多项式核函数的参数多,当多项式的阶数比较高的时候,核矩阵的元素值将趋于无穷大或者无穷小,计算复杂度会大到无法计算。

**RBF核函数(高斯核函数) :**

K(x,z)=exp(-\\frac{\|\|x-z\|\|\^2}{2\\sigma\^2})

对应的支持向量机为高斯径向基函数分类器。

高斯径向基函数是一种局部性强的核函数,其可以将一个样本映射到一个更高维的空间内,该核函数是应用最广的一个,无论大样本还是小样本都有比较好的性能,而且其相对于多项式核函数参数要少,因此大多数情况下在不知道用什么核函数的时候,优先使用高斯核函数。

**sigmoid核函数:**

\\kappa(x,z) = tanh(\\eta\ + \\theta)

采用sigmoid核函数,支持向量机实现的就是一种多层神经网络。

SVM的核函数如何选取 ----------- 最常用的是Linear核与RBF核。需要注意的是需要对数据归一化处理。

1、Linear核:主要用于线性可分的情形。参数少,速度快,对于一般数据,分类效果已经很理想了。

2、RBF核:主要用于线性不可分的情形。参数多,分类结果非常依赖于参数。

有很多人是通过训练数据的交叉验证来寻找合适的参数,不过这个过程比较耗时。

我个人的体会是:使用libsvm,默认参数,RBF核比Linear核效果稍差。

通过进行大量参数的尝试,一般能找到比linear核更好的效果。

如果特征的提取的好,包含的信息量足够大,很多问题都是线性可分的。

当然,如果有足够的时间去寻找RBF核参数,应该能达到更好的效果。

这些函数中应用最广的应该就是RBF核了,无论是小样本还是大样本,高维还是低维等情况,RBF核函数均适用

它相比其他的函数有一下优点:

1)RBF核函数可以将一个样本映射到一个更高维的空间,而且线性核函数是RBF的一个特例,也就是说如果考虑使用RBF,那么就没有必要考虑线性核函数了。

2)与多项式核函数相比,RBF需要确定的参数要少,核函数参数的多少直接影响函数的复杂程度。

另外,当多项式的阶数比较高时,核矩阵的元素值将趋于无穷大或无穷小,而RBF则在上,会减少数值的计算困难。

3)对于某些参数,RBF和sigmoid具有相似的性能。

因此,在选用核函数的时候,如果我们对我们的数据有一定的先验知识

就利用先验来选择符合数据分布的核函数;

如果不知道的话,通常使用交叉验证的方法,来试用不同的核函数,误差最下的即为效果最好的核函数,或者也可以将多个核函数结合起来,形成混合核函数。

在吴恩达的课上,也曾经给出过一系列的选择核函数的方法:

下面是吴恩达的见解:

1、如果Feature的数量很大,跟样本数量差不多,这时候选用LR或者是Linear Kernel的SVM

2、 如果Feature的数量比较小,样本数量一般,不算大也不算小,选用SVM+Gaussian Kernel

3、 如果Feature的数量比较小,而样本数量很多,需要手工添加一些feature变成第一种情况

hard_margin_svm.ipynb

** **代码** **

复制代码
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
生成模拟数据

** **代码** **

复制代码
# 导入sklearn模拟二分类数据生成模块
from sklearn.datasets.samples_generator import make_blobs
# 生成模拟二分类数据集
X, y =  make_blobs(n_samples=150, n_features=2, centers=2, cluster_std=1.2, random_state=40)
# 设置颜色参数
colors = {0:'r', 1:'g'}
# 绘制二分类数据集的散点图
plt.scatter(X[:,0], X[:,1], marker='o', c=pd.Series(y).map(colors))
plt.show();

** **代码** **

复制代码
# 将标签转换为1/-1
y_ = y.copy()
y_[y_==0] = -1
y_ = y_.astype(float)

** **代码** **

复制代码
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y_, test_size=0.3, random_state=43)
print(X_train.shape, y_train.shape, X_test.shape, y_test.shape)

** **输出** **

(105, 2) (105,) (45, 2) (45,)

cvxopt用法

** **代码** **

复制代码
from cvxopt import matrix, solvers

** **代码** **

复制代码
# 定义二次规划参数
P = matrix([[1.0,0.0],[0.0,0.0]])
q = matrix([3.0,4.0])
G = matrix([[-1.0,0.0,-1.0,2.0,3.0],[0.0,-1.0,-3.0,5.0,4.0]])
h = matrix([0.0,0.0,-15.0,100.0,80.0])

# 构建求解
sol = solvers.qp(P, q, G, h)

** **输出** **

pcost dcost gap pres dres

0: 1.0780e+02 -7.6366e+02 9e+02 0e+00 4e+01

1: 9.3245e+01 9.7637e+00 8e+01 6e-17 3e+00

2: 6.7311e+01 3.2553e+01 3e+01 2e-16 1e+00

3: 2.6071e+01 1.5068e+01 1e+01 2e-16 7e-01

4: 3.7092e+01 2.3152e+01 1e+01 1e-16 4e-01

5: 2.5352e+01 1.8652e+01 7e+00 6e-17 3e-16

6: 2.0062e+01 1.9974e+01 9e-02 9e-17 2e-16

7: 2.0001e+01 2.0000e+01 9e-04 2e-16 1e-16

8: 2.0000e+01 2.0000e+01 9e-06 1e-16 3e-16

Optimal solution found.

** **代码** **

复制代码
# 获取最优值
print(sol['x'], sol['primal objective'])

** **输出** **

7.13e-07

5.00e+00

20.00000617311241

线性可分支持向量机

代码

复制代码
### 实现线性可分支持向量机
### 硬间隔最大化策略
class Hard_Margin_SVM:
    ### 线性可分支持向量机拟合方法
    def fit(self, X, y):
        # 训练样本数和特征数
        m, n = X.shape

        # 初始化二次规划相关变量:P/q/G/h
        self.P = matrix(np.identity(n + 1, dtype=np.float))
        self.q = matrix(np.zeros((n + 1,), dtype=np.float))
        self.G = matrix(np.zeros((m, n + 1), dtype=np.float))
        self.h = -matrix(np.ones((m,), dtype=np.float))

        # 将数据转为变量
        self.P[0, 0] = 0
        for i in range(m):
            self.G[i, 0] = -y[i]
            self.G[i, 1:] = -X[i, :] * y[i]
        
        # 构建二次规划求解
        sol = solvers.qp(self.P, self.q, self.G, self.h)

        # 对权重和偏置寻优
        self.w = np.zeros(n,) 
        self.b = sol['x'][0] 
        for i in range(1, n + 1):
            self.w[i - 1] = sol['x'][i]
        return self.w, self.b

    ### 定义模型预测函数
    def predict(self, X):
        return np.sign(np.dot(self.w, X.T) + self.b)

** **代码** **

复制代码
# 创建线性可分支持向量机模型实例
hard_margin_svm = Hard_Margin_SVM()
# 执行训练
hard_margin_svm.fit(X_train, y_train)

** **输出** **

pcost dcost gap pres dres

0: 2.1061e-02 2.0725e+01 3e+02 2e+00 8e+02

1: 1.7678e-01 -4.0311e+01 5e+01 3e-01 1e+02

2: 2.5467e-01 -1.3854e+00 2e+00 1e-02 4e+00

3: 2.1218e-01 4.0205e-02 2e-01 6e-04 2e-01

4: 1.8309e-01 1.5738e-01 3e-02 8e-05 4e-02

5: 1.8241e-01 1.8207e-01 3e-04 1e-06 4e-04

6: 1.8239e-01 1.8239e-01 3e-06 1e-08 4e-06

7: 1.8239e-01 1.8239e-01 3e-08 1e-10 4e-08

Optimal solution found.

** **输出** **

(array(0.40882768, 0.44457681), 1.8310613288769853)

** **代码** **

复制代码
# 模型预测
y_pred = hard_margin_svm.predict(X_test)
from sklearn.metrics import accuracy_score
# 计算测试集准确率
print(accuracy_score(y_test, y_pred))

** **输出** **

1.0

** **代码** **

复制代码
from matplotlib.colors import ListedColormap

### 绘制线性可分支持向量机决策边界图
def plot_classifer(model, X, y):
    # 超参数边界
    x_min = -7
    x_max = 12
    y_min = -12
    y_max = -1
    step = 0.05
    # meshgrid
    xx, yy = np.meshgrid(np.arange(x_min, x_max, step),
                         np.arange(y_min, y_max, step))
    # 模型预测
    z = model.predict(np.c_[xx.ravel(), yy.ravel()])

    # 定义color map
    cmap_light = ListedColormap(['#FFAAAA', '#AAFFAA'])
    cmap_bold = ListedColormap(['#FF0000', '#003300'])
    z = z.reshape(xx.shape)

    plt.figure(figsize=(8, 5), dpi=96)
    plt.pcolormesh(xx, yy, z, cmap=cmap_light)
    plt.scatter(X[:, 0], X[:, 1], c=y, cmap=cmap_bold)
    plt.show()

** **代码** **

复制代码
plot_classifer(hard_margin_svm, X_train, y_train)

** **代码** **

复制代码
# 导入sklearn线性SVM分类模块
from sklearn.svm import LinearSVC
# 创建模型实例
clf = LinearSVC(random_state=0, tol=1e-5)
# 训练
clf.fit(X_train, y_train)
# 预测
y_pred = clf.predict(X_test)
# 计算测试集准确率
print(accuracy_score(y_test, y_pred))

** **输出** **

1.0

soft_margin_svm.ipynb

代码

复制代码
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

** **代码** **

复制代码
mean1, mean2 = np.array([0, 2]), np.array([2, 0])
covar = np.array([[1.5, 1.0], [1.0, 1.5]])
X1 = np.random.multivariate_normal(mean1, covar, 100)
y1 = np.ones(X1.shape[0])
X2 = np.random.multivariate_normal(mean2, covar, 100)
y2 = -1 * np.ones(X2.shape[0])
X_train = np.vstack((X1[:80], X2[:80]))
y_train = np.hstack((y1[:80], y2[:80]))
X_test = np.vstack((X1[80:], X2[80:]))
y_test = np.hstack((y1[80:], y2[80:]))
print(X_train.shape, y_train.shape, X_test.shape, y_test.shape)

** **输出** **

(160, 2) (160,) (40, 2) (40,)

** **代码** **

复制代码
# 设置颜色参数
colors = {1:'r', -1:'g'}
# 绘制二分类数据集的散点图
plt.scatter(X_train[:,0], X_train[:,1], marker='o', c=pd.Series(y_train).map(colors))
plt.show();

** **代码** **

复制代码
### 定义一个线性核函数
def linear_kernel(x1, x2):
    '''
    输入:
    x1: 向量1
    x2: 向量2
    输出:
    np.dot(x1, x2): 两个向量的点乘
    '''
    return np.dot(x1, x2)

** **代码** **

复制代码
from cvxopt import matrix, solvers

### 定义近似线性可分支持向量机
### 软间隔最大化策略
class Soft_Margin_SVM:
    ### 定义基本参数
    def __init__(self, kernel=linear_kernel, C=None):
        # 软间隔svm核函数,默认为线性核函数
        self.kernel = kernel
        # 惩罚参数
        self.C = C
        if self.C is not None: 
            self.C = float(self.C)
    
    ### 定义线性支持向量机拟合方法
    def fit(self, X, y):
        # 训练样本数和特征数
        m, n = X.shape
        
        # 基于线性核计算Gram矩阵
        K = self._gram_matrix(X)
                
        # 初始化二次规划相关变量:P/q/G/h
        P = matrix(np.outer(y,y) * K)
        q = matrix(np.ones(m) * -1)
        A = matrix(y, (1, m))
        b = matrix(0.0)
        
        # 未设置惩罚参数时的G和h矩阵
        if self.C is None:
            G = matrix(np.diag(np.ones(m) * -1))
            h = matrix(np.zeros(m))
        # 设置惩罚参数时的G和h矩阵
        else:
            tmp1 = np.diag(np.ones(m) * -1)
            tmp2 = np.identity(m)
            G = matrix(np.vstack((tmp1, tmp2)))
            tmp1 = np.zeros(m)
            tmp2 = np.ones(m) * self.C
            h = matrix(np.hstack((tmp1, tmp2)))

        # 构建二次规划求解
        sol = solvers.qp(P, q, G, h, A, b)
        # 拉格朗日乘子
        a = np.ravel(sol['x'])

        # 寻找支持向量
        spv = a > 1e-5
        ix = np.arange(len(a))[spv]
        self.a = a[spv]
        self.spv = X[spv]
        self.spv_y = y[spv]
        print('{0} support vectors out of {1} points'.format(len(self.a), m))

        # 截距向量
        self.b = 0
        for i in range(len(self.a)):
            self.b += self.spv_y[i]
            self.b -= np.sum(self.a * self.spv_y * K[ix[i], spv])
        self.b /= len(self.a)

        # 权重向量
        self.w = np.zeros(n,)
        for i in range(len(self.a)):
            self.w += self.a[i] * self.spv_y[i] * self.spv[i]

    ### 定义Gram矩阵计算函数
    def _gram_matrix(self, X):
        m, n = X.shape
        K = np.zeros((m, m))
        # 遍历计算Gram矩阵
        for i in range(m):
            for j in range(m):
                K[i,j] = self.kernel(X[i], X[j])
        return K
    
    ### 定义映射函数
    def project(self, X):
        if self.w is not None:
            return np.dot(X, self.w) + self.b
    
    ### 定义模型预测函数
    def predict(self, X):
        return np.sign(np.dot(self.w, X.T) + self.b)

** **代码** **

复制代码
from sklearn.metrics import accuracy_score
soft_margin_svm = Soft_Margin_SVM(C=0.1)
soft_margin_svm.fit(X_train, y_train)
y_pred = soft_margin_svm.predict(X_test)
# 计算测试集准确率
print('Accuracy of soft margin svm based on cvxopt: ', 
      accuracy_score(y_test, y_pred))

** **输出** **

pcost dcost gap pres dres

0: -1.6893e+01 -2.5699e+01 7e+02 2e+01 7e-15

1: -3.2732e+00 -2.3311e+01 6e+01 1e+00 5e-15

2: -1.7627e+00 -9.1626e+00 1e+01 1e-01 1e-15

3: -1.6913e+00 -2.8410e+00 1e+00 1e-02 2e-15

4: -1.8579e+00 -2.3123e+00 5e-01 4e-03 1e-15

5: -1.9334e+00 -2.1193e+00 2e-01 1e-03 8e-16

6: -1.9742e+00 -2.0305e+00 6e-02 4e-04 1e-15

7: -1.9872e+00 -2.0040e+00 2e-02 8e-05 9e-16

8: -1.9936e+00 -1.9941e+00 5e-04 1e-06 1e-15

9: -1.9938e+00 -1.9938e+00 2e-05 5e-08 1e-15

10: -1.9938e+00 -1.9938e+00 2e-07 5e-10 1e-15

Optimal solution found.

29 support vectors out of 160 points

Accuracy of soft margin svm based on cvxopt: 0.925

** **代码** **

复制代码
def plot_classifier(X1_train, X2_train, clf):
    plt.plot(X1_train[:,0], X1_train[:,1], "ro")
    plt.plot(X2_train[:,0], X2_train[:,1], "go")
    plt.scatter(soft_margin_svm.spv[:,0], soft_margin_svm.spv[:,1], 
                s=100, c="", edgecolors="b", label="support vector")

    X1, X2 = np.meshgrid(np.linspace(-4,4,50), np.linspace(-4,4,50))
    X = np.array([[x1, x2] for x1, x2 in zip(np.ravel(X1), np.ravel(X2))])
    Z = soft_margin_svm.project(X).reshape(X1.shape)
    plt.contour(X1, X2, Z, [0.0], colors='k', linewidths=1, origin='lower')
    plt.contour(X1, X2, Z + 1, [0.0], colors='grey', linewidths=1, origin='lower')
    plt.contour(X1, X2, Z - 1, [0.0], colors='grey', linewidths=1, origin='lower')
    plt.legend()
    plt.show()
    
plot_classifier(X_train[y_train==1], X_train[y_train==-1], soft_margin_svm)

** **代码** **

复制代码
from sklearn import svm
# 创建svm模型实例
clf = svm.SVC(kernel='linear')
# 模型拟合
clf.fit(X_train, y_train)
# 模型预测
y_pred = clf.predict(X_test)
# 计算测试集准确率
print('Accuracy of soft margin svm based on sklearn: ', 
      accuracy_score(y_test, y_pred))

** **输出** **

Accuracy of soft margin svm based on sklearn: 0.925

non-linear_svm.ipynb

** **代码** **

复制代码
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

** **代码** **

复制代码
mean1, mean2 = np.array([-1, 2]), np.array([1, -1])
mean3, mean4 = np.array([4, -4]), np.array([-4, 4])
covar = np.array([[1.0, 0.8], [0.8, 1.0]])
X1 = np.random.multivariate_normal(mean1, covar, 50)
X1 = np.vstack((X1, np.random.multivariate_normal(mean3, covar, 50)))
y1 = np.ones(X1.shape[0])
X2 = np.random.multivariate_normal(mean2, covar, 50)
X2 = np.vstack((X2, np.random.multivariate_normal(mean4, covar, 50)))
y2 = -1 * np.ones(X2.shape[0])
X_train = np.vstack((X1[:80], X2[:80]))
y_train = np.hstack((y1[:80], y2[:80]))
X_test = np.vstack((X1[80:], X2[80:]))
y_test = np.hstack((y1[80:], y2[80:]))
print(X_train.shape, y_train.shape, X_test.shape, y_test.shape)

** **输出** **

(160, 2) (160,) (40, 2) (40,)

** **代码** **

复制代码
# 设置颜色参数
colors = {1:'r', -1:'g'}
# 绘制二分类数据集的散点图
plt.scatter(X_train[:,0], X_train[:,1], marker='o', c=pd.Series(y_train).map(colors))
plt.show();

** **代码** **

复制代码
### 定义高斯核函数
def gaussian_kernel(x1, x2, sigma=5.0):
    '''
    输入:
    x1: 向量1
    x2: 向量2
    输出:
    两个向量的高斯核
    '''
    return np.exp(-1 * np.linalg.norm(x1-x2)**2 / (2 * (sigma ** 2)))

** **代码** **

复制代码
from cvxopt import matrix, solvers

### 定义线性不可分支持向量机
### 借助于高斯核函数转化为线性可分的情形
class Non_Linear_SVM:
    ### 定义基本参数
    def __init__(self, kernel=gaussian_kernel):
        # 非线性可分svm核函数,默认为高斯核函数
        self.kernel = kernel
    
    ### 定义非线性可分支持向量机拟合方法
    def fit(self, X, y):
        # 训练样本数和特征数
        m, n = X.shape
        
        # 基于线性核计算Gram矩阵
        K = self._gram_matrix(X)
                
        # 初始化二次规划相关变量:P/q/A/b/G/h
        P = matrix(np.outer(y,y) * K)
        q = matrix(np.ones(m) * -1)
        A = matrix(y, (1, m))
        b = matrix(0.0)
        G = matrix(np.diag(np.ones(m) * -1))
        h = matrix(np.zeros(m))

        # 构建二次规划求解
        sol = solvers.qp(P, q, G, h, A, b)
        # 拉格朗日乘子
        a = np.ravel(sol['x'])

        # 寻找支持向量
        spv = a > 1e-5
        ix = np.arange(len(a))[spv]
        self.a = a[spv]
        self.spv = X[spv]
        self.spv_y = y[spv]
        print('{0} support vectors out of {1} points'.format(len(self.a), m))

        # 截距向量
        self.b = 0
        for i in range(len(self.a)):
            self.b += self.spv_y[i]
            self.b -= np.sum(self.a * self.spv_y * K[ix[i], spv])
        self.b /= len(self.a)

        # 权重向量
        self.w = None

    ### 定义Gram矩阵计算函数
    def _gram_matrix(self, X):
        m, n = X.shape
        K = np.zeros((m, m))
        # 遍历计算Gram矩阵
        for i in range(m):
            for j in range(m):
                K[i,j] = self.kernel(X[i], X[j])
        return K
    
    ### 定义映射函数
    def project(self, X):
        y_pred = np.zeros(len(X))
        for i in range(X.shape[0]):
            s = 0
            for a, spv_y, spv in zip(self.a, self.spv_y, self.spv):
                s += a * spv_y * self.kernel(X[i], spv)
            y_pred[i] = s
        return y_pred + self.b
    
    ### 定义模型预测函数
    def predict(self, X):
        return np.sign(self.project(X))

** **代码** **

复制代码
# 导入sklearn准确率评估函数
from sklearn.metrics import accuracy_score
# 创建非线性可分支持向量机模型实例
non_linear_svm = Non_Linear_SVM()
# 模型拟合
non_linear_svm.fit(X_train, y_train)
# 模型预测
y_pred = non_linear_svm.predict(X_test)
# 计算测试集准确率
print('Accuracy of soft margin svm based on cvxopt: ', 
      accuracy_score(y_test, y_pred))

** **输出** **

pcost dcost gap pres dres

0: -5.3110e+01 -1.6223e+02 4e+02 2e+01 2e+00

1: -8.0716e+01 -1.8786e+02 2e+02 5e+00 7e-01

2: -1.0556e+02 -1.9757e+02 1e+02 3e+00 4e-01

3: -1.7380e+02 -2.7165e+02 1e+02 3e+00 4e-01

4: -2.4244e+02 -2.8787e+02 6e+01 9e-01 1e-01

5: -2.5720e+02 -2.6511e+02 9e+00 1e-01 1e-02

6: -2.6014e+02 -2.6216e+02 2e+00 1e-02 2e-03

7: -2.6162e+02 -2.6165e+02 3e-02 2e-04 2e-05

8: -2.6164e+02 -2.6164e+02 3e-04 2e-06 2e-07

9: -2.6164e+02 -2.6164e+02 3e-06 2e-08 2e-09

Optimal solution found.

9 support vectors out of 160 points

Accuracy of soft margin svm based on cvxopt: 1.0

** **代码** **

复制代码
### 绘制非线性可分支持向量机
def plot_classifier(X1_train, X2_train, clf):
    plt.plot(X1_train[:,0], X1_train[:,1], "ro")
    plt.plot(X2_train[:,0], X2_train[:,1], "go")
    plt.scatter(non_linear_svm.spv[:,0], non_linear_svm.spv[:,1], 
                s=100, c="", edgecolors="b", label="support vector")

    X1, X2 = np.meshgrid(np.linspace(-4,4,50), np.linspace(-4,4,50))
    X = np.array([[x1, x2] for x1, x2 in zip(np.ravel(X1), np.ravel(X2))])
    Z = non_linear_svm.project(X).reshape(X1.shape)
    plt.contour(X1, X2, Z, [0.0], colors='k', linewidths=1, origin='lower')
    plt.contour(X1, X2, Z + 1, [0.0], colors='grey', linewidths=1, origin='lower')
    plt.contour(X1, X2, Z - 1, [0.0], colors='grey', linewidths=1, origin='lower')
    plt.legend()
    plt.show()
    
plot_classifier(X_train[y_train==1], X_train[y_train==-1], non_linear_svm)

** **代码** **

复制代码
from sklearn import svm
# 创建svm模型实例
clf = svm.SVC(kernel='rbf')
# 模型拟合
clf.fit(X_train, y_train)
# 模型预测
y_pred = clf.predict(X_test)
# 计算测试集准确率
print('Accuracy of soft margin svm based on sklearn: ', 
      accuracy_score(y_test, y_pred))

** **输出** **

Accuracy of soft margin svm based on sklearn: 1.0

** **输出** **

D:\Installation\anaconda\install\lib\site-packages\sklearn\svm\base.py:196: FutureWarning: The default value of gamma will change from 'auto' to 'scale' in version 0.22 to account better for unscaled features. Set gamma explicitly to 'auto' or 'scale' to avoid this warning.

"avoid this warning.", FutureWarning)

三个 notebook 都是用 cvxopt 解对偶二次规划 手写 SVM,再和 sklearn 对照;差别主要在数据是否可分是否允许误分是否用核函数

一句话定位

Notebook 适用场景 核心策略
hard_margin_svm 线性可分 硬间隔最大化
soft_margin_svm 近似线性可分(有噪声/重叠) 软间隔 + 惩罚参数
non-linear_svm 线性不可分 高斯核映射到高维后再间隔最大化

区别

1. 数据与假设

  • 硬间隔 :make_blobs 两类簇,基本可被直线分开;要求全部样本满足
  • 软间隔 :两类高斯分布有重叠;允许少量点落入间隔甚至越界,用松弛变量
  • 非线性:四团交错数据,线性边界不够;用 RBF/高斯核做非线性决策面。

2. 优化约束(对偶里的

  • 硬间隔 / 本仓库非线性实现 :大致 (硬间隔约束更严;非线性 notebook 也按硬间隔形式写,靠核变可分)。
  • 软间隔 越大越接近硬间隔,越小越容忍误分。

3. 核函数

  • 硬/软:线性核(决策边界是直线)。
  • 非线性:高斯核 (边界弯曲)。

联系(递进关系)

硬间隔 SVM(理想线性可分)

↓ 允许误分 / 加 C

软间隔 SVM(现实线性近似可分)

↓ 核技巧(特征映射)

非线性 SVM(原空间线性不可分)

共同骨架:

  1. 最大化间隔 → 最小化 (软间隔再加
  2. 拉格朗日对偶 → 对 的二次规划
  3. 解出支持向量()→ 得到 或核形式决策函数

实践中常把后两步合在一起:软间隔 + 核函数(sklearn 的 SVC(kernel='rbf', C=...)),这三个 notebook 是把三种情形拆开演示。

相关推荐
ArkAPI1 小时前
Claude Code 连发安全修复:AI 编程 Agent 的权限,正在成为新的“安全事故高发区”
人工智能·大模型·api·codex·ai工具·claude code·arkapi
Thecozzy1 小时前
大厂 AI Coding 面试大招
人工智能·面试·职场和发展
维核科技1 小时前
推理加速实战:投机解码与KV Cache优化让大模型“快起来“
人工智能
数智化管理手记1 小时前
海量数据如何沉淀有效数据资产?数据标准化治理方案如何搭建?
java·大数据·人工智能
过期的秋刀鱼!1 小时前
LangChain-D1-模型的工作流程
人工智能·python·langchain
科技象限1 小时前
数智落地提速:2026下半年传统行业智能化升级爆发场景预判
人工智能
cfm_29142 小时前
SpringAI + Ollama 本地大模型
java·开发语言·人工智能·语言模型
147API2 小时前
蒸馏模型版本升级怎么做,权重、评测器和服务配置一起管
人工智能·深度学习·蒸馏·模型蒸馏
湘美书院--湘美谈教育2 小时前
湘美书院主理人谈AI文学:提示词与Skill的与时俱进
大数据·人工智能·安全·自动化·生活
漏刻有时2 小时前
本地部署 Dify + DeepSeek 搭建AI知识问答客服全流程复盘
人工智能