前言
支持向量機(SVM)是一種很重要的機器學習分類算法,本身是一種線性分類算法,但是由於加入了核技巧,使得SVM也可以進行非線性數據的分類;SVM本來是一種二分類分類器,但是可以擴展到多分類,本篇不會進行對其推導一步一步羅列公式,因為當你真正照着書籍進行推導后你就會發現他其實沒那么難,主要是動手。本篇主要集中與實現,即使用著名的序列最小最優化(SMO)算法進行求解,本篇實現的代碼主要參考了Platt J. Sequential minimal optimization: A fast algorithm for training support vector machines[J]. 1998.這是SMO的論文,論文中詳細解釋了如何使用SMO算法,還有偽代碼,我的C++程序就是根據偽代碼實現的(沒錯,SMO算法我推不出來)。代碼地址.
對於SVM的一些理解
首先,《統計學習方法》中對於SVM的講解已經很好了,請務必跟着一步一步推導,這樣你就會發現整個SVM的推導過程無非就是以下幾步:
- 將分類器建模成n維空間中的超平面,但是這個超平面有個很重要的選取原則,那就是讓所有樣本點到超平面的距離都盡量大,也就是讓距離超平面最近的點到超平面的距離達到最大,於是得出了約束最大化問題。
- 將問題簡化到只和決定超平面的參數w有關,使用熟悉的拉格朗日數乘法將約束最優化問題變成一個式子,然后轉化為對偶問題。
- 求解對偶問題的最優解a,然后根據原問題和對偶問題的關系由a求出w,此時就得到SVM的參數了。
1,2步都是需要推導的,唯獨涉及實現的地方是第3步,我們實現的重點變成了如何快速的得到最優解a(a可是有N個分量的,N為訓練樣本數)。於是SMO算法就出現了。
再者關於核函數,之前看博客,有人理解成核函數是一種映射,即將非線性問題映射為線性問題,有人評論說這是不嚴謹的,核函數不是一種映射,當時很迷茫,但是現在看來,核函數是一種技巧,他讓我們可以使用目前空間的內積來代表某個目標空間的內積。
具體的關於SVM的推導和核技巧的理解查看書籍就可以,自己推導一遍就都明白了。
序列最小最優化(SMO)算法
《統計學習方法》上對於SMO算法的講解很清楚(跟原論文思路一樣),就是將待優化的n個參數選兩個作為優化對象,其他的固定,然后轉化為二元最優化問題。道理我都懂,但是實現的時候遇到了很多麻煩,關鍵在於啟發式的變量選取,於是便找到原論文,沒想到Platt大神已經把偽代碼寫好了,於是,我就把他的偽代碼用c++實現了一遍。這部分我對一些推導還是不明白,在這里就不獻丑了,看代碼吧,首先給出論文中的偽代碼截圖。
代碼結構
c++實現
這部分主要列出偽代碼的takestep部分各變量的更新代碼。
int SVM::SMOTakeStep(int& i1, int& i2) {
//變量名跟偽代碼中基本一樣,這里用i1, i2代表數據點對應的的拉格朗日乘子,E每個樣本點的預測輸出與真值的誤差
//存儲在vector中,避免重復計算
...
...
...
double a1 = alpha[i1] + s * (alpha[i2] - a2);
double b1;
//please notice that the update equation is from <<統計學習方法>>p130, not the equation in paper
b1= -E[i1] - y1 * (a1 - alpha[i1]) * kernel(trainDataF[i1], trainDataF[i1]) -
y2 * (a2 - alpha[i2]) * kernel(trainDataF[i1], trainDataF[i2]) + b;
double b2;
b2 = -E[i2] - y1 * (a1 - alpha[i1]) * kernel(trainDataF[i1], trainDataF[i2]) -
y2 * (a2 - alpha[i2]) * kernel(trainDataF[i2], trainDataF[i2]) + b;
double bNew = (b1 + b2) / 2;
b = bNew;
w = w + y1 * (a1 - alpha[i1]) * trainDataF[i1] + y2 * (a2 - alpha[i2]) *
trainDataF[i2];
//this is the linear SVM case, this equation are from the paper equation 22
alpha[i1] = a1;
alpha[i2] = a2;
// vector<double> wtmp (indim);
// for (int i=0; i<trainDataF.size();++i)
// {
// auto tmp = alpha[i]*trainDataF[i]*trainDataGT[i];
// wtmp = wtmp+tmp;
// }
// w = wtmp;
E[i1] = computeE(i1);
E[i2] = computeE(i2);
return 1;
特別注意b的計算公式,這里被坑了好久,原論文的計算b1 ,b2的公式全是正號,因為論文中svm的超平面公式是\(wx-b\),這與書上的公式不同,所以導致我的算法一直不收斂,最后從頭看論文才發現...
其他的實現都在這里,如果有問題歡迎交流。