ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

拉格朗日乘子与KKT条件:SVM对偶推导和SMO实现

拉格朗日乘子与KKT条件:SVM对偶推导和SMO实现

学 SVM 的人分成两拨:一拨能把对偶问题从头推到尾,另一拨靠背公式撑过面试,两个月后连 α 的约束是0 ≤ α ≤ C还是α ≥ 0都要愣一下。分水岭就卡在一个地方——拉格朗日算子和KKT条件这套东西到底在干什么。很多人管它叫"算子",其实标准叫法是"乘子"(multiplier),名字的差别背后是理解深度:算子听起来像个神秘装置,乘子则明明白白告诉你它是"给约束配的一个价格"。这篇不打算把 SVM 讲成一本凸优化教材,我想干的事情更具体:把拉格朗日函数是怎么被"逼"出来的、KKT 那五条为什么一条都不能少、以及这套理论最后怎么在 SVM 里落成一行行能跑的代码,全部摊开说清楚。如果你正在啃 SVM 的推导,或者写过调包代码但不知道C和gamma到底在动什么,这篇应该能帮你把中间那层窗户纸捅破。全文涉及的内容会从动机、构造、KKT 逐条拆解、对偶理论一路走到手写简化 SMO,代码是可以直接跑的。

1. 约束优化的真实痛点:为什么 SVM 绕不开这一套

1.1 一个让新手懵圈的现场

假设你手头有个最朴素的需求:找一个点,让它离原点最近,但又必须落在某条直线上。写成数学就是min (x² + y²)满足x + y = 2。不用任何高深工具,代进去就完事了:y = 2 - x,代入得到2x² - 4x + 4,求导4x - 4 = 0,x = 1, y = 1,最近距离平方是 2。这是一元函数极值,初中生也能做。

问题在于,真实问题里约束不是一条直线这么客气。机器学习里出现的约束通常是成百上千条,而且是不等式。SVM 的原始问题就是典型:min ½‖w‖²,同时要求对每一个训练样本y_i(w·x_i + b) ≥ 1。这里w可能有上万维,样本可能有几万个,约束就是几万条不等式。你不可能像消元那样一个个代进去。

这时候有两条路。第一条是直接在有约束的可行域里做搜索,比如投影梯度:每次走一步,走完再"投影"回可行域。这条路能走,但很别扭——投影到高维半空间的交集上本身就是个子问题,而且一旦约束是等式加不等式的混合,投影的计算量会失控。第二条路是把约束"塞进"目标函数里,把有约束问题变成无约束问题,然后套用我们熟悉的无约束优化工具。拉格朗日乘子法就是第二条路,也是 SVM 走的那条路。

我见过不少人第一次看到L(w, b, α) = ½‖w‖² - Σα_i[y_i(w·x_i + b) - 1]时的反应是"这个式子哪来的"。答案不神秘:它就是"目标函数 + 每个约束配一个系数,乘上这个约束的违背量"。

1.2 拉格朗日乘子的几何直觉:等高线与约束面相切

要真正理解乘子,得先接受一个几何图像。考虑二维平面上min f(x, y)受曲线g(x, y) = 0约束。画出f的等高线,是一圈圈闭合曲线;约束曲线是一条线。假如最优点落在约束曲线上某处,你沿着约束曲线走,f的值不能继续下降——否则它就不是最优点了。这意味着在最优点附近,约束曲线的切线方向与f的等高线切线方向一致,换句话说,两条曲线相切。

相切在代数上意味着什么?等高线的法向量是∇f,约束曲线的法向量是∇g。两个曲线相切,法向量就平行。于是存在一个标量λ,使得∇f = -λ∇g,或者写成∇f + λ∇g = 0。

这个式子就是拉格朗日乘子法的全部灵魂。λ不是什么玄学参数,它是"两个梯度共线时的比例系数"。你如果记不住符号,就记住这一句:在最优点,目标函数的梯度必须被约束的法向"抵消"掉,因为任何沿着约束曲面的方向都不能让目标继续下降。

SVM 里同样的故事发生在更高的维度。½‖w‖²的等高线是一圈圈超球面,每条约束y_i(w·x_i + b) = 1在参数空间里是一个超平面。最优的w落在这些超平面的交集边界上,∇(½‖w‖²) = w必须被若干条起作用约束的法向线性组合出来。把这句话写成式子,w = Σ α_i y_i x_i就自然而然地掉出来了——它是"梯度共线"的直接翻译。

1.3 SVM 为什么非要对偶形式

