日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) >

机器学习实战之SVM

發(fā)布時(shí)間:2025/4/5 37 豆豆
生活随笔 收集整理的這篇文章主要介紹了 机器学习实战之SVM 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

一引言:

  支持向量機(jī)這部分確實(shí)很多,想要真正的去理解它,不僅僅知道理論,還要進(jìn)行相關(guān)的代碼編寫(xiě)和測(cè)試,二者想和結(jié)合,才能更好的幫助我們理解SVM這一非常優(yōu)秀的分類(lèi)算法

  支持向量機(jī)是一種二類(lèi)分類(lèi)算法,假設(shè)一個(gè)平面可以將所有的樣本分為兩類(lèi),位于正側(cè)的樣本為一類(lèi),值為+1,而位于負(fù)一側(cè)的樣本為另外一類(lèi),值為-1。

  我們說(shuō)分類(lèi),不僅僅是將不同的類(lèi)別樣本分隔開(kāi),還要以比較大的置信度來(lái)分隔這些樣本,這樣才能使絕大部分樣本被分開(kāi)。比如,我們想通過(guò)一個(gè)平面將兩個(gè)類(lèi)別的樣本分開(kāi),如果這些樣本是線性可分(或者近視線性可分),那么這樣的平面有很多,但是如果我們加上要以最大的置信度來(lái)將這些樣本分開(kāi),那么這樣的平面只有一條。那么怎么才能找到這樣的平面呢?這里不得不提到幾個(gè)概念

1 幾何間隔

  幾何間隔的概念,簡(jiǎn)單理解就是樣本點(diǎn)到分隔平面的距離

2 間隔最大化

  想要間隔最大化,我們必須找到距離分隔平面最近的點(diǎn),并且使得距離平面最近的點(diǎn)盡可能的距離平面最遠(yuǎn),這樣,每一個(gè)樣本就都能夠以比較大的置信度被分隔開(kāi)

算法的分類(lèi)預(yù)測(cè)能力也就越好

  顯然,SVM算法的關(guān)鍵所在,就是找到使得間隔最大化的分隔超平面(如果特征是高維度的情況,我們稱(chēng)這樣的平面為超平面)

  這里關(guān)于SVM學(xué)習(xí),推薦兩本書(shū):統(tǒng)計(jì)學(xué)習(xí)方法(李航)和機(jī)器學(xué)習(xí)實(shí)戰(zhàn),二者結(jié)合,可以幫助我們理解svm算法

2 支持向量機(jī)

  關(guān)于支持向量機(jī)的推導(dǎo),無(wú)論是書(shū)上還是很多很優(yōu)秀的博客都寫(xiě)的非常清楚,大家有興趣可以看上面推薦的統(tǒng)計(jì)與學(xué)習(xí)方法書(shū),寫(xiě)的淺顯易懂,或者看這幾篇博客

http://blog.csdn.net/app_12062011/article/details/50536369

機(jī)器學(xué)習(xí)算法支持向量機(jī)系列博客

http://blog.csdn.net/zouxy09/article/details/16955347

http://blog.csdn.net/zouxy09/article/details/17291543

http://blog.csdn.net/zouxy09/article/details/17291805

http://blog.csdn.net/zouxy09/article/details/17292011

?

  這兩位博主都重點(diǎn)講解了SVM的推導(dǎo)過(guò)程。這里我就本著站在巨人的肩膀上的思想,不再贅述,我的側(cè)重點(diǎn)在于實(shí)際的代碼編寫(xiě)上,比較理論總歸要回到實(shí)踐上,這也是每個(gè)算法的歸宿所在。

  好了,下面我就簡(jiǎn)要寫(xiě)出簡(jiǎn)要介紹一下,線性支持向量機(jī),近似線性支持向量機(jī)以及非線性支持向量機(jī)(核函數(shù))

1 線性支持向量機(jī)

  求解線性支持向量機(jī)的過(guò)程是凸二次規(guī)劃問(wèn)題,所謂凸二次規(guī)劃問(wèn)題,就是目標(biāo)函數(shù)是凸的二次可微函數(shù),約束函數(shù)為仿射函數(shù)(滿足f(x)=a*x+b,a,x均為n為向量)。而我們說(shuō)求解凸二次規(guī)劃問(wèn)題可以利用對(duì)偶算法--即引入拉格朗日算子,利用拉格朗日對(duì)偶性將原始問(wèn)題的最優(yōu)解問(wèn)題轉(zhuǎn)化為拉格朗日對(duì)偶問(wèn)題,這樣就將求w*,b的原始問(wèn)題的極小問(wèn)題轉(zhuǎn)化為求alpha*(alpha>=0)的對(duì)偶問(wèn)題的極大問(wèn)題,即求出alpha*,在通過(guò)KKT條件求出對(duì)應(yīng)的參數(shù)w*,b,從而找到這樣的間隔最大化超平面,進(jìn)而利用該平面完成樣本分類(lèi)

