SVM的一般流程:
- 收集数据;
- 准备数据:数值型
- 分析数据:有助于可视化分隔超平面
- 训练算法;
- 测试算法;
- 使用算法;
简化的SMO算法:
创建alpha向量并将其初始化为0向量 while 迭代次数<最大迭代次数:(外循环) 对数据集中每个数据向量(内循环): if 该数据向量可以被优化: 随机选择另外一个数据向量 同时优化这两个向量 if 这两个向量都不能被优化: 退出内循环 if 所有向量都没被优化: 增加迭代数目,继续下一次循环
def somSimple(dataMatIn,classLabels,C,toler,maxIter): #5个输入参数:数据集,类别标签,常数C,容错率和最大循环次数 dataMatrix=mat(dataMatIn) labelMat=mat(classLabels).transpose() b=0 m,n=shape(dataMatrix) #m是样本个数,n是特征数 alphas=mat(zeros((m,1))) #alpha矩阵,alpha个数等于样本个数 iter=0 #用于存储在没有任何alpha改变的情况下遍历数据集的次数 while(iter<maxIter): alphaPairsChanged=0 #记录alpha是否已经进行了优化 for i in range(m): fXi=float(multiply(alphas,labelMat).T*(dataMatrix*dataMatrix[i,:].T))+b #multiply是对应元素相乘,*就是矩阵乘法 fXi是预测的f(x),是预测类别 Ei=fXi-float(labelMat[i]) #预测类别和真实标签的差值即为误差 if((labelMat[i]*Ei<-toler)and(alphas[i]<C))or((labelMat[i]*Ei>toler)and(alphas[i]>0)): #如果i样本的预测误差很大且αi不等于0或C就可以进行优化 j=selectJrand(i,m) #随机选择一个αj fXj=float(multiply(alphas,labelMat).T*(dataMatrix*dataMatrix[j,:].T))+b #计算αj的预测值 Ej=fXj-float(labelMat[j]) #计算αj的误差 alphaIold=alphas[i].copy() #将现在的alpha[i]和alpha[j]相应的保存 alphaJold=alphas[j].copy() #L和H用于将alpha[j]调整到0和C之间 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: #如果L==H就不做任何调整,直接做下一次的for循环 print("L==H") continue #计算δ,δ是αj的最优修改量,如果δ>=0就要跳出for循环的当前迭代 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 #用求出的L和H对αj进行调整 alphas[j]-=labelMat[j]*(Ei-Ej)/eta alphas[j]=clipAlpha(alphas[j],H,L) if(abs(alphas[j]-alphaJold)<0.00001): #如果αj调整过于轻微,则跳出当前的循环 print("j not moving enough") continue alphas[i]+=labelMat[j]*labelMat[i]*(alphaJold-alphas[j]) #αi也做调整,大小同αj方向相反 #为αi和αj设置一个常数项b #由f(x)=∑(m,i=1)αi*yi*xi.T*x+b,可以求得常数项b 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 if(0<alphas[i])and(C>alphas[i]): b=b1 elif(0<alphas[j])and(C>alphas[j]): b=b2 else: b=(b1+b2)/2.0 alphaPairsChanged+=1 #表示该alpha对进行了优化 print("iter:{}".format(iter),"i:{}".format(i),",pairs changed{}".format(alphaPairsChanged)) if(alphaPairsChanged==0): #遍历数据集结束后,没有一对alpha进行了优化,则要将遍历次数加一 iter+=1 else: iter=0 print("iteration number:{}".format(iter)) #否则遍历结束 return b,alphas #返回SMO求得的b和α值
- 在所有数据集上进行单遍扫描
- 在非边界(不等于边界0或C的alpha值)alpha中实现单遍扫描:需要先建立这些alpha值得列表,然后再对这个表进行遍历,同时会跳过那些已知的不会改变的alpha值。
"""建立一个类来保存所有数据的值""" class optStruct: def __init__(self,dataMatIn,classLabels,C,toler): 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,eCache的第一列给出eCache是否有效地标志位,第二列给出的实际的E值 """辅助函数用来计算误差E""" def calcEk(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 """用于选择第二个alpha(内循环的alpha)""" """目标是选择合适的第二个alpha保证在每次优化中采用最大步长""" def selectJ(i,oS,Ei): maxK=-1 maxDeltaE=0 Ej=0 oS.eCache[i]=[1,Ei] #设置Ei有效(1) validEcacheList=nonzero(oS.eCache[:,0].A)[0] #构造一个非零列表".A"将矩阵转化为列表,非零E值对应的alpha值 if(len(validEcacheList))>1: for k in validEcacheList: #validEcacheList的所有值上循环找到使得改变最大的那个值 if k==i: continue Ek=calcEk(oS,k) deltaE=abs(Ei-Ek) if(deltaE>maxDeltaE): maxK=k maxDeltaE=deltaE Ej=Ek return maxK,Ej else: #如果是第一次循环就随机选择一个alpha j=selectJ(i,oS.m) Ej=calcEk(oS,j) return j,Ej """误差值并存入缓存,在对alpha优化时会用到""" def updateEk(oS,k): Ek=calcEk(oS,k) oS.eCache[k]=[1,Ek] """调整大于H或小于L的alpha值""" def clipAlpha(aj,H,L): if aj>H: aj=H if L>aj: aj=L return aj """完整SMO中的优化例程""" def innerL(i,oS): Ei=calcEk(oS,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) alphaIold=oS.alphas[i].copy() alphaJold=oS.alphas[j].copy() if(oS.labelMat[i]!=oS.alphas[i]): 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=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) updateEk(oS,j) if(abs(oS.alphas[j]-alphaJold)<0.00001): print("j not mving enough") return 0 oS.alphas[i]+=oS.labelMat[j]*oS.labelMat[i]*(alphaJold-oS.alphas[j]) updateEk(oS,i) b1=oS.b-Ei-oS.labelMat[i]*(oS.alphas[i]-alphaJold)*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]-alphaJold)*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 smoP(dataMatIn,classLabels,C,toler,maxIter,kTup=('lin',0)): 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: for i in range(oS.m): alphaPairsChanged+=innerL(i,oS) print("fullSet,iter:{}".format(iter),"i:{}".format(i),"pairs changed:{}".format(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:{}".format(iter),"i:{}".format(i),"pairs changed:{}".format(alphaPairsChanged)) iter+=1 if entireSet: entireSet=False elif(alphaPairsChanged==0): entireSet=True print("iteration number:{}".format(iter)) return oS.b,oS.alphas
以上我们就得到了SVM中的α,通过如下的函数,就可以求得w,并基于alpha获得分隔超平面。
"""求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对于测试样本如果y=wx+b的结果大于0则属于正类,如果小于0则属于负类。
文章来源: 机器学习实战笔记:支持向量机