有人会问,原始问题不是已经能用二次规划求解器直接解了吗,为什么教材非要绕一圈搞对偶?这个问题问得很好,因为如果只是想解一个线性可分的 SVM,确实可以直接调 QP 求解器。但对偶形式带来了三样原始形式给不了的东西。

第一样是核技巧的入口。对偶问题里,样本只以x_i · x_j内积的形式出现,从来不需要单独访问x_i或者x_j本身。这意味着你可以把内积整体换成一个核函数K(x_i, x_j),等价于把数据隐式映射到一个更高维甚至无穷维的空间里去,而完全不用显式构造那个映射。这个能力是 SVM 从线性分类器变成非线性分类器的根本,而它只在对偶形式下才成立。

第二样是约束结构的简化。原始问题是n条线性不等式约束,可行域是一个多面体;对偶问题是n个箱约束0 ≤ α_i ≤ C加一条等式Σ α_i y_i = 0。箱约束的好处是它天然可分解,每次只动两个坐标就能解析地求出最优步长,这就是 SMO 算法能高效工作的前提。你去解原始问题,n条耦合的线性不等式可没有这么友好的结构。

第三样是稀疏性的显式化。对偶解里,绝大多数α_i会精确等于 0,只有少数落在边界上的样本对应的α_i为正,这些样本就是支持向量。这个性质直接从 KKT 的互补松弛条件里读出来,不需要任何额外的工程手段。分类时只需要计算支持向量的内积,推理成本跟支持向量数量成正比而不是跟训练集大小成正比。

提示:对偶形式不是"更高级的等价写法",它是一个能在特定维度关系下带来计算优势的重参数化。当特征维度远大于样本数时(比如文本、生物特征),对偶问题的变量数是n而不是d,这个差距是数量级的。

2. 拉格朗日函数怎么构造:把约束"折叠"进目标

2.1 等式约束的构造套路

从最简单的情况入手:min f(x)满足h(x) = 0。构造拉格朗日函数L(x, λ) = f(x) + λh(x)。然后对x求偏导置零,得到∇f(x) + λ∇h(x) = 0,联立h(x) = 0一共n + 1个方程,解n + 1个未知数(x的n个分量加λ)。

这套流程之所以有效,核心在于它把一个"在曲面上找极值"的问题转换成了"在无约束空间中找一个鞍点"的问题。L(x, λ)关于x是要求极小,关于λ是要求极大——因为λ的符号会自动调整到让h(x)这一项起惩罚作用。鞍点这个说法一开始听着别扭,但你可以这么想:如果x违反了约束,λ就会往"加重惩罚"的方向调,逼着x回到可行域;而x一旦回到可行域,λ就没有继续增大的动力了。两边互相较劲,最后停下来的位置就是最优点。

多个等式约束只是把一项扩展成和式:L(x, λ) = f(x) + Σ λ_i h_i(x)。每个约束配一个自己的乘子,互不干扰,因为它们各自对应一个独立的法向。

2.2 乘子的符号:别背公式,看梯度方向

符号是新手最容易搞混的地方。有人写f - λh,有人写f + λh,还有人把不等式的符号和等式混在一起。我自己的记法是:只看约束的形式,不看教材的约定。

如果约束写成h(x) = 0,那么+λh和-λh都对,只是λ的符号相反,最终解出的x完全一样。因为等式约束是双向的,h = 0和-h = 0是同一个集合。

如果约束写成g(x) ≤ 0,那么必须用+μg(x),且要求μ ≥ 0。为什么是这个符号?因为在最优点,如果约束是起作用的(也就是g(x) = 0),那么目标函数的梯度必须指向可行域内部才能继续下降,而可行域内部是g < 0那一侧,∇g指向g增大的方向,也就是可行域外部。要让∇f被拉回来,就需要∇f = -μ∇g且μ ≥ 0,即∇f + μ∇g = 0且μ ≥ 0。

如果你的约束写成了g(x) ≥ 0(SVM 里就是这种形式,y_i(w·x_i + b) - 1 ≥ 0),那么为了维持μ ≥ 0的约定,就要写成-μg(x)。SVM 教材里那个L = ½‖w‖² - Σα_i[y_i(w·x_i + b) - 1]的负号就是这么来的。我第一次推导时在这里卡了半小时,就是因为照着≤ 0的模板抄,结果α的符号全反了,最后对偶问题成了最大化一个负定二次型。

注意:符号约定不统一是凸优化资料里最常见的坑。看任何一份推导,第一步先确认它用的是g ≤ 0还是g ≥ 0,然后再往下读,别硬套记忆里的公式。