2 近似線性支持向量機(jī)

  當(dāng)數(shù)據(jù)集并不是嚴(yán)格線性可分時(shí),即滿足絕不部分樣本點(diǎn)是線性可分,存在極少部分異常點(diǎn);這里也就是說(shuō)存在部分樣本不能滿足約束條件,此時(shí)我們可以引入松弛因子,這樣這些樣本點(diǎn)到超平面的函數(shù)距離加上松弛因子,就能保證被超平面分隔開(kāi)來(lái);當(dāng)然,添加了松弛因子sigma,我們也會(huì)添加對(duì)應(yīng)的代價(jià)項(xiàng),使得alpha滿足0=<alpha<=C

3 非線性支持向量機(jī)

  顯然,當(dāng)數(shù)據(jù)集不是線性可分的,即我們不能通過(guò)前面的線性模型來(lái)對(duì)數(shù)據(jù)集進(jìn)行分類(lèi)。此時(shí),我們必須想辦法將這些樣本特征符合線性模型,才能通過(guò)線性模型對(duì)這些樣本進(jìn)行分類(lèi)。這就要用到核函數(shù),核函數(shù)的功能就是將低維的特征空間映射到高維的特征空間,而在高維的特征空間中,這些樣本進(jìn)過(guò)轉(zhuǎn)化后,變成了線性可分的情況,這樣,在高維空間中,我們就能夠利用線性模型來(lái)解決數(shù)據(jù)集分類(lèi)問(wèn)題

? ?好了,我們就只講這么寫(xiě)大致的概念,如果想要透徹理解SVM建議還是要看看上面的書(shū)和博客文章,篇幅有限,我這里的中心在于凸二次規(guī)劃的優(yōu)化算法--SMO(序列最小最優(yōu)化算法)

3 SMO算法

  SMO是一種用于訓(xùn)練SVM的強(qiáng)大算法,它將大的優(yōu)化問(wèn)題分解為多個(gè)小的優(yōu)化問(wèn)題來(lái)進(jìn)行求解。而這些小優(yōu)化問(wèn)題往往很容易求解,并且對(duì)它們進(jìn)行順序求解和對(duì)整體求解結(jié)果是一致的。在結(jié)果一致的情況下,顯然SMO算法的求解時(shí)間要短很多,這樣當(dāng)數(shù)據(jù)集容量很大時(shí),SMO就是一致十分高效的算法

  SMO算法的目標(biāo)是找到一系列alpha和b,而求出這些alpha,我們就能求出權(quán)重w,這樣就能得到分隔超平面,從而完成分類(lèi)任務(wù)

  SMO算法的工作原理是:每次循環(huán)中選擇兩個(gè)alpha進(jìn)行優(yōu)化處理。一旦找到一對(duì)合適的alpha,那么就增大其中一個(gè)而減少另外一個(gè)。這里的"合適",意味著在選擇alpha對(duì)時(shí)必須滿足一定的條件,條件之一是這兩個(gè)alpha不滿足最優(yōu)化問(wèn)題的kkt條件,另外一個(gè)條件是這兩個(gè)alpha還沒(méi)有進(jìn)行區(qū)間化處理

  對(duì)于SMO算法編寫(xiě),我們采用由簡(jiǎn)單到復(fù)雜的方法,層層遞進(jìn),完成最終的SMO算法實(shí)現(xiàn),最后通過(guò)實(shí)際的用例對(duì)SVM模型進(jìn)行訓(xùn)練,并驗(yàn)證準(zhǔn)確性

1 簡(jiǎn)化版SMO算法

  簡(jiǎn)化版SMO算法,省略了確定要優(yōu)化的最佳a(bǔ)lpha對(duì)的步驟,而是首先在數(shù)據(jù)集上進(jìn)行遍歷每一個(gè)alpha,再在剩余的數(shù)據(jù)集中找到另外一個(gè)alpha,構(gòu)成要優(yōu)化的alpha對(duì),同時(shí)對(duì)其進(jìn)行優(yōu)化,這里的同時(shí)是要確保公式:Σαi*label(i)=0。所以改變一個(gè)alpha顯然會(huì)導(dǎo)致等式失效,所以這里需要同時(shí)改變兩個(gè)alpha。接下來(lái)看實(shí)際的代碼:

  簡(jiǎn)易版SMO算法的輔助函數(shù):

#SMO算法相關(guān)輔助中的輔助函數(shù) #1 解析文本數(shù)據(jù)函數(shù),提取每個(gè)樣本的特征組成向量,添加到數(shù)據(jù)矩陣 #添加樣本標(biāo)簽到標(biāo)簽向量 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#2 在樣本集中采取隨機(jī)選擇的方法選取第二個(gè)不等于第一個(gè)alphai的 #優(yōu)化向量alphaj def selectJrand(i,m):j=iwhile(j==i):j=int(random.uniform(0,m))return j#3 約束范圍L<=alphaj<=H內(nèi)的更新后的alphaj值 def clipAlpha(aj,H,L):if aj>H:aj=Hif L>aj:aj=Lreturn aj

