算法笔记更新~
SVM(支持向量机),相信有一些机器学习基础的朋友对这个算法应该早已耳熟。SVM是现有的机器学习基础算法里较为能扛的一个。此篇文章偏向实战,对svm背后繁杂而又精致的数学知识不做展开叙述,笔者学习时参考的是东大一位智慧与才情并存的教授在知乎发表的文章:零基础学习SVM,教授讲解的十分详细,引人入胜,层层递进的同时令人不禁感慨数学的美妙!如果你对svm最后的目标函数一无所知,同时又有兴趣探个究竟,强烈安利你移步上述链接。
SVM经过一番推导之后,得到的目标函数为:
对这个优化目标函数,解读为:需要找到一组α,<α1, α2, α3…… >,使得上式值最大。一旦α值确定,分隔超平面也就确定了。
在线性约束条件下优化具有多个变量的二次函数目标函数并不容易,1996年发布的序列最小最优化算法(SMO),用于训练SVM。SMO算法的目标是找出一系列α,从而得到b值,进而计算权重向量w,w与b确定后,分隔超平面也就确定了。
SMO工作原理:每次循环中选择两个α进行优化处理,一旦找到满足条件的两个α,就增大其中一个同时减小另外一个。此处的条件为:(1)两个α必须要在间隔边界之外,(2)还未进行过区间处理或者不在边界上。
简化版SMO算法与完整版SMO算法的主要区别在于α选择方式不同,完整版SMO算法是对简化版SMO算法的优化,旨在加快其运行速度。
简化版SMO:第一个α:依次遍历数据集。第二个α:随机选择
完整版SMO:第一个α,选择方式在两种方式之间交替进行。(1)、在所有数据集上进行单遍扫描。(2)、在所有非边界α中实现单遍扫描。非边界α指的是那些不等于边界0或者C的α值。第二个α:在优化过程中,会通过最大化步长的方式来获得第二个α值。此处的步长指的是两个α对应的实例的分类误差。
##################加载数据集##########################
def loadDataSet(fileName):
dataMat = []; labelMat = []
fr = open(fileName)
for line in fr.readlines():
lineArr = line.strip().split('\t')
dataMat.append([float(lineArr[0]), float(lineArr[1])])
labelMat.append(float(lineArr[2]))
return dataMat,labelMat
进行优化时需要调用的辅助函数
#########随机选择第二个α,与第一个不相等######
def selectJrand(i,m):
j=i #we want to select any J not equal to i
while (j==i):
j = int(random.uniform(0,m))
return j
##########调整新的α对应的上下限#############
def clipAlpha(aj,H,L):
if aj > H:
aj = H
if L > aj:
aj = L
return aj
##########简化版SMO
def smoSimple(dataMatIn, classLabels, C, toler, maxIter):
dataMatrix = mat(dataMatIn); labelMat = mat(classLabels).transpose()
b = 0; m,n = shape(dataMatrix)
alphas = mat(zeros((m,1))) ##所有α初始化为0
iter = 0
while (iter < maxIter):
alphaPairsChanged = 0 #统计成功修改的α对数
for i in range(m):
#第一个α是依次遍历的
fXi = float(multiply(alphas,labelMat).T*(dataMatrix*dataMatrix[i,:].T)) + b
Ei = fXi - float(labelMat[i])#if checks if an example violates KKT conditions
#第一个α可以调整,就开始选择第二个,不可以则进入下一次循环
if ((labelMat[i]*Ei < -toler) and (alphas[i] < C)) or ((labelMat[i]*Ei > toler) and (alphas[i] > 0)):
#随机选择第二个α
j = selectJrand(i,m)
fXj = float(multiply(alphas,labelMat).T*(dataMatrix*dataMatrix[j,:].T)) + b
Ej = fXj - float(labelMat[j])
#保存更新前的两个α值
alphaIold = alphas[i].copy(); alphaJold = alphas[j].copy();
#如果两个点不同类,则α1-α2=k,否则α1+α2=k
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
#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
alphas[j] -= labelMat[j]*(Ei - Ej)/eta
alphas[j] = clipAlpha(alphas[j],H,L)
##若修改量太小,则重新选择α
if (abs(alphas[j] - alphaJold) < 0.00001): print("j not moving enough"); continue
#对i进行同样的修改,方向相反
alphas[i] += labelMat[j]*labelMat[i]*(alphaJold - alphas[j])#update i by the same amount as j
#the update is in the oppostie direction
#根据α1及α2计算出b1、b2值
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
###满足0<α
if (0 < alphas[i]) and (C > alphas[i]): b = b1
elif (0 < alphas[j]) and (C > alphas[j]): b = b2
#否则,对应的实例点在边界面内,或者在两条边界线之间,在决策面方程中的b值取二者中间值
else: b = (b1 + b2)/2.0
##成功修改一对α,统计值+1
alphaPairsChanged += 1
print ("iter: %d i:%d, pairs changed %d"% (iter,i,alphaPairsChanged))
if (alphaPairsChanged == 0): iter += 1
else: iter = 0
print("iteration number: %d" % iter)
return b,alphas
函数功能测试
#########测试简化版SMO############
dataArr,labelArr=loadDataSet('testSet.txt')
b,alphas=smoSimple(dataArr,labelArr,0.6,0.01,40)
print(b)
print(alphas[alphas>0]) #输出α>0的值,0<α
for i in range(100):
if alphas[i]>0.0:print(dataArr[i],labelArr[i]) #打印支持向量及其及对应的类别
最终输出:
[[-3.88824118]] #b值
[[0.12674555 0.24476987 0.37151542]] #α值(α>0)
[4.658191, 3.507396] -1.0 #支持向量的特征及标签
[3.457096, -0.082216] -1.0
[6.080573, 0.418886] 1.0
辅助函数:
##建立数据结构,方便使用对象
class optStruct:
def __init__(self,dataMatIn, classLabels, C, toler): # Initialize the structure with the parameters
self.X = dataMatIn
self.labelMat = classLabels
self.C = C
self.tol = toler #容错率
self.m = shape(dataMatIn)[0]
self.alphas = mat(zeros((self.m,1)))
self.b = 0
self.eCache = mat(zeros((self.m,2))) # 第一列是否有效的标志位,第二列为E值。
##计算E值并返回
def calcE(oS, k):
fXk = float(multiply(oS.alphas,oS.labelMat).T*(oS.X*oS.X[k,:].T)) + oS.b
Ek = fXk - float(oS.labelMat[k])
return Ek
#内循环中的启发式方法,用于选择第二个α。
def selectJ(i, oS, Ei):
maxK = -1; maxDeltaE = 0; Ej = 0
oS.eCache[i] = [1,Ei]
#返回非零E值所对应的α值,如果是第一次循环,则随机选择,如果不是,则选择Ei-Ej最大的α值
validEcacheList = nonzero(oS.eCache[:,0].A)[0]
if (len(validEcacheList)) > 1:
for k in validEcacheList: #loop through valid Ecache values and find the one that maximizes delta E
if k == i: continue #don't calc for i, waste of time
Ek = calcE(oS, k)
deltaE = abs(Ei - Ek)
if (deltaE > maxDeltaE):
maxK = k; maxDeltaE = deltaE; Ej = Ek
return maxK, Ej
else:
j = selectJrand(i, oS.m)
Ej = calcE(oS, j)
return j, Ej
#用于计算误差值E,并将其存入缓存中
def updateE(oS, k):
Ek = calcE(oS, k)
oS.eCache[k] = [1,Ek]
优化过程
###########完整版SMO算法中的优化例程#################
def innerL(i, oS):
Ei = calcE(oS, i) #计算第i个阿尔法值对应的实例的误差
if ((oS.labelMat[i]*Ei < -oS.tol) and (oS.alphas[i] < oS.C)) or ((oS.labelMat[i]*Ei > oS.tol) and (oS.alphas[i] > 0)):
#选择第二个α
j,Ej = selectJ(i, oS, Ei) #this has been changed from selectJrand
#存储更新前的α
alphaIold = oS.alphas[i].copy(); alphaJold = oS.alphas[j].copy();
#如果两个点不同类,则α1-α2=k,否则α1+α2=k
if (oS.labelMat[i] != oS.labelMat[j]):
L = max(0, oS.alphas[j] - oS.alphas[i])
H = min(oS.C, oS.C + oS.alphas[j] - oS.alphas[i])
else:
L = max(0, oS.alphas[j] + oS.alphas[i] - oS.C)
H = min(oS.C, oS.alphas[j] + oS.alphas[i])
if L==H: print ("L==H"); return 0
#eta为最优修改量
eta = 2.0 * oS.X[i,:]*oS.X[j,:].T - oS.X[i,:]*oS.X[i,:].T - oS.X[j,:]*oS.X[j,:].T
if eta >= 0: print("eta>=0"); return 0
oS.alphas[j] -= oS.labelMat[j]*(Ei - Ej)/eta
oS.alphas[j] = clipAlpha(oS.alphas[j],H,L)
#更新eCache,存入Ej值
updateE(oS, j) #added this for the Ecache
if (abs(oS.alphas[j] - alphaJold) < 0.00001): print("j not moving enough"); return 0
#对第一个α以同样步长更新
oS.alphas[i] += oS.labelMat[j]*oS.labelMat[i]*(alphaJold - oS.alphas[j])#update i by the same amount as j
#更新eCache,存入Ei值
updateE(oS, i) #added this for the Ecache #the update is in the oppostie direction
###满足0<α
b1 = oS.b - Ei- oS.labelMat[i]*(oS.alphas[i]-alphaIold)*oS.X[i,:]*oS.X[i,:].T - oS.labelMat[j]*(oS.alphas[j]-alphaJold)*oS.X[i,:]*oS.X[j,:].T
b2 = oS.b - Ej- oS.labelMat[i]*(oS.alphas[i]-alphaIold)*oS.X[i,:]*oS.X[j,:].T - oS.labelMat[j]*(oS.alphas[j]-alphaJold)*oS.X[j,:]*oS.X[j,:].T
if (0 < oS.alphas[i]) and (oS.C > oS.alphas[i]): oS.b = b1
elif (0 < oS.alphas[j]) and (oS.C > oS.alphas[j]): oS.b = b2
else: oS.b = (b1 + b2)/2.0
return 1 #更新成功一对α
else: return 0
主函数,调用此函数即可运行完整版SMO算法
def smoPwithout_K(dataMatIn, classLabels, C, toler, maxIter): #full Platt SMO
#初始化所有数据对象
oS = optStruct(mat(dataMatIn),mat(classLabels).transpose(),C,toler)
iter = 0
entireSet = True; alphaPairsChanged = 0
while (iter < maxIter) and ((alphaPairsChanged > 0) or (entireSet)):
alphaPairsChanged = 0
if entireSet: #go over all
##遍历所有数据集
for i in range(oS.m):
alphaPairsChanged += innerL(i,oS)
print("fullSet, iter: %d i:%d, pairs changed %d" % (iter,i,alphaPairsChanged))
iter += 1
##遍历所有非边界α值
else:
nonBoundIs = nonzero((oS.alphas.A > 0) * (oS.alphas.A < C))[0]
for i in nonBoundIs:
alphaPairsChanged += innerL(i,oS)
print ("non-bound, iter: %d i:%d, pairs changed %d" % (iter,i,alphaPairsChanged))
iter += 1
#控制两种方式交替进行
if entireSet: entireSet = False #toggle entire set loop
elif (alphaPairsChanged == 0): entireSet = True
print("iteration number: %d" % iter)
return oS.b,oS.alphas
函数功能测试:
dataArr,labelArr=loadDataSet('testSet.txt')
b,alphas=smoPwithout_K(dataArr,labelArr,0.6,0.001,40)
print(b)
print(alphas[alphas>0])
for i in range(100):
if alphas[i]>0.0:print(dataArr[i],labelArr[i])
输出:
[[-2.88322544]] #b值
[[0.1069263 0.04393521 0.0165247 0.0651882 0.05993338 0.0277492
0.03089371 0.16949654]] #α值(α>0)
[3.634009, 1.730537] -1.0 #支持向量的特征及标签
[4.658191, 3.507396] -1.0
[3.223038, -0.552392] -1.0
[3.457096, -0.082216] -1.0
[6.960661, -0.245353] 1.0
[2.893743, -1.643468] -1.0
[5.286862, -2.358286] 1.0
[6.080573, 0.418886] 1.0
计算w值:
def calcWs(alphas,dataArr,classLabels):
X = mat(dataArr); labelMat = mat(classLabels).transpose()
m,n = shape(X)
w = zeros((n,1))
for i in range(m):
w += multiply(alphas[i]*labelMat[i],X[i,:].T)
return w
此处仅针对线性可分的数据集。(带核版的SMO不做阐述)
if __name__ == '__main__':
dataMat, labelMat = svm.loadDataSet('testSet.txt')
##简单版SMO
b,alphas=svm.smoSimple(dataMat,labelMat,0.6,0.01,40)
# #完整版SMO
b,alphas =svm.smoPwithout_K(dataMat,labelMat,0.6,0.001,40)
#带核版
#b,alphas =svm.smoPK(dataMat, labelMat, 200, 0.0001, 10000, ('rbf', 1.3))
w =svm.calcWs(alphas,dataMat, labelMat)
visual(dataMat, labelMat,w, b)
简化版SMO输出:
[[0.04892969 0.19730257 0.10204282 0.14944409 0.19883099]] #α值(α>0)
[4.658191, 3.507396] -1.0 #支持向量的特征及标签
[3.457096, -0.082216] -1.0
[2.893743, -1.643468] -1.0
[5.286862, -2.358286] 1.0
[6.080573, 0.418886] 1.0
[[0.04916579 0.05794998 0.16680268 0.02385687 0.02385687 0.10711577
0.16680268]] #α值(α>0)
[3.634009, 1.730537] -1.0 #支持向量的特征及标签
[3.125951, 0.293251] -1.0
[3.223038, -0.552392] -1.0
[3.457096, -0.082216] -1.0
[6.960661, -0.245353] 1.0
[5.286862, -2.358286] 1.0
[6.080573, 0.418886] 1.0
以上就是SMO算法简单版及完整版的具体实现。