如果想要详细了解支持向量机的原理,本人推荐一篇文章,读者可详细的阅读,大有裨益:
https://blog.csdn.net/v_july_v/article/details/7624837
import numpy
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):#随机选择一个和i不同的j
j=i
while(j==i):
j=int(numpy.random.uniform(0,m))
return j
def clipAlpha(aj,H,L):#用于调整aj值,让aj在H和L的范围内
if aj>H:
aj=H
if L>aj:
aj=L
return aj
def smoSimple(dataMatIn,classLabels,C,toler,maxIter):#输入分别为数据集,类标签,常数C,容许错误率,退出前最大的循环次数
dataMatrix=numpy.mat(dataMatIn)#输入数据集变为矩阵
labelMat=numpy.mat(classLabels).transpose()#把输入标签变为矩阵并且转置,得到一个列向量,标签的每行对应每行的数据
b=0#初始化
m,n=numpy.shape(dataMatrix)#得到矩阵的行列数
alphas=numpy.mat(numpy.zeros((m,1)))#定义一个m*1的零矩阵
iter=0#初始化迭代次数
##只有在所有数据集上遍历maxIter次,且不再发生任何alpha修改之后,程序才会停止并退出while循环
while (itertoler)and (alphas[i]>0)):#将违反KKT条件的找出来,具体公式来源见https://blog.csdn.net/youhuakongzhi/article/details/86660281
j=selectJrand(i,m)#在m个中随机选择一个和i不同的j
fXj=float(numpy.multiply(alphas,labelMat).T*(dataMatrix*dataMatrix[j,:].T))+b#预测第j个数据的类别
Ej=fXj-float(labelMat[j])#计算第j个的误差
alphaIold=alphas[i].copy()#保存更新前的aplpha值,使用深拷贝
alphaJold=alphas[j].copy()
if (labelMat[i]!=labelMat[j]):#如果yi和yj的标签不一样
L=max(0,alphas[j]-alphas[i])#alphas[j]new的取值范围
H=min(C,C+alphas[j]-alphas[i])
else:#如果yi和yj的标签一样
L=max(0,alphas[j]+alphas[i]-C)#alphas[j]new的取值范围
H=min(C,alphas[j]+alphas[i])
if L==H:print("L==H");continue#退出本次循环,直接进行下一次for循环
# 步骤3:计算eta=-2*Kij+Kii+Kjj,而这儿eta=2*Kij-Kii-Kjj,所以下面公式中用的是减号
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#退出本次循环,直接进行下一次for循环
# 目标函数是求最小值,这儿eta是负的二阶导数,因此二阶导数不能小于等于0
alphas[j]-=labelMat[j]*(Ei-Ej)/eta#更新alphas[j]
alphas[j]=clipAlpha(alphas[j],H,L)#用于调整aj值,让aj在H和L的范围内
if (abs(alphas[j]-alphaJold)<0.00001):#alphas[j]变化太小
print("j not moving enough"); continue
alphas[i]+=labelMat[j]*labelMat[i]*(alphaJold-alphas[j])#更新alphas[i]
#更新b1
b1=b-Ei-labelMat[i]*(alphas[i]-alphaIold)*dataMatrix[i,:]*dataMatrix[i,:].T-labelMat[j]*(alphas[j]-alphaJold)*\
dataMatrix[i,:]*dataMatrix[j,:].T
# 更新b2
b2=b-Ej-labelMat[i]*(alphas[i]-alphaIold)*dataMatrix[i,:]*dataMatrix[j,:].T-labelMat[j]*(alphas[j]-alphaJold)*\
dataMatrix[j,:]*dataMatrix[j,:].T
# 更新b
if (0alphas[i]): b=b1
elif (0alphas[j]): b=b2
else: b=(b1+b2)/2.0
alphaPairsChanged +=1
print("iter : %d i:%d, pairs changed %d" %(iter,i,alphaPairsChanged))
if (alphaPairsChanged==0):iter+=1#检査alpha值是否做了更新,如果有更新则将iter为0后继续运行程序
else:iter=0
print("iteration number: %d" %iter)
return b,alphas
#数据结构保存所有的重要值
#参数 dataMatIn 数据矩阵,classLabels数据标签 c松弛变量 toler容错率
class optStruct:
def __init__(self,dataMatIn,classLabels,C,toler,kTup):
self.X = dataMatIn # X:训练的数据集
self.labelMat = classLabels # labelMat:X对应的类别标签
self.C = C # C:松弛变量系数
self.tol = toler # tol:容错率
self.m = numpy.shape(dataMatIn)[0] # m:样本的个数
self.alphas = numpy.mat(numpy.zeros((self.m, 1))) # alphas:拉格朗日系数,需要优化项
self.b = 0 # b:阈值
self.eCache = numpy.mat(numpy.zeros((self.m, 2)))
self.K = numpy.mat(numpy.zeros((self.m, self.m)))
# 第一列 标志位,标志Ek是否有效,1为有效,0为无效 第二列 错误率Ek
for i in range(self.m):#计算所有数据的核K
self.K[:,i]=kernelTrans(self.X, self.X[i,:], kTup)#两个数值型变量和一个元组
def calcEk(oS,k):#计算第K个数据误差Ek
#fXk=float(numpy.multiply(oS.alphas,oS.labelMat).T*(oS.X*oS.X[k,:].T))+oS.b
#Ek=fXk-float(oS.labelMat[k])
fXk = float(numpy.multiply(oS.alphas, oS.labelMat).T * oS.K[:,k]+oS.b)
Ek = fXk - float(oS.labelMat[k])
return Ek
def selectJ(i,oS,Ei):#选择第j个数据
#启发式算法选择j,选择具有最大步长的j
# 1.定义步长maxDeltaE (Ei-Ek) 取得最大步长时的K值maxK
# 需要返回的Ej (具有最大步长 ,即|Ei-Ej|值最大)
maxK=-1;
maxDeltaE=0
Ej=0
# 2.将Ei保存到数据结构的eCache中去
oS.eCache[i]=[1,Ei]#Ei - 标号为i的数据误差,oS - 数据结构
validEcacheList=numpy.nonzero(oS.eCache[:,0].A)[0]#返回误差不为0的数据的索引值
# 3.判断 如果len(validEcacheList)>1 遍历validEcacheList,找到最大的|Ei-Ej|
if (len(validEcacheList))>1:#有不为0的误差
for k in validEcacheList:
if k==i:continue
Ek=calcEk(oS,k)#计算第k个误差
deltaE=abs(Ei-Ek)#求取绝对值
if (deltaE>maxDeltaE):#选出最大的deltaE和下标k
maxK=k;
maxDeltaE=deltaE
Ej=Ek
return maxK,Ej
else:
j=selectJrand(i,oS.m)#随机选择一个和i不同的j
Ej=calcEk(oS,j)#计算误差
return j,Ej
def updateEk(oS,k):#更新Ek
Ek=calcEk(oS,k)
oS.eCache[k]=[1,Ek]
'''优化的SMO算法
Parameters:
i - 标号为i的数据的索引值
oS - 数据结构
Returns:
1 - 有任意一对alpha值发生变化
0 - 没有任意一对alpha值发生变化或变化太小'''
def innerL(i,oS):
Ei=calcEk(oS,i)#计算第i数据的误差
##将违反KKT条件的找出来,具体公式来源见https://blog.csdn.net/youhuakongzhi/article/details/86660281
if ((oS.labelMat[i]*Ei<-oS.tol)and(oS.alphas[i]oS.tol)and(oS.alphas[i]>0)):
j,Ej=selectJ(i,oS,Ei)#根据步长选择第J个数据
alphaIold=oS.alphas[i].copy()#保存更新前的aplpha值,使用深拷贝
alphaJold=oS.alphas[j].copy()#保存更新前的aplpha值,使用深拷贝
if (oS.labelMat[i]!=oS.labelMat[j]):#如果yi和yj的标签不一样,alphas[j]new的取值范围
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*Kij+Kii+Kjj,而这儿eta=2*Kij-Kii-Kjj,所以下面公式中用的是减号,也可以看做关于oS.alphas[j]的二阶导数
#eta=2.0*oS.X[i,:]*oS.X[j,:].T-oS.X[i,:]*oS.X[i,:].T-oS.X[j,:]*oS.X[j,:].T
eta=2.0*oS.K[i,j]-oS.K[i,i]-oS.K[j,j]
if eta>=0: print("eta>=0"); return 0#目标函数是求最小值,这儿eta是负的二阶导数,因此二阶导数不能小于等于0
oS.alphas[j]-=oS.labelMat[j]*(Ei-Ej)/eta#更新oS.alphas[j]
oS.alphas[j]=clipAlpha(oS.alphas[j],H,L)#消减到规定范围内
updateEk(oS,j)#更新Ej至误差缓存
if (abs(oS.alphas[j]-alphaJold)<0.00001):
print("j not moving engough"); return 0
# 步骤6:更新alpha_i
oS.alphas[i]+=oS.labelMat[j]*oS.labelMat[i]*(alphaJold-oS.alphas[j])
updateEk(oS,i)#更新Ei至误差缓存
# 步骤7:更新b_1和b_2
#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
b1=oS.b-Ei-oS.labelMat[i]*(oS.alphas[i]-alphaIold)*oS.K[i,i]-oS.labelMat[j]*(oS.alphas[j]-alphaJold)*oS.K[j,j]
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
# 步骤8:根据b_1和b_2更新b
if (0oS.alphas[i]):oS.b=b1
elif (0oS.alphas[j]):oS.b=b2
else:oS.b=(b1+b2)/2.0
return 1
else:return 0
def smoP(dataMatIn,classLabels,C,toler,maxIter,kTup=('lin',0)):
"""
完整的线性SMO算法
Parameters:
dataMatIn - 数据矩阵
classLabels - 数据标签
C - 松弛变量
toler - 容错率
maxIter - 最大迭代次数
Returns:
oS.b - SMO算法计算的b
oS.alp"""
oS=optStruct(numpy.mat(dataMatIn),numpy.mat(classLabels).transpose(),C,toler,kTup)#初始化数据结构
iter=0#初始化当前迭代次数
entireSet=True
alphaPairsChanged=0
# 遍历整个数据集都alpha也没有更新或者超过最大迭代次数,则退出循环
while (iter0)or (entireSet)):
alphaPairsChanged=0
if entireSet:#遍历整个数据集#首先进行完整遍历,过程和简化版的SMO一样
for i in range(oS.m):
alphaPairsChanged+=innerL(i,oS)#使用优化的SMO算法,如果oS.alphas[j]和oS.alphas[i]更新,则返回1
print("fullSet, iter: %d i: %d, pairs changed %d" %(iter,i,alphaPairsChanged))
iter+=1#循环完一次,迭代次数加1
else: #非边界遍历,挑选其中alpha值在0和C之间非边界alpha进行优化
# 遍历不在边界0和C的alpha
nonBoundIs=numpy.nonzero((oS.alphas.A>0)*(oS.alphas.A0)[0] #获得支持向量
sVs=datMat[svInd] #get matrix of only support vectors#得到仅仅有支持向量的数据矩阵
labelSV = labelMat[svInd];#得到支持向量数据的标签
print ("there are %d Support Vectors" % numpy.shape(sVs)[0])#输出支持响亮的个数
m,n = numpy.shape(datMat)#得到原始数据的行列数
errorCount = 0
for i in range(m):
kernelEval = kernelTrans(sVs,datMat[i,:],('rbf', k1))
predict=kernelEval.T * numpy.multiply(labelSV,alphas[svInd]) + b#根据支持向量的点,计算超平面,返回预测结果
if numpy.sign(predict)!=numpy.sign(labelArr[i]): errorCount += 1#如果分类预测的和实际的标签不相符,误差数量加1
print ("the training error rate is: %f" % (float(errorCount)/m))#打印错误率
dataArr,labelArr = loadDataSet('testSetRBF2.txt')#加载测试集
errorCount = 0
datMat=numpy.mat(dataArr); labelMat = numpy.mat(labelArr).transpose()
m,n = numpy.shape(datMat)
for i in range(m):
kernelEval = kernelTrans(sVs,datMat[i,:],('rbf', k1))#计算各个点的核
predict=kernelEval.T * numpy.multiply(labelSV,alphas[svInd]) + b #根据支持向量的点,计算超平面,返回预测结果
if numpy.sign(predict)!=numpy.sign(labelArr[i]): errorCount += 1#返回数组中各元素的正负符号,用1和-1表示,并统计错误个数
print ("the test error rate is: %f" % (float(errorCount)/m))#打印错误率
#回顾手写字
# 读取数据到矩阵
def img2vector(filename):
# 创建向量
returnVect = numpy.zeros((1,1024))
# 打开数据文件,读取每行内容
fr = open(filename)
for i in range(32):
lineStr = fr.readline()
# 将每行前32字符转成int存入向量
for j in range(32):
returnVect[0,32*i+j] = int(lineStr[j])
return returnVect
#将文件夹下面的数字文件 转换成数据特征(每个数字1/0就是一个特征,每个文件有1024个特征)和类别标签
def loadImages(dirName):
from os import listdir#os.listdir() 方法用于返回指定的文件夹包含的文件或文件夹的名字的列表
hwLabels = []
trainingFileList = listdir(dirName) #load the training set
m = len(trainingFileList)
trainingMat = numpy.zeros((m,1024))
for i in range(m):
fileNameStr = trainingFileList[i]
fileStr = fileNameStr.split('.')[0] ##对文件名进行分割。例如2_45.txt,从.那个地方开始分割文件名,就得到2_45和txt两部分,
classNumStr = int(fileStr.split('_')[0])#对文件名进行分割。例如2_45,从_那个地方开始分割文件名,就得到2和45两部分,相当于数字的标签值
if classNumStr == 9: hwLabels.append(-1) #二分类,数字9标签为-1 其他的标签为+1
else: hwLabels.append(1)
trainingMat[i,:] = img2vector('%s/%s' % (dirName, fileNameStr))#读取数据到矩阵
return trainingMat, hwLabels
def testDigits(kTup=('rbf', 10)):
dataArr,labelArr = loadImages('trainingDigits')#加载训练数据
b,alphas = smoP(dataArr, labelArr, 200, 0.0001, 10000, kTup)
datMat=numpy.mat(dataArr); labelMat = numpy.mat(labelArr).transpose()
svInd=numpy.nonzero(alphas.A>0)[0]#得到支持向量
sVs=datMat[svInd]#支持向量数据
labelSV = labelMat[svInd];#支持向量标签
print ("there are %d Support Vectors" % numpy.shape(sVs)[0])
m,n = numpy.shape(datMat)
errorCount = 0
for i in range(m):
kernelEval = kernelTrans(sVs,datMat[i,:],kTup)##计算各个点的核
predict=kernelEval.T * numpy.multiply(labelSV,alphas[svInd]) + b#预测分类平面
if numpy.sign(predict)!=numpy.sign(labelArr[i]): errorCount += 1
print ("the training error rate is: %f" % (float(errorCount)/m))
dataArr,labelArr = loadImages('testDigits')#加载测试数据
errorCount = 0
datMat=numpy.mat(dataArr); labelMat = numpy.mat(labelArr).transpose()
m,n = numpy.shape(datMat)
for i in range(m):
kernelEval = kernelTrans(sVs,datMat[i,:],kTup)
predict=kernelEval.T * numpy.multiply(labelSV,alphas[svInd]) + b
if numpy.sign(predict)!=numpy.sign(labelArr[i]): errorCount += 1
print ("the test error rate is: %f" % (float(errorCount)/m) )