2.3 手算一个等式约束的例子

拿一个能算到底的例子练手:min x² + y²满足x + y = 2。

构造L(x, y, λ) = x² + y² + λ(x + y - 2)。

对x求偏导:2x + λ = 0,得x = -λ/2。 对y求偏导:2y + λ = 0,得y = -λ/2。

所以x = y。代回约束:2x = 2,x = y = 1,λ = -2。目标值1 + 1 = 2。

这个结果和我们直接消元得到的一致。注意λ = -2是负数,这完全正常——等式约束的乘子符号没有限制。同时注意一个细节:λ的量纲是"目标函数单位每约束单位",这里约束x + y增加 1,最优值会增加λ = -2?等下,实际是减小 2。因为约束从x + y = 2放宽到x + y = 3,最优点变成(1.5, 1.5),目标值4.5,增加了 2.5 而不是 -2。

这里我算错了方向。正确的敏感度关系是:把约束写成h(x) = x + y - 2 = 0,那么∂(最优值)/∂(约束右端项) = -λ = 2。验证一下:右端从 2 变到 3,约束变成x + y = 3,最优点(1.5, 1.5),目标值2.25 + 2.25 = 4.5,相对原来增加了 2.5。而-λ = 2只给出了一阶近似,因为这是个二次函数,二阶项贡献了剩下的 0.5。乘子给的是局部敏感度,不是全局增量,这一点在理解影子价格时非常重要。

2.4 从等式到不等式:符号是这么变的

不等式的引入让整个框架复杂了一档,因为多了一个"这个约束到底起不起作用"的判断。考虑min f(x)受g(x) ≤ 0。构造L(x, μ) = f(x) + μg(x),μ ≥ 0。

现在有两种情况。如果无约束最优解本身就满足g(x) < 0,那约束是"松"的,最优点就是无约束最优点,此时∇f = 0,要让∇f + μ∇g = 0成立,只能是μ = 0。反过来,如果μ > 0,那么为了让等式成立,∇f必须非零,也就是最优点被约束"顶住"了,必然是g(x) = 0。

把这两种情况合起来写,就是μg(x) = 0。这一条叫互补松弛,是整个 KKT 体系里信息量最大的一条,后面会专门展开。它说的是:乘子和约束违背量,两者至少有一个是零。约束不起作用的时候乘子为零,乘子为正的时候约束一定卡在边界上。

这个逻辑放到 SVM 里就是:α_i = 0的样本,y_i(w·x_i + b) - 1 > 0,它们离边界很远,对模型没有影响;α_i > 0的样本,y_i(w·x_i + b) - 1 = 0,它们恰好压在间隔边界上,这就是支持向量。支持向量的定义不是"离决策边界近的点",而是"互补松弛条件里乘子非零的那些点",这两个说法在硬间隔下等价,但在软间隔下就有了差别,后面细说。

3. KKT 条件逐条拆解:五个条件到底在说什么

3.1 原始可行性:约束得先满足

第一条最没有技术含量,但对写代码的人最重要:g_j(x) ≤ 0,h_i(x) = 0。这就是说,你找到的那个点必须在可行域里。

在实际求解里,这一条对应的是"裁剪"操作。SMO 每次更新两个乘子α_i和α_j之后,必须把它们强制投影回0 ≤ α ≤ C且Σ α_i y_i = 0的可行域。两步更新里,等式约束由解析公式保证,箱约束靠clip保证:α_j更新完先裁到区间[L, H],再由等式约束反推α_i。这个裁剪不是可选的工程优化,它是原始可行性的直接要求,不裁的话下一轮算出来的所有量都没有意义。

我见过一个初学者写的版本,忘了裁剪,跑了几百轮之后α出现了负值,然后w的计算结果莫名其妙,最后排查了半天才发现问题。这类错误的特征是:损失函数可能还在下降,但解已经跑出了可行域,KKT 违反度的计算完全失效。

3.2 平稳性:梯度为零这件事在约束下的正确形态

第二条是∇f(x) + Σ μ_j ∇g_j(x) + Σ λ_i ∇h_i(x) = 0。这一条我在 1.2 节已经用几何图像解释过了,它是"目标梯度被约束法向抵消"的代数表达。

需要强调的一点是:这个条件是在拉格朗日函数上求梯度为零,不是原目标函数。很多人第一次看会说"最优点梯度怎么会是零,明明在边界上梯度不为零"。当然不为零,∇f一般不在原点是零,∇L才是零。搞混这两个会带来严重的后果,比如在写代码时用∇f的范数作为收敛判据,那就会永远等不到收敛。

