支持向量機(SVM)


斷斷續續看了好多天,趕緊補上坑。

感謝july的 http://blog.csdn.net/v_july_v/article/details/7624837/

以及CSDN上淘的比較正規的SMO C++ 模板代碼。~LINK~

 

1995年提出的支持向量機(SVM)模型,是淺層學習中較新代表,當然Adaboost更新一點。

按照Andrew NG的說法: "SVM的效果大概相當於調整最好的神經網絡。"於是,SVM被各種神化,被譽為"未來人類的希望,世界人民的終極武器"。

甚至是IEEE選舉的數據挖掘十大算法中唯一一個神經網絡,對,沒錯,SVM就是個MLP。

 

SVM忽悠大法第一條“我有核函數!":

傻子都知道RBF徑向基核函數在SVM發明的7年前就已經被用於RBF神經網絡,RBF網絡本質就是個把激活函數從Sigmoid替換成RBF的MLP。

SVM處理線性不可分的能力大致來自於兩個部分,非線性映射(重點)、高維空間(核函數)。

SVM的非線性映射藏的比較隱秘,來自其偽·隱層結構:支持向量神經元層。至於核函數?就是個激活函數罷了。

實際上Logistic-Sigmoid函數也是核函數之一,Logistic回歸不能處理線性不可分問題,並不是因為沒有核函數,其實它有。

缺乏非線性映射變換的隱層結構,才是只能畫線,不能畫圓的關鍵,在這點上,不從神經網絡角度理解SVM,是很容易產生誤解的。

RBF較之於Sigmoid,確實有更快的收斂、更精確的逼近,但是,徑向范圍$\sigma$的選取並非易事,過大過小都會影響映射,至於SVM中默認

取定值的做法?其實並不完美。

 

SVM忽悠大法第二條“我分類效果好":

SVM的最優間隔理論,從數學上來講,very good,其效果也比直接用Gradient Descent擬合好。

當然,這得益於SVM的分類環境假設:一刀切二類分類。但是,更多情況下是K類分類。

SVM確實有多類改進方法,比如基於投票機制的KNN-SVM,但是效果並不好。

相比於傳統神經網絡天然的多分類輸出層,比如Softmax,遜色不少。

尤其在深度學習里,當樣本和訓練時間不作為第一要素、精度由深度網絡保證,SVM的作用就弱化不少。

比如用戶物體識別的ImageNet網絡里,共有1000類需要分類,訓練多個SVM,來達到多分類的目的,效果不會很好。

原因正如Bengio吐槽決策樹那樣,樣本的輸入空間被切分后,全樣本之間的稀疏特征被分離,不利於整體的協同表達。

當然這在淺層學習中,尤其是在數據挖掘這類的統計數據上,問題並不是很大。

 

SVM的優勢:

SVM本質就是個MLP,但是這個MLP真的很贊,它智能地解決了MLP中隱層神經元個數問題。

自MLP結構發明以來,隱層數、隱層神經元個數一直是最頭疼的地方。

比如,世界上的許多騙錢模擬大腦計划,大喊着要模擬人腦840億個神經元,瘋狂在隱層上做文章。純粹在浪費時間。

SVM直接出來打臉:隱層神經元無須很多,只要覆蓋支持向量即可。

其次,徑向基函數的徑向基中心選取,在二次規划的求解當中也被解決,而無須依靠訓練。

 

SVM的劣勢:

來自深度學習大師Yoshua Bengio的吐槽:

學習好的表示(representations)是深度學習的核心目的,而非像 SVM 一樣就是在特征的固定集合做一個線性預測。(吐槽 SVM 用 kernel 轉移重點)。

SVM終究是MLP,精確的MLP,但並不是拯救世界的神器,它只不過是淺層學習這個”數值游戲“的大贏家。

尺有所短,寸有所長。對於SVM和傳統神經網絡,我們的態度應該是不吹不黑。知其然,知其所以然。

 

 

①最大化幾何間隔。

如果數據能一道線切開的話,我們希望離這道線最近的點的距離越長越好,否則容易分類錯誤,這就是SVM的核心。

定義函數間隔di=ywxi+b),其中y=1 or -1,問題在於2*didi是等效的分隔平面,所以棄之。

所以又定義幾何距離D=d/||w||||w||=根號(w1^2+w2^2+...wn^2)

目標函數max D,約束條件di=ywxi+b)>=d, 這里的d是取最小的函數間隔,保證離分割平面最近,且最大化距離。

 

化簡處理:令w’=w/d, b’=b/d, 這樣約束條件等式相當於兩邊被除了d,變成di=yw’xi+b)>=1,

D也相當於變成1/||w||,為了方便,以后w’全部用w代替。

di=ywxi+b)>=1, 這是個有趣的式子,由於y=±1,單獨取出(wxi+b),那么±1的就是傳說中里分隔平面最近的點了,稱之為支持向量。

 

 

②探究規划問題

目標函數=max D=max 1/||w||,分式好討厭,等效成min (數學原理不知道是啥==

然后引入拉格朗日乘子α,把目標函數、約束條件捆在一起成一個式子。

這樣有新的目標函數:

這個式子很容易被誤解,它的max針對的是

min針對的是,注意看min/max底部,不要很矛盾地理解成對整個式子。

max部分是重點,引入變量α,成為拉格朗日乘子。max這部分的意義在於等效約束條件,

由於ywxi+b)>=1,如果出現小於1的情況,那么減去的這部分就是一個負數,負負得正,會使前面的min黯然失色。至於大於1的情況,可以令α=0 踢掉它。

最理想的情況下,=min 

然而這個式子還是比較麻煩的,min規划是先決條件,然后還要考慮不定的乘子部分。

利用拉格朗日對偶性大法,逆轉下兩個條件,變成對偶規划問題。,要滿足KKT條件,就有d*<=p*

KKT條件說白了就是0<α<C. (define C 100),0 or C稱之為邊界。

這樣就可以先定住maxmin,最后再求max了。

 

③大道至簡

上面這個目標函數的min(主元是w、b) 部分化簡很神奇,先分別對主元w和b求偏導,令偏導數為0。

第一個偏導結果巧妙地用alpha、y、x替代了惡心的w

第二個偏導結果則是帶來了一個新的約束條件,好處是化簡目標函數時消掉了b,壞處是給計算alpha乘子帶來麻煩。(由此SMO算法誕生了)

將w的代替式拖到里,經過復雜的化簡(詳見July大神博客),b也被新的約束條件(為0)給消去了。

最后=,很簡單,很優美。

加上max和兩個約束條件之后

 

max部分將由SMO算法完成,SMO每次抽取確定兩個乘子,然后更新wb

更新w

 ,(這是單乘子的更新方式,SMO中不能這么干)

更新b

 

這個b比較好玩,w確定之后,實際上分隔平面的方向被確定了。

想想一次函數y=wx+b,如果b沒確定,則這個一次函數就可能是兩個邊界間任意一條直線。

那么b應該是什么值呢,應該取兩條邊界的中間那條比較好。(SMO中b也不能這么干)

 

④分類函數

訓練數據時,需要一個預測分類函數,以便算出誤差E。(這個E在SMO中至關重要)

分類函數自然就是帶入式子y=wx+b,但是這可不是二元一次方程,w是多維的。

在上面中我們有,那么分類函數就變成

 

<xi,x>是內積。這里先不管這個內積怎么算。

 

SVM的超級武器——核函數

特征空間的映射的引入使得SVM秒掉了各種回歸模型。它的原理就是將低維度數據,映射到高維度數據。

比如原來數據是2維,如果是非線性數據,很難划分隔面。但是給它加到5維,就很容易一刀切出划分面了。

 

巧妙的映射了維度,這對分類函數中的內積計算產生了影響,為了計算在高維空間的內積,出現了核函數。

新的分類函數為,核函數取代了原本的內積計算。

 

目前使用的核函數通常是 "高斯核“函數,又名徑向基核函數。

 

 

⑥松弛變量的優化

outlier(異常值)是SVM需要解決的一個問題。

如果就存在一個異常值,離分隔平面超級超級超級近,設這么一個垃圾值為支持向量顯然不是明智選擇。(間隔越小,平面越難被划分,分類效果就越差)

所以引入松弛變量,使得原目標函數變為

切入約束條件后,拉格朗日函數變成:

然后按照⑤中方法化簡,多了一個新主元,並求導令倒數為0。

你不必關系ri是什么,只要知道ri>=0即可,移個項,αi=C+ri。實際意義就是αi多了一個上界C。

