机器学习笔记——SVM学习心得

支持向量机(support vector machines)是一个二分类的分类模型(或者叫做分类器)。

  它分类的思想是,给定给一个包含正例和反例的样本集合,svm的目的是寻找一个超平面来对样本根据正例和反例进行分割。 各种资料对它评价甚高,说“ 它在解决小样本、非线性及高维模式识别中表现出许多特有的优势,并能够推广应用到函数拟合等其他机器学习问题中”

一、SVM的基本想法
  回到我们最开始讨论的KNN算法,它占用的内存十分的大,而且需要的运算量也非常大。那么我们有没有可能找到几个最有代表性的点(即保留较少的点)达到一个可比的效果呢?
  要回答这个问题,我们首先必须思考如何确定点的代表性?我想关于代表性至少满足这样一个条件:无论非代表性点存在多少,存在与否都不会影响我们的决策结果。显然如果仍旧使用KNN算法的话,是不会存在训练集的点不是代表点的情况。那么我们应该选择一个怎样的“距离”满足仅依靠代表点就能得到全体点一致的结果吗?先看下面一个例子:假设我们的训练集分为正例与反例两类,分别用红色的圆圈与蓝色的五角星表示,现在出现了两个未知的案例,也就是图中绿色的方块,我们如何去分类这两个例子呢?

机器学习之SVM学习心得

   在KNN算法中我们考虑的是未知样例与已知的训练样例的平均距离,未知样例与正例和反例的“距离”谁更近,那么他就是对应的分类。
   同样是利用距离,我们可以换一个方式去考虑:假设图中的红线是对正例与反例的分类标准(记为w ∙ x+b=0),那么我们的未知样例与红线的“距离”就成了一个表示分类信度的标准,而w ∙ y+b(y为未知样例的数据)的符号则可以看成是分类的标识。
   但是遗憾的是我们不知道这样的一条分类标准(分类线)是什么,那么我们一个比较自然的想法就是从已知的分类数据(训练集)里找到离分割线最近的点,确保他们离分割面尽可能的远。这样我们的分类器会更稳健一些。
   从上面的例子来看,虚线穿过的样例便是离分割线最近的点,这样的点可能是不唯一的,因为分割线并不确定,下图中黑线穿过的训练样例也满足这个要求:


机器学习之SVM学习心得

  所以“他们离分割面尽可能的远”这个要求就十分重要了,他告诉我们一个稳健的超平面是红线而不是看上去也能分离数据的黄线。
  这样就解决了我们一开始提出的如何减少储存量的问题,我们只要存储虚线划过的点即可(因为在w ∙ x+b=-1左侧,w ∙ x+b=1右侧的点无论有多少都不会影响决策)。像图中虚线划过的,距离分割直线(比较专业的术语是超平面)最近的点,我们称之为支持向量。这也就是为什么我们这种分类方法叫做支持向量机的原因。
   至此,我们支持向量机的分类问题转化为了如何寻找最大间隔的优化问题。
   一般的解决方法有两种1是用现成的优化工具包直接求解,2是使用Lagrange Duality找到一种更有效的方法求解。
其中方法2具有两个优点:
a、更好解
b、可以自然地引入核函数,推广到非线性分类
所以这里选用了第二种方法。
  对于上述的最优化问题先需要构造拉格朗日函数,由此引出我们下一个环节,关于数学基础的介绍。
二、数学基础
  Fermat引理,其中的核心告诉我们在该点可微的情况下,如果该点为极值点,则其导数必为0。
2.1 仅含等式约束的优化问题
Lagrange乘子法:

机器学习之SVM学习心得

这是一个等式约束的优化问题,构建Lagrange乘子

机器学习之SVM学习心得

对每个变量和待求参数分别求偏导就得到了极值点的待求集合。
2.2含不等式约束的优化问题(主要是Lagrange Duality)
考虑原始问题:
minxf(x) 
s.t. ci(x)⩽0,i=1,2,⋯,k
hj(x)=0,j=1,2,⋯,l
接下来,引进广义拉格朗日函数(generalized Lagrange function)

机器学习之SVM学习心得

