资讯详情

资讯详情

支持向量机拉格朗日对偶详解:从KKT条件到核函数落地

支持向量机这块内容很多教程一上来就堆公式拉格朗日、KKT、对偶问题符号满屏飞最后读者只记住了一个名字完全不知道这东西到底在干什么。我当年学的时候也是这样对着推导过程看了三遍感觉每一步都懂合上书又什么都想不起来。后来自己动手把整个推导从头走了一遍再把对偶形式拿来做了几个小实验才算真正摸到门道。这篇文章准备把拉格朗日对偶法这部分掰开了讲重点不是把公式抄一遍而是解释每一步为什么要这么做做完之后得到了什么以及这些结论后来在核函数、软间隔这些方向上是怎样被用起来的。适合刚学完SVM基本概念、准备进入优化理论部分的读者也适合学过但当时没太想明白、想回头再捋一遍的人。1. 原始优化问题为什么非要对偶1.1 硬间隔SVM到底在求什么先回到SVM最原始的出发点。我们手里有一组训练样本每个样本有特征向量和类别标签任务是找一个超平面把两类数据分开并且让这个分界面的“安全余量”尽可能大。超平面写成的形式是w·x b 0对一个样本点的分类置信度用函数间隔来表示。为了让这个间隔的度量不随w的缩放而改变我们把它归一化成几何间隔。硬间隔SVM直接假设数据是线性可分的这时最理想的超平面是让离分界面最近的那个样本点到超平面的距离最大。把约束条件做一次归一化令所有样本的函数间隔至少为1优化目标就写成min 1/2 ||w||^2 s.t. y_i (w·x_i b) 1, i 1, 2, ..., n这里系数1/2纯粹是为了后面求导方便。目标函数是二次的约束是线性的所以这是一个凸二次规划问题理论上可以直接用现成的优化工具包求解。既然能直接解为什么还要费劲巴拉去构造拉格朗日函数转成对偶问题1.2 直接求解的麻烦在哪里直接解原始问题会碰到几个实际问题。第一个是维度灾难。w的维度跟特征维度一致如果特征维度是几万甚至更高直接在原始空间求解就非常吃力。第二个更关键如果只用线性超平面SVM的表达能力非常有限遇到非线性分界的数据根本没法处理。当时的研究者们已经意识到如果把样本映射到高维空间线性分类器就有机会变成非线性分类器。但高维映射带来一个新的问题映射之后的维度可能高到无法计算甚至趋于无穷维。这条路看起来走不通。但拉格朗日对偶提供了一个转机。把原始问题转成对偶问题之后优化变量从w和b变成了拉格朗日乘子αα的个数等于样本数量跟特征维度没关系。更重要的是目标函数里只出现样本之间的内积形式x_i·x_j。这意味着我们不需要显式知道映射后的坐标只需要知道映射后的内积结果。这个性质直接催生了核函数技巧。所以对偶不仅仅是一个数学技巧它实际上是SVM能够扩展到非线性情形的关键一步。没有这一步SVM可能只是一个普通的线性分类器不会成为后来那么经典的工具。2. 拉格朗日乘子法与KKT条件2.1 约束优化问题怎么处理回忆一下高等数学里的条件极值问题。要求在约束g(x)0下最小化f(x)做法是构造拉格朗日函数L(x,λ)f(x)λg(x)然后对x求导并令导数为零。这个思路可以推广到不等式约束。SVM的约束是1 - y_i(w·x_i b) 0这是不等式约束。对每个不等式约束引入一个拉格朗日乘子α_i并且要求α_i 0写成拉格朗日函数L(w, b, α) 1/2 ||w||^2 - Σ_i α_i [ y_i (w·x_i b) - 1 ]注意这里用的是减号配合α_i 0的约束。这样设计的原因是如果某个样本点满足了约束也就是y_i(w·x_ib) 1那么括号里是非负的α_i又有下界这一项在最大化过程中不会让L无限增大。2.2 原始问题与对偶问题的关系把拉格朗日函数看作是关于w、b的函数先固定α对w和b求最小然后再对α求最大。这样一个“先min后max”的结构构成了原始问题的等价形式min_w,b max_α L(w,b,α), s.t. α_i 0数学上还有一个“先max后min”的形式也就是对偶问题max_α min_w,b L(w,b,α), s.t. α_i 0一般情况下对偶问题的解不会大于原始问题的解这叫弱对偶。但对于SVM这种目标函数是凸函数、约束是线性函数的特殊情况强对偶成立两个问题的最优值相等。这意味着我们可以放心地去解那个更容易处理的对偶问题。2.3 KKT条件为什么重要强对偶成立时最优解还必须满足一组条件叫KKT条件。SVM情形下这些条件包括原始变量梯度为零的条件、原约束可行条件、对偶变量非负条件以及最关键的互补松弛条件α_i [ y_i (w·x_i b) - 1 ] 0这个式子的含义非常深刻。对于绝大多数样本点括号里的约束是严格大于零的也就是这些点离分界面比较远为了让乘积等于零必须令对应的α_i 0。只有当某个样本恰好落在间隔边界上也就是y_i(w·x_ib) 1时α_i才可以取正值。这些α_i不为零的样本点就是支持向量。它们决定了最终的分界面其余样本点无论怎么移动只要不穿越间隔边界对模型没有任何影响。这个性质在实际使用中很有价值我在做大规模数据分类时经常利用这个特点进行数据筛选删掉那些明显远离分界面的点对模型效果几乎没影响但训练速度能快不少。3. 拉格朗日对偶的完整推导过程3.1 对w和b求偏导整个推导的前半部分属于纯机械操作。把拉格朗日函数分别对w和b求偏导令它们等于零∂L/∂w w - Σ_i α_i y_i x_i 0 ∂L/∂b - Σ_i α_i y_i 0由第一个等式得到w Σ_i α_i y_i x_i这说明最终分类超平面的法向量w是训练样本的线性组合组合系数是α_i y_i而且只有支持向量对应的α_i才非零。由第二个等式得到Σ_i α_i y_i 0这个约束在对偶问题里会保留下来。3.2 对偶函数的形式把上面两个结果代回拉格朗日函数。这里需要一点代数耐心但每一步都是直来直往的代入展开。先处理第二项Σ_i α_i y_i (w·x_i b) w·(Σ_i α_i y_i x_i) b(Σ_i α_i y_i)中间括号里正是w最后一项又因为Σα_i y_i 0而消失所以第二项整体就等于w·w ||w||^2。于是拉格朗日函数变成L 1/2 ||w||^2 - ||w||^2 -1/2 ||w||^2再把w Σα_i y_i x_i代入得到L -1/2 Σ_i Σ_j α_i α_j y_i y_j (x_i·x_j) Σ_i α_i为了求最大值时符号好看通常写成对偶问题的目标函数形式max_α Σ_i α_i - 1/2 Σ_i Σ_j α_i α_j y_i y_j (x_i·x_j) s.t. α_i 0, Σ_i α_i y_i 0这就是教科书里常见的SVM对偶形式。3.3 b怎么求对偶问题解出α之后w可以根据w Σα_i y_i x_i直接算出来但b没法从对偶问题里直接得到。b需要利用KKT条件反推。对于任何一个支持向量也就是满足α_k 0的样本点有y_k (w·x_k b) 1两边同乘y_k因为y_k² 1得到b y_k - w·x_k严格来说任意一个支持向量都能算出一个b值。数值稳定性更好的做法是对全部支持向量求出来的b取平均。我在实际代码里习惯用这个平均方式。单个支持向量计算时如果特征分布存在轻微异常可能导致b有微小偏差平均之后结果更稳。3.4 从对偶解到预测函数预测时不需要显式存储w。对于一个新样本z分类决策写成f(z) sign( Σ_i α_i y_i (x_i·z) b )这里又出现了样本之间的内积。这个形式的重要性在后面会体现出来一旦把内积替换成核函数整个算法就能自然地处理非线性问题而前面推导的所有过程都不需要改动。4. 对偶形式与核技巧的关系4.1 为什么对偶形式催生了核函数原始问题的优化变量是ww的维度跟特征空间维度一致。如果我们想用高维映射把x映射成φ(x)那w的维度就是φ空间的维度很可能高得没法直接优化。但对偶问题的优化变量是α维度等于样本数量。目标函数里出现的只是φ(x_i)·φ(x_j)这样的内积。如果我们能找到一个二元函数K使得K(x_i, x_j) φ(x_i)·φ(x_j)那么在求解对偶问题和做预测的时候就完全不需要知道φ的具体形式只需要会算K。这个操作就是核技巧。4.2 常见核函数怎么选实际用得最多的几个核函数线性核K(x,z) x·z等价于不映射适合特征维度高、样本量大的场景。多项式核K(x,z) (x·z c)^d能表达有限维的非线性映射。高斯径向基核K(x,z) exp(-γ||x-z||²)对应无限维映射是最常用的默认选择。高斯核里那个γ参数值得多说两句。γ越大高斯函数的“尖峰”越窄每个训练样本的影响范围越小模型越容易过拟合γ越小模型越平滑但太小了又可能欠拟合。我一般先用默认值然后围绕默认值做log尺度搜索比如默认值是0.1就试0.01、0.1、1这三个量级。4.3 核函数的计算开销很多人担心核矩阵的存储和计算。核矩阵是n×n的矩阵n是样本数。样本上万的时候这个矩阵就上百MB了计算代价也很可观。我踩过的一个坑是直接用高斯核处理几万样本内存直接爆了。后来改成先用线性核跑一版作为baseline确认非线性确实能带来提升再上高斯核并且把样本做一次下采样或者用mini-batch方式。日常做实验时如果样本量超过两万我一般会优先考虑线性SVM或者换用近似核方法。5. 软间隔与惩罚系数C5.1 硬间隔为什么不够用硬间隔要求所有样本都满足约束y_i(w·x_ib) 1这在真实数据里几乎不可能满足。数据里总会有噪声、标注错误或者两类样本天然重叠。强行要求严格可分会导致分界面被一两个异常样本带偏泛化能力反而很差。解决办法是允许一部分样本违反间隔约束但违反得越厉害代价越大。引入松弛变量ξ_i 0把约束放宽成y_i (w·x_i b) 1 - ξ_i目标函数里加上惩罚项min 1/2 ||w||^2 C Σ_i ξ_iC是惩罚系数控制“模型复杂度”和“误分类容忍度”之间的平衡。5.2 软间隔的对偶形式引入松弛变量之后重新构造拉格朗日函数会多出来一组关于ξ的乘子。推导过程和硬间隔几乎一样只是α_i的范围从α_i 0变成了0 α_i C。这个约束在实际调参中非常直观。C越大允许的松弛越小模型越倾向于让训练集完全正确但可能过拟合C越小模型越平滑但可能欠拟合。5.3 调C的实操心得我调C的习惯是先在对数尺度上粗搜比如从0.001到1000每隔10倍取一个值。每轮训练用交叉验证看结果找出表现较好的区间再在那个区间内细搜。有一次处理一个信用评分数据集正负样本比例悬殊直接调C怎么都不太理想。后来发现问题出在类别不平衡上把类别权重打开让少数类的错误代价更高之后效果立刻就好了。所以调C之前先确认一下数据本身的分布有没有问题。6. 一个二维小实验动手走一遍完整流程6.1 实验设计与数据准备理论推导再完整不动手算一遍总觉得不踏实。我准备了一个简单的二维分类数据线性可分故意只挑少数几个点算一遍把整个流程串起来。假设有四个样本点样本x1x2标签12212241342-1454-1这几个点在二维平面上的分布是两个正类在左上方偏聚两个负类在右下方偏聚大致能用一个斜线分开。6.2 Python代码实现直接写一个最小实现核心逻辑是自己定义二次规划求解工具还是用现成库来做。import numpy as np from cvxopt import matrix, solvers # 训练数据 X np.array([[2, 2], [2, 4], [4, 2], [5, 4]], dtypefloat) y np.array([1, 1, -1, -1], dtypefloat) n_samples, n_features X.shape # 构造对偶问题min 1/2 α.T Q α - 1^T α # Q_ij y_i y_j x_i·x_j K np.dot(X, X.T) Q np.outer(y, y) * K Q matrix(Q) # 线性项 p matrix(-np.ones(n_samples)) # 约束α 0 G matrix(-np.eye(n_samples)) h matrix(np.zeros(n_samples)) # 等式约束Σ α_i y_i 0 A matrix(y.reshape(1, -1), (1, n_samples), d) b matrix(0.0) solvers.options[show_progress] False solution solvers.qp(Q, p, G, h, A, b) alpha np.array(solution[x]).flatten() print(alpha:, alpha)运行之后得到的结果是α只在两个样本上非零恰好是间隔边界上的那两个点另外两个点的α归零。这跟KKT条件的预期完全一致。6.3 通过结果验证KKT条件根据对偶解继续求w和bw np.sum(alpha * y * X.T, axis1) support_vectors np.where(alpha 1e-5)[0] b_values [] for i in support_vectors: b_value y[i] - np.dot(w, X[i]) b_values.append(b_value) b np.mean(b_values) print(w:, w) print(b:, b)决策函数对新样本分类只需要def predict(x): score np.dot(w, x) b return np.sign(score)把w解出来之后可以画一条分界线直观看到它确实位于两类样本的最中间。这个小实验做完之后我建议你也亲手敲一遍。这个流程走通了后面扩展到核函数只需要把内积X[i]·X[j]替换成K(X[i], X[j])整个框架完全不用动。7. 常见问题与排查技巧7.1 为什么求出来的α很多都是0一对正常训练下来的SVM得到的α应该是稀疏的大部分样本对应的α是0只有少数支持向量的α非零。这是符合预期的。但是如果你发现α几乎没有一个为0那就有问题了。我遇到过几种情况。一种是C设得太大软间隔实际上退化成硬间隔模型为了照顾个别异常点不得不让很多样本都变成支持向量。另一种是数据中存在大量噪声分界面被挤得七扭八歪导致大量样本落在间隔边界附近。这时候需要检查数据质量而不是急着调参。7.2 高斯核训练慢怎么办高斯核需要计算每个样本对之间的核值复杂度是O(n²)样本量过万之后速度会明显下降。我有一次处理四万条样本用默认参数跑高斯核SVM训练花了将近四十分钟内存也吃紧。后来调整了思路先做特征标准化再抽一万条样本快速确定合理的C和γ范围然后逐步增加样本量微调。最终模型的效果没差多少但训练时间缩短到几分钟。如果你的业务场景对推理延迟有要求还可以把支持向量导出单独做一层缓存避免每次都重新算决策函数。7.3 训练结果全是一个类别怎么办模型把全部样本都预测成多数类通常是两类样本比例悬殊导致的。SVM在类别不平衡时决策边界会偏向少数类一侧严重时少数类完全被吞掉。解决方案有两个方向。一个是调整类别权重让少数类的错误代价更高另一个是调整C或者在训练前做简单的过采样/欠采样。我实际对比下来调整类别权重最简单直接大多数开源工具都内置了这个参数。7.4 决策边界画出来歪歪扭扭决策边界出现锯齿状扭曲多半是核函数太强或者C太大模型在训练集上挖得太细把噪声也学进去了。这种情况交叉验证分数会暴露出来训练集分数接近满分验证集分数掉得厉害。解决办法是调大C或者降低γ让决策边界更平滑。如果用的多项式核也可以适当降低阶数或者调大常数项c让低阶成分占比更高。如果还不行回到数据层面看看是不是特征没有做标准化。径向基核函数极度依赖样本距离特征量纲不一致的话数值范围大的特征会完全主导核值计算导致分类效果很差。8. 实操中值得记住的几个体会先说一个最让我印象深刻的点。拉格朗日对偶出来的结果直接决定了SVM模型的稀疏性。α的非零项对应的那些样本才是真正影响决策边界的数据。这就解释了为什么SVM在小样本、高维场景下表现很好因为它本质上是在用少数关键样本代表整个数据集。第二个体会是内核技巧的威力不在推导过程本身而在于它把“特征映射”和“相似度计算”解耦了。你完全可以用自定义的核函数来表达领域知识只要它满足核函数的数学条件。我见过有人用编辑距离做DNA序列分类的核函数也有人用子串匹配核做文本分类效果都比普通特征工程要好。最后一个建议SVM的数学推导确实有门槛但不要卡在推导上不动。先把代码跑通把支持向量可视化出来把C和γ对决策边界的影响直观感受一遍再回头推导公式你会发现每一步都有了具体的含义。我自己就是这个学习顺序比一开始硬啃公式效率高得多。对偶问题这部分是整个SVM理论中最值得花时间的地方理解了它后面不管接触什么变种稀疏化方法、结构化预测、还是核方法的各种进阶应用都能很快抓住本质。
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →