手写机器学习算法07——支持向量机

引言

支持向量机(SVM,Support Vector Machine)是一种分类算法,其基本思想是在样本空间中找到一个超平面,在将不同类别的样本分开的前提下,使超平面离距自己最近的样本尽可能远。
在这里插入图片描述

如上图所示二维空间中,支持向量机算法的目标就是找到右边的黑色实线所代表的超平面,在成功分类样本的前提下,使得两条虚线之间的间隔最大,以获得最好的泛化能力。落在虚线上的样本点被称为支持向量

算法原理

超平面模型

我们需要找到一个超平面来划分样本,那么我们可以先用以下线性方程描述超平面:

w T x + b = 0 w^Tx+b=0 wTx+b=0

其中 w w w法向量,决定了超平面的方向;b是位移项,决定了超平面与原点之间的距离。

点到超平面距离公式

由于算法涉及到超平面与样本点的距离问题,现在我们推导样本空间中任意点 x x x到超平面 h h h之间的距离。

点到超平面距离

上图中的点 x x x是样本空间中任意点, x ′ x' x x ′ ′ x'' x是超平面上的点, w w w是超平面的法向量。点 x x x到平面的距离可以转化为点 x x x到平面任意一点 x ′ x' x的向量在法向量 w w w上的投影。

根据向量点积的几何定义:
a ⃗ ⋅ b ⃗ = ∣ a ∣ ∣ b ∣ cos ⁡ θ \vec{a} \cdot \vec{b} = |a| |b| \cos \theta a b =abcosθ

可得出 a ⃗ \vec{a} a b ⃗ \vec{b} b 上的投影:

∣ a ∣ cos ⁡ θ = a ⃗ ⋅ b ⃗ ∣ b ∣ |a| \cos \theta = \frac{\vec{a} \cdot \vec{b}}{|b|} acosθ=ba b