在 SVM 里,平稳性条件给出两个结果:∇_w L = 0 ⇒ w = Σ α_i y_i x_i,∇_b L = 0 ⇒ Σ α_i y_i = 0。第一个式子说明w完全由训练样本的加权和表示,权重就是α_i y_i;第二个式子说明正负类的乘子加权和为零。后一个式子在 SMO 里是硬性约束,每次更新完两个变量必须重新满足它。

3.3 对偶可行性:不等式乘子的符号要求

第三条μ_j ≥ 0。这条约束看着最没内容,实际上它是整个理论成立的关键支柱。如果没有它,μ可以取负值,那么拉格朗日函数关于μ就不再是凹的,弱对偶性就崩了,所有的对偶结论都会失效。

这条约束并没有直接出现在拉格朗日函数的构造里,它是被"逼"出来的。你如果固定x,把拉格朗日函数看成μ的函数,L(x, μ) = f(x) + μg(x)。如果g(x) > 0(违反了约束),要让这个函数有上界,μ只能取 0 到无穷;如果g(x) ≤ 0,μ增大时L会减小,上确界在μ → ∞时取到且值为+∞。所以对偶函数q(μ) = inf_x L(x, μ)只在μ ≥ 0上有意义,这就是这条条件在数学上的来源。

在 SVM 里,α_i ≥ 0就是这一条。软间隔版本里它变成0 ≤ α_i ≤ C,上界C来自另一个约束的对偶可行性,这个我放到 5.6 节讲。

3.4 互补松弛:整个 KKT 里最有信息量的一条

第四条μ_j g_j(x) = 0,逐分量成立。我在 2.4 节已经讲了它的推导逻辑,这里只讲它在 SVM 里的实际后果,因为这一条是理解支持向量最直接的入口。

硬间隔 SVM 的互补松弛是α_i [y_i(w·x_i + b) - 1] = 0。逐个样本分析:

  • α_i = 0:这个样本对w没有贡献,因为它不出现在w = Σ α_i y_i x_i里。它的约束是松的,y_i(w·x_i + b) > 1,离边界有距离。
  • α_i > 0:约束必须取等号,y_i(w·x_i + b) = 1。这个样本恰好压在间隔边界上,它就是支持向量。

于是w只由支持向量决定,训练完以后那些α_i = 0的样本可以直接扔掉,预测时完全用不上。这就是 SVM 稀疏性的来源。它不是设计出来的优化技巧,而是互补松弛条件的直接推论。

软间隔的情况稍微丰富一点,因为α_i被封在[0, C]内,于是分三种情况:

  • α_i = 0:不在间隔内,也不在边界上,y_i f(x_i) > 1。
  • 0 < α_i < C:正好在间隔边界上,y_i f(x_i) = 1。这类叫自由支持向量。
  • α_i = C:在间隔内部或者被误分类,y_i f(x_i) ≤ 1。这类叫边界支持向量。

第三种情况下,乘子被上界卡住,不能再增大,所以约束是松的(y_i f(x_i) < 1)但乘子非零,互补松弛仍然成立——因为它只要求α_i和y_i f(x_i) - 1里有一个为零,现在α_i = C ≠ 0,那y_i f(x_i) - 1必须为零?

不对,这里得小心。软间隔的 KKT 里互补松弛有两组:α_i[y_i f(x_i) - 1 + ξ_i] = 0和(C - α_i)ξ_i = 0。当α_i = C时,第二组要求ξ_i = 0?

也不对。第二组是β_i ξ_i = 0,其中β_i = C - α_i。α_i = C时β_i = 0,ξ_i可以任意非负,所以松弛变量为正、样本落在间隔内部甚至被误分类,都是允许的。这个细节在推公式的时候很容易搞错方向,我在 5.6 节会把完整的两组条件列清楚。

3.5 凸性:KKT 从"必要"升级为"充要"的开关

到这里五条齐了:原始可行性、平稳性、对偶可行性、互补松弛。有教材把平稳性拆成原始/对偶两组,成了六条;也有把凸性当成第五条 KKT 条件的。我倾向于后者,因为对做 SVM 的人来说,凸性是最该被单独强调的一条。

原因是这样:对于一般的光滑约束优化问题,KKT 条件是必要条件——最优点处一定满足 KKT,但满足 KKT 的点不一定是最优点,也可能是鞍点或者局部极大点。而对凸优化问题(目标函数凸、等式约束仿射、不等式约束凸),KKT 条件升级成充要条件:只要一个点满足 KKT,它就是全局最优解。