下面来解释一下我们为什么要构建这个函数:
θp(x)=maxα,β;αi⩾0L(x,α,β)
且D为x满足原约束的集合,我们可以很轻松的得到:
minθp(x)=minxϵDf(x)
  等式的前一项为D域上的问题,后一项为全局意义上的问题,这样就将原始的最优化问题转化成了极大极小问题,但是我们知道凸优化问题是我们常解决的一类问题,但是极小极大问题未必是凸优化问题。
  所以我们下面来考虑极小极大问题的对偶问题,对于上述两个问题,存在以下三个定理:
  令原始问题的最优值为d∗d∗,对偶问题的最优值为p∗p∗,这里的原始问题指的是对偶问题对应的原始问题,不是整篇文章最开始的原始问题(虽然结果是一致的)。
  1.假定原始问题和对偶问题均有最优值,则d∗⩽p∗d∗⩽p∗
  2.考虑原始问题和对偶问题,假定函数f(x)f(x)和ci(x)ci(x)为凸函数,hj(x)hj(x)为仿射函数;并且假设不等式约束ci(x)ci(x)是严格可行的,即存在xx,对所有的ii都有ci(x)<0ci(x)<0,则存在x∗x∗,α∗α∗,β∗β∗,使x∗x∗是原始问题的解,α∗α∗,β∗β∗是对偶问题的解,且d∗=p∗=L(x∗,α∗,β∗)d∗=p∗=L(x∗,α∗,β∗)。
  3.对原始问题和对偶问题,假设函数f(x)f(x)和ci(x)ci(x)为凸函数,hj(x)hj(x)为仿射函数;并且假设不等式约束ci(x)ci(x)是严格可行的,则x∗,α∗,β∗x∗,α∗,β∗分别为原始问题和对偶问题的解的充分条件是x∗,α∗,β∗x∗,α∗,β∗满足KKT条件:
 
机器学习之SVM学习心得

三、SVM用于多类分类
SVM本身是一种典型的二分类器,那如何处理现实中的多分类问题呢?
常用的有三种方法:

1.一对多
也就是“一对其余”(One-against-All) 的方式,就是每次仍然解一个两类分类的问题。
这样对于n个样本会得到n个分类器。
但是这种方式可能会出现分类重叠现象或者不可分类现象
而且由于“其余”的数据集过大,这样其实就人为造成了“数据偏斜”的问题

2.一对一
每次选择一个类作为正样本,负样本只用选其余的一个类,这样就避免了数据偏斜的问题。
很明显可以看出这种方法训练出的分类个数是k*(k-1)/2,虽然分类器的个数比上面多了,但是训练阶段所用的总时间却比“一类对其余”方法少很多。
这种方法可能使多个分类器指向同一个类别,所以可以采用“投票”的方式确定哪个类别:哪个分类器获得的票数多就是哪个分类器。
这种方式也会有分类重叠的现象,但是不会有不可分类的情况,因为不可能所有类别的票数都是0。
但是也很容易发现这种方法是分类器的数目呈平方级上升。

3.DAG SVM
假设有1、2、3、4、5五个类,那么可以按照如下方式训练分类器( 这是一个有向无环图,因此这种方法也叫做DAG SVM)
这种方式减少了分类器的数量,分类速度飞快,而且也没有分类重叠和不可分类现象。
但是假如一开始的分类器回答错误,那么后面的分类器没有办法纠正,错误会一直向下累积。
为了减少这种错误累积,根节点的选取至关重要。

 

四、算法程序实现