上面是簡(jiǎn)易版SMO算法需要用到的一些功能,我們將其包裝成函數(shù),需要時(shí)調(diào)用即可,接下來(lái)看算法的偽代碼:

#SMO算法的偽代碼 #創(chuàng)建一個(gè)alpha向量并將其初始化為0向量 #當(dāng)?shù)螖?shù)小于最大迭代次數(shù)時(shí)(w外循環(huán))#對(duì)數(shù)據(jù)集中每個(gè)數(shù)據(jù)向量(內(nèi)循環(huán)):#如果該數(shù)據(jù)向量可以被優(yōu)化:#隨機(jī)選擇另外一個(gè)數(shù)據(jù)向量#同時(shí)優(yōu)化這兩個(gè)向量#如果兩個(gè)向量都不能被優(yōu)化,退出內(nèi)循環(huán)
#如果所有向量都沒(méi)有被優(yōu)化,增加迭代次數(shù),繼續(xù)下一次循環(huán)

實(shí)際代碼如下:

#@dataMat :數(shù)據(jù)列表 #@classLabels:標(biāo)簽列表 #@C :權(quán)衡因子(增加松弛因子而在目標(biāo)優(yōu)化函數(shù)中引入了懲罰項(xiàng)) #@toler :容錯(cuò)率 #@maxIter :最大迭代次數(shù) def smoSimple(dataMat,classLabels,C,toler,maxIter):#將列表形式轉(zhuǎn)為矩陣或向量形式dataMatrix=mat(dataMatIn);labelMat=mat(classLabels).transpose()#初始化b=0,獲取矩陣行列b=0;m,n=shape(dataMatrix)#新建一個(gè)m行1列的向量alphas=mat(zeros((m,1)))#迭代次數(shù)為0iter=0while(iter<maxIter):#改變的alpha對(duì)數(shù)alphaPairsChanged=0#遍歷樣本集中樣本for i in range(m):#計(jì)算支持向量機(jī)算法的預(yù)測(cè)值fXi=float(multiply(alphas,labelMat).T*\(dataMatrix*dataMatrix[i,:].T))+b#計(jì)算預(yù)測(cè)值與實(shí)際值的誤差Ei=fXi-float(labelMat[i])#如果不滿足KKT條件,即labelMat[i]*fXi<1(labelMat[i]*fXi-1<-toler)#and alpha<C 或者labelMat[i]*fXi>1(labelMat[i]*fXi-1>toler)and alpha>0if((labelMat[i]*Ei<-toler)and(alpha<C))or\((labelMat[i]*Ei>toler)and(alpha[i]>0))):#隨機(jī)選擇第二個(gè)變量alphajj=selectJrand(i,m)#計(jì)算第二個(gè)變量對(duì)應(yīng)數(shù)據(jù)的預(yù)測(cè)值fXj=float(multiply(alphas,labelMat).T*\(dataMatrix*dataMatrix[j,:]).T)+b#計(jì)算與測(cè)試與實(shí)際值的差值Ej=fXj-float(label[j])#記錄alphai和alphaj的原始值,便于后續(xù)的比較alphaIold=alphas[i].copy()alphaJold=alphas[j].copy()#如何兩個(gè)alpha對(duì)應(yīng)樣本的標(biāo)簽不相同if(labelMat[i]!=labelMat[j]):#求出相應(yīng)的上下邊界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#根據(jù)公式計(jì)算未經(jīng)剪輯的alphaj#------------------------------------------eta=2.0*dataMatrix[i,:]*dataMatrix[j,:].T-\dataMatrix[i,:]*dataMatrix[i,:].T-\dataMatrix[j,:]*dataMatrix[j,:].T#如果eta>=0,跳出本次循環(huán)if eta>=0:print("eta>=0"):continuealphas[j]-=labelMat[j]*(Ei-Ej)/etaalphas[j]=clipAlpha(alphas[j],H,L)#------------------------------------------ #如果改變后的alphaj值變化不大,跳出本次循環(huán) if(abs(alphas[j]-alphaJold)<0.00001):print("j not moving\enough");continue#否則,計(jì)算相應(yīng)的alphai值alphas[i]+=labelMat[j]*labelMat[i]*(alphaJold-alphas[j])#再分別計(jì)算兩個(gè)alpha情況下對(duì)于的b值b1=b-Ei-labelMat[i]*(alphas[i]-alphaIold)*\dataMatrix[i,:]*dataMat[i,:].T-\labelMat[j]*(alphas[j]-alphaJold)*\dataMatrix[i,:]*dataMatrix[j,:].Tb2=b-Ej-labelMat[i]*(alphas[i]-alphaIold)*\dataMatrix[i,:]*dataMatrix[j,:].T-\labelMat[j]*(alphas[j]-alphaJold)*\dataMatrix[j,:]*dataMatrix[j,:].T#如果0<alphai<C,那么b=b1if(0<alphas[i]) and (C>alphas[i]):b=b1#否則如果0<alphai<C,那么b=b1elif (0<alphas[j]) and (C>alphas[j]):b=b2#否則,alphai,alphaj=0或Celse:b=(b1+b2)/2.0#如果走到此步,表面改變了一對(duì)alpha值alphaPairsChanged+=1print("iter: &d i:%d,paird changed %d",%(iter,i,alphaPairsChanged))#最后判斷是否有改變的alpha對(duì),沒(méi)有就進(jìn)行下一次迭代if(alphaPairsChanged==0):iter+=1#否則,迭代次數(shù)置0,繼續(xù)循環(huán)else:iter=0print("iteration number: %d" %iter)#返回最后的b值和alpha向量return b,alphas