这个升级至关重要。它意味着你不需要"找到最优解再验证 KKT",而是可以"直接求解 KKT 方程组,解出来就是最优解"。SMO 之所以能用一个简单的启发式迭代收敛到全局最优,靠的就是这个性质。它每一轮都在减少 KKT 违反度,而 KKT 违反度归零等价于找到全局最优。

SVM 的原始问题是min ½‖w‖²受线性不等式约束,目标函数是严格凸的二次函数,约束是仿射的,完美符合凸优化的条件。所以 KKT 在这里是充要的,没有任何理论上的坑。

3.6 一个手工验证互补松弛的例子

还是拿一个能算完的例子:min (x - 1)² + (y - 1)²满足x + y ≤ 1。

无约束最优是(1, 1),代入约束1 + 1 = 2 > 1,违反了。所以约束一定起作用。

构造L = (x-1)² + (y-1)² + μ(x + y - 1),μ ≥ 0。

平稳性:2(x-1) + μ = 0,2(y-1) + μ = 0,得x = y = 1 - μ/2。

互补松弛:μ(x + y - 1) = 0。假设μ = 0,则x = y = 1,x + y - 1 = 1 > 0,矛盾。所以μ > 0,必有x + y = 1,即2(1 - μ/2) = 1,2 - μ = 1,μ = 1。

检验μ = 1 ≥ 0,满足对偶可行性。最优解x = y = 0.5,目标值0.25 + 0.25 = 0.5。

这个例子里μ = 1也有直观解释:约束右端从 1 放宽到 1.2,最优点会向(1, 1)移一点,目标值大约下降0.1左右,-μ给出的一阶敏感度是-1,方向对得上。

4. 对偶函数与强对偶:SVM 对偶推导的地基

4.1 对偶函数的定义与它为什么一定是凹的

定义拉格朗日函数之后,取它对原始变量x的下确界,得到q(λ, μ) = inf_x L(x, λ, μ)。这个q叫对偶函数。

它有一个非常反直觉但极其重要的性质:不管原问题是不是凸的,q一定是凹函数。

证明只需要一行。L(x, λ, μ)关于(λ, μ)是仿射的,也就是一堆线性函数的和。一族仿射函数逐点取下确界,结果一定是凹的(这个性质叫"下确界保持凹性",因为凹函数的下水平集是凸的,取交还是凸的)。所以q是凹的,无论f、g、h长什么样。

这个性质的实际意义在于:对偶问题max q(λ, μ)受μ ≥ 0是一个凹最大化问题,等价于凸最小化。也就是说,哪怕原始问题是难啃的非凸问题,它的对偶问题也是凸的,可以用凸优化工具求解。

对 SVM 来说,我们更关心的是另一个推论:q的具体表达式是可以解析算出来的。因为L关于w和b是二次加线性的,下确界可以直接用求导置零的方式算出来,得到q(α) = Σ α_i - ½ ΣΣ α_i α_j y_i y_j x_i·x_j。这个式子是α的凹二次函数,最大化它就是一个标准的二次规划问题。

4.2 弱对偶:一个几行就能证明但极其关键的结论

设p*是原始问题的最优值,d*是对偶问题的最优值。弱对偶说d* ≤ p*。

证明:对任意可行的x(满足g(x) ≤ 0、h(x) = 0)和任意μ ≥ 0,有L(x, μ) = f(x) + μg(x) ≤ f(x),因为μ ≥ 0且g(x) ≤ 0,乘积非正。而q(μ) = inf_x L(x, μ) ≤ L(x, μ) ≤ f(x)。这个不等式对任意可行的x成立,取x为原始最优解x*,得q(μ) ≤ f(x*) = p*。再对μ取上确界,d* ≤ p*。

这几行推导没有用到任何凸性假设,所以它普适。它的含义是:对偶问题给出的永远是原始最优值的下界,而且这个界是能算的(因为对偶问题是凸的)。

SVM 里的实际用途是:如果你用某种启发式方法在解对偶问题,任何时刻的q(α)值都是原始最优值的下界,你可以用它做单调性检测。更有用的是,p* - d*这个差值叫对偶间隙,它是零的时候说明两边都找到了解,可以做收敛判据。

4.3 强对偶成立的条件与 SVM 的合规性

强对偶说d* = p*,对偶间隙为零。这不是无条件成立的。最常用的充分条件是Slater 条件:问题是凸的,且存在一个严格可行点,即存在x使得所有不等式约束严格成立(g_j(x) < 0),等式约束成立。