import time
import random
import numpy as np
import math
import copy
a=np.matrix([[1.2,3.1,3.1]])
#print a.astype(int)
#print a.A
class SVM:
      def __init__(self,data,kernel,maxIter,C,epsilon):
            self.trainData=data
            self.C=C  #惩罚因子
            self.kernel=kernel
            self.maxIter=maxIter
            self.epsilon=epsilon
            self.a=[0 for i in range(len(self.trainData))]
            self.w=[0 for i in range(len(self.trainData[0][0]))]
            self.eCache=[[0,0] for i in range(len(self.trainData))]
            self.b=0
            self.xL=[self.trainData[i][0] for i in range(len(self.trainData))]
            self.yL=[self.trainData[i][1] for i in range(len(self.trainData))]
      def train(self):
            #support_Vector=self.__SMO()
            self.__SMO()
            self.__update()
      def __kernel(self,A,B):
            #核函数 是对输入的向量进行变形 从低维映射到高维度
            res=0
            if self.kernel=='Line':
                  res=self.__Tdot(A,B)
            elif self.kernel[0]=='Gauss':
                  K=0
                  for m in range(len(A)):
                       K+=(A[m]-B[m])**2
                  res=math.exp(-0.5*K/(self.kernel[1]**2))
            return res

      def __Tdot(self,A,B):
            res=0
            for k in range(len(A)):
                  res+=A[k]*B[k]
            return res

      def __SMO(self):
            #SMO是基于 KKT 条件的迭代求解最优化问题算法
            #SMO是SVM的核心算法
            support_Vector=[]
            self.a=[0 for i in range(len(self.trainData))]
            pre_a=copy.deepcopy(self.a)
            for it in range(self.maxIter):
                  flag=1
                  for i in range(len(self.xL)):
                        #print self.a
                        #更新 self.a  使用 机器学习实战的求解思路
                        #计算 j更新
                        diff=0
                        self.__update()
                        #选择有最大误差的j 丹麦理工大学的算法是 对j在数据集上循环, 随机选取i 显然效率不是很高
                        #机器学习实战 硬币书表述正常 代码混乱且有错误 启发式搜索
                        Ei=self.__calE(self.xL[i],self.yL[i])
                        j,Ej=self.__chooseJ(i,Ei)
                        #计算 L H
                        (L,H)=self.__calLH(pre_a,j,i)
                        #思路是先表示为self.a[j] 的唯一变量的函数 再进行求导(一阶导数=0 更新)
                       kij=self.__kernel(self.xL[i],self.xL[i])+self.__kernel(self.xL[j],self.xL[j])-2*self.__kernel(self.xL[i],self.xL[j])
                        #print kij,"aa"
                        if(kij==0):
                              continue
                        self.a[j] = pre_a[j] + float(1.0*self.yL[j]*(Ei-Ej))/kij
                        #下届是L 也就是截距,小于0时为0
                        #上届是H 也就是最大值,大于H时为H
                        self.a[j] = min(self.a[j], H)
                        self.a[j] = max(self.a[j], L)
                        #self.a[j] = min(self.a[j], H)
                        #print L,H
                        self.eCache[j]=[1,self.__calE(self.xL[j],self.yL[j])]
                        self.a[i] = pre_a[i]+self.yL[i]*self.yL[j]*(pre_a[j]-self.a[j])
                        self.eCache[i]=[1,self.__calE(self.xL[i],self.yL[i])]
                        diff=sum([abs(pre_a[m]-self.a[m]) for m in range(len(self.a))])
                        #print diff,pre_a,self.a
                        if diff < self.epsilon:
                              flag=0
                        pre_a=copy.deepcopy(self.a)
                  if flag==0:
                        print it,"break"
                        break
            #return support_Vector
      def __chooseJ(self,i,Ei):
            self.eCache[i]=[1,Ei]
            chooseList=[]
            #print self.eCache
            #从误差缓存中得到备选的j的列表 chooseList  误差缓存的作用:解决初始选择问题
            for p in range(len(self.eCache)):
                  if self.eCache[p][0]!=0 and p!=i:
                        chooseList.append(p)
            if len(chooseList)>1:
                  delta_E=0
                  maxE=0
                  j=0
                  Ej=0
                  for k in chooseList:
                        Ek=self.__calE(self.xL[k],self.yL[k])
                        delta_E=abs(Ek-Ei)
                        if delta_E>maxE:
                              maxE=delta_E
                              j=k
                              Ej=Ek
                  return j,Ej
            else:
                  #最初始状态
                  j=self.__randJ(i)
                  Ej=self.__calE(self.xL[j],self.yL[j])
                  return j,Ej
      def __randJ(self,i):
            j=i
            while(j==i):
                  j=random.randint(0,len(self.xL)-1)
            return j
      def __calLH(self,pre_a,j,i):
            if(self.yL[j]!= self.yL[i]):
                  return (max(0,pre_a[j]-pre_a[i]),min(self.C,self.C-pre_a[i]+pre_a[j]))
            else:
                  return (max(0,-self.C+pre_a[i]+pre_a[j]),min(self.C,pre_a[i]+pre_a[j]))
      def __calE(self,x,y):
            #print x,y
            y_,q=self.predict(x)
            return y_-y
      def __calW(self):
            self.w=[0 for i in range(len(self.trainData[0][0]))]
            for i in range(len(self.trainData)):
                  for j in range(len(self.w)):
                        self.w[j]+=self.a[i]*self.yL[i]*self.xL[i][j]
      def __update(self):
            #更新 self.b 和 self.w
            self.__calW()
            #得到了self.w 下面求b
            #print self.a
            maxf1=-99999
            min1=99999
            for k in range(len(self.trainData)):
                  y_v=self.__Tdot(self.w,self.xL[k])
                  #print y_v
                  if self.yL[k]==-1:
                        if y_v>maxf1:
                              maxf1=y_v
                  else:
                        if y_v                              min1=y_v
            self.b=-0.5*(maxf1+min1)
      def predict(self,testData):
            pre_value=0
            #从trainData 改成 suport_Vector
            for i in range(len(self.trainData)):
                  pre_value+=self.a[i]*self.yL[i]*self.__kernel(self.xL[i],testData)
            pre_value+=self.b
            #print pre_value,"pre_value"
            if pre_value<0:
                  y=-1
            else:
                  y=1
            return y,abs(pre_value-0)
      def save(self):
            pass
 

def LoadSVM():
      pass
参考文献:
【1】zouxy09的博客:http://blog.csdn.net/zouxy09/article/details/17291543
【2】Jasper的博客:http://www.blogjava.net/zhenandaci/archive/2009/02/13/254519.html
【3】pluskid的博客:http://blog.pluskid.org/?p=685
【4】《机器学习 》,周志华著
【5】《统计学习方法》,李航著
【6】《机器学习实战》Peter Harrington著

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值