同⑤中那樣化簡拉格朗日函數,最后驚奇發現,松弛變量被消了,和⑤最后的式子一樣。

其實松弛變量引入的唯一變化,就是多了個KKT條件多了個上界C。(C的取值很難說,模板代碼里取的是100)

)新的KKT條件如下:

α=0,表示非支持向量點。

0<α<C,表示支持向量點。

α=C,表示的是兩條支持向量間的outlier(這些點通常被無視掉)

 

⑥SMO算法

1998年4月,位於總部的微軟研究院副主任John Platt發明了這個奇怪的SMO算法,從此SVM超神了。

John Platt那篇論文省去了公式推導,所以大部分情況都是,直接給你個XX情況的公式是XX,以及給出的很殘破的C偽代碼

對初學者很不友好,鏈接  ~Link~,想要完全解釋清楚還是比較難的。

感謝CSDN某網友提供的完整的SMO的C++代碼,里面的變量名幾乎與John Platt論文中的C偽代碼一致。

 

Part I  SMO的公式推導

①新的分類函數與新的目標函數

SMO定義了新的分類函數u,其實也不算新,就是把b的符號變了。

這個式子就是把y=wx-b,w替換了而已,上面已經出現了類似物了。

也定義的新的目標函數

 

減號兩邊換一下,從max變成了min,意義不明。

 

②兩個乘子問題

還記得這玩意不,偏導出來的坑爹約束條件。

假設我們每次抽取一個乘子,設為α1,那么改了α1之后,能保證上面那個約束條件成立么?很可惜,不好辦。

所以John Platt提出了SMO(最小序列算法),他認為至少(最小)要同時改變兩個乘子。

分為主動乘子(α2)和被動乘子(α1)。每次主動算α2,然后根據約束條件,推出α1的值,這樣α1光榮地做了嫁衣。

一個有趣的想法,比如這次搞定了α2,到了下一次,上次的α2又變成了α1,被動算出來之后,會不會導致上次的α2過程白算了?然后惡性循環?

其實想多了,實際測試,每次抽兩個乘子,只會使結果變好,而不會卡死在循環里。

 

於是算α2成為頭等大事。

先確定α2的范圍,已有約束條件:0<=α1<=C, 0<=α2<=C (稱為矩形條件)

y1α1+y2α2=γ,γ=Σ(yi*αi),i=3,4....,m(那個頭疼的約束條件,提取出兩項)

由於yi=±1, 這里分兩種情況。(γ不知道正負)

情況1: y1=y2

y1α1+y2α2=γ  => α1+α2=±γ(兩條直線)

情況2:y1≠y2,即異號。

y1α1+y2α2=γ  => α1-α2=±γ(兩條直線)

畫個圖

 

這樣當y1=y2,α2有下界L=max(0,γ-C),上界H=min(γ,C)

y1≠y2,α2有下界L=max(0,-γ),上界H=min(C-γ,C)

注意這里的α2稱之為α2(new),原來的α2稱為α2(old),同時下面所有打(*)號的變量都是old變量。

 

α2的具體值怎么確定呢?有如下推導。

首先,抽取兩個乘子后,目標函數被剖分成這樣

就是把i、j∈[1,2]的部分提出來了,但是至今不理解vi部分是怎么出來的。

對於條件,令兩個式子各乘y1,有

其中,化簡目標函數:

對α2求導並令導數為0:

化簡:

化簡:

令ui-yi=Ei,繼續化簡,有:

,

裁剪α2(new):

算出α1(new)

特別的,當η<0時,違反了Mercer條件,目標函數為可能∞,這時候α2不能使用上述方式計算,論文中這么寫 

看不懂,填坑,先按模板代碼來。

 

③啟發式抽取乘子。

盡管α1是被動乘子,我們還是得先抽取α1,然后啟發式抽取α2。

啟發式(經驗式)主要是根據已有經驗來安排三種抽取乘子的順序。

最壞情況其實就是O(m)掃一遍全部數據找到一個α2,當然啟發式原則保證我們的RP不會那么差。

一、非邊界(0 or C)最大化|E1-E2|規則:選擇絕對值最大的先計算。

二、非邊界隨機規則:非邊界alpha里隨機選擇α2