? ? ?上面的代碼量看起來(lái)很多,但事實(shí)上只要理解了SVM算法的理論知識(shí),就很容易理解,其只不過(guò)是將理論轉(zhuǎn)化為機(jī)器可以運(yùn)行的語(yǔ)言而已。

  ?上面代碼在一臺(tái)性能一般的筆記本上對(duì)100個(gè)樣本的數(shù)據(jù)集上運(yùn)行,收斂時(shí)間14.5秒,取得了令人滿意的分類(lèi)效果

? ? ?當(dāng)然,上面的代碼通過(guò)對(duì)整個(gè)數(shù)據(jù)集進(jìn)行兩次遍歷的方法來(lái)尋找alpha對(duì)的方法,顯然存在一定的不足,如果數(shù)據(jù)集規(guī)模較小的情況下,或許還可以滿足要求。但是對(duì)于大規(guī)模的數(shù)據(jù)集而言,上面的代碼顯然收斂速度非常慢,所以,接下來(lái)我們?cè)诖嘶A(chǔ)上對(duì)選取合適的alpha對(duì)方法進(jìn)行改進(jìn),采用啟發(fā)式的方法來(lái)選取合適的alpha對(duì),從而提升運(yùn)算效率。

2 啟發(fā)式選取alpha變量的SMO算法

  啟發(fā)式的SMO算法一個(gè)外循環(huán)來(lái)選擇第一個(gè)alpha值,并且其選擇過(guò)程會(huì)在下面兩種方法之間進(jìn)行交替:

(1)在所有數(shù)據(jù)集上進(jìn)行單遍掃描

(2)另一種方法是在間隔邊界上樣本點(diǎn)進(jìn)行單遍掃描,所謂間隔邊界上的點(diǎn)即為支持向量點(diǎn)。

? ? ?顯然,對(duì)于整個(gè)數(shù)據(jù)集遍歷比較容易,而對(duì)于那些處于間隔邊界上的點(diǎn),我們還需要事先將這些點(diǎn)對(duì)應(yīng)的alpha值找出來(lái),存放在一個(gè)列表中,然后對(duì)列表進(jìn)行遍歷;此外,在選擇第一個(gè)alpha值后,算法會(huì)通過(guò)一個(gè)內(nèi)循環(huán)來(lái)選擇第二個(gè)值,在優(yōu)化的過(guò)程中依據(jù)alpha的更新公式αnew,unc=aold+label*(Ei-Ej)/η,(η=dataMat[i,:]*dataMat[i,:].T+dataMat[j,:]*dataMat[j,:].T-2*dataMat[i,:]*dataMat[j,:].T),可知alpha值的變化程度更Ei-Ej的差值成正比,所以,為了使alpha有足夠大的變化,選擇使Ei-Ej最大的alpha值作為另外一個(gè)alpha。所以,我們還可以建立一個(gè)全局的緩存用于保存誤差值,便于我們選擇合適的alpha值

  下面是創(chuàng)建的一個(gè)數(shù)據(jù)結(jié)構(gòu)類(lèi),便于我們存取算法中需要用到的重要數(shù)據(jù):

