支持向量機(jī)
linear regression 奴拦, perceptron learning algorithm , logistics regression都是分類器逢捺,我們可以使用這些分類器做線性和非線性的分類混槐,比如下面的一個(gè)問(wèn)題:
這里的每一條線都是可以把這個(gè)平面分開的狰贯,支持向量機(jī)要做的就是要在這些可以選擇的直線中選擇一條最好的直線來(lái)作為分類的直線。再給一個(gè)簡(jiǎn)單的解釋刑棵,比如下面的三個(gè)圖片巴刻,圓圈區(qū)域越大,說(shuō)明這條直線對(duì)這些點(diǎn)放錯(cuò)的容忍度就越高:
①超平面
介紹SVM之前蛉签,先來(lái)看超平面的概念:
其實(shí)超平面就是用于分割當(dāng)前維度的一個(gè)空間胡陪。比如一維可以用一個(gè)點(diǎn)來(lái)進(jìn)行分割沥寥,二維用一條線來(lái)進(jìn)行分割,那么這些點(diǎn)和線就叫做“超”平面柠座。加雙引號(hào)是因?yàn)樗麄兤鋵?shí)并不是正在的超平面邑雅,因?yàn)槌矫媸且蟠笥谌S的。
所以四維空間里面的超平面就是三維妈经。比如在二維的空間里的超平面就是一條直線:
三維里面的超平面:
(其實(shí)這里的應(yīng)該都不能叫超平面淮野,因?yàn)槌矫媸侨S以及三維以上的)
我們把a(bǔ) , b 吹泡, c看做是W0 , W1 , W2...骤星,把x , y , z看做是x1 , x2 , x3,那么就有:
而W向量就是這個(gè)平面的法向量荞胡,我們要求的就是法向量妈踊,證明一下:
以二維為例:
相減得到:
而[ (X_0 - X_1) , (Y_0 , Y_1)]這個(gè)點(diǎn)就在這個(gè)平面上,所以得到了:
所以W就是平面的法向量泪漂,這上面的X代表的是這個(gè)平面而不是點(diǎn)廊营。
②函數(shù)間隔的最大化
剛剛說(shuō)到支持向量機(jī)也不是找超平面了,而是找最好的超平面萝勤,也就是對(duì)于點(diǎn)的犯錯(cuò)的容忍度越大越好露筒,其實(shí)就是函數(shù)間隔越大越好:
右邊的明顯要好過(guò)左邊的,因?yàn)樽筮叺目煞稿e(cuò)空間大啊敌卓。所以我們要尋找的就是最大最肥的超平面——hperplane慎式。
函數(shù)的間隔最大,其實(shí)就是距離直線距離最短的點(diǎn)離直線的距離要最大趟径。所以先要知道直線的距離怎么求:
首先我們假設(shè)X0在平面上的投影是X1瘪吏,則肯定有法向量W垂直于X0X1,:
又因?yàn)椋?br>
右因?yàn)閄1在平面上的,前半部分就是-b了蜗巧,可以寫成:
和上面那條式子相等就得到:
這就是我們要求的點(diǎn)到直線的距離了掌眠。而如果這個(gè)hperplane是正確的話,那么所有點(diǎn)的分類都是對(duì)的幕屹,那么我們就默認(rèn)他是對(duì)的蓝丙,于是有:
這里可以相乘的條件是,我們默認(rèn)label正確的是1錯(cuò)誤的是-1望拖,如果你的錯(cuò)誤是0正確是1的話公式是不同的渺尘。乘上一個(gè)Y首先是可以去掉絕對(duì)值,使得函數(shù)變得可微说敏,另外乘上之后函數(shù)值的絕對(duì)值也不會(huì)有變化鸥跟,使得求解更加方便。
所以盔沫,最后的我們的優(yōu)化目標(biāo)就是這樣了:
里面的minimize是指找到距離hperplane最小距離的點(diǎn)锌雀,最外面就是挑選一個(gè)最好的W,b使得這個(gè)距離最小的點(diǎn)距離hperplane是最大的蚂夕。
③目標(biāo)函數(shù)的化簡(jiǎn)
對(duì)于上面的式子,注意看到里面的那個(gè)式子:
舉一個(gè)例子:
我們代入(4,3)這個(gè)點(diǎn)腋逆,得到19婿牍,似乎這個(gè)數(shù)字太大了,我們不想要他這么大惩歉,我們兩邊同時(shí)除去19等脂,這個(gè)時(shí)候我們的超平面就變成了:
數(shù)字是邊了,但是這個(gè)超平面還是在這個(gè)位置撑蚌,所以可以認(rèn)為超平面是沒(méi)有變化的上遥,這就證明了我們上面那個(gè)式子:
是可以通過(guò)對(duì)w,b進(jìn)行放縮而把左邊的結(jié)果放大的無(wú)限多倍的。既然這樣争涌,那這個(gè)東西留著有什么意義粉楚,直接放縮到1就可以了,于是我們把他放縮到1亮垫,也就是最小值是1模软。其實(shí)等于1都是差不多的,因?yàn)樽钚≈抵蠖际?饮潦,于是就是有了:
那么target fomula就可以變成這樣:
放縮之后并不是就完了燃异,這個(gè)是要加入當(dāng)做條件來(lái)使用的。對(duì)于這個(gè):
所以問(wèn)題就變成了:
為什么要加上1/2呢稀并?其實(shí)是為了后面求導(dǎo)的時(shí)候方便化簡(jiǎn)的仅颇,但是這樣對(duì)結(jié)果又沒(méi)有什么影響。而W變成平方其實(shí)就是用上凸優(yōu)化碘举,因?yàn)槠椒街缶蛡€(gè)凸函數(shù)了忘瓦,這樣變換同樣對(duì)于最優(yōu)結(jié)果沒(méi)有任何影響。
所以最后要優(yōu)化的結(jié)果:
④Dual problem and KKT condiction
對(duì)于上述有條件的最優(yōu)化問(wèn)題殴俱,自然就要用上lagrange乘子法了。
右邊的約束條件是要小于等于0的枚抵,α ≥ 0线欲,只不過(guò)前面是符號(hào)所以轉(zhuǎn)一下而已。
到這里汽摹,其實(shí)只要把等式扔到Quadratic Programming里面直接計(jì)算就好了李丰。下面的步驟其實(shí)都是圍繞解決這個(gè)優(yōu)化問(wèn)題展開的。
先在這停頓一下逼泣,我們考慮一下:
⑴SVM的機(jī)器學(xué)習(xí)可行性問(wèn)題:
首先先來(lái)觀察一下這個(gè)式子趴泌,感覺(jué)似曾相識(shí)舟舒。他和L2 regularization很像,對(duì)于L2 regularization嗜憔,首先是先要計(jì)算Ein的最小值秃励,所謂的Ein就是該模型在當(dāng)前的訓(xùn)練數(shù)據(jù)上犯錯(cuò)誤的期望值。然后再正則化吉捶,所以L2是Minimizing Ein and Regularized L2 Paradigms夺鲜;而支持向量機(jī)正好相反,他是先假設(shè)我這個(gè)平面是分類正確的呐舔,然后minimize W方:
后面我們會(huì)用這個(gè)結(jié)論的币励。
回顧一下機(jī)器學(xué)習(xí)要解決的問(wèn)題:
①Ein ≈ Eout
Ein剛剛解釋過(guò)了,Eout就是這個(gè)model在全局所犯的錯(cuò)誤珊拼,Ein ≈ Eout就是要求這個(gè)model是可以反映全局的食呻,如果不能反映,那就是過(guò)擬合了澎现。
②Ein ≈ 0
這個(gè)就是訓(xùn)練錯(cuò)誤要接近于0仅胞,在這一步就容易發(fā)生過(guò)擬合的現(xiàn)象了。
而Ein ≈ Eout昔头,也就是泛化能力是被VC dimension限制的饼问,也就是說(shuō),越復(fù)雜的模型他的VC dimension越復(fù)雜揭斧。也就是VC bound右邊的Ω會(huì)很大莱革,VC bound就會(huì)很大,導(dǎo)致Ein 遠(yuǎn)遠(yuǎn)小于Eout了讹开,因?yàn)閺?fù)雜的模型意味著更加小的Ein盅视。
再提一下,VC dimension就是break point - 1得到的旦万。
如果是這樣的話闹击,那么正常來(lái)說(shuō)SVM的VC dimension也會(huì)很大啊,因?yàn)樗腤是和數(shù)據(jù)維度相關(guān)的成艘,數(shù)據(jù)多少維赏半,那么W就多少個(gè),而W代表的是自由度淆两,通常也就代表這VC dimension断箫,但是SVM的效果還是很好,為什么呢秋冰?是什么東西限制著SVM的VC dimension仲义?
我們來(lái)看一個(gè)例子:
在一個(gè)圓上,有三個(gè)點(diǎn),你想找到一條可以分開的直線埃撵,可以得到VC dimension是3(之前有同學(xué)看到在一個(gè)圓上分類他的VC dimension是無(wú)限的的赵颅,這是因?yàn)橛袩o(wú)數(shù)多個(gè)點(diǎn)給你玩,這里就三個(gè)點(diǎn)暂刘,無(wú)限你又用不了饺谬,所以就只能是三個(gè)了啦),但是如果加上限制條件鸳惯,這條線寬是5商蕴,那么VC dimension就是0了达皿,因?yàn)闆](méi)有地方塞進(jìn)去荸镊。所以如果是large margin漩蟆,VC dimension ≤ 3的敷待。如圖:
所以梯捕,large margin就是SVM的VCdimension的限制條件菩彬,導(dǎo)致它的分類效果很好屉栓,VC dimension小了自然泛化能力就好了旨枯,這里就解決了Ein ≈ Eout的問(wèn)題独悴,Ein ≈ 0這就是我們后面要用凸優(yōu)化來(lái)解決的問(wèn)題了例书。
回到正題:
如何優(yōu)化我們的target function?
上面的討論我們已經(jīng)得到了VC dimension ≤ d + 1刻炒,我們悲觀估計(jì)一下决采,就算SVM的VC dimension是d + 1了,加上d就是數(shù)據(jù)維度坟奥,加上的1是偏置值b树瞭。那么如果數(shù)據(jù)維度很大的話,計(jì)算復(fù)雜度是很高的爱谁,另外晒喷,現(xiàn)在我們所研究的SVM還是linear separable的,如果是來(lái)個(gè)nonlinear transform访敌,數(shù)據(jù)維度就更加大了凉敲,再加上一般數(shù)據(jù)數(shù)量都是很的,時(shí)間會(huì)很長(zhǎng)寺旺。所以我們要想一個(gè)方法來(lái)把數(shù)據(jù)維度d轉(zhuǎn)移到其他地方去或者之間丟了爷抓。
而Daul problem恰好就可以解決。
回到origin target function:
我們需要最小化:
對(duì)原函數(shù)做一些變換:
當(dāng)這個(gè)點(diǎn)是違反了條件的時(shí)候阻塑,那么約束條件就會(huì) > 0蓝撇,α > 0,再maximum α那么就是無(wú)限大了叮姑,這個(gè)時(shí)候就不可能是最小值唉地,不可能取他,因?yàn)槭菬o(wú)限大了传透。
當(dāng)這個(gè)點(diǎn)是不違反的時(shí)候耘沼,那么約束條件就會(huì) < 0,α > 0朱盐,再maximum α約束條件就是0群嗤,minimize w,b之后就還是原來(lái)的target function兵琳。
所以變換之后:
變換之后的問(wèn)題 == origin target function
再次停頓一下狂秘,考慮一下KKT條件是什么:
⑵KKT 條件的引出
對(duì)于上述裝換過(guò)的target function,有如下性質(zhì):
那么對(duì)于任何的條件都會(huì)有左邊≥右邊的躯肌。左邊再加上一個(gè)w,b取最姓叽骸:
同樣是大于右邊的所有情況,那么自然了清女,我右邊再加上一個(gè)取α的最大值也是可以的:
而在右邊的我們把條件minimize和maximum調(diào)換過(guò)的式子就叫做Daul Problem钱烟。所以,原問(wèn)題是≥對(duì)偶問(wèn)題的嫡丙。那么有沒(méi)有什么辦法可以把原問(wèn)題的求解轉(zhuǎn)換成對(duì)偶問(wèn)題的求解呢拴袭?
⑶KKT 條件的簡(jiǎn)單證明
對(duì)偶的意思就是存在一個(gè)最優(yōu)的解使得兩邊的等式成立。所以我們假設(shè)有一個(gè)W和B是最優(yōu)的曙博,那么有:
而最后可以看到求出來(lái)的解正是我們要求的f(W)原目標(biāo)函數(shù)拥刻,而原式子:
代進(jìn)去也將是這個(gè)結(jié)果,因?yàn)閙aximum之后ag(x) = 0父泳,所以本質(zhì)上這個(gè)函數(shù)還是求f(W)的最小值般哼。所以對(duì)偶式子和原式在結(jié)果上是沒(méi)有差別的。根據(jù)上面的式子尘吗,我們本質(zhì)就是要求
的最小值逝她,當(dāng)然這里的W,B要替換成原來(lái)的變量w,b了睬捶。求最小值自然就是求梯度為0了黔宛,所以w,b梯度為0的條件就有了。還有一個(gè)ag(x) = 0的條件擒贸,這個(gè)其實(shí)是前提條件:
我們之前說(shuō)這個(gè)式子是等同目標(biāo)函數(shù)的臀晃,既然要等同自然是要把后面的g(x)和h(x)消去啊介劫!而h(x) = 0本來(lái)就消去了徽惋,而g(x) < 0,求最大必然就ag(x) = 0了座韵,因?yàn)橹挥羞@個(gè)條件险绘,才能消去后面的ag(x)把這個(gè)minimum maximum式子變成minimumf(w)的式子踢京。所以再加上先前的幾個(gè)拉格朗日條件就組成了KKT條件了。
所以KKT condition就是:
最后的幾個(gè)條件其實(shí)是lagrange乘子法的條件宦棺。
一點(diǎn)小的問(wèn)題
這篇文章是沒(méi)有考研之前學(xué)的瓣距,對(duì)于條件極值和無(wú)條件極值理解還不是很透徹,考完回來(lái)再看之后發(fā)現(xiàn)其實(shí)和高數(shù)學(xué)的條件極值和無(wú)條件極值沒(méi)有什么區(qū)別代咸。
回到正題更鲁,既然我們知道了可以利用KKT condition把origin target function轉(zhuǎn)換到daul problem 來(lái)求解,那么上面這個(gè)問(wèn)題我們嘗試用KKT條件求解一下:
首先對(duì)w奇钞,b求偏導(dǎo):
把結(jié)果代回到dual problem:
所以最后我們的target function就變成了這樣澡为。
最后我們可以用QP對(duì)這個(gè)問(wèn)題進(jìn)行求解,求出了α之后景埃,我們隨便取一個(gè)α是非0的媒至,也就是>0的解,這時(shí)候利用α*g(x) = 0的條件得到
b就求出來(lái)了谷徙,對(duì)于w直接代換上面的公式就好了拒啰。
而當(dāng)α>0,由上面的公式可以得到這個(gè)點(diǎn)就剛剛好是在邊界上完慧,而這些點(diǎn)就叫做support vector谋旦,支持向量的點(diǎn)。我們的擬合直線也將會(huì)由著些點(diǎn)確定屈尼,其他不是support vector的點(diǎn)α就是0册着。
又停頓一下,我們對(duì)這個(gè)式子思考一下:
⑷為什么我們需要dual problem
其實(shí)這個(gè)最優(yōu)問(wèn)題用普通的QP求解也是可以的脾歧,但是如果我們的數(shù)據(jù)維度很大甲捏,而經(jīng)過(guò)feature transform之后維度就好更大了,這樣就會(huì)導(dǎo)致VC dimension會(huì)增大鞭执,特別是后面用的多項(xiàng)式核司顿,RBF核等等芒粹。經(jīng)過(guò)對(duì)偶問(wèn)題KKT條件的變換之后,我們的目標(biāo)式子:
轉(zhuǎn)換成對(duì)偶問(wèn)題之后大溜,變量個(gè)數(shù)是N個(gè)是辕,約束條件也是N個(gè),于VC dimension就沒(méi)有了關(guān)系猎提,從某種意義上是簡(jiǎn)化了計(jì)算復(fù)雜度。其實(shí)計(jì)算復(fù)雜度還是沒(méi)有變旁蔼,只是把維度的計(jì)算提升到了變量之間點(diǎn)的內(nèi)積罷了锨苏。將原始SVM轉(zhuǎn)化為對(duì)偶問(wèn)題,本意是在非線性變化棺聊,進(jìn)行特征轉(zhuǎn)換后伞租,如果d’很大,為了簡(jiǎn)化計(jì)算限佩,消除d’的影響葵诈。進(jìn)一步引入Kernel SVM,根本上解決上述問(wèn)題祟同。注意了作喘,這里只是從某個(gè)角度看確實(shí)是消除了d維度的影響,實(shí)際上并沒(méi)有消失晕城,只是轉(zhuǎn)移到了計(jì)算內(nèi)積里面而已泞坦。
回到正題,我們的target function:
至于這個(gè)α怎么求砖顷,后面會(huì)用SMO算法求解贰锁。
到這里linear SVM就算結(jié)束了。
這就是分類函數(shù)滤蝠。
再停頓一下豌熄,什么是支持向量點(diǎn),為什么非支持向量的點(diǎn)α = 0物咳?這里僅僅思考linear SVM锣险,如果是soft margin又不一樣了。
⑸支持向量
如果是支持向量览闰,他的function margin是1囱持;而對(duì)于不少支持向量的點(diǎn),function margin > 1焕济,所以右邊是負(fù)數(shù)纷妆,為了滿足最大,所以α只能為0了晴弃,所以非支持向量的點(diǎn)α就是0掩幢。
⑤kernel Support Vector Machine
回到正題逊拍,剛剛只是講了linear SVM,是對(duì)于linear separable有效而已际邻,如果是linear inseparable呢芯丧?比如一個(gè)圓形,這樣就玩不了世曾。記得之前l(fā)inear regression和logistics regression講到過(guò)一個(gè)feature transform缨恒,如果是非線性的我們可以映射到其他維度進(jìn)行解決,比如最常見(jiàn)的polynomial transform轮听,但是這樣問(wèn)題來(lái)了骗露,剛剛不是才把維度d轉(zhuǎn)移到內(nèi)積嗎?(用dual problem的KKT condition)在來(lái)個(gè)feature transform那就是φ(x0)φ(x1)了血巍,維度就更大了萧锉。
比如polynomial:
二項(xiàng)式的是這樣的,注意到中間好像多了一個(gè)X1Xd述寡,這是為了后面計(jì)算方便而已柿隙。兩個(gè)做內(nèi)積:
可以看到,最后的轉(zhuǎn)換就只和原始空間有關(guān)系而已鲫凶,對(duì)于轉(zhuǎn)換只后的z空間的維度沒(méi)有關(guān)系禀崖。比如x空間是2維的,為了解決nonlinear problem螟炫,我們映射到了z空間帆焕,在z空間里面維度肯定會(huì)比在x空間的原始維度要大,而最后用z空間做內(nèi)積我們就只需要拿x空間的原始維度就好了不恭,因?yàn)槲覀兛梢韵葍?nèi)積再升維叶雹,而不是先升維再內(nèi)積。
這種就叫做核函數(shù)了换吧。
最后的分類函數(shù)用kernel function替代:
剛剛所講的就是核函數(shù)的一種——polynomial kernel function
加上幾個(gè)參數(shù)折晦,γ就是它的參數(shù)了,最后化簡(jiǎn)一下:
雖然都是二次轉(zhuǎn)換沾瓦,對(duì)應(yīng)到同一個(gè)z空間满着。但是,如果他們的γ系數(shù)不同贯莺,內(nèi)積就會(huì)不一樣风喇,那么就代表有不同的距離,最終可能會(huì)得到不同的SVM margin缕探。所以魂莫,系數(shù)不同,可能會(huì)得到不同的hperplane爹耗。
看一下γ系數(shù)對(duì)于hperplane的影響:
使用高階的polynomial kernel的話耙考,得到的Support Vector數(shù)量不會(huì)太多谜喊,分類面不會(huì)太復(fù)雜,防止過(guò)擬合倦始。同樣也避開了對(duì)升維之后維度的依賴斗遏。
接下來(lái)介紹另外一種kernel function——Gaussion kernel function
剛剛介紹的Q階多項(xiàng)式是有限維度的,如果是無(wú)限維度的能不能通過(guò)kernel來(lái)簡(jiǎn)化計(jì)算鞋邑?诵次?有一個(gè)無(wú)限維的kernel function——Gaussion kernel
這和我們之前見(jiàn)的有些不同,只是去掉了下面的方差而已枚碗,方差是定值沒(méi)有什么太大的影響逾一。逆推看看它的維度是多少:
推出來(lái)后面的維度是無(wú)限個(gè)(中間用的是Taylor展開,因?yàn)閑的特殊求導(dǎo)性質(zhì)可以簡(jiǎn)化)视译。
分類函數(shù)就出來(lái)了。
但是核函數(shù)的過(guò)擬合還是有一點(diǎn)嚴(yán)重的:
γ對(duì)于核函數(shù)的影響有點(diǎn)大归敬。如果取值很大的話最后就會(huì)形成一個(gè)一個(gè)的小圈圈把那些點(diǎn)圈起來(lái)酷含。
又得停頓一下,思考一下核函數(shù)的意義以及他們之間的對(duì)比:
⑹Comparison of Kernels
Polynomial Kernel的hyperplanes是由多項(xiàng)式曲線構(gòu)成汪茧。優(yōu)點(diǎn):階數(shù)可以靈活設(shè)置椅亚,更貼近實(shí)際分布;缺點(diǎn):當(dāng)Q很到的時(shí)候舱污,如果kernel里面的值算出來(lái)是<1呀舔,那就基本接近0了,大于1就會(huì)變得很大扩灯,增加計(jì)算復(fù)雜度媚赖。而且參數(shù)過(guò)多,難以選擇合適的值珠插。
Gaussan Kernel的優(yōu)點(diǎn)是邊界更加復(fù)雜多樣惧磺,能最準(zhǔn)確地區(qū)分?jǐn)?shù)據(jù)樣本,數(shù)值計(jì)算K值波動(dòng)較小捻撑,而且只有一個(gè)參數(shù)磨隘,容易選擇;缺點(diǎn)是由于特征轉(zhuǎn)換到無(wú)限維度中顾患,w沒(méi)有求解出來(lái)番捂,計(jì)算速度要低于linear kernel,而且可能會(huì)發(fā)生過(guò)擬合江解。mysterious——no w设预;slower;too powerful犁河。
之前說(shuō)過(guò)通過(guò)對(duì)偶問(wèn)題絮缅,我們的把數(shù)據(jù)維度轉(zhuǎn)移到了內(nèi)積上鲁沥,所以從某一方面來(lái)看我們確實(shí)是做到了簡(jiǎn)化計(jì)算復(fù)雜度,但是實(shí)際上內(nèi)積還是屬于一個(gè)很大的計(jì)算耕魄。所以核函數(shù)的功能之一画恰,就是簡(jiǎn)化計(jì)算旦袋,把升維和計(jì)算內(nèi)積合在了一起遭贸,減少計(jì)算復(fù)雜度。把計(jì)算步驟結(jié)合在了一起呀页,之前是先映射再計(jì)算內(nèi)積则奥,現(xiàn)在是一起做了考润。核函數(shù)的功能之二,就是可以很好的計(jì)算兩個(gè)樣本點(diǎn)的相似性读处,即內(nèi)積糊治。既然是代表相似性,我們可不可以使用其他的核函數(shù)呢罚舱?或者自己創(chuàng)建一個(gè)井辜,比如歐氏距離,余弦距離等等管闷?答案是不行粥脚。
先來(lái)看一下kernel的矩陣:
這有點(diǎn)像之前的協(xié)方差矩陣,只是沒(méi)有減去均值包个,所以對(duì)稱半正定是基本性質(zhì)了刷允。所以自然,我們自己創(chuàng)建或選擇的時(shí)候也要選擇①symmetric對(duì)稱②positive semi-definite 半正定碧囊。這也是核函數(shù)有效性的判斷树灶。
回到正題,剛剛只是講了一下對(duì)核函數(shù)的理解糯而。
⑥Soft-Margin Support Vector Machine
上面應(yīng)用到的Gaussion Kernel貌似還是會(huì)出現(xiàn)過(guò)擬合破托,而且還是蠻嚴(yán)重的,這說(shuō)明large margin已經(jīng)限制不了Gaussion kernel了歧蒋,我們需要找其他方法來(lái)處理這個(gè)問(wèn)題土砂。
之前有一個(gè)比較簡(jiǎn)單的算法——perceptron learning algorithm
這個(gè)算法對(duì)于nonlinear problem有一個(gè)很好的處理方式,我們不要求一定要分類正確谜洽,我們只要求找到一個(gè)錯(cuò)誤最少的分類就可以了萝映。所以他的function是這樣:
不正確的就加個(gè)1,最小為止阐虚。SVM也可以用這種方法來(lái)限制序臂。
加上一個(gè)條件,C參數(shù)就是對(duì)于這些錯(cuò)誤懲罰度是多少,條件也變了奥秆,正確的≥ 1逊彭,錯(cuò)誤的不管他。不管是小錯(cuò)還是大錯(cuò)构订。
整合一下:
這個(gè)式子其實(shí)沒(méi)有什么用侮叮,首先不是線性的,用不了二次規(guī)劃悼瘾,更不要說(shuō)對(duì)偶這些了囊榜,其次大錯(cuò)小錯(cuò)都是同等對(duì)待,connot distinguish small error and large error亥宿。
對(duì)于上述方案繼續(xù)修正:
我們采用一個(gè)ξ作為一個(gè)犯錯(cuò)程度卸勺,程度越大,懲罰越大烫扼。懲罰就是這個(gè)式子數(shù)值會(huì)變大曙求,然后SVM要花更多的力氣去處理。
接下來(lái)就是對(duì)偶問(wèn)題的推導(dǎo)映企,和之前的hard其實(shí)差不多的悟狱,lagrange 乘子法加對(duì)偶條件:
同樣,KKT條件:
C - α = β
所以有:0 < α < C
其他的基本一致:w求導(dǎo)為0:
b求導(dǎo):
接下來(lái)就是求b了:
求b的公式里面有一個(gè)矛盾的地方卑吭,就是我們要求b首先得要求出來(lái)ξ的值芽淡,但是ξ的值也只有b的公式可以求的處理马绝,所以這就有一個(gè)雞生蛋蛋生雞的問(wèn)題豆赏。所以我們口語(yǔ)去掉這個(gè)ξ。我們剛剛用到的是拉格朗日乘子法富稻,后面的β(-ξ)是一個(gè)仿射函數(shù)掷邦,仿射函數(shù)有β(-ξ) = 0的性質(zhì),所以把β代換一下就得到了上圖的公式椭赋。那么去掉ξ就是等于0了抚岗,那么就只有C-α不等于0才有啊,所以當(dāng)這個(gè)α ∈ (0 哪怔, C)的時(shí)候就有ξ為0宣蔚,而后面我們會(huì)講到當(dāng)α∈(0,C)的時(shí)候這個(gè)點(diǎn)其實(shí)是支持向量的點(diǎn)认境。這樣就可以求出了b胚委。
接下來(lái)看看C取值:
直接從我以前在CSDN里面寫過(guò)的拷貝過(guò)來(lái)了。
接下來(lái)看一下一個(gè)比較重要的東西:
physical significance of α
為什么βξ = 0叉信?原因和前一個(gè)公式是一樣的亩冬,因?yàn)橐∽畲笾担赃@里要等于0硼身,β ≥ 0硅急,而實(shí)際公式是negative ξ覆享,所以乘上去要是0才能有最大;第二营袜,如果不是等于0就不等于是原問(wèn)題的求解了撒顿,不等于0就無(wú)端端多了一個(gè)inequality,和原問(wèn)題不對(duì)等了连茧。之后才能進(jìn)行daul problem的轉(zhuǎn)換核蘸。
我們主要是從上面這兩個(gè)公式來(lái)看當(dāng)α取值不同的時(shí)候?qū)?yīng)的物理意義。
當(dāng)α = 0啸驯,得ξ = 0客扎,這個(gè)點(diǎn)就是沒(méi)有放錯(cuò)的點(diǎn),因?yàn)棣?= 0罚斗,不需要容忍徙鱼。而α = 0,所以不是支持向量機(jī)的點(diǎn)针姿,所以代表的就是在bound外并且分類正確的點(diǎn)袱吆。
當(dāng)α∈(0,C)距淫,還是得到ξ = 0绞绒,這時(shí)候就不一樣了,還沒(méi)有錯(cuò)誤的點(diǎn)榕暇,但是第一條式子括號(hào)里面等于0了蓬衡,意味著就是在bound上的點(diǎn),那么就是支持向量點(diǎn)了彤枢。
當(dāng)α = C狰晚,不能確定ξ是不是0了,
缴啡,表示就是錯(cuò)了多少壁晒,這種有兩種情況,一種是分類正確了业栅,但是距離太近秒咐;或者是分類錯(cuò)了。
當(dāng)α > C碘裕,不存在的携取,上面都限制了。
理一下整個(gè)思路娘汞。
①找到最好的hperplane歹茶,最寬的那個(gè)。
②得到target function
③發(fā)現(xiàn)feature transform之后維度對(duì)于計(jì)算機(jī)復(fù)雜度有很大影響,用dual problem轉(zhuǎn)移到內(nèi)積處理
④轉(zhuǎn)移之后發(fā)現(xiàn)還是復(fù)雜度在的惊豺,引出了kernel function
⑤發(fā)現(xiàn)kernel function還是有overfitting的情況燎孟,于是又引入了soft margin
在講SMO算法之前,先講一下對(duì)于error function的理解:
⑺對(duì)于SVM error function的理解
我們把SVM換一種形式尸昧。對(duì)于ξ揩页,其實(shí)他是每一個(gè)點(diǎn)距離邊界有多遠(yuǎn),一種是violating margin烹俗,即不滿足y(wTz + b) ≥ 1爆侣,那么ξ就可以表示成:1 - y(wTz + b) > 0。第二種情況就是not violating margin幢妄,即這個(gè)點(diǎn)在邊界之外兔仰,就是滿足上述公式了,這個(gè)時(shí)候ξ就是0蕉鸳,我們整合一下:
ξ = max ( 1 - y(wTz + b) , 0 )乎赴,代換進(jìn)原來(lái)的支持向量機(jī)公式:
這個(gè)就是支持向量機(jī)的error function,先預(yù)判了Ein = 0潮尝,也就是全對(duì)的情況榕吼,前面有說(shuō)到。
這個(gè)function有點(diǎn)像我們之前所學(xué)的L2 lost function:
這和logistics regression的L2范式的cost function很相似勉失。
其實(shí)就差不多是一樣的羹蚣,沒(méi)有什么差別,但是既然是相同的為什么不用這種方法呢乱凿??jī)蓚€(gè)原因顽素,一個(gè)是這種無(wú)條件的最優(yōu)化問(wèn)題無(wú)法通過(guò)QP解決,即對(duì)偶推導(dǎo)和kernel都無(wú)法使用告匠;另一個(gè)是這種形式中包含的max()項(xiàng)可能造成函數(shù)并不是處處可導(dǎo)戈抄,這種情況難以用微分方法解決离唬。
對(duì)比發(fā)現(xiàn)后专,L2 regularization和soft margin SVM形式是一樣的,兩個(gè)式子λ和C是互相對(duì)應(yīng)的输莺。soft marginSVM里面的large margin就對(duì)應(yīng)著L2 regularization里面的short w戚哎,都是讓hypothesis set可以簡(jiǎn)單點(diǎn)。λ和C也是互相對(duì)應(yīng)嫂用,λ大型凳,w就小,正則化的程度就越大嘱函;C小甘畅,Ein就大,響應(yīng)這個(gè)margin也會(huì)打,所以增大C和減小λ是一個(gè)意思疏唾,所以large margin等同于regularization蓄氧,都是防止過(guò)擬合作用的。
如果是按照我們之前的err0/1槐脏,正確為1喉童,錯(cuò)誤就是0,那么有:
可以看到SVM他是大于err0/1的顿天,根據(jù)VC bound理論是可以用來(lái)代替err0/1分類的堂氯。
后面再加上logic function的cost function:
而這個(gè)幾乎就是和L2-regularized logistic regression一樣的。Logistic Regression和Soft-Margin SVM都是在最佳化err0/1的上界而已牌废⊙拾祝可以看出,求解regularized logistic regression的問(wèn)題等同于求解soft-margin SVM的問(wèn)題鸟缕。
⑻損失函數(shù)
常見(jiàn)的損失函數(shù):
err0/1:
此時(shí)soft margin就是這樣了局扶,大于0就是1小于就是0。
不敏感損失函數(shù) —— hinge lost function
還有對(duì)數(shù)損失函數(shù)交叉熵等等叁扫。logistics用的是交叉熵三妈,SVM就是用的hinge lost function。支持向量機(jī)就是一個(gè)結(jié)構(gòu)風(fēng)險(xiǎn)最小化的近似實(shí)現(xiàn)莫绣,結(jié)構(gòu)風(fēng)險(xiǎn)相當(dāng)于期望風(fēng)險(xiǎn)(Eout)的一個(gè)上界畴蒲,它是經(jīng)驗(yàn)風(fēng)險(xiǎn)(Ein)和置信區(qū)間(Ω模型復(fù)雜度)的和,經(jīng)驗(yàn)風(fēng)險(xiǎn)依賴于決策函數(shù)f的選取对室,但是置信區(qū)間是模燥,F(xiàn)的VC維的增函數(shù),兩者是矛盾的掩宜。矛盾體現(xiàn)在:當(dāng)VC維數(shù)變大的時(shí)候可以選到更好的f使得經(jīng)驗(yàn)風(fēng)險(xiǎn)比較小蔫骂,但是此時(shí)的置信區(qū)間比較大。這就是對(duì)應(yīng)了VC bound理論牺汤。還好去聽(tīng)了臺(tái)灣大學(xué)林軒宇老師課程辽旋,對(duì)這些機(jī)器學(xué)習(xí)理論基礎(chǔ)有了解。
回到正題檐迟,開始SMO算法补胚。
⑦SMO算法
target function:
剛剛我們知道怎么求w,b,但是那是在知道了α的前提下追迟,現(xiàn)在就來(lái)求α溶其。
基本思路:
選擇兩個(gè)變量,固定其他變量敦间,針對(duì)兩個(gè)變量構(gòu)建一個(gè)二次規(guī)劃問(wèn)題瓶逃。每次針對(duì)兩個(gè)變量來(lái)求解目標(biāo)函數(shù)的最小值束铭,求解完后,繼續(xù)尋找新的變量求目標(biāo)函數(shù)厢绝,在每次尋找新α的過(guò)程中纯露,目標(biāo)函數(shù)將進(jìn)一步得到優(yōu)化,直到所有的αi更新完了代芜。而對(duì)于α的選取埠褪,一個(gè)是違反KKT條件最嚴(yán)重的那一個(gè),另一個(gè)由約束條件自動(dòng)確定挤庇。
首先钞速,假設(shè)我們選取了兩個(gè)變量α1,α2嫡秕,固定其他變量之后:
所以只要求出α2渴语,α1就知道了。
原目標(biāo)函數(shù)化簡(jiǎn)之后:
K11指的就是x1和自己本身做核函數(shù)昆咽。由于我們已經(jīng)固定了除了α1和α2驾凶,所以自然其他的常量我們可以去掉了,不如優(yōu)化w+1掷酗,和優(yōu)化w是一樣的调违,去掉固定常數(shù)項(xiàng)就留下了上圖的公式。
別忘了條件泻轰,條件是后面求解的關(guān)鍵技肩。
首先我們要得到α1,α2的范圍浮声。
由著兩個(gè)約束條件限制虚婿。
所以有:
所以當(dāng)我們更新了α之后,我們還要根據(jù)范圍剪輯α才可以泳挥。
我們假設(shè):
剪輯范圍:
再假設(shè)一個(gè)定值然痊,也就是i = 3開始求和的:
目標(biāo)式子:
用上面的vi代換之后:
求α2的話自然是求導(dǎo)了:
為0得到:
這里的化簡(jiǎn)有點(diǎn)麻煩:
手動(dòng)證明一下。
用假設(shè)替換一下上面的式子:
就可以了屉符。
SMO算法有兩個(gè)要點(diǎn):①α1的選擇剧浸,違反KKT最嚴(yán)重的條件②α2的選擇策略
很重要的問(wèn)題,變量要怎么選擇筑煮,后面會(huì)有例子證明辛蚊。
⑼變量的選擇方式
SMO稱選擇第1個(gè)變量的過(guò)程為外層循環(huán)粤蝎。外層循環(huán)在訓(xùn)練樣本中選取違反KKT條件最嚴(yán)重的樣本點(diǎn)真仲,Violation of the most serious sample of KKT conditions。我第一次看這東西是懵逼的初澎。但是仔細(xì)想一下秸应,就是檢測(cè)哪一個(gè)樣本是沒(méi)有滿足KKT的條件:
首先遍歷所有0 < α < C的樣本點(diǎn)虑凛,看看是不是滿足的,如果沒(méi)有載變量所有的软啼。檢測(cè)是否滿足KKT桑谍。所以在SMO迭代的兩個(gè)步驟中,只要α中有一個(gè)違背了KKT條件祸挪,這一輪迭代完成后锣披,目標(biāo)函數(shù)的值必然會(huì)增大。Generally speaking贿条,KKT條件違背的程度越大雹仿,迭代后的優(yōu)化效果越明顯,增幅越大整以。
α1選完了自然就是選擇第二個(gè)α了胧辽,第二個(gè)變量的選擇叫做內(nèi)存循環(huán),我們這里先用普通隨機(jī)選擇公黑,看看效果如何邑商。
⑧算法實(shí)現(xiàn)——version 1
首先是導(dǎo)入各種各樣的包和一個(gè)工具了:
import numpy as np
import matplotlib.pyplot as plt
import random
import seaborn as sea
import pandas as pd
def get_positive_and_negative():
dataSet = pd.read_csv('Datas/LogiReg_data.txt', names=['V1', 'V2', 'Class'])
dataSet.Class[dataSet.Class == 0] = -1
dataSet = dataSet[60 : 80]
positive = dataSet[dataSet['Class'] == 1]
negative = dataSet[dataSet['Class'] == -1]
return positive , negative , dataSet
def show_picture(positive , negative):
columns = ['V1', 'V2']
fig, ax = plt.subplots(figsize=(10, 5))
ax.scatter(positive[columns[0]], positive[columns[1]], s=30, c="b", marker="o", label="class 1")
ax.scatter(negative[columns[0]], negative[columns[1]], s=30, c="r", marker="x", label="class -1")
ax.legend()
ax.set_xlabel('V1')
ax.set_ylabel('V3')
plt.show()
def load_data_set():
_ , _ , file = get_positive_and_negative()
orig_data = file.as_matrix()
cols = orig_data.shape[1]
data_mat = orig_data[ : , 0 : cols-1]
label_mat = orig_data[ : , cols-1 : cols]
return data_mat , label_mat
positive , negative , data = get_positive_and_negative()
show_picture(positive , negative)
print(data)
第一個(gè)是得到正負(fù)樣本,然后顯示凡蚜,最后一個(gè)是加載數(shù)據(jù)人断,數(shù)據(jù)隨便找一個(gè)就好了。
positive , negative , data = get_positive_and_negative()
show_picture(positive , negative)
最后調(diào)用一些看看這些點(diǎn)是什么:
還有一些是對(duì)α的限制和一下工具函數(shù):
'''''
Generate a random number
'''
def select_jrand(i , m):
j = i
while(j == i):
j = int(random.uniform(0 , m))
return j
pass
'''''
restraint the α
'''
def clip_alpha(aj , H , L):
if aj > H:
aj = H
elif aj < L:
aj = L
return aj
pass
接下來(lái)就是實(shí)現(xiàn)支持向量機(jī)了:
def SVM(data_mat , class_label , C , tolar , max_iter):
data_mat = np.mat(data_mat)
label_mat = np.mat(class_label)
b = 0
m , n = np.shape(data_mat)
alphas = np.zeros((m , 1))
iter = 0
while iter < max_iter:
#作為迭代變化量
alpha_pairs_changed = 0
#作為第一個(gè)a
for i in range(m):
WT_i = np.dot(np.multiply(alphas , label_mat).T , data_mat)
f_xi = float(np.dot(WT_i , data_mat[i , :].T)) + b
Ei = f_xi - float(label_mat[i])
if ((label_mat[i]*Ei < -tolar) and (alphas[i] < C)) or ((label_mat[i]*Ei > tolar) and (alphas[i] > 0)):
j = Tools.select_jrand(i , m)
WT_j = np.dot(np.multiply(alphas , label_mat).T , data_mat)
f_xj = float(np.dot(WT_j , data_mat[j , :].T)) + b
Ej = f_xj - float(label_mat[j])
alpha_iold = alphas[i].copy()
alpha_jold = alphas[j].copy()
if (label_mat[i] != label_mat[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 H == L:
continue
eta = 2.0 * data_mat[i, :] * data_mat[j, :].T - data_mat[i, :] * data_mat[i, :].T - data_mat[j, :] * data_mat[j, :].T
if eta >= 0: continue
alphas[j] = (alphas[j] - label_mat[j]*(Ei - Ej))/eta
alphas[j] = Tools.clip_alpha(alphas[j], H, L)
if (abs(alphas[j] - alpha_jold) < 0.00001):
continue
alphas[i] = alphas[i] + label_mat[j]*label_mat[i]*(alpha_jold - alphas[j])
b1 = b - Ei + label_mat[i]*(alpha_iold - alphas[i])*np.dot(data_mat[i,:], data_mat[i,:].T) +\
label_mat[j]*(alpha_jold - alphas[j])*np.dot(data_mat[i,:], data_mat[j,:].T)
b2 = b - Ej + label_mat[i]*(alpha_iold - alphas[i])*np.dot(data_mat[i,:], data_mat[j,:].T) +\
label_mat[j]*(alpha_jold - alphas[j])*np.dot(data_mat[j,:], data_mat[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
print(b)
alpha_pairs_changed += 1
pass
if alpha_pairs_changed == 0:
iter += 1
else:
iter = 0
support_x = []
support_y = []
class1_x = []
class1_y = []
class01_x = []
class01_y = []
for i in range(m):
if alphas[i] > 0.0:
support_x.append(data_mat[i, 0])
support_y.append(data_mat[i, 1])
for i in range(m):
if label_mat[i] == 1:
class1_x.append(data_mat[i, 0])
class1_y.append(data_mat[i, 1])
else:
class01_x.append(data_mat[i, 0])
class01_y.append(data_mat[i, 1])
w_best = np.dot(np.multiply(alphas, label_mat).T, data_mat)
fig, ax = plt.subplots(figsize=(10, 5))
ax.scatter(support_x, support_y, s=100, c="y", marker="v", label="support_v")
ax.scatter(class1_x, class1_y, s=30, c="b", marker="o", label="class 1")
ax.scatter(class01_x, class01_y, s=30, c="r", marker="x", label="class -1")
lin_x = np.linspace(0, 100)
lin_y = (-float(b) - w_best[0, 0] * lin_x) / w_best[0, 1]
plt.plot(lin_x, lin_y, color="black")
ax.legend()
ax.set_xlabel("factor1")
ax.set_ylabel("factor2")
plt.show()
return b , alphas
datamat , labelmat = dataSet.load_data_set()
b, alphas = SVM(datamat , labelmat , 0.6 , 0.001 , 10)
print(b , alphas)
首先傳入的后面幾個(gè)參數(shù)分別是懲罰力度朝蜘,容忍度含鳞。比較重要的應(yīng)該是這一句:
if ((label_mat[i]*Ei < -tolar) and (alphas[i] < C)) or ((label_mat[i]*Ei > tolar) and (alphas[i] > 0)):
這句話翻譯過(guò)去就是yg(x) < 1 - ξ或者是y(g(x)) > 1+ξ。如果是小于芹务,則這個(gè)點(diǎn)是離hperplane比較近蝉绷,這時(shí)候這個(gè)點(diǎn)應(yīng)該是等于C才對(duì)的;如果是大于了枣抱,也就是遠(yuǎn)大于邊界了熔吗,那就是離邊界很遠(yuǎn)了,但是α又大于0佳晶,離邊界遠(yuǎn)意味著不是支持向量桅狠,所以α應(yīng)該是0,所以可以改變轿秧。
后面的那些就是依據(jù)公式來(lái)的:
每一條都是對(duì)應(yīng)公式寫的中跌。
最后就是打印了。
效果:
可以看到是極度不穩(wěn)定菇篡。這是幾個(gè)月前我實(shí)現(xiàn)的违柏,后來(lái)現(xiàn)在我又重新實(shí)現(xiàn)了一個(gè),用了一些改進(jìn)方法敛惊。為什么會(huì)不穩(wěn)定,我總結(jié)了幾個(gè)原因:
①?zèng)]有緩存凸克,更新慢,迭代次數(shù)不夠
②對(duì)于α2的選取沒(méi)有很好的采取策略
③對(duì)于n闷沥,也就是更新公式:
我沒(méi)有判斷是不是大于0的萎战。n是什么東西呢?
他要是小于0意味著這個(gè)kernel matrix就不是半正定的了舆逃,K11 + K22 < 2K12蚂维;另外,這個(gè)n其實(shí)是:
的二階導(dǎo)數(shù)路狮,小于0就不是凸函數(shù)了鸟雏,哪來(lái)的凸優(yōu)化。所以應(yīng)該是更新的時(shí)候遇到這些情況導(dǎo)致不穩(wěn)定的览祖。
基于上面的缺點(diǎn)更換策略孝鹊。
⑨算法實(shí)現(xiàn)——version 2
首先要改變的是加上一個(gè)緩存,用來(lái)保存Ei的值展蒂,使得計(jì)算更塊又活。其次就是α2的選擇策略,在優(yōu)化過(guò)程中锰悼,會(huì)通過(guò)最大化步長(zhǎng)的方式來(lái)獲得第二個(gè)alpha值柳骄。第二步優(yōu)化為,數(shù)據(jù)集全程掃描策略與在非邊界alpha對(duì)中進(jìn)行更新策略交替進(jìn)行箕般。對(duì)于n耐薯,會(huì)進(jìn)行判斷是不是大于0,在這里是用-號(hào)的丝里,所以n與我們表達(dá)式上的是想反方向曲初,所以是大于0。
首先還是工具:
'''
load data and define some tool function
'''
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
import pandas as pd
import random
def loadDataSet(filename):
'''
:param filename:
:return dataset and label:
'''
dataset = []
label = []
fr = open(filename)
for line in fr.readlines():
lineArr = line.strip().split('\t')
dataset.append( [np.float32(lineArr[0]) , np.float32(lineArr[1])] )
label.append(np.float32(lineArr[2]))
return dataset , label
pass
'''
select alpha2 randomly
'''
def selectAlphaTwo(i , m):
'''
:param i:
:param m:
:return:
'''
j = i
while(j == i):
j = int(random.uniform(0 , m))
return j
def rangeSelectionForAlpha(aj , H , L):
if aj > H:
aj = H
if L > aj:
aj = L
return aj
pass
'''
calculate Ei
'''
def calculateEi(os , k):
fxk = float(np.multiply(os.alphas, os.labels).T * (os.x * os.x[k, :].T)) + os.b
Ek = fxk - float(os.labels[k])
return Ek
'''
put the Ei into the cache when calculate Ei
'''
def selectj(i , os , Ei):
maxk = -1
maxDeltaE = 0
Ej = 0
os.eCache[i] = [1 , Ei]
validEachlist = np.nonzero(os.eCache[: , 0].A)[0]
if (len(validEachlist) > 1):
for k in validEachlist:
if k == i:
continue
Ek = calculateEi(os , k)
deltaE = np.abs(Ei - Ek)
if deltaE > maxDeltaE:
maxk = k
maxDeltaE = deltaE
Ej = Ek
return maxk , Ej
pass
else:
j = selectAlphaTwo(i , os.m)
Ej = calculateEi(os , j)
return j , Ej
pass
'''
draw picture
'''
def drawDataset(data , label , x = None , y = None , line = True , alphas = None , kernel = True):
index_one = []
index_negative_one = []
for i in range(100):
if label[i] == 1:
index_one.append(data[i])
else:
index_negative_one.append(data[i])
index_one = np.matrix(index_one)
index_negative_one = np.matrix(index_negative_one)
plt.scatter(index_one[ : , 0].tolist() , index_one[: , 1].tolist() , c = 'r' , marker='<' , label = 'class equal one')
plt.scatter(index_negative_one[: , 0].tolist() , index_negative_one[: , 1].tolist() , c = 'b' , marker='x' , label = 'class equal negative one')
if line == True:
plt.plot(x , y)
pass
'''
draw the support vector,the point which the α not equal zero
'''
if line == True or kernel == True:
a1 = []
for i in range(len(alphas)):
a = alphas[i]
if a != 0:
a1.append(data[i])
a1 = np.matrix(a1)
print('The number of the support vector : ' , len(a1))
plt.scatter(a1[: , 0].tolist(),a1[: , 1].tolist(), s=150, c='none', alpha=0.7,
linewidth=1.5, edgecolor='#AB3319' , label = 'support vector')
plt.legend()
plt.xlabel('X axis')
plt.ylabel('Y axis')
plt.show()
def updateEk(os,k):
Ek = calculateEi(os,k)
os.eCache[k]=[1,Ek]
if __name__ == '__main__':
data , label = loadDataSet('../Data/testSetRBF.txt')
drawDataset(data , label , line=False ,kernel=False)
SMO算法唯一的一個(gè)類:
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
import pandas as pd
import random
import KernelTransform
class optStruct:
def __init__(self , dataMat , labels , C , toler):
self.x = dataMat
self.labels = labels
self.C = C
self.toler = toler
self.m = np.shape(dataMat)[0]
self.alphas = np.mat(np.zeros((self.m , 1)))
self.b = 0
self.eCache = np.mat(np.zeros((self.m , 2)))
self.K = np.mat(np.zeros((self.m , self.m)))
for i in range(self.m):
self.K[: , i] = KernelTransform.kernelTrans(self.x , self.x[i , :] , kTup=('rbf' , 1.2))
pass
if __name__ == '__main__':
os = optStruct([1,2] , [3,4] , 1,1)
a = os.alphas.tolist()[0][0] - os.alphas.tolist()[1][0]
print(max(1.0 , a))
需要解釋的應(yīng)該只有selectj()了杯聚,這個(gè)是通過(guò)計(jì)算最大不長(zhǎng)來(lái)選擇α2的臼婆。
首先我們假設(shè)最大不長(zhǎng)是-1,因?yàn)橄鄿p有絕對(duì)值不可能是negative幌绍;os.eCache是我們的緩存的Ei颁褂,先把Ei存進(jìn)去,1,表示這個(gè)數(shù)字不是0傀广,這一步就是得到這個(gè)緩存里面所有有效(不為0)的Ei颁独。判斷得到的列表是不是有東西,沒(méi)有就隨機(jī)選擇了伪冰。還是再解釋一下為什么要這個(gè)建立表格吧誓酒!
我們?cè)谶x擇第一個(gè)α1的時(shí)候,選擇的是在邊界外的點(diǎn)糜值,也就是非邊界的點(diǎn)丰捷。 優(yōu)先選擇遍歷非邊界數(shù)據(jù)樣本坯墨,因?yàn)榉沁吔鐢?shù)據(jù)樣本更有可能需要調(diào)整寂汇,邊界數(shù)據(jù)樣本常常不能得到進(jìn)一步調(diào)整而留在邊界上病往。由于大部分?jǐn)?shù)據(jù)樣本都很明顯不可能是支持向量,因此對(duì)應(yīng)的α乘子一旦取得零值就無(wú)需再調(diào)整骄瓣。遍歷非邊界數(shù)據(jù)樣本并選出他們當(dāng)中違反KKT 條件為止停巷。當(dāng)某一次遍歷發(fā)現(xiàn)沒(méi)有非邊界數(shù)據(jù)樣本得到調(diào)整時(shí),遍歷所有數(shù)據(jù)樣本榕栏,以檢驗(yàn)是否整個(gè)集合都滿足KKT條件畔勤。如果整個(gè)集合的檢驗(yàn)中又有數(shù)據(jù)樣本被進(jìn)一步進(jìn)化,則有必要再遍歷非邊界數(shù)據(jù)樣本扒磁。這樣庆揪,不停地在遍歷所有數(shù)據(jù)樣本和遍歷非邊界數(shù)據(jù)樣本之間切換,直到整個(gè)樣本集合都滿足KKT條件為止妨托。以上用KKT條件對(duì)數(shù)據(jù)樣本所做的檢驗(yàn)都以達(dá)到一定精度ε就可以停止為條件缸榛。如果要求十分精確的輸出算法,則往往不能很快收斂兰伤。所以在echa中緩存的第一次選出的α内颗,因?yàn)槲覀冞x出來(lái)的就是非邊界上的點(diǎn),α2選擇的時(shí)候繼續(xù)在上面遍歷敦腔,雖然緩存是存了Ei均澳,但是這個(gè)Ei不能直接用,因?yàn)槟莻€(gè)是舊的值符衔。所以α的迭代策略就是非邊界和全局選取兩種交替進(jìn)行了找前。
之后就是正式的算法了:
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
import pandas as pd
import random
import Tool
import smo_class
import KernelTransform
def innerL(i ,os):
Ei = Tool.calculateEi(os , i)
if ((os.labels[i]*Ei < -os.toler) and
(os.alphas[i] < os.C)) or ((os.labels[i]*Ei > os.toler) and
(os.alphas[i] > 0)):
j , Ej = Tool.selectj(i , os , Ei)
alphaIold = os.alphas[i].copy()
alphaJold = os.alphas[j].copy()
if (os.labels[i] != os.labels[j]):
L = max(0 , os.alphas[j] - os.alphas[i])
H = min(os.C , os.C + np.array(os.alphas)[j] - np.array(os.alphas)[i])
else:
L = max(0 , os.alphas[j] + os.alphas[i] - os.C)
H = min(os.C , np.array(os.alphas)[j] + np.array(os.alphas)[i])
if 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('η> 0,the kernel matrix is not semi-positive definite')
return 0
os.alphas[j] -= os.labels[j]*(Ei - Ej)/eta
os.alphas[j] = Tool.rangeSelectionForAlpha(os.alphas[j] , H , L)
Tool.updateEk(os , j)
if (abs(os.alphas[j] - alphaJold) < 0.00001):
print("j not moving enough")
return 0
os.alphas[i] += os.labels[j] * os.labels[i] * (alphaJold - os.alphas[j])
Tool.updateEk(os , i)
b1 = os.b - Ei - os.labels[i] * (os.alphas[i] - alphaIold) * \
os.x[i, :] * os.x[i, :].T - os.labels[j] * \
(os.alphas[j] - alphaJold) * os.x[i, :] * os.x[j, :].T
b2 = os.b - Ej - os.labels[i] * (os.alphas[i] - alphaIold) * \
os.x[i, :] * os.x[j, :].T - os.labels[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
def smo(data,labels,C = 0.6,toler = 0.001,maxIter = 40 , kernel = True):
oS = smo_class.optStruct(np.mat(data),np.mat(labels).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):
if kernel == True:
alphaPairsChanged += KernelTransform.innerL(i,oS)
else:
alphaPairsChanged += innerL(i, oS)
print("fullSet,iter: %d i: %d,pairs changed %d" %\
(iter,i,alphaPairsChanged))
iter +=1
else:
# 兩個(gè)元素乘積非零判族,每?jī)蓚€(gè)元素做乘法[0,1,1,0,0]*[1,1,0,1,0]=[0,1,0,0,0]
nonBoundIs = np.nonzero((oS.alphas.A > 0)*(oS.alphas.A < C))[0]
for i in nonBoundIs:
alphaPairsChanged += innerL(i,oS)
print("nou-bound,iter: %d i:%d,pairs changed %d" % (iter,i,alphaPairsChanged))
iter +=1
# entireSet 控制交替的策略選擇
if entireSet:
entireSet = False
# 必須有alpha對(duì)進(jìn)行更新
elif(alphaPairsChanged == 0):
entireSet = True
print("iteration number:%d" % iter)
return oS.b,oS.alphas
entireSet就是交換策略的標(biāo)志纸厉。貌似沒(méi)有什么好說(shuō)的。
之后就是執(zhí)行函數(shù)這些了:
import Tool
import SMO
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
import KernelTransform
'''
calculate w and draw the picture,
the variable which the α not equal zero ,
we call support vector
'''
def calculateW(alphas , data , labels):
x = np.mat(data)
label = np.mat(labels).transpose()
m , n = np.shape(x)
w = np.zeros((n , 1))
for i in range(m):
w += np.multiply(alphas[i] * label[i] , x[i , :].T)
return w
pass
if __name__ == '__main__':
data, label = Tool.loadDataSet('../Data/testSet.txt')
b,alphas = SMO.smo(data , label , kernel=False)
w = calculateW(alphas , data , label)
x = np.arange(0 , 11)
print(w)
y = (-b - w[0]*x)/w[1]
Tool.drawDataset(data , label , x , y.tolist()[0] , line=True , alphas=alphas)
data, label = Tool.loadDataSet('../Data/testSetRBF.txt')
b, alphas = SMO.smo(data, label,kernel=True ,maxIter=100)
svInd = np.nonzero(alphas.A > 0)[0]
Tool.drawDataset(data, label, line=False, alphas=alphas)
有一個(gè)是kernel function的五嫂,先不用管颗品。
效果:
圈起來(lái)的是支持向量點(diǎn),好很多了沃缘。
⑩算法實(shí)現(xiàn)——version 3
kernel function加上躯枢,先看看原來(lái)的數(shù)據(jù):
需要改的其實(shí)就是內(nèi)積就可以了,到處看看哪里有內(nèi)積就改改他槐臀,修改過(guò)后的innel和smo:
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
import pandas as pd
import Tool
def kernelTrans(X,A,kTup):
m,n = np.shape(X)
K = np.mat(np.zeros((m,1)))
if kTup[0]=='lin':
K = X*A.T
elif kTup[0] =='rbf':
for j in range(m):
deltRow = X[j,:]-A
K[j] = deltRow*deltRow.T
K = np.exp(K/(-1*kTup[1]**2))
return K
'''
update the innel function
'''
def innerL(i ,os):
Ei = calculateEi(os , i)
if ((os.labels[i]*Ei < -os.toler) and
(os.alphas[i] < os.C)) or ((os.labels[i]*Ei > os.toler) and
(os.alphas[i] > 0)):
j , Ej = Tool.selectj(i , os , Ei)
alphaIold = os.alphas[i].copy()
alphaJold = os.alphas[j].copy()
if (os.labels[i] != os.labels[j]):
L = max(0 , os.alphas[j] - os.alphas[i])
H = min(os.C , os.C + np.array(os.alphas)[j] - np.array(os.alphas)[i])
else:
L = max(0 , os.alphas[j] + os.alphas[i] - os.C)
H = min(os.C , np.array(os.alphas)[j] + np.array(os.alphas)[i])
if L == H:
return 0
eta = 2.0 * os.K[i, j] - os.K[i, i] - os.K[j, j]
if eta >= 0:
print('η> 0锄蹂,the kernel matrix is not semi-positive definite')
return 0
os.alphas[j] -= os.labels[j]*(Ei - Ej)/eta
os.alphas[j] = Tool.rangeSelectionForAlpha(os.alphas[j] , H , L)
updateEk(os , j)
if (abs(os.alphas[j] - alphaJold) < 0.00001):
print("j not moving enough")
return 0
os.alphas[i] += os.labels[j] * os.labels[i] * (alphaJold - os.alphas[j])
updateEk(os , i)
b1 = os.b - Ei - os.labels[i] * (os.alphas[i] - alphaIold) * \
os.K[i , i] - os.labels[j] * \
(os.alphas[j] - alphaJold) * os.K[i , j]
b2 = os.b - Ej - os.labels[i] * (os.alphas[i] - alphaIold) * \
os.K[i , j] - os.labels[j] * \
(os.alphas[j] - alphaJold) * os.K[j , j]
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
'''
updata the Ei
'''
def calculateEi(os , k):
fxk = float(np.multiply(os.alphas, os.labels).T * os.K[:, k] + os.b)
Ek = fxk - float(os.labels[k])
return Ek
def updateEk(os,k):
Ek = calculateEi(os,k)
os.eCache[k]=[1,Ek]
剛剛那個(gè)執(zhí)行函數(shù)其實(shí)已經(jīng)包括了kernel的,所以直接就可以看到效果了:
用的是Gaussion kernel水慨,不知道怎么做擬合得糜,就把支持向量點(diǎn)圈出來(lái)就好了敬扛。
最后附上所有代碼GitHub:
https://github.com/GreenArrow2017/MachineLearning