SVM的一般流程:
- 收集数据;
- 准备数据:数值型
- 分析数据:有助于可视化分隔超平面
- 训练算法;
- 测试算法;
- 使用算法;
简化的SMO算法:
SMO算法中的外循环确定要优化的最佳alpha对,简化版跳过这一步骤,首先在数据集上遍历每一个alpha,然后在剩下的alpha集合中随机选择另一个alpha,从而构建alpha对。为此,下述代码构建一个辅助函数,用于在某个区间范围内随机选择一个整数。另一个辅助函数,用于在数值太大时对其进行调整。
伪代码如下:
创建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和α值
通过上述代码,就可以获得支持向量机模型的参数α和b,就可以构造出支持向量机。
然而简化的SMO在数据处理的速度上性能极差,因此下面实现完整版的SMO算法。完整的SMO和简化版SMO在实现alpha的更改和代数运算环节一模一样,在优化过程中,唯一不同的就是选择alpha的方式上。完整的SMO算法通过一个外循环选择第一个alpha值,并且选择过程会在两种方式之间进行交替:
- 在所有数据集上进行单遍扫描
- 在非边界(不等于边界0或C的alpha值)alpha中实现单遍扫描:需要先建立这些alpha值得列表,然后再对这个表进行遍历,同时会跳过那些已知的不会改变的alpha值。
选择第一个alpha后,通过内循环来选择第二个alpha值。在优化过程中,通过最大化步长的方式来获得第二个alpha值。建立一个全局的缓存用于保存误差值,并从中选在使得步长或者说Ei-Ej最大的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则属于负类。