#啟發(fā)式SMO算法的支持函數(shù) #新建一個(gè)類(lèi)的收據(jù)結(jié)構(gòu),保存當(dāng)前重要的值 class optStruct:def __init__(self,dataMatIn,classLabels,C,toler):self.X=dataMatInself.labelMat=classLabelsself.C=Cself.tol=tolerself.m=shape(dataMatIn)[0]self.alphas=mat(zeros((self.m,1)))self.b=0self.eCache=mat(zeros((self.m,2))) #格式化計(jì)算誤差的函數(shù),方便多次調(diào)用 def calcEk(oS,k):fXk=float(multiply(oS.alphas,oS.labelMat).T*\(oS.X*oS.X[k,:].T))+oS.bEk=fXk-float(oS.labelMat[k])return Ek #修改選擇第二個(gè)變量alphaj的方法 def selectJ(i,oS,Ei):maxK=-1;maxDeltaE=-;Ej=0#將誤差矩陣每一行第一列置1,以此確定出誤差不為0#的樣本oS.eCache[i]=[1,Ei]#獲取緩存中Ei不為0的樣本對(duì)應(yīng)的alpha列表validEcacheList=nonzero(oS.Cache[:,0].A)[0]#在誤差不為0的列表中找出使abs(Ei-Ej)最大的alphajif(len(validEcacheList)>0):for k in validEcacheList:if k ==i:continueEk=calcEk(oS,k)deltaE=abs(Ei-Ek)if(deltaE>maxDeltaE):maxK=k;maxDeltaE=deltaE;Ej=Ekreturn maxK,Ejelse:#否則,就從樣本集中隨機(jī)選取alphajj=selectJrand(i,oS.m)Ej=calcEk(oS,j)return j,Ej #更新誤差矩陣 def updateEk(oS,k):Ek=calcEk(oS,k)oS.eCache[k]=[1,Ek]

  好了,有了這些輔助性的函數(shù),我們就可以很容易的實(shí)現(xiàn)啟發(fā)式的SMO算法的具體代碼:

#SMO外循環(huán)代碼 def smoP(dataMatIn,classLabels,C,toler,maxIter,kTup=('lin',0)):#保存關(guān)鍵數(shù)據(jù)oS=optStruct(mat(dataMatIn),mat(classLabels).transpose(),C,toler)iter=0enrireSet=True;alphaPairsChanged=0#選取第一個(gè)變量alpha的三種情況,從間隔邊界上選取或者整個(gè)數(shù)據(jù)集while(iter<maxIter)and((alphaPairsChanged>0)or(entireSet)):alphaPairsChanged=0#沒(méi)有alpha更新對(duì)if entireSet:for i in range(oS.m):alphaPairsChanged+=innerL(i,oS)print("fullSet,iter: %d i:%d,pairs changed %d",%\(iter,i,alphaPairsChanged))else:#統(tǒng)計(jì)alphas向量中滿足0<alpha<C的alpha列表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+=1if entireSet:entireSet=False#如果本次循環(huán)沒(méi)有改變的alpha對(duì),將entireSet置為true,#下個(gè)循環(huán)仍遍歷數(shù)據(jù)集elif (alphaPairsChanged==0):entireSet=Trueprint("iteration number: %d",%iter)return oS.b,oS.alphas
#內(nèi)循環(huán)尋找alphaj def innerL(i,oS):#計(jì)算誤差Ei=calcEk(oS,i)#違背kkt條件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=alphas[i].copy();alphaJold=alphas[j].copy()#計(jì)算上下界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#計(jì)算兩個(gè)alpha值eta=2.0*oS.X[i,:]*oS.X[j,:].T-oS.X[i,:]*oS.X[i,:].T-\oS.X[j,:]*oS.X[j,:].Tif eta>=0:print("eta>=0");return 0oS.alphas[j]-=oS.labelMat[j]*(Ei-Ej)/etaoS.alphas[j]=clipAlpha(oS.alphas[j],H,L)updateEk(oS,j)if(abs(oS.alphas[j]-alphaJold)<0.00001):print("j not moving enough");return 0oS.alphas[i]+=oS.labelMat[j]*oS.labelMat[i]*\(alphaJold-oS.alphas[j])updateEk(oS,i)#在這兩個(gè)alpha值情況下,計(jì)算對(duì)應(yīng)的b值#注,非線性可分情況,將所有內(nèi)積項(xiàng)替換為核函數(shù)K[i,j]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,:].Tb2=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,:].Tif(0<oS.alphas[i])and (oS.C>oS.alphas[i]):oS.b=b1elif(0<oS.alphas[j])and (oS.C>oS.alphas[j]):oS.b=b2else:oS.b=(b1+b2)/2.0#如果有alpha對(duì)更新return 1#否則返回0else return 0

? ? ?顯然,上面的SMO完整代碼是分為內(nèi)外兩個(gè)循環(huán)函數(shù)來(lái)編寫(xiě)的,采取這樣的結(jié)構(gòu)可以更方便我們?nèi)ダ斫膺x取兩個(gè)alpha的過(guò)程;既然,我們已經(jīng)計(jì)算出了alpha值和b值,那么顯然我們可以利用公式w*=Σαi*label[i]*dataMat[i,:]計(jì)算出相應(yīng)的權(quán)值參數(shù),然后就可以得到間隔超平面的公式w*x+b*來(lái)完成樣本的分類(lèi)了,由于SVM算法是一種二類(lèi)分類(lèi)算法,正值為1,負(fù)值為-1,即分類(lèi)的決策函數(shù)為跳躍函數(shù)sign(w*x+b*)

然后,我們可以編寫(xiě)一小段測(cè)試代碼,來(lái)利用SMO算法得到的alpha值和b值,計(jì)算分類(lèi)決策函數(shù),從而實(shí)現(xiàn)具體的預(yù)測(cè)分類(lèi)了

