做机器学习的一定对支持向量机(support vector machine-SVM)颇为熟悉,因为在深度学习出现之前,SVM一直霸占着机器学习老大哥的位子。他的理论很优美,各种变种改进版本也很多,比如latent-SVM, structural-SVM等。这节先来看看SVM的理论吧,在(图一)中A图表示有两类的数据集,图B,C,D都提供了一个线性分类器来对数据进行分类?但是哪个效果好一些?
(图一)
可能对这个数据集来说,三个的分类器都一样足够好了吧,但是其实不然,这个只是训练集,现实测试的样本分布可能会比较散一些,各种可能都有,为了应对这种情况,我们要做的就是尽可能的使得线性分类器离两个数据集都尽可能的远,因为这样就会减少现实测试样本越过分类器的风险,提高检测精度。这种使得数据集到分类器之间的间距(margin)最大化的思想就是支持向量机的核心思想,而离分类器距离最近的样本成为支持向量。既然知道了我们的目标就是为了寻找最大边距,怎么寻找支持向量?如何实现?下面以(图二)来说明如何完成这些工作。
(图二)
假设(图二)中的直线表示一个超面,为了方面观看显示成一维直线,特征都是超面维度加一维度的,图中也可以看出,特征是二维,而分类器是一维的。如果特征是三维的,分类器就是一个平面。假设超面的解析式为,那么点A到超面的距离为
,下面给出这个距离证明:
(图三)
在(图三)中,青色菱形表示超面,Xn为数据集中一点,W是超面权重,而且W是垂直于超面的。证明垂直很简单,假设X’和X’’都是超面上的一点,
,因此W垂直于超面。知道了W垂直于超面,那么Xn到超面的距离其实就是Xn和超面上任意一点x的连线在W上的投影,如(图四)所示:
(图四)
而(Xn-X)在W上的投影可通过(公式一)来计算,另外(公式一)也一并完成距离计算:
(公式一)
注意最后使用了配项法并且用了超面解析式才得出了距离计算。有了距离就可以来推导我们刚开始的想法:使得分类器距所有样本距离最远,即最大化边距,但是最大化边距的前提是我们要找到支持向量,也就是离分类器最近的样本点,此时我们就要完成两个优化任务,找到离分类器最近的点(支持向量),然后最大化边距。如(公式二)所示:
(公式二)
大括号里面表示找到距离分类超面最近的支持向量,大括号外面则是使得超面离支持向量的距离最远,要优化这个函数相当困难,目前没有太有效的优化方法。但是我们可以把问题转换一下,如果我们把大括号里面的优化问题固定住,然后来优化外面的就很容易了,可以用现在的优化方法来求解,因此我们做一个假设,假设大括号里的分子等于1,那么我们只剩下优化W咯,整个优化公式就可以写成(公式三)的形式:
(公式三)
这下就简单了,有等式约束的优化,约束式子为,这个约束等式背后还有个小窍门,假设我们把样本Xn的标签设为1或者-1,当Xn在超面上面(或者右边)时,带入超面解析式得到大于0的值,乘上标签1仍然为本身,可以表示离超面的距离;当Xn在超面下面(或者左边)时,带入超面解析式得到小于0的值,乘上标签-1也是正值,仍然可以表示距离,因此我们把通常两类的标签0和1转换成-1和1就可以把标签信息完美的融进等式约束中,(公式三)最后一行也体现出来咯。下面继续说优化 求解(公式四)的方法,在最优化中,通常我们需要求解的最优化问题有如下几类:
(i)无约束优化问题,可以写为:
min f(x);
(ii)有等式约束的优化问题,可以写为:
min f(x),
s.t. h_i(x) = 0; i =1, ..., n
(iii)有不等式约束的优化问题,可以写为:
min f(x),
s.t. g_i(x) <= 0; i =1, ..., n
h_j(x) = 0; j =1,..., m
对于第(i)类的优化问题,常常使用的方法就是Fermat定理,即使用求取f(x)的导数,然后令其为零,可以求得候选最优值,再在这些候选值中验证;如果是凸函数,可以保证是最优解。
对于第(ii)类的优化问题,常常使用的方法就是拉格朗日乘子法(LagrangeMultiplier),即把等式约束h_i(x)用一个系数与f(x)写为一个式子,称为拉格朗日函数,而系数称为拉格朗日乘子。通过拉格朗日函数对各个变量求导,令其为零,可以求得候选值集合,然后验证求得最优值。
对于第(iii)类的优化问题,常常使用的方法就是KKT条件。同样地,我们把所有的等式、不等式约束与f(x)写为一个式子,也叫拉格朗日函数,系数也称拉格朗日乘子,通过一些条件,可以求出最优值的必要条件,这个条件称为KKT条件。
而(公式三)很明显符合第二类优化方法,因此可以使用拉格朗日乘子法来对其求解,在求解之前,我们先对(公式四)做个简单的变换。最大化||W||的导数可以最小化||W||或者W’W,如(公式四)所示:
(公式四)
套进拉格朗日乘子法公式得到如(公式五)所示的样子:
(公式五)
在(公式五)中通过拉格朗日乘子法函数分别对W和b求导,为了得到极值点,令导数为0,得到
,然后把他们代入拉格朗日乘子法公式里得到(公式六)的形式:
(公式六)
(公式六)后两行是目前我们要求解的优化函数,现在只需要做个二次规划即可求出alpha,二次规划优化求解如(公式七)所示:
(公式七)
通过(公式七)求出alpha后,就可以用(公式六)中的第一行求出W。到此为止,SVM的公式推导基本完成了,可以看出数学理论很严密,很优美,尽管有些同行们认为看起枯燥,但是最好沉下心来从头看完,也不难,难的是优化。二次规划求解计算量很大,在实际应用中常用SMO(Sequential minimal optimization)算法,SMO算法打算放在下节结合代码来说。
上面基本完成了SVM的理论推倒,寻找最大化间隔的目标最终转换成求解拉格朗日乘子变量alpha的求解问题,求出了alpha即可求解出SVM的权重W,有了权重也就有了最大间隔距离,但是其实上节我们有个假设:就是训练集是线性可分的,这样求出的alpha在[0,infinite]。但是如果数据不是线性可分的呢?此时我们就要允许部分的样本可以越过分类器,这样优化的目标函数就可以不变,只要引入松弛变量即可,它表示错分类样本点的代价,分类正确时它等于0,当分类错误时
,其中Tn表示样本的真实标签-1或者1,回顾上节中,我们把支持向量到分类器的距离固定为1,因此两类的支持向量间的距离肯定大于1的,当分类错误时
肯定也大于1,如(图五)所示(这里公式和图标序号都接上一节)。
(图五)
这样有了错分类的代价,我们把上节(公式四)的目标函数上添加上这一项错分类代价,得到如(公式八)的形式:
(公式八)
重复上节的拉格朗日乘子法步骤,得到(公式九):
(公式九)
多了一个Un乘子,当然我们的工作就是继续求解此目标函数,继续重复上节的步骤,求导得到(公式十):
(公式十)
又因为alpha大于0,而且Un大于0,所以0<alpha<C,为了解释的清晰一些,我们把(公式九)的KKT条件也发出来(上节中的第三类优化问题),注意Un是大于等于0:
推导到现在,优化函数的形式基本没变,只是多了一项错分类的价值,但是多了一个条件,0<alpha<C,C是一个常数,它的作用就是在允许有错误分类的情况下,控制最大化间距,它太大了会导致过拟合,太小了会导致欠拟合。接下来的步骤貌似大家都应该知道了,多了一个C常量的限制条件,然后继续用SMO算法优化求解二次规划,但是我想继续把核函数也一次说了,如果样本线性不可分,引入核函数后,把样本映射到高维空间就可以线性可分,如(图六)所示的线性不可分的样本:
(图六)
在(图六)中,现有的样本是很明显线性不可分,但是加入我们利用现有的样本X之间作些不同的运算,如(图六)右边所示的样子,而让f作为新的样本(或者说新的特征)是不是更好些?现在把X已经投射到高维度上去了,但是f我们不知道,此时核函数就该上场了,以高斯核函数为例,在(图七)中选几个样本点作为基准点,来利用核函数计算f,如(图七)所示:
(图七)
这样就有了f,而核函数此时相当于对样本的X和基准点一个度量,做权重衰减,形成依赖于x的新的特征f,把f放在上面说的SVM中继续求解alpha,然后得出权重就行了,原理很简单吧,为了显得有点学术味道,把核函数也做个样子加入目标函数中去吧,如(公式十一)所示:
(公式十一)
其中K(Xn,Xm)是核函数,和上面目标函数比没有多大的变化,用SMO优化求解就行了,代码如下:
1 def smoPK(dataMatIn, classLabels, C, toler, maxIter): #full Platt SMO 2 oS = optStruct(mat(dataMatIn),mat(classLabels).transpose(),C,toler) 3 iter = 0 4 entireSet = True; alphaPairsChanged = 0 5 while (iter < maxIter) and ((alphaPairsChanged > 0) or (entireSet)): 6 alphaPairsChanged = 0 7 if entireSet: #go over all 8 for i in range(oS.m): 9 alphaPairsChanged += innerL(i,oS) 10 print "fullSet, iter: %d i:%d, pairs changed %d" % (iter,i,alphaPairsChanged) 11 iter += 1 12 else:#go over non-bound (railed) alphas 13 nonBoundIs = nonzero((oS.alphas.A > 0) * (oS.alphas.A < C))[0] 14 for i in nonBoundIs: 15 alphaPairsChanged += innerL(i,oS) 16 print "non-bound, iter: %d i:%d, pairs changed %d" % (iter,i,alphaPairsChanged) 17 iter += 1 18 if entireSet: entireSet = False #toggle entire set loop 19 elif (alphaPairsChanged == 0): entireSet = True 20 print "iteration number: %d" % iter 21 return oS.b,oS.alphas
下面演示一个小例子,手写识别。
(1)收集数据:提供文本文件
(2)准备数据:基于二值图像构造向量
(3)分析数据:对图像向量进行目测
(4)训练算法:采用两种不同的核函数,并对径向基函数采用不同的设置来运行SMO算法。
(5)测试算法:编写一个函数来测试不同的核函数,并计算错误率
(6)使用算法:一个图像识别的完整应用还需要一些图像处理的只是,此demo略。
完整代码如下:
1 from numpy import * 2 from time import sleep 3 4 def loadDataSet(fileName): 5 dataMat = []; labelMat = [] 6 fr = open(fileName) 7 for line in fr.readlines(): 8 lineArr = line.strip().split('\t') 9 dataMat.append([float(lineArr[0]), float(lineArr[1])]) 10 labelMat.append(float(lineArr[2])) 11 return dataMat,labelMat 12 13 def selectJrand(i,m): 14 j=i #we want to select any J not equal to i 15 while (j==i): 16 j = int(random.uniform(0,m)) 17 return j 18 19 def clipAlpha(aj,H,L): 20 if aj > H: 21 aj = H 22 if L > aj: 23 aj = L 24 return aj 25 26 def smoSimple(dataMatIn, classLabels, C, toler, maxIter): 27 dataMatrix = mat(dataMatIn); labelMat = mat(classLabels).transpose() 28 b = 0; m,n = shape(dataMatrix) 29 alphas = mat(zeros((m,1))) 30 iter = 0 31 while (iter < maxIter): 32 alphaPairsChanged = 0 33 for i in range(m): 34 fXi = float(multiply(alphas,labelMat).T*(dataMatrix*dataMatrix[i,:].T)) + b 35 Ei = fXi - float(labelMat[i])#if checks if an example violates KKT conditions 36 if ((labelMat[i]*Ei < -toler) and (alphas[i] < C)) or ((labelMat[i]*Ei > toler) and (alphas[i] > 0)): 37 j = selectJrand(i,m) 38 fXj = float(multiply(alphas,labelMat).T*(dataMatrix*dataMatrix[j,:].T)) + b 39 Ej = fXj - float(labelMat[j]) 40 alphaIold = alphas[i].copy(); alphaJold = alphas[j].copy(); 41 if (labelMat[i] != labelMat[j]): 42 L = max(0, alphas[j] - alphas[i]) 43 H = min(C, C + alphas[j] - alphas[i]) 44 else: 45 L = max(0, alphas[j] + alphas[i] - C) 46 H = min(C, alphas[j] + alphas[i]) 47 if L==H: print "L==H"; continue 48 eta = 2.0 * dataMatrix[i,:]*dataMatrix[j,:].T - dataMatrix[i,:]*dataMatrix[i,:].T - dataMatrix[j,:]*dataMatrix[j,:].T 49 if eta >= 0: print "eta>=0"; continue 50 alphas[j] -= labelMat[j]*(Ei - Ej)/eta 51 alphas[j] = clipAlpha(alphas[j],H,L) 52 if (abs(alphas[j] - alphaJold) < 0.00001): print "j not moving enough"; continue 53 alphas[i] += labelMat[j]*labelMat[i]*(alphaJold - alphas[j])#update i by the same amount as j 54 #the update is in the oppostie direction 55 b1 = b - Ei- labelMat[i]*(alphas[i]-alphaIold)*dataMatrix[i,:]*dataMatrix[i,:].T - labelMat[j]*(alphas[j]-alphaJold)*dataMatrix[i,:]*dataMatrix[j,:].T 56 b2 = b - Ej- labelMat[i]*(alphas[i]-alphaIold)*dataMatrix[i,:]*dataMatrix[j,:].T - labelMat[j]*(alphas[j]-alphaJold)*dataMatrix[j,:]*dataMatrix[j,:].T 57 if (0 < alphas[i]) and (C > alphas[i]): b = b1 58 elif (0 < alphas[j]) and (C > alphas[j]): b = b2 59 else: b = (b1 + b2)/2.0 60 alphaPairsChanged += 1 61 print "iter: %d i:%d, pairs changed %d" % (iter,i,alphaPairsChanged) 62 if (alphaPairsChanged == 0): iter += 1 63 else: iter = 0 64 print "iteration number: %d" % iter 65 return b,alphas 66 67 def kernelTrans(X, A, kTup): #calc the kernel or transform data to a higher dimensional space 68 m,n = shape(X) 69 K = mat(zeros((m,1))) 70 if kTup[0]=='lin': K = X * A.T #linear kernel 71 elif kTup[0]=='rbf': 72 for j in range(m): 73 deltaRow = X[j,:] - A 74 K[j] = deltaRow*deltaRow.T 75 K = exp(K/(-1*kTup[1]**2)) #divide in NumPy is element-wise not matrix like Matlab 76 else: raise NameError('Houston We Have a Problem -- \ 77 That Kernel is not recognized') 78 return K 79 80 class optStruct: 81 def __init__(self,dataMatIn, classLabels, C, toler, kTup): # Initialize the structure with the parameters 82 self.X = dataMatIn 83 self.labelMat = classLabels 84 self.C = C 85 self.tol = toler 86 self.m = shape(dataMatIn)[0] 87 self.alphas = mat(zeros((self.m,1))) 88 self.b = 0 89 self.eCache = mat(zeros((self.m,2))) #first column is valid flag 90 self.K = mat(zeros((self.m,self.m))) 91 for i in range(self.m): 92 self.K[:,i] = kernelTrans(self.X, self.X[i,:], kTup) 93 94 def calcEk(oS, k): 95 fXk = float(multiply(oS.alphas,oS.labelMat).T*oS.K[:,k] + oS.b) 96 Ek = fXk - float(oS.labelMat[k]) 97 return Ek 98 99 def selectJ(i, oS, Ei): #this is the second choice -heurstic, and calcs Ej 100 maxK = -1; maxDeltaE = 0; Ej = 0 101 oS.eCache[i] = [1,Ei] #set valid #choose the alpha that gives the maximum delta E 102 validEcacheList = nonzero(oS.eCache[:,0].A)[0] 103 if (len(validEcacheList)) > 1: 104 for k in validEcacheList: #loop through valid Ecache values and find the one that maximizes delta E 105 if k == i: continue #don't calc for i, waste of time 106 Ek = calcEk(oS, k) 107 deltaE = abs(Ei - Ek) 108 if (deltaE > maxDeltaE): 109 maxK = k; maxDeltaE = deltaE; Ej = Ek 110 return maxK, Ej 111 else: #in this case (first time around) we don't have any valid eCache values 112 j = selectJrand(i, oS.m) 113 Ej = calcEk(oS, j) 114 return j, Ej 115 116 def updateEk(oS, k):#after any alpha has changed update the new value in the cache 117 Ek = calcEk(oS, k) 118 oS.eCache[k] = [1,Ek] 119 120 def innerL(i, oS): 121 Ei = calcEk(oS, i) 122 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)): 123 j,Ej = selectJ(i, oS, Ei) #this has been changed from selectJrand 124 alphaIold = oS.alphas[i].copy(); alphaJold = oS.alphas[j].copy(); 125 if (oS.labelMat[i] != oS.labelMat[j]): 126 L = max(0, oS.alphas[j] - oS.alphas[i]) 127 H = min(oS.C, oS.C + oS.alphas[j] - oS.alphas[i]) 128 else: 129 L = max(0, oS.alphas[j] + oS.alphas[i] - oS.C) 130 H = min(oS.C, oS.alphas[j] + oS.alphas[i]) 131 if L==H: print "L==H"; return 0 132 eta = 2.0 * oS.K[i,j] - oS.K[i,i] - oS.K[j,j] #changed for kernel 133 if eta >= 0: print "eta>=0"; return 0 134 oS.alphas[j] -= oS.labelMat[j]*(Ei - Ej)/eta 135 oS.alphas[j] = clipAlpha(oS.alphas[j],H,L) 136 updateEk(oS, j) #added this for the Ecache 137 if (abs(oS.alphas[j] - alphaJold) < 0.00001): print "j not moving enough"; return 0 138 oS.alphas[i] += oS.labelMat[j]*oS.labelMat[i]*(alphaJold - oS.alphas[j])#update i by the same amount as j 139 updateEk(oS, i) #added this for the Ecache #the update is in the oppostie direction 140 b1 = oS.b - Ei- oS.labelMat[i]*(oS.alphas[i]-alphaIold)*oS.K[i,i] - oS.labelMat[j]*(oS.alphas[j]-alphaJold)*oS.K[i,j] 141 b2 = oS.b - Ej- oS.labelMat[i]*(oS.alphas[i]-alphaIold)*oS.K[i,j]- oS.labelMat[j]*(oS.alphas[j]-alphaJold)*oS.K[j,j] 142 if (0 < oS.alphas[i]) and (oS.C > oS.alphas[i]): oS.b = b1 143 elif (0 < oS.alphas[j]) and (oS.C > oS.alphas[j]): oS.b = b2 144 else: oS.b = (b1 + b2)/2.0 145 return 1 146 else: return 0 147 148 def smoP(dataMatIn, classLabels, C, toler, maxIter,kTup=('lin', 0)): #full Platt SMO 149 oS = optStruct(mat(dataMatIn),mat(classLabels).transpose(),C,toler, kTup) 150 iter = 0 151 entireSet = True; alphaPairsChanged = 0 152 while (iter < maxIter) and ((alphaPairsChanged > 0) or (entireSet)): 153 alphaPairsChanged = 0 154 if entireSet: #go over all 155 for i in range(oS.m): 156 alphaPairsChanged += innerL(i,oS) 157 print "fullSet, iter: %d i:%d, pairs changed %d" % (iter,i,alphaPairsChanged) 158 iter += 1 159 else:#go over non-bound (railed) alphas 160 nonBoundIs = nonzero((oS.alphas.A > 0) * (oS.alphas.A < C))[0] 161 for i in nonBoundIs: 162 alphaPairsChanged += innerL(i,oS) 163 print "non-bound, iter: %d i:%d, pairs changed %d" % (iter,i,alphaPairsChanged) 164 iter += 1 165 if entireSet: entireSet = False #toggle entire set loop 166 elif (alphaPairsChanged == 0): entireSet = True 167 print "iteration number: %d" % iter 168 return oS.b,oS.alphas 169 170 def calcWs(alphas,dataArr,classLabels): 171 X = mat(dataArr); labelMat = mat(classLabels).transpose() 172 m,n = shape(X) 173 w = zeros((n,1)) 174 for i in range(m): 175 w += multiply(alphas[i]*labelMat[i],X[i,:].T) 176 return w 177 178 def testRbf(k1=1.3): 179 dataArr,labelArr = loadDataSet('testSetRBF.txt') 180 b,alphas = smoP(dataArr, labelArr, 200, 0.0001, 10000, ('rbf', k1)) #C=200 important 181 datMat=mat(dataArr); labelMat = mat(labelArr).transpose() 182 svInd=nonzero(alphas.A>0)[0] 183 sVs=datMat[svInd] #get matrix of only support vectors 184 labelSV = labelMat[svInd]; 185 print "there are %d Support Vectors" % shape(sVs)[0] 186 m,n = shape(datMat) 187 errorCount = 0 188 for i in range(m): 189 kernelEval = kernelTrans(sVs,datMat[i,:],('rbf', k1)) 190 predict=kernelEval.T * multiply(labelSV,alphas[svInd]) + b 191 if sign(predict)!=sign(labelArr[i]): errorCount += 1 192 print "the training error rate is: %f" % (float(errorCount)/m) 193 dataArr,labelArr = loadDataSet('testSetRBF2.txt') 194 errorCount = 0 195 datMat=mat(dataArr); labelMat = mat(labelArr).transpose() 196 m,n = shape(datMat) 197 for i in range(m): 198 kernelEval = kernelTrans(sVs,datMat[i,:],('rbf', k1)) 199 predict=kernelEval.T * multiply(labelSV,alphas[svInd]) + b 200 if sign(predict)!=sign(labelArr[i]): errorCount += 1 201 print "the test error rate is: %f" % (float(errorCount)/m) 202 203 def img2vector(filename): 204 returnVect = zeros((1,1024)) 205 fr = open(filename) 206 for i in range(32): 207 lineStr = fr.readline() 208 for j in range(32): 209 returnVect[0,32*i+j] = int(lineStr[j]) 210 return returnVect 211 212 def loadImages(dirName): 213 from os import listdir 214 hwLabels = [] 215 trainingFileList = listdir(dirName) #load the training set 216 m = len(trainingFileList) 217 trainingMat = zeros((m,1024)) 218 for i in range(m): 219 fileNameStr = trainingFileList[i] 220 fileStr = fileNameStr.split('.')[0] #take off .txt 221 classNumStr = int(fileStr.split('_')[0]) 222 if classNumStr == 9: hwLabels.append(-1) 223 else: hwLabels.append(1) 224 trainingMat[i,:] = img2vector('%s/%s' % (dirName, fileNameStr)) 225 return trainingMat, hwLabels 226 227 def testDigits(kTup=('rbf', 10)): 228 dataArr,labelArr = loadImages('trainingDigits') 229 b,alphas = smoP(dataArr, labelArr, 200, 0.0001, 10000, kTup) 230 datMat=mat(dataArr); labelMat = mat(labelArr).transpose() 231 svInd=nonzero(alphas.A>0)[0] 232 sVs=datMat[svInd] 233 labelSV = labelMat[svInd]; 234 print "there are %d Support Vectors" % shape(sVs)[0] 235 m,n = shape(datMat) 236 errorCount = 0 237 for i in range(m): 238 kernelEval = kernelTrans(sVs,datMat[i,:],kTup) 239 predict=kernelEval.T * multiply(labelSV,alphas[svInd]) + b 240 if sign(predict)!=sign(labelArr[i]): errorCount += 1 241 print "the training error rate is: %f" % (float(errorCount)/m) 242 dataArr,labelArr = loadImages('testDigits') 243 errorCount = 0 244 datMat=mat(dataArr); labelMat = mat(labelArr).transpose() 245 m,n = shape(datMat) 246 for i in range(m): 247 kernelEval = kernelTrans(sVs,datMat[i,:],kTup) 248 predict=kernelEval.T * multiply(labelSV,alphas[svInd]) + b 249 if sign(predict)!=sign(labelArr[i]): errorCount += 1 250 print "the test error rate is: %f" % (float(errorCount)/m) 251 252 253 '''#######******************************** 254 Non-Kernel VErsions below 255 '''#######******************************** 256 257 class optStructK: 258 def __init__(self,dataMatIn, classLabels, C, toler): # Initialize the structure with the parameters 259 self.X = dataMatIn 260 self.labelMat = classLabels 261 self.C = C 262 self.tol = toler 263 self.m = shape(dataMatIn)[0] 264 self.alphas = mat(zeros((self.m,1))) 265 self.b = 0 266 self.eCache = mat(zeros((self.m,2))) #first column is valid flag 267 268 def calcEkK(oS, k): 269 fXk = float(multiply(oS.alphas,oS.labelMat).T*(oS.X*oS.X[k,:].T)) + oS.b 270 Ek = fXk - float(oS.labelMat[k]) 271 return Ek 272 273 def selectJK(i, oS, Ei): #this is the second choice -heurstic, and calcs Ej 274 maxK = -1; maxDeltaE = 0; Ej = 0 275 oS.eCache[i] = [1,Ei] #set valid #choose the alpha that gives the maximum delta E 276 validEcacheList = nonzero(oS.eCache[:,0].A)[0] 277 if (len(validEcacheList)) > 1: 278 for k in validEcacheList: #loop through valid Ecache values and find the one that maximizes delta E 279 if k == i: continue #don't calc for i, waste of time 280 Ek = calcEk(oS, k) 281 deltaE = abs(Ei - Ek) 282 if (deltaE > maxDeltaE): 283 maxK = k; maxDeltaE = deltaE; Ej = Ek 284 return maxK, Ej 285 else: #in this case (first time around) we don't have any valid eCache values 286 j = selectJrand(i, oS.m) 287 Ej = calcEk(oS, j) 288 return j, Ej 289 290 def updateEkK(oS, k):#after any alpha has changed update the new value in the cache 291 Ek = calcEk(oS, k) 292 oS.eCache[k] = [1,Ek] 293 294 def innerLK(i, oS): 295 Ei = calcEk(oS, i) 296 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)): 297 j,Ej = selectJ(i, oS, Ei) #this has been changed from selectJrand 298 alphaIold = oS.alphas[i].copy(); alphaJold = oS.alphas[j].copy(); 299 if (oS.labelMat[i] != oS.labelMat[j]): 300 L = max(0, oS.alphas[j] - oS.alphas[i]) 301 H = min(oS.C, oS.C + oS.alphas[j] - oS.alphas[i]) 302 else: 303 L = max(0, oS.alphas[j] + oS.alphas[i] - oS.C) 304 H = min(oS.C, oS.alphas[j] + oS.alphas[i]) 305 if L==H: print "L==H"; return 0 306 eta = 2.0 * oS.X[i,:]*oS.X[j,:].T - oS.X[i,:]*oS.X[i,:].T - oS.X[j,:]*oS.X[j,:].T 307 if eta >= 0: print "eta>=0"; return 0 308 oS.alphas[j] -= oS.labelMat[j]*(Ei - Ej)/eta 309 oS.alphas[j] = clipAlpha(oS.alphas[j],H,L) 310 updateEk(oS, j) #added this for the Ecache 311 if (abs(oS.alphas[j] - alphaJold) < 0.00001): print "j not moving enough"; return 0 312 oS.alphas[i] += oS.labelMat[j]*oS.labelMat[i]*(alphaJold - oS.alphas[j])#update i by the same amount as j 313 updateEk(oS, i) #added this for the Ecache #the update is in the oppostie direction 314 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 315 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 316 if (0 < oS.alphas[i]) and (oS.C > oS.alphas[i]): oS.b = b1 317 elif (0 < oS.alphas[j]) and (oS.C > oS.alphas[j]): oS.b = b2 318 else: oS.b = (b1 + b2)/2.0 319 return 1 320 else: return 0 321 322 def smoPK(dataMatIn, classLabels, C, toler, maxIter): #full Platt SMO 323 oS = optStruct(mat(dataMatIn),mat(classLabels).transpose(),C,toler) 324 iter = 0 325 entireSet = True; alphaPairsChanged = 0 326 while (iter < maxIter) and ((alphaPairsChanged > 0) or (entireSet)): 327 alphaPairsChanged = 0 328 if entireSet: #go over all 329 for i in range(oS.m): 330 alphaPairsChanged += innerL(i,oS) 331 print "fullSet, iter: %d i:%d, pairs changed %d" % (iter,i,alphaPairsChanged) 332 iter += 1 333 else:#go over non-bound (railed) alphas 334 nonBoundIs = nonzero((oS.alphas.A > 0) * (oS.alphas.A < C))[0] 335 for i in nonBoundIs: 336 alphaPairsChanged += innerL(i,oS) 337 print "non-bound, iter: %d i:%d, pairs changed %d" % (iter,i,alphaPairsChanged) 338 iter += 1 339 if entireSet: entireSet = False #toggle entire set loop 340 elif (alphaPairsChanged == 0): entireSet = True 341 print "iteration number: %d" % iter 342 return oS.b,oS.alphas
运行结果如(图八)所示:
(图八)
上面代码有兴趣的可以读读,用的话,建议使用libsvm。
上面内容转载至朋友博客:http://blog.csdn.net/marvin521/article/details/9305497
Ps:正如文章开头所说的SVM算法以其优异的性能,曾常年霸占机器学习兵器排行榜头名,也是当年导师叫我学习的第一个算法,真怀念当时满怀热情熬夜通宵Libsvm的时光。推荐SVM入门神马八股介绍http://www.blogjava.net/zhenandaci/archive/2009/02/13/254519.html和大牛的burges 的 tutorial http://research.microsoft.com/pubs/67119/svmtutorial.pdf ,SVM好是好,但是有几个纠结的点1,参数调节比较蛋疼,对于数据量大,采用网格搜寻得花好几天的时间。2,万恶的kernel函数,线性核还好,神马RBF, polynomial是坑人的节奏,不知道后面出的tree,string的kernel咋样。前段时间百度深度学习研究院副院长余凯在微博上抛出来一行代码写出SVM的言论引来各路神牛激烈讨论,有1行Python的,Matlab,R的,各种奇葩答案,针对SVM的问题好多学者搞起了large scale 的SVM,从优化算法到代码实现,推荐http://leon.bottou.org/projects/sgd 作者理论跟工程代码 都很赞。关于并行的Mahout里面有实现,本书的第十五章就有基于MrJob 的并行SVM算法的实现。GPU方面也有http://patternsonascreen.net/cuSVM.html,http://code.google.com/p/multisvm/,这两个都是CUDA架构下的实现,记得当时在学校上届师兄就用OpenCL把SVM应用到Ranking里面而获得当年AMD高性能比赛的第一名。啊,闲话扯远了,最后引用下微博达人夏粉_百度说的一句话:我分享常问的问题,请介绍一下SVM,Boosting,LR中任何一个最熟悉的算法的目标函数、优化过程、并行实现、算法收敛性、样本复杂度、适用场景、调参经验。
PS:附上博客达人july整理的关于SVM的材料,他的博客内容也非常赞,适合入门者http://blog.csdn.net/v_july_v/article/details/7624837
参考文献及推荐阅读
- 《支持向量机导论》,[美] Nello Cristianini / John Shawe-Taylor 著;
- 支持向量机导论一书的支持网站:http://www.support-vector.net/;
- 《数据挖掘导论》,[美] Pang-Ning Tan / Michael Steinbach / Vipin Kumar 著;
- 《数据挖掘:概念与技术》,(加)Jiawei Han;Micheline Kamber 著;
- 《数据挖掘中的新方法:支持向量机》,邓乃扬 田英杰 著;
- 《支持向量机--理论、算法和扩展》,邓乃扬 田英杰 著;
- 支持向量机系列,pluskid:http://blog.pluskid.org/?page_id=683;
- http://www.360doc.com/content/07/0716/23/11966_615252.shtml;
- 数据挖掘十大经典算法初探;
- 《模式识别支持向量机指南》,C.J.C Burges 著;
- 《统计学习方法》,李航著(第7章有不少内容参考自支持向量机导论一书,不过,可以翻翻看看);
- 《统计自然语言处理》,宗成庆编著,第十二章、文本分类;
- SVM入门系列,Jasper:http://www.blogjava.net/zhenandaci/category/31868.html;
- 最近邻决策和SVM数字识别的实现和比较,作者不详;
- 斯坦福大学机器学习课程原始讲义:http://www.cnblogs.com/jerrylead/archive/2012/05/08/2489725.html;
- 斯坦福机器学习课程笔记:http://www.cnblogs.com/jerrylead/tag/Machine%20Learning/;
- http://www.cnblogs.com/jerrylead/archive/2011/03/13/1982639.html;
- http://www.cnblogs.com/jerrylead/archive/2011/03/18/1988419.html;
- 数据挖掘掘中所需的概率论与数理统计知识、上;
- 关于机器学习方面的文章,可以读读:http://www.cnblogs.com/vivounicorn/category/289453.html;
- 数学系教材推荐:http://blog.sina.com.cn/s/blog_5e638d950100dswh.html;
- 《神经网络与机器学习(原书第三版)》,[加] Simon Haykin 著;
- 正态分布的前世今生:http://t.cn/zlH3Ygc;
- 《数理统计学简史》,陈希孺院士著;
- 《最优化理论与算法(第2版)》,陈宝林编著;
- A Gentle Introduction to Support Vector Machines in Biomedicine:http://www.nyuinformatics.org/downloads/supplements/SVM_Tutorial_2010/Final_WB.pdf,此PPT很赞,除了对引入拉格朗日对偶变量后的凸二次规划问题的深入度不够之外,其它都挺好,配图很精彩,本文有几张图便引自此PPT中;
- 来自卡内基梅隆大学carnegie mellon university(CMU)的讲解SVM的PPT:http://www.autonlab.org/tutorials/svm15.pdf;
- 发明libsvm的台湾林智仁教授06年的机器学习讲义SVM:http://wenku.baidu.com/link?url=PWTGMYNb4HGUrUQUZwTH2B4r8pIMgLMiWIK1ymVORrds_11VOkHwp-JWab7IALDiors64JW_6mD93dtuWHwFWxsAk6p0rzchR8Qh5_4jWHC;
- http://staff.ustc.edu.cn/~ketang/PPT/PRLec5.pdf;
- Introduction to Support Vector Machines (SVM),By Debprakash Patnai M.E (SSA),https://www.google.com.hk/url?sa=t&rct=j&q=&esrc=s&source=web&cd=1&ved=0CCwQFjAA&url=http%3a%2f%2fwww%2epws%2estu%2eedu%2etw%2fccfang%2findex%2efiles%2fAI%2fAI%26ML-Support%2520Vector%2520Machine-1%2eppt&ei=JRR6UqT5C-iyiQfWyIDgCg&usg=AFQjCNGw1fTbpH4ltQjjmx1d25ZqbCN9nA;
- 多人推荐过的libsvm:http://www.csie.ntu.edu.tw/~cjlin/libsvm/;
- 《machine learning in action》,中文版为《机器学习实战》;