SVM 满足这个条件吗?原始问题是凸二次规划,约束是y_i(w·x_i + b) - 1 ≥ 0。只要数据是线性可分的,就一定能找到一组(w, b)让所有约束严格大于零(把‖w‖缩小一点就行)。所以 Slater 条件成立,强对偶成立。

对于线性不可分的数据,硬间隔 SVM 的可行域是空的,原始问题无解,这套推导直接失效。这也是软间隔必须引入的原因:它不是"为了容忍噪声而做的工程妥协",而是"让可行域非空、让强对偶仍然成立"的数学要求。引入松弛变量ξ之后,可行域重新变成非空的,Slater 条件再次成立,整套理论继续可用。

提示:很多教程说软间隔是为了处理噪声,这个说法对但不完整。从优化理论角度看,硬间隔在线性不可分数据上连可行解都没有,问题本身是病态的,软间隔是把问题变成良性问题的必要改造。

4.4 对偶间隙为零意味着什么

强对偶成立时,原始最优解和对偶最优解之间有一个很强的联系:如果x*是原始最优、(λ*, μ*)是对偶最优,那么x*一定是L(x, λ*, μ*)关于x的极小点。这一点是 KKT 平稳性条件的来源,也是 SMO 能通过解对偶问题得到原始解w的理论依据。

更实用的一点是,对偶间隙可以当收敛判据。SMO 迭代过程中,α每更新一次都可以算出一个q(α)值和一个原始可行解的f值(用当前的w、b代入),两者的差就是当前的间隙上界。不过实际实现里很少有人真的去算这个,因为计算原始可行解的开销不小,大家更常用 KKT 违反度作为替代判据,原因我在第 6 节解释。

5. 把 SVM 塞进这套框架:对偶推导全过程

5.1 原始问题:硬间隔的形式化

给定训练集{(x_i, y_i)},y_i ∈ {-1, +1},硬间隔 SVM 要找一个超平面w·x + b = 0,使得所有样本都被正确分类,且离超平面的最小距离(几何间隔)最大化。

几何间隔是y_i(w·x_i + b) / ‖w‖。由于w和b可以同比例缩放而不改变超平面,我们约定让最近的样本满足y_i(w·x_i + b) = 1,这样几何间隔就是1/‖w‖。最大化1/‖w‖等价于最小化‖w‖,再平方除以二就是为了求导方便:

min_{w,b} ½‖w‖²,s.t. y_i(w·x_i + b) ≥ 1, i = 1..n。

函数间隔取 1 这个约定不是随意的,它是利用缩放自由度做的规范化。没有这个规范,w可以任意小,问题就没有下界。这个细节在推导时经常被略过,但它是整个形式化能成立的前提。

5.2 构造拉格朗日函数

按 2.2 节的符号约定,约束写成了≥ 0的形式,所以要用减号配乘子:

L(w, b, α) = ½‖w‖² - Σ α_i [y_i(w·x_i + b) - 1],α_i ≥ 0。

展开一下会更清楚每一项的来源:

L = ½‖w‖² - Σ α_i y_i w·x_i - b Σ α_i y_i + Σ α_i。

第一项是原始目标,后面三项分别是约束里的w部分、b部分和常数部分配上的乘子。

5.3 对 w 和 b 求偏导:为什么置零就是全局最优

对w求梯度:∇_w L = w - Σ α_i y_i x_i。置零得w = Σ α_i y_i x_i。

对b求偏导:∂L/∂b = -Σ α_i y_i。置零得Σ α_i y_i = 0。

这里有一个必须说清楚的细节:为什么"置零"就一定是全局最小,而不是局部极值?

看L关于w的 Hessian,是单位矩阵I,正定。所以L关于w是严格凸的二次函数,唯一的驻点就是全局最小点。这个论证很简单但很重要,因为它保证了q(α) = inf_w L这个下确界是可以解析算出来的,不需要解任何子问题。

b的情况更微妙。L关于b是线性的,系数是-Σ α_i y_i。线性函数没有内部极小点:系数非零时,b → ±∞会让L → -∞,下确界是负无穷;只有系数为零时,L与b无关,下确界才有限。

于是Σ α_i y_i = 0这个条件不是"我们额外加上的约束",而是"下确界有限"这件事本身逼出来的。任何一组让Σ α_i y_i ≠ 0的α,对应的对偶函数值都是-∞,在最大化时会被自动淘汰。这个视角比"求导置零"更深一层,它解释了为什么对偶问题里会凭空多出一条等式约束。