#求出了alpha值和對(duì)應(yīng)的b值,就可以求出對(duì)應(yīng)的w值,以及分類(lèi)函數(shù)值 def predict(alphas,dataArr,classLabels):X=mat(dataArr);labelMat=mat(classLabels)m,n=shape(X)w=zeros((n,1))for i in range(m):w+=multiply(alphas[i]*labelMat[i],X[i,:].T)result=dataArr[0]*mat(ws)+breturn sign(result)

? ? ? 看一下分類(lèi)效果:

3 核函數(shù)

  核函數(shù)的目的主要是為了解決非線性分類(lèi)問(wèn)題,通過(guò)核技巧將低維的非線性特征轉(zhuǎn)化為高維的線性特征,從而可以通過(guò)線性模型來(lái)解決非線性的分類(lèi)問(wèn)題。

如下圖,當(dāng)數(shù)據(jù)集不是線性可分時(shí),即數(shù)據(jù)集分布是下面的圓形該怎么辦呢?

?

  顯然,此時(shí)數(shù)據(jù)集線性不可分,我們無(wú)法用一個(gè)超平面來(lái)將兩種樣本分隔開(kāi);那么我們就希望將這些數(shù)據(jù)進(jìn)行轉(zhuǎn)化,轉(zhuǎn)化之后的數(shù)據(jù)就能夠通過(guò)一個(gè)線性超平面將不同類(lèi)別的樣本分開(kāi),這就需要核函數(shù),核函數(shù)的目的主要是為了解決非線性分類(lèi)問(wèn)題,通過(guò)核技巧將低維的非線性特征轉(zhuǎn)化為高維的線性特征,從而可以通過(guò)線性模型來(lái)解決非線性的分類(lèi)問(wèn)題。

  而徑向基核函數(shù),是SVM中常用的一個(gè)核函數(shù)。徑向基核函數(shù)是一個(gè)采用向量作為自變量的函數(shù),能夠基于向量距離運(yùn)算輸出一個(gè)標(biāo)量。徑向基核函數(shù)的高斯版本公式為:

  k(x,y)=exp(-||x-y||2/2σ2),其中,σ為到達(dá)率,決定了函數(shù)值跌落至0的速度

  下面通過(guò)代碼編寫(xiě)高斯核函數(shù):

#徑向基核函數(shù)是svm常用的核函數(shù) #核轉(zhuǎn)換函數(shù) def kernelTrans(X,A,kTup):m,n=shape(X)K=mat(zeros((m,1)))#如果核函數(shù)類(lèi)型為'lin'if kTup[0]=='lin':K=X*A.T#如果核函數(shù)類(lèi)型為'rbf':徑向基核函數(shù)#將每個(gè)樣本向量利用核函數(shù)轉(zhuǎn)為高維空間elif kTup[0]=='rbf'for j in range(m):deltaRow=X[j,:]-AK[j]=deltaRow*deltaRow.TK=exp(K/(-1*kTup[1]**2))elseraise NameError('Houston we Have a Problem -- \That Kernel is not recognised')return K#對(duì)核函數(shù)處理的樣本特征,存入到optStruct中 class optStruct:def __init__(self,dataMatIn,classLabels,C,toler,kTup):self.X=dataMatInself.labelMat=classLabelsself.C=Cself.tol=tolerself.m=shape(dataMatIn)[0]self.alphas=mat(zeros((self.m,1)))self.b=0self.eCache=mat(zeros((self.m,2)))self.K=mat(zeros((self.m,self.m)))for i in range(self.m):self.K[:,i]=kernelTrans(self.X,self.X[i,:],kTup)

需要說(shuō)明的是,這里引入了一個(gè)變量kTup,kTup是一個(gè)包含核信息的元組,它提供了選取的核函數(shù)的類(lèi)型,比如線性'lin'或者徑向基核函數(shù)'rbf';以及用戶(hù)提供的到達(dá)率σ

  有了高斯核函數(shù)之后,我們只要將上面的SMO算法中所有的內(nèi)積項(xiàng)替換為核函數(shù)即可,比如講dataMat[i,:]*dataMat[j,:].T替換為k[i,j]即可,替換效果如下:

def innerL(i,oS):#計(jì)算誤差Ei=calcEk(oS,i)#違背kkt條件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=alphas[i].copy();alphaJold=alphas[j].copy()#計(jì)算上下界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#計(jì)算兩個(gè)alpha值eta=2.0*oS.K[i,j]-oS.K[i,i]-oS.K[j,j]if eta>=0:print("eta>=0");return 0oS.alphas[j]-=oS.labelMat[j]*(Ei-Ej)/etaoS.alphas[j]=clipAlpha(oS.alphas[j],H,L)updateEk(oS,j)if(abs(oS.alphas[j]-alphaJold)<0.00001):print("j not moving enough");return 0oS.alphas[i]+=oS.labelMat[j]*oS.labelMat[i]*\(alphaJold-oS.alphas[j])updateEk(oS,i)#在這兩個(gè)alpha值情況下,計(jì)算對(duì)應(yīng)的b值#注,非線性可分情況,將所有內(nèi)積項(xiàng)替換為核函數(shù)K[i,j]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]b2=oS.b-Ej-oS.labelMat[i]*(oS.alphas[i]-alphaIold)*\oS.k[i,j]-\oS.labelMat[j]*(oS.alphas[j]-alphaJold)*\oS.k[i,j]if(0<oS.alphas[i])and (oS.C>oS.alphas[i]):oS.b=b1elif(0<oS.alphas[j])and (oS.C>oS.alphas[j]):oS.b=b2else:oS.b=(b1+b2)/2.0#如果有alpha對(duì)更新return 1#否則返回0else return 0

 有了核函數(shù),我們就能對(duì)非線性的數(shù)據(jù)集進(jìn)行分類(lèi)預(yù)測(cè)了,接下來(lái)就是編寫(xiě)代碼利用核函數(shù)進(jìn)行測(cè)試,需要說(shuō)明的是,在優(yōu)化的過(guò)程中,我們僅僅需要找到支持向量和其對(duì)應(yīng)的alpha值,而對(duì)于其他的樣本值可以不用管,甚至可以舍棄,因?yàn)檫@些樣本將不會(huì)對(duì)分類(lèi)預(yù)測(cè)函數(shù)造成任何影響。這也就是SVM相比KNN算法的優(yōu)秀的地方所在

#測(cè)試核函數(shù) #用戶(hù)指定到達(dá)率 def testRbf(k1=1.3):#第一個(gè)測(cè)試集dataArr,labelArr=loadDataSet('testSetRBF.txt')b,alphas=smoP(dataArr,labelArr,200,0.0001,10000,('rbf',k1))dataMat=mat(dataArr);labelMat=mat(labelArr).transpose()svInd=nonzero(alphas.A>0)[0]sVs=dataMat[svInd]labelSV=labelMat[svInd]print("there are %d Support Vectors",%shape(sVs)[0])m,n=shape(dataMat)errorCount=0for i in range(m):kernelEval=kernelTrans(sVs,dataMat[i,:],('rbf',k1))predict=kernelEval.T*multiply(labelSV,alphas[svInd])+bif sign(predict)!=sign(labelArr[i]):errorCount+=1print("the training error rate is: %f",%(float(errorCount)/m))#第二個(gè)測(cè)試集dataArr,labelArr=loadDataSet('testSetRBF2.txt')dataMat=mat(dataArr);labelMat=mat(labelArr).transpose()errorCount=0m,n=shape(dataMat)for i in range(m):kernelEval=kernelTrans(sVs,dataMat[i,:],('rbf',k1))predict=kernelEval.T*multiply(labelSV,alphas[svInd])+bif sign(predict)!=sign(labelArr[i]):errorCount+=1print("the training error rate is: %f",%(float(errorCount)/m))

? ? ?當(dāng)用戶(hù)輸入σ=1.3時(shí)的實(shí)驗(yàn)結(jié)果為:

? ? ?當(dāng)σ=0.1時(shí)實(shí)驗(yàn)結(jié)果為:

? ? 通過(guò)輸入不同的σ值(當(dāng)然,迭代次數(shù)也會(huì)有一定的影響,我們只討論σ值),我們發(fā)現(xiàn)測(cè)試錯(cuò)誤率,訓(xùn)練誤差率,支持向量個(gè)數(shù)都會(huì)發(fā)生變化,在一定的范圍內(nèi),支持向量數(shù)目的下降,會(huì)使得訓(xùn)練錯(cuò)誤率和測(cè)試錯(cuò)誤率都下降,但是當(dāng)?shù)诌_(dá)某處的最優(yōu)值時(shí),再次通過(guò)增大σ值的方法減少支持向量,此時(shí)訓(xùn)練錯(cuò)誤率下降,而測(cè)試誤差上升

  簡(jiǎn)言之,對(duì)于固定的數(shù)據(jù)集,支持向量的數(shù)目存在一個(gè)最優(yōu)值,如果支持向量太少,會(huì)得到一個(gè)很差的決策邊界;而支持向量太多,也相當(dāng)于利用整個(gè)數(shù)據(jù)集進(jìn)行分類(lèi),就類(lèi)似于KNN算法,顯然運(yùn)算速度不高。

三,SVM實(shí)例:手寫(xiě)識(shí)別問(wèn)題

  相較于第二張的KNN算法,盡管KNN也能取得不錯(cuò)的效果;但是從節(jié)省內(nèi)存的角度出發(fā),顯然SVM算法更勝一籌,因?yàn)槠洳恍枰4嬲鎮(zhèn)€數(shù)據(jù)集,而只需要其作用的支持向量點(diǎn),而取得不錯(cuò)的分類(lèi)效果。

#實(shí)例:手寫(xiě)識(shí)別問(wèn)題 #支持向量機(jī)由于只需要保存支持向量,所以相對(duì)于KNN保存整個(gè)數(shù)據(jù)集占用更少內(nèi)存 #且取得可比的效果#基于svm的手寫(xiě)數(shù)字識(shí)別 def loadImages(dirName):from os import listdirhwLabels=[]trainingFileList=listdir(dirName)m=len(trainingFileList)trainingMat=zeros((m,1024))for i in range(m):fileNameStr=trainingFileList[i]fileStr=fileNameStr.split('.')[0]classNumStr=int(fileStr.split('_')[0])if classNumStr==9:hwLabels.append(-1)else:hwLabels.append(1)trainingMat[i,:]=img2vector('%s/%s',%(dirName,fileNameStr))return hwLabels,trainingMat#將圖像轉(zhuǎn)為向量 def img2vector(fileaddir):featVec=zeros((1,1024))fr=open(filename)for i in range(32):lineStr=fr.readline()for j in range(32):featVec[0,32*i+j]=int(lineStr[j])return featVec#利用svm測(cè)試數(shù)字 def testDigits(kTup=('rbf',10)):#訓(xùn)練集dataArr,labelArr=loadDataSet('trainingDigits')b,alphas=smoP(dataArr,labelArr,200,0.0001,10000,kTup)dataMat=mat(dataArr);labelMat=mat(labelArr).transpose()svInd=nonzero(alphas.A>0)[0]sVs=dataMat[svInd]labelSV=labelMat[svInd]print("there are %d Support Vectors",%shape(sVs)[0])m,n=shape(dataMat)errorCount=0for i in range(m):kernelEval=kernelTrans(sVs,dataMat[i,:],kTup)predict=kernelEval.T*multiply(labelSV,alphas[svInd])+bif sign(predict)!=sign(labelArr[i]):errorCount+=1print("the training error rate is: %f",%(float(errorCount)/m))#測(cè)試集dataArr,labelArr=loadDataSet('testDigits.txt')dataMat=mat(dataArr);labelMat=mat(labelArr).transpose()errorCount=0m,n=shape(dataMat)for i in range(m):kernelEval=kernelTrans(sVs,dataMat[i,:],('rbf',k1))predict=kernelEval.T*multiply(labelSV,alphas[svInd])+bif sign(predict)!=sign(labelArr[i]):errorCount+=1print("the training error rate is: %f",%(float(errorCount)/m))

下面來(lái)看一下,在kTup=('rbf',20)情況下的測(cè)試誤差率和支持向量個(gè)數(shù)情況

并且通過(guò)嘗試不同的σ值,以及嘗試了線性核函數(shù),可以得到關(guān)于不同σ值的書(shū)寫(xiě)數(shù)字識(shí)別性能:

內(nèi)核模式,設(shè)置 訓(xùn)練錯(cuò)誤率(%) 測(cè)試錯(cuò)誤率(%) 支持向量數(shù)
rbf,0.1 0 52 402
rbf,5 0 3.2 402
rbf,10 0 0.5 99
rbf,50 0.2 2.2 41
rbf,100 4.5 4.3 26
Linear 2.7 2.2 38

? ? ? 由上圖可以看出,σ值在取10時(shí)取得了最好的分類(lèi)效果,這也印證了我們上面的敘述。即對(duì)于固定的數(shù)據(jù)集,存在最優(yōu)的支持向量個(gè)數(shù),使得分類(lèi)錯(cuò)誤率最低。支持向量的個(gè)數(shù)會(huì)隨著σ值的增大而逐漸減少,但是分類(lèi)錯(cuò)誤率確實(shí)一個(gè)先降低后升高的過(guò)程。即最小的分類(lèi)錯(cuò)誤率并不意味著最少的支持向量個(gè)數(shù)。

4 總結(jié)

  支持向量機(jī)是一種通過(guò)求解凸二次規(guī)劃問(wèn)題來(lái)解決分類(lèi)問(wèn)題的算法,具有較低的泛化錯(cuò)誤率。而SMO算法可以通過(guò)每次只優(yōu)化兩個(gè)alpha值來(lái)加快SVM的訓(xùn)練速度。

  核技巧是將數(shù)據(jù)由低維空間映射到高維空間,可以將一個(gè)低維空間中的非線性問(wèn)題轉(zhuǎn)換為高維空間下的線性問(wèn)題來(lái)求解。而徑向基核函數(shù)是一個(gè)常用的度量?jī)蓚€(gè)向量距離的核函數(shù)。

  最后,支持向量機(jī)的優(yōu)缺點(diǎn):

   優(yōu)點(diǎn):泛化錯(cuò)誤率低,計(jì)算開(kāi)銷(xiāo)不大

   缺點(diǎn):對(duì)參數(shù)調(diào)節(jié)和核函數(shù)的選擇敏感,且僅適用于二類(lèi)分類(lèi)

總結(jié)

以上是生活随笔為你收集整理的机器学习实战之SVM的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。