十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

线性可分SVM基本型:从最大间隔到支持向量机核心原理

线性可分SVM基本型:从最大间隔到支持向量机核心原理 如果你刚接触机器学习或者已经用过一堆分类模型但一听到别人聊SVM支持向量机就本能地想绕开那我建议你把这篇文章看完。SVM是机器学习里最经典、也最值得吃透的算法之一而“线性可分SVM的基本型”就是这个算法体系里最干净、最核心的入口它没有复杂的核函数也没有软间隔的松弛变量有的只是一条清晰到近乎完美的分类边界。这个模型能解决什么问题简单说就是当两类数据可以被一条直线干脆利落地分开时SVM能找到那条“最稳妥”的分界线让分类结果不仅在训练集上准确在新数据上也不容易翻车。这篇内容特别适合刚学完感知机、想进阶理解支持向量机原理的读者也适合已经会调库但始终没搞懂“间隔”“支持向量”到底是什么的实践派。我会从问题定义、数学推导、代码实现到踩坑记录一起讲透保证你看完能自己从头推一遍公式也能动手复现一个完整的线性SVM。1. 从感知机到最大间隔线性可分SVM到底在优化什么1.1 感知机只能“分对”SVM要“分出一个安全地带”要理解线性可分SVM最好的参照物是感知机。感知机的思路非常直接如果两类样本是线性可分的那就找一条直线把两边分开。问题是这样的直线有无数条感知机随机初始化权重之后只要样本被分对就停止更新最后得到哪条线很大程度取决于初始值和样本顺序。这意味着什么我举个生活化的例子。假设你在一条马路中间要画一条双黄线这条线只要能把左右两边的车流分开就算“正确”。但如果你把线贴着某一侧的车画虽然现在没有压到车旁边一有车辆轻微变道就撞上了。感知机干的就是这件事只要现在没撞上就算成功根本不考虑留不留安全余量。而SVM的做法完全不同它不只要求线把两类点分开还要求这条线离最近的样本点“远远地”离开两边留出尽可能宽的空地。这个“空地”就是间隔marginSVM全名里的Support Vector Machine实际上就是在找这样一组能撑起最大间隔的样本点。那为什么间隔大就能带来更好的泛化可以这样理解模型在训练集上表现好不算本事关键是遇到没见过的数据还能稳住。如果分类边界紧贴着训练样本稍微有点测量误差或者数据抖动边界另一侧的样本就可能被误判。反过来如果边界到两边数据都有足够宽的缓冲带即使新样本有一些随机扰动也大概率落在正确的这一边。1.2 为什么非要从“线性可分”讲起很多初学者上来就学带核函数的SVM结果被RBF、多项式核这些概念搞得一头雾水。这里我强烈建议按顺序走先吃透线性可分SVM再加软间隔处理线性不可分最后用核技巧解决非线性问题。原因很简单线性可分SVM是整个体系的骨架你看不懂这一篇后面所有东西都是在空中楼阁。所谓线性可分严格定义是对二分类数据集 D{(x_1,y_1),(x_2,y_2),...,(x_N,y_N)}其中 y_i∈{-1,1}如果存在一个超平面 w·xb0 能把所有 1 和 -1 样本完全分开这个数据集就是线性可分的。二维空间里这个“超平面”就是一条直线三维空间里是一个平面高维空间里才是真正的超平面。从数学角度线性可分SVM的完整学习路径可以分为四步第一步定义间隔第二步构造优化目标得到基本型第三步通过拉格朗日对偶转化为对偶问题第四步用KKT条件找到支持向量。本文的重点在前三步第四步会解释为什么最终模型只由少数几个训练样本决定。这也是整个SVM理论中最反直觉、也最迷人的一点明明用全部数据训练最后决策时却只认几个“钉子户”。2. 数学推导不劝退间隔公式与基本型是怎么来的2.1 几何间隔而不是函数间隔先解决“缩放污染”问题我们先用一个具体的二维例子来感受一下。假设有个样本点 x(2,3)分类平面是 2x_1 3x_2 - 5 0代入得到 22 33 - 5 8。这个结果说明什么一方面符号为正说明点在平面的某一侧另一方面绝对值大小可以粗略表示点离平面的远近。我把这个值称为“函数间隔”。但问题马上来了如果我把平面的系数从 (2,3,-5) 同时放大到 (4,6,-10)这两个方程描述的根本是同一条直线但代入同一个点得到的结果却从 8 变成了 16。也就是说函数间隔的值会因为系数缩放而任意变大用它来衡量“点到平面距离”是自欺欺人。解决办法是把它归一化除以法向量 w 的模长。回想一下高中数学里点到直线的距离公式有点 (x_0,y_0) 和直线 axbyc0距离是 |ax_0by_0c| / sqrt(a^2b^2)。推广到高维点 x_i 到超平面 w·xb0 的距离就是 |w·x_ib| / ||w||。对于分类正确的样本y_i(w·x_ib) 一定大于 0所以距离可以写成 y_i(w·x_ib)/||w||。这个表达式不再受系数缩放影响因为 w 放大 k 倍分子和分母一起放大 k 倍比值不变这才是真正的“几何间隔”。2.2 从最大间隔到L2范数最小化基本型的完整推导整个数据集到超平面的间隔怎么定义不能取平均值因为SVM关注的是“最差的那个”——离边界最近的那个样本如果都离得够远其他样本自然安全。因此全局间隔是γ min_i y_i(w·x_ib) / ||w||这个式子意味着我们要在所有训练样本里找到距离超平面最近的那个点然后最大化这个最小距离。问题可以写成max_{w,b} γ, s.t. y_i(w·x_ib) / ||w|| ≥ γ, i1,...,N到这里优化目标已经出现但直接求最大值还是别扭因为有个 ||w|| 在分母上。解决办法是用“尺度归一化”这个小技巧。既然 w 和 b 同时缩放不会改变超平面我就可以人为规定离超平面最近的样本的函数间隔刚好等于 1也就是min_i y_i(w·x_ib) 1这个规定不改变问题本质就像全世界用“米”和“英尺”量同一段路长度数值不同但距离一样。把 γ 1/||w|| 代回去最大化 1/||w|| 就等价于最小化 ||w||^2/2。加上约束条件得到SVM的基本型min_{w,b} 1/2 ||w||^2 s.t. y_i(w·x_ib) ≥ 1, i1,...,N约束条件 y_i(w·x_ib) ≥ 1 的含义是每个样本至少落在间隔边界上或者落在间隔边界外侧的正确区域里。等号成立的样本就是所谓的支持向量它们恰好站在间隔边界上。2.3 看一眼基本型凸二次规划意味着什么上面这个优化目标在机器学习里极其友好它是一个凸二次规划问题。“凸”的意思是目标函数只有一个全局最小值不存在一堆局部极小点干扰你初始值随便选最后都能落到同一个结果上。“二次”指的是目标函数里 ||w||^2 是 w 的二次项而约束条件关于 w 和 b 都是线性的。这类问题有非常成熟的数值解法这也是SVM在上世纪九十年代能火起来的重要原因——在深度学习还不发达的年代凸优化给了研究者极大的确定性。这里顺便说一个我早年的误区总以为基本型里的 1/2 是随便写的系数。实际上它纯粹是为了求导方便对 ||w||^2 求导后系数抵消得到 w Σ α_i y_i x_i 这种干净的形式。如果不乘 1/2后面所有求导结果都要多个 2公式就没那么顺眼了。3. 支持向量的真正含义拉格朗日对偶与KKT条件3.1 原始问题不好解对偶问题哪里好基本型已经是一个可以求解的凸优化问题直接用现成的二次规划求解器就能算。那为什么教科书还要花大篇幅讲拉格朗日对偶我总结下来有两个层面的原因。第一个原因是山高皇帝远原始问题的复杂度跟特征维度直接相关。如果特征维度特别高甚至出现维度比样本数还多的情况直接在原始空间里解 w 的计算代价不一定是好消息。而对偶问题在转化后优化变量变成了 α每个样本对应一个 α问题规模只和样本数挂钩不再直接受维度影响。第二个原因更加关键对偶问题里训练样本只以内积 x_i·x_j 的形式出现。这个细节在今天是核技巧的入口是SVM能处理非线性问题的秘密通道。你后面学的所有核函数本质都是把这个内积替换成某种高维映射的内积而整个过程在数学上完全不需要真的去算高维特征。可以说不对偶无核技巧。3.2 拉格朗日函数与求偏导五六行就能推完我一度觉得拉格朗日对偶很抽象但亲手推过一遍之后发现步骤其实非常机械。先构造拉格朗日函数把约束条件用拉格朗日乘子 α_i 吸收进目标函数L(w,b,α) 1/2 ||w||^2 Σ_{i1}^N α_i [1 - y_i(w·x_ib)]其中 α_i ≥ 0。注意方括号里就是“约束的违反程度”如果某个样本离间隔边界还远这个值是个负数离得越近越接近 0违反约束时会变成正数。原始问题的最小化会让这些违反项尽可能小。对 w 求偏导并令其等于 0∂L/∂w w - Σ α_i y_i x_i 0得到 w Σ α_i y_i x_i。对 b 求偏导并令其等于 0∂L/∂b -Σ α_i y_i 0得到 Σ α_i y_i 0。这两个结果代回拉格朗日函数一顿整理之后得到对偶问题max_α Σ_{i1}^N α_i - 1/2 Σ_{i1}^N Σ_{j1}^N α_i α_j y_i y_j (x_i·x_j) s.t. α_i ≥ 0, Σ_{i1}^N α_i y_i 0推导过程看起来有不少项但细心点会发现交叉项恰好能把拉格朗日函数里的 -1/2||w||^2 抵消掉相当一部分留下的正是这个形式。这个对偶问题依旧是个凸二次规划求解它得到一组最优的 α然后通过 w Σ α_i y_i x_i 还原出权重。3.3 KKT条件为什么模型只剩下一群“钉子户”对偶问题解出来后真正决定模型长相的是KKT条件。SVM的KKT条件里有一条互补松弛条件α_i [1 - y_i(w·x_ib)] 0, i1,...,N这条式子揭示了真相对每个样本要么 α_i 0要么 1 - y_i(w·x_ib) 0即 y_i(w·x_ib)1样本正好落在间隔边界上。如果 α_i 0这个样本在 w 的表达式 w Σ α_i y_i x_i 里贡献是零对模型毫无影响。只有 α_i 0 的样本参与决定 w 和 b而这些样本就是所谓的支持向量。这也是SVM名字的由来——模型不是靠全体训练样本撑起来的而是靠少数几个边界样本“支持”起来的。训练完成后绝大多数训练样本都可以丢弃保留的只有支持向量和对应的 α 值。预测新样本时只需要计算 w·xb而 w 只是支持向量的线性组合所以计算量也不大。我经常跟朋友开玩笑说SVM像极了现实里的评审团几百号人参加选拔最后说了算的就那么几个“硬核评委”其他人投不投票结果都一样。4. 5分钟实战用Scikit-learn复现线性可分SVM4.1 数据准备别跳过标准化这一步理论讲再多不如在代码里跑一遍来得实在。我们的目标是用一个二维的线性可分数据集训练一个线性核SVM然后把支持向量、决策边界都画出来。先构造数据import numpy as np import matplotlib.pyplot as plt from sklearn.svm import SVC from sklearn.preprocessing import StandardScaler from sklearn.datasets import make_blobs X, y make_blobs(n_samples80, centers2, n_features2, cluster_std1.2, random_state42) y 2 * y - 1 # 把标签转成 -1 和 1方便对应公式上面的 y 原本是 0 和 1我把它们映射成 -1 和 1这样和理论部分的符号保持一致。接下来最关键的一步是标准化scaler StandardScaler() X_scaled scaler.fit_transform(X)这个步骤我见过的坑太多了。SVM的优化目标依赖样本间的距离和范数如果两个特征的量纲不同比如一个在 0 到 1 之间另一个在上千级别几何间隔就会被“大数值”特征完全主导小数值特征即使信息量再大也起不到作用。标准化把每个特征都拉成均值 0、标准差 1保证所有特征在距离计算里公平竞争。4.2 用SVC实现硬间隔并画出决策边界训练模型就用一行代码svc SVC(kernellinear, C1e10, random_state42) svc.fit(X_scaled, y)这里 C 设为很大的值就是为了逼近硬间隔SVM也就是本文推导的“基本型”场景。C 在软间隔里控制对误分类的惩罚力度C 越大越接近严格不犯错。实际训练时 C1e10 已经够大但不建议再往上堆容易引发数值问题。画图的部分我习惯先在网格上生成足够密的点然后调用 decision_function 得到每个点到超平面的函数值再用等高线画出决策边界和间隔边界xx, yy np.meshgrid(np.linspace(X_scaled[:, 0].min()-0.5, X_scaled[:, 0].max()0.5, 500), np.linspace(X_scaled[:, 1].min()-0.5, X_scaled[:, 1].max()0.5, 500)) Z svc.decision_function(np.c_[xx.ravel(), yy.ravel()]) Z Z.reshape(xx.shape) plt.contourf(xx, yy, Z, levels[-1, 0, 1], colors(gray, white, gray), alpha0.3) plt.contour(xx, yy, Z, levels[-1, 0, 1], colors(red, black, blue), linewidths(0.8, 2.0, 0.8)) plt.scatter(X_scaled[:, 0], X_scaled[:, 1], cy, cmapbwr, edgecolorsk) plt.scatter(svc.support_vectors_[:, 0], svc.support_vectors_[:, 1], s120, facecolorsnone, edgecolorsgreen, linewidths2) plt.show()画出来的图应该一目了然黑色实线是决策边界红色和蓝色虚线是间隔边界绿圈标出的就是支持向量。你会看到所有绿圈都准确地站在间隔边界上不多不少。4.3 从训练结果里读出w、b和支持向量训练完模型直接打印几个关键属性print(w:, svc.coef_) print(b:, svc.intercept_) print(支持向量索引:, svc.support_) print(每类支持向量数:, svc.n_support_) print(支持向量:, svc.support_vectors_)输出会类似w: [[1.60773411 1.2874529]] b: [-0.15899044] 支持向量索引: [12 23 34 59 67] 每类支持向量数: [2 3]coef_ 就是理论中的 w 向量intercept_ 就是 b。用 decision_function 计算样本分数时就是按照 w·xb 来的。支持向量索引对应原始训练集中的位置每类支持向量数告诉我们模型被每一类中的几个“钉子户”支撑了起来。这里我特别建议做一个验证手动计算某个支持向量到决策边界的距离。对标准化后的数据支持向量满足 |w·xb| / ||w|| 1/||w||也就是间隔值。你随便挑一个 support_vectors_ 里的点手动代入 w 和 b算出来的函数间隔应该无限接近 1 或 -1实际会有极小的数值误差这是浮点运算的普遍现象。5. 实操中的常见问题与避坑记录5.1 特征尺度差异导致的“决策边界偏移”这个问题在上一节提过但值得单独再讲一次。我自己刚学SVM时有一组真实业务数据两个特征分别是人次几百到几千和转化率0.01 到 0.3不标准化直接训练结果决策边界几乎只由“人次”这个维度决定转化率维度在距离计算里像不存在一样。画出来的决策边界垂直于人次轴模型在验证集上效果很一般。解决办法就是标准化而且要注意必须先用训练集的均值和标准差去标准化测试集不能把测试集混在一起算否则会引入未来信息评估结果虚高。5.2 ConvergenceWarningC太大导致的不收敛如果你把 C 设成 1e15训练时很可能收到收敛警告。原因是C非常大时优化问题对误分类的惩罚极其严厉数值求解器需要更小心地逼近最优解迭代次数不够就触发提前停止。处理办法有几种一是把 max_iter 调大比如从默认的 -1无限制改成 5000二是把 tol 适当放宽比如从 1e-3 调到 1e-4三是把 C 降到一个既能保证硬间隔效果、数值又稳定的范围比如 1e6 到 1e10。如果你用的是 LinearSVC还有个常见坑LinearSVC 的 C 和 SVC 的 C 在数学形式上不完全一样LinearSVC 用的是平方合页损失的正则化形式直接照搬调参经验经常不对付。5.3 判断数据是否“线性可分”的直观方法有的读者会问我怎么知道手里的数据到底是不是线性可分最直观的方法是降维可视化二维数据直接画散点图看两个类别之间能不能画一根清晰的线。三维数据可以旋转视角观察。高维数据没法直接看可以用一个“探路”办法先用线性SVM或逻辑回归训练如果训练集准确率已经接近100%说明数据在特征空间里很可能是接近线性可分的如果训练集准确率远未饱和说明特征表达还不够好。另一种情况更隐蔽数据整体可分成几大簇但簇之间有少量点穿到了对面阵营这就是“近似线性可分”。这种场景单纯用硬间隔SVM会过拟合那些噪声点正确做法是下一篇文章要讲的软间隔SVM让模型允许少量错分换取更大的间隔和更好的泛化。5.4 决策边界画出来总觉得“歪”如果你发现决策边界的角度和直觉不符先别怀疑算法检查两个地方。第一确认特征是否标准化了第二确认类别标签的顺序。SVM 的符号约定里y_i 取 1 和 -1如果标签顺序反了decision_function 输出符号会整体反转但这不影响分类准确率只是边界上的间隔符号对不上容易让人看错。另外如果训练集中有聚类中心偏移特别大的簇边样本哪怕它们离全局边界很远也可能因距离度量问题被误判为支持向量。观察支持向量的分布如果绿圈密集集中在一侧数据分布可能不平衡这个留到后续做类别权重时再细说。6. 学完基本型之后下一步往哪走6.1 线性不可分软间隔与松弛变量线性可分SVM的硬间隔假设非常优雅但现实世界几乎不会有那么“听话”的数据。总有个别样本会跑到对方阵营里去或者紧贴着边界捣乱。为了处理这种情况SVM引入了松弛变量 ξ_i允许部分样本的函数间隔小于1同时目标函数里加上对松弛量的惩罚。这就是软间隔SVM也就是 SVC 里 C 参数的真正意义所在。我在评估一个模型时如果看到支持向量全都在间隔边界上一个越界的都没有反而要警惕是否过拟合了。真正在业务数据上表现好的SVM往往允许少量“不可控因子”存在这就是为什么我建议即使在线性可分的数据上也别死磕 C 极值。6.2 核技巧对偶形式里埋下的伏笔回顾一下对偶问题的目标函数它只涉及 x_i·x_j 这种内积。如果我们想在高维空间里做线性分类最直接的办法是把每个样本 x_i 先映射成 φ(x_i)然后在高维空间里做线性SVM。可是维度一旦变得非常大直接计算 φ(x_i)·φ(x_j) 的代价会爆炸。而核函数 K(x_i,x_j) φ(x_i)·φ(x_j) 可以直接算出一个数值等价于高维空间的内积完全绕过显式映射。这件事能在数学上成立根源就在于对偶形式里只有内积没有单独的 x_i。这就是为什么我在标题里强调“基本型”因为后续所有扩展结论都可以从这一篇的推导出发。6.3 想真正吃透SVM建议按这个顺序练手第一步手动推导不翻书从间隔定义开始推导出基本型再拉格朗日对偶到对偶问题最后列出KKT条件。这步熟练后SVM对你就不再是黑盒。第二步用Python自己实现一个简化版的线性SVM训练器可以用现成的CVXOPT求解对偶问题也可以自己写SMO算法的简化版本。第三步回到Scikit-learn用 SVC 验证自己实现的结果对比 coef_ 和 support_ 是否一致。我自己的体会是只看推导和只调库都不够。推导能让你理解模型为什么有效调库能让你知道模型在工程里有什么脾气。两者结合之后再遇到“为什么SVM在新数据上不稳定”“为什么决策边界长这样”这类问题你会有一种“一切尽在掌握”的踏实感。最后分享一个小技巧想验证自己是否真的理解了线性可分SVM就试着预测一下“把C从1e3调到1e6支持向量的数量会怎么变”。想明白这个你离彻底掌握SVM就不远了。
返回列表