同理我们可以得出向量 ( x − x ′ ) (x-x') (xx)在法向量 w w w上的投影为:

d i s t ( x , h ) = ∣ w T ∣ ∣ w ∣ ∣ ( x − x ′ ) ∣ dist(x,h)=\left | \frac{w^T}{||w||}(x-x') \right | dist(x,h)=wwT(xx)

又由于点 x ′ x' x是平面上点,满足 w T x ′ + b = 0 w^Tx'+b=0 wTx+b=0,因此上式可改为:

d i s t ( x , h ) = 1 ∣ ∣ w ∣ ∣ ∣ w T x + b ∣ (1) dist(x,h)= \frac{1}{||w||}|w^Tx+b| \tag 1 dist(x,h)=w1wTx+b(1)

目标函数

如果超平面能够正确地将样本分类,那么决策方程 y ( x ) y(x) y(x)可写为:

y ( x i ) = { w T x + b ≥ 0 , y i = + 1 w T x + b ≤ 0 , y i = − 1 y(x_i)= \begin{cases} w^Tx+b \geq 0 , & y_i=+1 \\ w^Tx+b \leq 0 , & y_i=-1 \end{cases} y(xi)={wTx+b0,wTx+b0,yi=+1yi=1

即当 x x x是正例时,样本点在超平面上方;当 x x x是负例是,样本点在超平面下方。

如上方程组仍有些复杂,因此我们可以将 y ( x i ) y(x_i) y(xi)乘以 y i y_i yi简化方程:

y i ⋅ y ( x i ) ≥ 0 y_i \cdot y(x_i)\geq0 yiy(xi)0

为了方便后续计算,在这里我们可以通过对 w w w放缩变换使得方程右边为1:

y i ⋅ y ( x i ) ≥ 1 (2) y_i \cdot y(x_i)\geq1 \tag 2 yiy(xi)1(2)

结合 ( 1 ) ( 2 ) (1)(2) (1)(2),可得:

d i s t ( x , h ) = 1 ∣ ∣ w ∣ ∣ ( y i ⋅ y ( x i ) ) (3) dist(x,h)= \frac{1}{||w||}(y_i \cdot y(x_i)) \tag 3 dist(x,h)=w1(yiy(xi))(3)

由于 y i ⋅ y ( x i ) y_i \cdot y(x_i) yiy(xi)恒大于0,因此原式 ( 1 ) (1) (1)中的绝对值脱掉了。

我们的优化目标是让超平面距离最近的样本点越远越好:

arg ⁡ max ⁡ w , b { 1 ∣ ∣ w ∣ ∣ min ⁡ i [ y i ⋅ ( w T x i + b ) ] } (4) \arg \max_{w,b} \left\{ \frac{1}{||w||}\min_{i} [y_i \cdot (w^Tx_i+b)] \right\} \tag 4 argw,bmax{w1imin[yi(wTxi+b)]}(4)

由于 y i ⋅ ( w T x i + b ) ≥ 1 y_i \cdot (w^Tx_i+b) \geq 1 yi(wTxi+b)1,因此我们只需考虑:

arg ⁡ max ⁡ w , b 1 ∣ ∣ w ∣ ∣ (5) \arg \max_{w,b} \frac{1}{||w||} \tag 5 argw,bmaxw1(5)

( 5 ) (5) (5)是一个求解极大值的问题,可以等价于:

arg ⁡ min ⁡ w , b 1 2 ∣ ∣ w ∣ ∣ 2 (6) \arg \min_{w,b} \frac{1}{2}||w||^2 \tag 6 argw,bmin21w2(6)

至此,我们得到了支持向量机的基本形

arg ⁡ min ⁡ w , b 1 2 ∣ ∣ w ∣ ∣ 2 s . t .     y i ( w T x i + b ) ≥ 1 (7) \begin{aligned} & \arg \min_{w,b} \frac{1}{2}||w||^2 \\ \tag7 & s.t. \space \space \space y_i(w^T x_i +b) \geq 1 \end{aligned} argw,bmin21w2s.t.   yi(wTxi+b)1(7)

即求出在约束条件 y i ( w T x i + b ) ≥ 1 y_i(w^T x_i +b) \geq 1 yi(wTxi+b)1下,目标函数 1 2 ∣ ∣ w ∣ ∣ 2 \frac{1}{2}||w||^2 21w2的极小值点。

带约束条件的极值问题求解

上面的式子 ( 7 ) (7) (7)中的目标是很典型的条件极值问题,我们可以采用拉格朗日乘子法来解决。

构造拉格朗日函数

L ( w , b , α ) = 1 2 ∣ ∣ w ∣ ∣ 2 − ∑ i = 1 m α i ( y i ( w T x i + b ) − 1 ) (8) L(w,b,\alpha)=\frac{1}{2}||w||^2-\sum_{i=1}^{m}\alpha_{i}(y_i(w^Tx_i+b)-1) \tag 8 L(w,b,α)=21w2i=1mαi(yi(wTxi+b)1)(8)

L ( w , b , α ) L(w,b,\alpha) L(w,b,α) w w w b b b的偏导为0可得:

{ w = ∑ i = 1 m a i y i x i ∑ i = 1 m a i y i = 0 (9) \left\{ \begin{array}{lr} \begin{aligned} &w=\sum_{i=1}^ma_iy_ix_i \\ \tag 9 &\sum_{i=1}^ma_iy_i=0 \end{aligned} \end{array} \right. w=i=1maiyixii=1maiyi=0(9)

( 9 ) (9) (9)代入 ( 8 ) (8) (8),可得:

L ( w , b , α ) = 1 2 w T w − w T ∑ i = 1 m α i y i x i − b ∑ i = 1 m a i y i + ∑ i = 1 m α i = ∑ i = 1 m α i − 1 2 ( ∑ i = 1 m α i y i x i ) T ∑ i = 1 m α i y i x i = ∑ i = 1 m α i − 1 2 ∑ i = 1 m ∑ j = 1 m α i α j y i y j x i T x j (10) \begin{aligned} L(w,b,\alpha) &= \frac{1}{2}w^Tw-w^T\sum_{i=1}^m\alpha_iy_ix_i-b\sum_{i=1}^ma_iy_i+\sum_{i=1}^m \alpha_i \\ & = \sum_{i=1}^m\alpha_i-\frac{1}{2}(\sum_{i=1}^{m}\alpha_iy_ix_i)^T\sum_{i=1}^{m}\alpha_iy_ix_i\\ &=\sum_{i=1}^m\alpha_i-\frac{1}{2}\sum_{i=1}^m\sum_{j=1}^{m}\alpha_i\alpha_jy_iy_jx_i^Tx_j \end{aligned} \tag {10} L(w,b,α)=21wTwwTi=1mαiyixibi=1maiyi+i=1mαi=i=1mαi21(i=1mαiyixi)Ti=1mαiyixi=i=1mαi21i=1mj=1mαiαjyiyjxiTxj(10)

至此我们得到了 ( 7 ) (7) (7)的对偶问题:

max ⁡ α { ∑ i = 1 m α i − 1 2 ∑ i = 1 m ∑ j = 1 m α i α j y i y j x i T x j } \begin{aligned} &\max_\alpha \left\{ \sum_{i=1}^m\alpha_i-\frac{1}{2}\sum_{i=1}^m\sum_{j=1}^{m}\alpha_i\alpha_jy_iy_jx_i^Tx_j \right\}\\ \end{aligned} αmax{i=1mαi21i=1mj=1mαiαjyiyjxiTxj}

再将减号两边对调,把问题转化为求极小值问题,另外别忘了KKT条件

min ⁡ α { 1 2 ∑ i = 1 m ∑ j = 1 m α i α j y i y j x i T x j − ∑ i = 1 m α i } s . t .     ∑ i = 1 m α i y i = 0            α i ≥ 0            y i ( w T x i + b ) − 1 ≥ 0            a i ( y i ( w T x i + b ) − 1 ) = 0 (11) \begin{aligned} &\min_\alpha \left\{\frac{1}{2}\sum_{i=1}^m\sum_{j=1}^{m}\alpha_i\alpha_jy_iy_jx_i^Tx_j -\sum_{i=1}^m\alpha_i\right\}\\ & s.t. \space \space \space \sum_{i=1}^m\alpha_iy_i=0 \\ \tag {11} &\space \space \space\space \space \space\space \space \space\space \alpha_i \geq 0\\ &\space \space \space\space \space \space\space \space \space\space y_i(w^Tx_i+b)-1 \geq 0\\ &\space \space \space\space \space \space\space \space \space\space a_i(y_i(w^Tx_i+b)-1)=0 \end{aligned} αmin{21i=1mj=1mαiαjyiyjxiTxji=1mαi}s.t.   i=1mαiyi=0          αi0          yi(wTxi+b)10          ai(yi(wTxi+b)1)=0(11)

上式中的 y i y_i yi x i x_i xi都是已知量,代入求得 α \alpha α后,求出 w w w b b b即可得到超平面:

f ( x ) = w T x + b = ∑ i = 1 m α i y i x i T x + b \begin{aligned} f(x)&=w^Tx+b \\ &=\sum_{i=1}^m\alpha_iy_ix_i^Tx+b \end{aligned} f(x)=wTx+b=i=1mαiyixiTx+b

手算简单案例

在这里插入图片描述

如上图所示,红色的圆代表正例,黑色的叉叉代表负例,现在我们要根据支持向量机的原理手算超平面

将样本数据代入至 ( 11 ) (11) (11)

1 2 ( 18 α 1 2 + 25 α 2 2 + 2 α 3 2 + 42 α 1 α 2 − 12 α 1 α 3 − 14 α 2 α 3 ) − α 1 − α 2 − α 3 (12) \frac{1}{2}(18\alpha_1^2+25\alpha_2^2+2\alpha_3^2+42\alpha_1\alpha_2-12\alpha_1\alpha_3-14\alpha_2\alpha_3)-\alpha_1-\alpha_2-\alpha_3 \tag {12} 21(18α12+25α22+2α32+42α1α212α1α314α2α3)α1α2α3(12)

根据约束条件:
∑ i = 1 m ( α i y i ) = 0 \sum_{i=1}^m(\alpha_iy_i)=0 i=1m(αiyi)=0

有:

α 1 + α 2 = α 3 \alpha_1+\alpha_2=\alpha_3 α1+α2=α3

代入消元可得:

4 α 1 2 + 13 2 α 2 2 + 10 α 1 α 2 − 2 α 1 − 2 α 2 (13) 4\alpha_1^2+\frac{13}{2}\alpha_2^2+10\alpha_1\alpha_2-2\alpha_1-2\alpha_2\tag {13} 4α12+213α22+10α1α22α12α2(13)

分别对 α 1 \alpha_1 α1 α 2 \alpha_2 α2求偏导,令偏导等于0:

{ 8 α 1 + 10 α 2 − 2 = 0 13 α 2 + 10 α 1 − 2 = 0 \left\{ \begin{array}{lr} 8\alpha_1+10\alpha_2-2=0 \\ 13\alpha_2+10\alpha_1-2=0 \end{array} \right. {8α1+10α22=013α2+10α12=0

解得:

α 1 = 1.5 α 2 = − 1 \alpha_1=1.5 \\ \alpha_2=-1 α1=1.5α2=1

该结果并不满足 ( 11 ) (11) (11)中的约束 α i ≥ 0 \alpha_i \geq 0 αi0,那么解应该在边界上。

首先试试 α 1 = 0 \alpha_1=0 α1=0,代回 ( 13 ) (13) (13)可得:

{ α 1 = 0 α 2 = 2 13 \left\{ \begin{array}{lr} \begin{aligned} &\alpha_1=0\\ &\alpha_2= \frac{2}{13} \end{aligned} \end{array} \right. α1=0α2=132

然后试试 α 2 = 0 \alpha_2=0 α2=0,代回 ( 13 ) (13) (13)可得:

{ α 1 = 1 4 α 2 = 0 \left\{ \begin{array}{lr} \begin{aligned} &\alpha_1= \frac{1}{4}\\ &\alpha_2= 0 \end{aligned} \end{array} \right. α1=41α2=0

然后分别把以上两组 α \alpha α值代回 ( 13 ) (13) (13),发现第二组计算出的最终结果最小。

因此可以确定:

{ α 1 = 1 4 α 2 = 0 α 3 = 1 4 \left\{ \begin{array}{lr} \begin{aligned} &\alpha_1= \frac{1}{4}\\ &\alpha_2= 0\\ &\alpha_3= \frac{1}{4} \end{aligned} \end{array} \right. α1=41α2=0α3=41

α 1 \alpha_1 α1 α 3 \alpha_3 α3不为0,因此 x 1 x_1 x1 x 3 x_3 x3是在最大间隔边界上的支持向量

最后我们把 α \alpha α的值带入 ( 9 ) (9) (9)求解 w w w

w = ∑ i = 1 m a i y i x i T = ( 0.5 , 0.5 ) w=\sum_{i=1}^ma_iy_ix_i^T=(0.5,0.5) w=i=1maiyixiT=(0.5,0.5)

将支持向量 x 1 x_1 x1或者 x 3 x_3 x3带入超平面模型,求得 b b b
b = y 1 − w x 1 = 1 − 3 = − 2 b = y 3 − w x 3 = − 1 − 1 = − 2 \begin{aligned} &b=y_1 -wx_1=1-3=-2 \\ &b=y_3-wx_3=-1-1=-2 \end{aligned} b=y1wx1=13=2b=y3wx3=11=2

因此求得的超平面为:

0.5 x 1 + 0.5 x 2 − 2 = 0 0.5x_1+0.5x_2-2=0 0.5x1+0.5x22=0

图像效果如图所示:
在这里插入图片描述
红色的实线是超平面,黑色的虚线是最大间隔边界。容易看出,超平面的位置只与支持向量有关,与其他样本点无关。

核函数

之前的例子展现了SVM的一般计算步骤,但是像该例这样线性可分的样本可以看做是特例,更多情况下的样本集是非线性可分的。这时我们就需要用某种变化,将不可分变为可分,类似于下图:
在这里插入图片描述

常用核函数:

  • 多项式核: k ( x , x i ) = ( ( x ∗ x i ) + 1 ) d k(x,x_i)=((x∗x_i)+1)^d k(x,xi)=((xxi)+1)d
  • 高斯核: k ( x , x i ) = exp ⁡ ( − ∣ ∣ x − x i ∣ ∣ 2 σ 2 ) k(x,x_i)=\exp(−\frac{||x−xi||^2}{\sigma^2}) k(x,xi)=exp(σ2xxi2)
  • 线性核: κ ( x , x i ) = x ∗ x i κ(x,x_i)=x∗xi κ(x,xi)=xxi

手写SVM算法

实现的有些烂,后面换种思路。。

import numpy as np
import pandas as pd
import sympy as sp
from itertools import combinations
import sys
import matplotlib.pyplot as plt


class SVM:

    def __init__(self,pdData):
        datas = pdData.values
        flag,a=self.step1(datas)
        if not flag:
            a=self.step2(datas)
        self.step3(datas,a)


    def step1(self,datas):
        m=datas.shape[0]
        self.objective_function=sp.symbols('tmp')
        constraint=sp.symbols('tmp')
        for i in range(1,m+1):
            for j in range(1,m+1):
                a_i=sp.symbols('a'+str(i))
                a_j=sp.symbols('a'+str(j))
                y_i=datas[i-1,datas.shape[1]-1]
                y_j=datas[j-1,datas.shape[1]-1]
                x_i=datas[i-1,0:datas.shape[1]-1]
                x_j=datas[j-1,0:datas.shape[1]-1]
                self.objective_function=self.objective_function+(1/2)*(a_i*a_j*y_i*y_j*np.dot(x_i.T,x_j))
            self.objective_function=self.objective_function-sp.symbols('a'+str(i))
            constraint=constraint+sp.symbols('a'+str(i))*datas[i-1,datas.shape[1]-1]
            
        self.objective_function=self.objective_function-sp.symbols('tmp')
        constraint=constraint-sp.symbols('tmp')
        print('代入样本数据:'+str(self.objective_function))
        print('有约束条件:'+str(constraint))
        self.a1=sp.solve(constraint,sp.symbols('a1'))[0]
        print('a1='+str(self.a1))
        self.objective_function=self.objective_function.subs({'a1':self.a1})
        print('代入换元:'+str(self.objective_function))

        # 对a分别求偏导
        da={}
        for i in range(m-1):
            da_i=sp.diff(self.objective_function,sp.symbols('a'+str(i+2)))
            print('对a'+str(i+2)+'求偏导:'+str(da_i))
            da[sp.symbols('a'+str(i+2))]=da_i

        print('联立求解')
        result=sp.solve(list(da.values()),list(da))
        if result ==[]:
            print('a无解')
            return False,[]
        result[sp.symbols('a1')]=self.a1.subs(result)
        print(result)

        # 判断是否符合约束
        bol=True
        for key in result:
            if result[key]<0:
                bol=False
                break
        if not bol:
            print('不满足约束a>=0,解应在边界上')
            return False,[]
        
        print('a的解满足条件')
        return True,result
    
    def step2(self,datas):
        m=datas.shape[0]
        li=[]
        alist=[]
        for i in range(1,m):
            alist.append(sp.symbols('a'+str(i+1)))
            
        for i in range(1,m-1):
            arrs=list(combinations(alist,i))
            for arr in arrs:
                print('令',end='')  
                subs={} 
                for a_i in arr:
                    print(a_i,end='')
                    subs[a_i]=0
                print('等于0')
                tmp=self.objective_function.subs(subs)
                print('原式变为:'+str(tmp))
               
                dda={}
                for i in range(m-1):
                    da_i=sp.diff(tmp,sp.symbols('a'+str(i+2)))
                    print('对a'+str(i+2)+'求偏导:'+str(da_i))
                    dda[sp.symbols('a'+str(i+2))]=da_i
                print('联立求解')
                dic=sp.solve(list(dda.values()),list(dda))
                if dic ==[]:
                    print('无解')
                    continue
                dic.update(subs)
                dic[sp.symbols('a1')]=self.a1.subs(dic)

                if (np.array(list(dic.values()))==0).all():
                    continue
                print(dic)
                li.append(dic)
                
        _index=-1
        _min_value=sys.maxsize
        print('原式:'+str(self.objective_function))
        for i in range(len(li)):
            print(li[i])
            bol = True
            for key in li[i]:
                if (not type(li[i][key])==float and not type(li[i][key])==int) or li[i][key]<0:
                    bol=False
                    break
                
            if not bol:
                continue

            result=self.objective_function.subs(li[i])
            if result<_min_value:
                _min_value=result
                _index=i
        
        a=li[_index]
        print('计算a的结果:'+str(a))
        return a

    def step3(self,datas,a):
        # 求解w
        self.W=0
        vec_index=-1

        for i in range(len(a)):
            a_i=a[sp.symbols('a'+str(i+1))]
            y_i=datas[i,datas.shape[1]-1]
            x_i=datas[i,0:datas.shape[1]-1]
            self.W=self.W+a_i*y_i*x_i.T

            if not a_i == 0 and vec_index == -1:
                vec_index=i
     
        # 求解b
        x=datas[vec_index,0:datas.shape[1]-1]
        y=datas[vec_index,datas.shape[1]-1]

        self.b=y-np.dot(self.W.T,x)
        print('最优w参数:'+str(self.W)) 
        print('最优b参数:'+str(self.b))

    def __model(self,W,b,X):
        return np.dot(W,X)+b

    def model(self,X):
        exp=sp.symbols('tmp')
        for i in range(len(self.W)):
            exp=exp+self.W[i]*sp.symbols('x'+str(i+1))
        exp=exp-sp.symbols('tmp')
        exp=exp+self.b
        exp=sp.solve(exp,sp.symbols('x2'))[0]
        result=[]
        for x in X:
            result.append(exp.subs({'x1':x}))
        return result

    def classify(self,X):
        result=[]
        for item in X:
            if self.__model(self.W,self.b,item) >0:
                result.append(1)
            elif self.__model(self.W,self.b,item) <0:
                result.append(-1)
            elif self.__model(self.W,self.b,item) ==0:
                result.append(0)
        return result

数据集:

c1c2class
34.6236596245169778.0246928153624-1
30.2867107682260743.89499752400101-1
35.8474087699387272.90219802708364-1
60.1825993862097686.308552095468261
79.032736050710175.34437643691031
45.0832774766833956.3163717815305-1
61.1066645368476696.511425884896241
75.0247455673888946.554013541165381
76.0987867022625787.420569719268031
84.4328199612003543.533393310721091

测试代码:

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from svm import SVM

def gaussian(x1,x2,sigma):
    return np.exp(-((x1-x2)**2)/(1/2*sigma**2))

if __name__ == "__main__":
    pdData=pd.read_csv("LogiReg_data.csv")

    p=pdData[pdData['class']==1].values # 正例
    n=pdData[pdData['class']==-1].values # 负例

    plt.scatter(p[:,0],p[:,1]) # 画出正例散点图
    plt.scatter(n[:,0],n[:,1]) # 画出负例散点图
    svm=SVM(pdData) # 求解SVM
    x=np.arange(0,100,0.01)
    plt.plot(x,svm.model(x)) # 画出超平面

    plt.show()

运行结果:
在这里插入图片描述

  • 11
    点赞
  • 41
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

黄嘉成

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值