我当年就是在这里想通的:对偶问题的约束不是人为设定的,是拉格朗日函数本身的结构决定的,你只是把"哪些α能让下确界有限"这个条件显式写了出来。

5.4 回代得到对偶问题

把w = Σ α_i y_i x_i和Σ α_i y_i = 0代回L。

第一项:½‖w‖² = ½ (Σ α_i y_i x_i)·(Σ α_j y_j x_j) = ½ ΣΣ α_i α_j y_i y_j (x_i·x_j)。

第二项:-Σ α_i y_i w·x_i = -Σ α_i y_i (Σ α_j y_j x_j)·x_i = -ΣΣ α_i α_j y_i y_j (x_i·x_j)。

第三项-b Σ α_i y_i = 0(用了等式约束)。

第四项Σ α_i。

合起来:L = Σ α_i - ½ ΣΣ α_i α_j y_i y_j (x_i·x_j)。

对偶问题就是最大化这个式子:

max_α Σ α_i - ½ ΣΣ α_i α_j y_i y_j (x_i·x_j),s.t. Σ α_i y_i = 0, α_i ≥ 0。

写成矩阵形式,令K是 Gram 矩阵K_ij = x_i·x_j,y是标签向量,Q = (y y^T) ⊙ K(Hadamard 积),则目标函数是1^T α - ½ α^T Q α。Q是半正定的,所以这是凹二次最大化,标准的凸二次规划。

注意这里Q的维度是n × n。当样本数上万时,光存Q就要占 800MB 内存(float64),这是个实打实的工程约束,第 6 节会讲怎么处理。

5.5 KKT 在 SVM 中的具体形态:支持向量从哪冒出来

把 KKT 四条逐条翻译到 SVM:

  • 原始可行性:y_i(w·x_i + b) ≥ 1,α_i ≥ 0。
  • 平稳性:w = Σ α_i y_i x_i,Σ α_i y_i = 0。
  • 对偶可行性:α_i ≥ 0。
  • 互补松弛:α_i [y_i(w·x_i + b) - 1] = 0。

第四条是全部价值所在。它把样本分成两类,α_i = 0的(非支持向量)和α_i > 0的(支持向量)。而后者必须满足y_i(w·x_i + b) = 1。

这个等式给出了b的计算方法:随便挑一个α_i > 0的样本,b = y_i - w·x_i。理论上任何一个支持向量都能算出同样的b。实际实现里通常会取所有自由支持向量的平均值,因为浮点误差会让每个算出来的b略有不同,取平均更稳。

我试过只用第一个支持向量算b,在某些病态数据上偏差能到1e-2量级,取平均之后降到1e-10以下。这个差别在测试集上可能不明显,但如果你要做 KKT 违反度检查,偏差大了会误判。

5.6 软间隔:C 是怎么进场的

软间隔引入松弛变量ξ_i ≥ 0,原始问题变成:

min ½‖w‖² + C Σ ξ_i,s.t. y_i(w·x_i + b) ≥ 1 - ξ_i, ξ_i ≥ 0。

C是惩罚系数,控制"最大化间隔"和"最小化违反量"之间的权衡。C越大,越不容忍违反;C → ∞退化成硬间隔。

拉格朗日函数:L = ½‖w‖² + C Σ ξ_i - Σ α_i[y_i(w·x_i + b) - 1 + ξ_i] - Σ β_i ξ_i,α_i ≥ 0, β_i ≥ 0。

对ξ_i求偏导:C - α_i - β_i = 0,即α_i + β_i = C。由于β_i ≥ 0,得到α_i ≤ C。这就是上界的来源——它不是人为加的限制,是对ξ的对偶可行性条件推出来的。

把ξ消掉之后,对偶问题变成:

max_α Σ α_i - ½ ΣΣ α_i α_j y_i y_j K_ij,s.t. Σ α_i y_i = 0, 0 ≤ α_i ≤ C。

目标函数和硬间隔完全一样,唯一的区别是多了上界C。这个变化看似微小,但让整个问题在线性不可分数据上变得有解。

软间隔的互补松弛有两组,必须写全:

  • α_i [y_i(w·x_i + b) - 1 + ξ_i] = 0
  • (C - α_i) ξ_i = 0

按α_i的取值分三种情况:

α_i 取值ξ_iy_i f(x_i)样本位置
α_i = 00≥ 1间隔外,分类正确
0 < α_i < C0= 1恰在间隔边界上
α_i = C≥ 0≤ 1间隔内或被误分类