三、含邊界隨機規則:實在沒有辦法了,算上邊界再隨機吧。

 

④收斂條件

 由於每次計算α2時,都是由導數決定的,也就是說,每次新的α2產生,都標志着目標函數的優化。

一旦全部α2收斂(即全部乘子收斂,α1若不收斂,下次可能作為α2被調整),則標志着目標函數優化完畢。

SMO主循環遍歷全部數據點選擇α1(mainProcess函數)

次循環隨機/最大化|E1-E2|規則選擇起點開始遍歷選擇α2 (exampleExamine函數)

相當於兩層for循環驗證全部乘子對,保證最后的目標函數是收斂的。

 

⑤雙乘子下的幾個參數求法

一、α1

α1先通過公式,即 a1 = alph1 - s * (a2 - alph2)

若違反KKT條件,則設為邊界,並按照約束條件對α2進行增/減。

二、b

John Platt不知道從哪搞來的公式。目前不知道推導過程。

雙乘子情況下求b,論文中這么解釋:

①α1非邊界:則b=

②α2非邊界,則b=

③α1、α2均在邊界,則取b1、b2均值

④α1、α2均非邊界,則b1=b2,所以①、②任取一個就行了。

三、w

 

Part Ⅱ  SMO的C++代碼研究 (坑ing)

一、SMO主過程。

void SMO::smo_main_process()
{
    read_in_data();    //initialize
    if(!is_test_only)
    {
        alph.resize(end_support_i,0);
        b=0;
        error_cache.resize(N);
    }
    self_dot_product.resize(N);
    precomputed_self_dot_product();
    if (!is_test_only)
    {
        numChanged = 0;
        examineAll = 1;
        while (numChanged > 0 || examineAll)
        {
            numChanged = 0;
            if (examineAll)
            {
                for (int k = 0; k < N; k++)
                    numChanged += examineExample (k);
            }
            else
            {
                for (int k = 0; k < N; k++)
                    if (alph[k] != 0 && alph[k] != C)
                        numChanged += examineExample (k);
            }
            if (examineAll == 1)
                examineAll = 0;
            else if (numChanged == 0)
                examineAll = 1;
        }
    }
}
SMO主過程

這個過程由兩個Bool值控制,numChanged 、 examineAll.

首先第一遍檢查全部乘子,之后檢查非邊界乘子(邊界乘子的值通常不會改變了),查不到了再重新檢查全部,如果全部還查不到,則結束,計算完畢。

檢查並修改乘子是一個智能的過程,綜合使用三種手段。

int SMO::examineExample(int i1)
{
    float y1, alph1, E1, r1;
    y1 = (float)target[i1];
    alph1 = alph[i1];
    if (alph1 > 0 && alph1 < C)
        E1 = error_cache[i1];
    else
        E1 = learned_func_nonlinear(i1) - y1;

    r1 = y1 * E1;
    if ((r1 < -tolerance && alph1 < C)||(r1 > tolerance && alph1 > 0))
    {
        /////////////使用三種方法選擇第二個乘子
        //1:在non-bound乘子中尋找maximum fabs(E1-E2)的樣本
        //2:如果上面沒取得進展,那么從隨機位置查找non-boundary 樣本
        //3:如果上面也失敗,則從隨機位置查找整個樣本,改為bound樣本
        if (examineFirstChoice(i1,E1))  return 1;     //1
        if (examineNonBound(i1))   return 1;       //2
        if (examineBound(i1))  return 1;             //3
    }
///沒有進展
    return 0;
}
利用三種手段修改兩個乘子

alpha1直接獲取,由於每個乘子對應一條訓練數據,所以該條數據誤差E1=預測值-真實值,預測值就是把該條數據帶入學習方程(分類函數里)去

//徑向基核函數
float SMO::kernel_func(int i,int k)
{
    float sum=dot_product_func(i,k);
    sum*=-2;
    sum+=self_dot_product[i]+self_dot_product[k];
    return exp(-sum/two_sigma_squared);
}



float SMO::learned_func_nonlinear(int k)
{
    float sum=0;
    for(int i=0; i<end_support_i; i++)
    {
        if(alph[i]>0)
            sum+=alph[i]*target[i]*kernel_func(i,k);
    }
    sum-=b;
    return sum;
}
核函數與分類函數


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM