前言:
SMO 是求解SVM的一种算法(顺序最小化算法)sequential minimal optimization ,由1998年Platt提出。
主要解如下凸二次规划对偶问题:
求解过程中 里面关于二次规划算法收敛性证明,看了很多文档一直没找到。
目录:
- SMO简介
- 二次规划解析方法
- 变量选择方法
- SMO 算法
- 例子
一 SMO简介
SMO是一种启发式算法:
如果所有的变量满足KKT条件,最优化问题的解就得到了。
否则选择两个变量,固定其它变量,针对这两个变量构建一个二次规划问题。
整个SMO算法包括两个部分:
a 求解二次规划的解析方法
b 选择变量的启发方法
二 二次规划解析
SMO 求解的子问题为:
首先分析约束条件,在满足约束条件下,求解极小值
假设初始可行解为,最优解为
,其中
因为:
2.1 当 ,根据
则:
2.2 当
则
为了计算方便,记
定理1:
沿着约束方向,未经剪辑的解是
其中:
经过剪辑后 的解是:
接着求
证明:
引入记号
目标函数可写成:
因为:
得到关于的目标函数:
对 求导:
:
带入 得到
三 变量的选择
3.1 第一个变量选择(外层循环)
选取违反KKT最严重的样本点,并将其对应的变量作为第一个变量
其中:
3.2 第二个变量选择(内层循环)
选择
最大的
3.3 计算阀值b 和差值
...................1
因为:
带入1:
同理
如果
如果 是0 或者C, 选择它们的中点作为
四 SMO算法
输入:
训练数据集
精度
C: 参数上限
输出:
;;;;;;;;;;;;;流程;;;;;;;;;;;;;;;;;;;;
- 取初始值
, k 代表迭代次数
- 选取优化变量
, 解析求解两个变量的最优化问题, 得到新的
3 若在精度 满足停止条件
转4,否则令k=k+1,转2
4
五 例子:
高斯核
线性核
CODE
# -*- coding: utf-8 -*-
"""
Created on Wed Nov 20 17:03:44 2019
@author: chengxf2
"""
import numpy as np
import os
import matplotlib.pyplot as plt
class SVM:
def DrawPot(self):
fig = plt.figure()
ax = fig.add_subplot(111)
for i in range(self.m):
x1 = self.dataMat[i,0]
x2 = self.dataMat[i,1]
label = self.labelMat[i,0]
if i in self.svIndex:
if label == -1:
ax.scatter(x1, x2, c = 'red' ,marker= 'o',s=80)
else:
ax.scatter(x1, x2, c = 'green' , marker= 's',s=80)
else:
ax.scatter(x1, x2, c ='black', marker= 'x',s=10)
plt.xlabel("x1")
plt.ylabel("x2")
plt.show()
"""
核函数
Args
dataMat: 数据集
z: 样本
return
k: 核函数
"""
def Kernel(self, dataMat, z):
m,n = np.shape(dataMat)
k = np.mat(np.zeros((m, 1)))
key = self.opt[0]
para = self.opt[1]
#print("\n key : ",key)
if "linear"==key:
k = dataMat*z.T
elif "Gauss"==key:
for j in range(m):
x = dataMat[j,:]
# print("\n x: ",x)
delta = x-z
k[j]=delta*delta.T
sigma = -para**2
k = np.exp(k/sigma)
else:
raise NameError("Error")
return k
"""
加载数据集
Args
None
return
None
"""
def LoadData(self):
file = open(self.path)
lines = file.readlines()
data = []
label =[]
for line in lines:
arr = line.strip().split('\t')
data.append([float(arr[0]),float(arr[1])])
label.append(float(arr[2]))
self.labelMat =np.mat(label).T # 标签集 #100 *1
self.dataMat = np.mat(data) ##数据集
self.m, self.n = np.shape(self.dataMat)
self.alpha = np.mat(np.zeros((self.m,1))) #拉格朗日乘子
self.eCache = np.mat(np.zeros((self.m,2))) ##[0,Ei], 第一位是标志位(0,1),第二位是Ei: g(xi)-yi
self.KMat = np.mat(np.zeros((self.m, self.m))) ##Gram矩阵,核函数
for i in range(self.m):
self.KMat[:,i] = self.Kernel(self.dataMat, self.dataMat[i,:])
#print("\n 核函数: \n",self.k)
"""
计算当前预测值和实际值的差别
Args
i: 样本点
return
Ei: 预测值和实际值的差
"""
def CalcE(self, i):
para = np.multiply(self.alpha, self.labelMat).T*self.KMat[:,i] ##(m,1)
#print("para ",para)
gx = float(para+self.b)
ei = gx - self.labelMat[i,0]
#print("\n ",ei)
return ei
"""
产生一个不同于i的值
Args
i: 当前的i
return
j
"""
def RandJ(self, i):
j = i
while(j==i):
j = np.random.uniform(0,self.m)
return int(j)
"""
选择J
Args
i: 样本点
Ei: 差值
return
j: 对应的alphaJ
Ej: 对应的差值
"""
def ChooseJ(self, i, Ei):
maxJ = -1; maxDiff = 0.0; Ej = 0.0;
self.eCache[i]=[1,Ei]
eList = np.nonzero(self.eCache[:,0].A)[0]
n = len(eList)
if n>1: ###循环1
for e in eList:
if e == i:
continue
Ek = self.CalcE(e)
diffE = abs(Ei-Ek)
if diffE>maxDiff:
maxDiff = diffE; maxJ = e; Ej = Ek
return maxJ, Ej ######***********************************逻辑差一点
else:
j = self.RandJ(i)
Ej = self.CalcE(j)
return j, Ej
"""
编辑参数
Args
aj: 参数
H: 上限
L : 下限
return
aj: 剪辑过的参数
"""
def ClipAlpha(self, aj, H, L):
if aj>H:
aj = H
elif aj<L:
aj = L
return aj
"""
当前的点是否违反KKT
Args
i: 当前的样本点
Ei: g-lable 预测值减去实际值
alpha+u = C
u*tol = 0
1: 当alpha (0,C) => u=(0,c),tol = 0, 则xi落在间隔边界上,yg-1 = 0=> yEi = 0
2: 当alpha = C=>u=0, yg-1+tol=0 =>yEi+tol = 0,只能落在分离平面和间隔边界间,
3: 当 alpha =0=> u=C, tol =0 : yg>=1 => yEi>=0
"""
def VolidataKKT(self, i, Ei):
ZERO = 1e-3
"""
bKKT = ((self.labelMat[i,0]*Ei<-self.tol) and (self.alpha[i,0]<self.c)) or \
((self.labelMat[i,0]*Ei>self.tol) and (self.alpha[i,0]>0))
"""
label = self.labelMat[i,0]
if self.alpha[i,0]>0 and self.alpha[i,0]<self.c :## 落在支撑平面上 #yg=1
if abs(label*Ei)>ZERO:
return True
else:
return False
if abs(self.alpha[i,0]-self.c)<=ZERO: ## alpha =c,ui=0,yg-1+tol=0 落在支撑平面内 yg <=1
if abs(label*Ei+self.tol)>ZERO:
return True
else:
return False
if abs(self.alpha[i,0])<ZERO: ## ui=C, tol =0 ,当前的点不起作用, yg>=1 就可以了
if label*Ei<ZERO:
return True
else:
return False
return False
"""
保存差值矩阵
Args:
i
return
None
"""
def SaveE(self, i):
Ei = self.CalcE(i)
self.eCache[i]=[1,Ei]
"""
进入内循环
Args
i: 当前样本点
先检测alphaI是否违反KKT,如果违反,再找到alphaJ,更新对应的参数
return
1: 进行了优化
0:未优化
"""
def InnerLoop(self, i):
Ei = self.CalcE(i)
bKKT = self.VolidataKKT(i, Ei)
if bKKT:
j, Ej = self.ChooseJ(i, Ei) ##选择合适的alphaJ,用来更新
IOld = self.alpha[i,0].copy()
JOld = self.alpha[j,0].copy()
###确定上下界
if (self.labelMat[i,0]!= self.labelMat[j,0]):
L = max (0, self.alpha[j,0]-self.alpha[i,0])
H = min(self.c, self.c+self.alpha[j,0]-self.alpha[i,0] )
else:
L = max(0, self.alpha[j,0]+self.alpha[i,0]-self.c)
H = min(self.c, self.alpha[j,0]+self.alpha[i,0])
if L == H:
return 0
eta = 2.0*self.KMat[i,j]-self.KMat[i,i]-self.KMat[j,j]
if eta>=0:
return 0
self.alpha[j]-= self.labelMat[j,0]*(Ei-Ej)/eta
self.alpha[j]= self.ClipAlpha(self.alpha[j,0],H,L)
self.SaveE(j)
if abs(self.alpha[j,0]-JOld)<1e-5:
return 0
self.alpha[i] +=self.labelMat[i,0]*self.labelMat[j,0]*(JOld- self.alpha[j,0])
self.SaveE(i)
b1 = -Ei- self.labelMat[i]*self.KMat[i,i]*(self.alpha[i]-IOld)-self.labelMat[j]*self.KMat[i,j]*(self.alpha[j]-JOld)+self.b
b2 = -Ej- self.labelMat[i]*self.KMat[i,j]*(self.alpha[i]-IOld)-self.labelMat[j]*self.KMat[j,j]*(self.alpha[j]-JOld)+self.b
if (self.alpha[i]>0 and self.alpha[i]<self.c):
self.b = b1
elif(self.alpha[j]>0 and self.alpha[j]<self.c):
self.b = b2
else:
self.b =(b1+b2)/2.0
return 1
else:
return 0
"""
分类
Args
testMat: 测试的样本
testLabel: 测试的标签
"""
def classification(self, testMat, testLabel):
m,n = np.shape(testMat)
print("\n *****classification******** ")
para = np.multiply(self.sptLabel, self.alpha[self.svIndex]) ##(1,35)
print("\n para ",np.shape(para))
errorNum = 0
for i in range(m):
z = testMat[i,:]
kMat =self.Kernel(self.sptVects, z) ##转换为核函数
pred = kMat.T*para+self.b
#print("\n pred ",pred)
if np.sign(pred) != np.sign(testLabel[i]):
errorNum = errorNum+1
print("错误率:::::::::::: ", errorNum/m)
def __init__(self):
self.b = 0
self.m = 0
self.path = os.path.abspath("testSet.txt")
self.c =1.0 ##惩罚因子 alpha取值上限
self.tol = 0.1 ##相当于软间隔
self.maxIter = 10000## 迭代次数
self.opt = ("linear", 1.0) ##核函数 linear Gauss
self.LoadData()
"""
训练
Args:
none
return
b:
sptVects: 支持向量的点
"""
def Train(self):
print("\n **********Train************** \n")
step = 0 ##外层迭代轮数
entireFlag = True ##完整的遍历
alphaPairsChanged = 0 ##是否由参数发生变更
while(step <self.maxIter) and ((alphaPairsChanged>0) or entireFlag):
alphaPairsChanged = 0
if entireFlag :
for i in range(self.m):
alphaPairsChanged +=self.InnerLoop(i)
step +=1
else:
nonBoundIs = np.nonzero((self.alpha.A>0)*(self.alpha.A<self.c))[0]
for i in nonBoundIs:
alphaPairsChanged +=self.InnerLoop(i)
step +=1
if entireFlag:
entireFlag = False ##切换标志位
elif (alphaPairsChanged ==0):
entireFlag = True
print("\n **********迭代次数************** \n",step)
#print("\n alpha: ",self.alpha)
print("\n: b: ",self.b)
self.svIndex = np.nonzero((self.alpha.A>0)*(self.alpha.A<self.c))[0] ##支持向量的点
self.sptVects = self.dataMat[self.svIndex] ##支持向量的点
self.sptLabel = self.labelMat[self.svIndex] ##支持向量点的标签
#print("\n svIndex ",self.svIndex)
#print("\n svIndex ",self.sptLabel)
self.classification(self.dataMat, self.labelMat)
self.DrawPot()
SVM = SVM()
SVM.Train()
参考文档
《机器学习与应用》
《统计学习方法》