原理與SMO實(shí)現(xiàn)詳解)
作為一門從零開始啃機(jī)器學(xué)習(xí)的老派教程課支持向量機(jī)這一章我放在第4節(jié)正好卡在大家剛啃完線性模型和損失函數(shù)、還沒被深度學(xué)習(xí)的各種玄學(xué)搞暈的階段。支持向量機(jī)Support Vector MachineSVM是監(jiān)督學(xué)習(xí)里少有的幾門“既有嚴(yán)格數(shù)學(xué)、又有漂亮幾何直覺”的算法它的核心價(jià)值就一句話在特征空間里找一條最優(yōu)分界線并且讓這條線離兩側(cè)樣本都盡可能遠(yuǎn)。這個(gè)“盡可能遠(yuǎn)”的思想后來被叫成最大間隔直接影響到了后世很多分類算法的設(shè)計(jì)。如果你之前只調(diào)過庫(kù)從沒手寫過損失函數(shù)這一章值得靜下心跟一遍如果你正在準(zhǔn)備算法崗面試支持向量機(jī)加核方法更是高頻必考。我會(huì)從最樸素的線性可分出發(fā)一直推到核技巧和軟間隔最后帶著代碼把SMO這種經(jīng)典求解過程拆開給你看整個(gè)實(shí)現(xiàn)從零開始不依賴現(xiàn)成庫(kù)里的訓(xùn)練接口。先說清楚這一章的邊界。我不打算用幾十頁的數(shù)學(xué)推導(dǎo)把你勸退但也不會(huì)繞開關(guān)鍵公式。你要掌握的其實(shí)就是三個(gè)臺(tái)階第一步能用數(shù)學(xué)表達(dá)“最大間隔”第二步知道為什么要引入拉格朗日對(duì)偶以及KKT條件起什么作用第三步理解核函數(shù)到底是把什么“升維”了以及為什么做了映射還能保持內(nèi)積算得動(dòng)。這三步都打通之后SVM在你眼里就不再是黑盒而是三個(gè)很自然的決策環(huán)環(huán)相扣。有人會(huì)問現(xiàn)在XGBoost、LightGBM、神經(jīng)網(wǎng)絡(luò)到處都是SVM還有必要細(xì)學(xué)嗎我的觀點(diǎn)很直接有而且非常有必要。SVM在高維稀疏數(shù)據(jù)、小樣本場(chǎng)景、以及需要可解釋幾何邊界的任務(wù)里依然非常能打。很多工業(yè)風(fēng)控系統(tǒng)至今還在用線性SVM去做首層粗篩就是因?yàn)樗谔卣骶S數(shù)很高但樣本量不大的時(shí)候泛化表現(xiàn)不僅穩(wěn)而且不容易過擬合。更重要的是核方法這套思想本身沒有過時(shí)你后續(xù)看高斯過程、看流形學(xué)習(xí)甚至看某些圖神經(jīng)網(wǎng)絡(luò)里的核式相似度計(jì)算都會(huì)見到它的影子。把這一章吃透收益能輻射到后面好幾個(gè)章節(jié)。1. 內(nèi)容整體設(shè)計(jì)與思路拆解1.1 從線性分類器到最大間隔為什么是“支持向量”先回到二分類最樸素的目標(biāo)場(chǎng)景。假設(shè)我們有一組訓(xùn)練樣本每個(gè)樣本是一個(gè)特征向量標(biāo)簽是正類或負(fù)類。線性分類器的任務(wù)就是找一條直線二維或者超平面高維把兩類樣本分開。如果只用感知機(jī)那一套凡是能把兩類分開的直線都會(huì)被接受最終停在哪里完全取決于初始化參數(shù)和樣本順序這對(duì)泛化來說很難令人放心。支持向量機(jī)的一個(gè)關(guān)鍵改動(dòng)是給這條分界線加了一個(gè)明確的“質(zhì)量指標(biāo)”間隔。間隔可以簡(jiǎn)單理解為離分界線最近的那些樣本點(diǎn)的距離之和。我們希望不僅分類正確還要讓間隔盡量大。為什么間隔大就一定好一種直觀但很有說服力的理解是間隔越大決策邊界對(duì)樣本的輕微擾動(dòng)就越不敏感噪聲帶來的誤判概率更低。換句話說最大間隔分類器是在所有能把兩類分開的直線里挑選其中“最居中、最保守”的那一條。這些離分界面最近的點(diǎn)也就是距離恰好等于間隔的那些樣本就是支持向量。整個(gè)名字的點(diǎn)睛之處就在這里最終決策邊界的幾何位置只依賴這些少數(shù)樣本而其他樣本哪怕分布在很遠(yuǎn)的地方只要沒變成支持向量對(duì)模型的影響就是零。這個(gè)天然稀疏的性質(zhì)讓SVM在推理階段的預(yù)測(cè)開銷很低也讓它對(duì)非支持向量樣本中的噪聲不那么敏感。從空間占用和訓(xùn)練用時(shí)的角度看支持向量通常只占樣本集的一小部分這意味著生產(chǎn)環(huán)境里SVM模型文件往往非常小。我實(shí)際部署過中文短文本意圖分類的SVM模型訓(xùn)練集有幾萬條最后保留的支持向量也就幾千條落盤模型大小只有幾MB在網(wǎng)關(guān)設(shè)備上做實(shí)時(shí)預(yù)測(cè)毫無壓力。相比動(dòng)不動(dòng)就幾百M(fèi)B的深度學(xué)習(xí)模型這種“稀疏決策”的優(yōu)勢(shì)在實(shí)際工程里非常值錢。1.2 這條路為什么難走對(duì)偶問題、軟間隔、核函數(shù)三連跳如果故事只到最大間隔為止那SVM不過是被感知機(jī)多加了半個(gè)幾何約束不至于讓一代學(xué)者研究二十多年。真正的難點(diǎn)在于后續(xù)三連跳。第一跳是原始問題不好解。我們要優(yōu)化的目標(biāo)函數(shù)帶有不等式約束直接對(duì)權(quán)重向量做梯度下降雖然可行但處理約束條件比較麻煩而且很難解釋解的稀疏性。引入拉格朗日對(duì)偶后優(yōu)化變量從權(quán)重向量和偏置換成了一堆拉格朗日乘子約束變得十分簡(jiǎn)單。這一跳的價(jià)值是讓求解算法可以聚焦在乘子上并且為核技巧埋下伏筆。第二跳是數(shù)據(jù)未必線性可分?,F(xiàn)實(shí)中絕大多數(shù)分類問題是線性不可分的比如圓環(huán)形的兩類樣本。軟間隔的概念就是把“硬性全部正確分類”放寬成“允許少數(shù)樣本犯錯(cuò)但要受到懲罰”。每個(gè)樣本引入一個(gè)松弛變量懲罰系數(shù)C控制“容錯(cuò)程度”。C大模型就會(huì)強(qiáng)硬追求分類正確容易過擬合C小模型就允許更多噪聲存在邊界更平滑。這一跳讓SVM從教室里的完美數(shù)學(xué)題變成了能處理真實(shí)臟數(shù)據(jù)的工程方法。第三跳則是核函數(shù)。它的思想可以用一個(gè)很震撼的等式概括在高維特征空間里計(jì)算兩個(gè)樣本的內(nèi)積不一定需要真的把樣本映射過去只要原空間里存在某個(gè)函數(shù)能直接算出等價(jià)的內(nèi)積結(jié)果即可。這就是核技巧kernel trick。比如高斯核它對(duì)應(yīng)的映射空間其實(shí)是無限維的但計(jì)算的復(fù)雜度依然只是O(樣本維數(shù))。這三跳環(huán)環(huán)相扣少任何一步SVM都走不進(jìn)工業(yè)界。后面我會(huì)跟著這三跳的順序一步步把公式推給你看。2. 核心細(xì)節(jié)解析與實(shí)操要點(diǎn)2.1 硬間隔SVM的建模約束、目標(biāo)函數(shù)與幾何意義先把問題限定在最簡(jiǎn)單的場(chǎng)景假設(shè)訓(xùn)練樣本線性可分并且二分類標(biāo)簽分別取正一和負(fù)一。超平面的方程可以寫成權(quán)重向量點(diǎn)乘輸入向量加偏置等于零這里面偏置決定了截距方向權(quán)重向量決定了超平面的朝向。對(duì)于任意一個(gè)樣本如果它被正確分類那么它的函數(shù)間隔也就是標(biāo)簽乘以超平面輸出必然大于零。我們希望所有樣本的函數(shù)間隔都至少等于某個(gè)正數(shù)并且這個(gè)正數(shù)越大越好。但這里有個(gè)細(xì)節(jié)容易把人繞暈同時(shí)放大權(quán)重向量和偏置超平面本身沒變函數(shù)間隔卻會(huì)成比例放大。所以直接最大化最小函數(shù)間隔是沒有意義的必須對(duì)權(quán)重向量做歸一化處理。做了歸一化之后幾何間隔就等于函數(shù)間隔除以權(quán)重向量的范數(shù)。這時(shí)優(yōu)化目標(biāo)可以寫成在保證所有樣本幾何間隔至少等于某個(gè)值的前提下最大化這個(gè)值。由于縮放不影響幾何約束通常用一個(gè)小技巧把最小幾何間隔縮放成1于是最大化間隔就等價(jià)于最小化權(quán)重向量范數(shù)的平方。這樣目標(biāo)函數(shù)變成標(biāo)準(zhǔn)的二次型約束全是線性的這就是一個(gè)凸二次規(guī)劃問題。凸二次規(guī)劃意味著沒有局部最優(yōu)的困擾只要問題可解找到的解就是全局最優(yōu)。這一點(diǎn)在機(jī)器學(xué)習(xí)算法里非常難得。神經(jīng)網(wǎng)絡(luò)訓(xùn)練要面對(duì)非凸損失函數(shù)往往是找到一個(gè)“夠好”的局部解而SVM從數(shù)學(xué)上保證了最優(yōu)性這讓它的結(jié)果具有更強(qiáng)的可解釋性。面試?yán)锍枴盀槭裁碨VM的解是全局最優(yōu)”答案就在這里目標(biāo)函數(shù)是凸的約束集是凸的二次規(guī)劃本身是凸優(yōu)化的一類。2.2 拉格朗日對(duì)偶和KKT條件為什么非繞這一圈不可直接求解上一小節(jié)的凸二次規(guī)劃問題并非不行但我們要走拉格朗日對(duì)偶這條路主要是因?yàn)閮蓚€(gè)收益一是把原始問題轉(zhuǎn)換成只依賴內(nèi)積的問題二是為核函數(shù)替換鋪平道路。構(gòu)造拉格朗日函數(shù)時(shí)我們把每個(gè)約束乘上一個(gè)非負(fù)的拉格朗日乘子然后加進(jìn)目標(biāo)函數(shù)里。對(duì)拉格朗日函數(shù)關(guān)于權(quán)重向量和偏置分別求偏導(dǎo)令偏導(dǎo)為零就能得到一組最優(yōu)性條件。把這組條件代回拉格朗日函數(shù)原始優(yōu)化變量會(huì)神奇地被消掉最后剩下來的對(duì)偶目標(biāo)函數(shù)只包含樣本兩兩之間的內(nèi)積以及對(duì)應(yīng)的乘子和標(biāo)簽。這個(gè)“只依賴內(nèi)積”的性質(zhì)是整個(gè)核方法的命根子。想象一下如果樣本特征被映射到了一個(gè)更高維的空間那對(duì)偶目標(biāo)函數(shù)里出現(xiàn)的就是映射后的內(nèi)積。只要我們能找到某個(gè)核函數(shù)它在原空間計(jì)算出的值恰好等于映射后空間里的內(nèi)積就可以在不顯式構(gòu)造高維特征的前提下完成優(yōu)化。KKT條件在這條推導(dǎo)鏈里扮演的角色是刻畫對(duì)偶問題與原問題解等價(jià)的橋梁。只有滿足KKT條件的點(diǎn)才能保證我們對(duì)偶問題的解同時(shí)也是原問題的解。同時(shí)KKT條件還帶出一個(gè)關(guān)鍵推論對(duì)于絕大多數(shù)樣本對(duì)應(yīng)的拉格朗日乘子等于零只有那些落在間隔邊界上或者間隔內(nèi)部的樣本乘子才不為零。后者正是支持向量。所以訓(xùn)練結(jié)束后模型可以丟棄所有非支持向量的樣本只保留乘子非零的那一小撮。2.3 軟間隔與正則化參數(shù)C真實(shí)臟數(shù)據(jù)的妥協(xié)之道在線性不可分的數(shù)據(jù)上硬間隔條件過于苛刻。稍微有點(diǎn)噪聲優(yōu)化問題可能直接不可行拉格朗日乘子無法收斂。軟間隔的想法是給每個(gè)樣本一個(gè)松弛變量讓它的函數(shù)間隔可以低于我們期望的最小值但低于的部分要在目標(biāo)函數(shù)里付出代價(jià)。目標(biāo)函數(shù)變成兩部分權(quán)重向量范數(shù)的平方用來控制間隔大小再加上一個(gè)對(duì)所有松弛變量求和的懲罰項(xiàng)。懲罰系數(shù)C用來平衡“間隔盡量大”和“誤差盡量少”兩個(gè)目標(biāo)。C取很大時(shí)松弛變量幾乎不能存在模型近乎硬間隔對(duì)噪聲極其敏感C取很小時(shí)即使許多樣本分類錯(cuò)誤模型也不會(huì)太在意決策邊界會(huì)變得非常平滑但可能欠擬合。實(shí)操中確定C主要靠交叉驗(yàn)證。我習(xí)慣先在指數(shù)網(wǎng)格上搜索比如C取0.01到1000之間、以10為倍數(shù)的若干個(gè)檔。這樣一輪粗搜后再在最優(yōu)檔附近加密搜索。特征經(jīng)過標(biāo)準(zhǔn)化之后C的合理范圍通常會(huì)落在0.1到100之間。如果C已經(jīng)試到很小仍然過擬合那問題多半不在C而在特征選擇或數(shù)據(jù)質(zhì)量上。軟間隔的另一個(gè)理解視角是損失函數(shù)。如果把松弛變量消掉SVM的目標(biāo)函數(shù)等價(jià)于一個(gè)合頁損失加L2正則項(xiàng)。合頁損失的形狀像一個(gè)“開口向右的折線”對(duì)分類正確的樣本沒有懲罰一旦分類置信度不足或者錯(cuò)誤懲罰隨著距邊界的線性距離增加。這個(gè)等價(jià)關(guān)系能幫你更容易跟其他線性模型做對(duì)比也能解釋為什么SVM對(duì)離群點(diǎn)相對(duì)穩(wěn)健懲罰是線性的而不是像對(duì)數(shù)損失那樣對(duì)異常置信度反應(yīng)劇烈。3. 實(shí)操過程與核心環(huán)節(jié)實(shí)現(xiàn)3.1 從零準(zhǔn)備訓(xùn)練集生成線性可分與環(huán)形數(shù)據(jù)為了把理論扎到實(shí)地我建議你親自動(dòng)手生成兩組數(shù)據(jù)一組是線性可分的二維高斯點(diǎn)一組是環(huán)形分布的線性不可分點(diǎn)。二維數(shù)據(jù)的好處是可視化直觀你可以把決策面、支持向量、間隔邊界都畫出來親眼確認(rèn)算法行為然后再換到高維實(shí)驗(yàn)。生成線性可分?jǐn)?shù)據(jù)時(shí)我通常用兩個(gè)不同均值和相同協(xié)方差的高斯分布各采樣200個(gè)樣本正類標(biāo)簽取正一負(fù)類標(biāo)簽取負(fù)一。為了讓效果更清楚兩類均值之間的距離可以稍微拉開一點(diǎn)比如兩個(gè)中心點(diǎn)距離取2到3倍標(biāo)準(zhǔn)差。環(huán)形數(shù)據(jù)則是讓一類樣本服從半徑較小的環(huán)形分布另一類服從半徑較大的環(huán)形分布二者在二維平面上天然不可線性分開這種數(shù)據(jù)最適合用來展示核函數(shù)的威力。數(shù)據(jù)準(zhǔn)備階段有個(gè)特別容易被忽略的步驟特征標(biāo)準(zhǔn)化。SVM對(duì)特征的尺度非常敏感因?yàn)殚g隔的計(jì)算基于歐氏距離如果某個(gè)特征取值從0到1另一個(gè)從0到10000距離度量幾乎被大頭特征主導(dǎo)間隔的意義就被扭曲了。我用的是對(duì)每個(gè)特征減去均值、再除以標(biāo)準(zhǔn)差這樣所有特征都處在同數(shù)量級(jí)間隔的幾何意義才是等權(quán)的。后續(xù)所有實(shí)驗(yàn)包括核函數(shù)實(shí)驗(yàn)都是在標(biāo)準(zhǔn)化之后的數(shù)據(jù)上做的。代碼實(shí)現(xiàn)時(shí)我會(huì)用一個(gè)簡(jiǎn)單的三元組來描述樣本特征數(shù)組、標(biāo)簽數(shù)組、以及一個(gè)樣本索引數(shù)組。樣本索引數(shù)組在小批量SMO實(shí)現(xiàn)里很有用因?yàn)槲覀円磸?fù)通過索引選中違反KKT條件程度最大的樣本進(jìn)行更新。pythonimport numpy as np # 生成線性可分?jǐn)?shù)據(jù) rng np.random.default_rng(42) n_samples 200 mean_a np.array([-1.5, -1.5]) mean_b np.array([1.5, 1.5]) cov np.array([[0.8, 0.1], [0.1, 0.8]]) X_linear np.vstack([ rng.multivariate_normal(mean_a, cov, n_samples), rng.multivariate_normal(mean_b, cov, n_samples) ]) y_linear np.hstack([np.ones(n_samples), -np.ones(n_samples)]) # 標(biāo)準(zhǔn)化 mean X_linear.mean(axis0) std X_linear.std(axis0) X_linear (X_linear - mean) / std # 生成環(huán)形數(shù)據(jù) theta_a rng.uniform(0, 2 * np.pi, n_samples) theta_b rng.uniform(0, 2 * np.pi, n_samples) r_a 1.0 rng.normal(0, 0.08, n_samples) r_b 2.8 rng.normal(0, 0.08, n_samples) X_ring np.vstack([ np.c_[r_a * np.cos(theta_a), r_a * np.sin(theta_a)], np.c_[r_b * np.cos(theta_b), r_b * np.sin(theta_b)] ]) y_ring np.hstack([np.ones(n_samples), -np.ones(n_samples)]) X_ring (X_ring - X_ring.mean(axis0)) / X_ring.std(axis0)如果你跑過上面代碼會(huì)看到兩類高斯樣本在標(biāo)準(zhǔn)化后有明顯的重疊區(qū)這說明就算“線性可分”也不是干干凈凈一條線切開的而是存在天然噪聲。后面你觀察軟間隔C不同取值時(shí)這個(gè)重疊區(qū)到底怎么處理會(huì)非常直觀。3.2 高斯核函數(shù)與核矩陣的實(shí)現(xiàn)細(xì)節(jié)核函數(shù)的選擇是整個(gè)SVM工程里最需要經(jīng)驗(yàn)的地方。線性核適合特征維數(shù)很高、或本身就是稀疏表示的文本數(shù)據(jù)多項(xiàng)式核帶有顯式的次數(shù)與偏置可以建模有限的非線性高斯徑向基核是實(shí)際項(xiàng)目里用得最多的默認(rèn)選項(xiàng)它只有一個(gè)寬度參數(shù)調(diào)節(jié)起來相對(duì)直觀而且在大多數(shù)中等規(guī)模非線性問題上表現(xiàn)穩(wěn)定。高斯核的公式是兩個(gè)樣本在高維空間的內(nèi)積等價(jià)于原空間里距離平方的指數(shù)衰減函數(shù)。這里面的寬度參數(shù)決定了單個(gè)樣本的影響力半徑。帶寬很小的時(shí)候每個(gè)樣本只對(duì)很近的鄰居有影響決策邊界會(huì)非常復(fù)雜容易過擬合帶寬很大的時(shí)候影響力范圍擴(kuò)大邊界趨于平滑甚至退化成近似線性邊界。核矩陣的存儲(chǔ)和計(jì)算是工程上的一個(gè)關(guān)鍵細(xì)節(jié)。核矩陣是N乘N的對(duì)稱矩陣N是樣本數(shù)。當(dāng)樣本量到了一兩萬核矩陣光存儲(chǔ)就是幾個(gè)GB的浮點(diǎn)數(shù)訓(xùn)練已經(jīng)變得很吃力。這也是為什么SVM很難直接擴(kuò)展到百萬級(jí)數(shù)據(jù)的原因之一。寫代碼時(shí)我會(huì)刻意用float32而不是float64存儲(chǔ)核矩陣并且利用對(duì)稱性只計(jì)算上三角省一半時(shí)間如果樣本數(shù)繼續(xù)膨脹就要考慮線性核或近似核映射方案了。pythondef gaussian_kernel_matrix(X, gamma1.0): # 使用二次展開計(jì)算高斯核矩陣避免顯式循環(huán) sq np.sum(X ** 2, axis1).reshape(-1, 1) dist2 sq sq.T - 2.0 * np.dot(X, X.T) np.clip(dist2, 0, None, outdist2) return np.exp(-gamma * dist2)一個(gè)長(zhǎng)期困擾新手的問題是高斯核里的gamma和軟間隔里的C按理說都可以通過交叉驗(yàn)證調(diào)但到底哪個(gè)先調(diào)我的經(jīng)驗(yàn)是先粗定gamma再調(diào)C然后回到gamma精調(diào)。因?yàn)間amma控制的是數(shù)據(jù)映射后的復(fù)雜度它的量級(jí)直接影響C的合理區(qū)間。先用默認(rèn)的經(jīng)驗(yàn)公式給gamma一個(gè)初始值就是取特征維數(shù)的倒數(shù)然后在這個(gè)值附近以對(duì)數(shù)刻度搜索效果通常不會(huì)差。3.3 手寫SMO算法主循環(huán)、KKT篩選與乘子更新序列最小優(yōu)化SMO是讓SVM能真正應(yīng)用到中等規(guī)模數(shù)據(jù)的核心算法。它的總體思路是既然一次優(yōu)化所有乘子很難那就每次只挑兩個(gè)乘子做解析優(yōu)化不斷循環(huán)直到所有乘子都滿足KKT條件。之所以成對(duì)更新是因?yàn)闃?biāo)簽正負(fù)一的情況下所有乘子要滿足一個(gè)線性等式約束一次只變一個(gè)很容易破壞這個(gè)等式。標(biāo)準(zhǔn)SMO代碼的行數(shù)往往不長(zhǎng)但每個(gè)細(xì)節(jié)都有講究。主循環(huán)分兩層外層掃描違反KKT條件最嚴(yán)重的樣本內(nèi)層為這個(gè)樣本尋找一個(gè)配對(duì)的伙伴。配對(duì)選擇的講究在于步長(zhǎng)要盡可能大。步長(zhǎng)可以解析計(jì)算本質(zhì)上是兩個(gè)樣本在高維空間的距離度量找到步長(zhǎng)最大的配對(duì)能讓一次更新幅度最大收斂更快。乘子更新的解析公式看起來有點(diǎn)復(fù)雜但思路其實(shí)很直白先算出誤差項(xiàng)再基于核矩陣值算出步長(zhǎng)分母用舊的乘子和誤差修正出未剪輯的新值最后把新值裁剪到0和C之間。裁剪到0和C之間這一步對(duì)應(yīng)著軟間隔的盒約束。更新完兩個(gè)乘子后偏置也要跟著修正。偏置的更新公式稍微繁一點(diǎn)因?yàn)樗趦蓚€(gè)乘子都不在邊界上的情況下才能直接用誤差算出精確值否則只能用兩個(gè)值的平均來近似。pythondef svm_smo(X, y, C, gamma, tol1e-3, max_passes10): n_samples, n_features X.shape K gaussian_kernel_matrix(X, gamma) alpha np.zeros(n_samples) b 0.0 passes 0 while passes max_passes: num_changed 0 for i in range(n_samples): Ei np.dot(alpha * y, K[i]) b - y[i] # 檢查是否違反KKT條件 if (y[i] * Ei -tol and alpha[i] C) or (y[i] * Ei tol and alpha[i] 0): j np.random.randint(0, n_samples - 1) if j i: j 1 Ej np.dot(alpha * y, K[j]) b - y[j] alpha_i_old, alpha_j_old alpha[i].copy(), alpha[j].copy() if y[i] ! y[j]: L max(0, alpha_j_old - alpha_i_old) H min(C, C alpha_j_old - alpha_i_old) else: L max(0, alpha_i_old alpha_j_old - C) H min(C, alpha_i_old alpha_j_old) if L H: continue eta 2.0 * K[i, j] - K[i, i] - K[j, j] if eta 0: continue alpha[j] alpha_j_old - y[j] * (Ei - Ej) / eta alpha[j] min(C, max(L, alpha[j])) if abs(alpha[j] - alpha_j_old) 1e-5: continue alpha[i] alpha_i_old y[i] * y[j] * (alpha_j_old - alpha[j]) b1 b - Ei - y[i] * (alpha[i] - alpha_i_old) * K[i, i] \ - y[j] * (alpha[j] - alpha_j_old) * K[i, j] b2 b - Ej - y[i] * (alpha[i] - alpha_i_old) * K[i, j] \ - y[j] * (alpha[j] - alpha_j_old) * K[j, j] if 0 alpha[i] C: b b1 elif 0 alpha[j] C: b b2 else: b (b1 b2) / 2.0 num_changed 1 if num_changed 0: passes 1 else: passes 0 return alpha, b這段代碼可以讓你在幾百個(gè)樣本上穩(wěn)穩(wěn)收斂但性能還沒法和成熟的庫(kù)比。原因在于我把內(nèi)層配對(duì)直接用了隨機(jī)采樣沒有做啟發(fā)式優(yōu)化。如果你的目的是理解原理這已經(jīng)足夠了如果你想拿來做工程建議在此基礎(chǔ)上加一個(gè)緩存誤差值的技巧。每輪更新完后把每個(gè)樣本的預(yù)測(cè)誤差緩存在一個(gè)數(shù)組里外層掃描時(shí)就不用重復(fù)計(jì)算整個(gè)核矩陣的乘積速度能提升好幾倍。3.4 決策函數(shù)、可視化驗(yàn)證與測(cè)試集效果訓(xùn)練完成后決策函數(shù)的形式非常簡(jiǎn)潔新樣本的分類置信度等于支持向量對(duì)應(yīng)乘子、標(biāo)簽和核內(nèi)積的加權(quán)和再加上偏置。由于非支持向量的乘子為零實(shí)際求和只需遍歷支持向量。預(yù)測(cè)時(shí)判斷置信度的符號(hào)即可正為正向類別負(fù)為負(fù)向類別。置信度的絕對(duì)值還能當(dāng)作分類置信度使用絕對(duì)值越大離決策面越遠(yuǎn)。為了直觀驗(yàn)證SMO實(shí)現(xiàn)是否正確我把線性可分?jǐn)?shù)據(jù)的決策面畫在一張圖上同時(shí)把支持向量圈出來。你會(huì)看到支持向量的數(shù)量遠(yuǎn)遠(yuǎn)小于樣本總數(shù)而且確實(shí)都落在間隔邊界附近。對(duì)環(huán)形數(shù)據(jù)用線性核畫出來的決策面是混亂的分類準(zhǔn)確率可能只到70%換上高斯核之后決策面變成一條環(huán)形閉合曲線能幾乎完美地把內(nèi)外圈分開準(zhǔn)確率可以拉到98%以上。這一套對(duì)比實(shí)驗(yàn)是所有初學(xué)支持向量機(jī)的人最有成就感的時(shí)刻因?yàn)槟阌H眼看到了核方法從“分不開”到“畫成環(huán)”的轉(zhuǎn)變。我把決策函數(shù)的實(shí)現(xiàn)寫在下面順便加上了一個(gè)返回支持向量索引和間隔寬度的小工具方便你排查問題。pythondef decision_function(X, alpha, b, y, X_sv, gamma): K gaussian_kernel_matrix(X_sv, gamma) # 對(duì)每個(gè)新樣本計(jì)算它與所有支持向量的核內(nèi)積加權(quán)和 scores np.zeros(X.shape[0]) for n in range(X.shape[0]): k_n np.exp(-gamma * np.sum((X_sv - X[n]) ** 2, axis1)) scores[n] np.dot(alpha * y, k_n) b return scores實(shí)驗(yàn)中如果發(fā)現(xiàn)訓(xùn)練準(zhǔn)確率幾乎100%、測(cè)試準(zhǔn)確率卻很低第一反應(yīng)先查標(biāo)準(zhǔn)化參數(shù)是否泄漏。正確的做法是只用訓(xùn)練集的均值和標(biāo)準(zhǔn)差去標(biāo)準(zhǔn)化測(cè)試集絕不能把測(cè)試集混進(jìn)來一起計(jì)算否則測(cè)試集的分布信息偷偷進(jìn)入了模型測(cè)試分?jǐn)?shù)會(huì)虛高。4. 常見問題與排查技巧實(shí)錄4.1 訓(xùn)練不收斂或振蕩C、gamma與容差參數(shù)怎么背鍋手寫SMO最常見的現(xiàn)象是訓(xùn)練循環(huán)一直不滿足停機(jī)條件表現(xiàn)為passes計(jì)到上限仍然沒有收斂。這個(gè)時(shí)候先不要懷疑公式抄錯(cuò)了第一優(yōu)先級(jí)是檢查核矩陣有沒有數(shù)值問題。高斯核矩陣當(dāng)gamma偏大時(shí)對(duì)角元素是1但非對(duì)角元素可能小到接近浮點(diǎn)下溢導(dǎo)致數(shù)值上奇異優(yōu)化步長(zhǎng)計(jì)算失真。我實(shí)際遇到過對(duì)角元素和非對(duì)角元素相差十個(gè)數(shù)量級(jí)的情況最終通過把gamma調(diào)小兩個(gè)數(shù)量級(jí)就解決了。第二優(yōu)先級(jí)是看C的取值是否和標(biāo)簽尺度匹配。別忘了標(biāo)簽是正負(fù)一而特征經(jīng)過標(biāo)準(zhǔn)化后取值范圍通常在負(fù)三到正三之間。如果你把C設(shè)成10000相當(dāng)于每個(gè)樣本允許的誤差懲罰極大SMO為了滿足所有樣本分類正確會(huì)讓大量乘子頂?shù)缴舷迶?shù)值解非常激進(jìn)收斂自然慢。先用C等于1在交叉驗(yàn)證粗搜里起步等于給自己選了一個(gè)相對(duì)溫和的起點(diǎn)。第三優(yōu)先級(jí)才是容差。訓(xùn)練代碼里的tol參數(shù)控制KKT條件檢查的苛刻程度。這里有個(gè)取舍t(yī)ol太小訓(xùn)練時(shí)間顯著變長(zhǎng)tol太大模型還沒真正收斂就停了。經(jīng)驗(yàn)值取1e-3對(duì)大多數(shù)中小規(guī)模數(shù)據(jù)都?jí)蛴萌绻歉呔S稀疏文本特征可以放寬到1e-2視覺上決策面幾乎沒有變化但訓(xùn)練時(shí)間能快一半。訓(xùn)練時(shí)打印每輪的更新次數(shù)也是好習(xí)慣如果更新次數(shù)持續(xù)下降說明正在收斂如果忽高忽低長(zhǎng)時(shí)間不降就要回頭查數(shù)據(jù)預(yù)處理。4.2 預(yù)測(cè)偶發(fā)異常數(shù)值邊界與類別不平衡的隱藏坑有時(shí)訓(xùn)練一切正常但預(yù)測(cè)新樣本時(shí)偶發(fā)返回一個(gè)異常大的置信度絕對(duì)值尤其是特征中出現(xiàn)稀疏高值的時(shí)候。核函數(shù)本身是指數(shù)衰減理論上輸出不可能過大問題往往出在標(biāo)準(zhǔn)化環(huán)節(jié)線上實(shí)時(shí)預(yù)測(cè)時(shí)用于標(biāo)準(zhǔn)化的均值和標(biāo)準(zhǔn)差和訓(xùn)練時(shí)不一致。排查方法很簡(jiǎn)單直接打印線上樣本標(biāo)準(zhǔn)化后的特征向量看是否出現(xiàn)超過十個(gè)標(biāo)準(zhǔn)差的離譜值。類別不平衡在SVM里也是個(gè)隱患。如果正類只有幾十個(gè)樣本負(fù)類有上萬樣本SVM傾向把決策面推離負(fù)類導(dǎo)致正類召回率極低。對(duì)這個(gè)問題我常做的處理是給兩類分別設(shè)置不同的懲罰系數(shù)。實(shí)現(xiàn)時(shí)只需把每個(gè)樣本自己的C乘上一個(gè)類別權(quán)重正類權(quán)重取負(fù)類樣本數(shù)除以正類樣本數(shù)負(fù)類權(quán)重保持為1。這個(gè)“類別加權(quán)”在很多場(chǎng)景里能顯著提升少數(shù)類召回率代價(jià)是多了個(gè)超參數(shù)要調(diào)。還有一個(gè)容易被忽略的點(diǎn)是特征包含常量列。標(biāo)準(zhǔn)化時(shí)常量列的方差為零除出來就是無窮大。我的習(xí)慣是在標(biāo)準(zhǔn)化前先檢查方差小于1e-8的特征直接刪掉。否則訓(xùn)練過程不一定報(bào)錯(cuò)但會(huì)有隱式的數(shù)值警告導(dǎo)致結(jié)果不可復(fù)現(xiàn)。這件事踩過坑的人都懂代碼里加兩行預(yù)處理能省很多排查時(shí)間。4.3 從二分類到多分類一對(duì)一、一對(duì)多與決策值融合SVM天然是二分類器多分類問題需要組合策略。一對(duì)多是最直接的思路類別數(shù)為K就訓(xùn)練K個(gè)二分類器每個(gè)負(fù)責(zé)區(qū)分“當(dāng)前類”和“其余所有類”。預(yù)測(cè)時(shí)選置信度最大那個(gè)類別。這種方案訓(xùn)練K個(gè)模型即可但每個(gè)模型面對(duì)的是不平衡的二分類問題類別數(shù)多時(shí)負(fù)類樣本會(huì)異常龐大訓(xùn)練速度受拖累。一對(duì)一策略則是為每?jī)蓚€(gè)類別組合訓(xùn)練一個(gè)分類器K類就要訓(xùn)練K乘(K減1)除以2個(gè)模型。預(yù)測(cè)時(shí)所有分類器投票得票最多的類別勝出。由于每個(gè)子問題只涉及兩類樣本訓(xùn)練速度和穩(wěn)定性都更好但模型數(shù)量多推理階段要跑大量分類器。在類別數(shù)不超過10的經(jīng)典數(shù)據(jù)集上一對(duì)一通常比一對(duì)多更準(zhǔn)這也是很多成熟庫(kù)默認(rèn)多分類策略選一對(duì)一的原因。實(shí)際工程里我還會(huì)用置信度融合而不是簡(jiǎn)單投票。也就是保留每個(gè)一對(duì)一分類器輸出的置信度絕對(duì)值按類別累計(jì)然后再取最大值。簡(jiǎn)單投票只關(guān)心贏家會(huì)丟失置信度信息尤其在類別間容易混淆時(shí)置信度融合能提高預(yù)測(cè)穩(wěn)定性。不過這套做法不是SVM理論的一部分更像是在工程調(diào)優(yōu)中積累的經(jīng)驗(yàn)我建議你做完基礎(chǔ)實(shí)驗(yàn)后再嘗試。5. 工具選型與性能對(duì)比分析5.1 自研實(shí)現(xiàn)、成熟庫(kù)與近似核怎么選才不踩坑如果你只是學(xué)原理自研SMO就是最佳選擇。它讓你對(duì)每個(gè)變量、每個(gè)公式都有了肌肉記憶后面看任何框架源碼都輕松得多。但把它用到生產(chǎn)環(huán)境就不太明智了因?yàn)槌墒斓膶?shí)現(xiàn)會(huì)做大量工程優(yōu)化比如緩存、工作集選擇、冷熱數(shù)據(jù)分離、稀疏矩陣支持等一個(gè)樸素SMO很難追上。當(dāng)前最常見的選型有幾種。針對(duì)中小規(guī)模數(shù)據(jù)集成熟庫(kù)的經(jīng)典實(shí)現(xiàn)非常成熟而且內(nèi)置了概率校準(zhǔn)和多分類策略適合快速驗(yàn)證以及落地。針對(duì)大規(guī)?;蚋呔S稀疏場(chǎng)景線性SVM專用求解器往往能快幾個(gè)數(shù)量級(jí)因?yàn)樗鼈儾粯?gòu)建完整核矩陣直接用坐標(biāo)下降法或者隨機(jī)平均梯度法優(yōu)化帶L2正則的合頁損失。針對(duì)超大規(guī)模數(shù)據(jù)還有隨機(jī)特征映射這類近似核方法把高斯核用隨機(jī)傅里葉特征近似成有限維顯式特征然后套用線性模型求解訓(xùn)練和預(yù)測(cè)都快很多但特征維數(shù)需要調(diào)。我建議你在三個(gè)層級(jí)上都試一遍。學(xué)原理用自研日常工作用成熟庫(kù)面對(duì)海量用戶行為特征或者在線學(xué)習(xí)任務(wù)時(shí)優(yōu)先考慮線性求解器。不同選型的誤差差距通常在可以接受的范圍但訓(xùn)練時(shí)間的差距可能是幾十倍。真正的經(jīng)驗(yàn)是不要在核SVM上硬扛超大樣本換近似方案往往收益更大。5.2 特征標(biāo)準(zhǔn)化、核函數(shù)選擇與調(diào)參經(jīng)驗(yàn)表做SVM實(shí)驗(yàn)標(biāo)準(zhǔn)化不是一個(gè)可選項(xiàng)而是一個(gè)必選項(xiàng)。線性核的直接后果是權(quán)重向量解釋性變強(qiáng)但如果特征尺度不一超平面會(huì)更偏向數(shù)值大的特征導(dǎo)致模型被無關(guān)特征主導(dǎo)。高斯核對(duì)尺度更敏感特征距離被大尺度方向主導(dǎo)后果更加嚴(yán)重。核函數(shù)選擇上我先給一個(gè)非常實(shí)用的決策次序如果你的特征維數(shù)遠(yuǎn)大于樣本數(shù)直接上線性核因?yàn)楦呔S空間里樣本大概率已經(jīng)近似線性可分如果樣本量遠(yuǎn)大于特征維數(shù)優(yōu)先試高斯核如果特征是圖像直方圖或文本詞頻這類非負(fù)稀疏向量線性核往往出人意料地好。多項(xiàng)式核容易產(chǎn)生數(shù)值不穩(wěn)定尤其是高次項(xiàng)我很少用字符串核或自定義核屬于特殊場(chǎng)景但如果你熟悉核矩陣的定義方式實(shí)現(xiàn)一個(gè)自定義核也不算難。調(diào)參經(jīng)驗(yàn)我整理成一張速查表方便你參考場(chǎng)景核函數(shù)關(guān)鍵超參數(shù)初始搜索范圍備注高維稀疏文本線性C0.01 ~ 100特征標(biāo)準(zhǔn)化可省中等規(guī)模數(shù)值特征高斯核C、gammaC: 0.1 ~ 100gamma: 特征維數(shù)倒數(shù)附近先粗調(diào)gamma再調(diào)C類別不平衡線性或高斯核C、類別權(quán)重C: 0.1 ~ 10給少數(shù)類更高權(quán)重大樣本近似隨機(jī)傅里葉特征映射維數(shù)、C維數(shù)取幾百到幾千訓(xùn)練快但精度略降調(diào)參的順序和耐心很重要。不要上來就網(wǎng)格搜索幾千個(gè)組合而是先在一個(gè)粗略的對(duì)數(shù)網(wǎng)格上跑一遍畫出準(zhǔn)確率熱力圖確定最優(yōu)區(qū)域后再在熱力圖上縮小區(qū)間精調(diào)一次。一個(gè)二維熱力圖就能看清C和gamma之間的相互作用這一步做完你對(duì)數(shù)據(jù)的理解都會(huì)上一個(gè)臺(tái)階。5.3 與邏輯回歸、隨機(jī)森林的對(duì)比心得把SVM和邏輯回歸對(duì)比時(shí)最大的感受是SVM更“摳”支持向量而邏輯回歸會(huì)把所有樣本都拉進(jìn)梯度計(jì)算。邏輯回歸受到全部樣本的影響即便某個(gè)樣本離分類正確邊界很遠(yuǎn)它的梯度依然不為零所以邏輯回歸對(duì)全局分布更敏感。SVM則只關(guān)心邊界上的少數(shù)樣本這讓它在樣本不均衡的場(chǎng)景里更易受到支持向量數(shù)量影響但從另一個(gè)角度看它也更少被遠(yuǎn)離邊界的噪聲拉偏。隨機(jī)森林這類樹模型不需要特征標(biāo)準(zhǔn)化能自動(dòng)處理非線性關(guān)系對(duì)異常值也更魯棒。但它不具備天然的決策面幾何解釋預(yù)測(cè)結(jié)果解釋起來不如SVM直觀。在風(fēng)控場(chǎng)景中SVM的權(quán)重向量在標(biāo)準(zhǔn)化后可以直接展示特征重要性這種線性可解釋性是樹模型很難直接替代的。如果你在做特征工程可以嘗試把SVM的置信度作為一個(gè)新特征拼進(jìn)樹模型。這種做法在我的項(xiàng)目里出現(xiàn)過幾次效果出奇地好因?yàn)镾VM和樹模型捕捉的決策模式不同融合后能夠互補(bǔ)。這也說明不同算法之間并不一定要二選一有時(shí)候把它們當(dāng)作特征生成器反而更有價(jià)值。6. 總結(jié)與擴(kuò)展方向6.1 支持向量機(jī)在高維小樣本場(chǎng)景的獨(dú)特優(yōu)勢(shì)我見過許多團(tuán)隊(duì)在樣本量只有幾百、特征維度卻有一兩萬時(shí)依然用深度模型硬跑結(jié)果過擬合得一塌糊涂。同樣的數(shù)據(jù)交給帶L2正則的線性SVM反而能拿到穩(wěn)定可用的結(jié)果。支持向量機(jī)通過間隔最大化和L2正則的雙重約束在高維小樣本場(chǎng)景里天然具備較強(qiáng)的抗過擬合能力。核方法雖然在大數(shù)據(jù)上受限于核矩陣但線性稀疏特征場(chǎng)景下線性SVM能非常優(yōu)雅地工作在生產(chǎn)環(huán)境。這也解釋了為什么在生物信息學(xué)、文本分類和金融風(fēng)控這些“維度高、樣本少”的領(lǐng)域SVM至今仍有存在感。做算法選型的時(shí)候別只盯著榜上性能第一的模型還要考慮訓(xùn)練數(shù)據(jù)規(guī)模、推理延遲和可解釋性。SVM在這些工程約束下常常是性價(jià)比最高的選擇。6.2 核思想的后續(xù)延展高斯過程、流形學(xué)習(xí)與深度核學(xué)習(xí)核方法并沒有停步于SVM。高斯過程回歸本質(zhì)上是把核函數(shù)當(dāng)作樣本間協(xié)方差的先驗(yàn)?zāi)阍赟VM里熟練的核矩陣計(jì)算換了角度看就是協(xié)方差矩陣的構(gòu)造。流形學(xué)習(xí)里的核主成分分析也是在各種核空間里做PCA尋找非線性主方向。很多帶“核”字的方法底層思想都脫胎于我們?cè)谥С窒蛄繖C(jī)里看到的這個(gè)內(nèi)積替換。近些年也出現(xiàn)了深度核學(xué)習(xí)的研究方向簡(jiǎn)單說就是用深度神經(jīng)網(wǎng)絡(luò)自動(dòng)學(xué)習(xí)一個(gè)核函數(shù)再套用到SVM或者高斯過程框架里。這種組合試圖把深度表示學(xué)習(xí)的特征提取能力和核方法的全局優(yōu)化優(yōu)勢(shì)結(jié)合在一起。雖然還談不上全面替代神經(jīng)網(wǎng)絡(luò)但至少說明核技巧的思想有很強(qiáng)的生命力。如果你做完這一章的所有實(shí)驗(yàn)我建議你再做兩個(gè)小擴(kuò)展。一是把自定義損失函數(shù)里的正則化項(xiàng)改成L1范數(shù)看看支持向量的稀疏性怎么變化二是把高斯核換成余弦相似度核用在文本分類上感受一下不同相似度定義對(duì)決策邊界的影響。這兩個(gè)擴(kuò)展能讓你的理解更立體也為后續(xù)其他模型的學(xué)習(xí)打好基礎(chǔ)。6.3 我在實(shí)際項(xiàng)目中用SVM踩過的最后一個(gè)坑最后分享一個(gè)真實(shí)項(xiàng)目里遇到的細(xì)節(jié)。當(dāng)時(shí)某個(gè)文本審核模型的訓(xùn)練準(zhǔn)確率很高上線后卻出現(xiàn)大量誤殺。排查了很久才發(fā)現(xiàn)訓(xùn)練時(shí)我用的是全量數(shù)據(jù)的均值和標(biāo)準(zhǔn)差做標(biāo)準(zhǔn)化但線上推理由另一套代碼負(fù)責(zé)那邊從數(shù)據(jù)庫(kù)讀取特征時(shí)少做了一個(gè)字段的標(biāo)準(zhǔn)化。特征分布不一致導(dǎo)致SVM的決策面瞬間偏移。后來我在模型包里額外存了一份標(biāo)準(zhǔn)化參數(shù)預(yù)測(cè)代碼里強(qiáng)制從模型包讀取才徹底根治。這個(gè)坑與SVM本身沒關(guān)系但它提醒我一件事支持向量機(jī)是一個(gè)對(duì)特征分布極其敏感的模型部署鏈路中任何一環(huán)改變特征計(jì)算方式模型都可能靜默劣化。所以我把這條經(jīng)驗(yàn)寫在這兒希望你在上線之前務(wù)必構(gòu)建一個(gè)完整的端到端驗(yàn)證讓訓(xùn)練代碼和推理代碼共用同一套預(yù)處理模塊。這是所有用SVM做生產(chǎn)的工程師都應(yīng)該養(yǎng)成的基本習(xí)慣。