第三种情况里ξ_i可以大于零,这就是为什么α_i = C的样本允许违反间隔。行 2 里ξ_i必须为零,是因为C - α_i > 0,互补松弛逼着ξ_i = 0,再代回第一组条件就得到y_i f(x_i) = 1。

这套分类是调参时的核心依据。你如果发现绝大多数α_i都顶在C上,说明C太小或者数据本身噪声太大,模型基本没学到东西;如果所有α_i都严格在(0, C)内部,说明C给得很大、数据近似线性可分,可以考虑直接用硬间隔的结论。

5.7 核技巧:只有对偶形式才能干的事

对偶问题里,样本只以x_i·x_j的形式出现。这个事实的价值在于:你可以把这个内积整体替换成K(x_i, x_j) = φ(x_i)·φ(x_j),其中φ是一个到高维(甚至无穷维)空间的映射,而完全不用构造φ。

常用的核:

核函数表达式参数适用场景
线性核x_i·x_j无高维稀疏数据,文本分类
多项式核(γ x_i·x_j + r)^dγ, r, d特征交互明显的中低维数据
RBF 核exp(-γ‖x_i - x_j‖²)γ通用默认选择,边界光滑
Sigmoid 核tanh(γ x_i·x_j + r)γ, r少见,参数敏感

为什么只有对偶形式能这么干?因为原始问题里w是显式的,你要做非线性映射就得真的构造φ(x)并把w升到高维,维度爆炸无法承受。对偶形式里w被消掉了,只剩内积,而内积可以用核函数在原始维度上算,成本只跟原始维度有关。

RBF 核对应的φ是无穷维的,这个映射根本没法显式构造,但对偶形式下毫无压力。这就是核技巧的威力,也是 SVM 在深度学习兴起之前长期占主导地位的技术原因。

γ的直观含义是"单个样本的影响半径"。γ越大,exp(-γ‖x_i - x_j‖²)衰减越快,影响半径越小,决策边界越扭曲,容易过拟合;γ越小,边界越平滑,容易欠拟合。

6. 动手实现:简化 SMO 与 KKT 违反度校验

6.1 准备工作:核矩阵与工作变量

SMO 的思路是把一个大 QP 拆成一串只有两个变量的子问题。为什么是两个?因为等式约束Σ α_i y_i = 0必须始终成立,一次只动一个变量会立刻破坏它,动两个才能同时满足等式和箱约束。这是 SMO 选择"两个乘子"的数学理由,不是工程上的随意决定。

每次选取一对(i, j),固定其他所有α,子问题有解析解:

α_j^{new} = α_j - y_j (E_i - E_j) / η,其中η = 2K_ij - K_ii - K_jj。

η的推导值得看一眼:η = K_ii + K_jj - 2K_ij = ‖φ(x_i) - φ(x_j)‖² ≥ 0。它是两个样本在特征空间里的距离平方。如果两个样本在特征空间完全重合,η = 0,子问题退化,无法更新,这时必须跳过这一对。代码里要显式判η ≤ 0的情况。

更新完α_j之后,α_i由等式约束反推:α_i^{new} = α_i + y_i y_j (α_j - α_j^{new})。

裁剪区间由箱约束和等式约束共同决定:

  • 若y_i ≠ y_j:L = max(0, α_j - α_i),H = min(C, C + α_j - α_i)。
  • 若y_i = y_j:L = max(0, α_i + α_j - C),H = min(C, α_i + α_j)。

这两组公式要记牢,写反了会导致不收敛。我自己的记忆方法是:y不同意味着两个乘子在等式约束里符号相反,它们会互相"争夺"总量;y相同则是一起"分享"总量,所以上界受α_i + α_j约束。

偏置b的更新也有讲究:

b1 = b - E_i - y_i(α_i^{new} - α_i)K_ii - y_j(α_j^{new} - α_j)K_ijb2 = b - E_j - y_i(α_i^{new} - α_i)K_ij - y_j(α_j^{new} - α_j)K_jj

如果α_i更新后在(0, C)内部,用b1;否则看α_j,用它对应的b2;两个都被卡在边界上就用(b1 + b2)/2。注意这里的E_i、E_j必须是更新前的值,更新完α之后再算E就错了。

6.2 两个乘子的解析更新与裁剪区间

完整的核心循环逻辑:

import numpy as np def kkt_violated(a, y, E, C, tol): """判断单个样本是否违反 KKT 条件""" r = y * E if a <= 1
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进