查看全课大纲(第 49 / 82 节)
- 1.函数与数据泛化
- 2.向量:从几何直觉到高维空间
- 3.矩阵与张量
- 4.特征值与特征向量:矩阵变换的不动方向
- 5.凸函数:从向量求导的矩阵记号到好优化的函数形状
- 6.概率与统计:机器学习的数学基础
- 7.独立性与随机变量:贝叶斯公式与二项分布
- 8.期望、方差与协方差:随机变量的数字画像
- 9.联合概率:当一条轴不够用的时候
- 10.概率分布(一):从伯努利到伽马函数
- 11.概率分布(二):贝塔、泊松、正态与两个贝叶斯故事
- 12.机器学习的模型:找输入与输出的关系
- 13.机器学习的应用
- 14.过拟合
- 15.机器学习的分类
- 16.有监督学习:从房价预测说起
- 17.损失函数
- 18.梯度下降(上):更新规则与学习速率
- 19.梯度下降(2)
- 20.牛顿方法
- 21.KNN简介
- 22.距离测算
- 23.朴素贝叶斯
- 24.朴素贝叶斯的实际应用
- 25.信息熵:决策树构建的度量基础
- 26.信息增益与决策树的构建
- 27.ID3算法
- 28.决策树节点的划分
- 29.CART树
- 30.Bagging
- 31.Boosting:自适应提升算法
- 32.Gradient Boosting
- 33.线性模型:回归与分类的同一副骨架
- 34.最小二乘:把最优参数直接解出来
- 35.最小二乘法的几何与概率视角
- 36.极大似然:给最小二乘一个概率出身
- 37.逻辑回归(1):把线性模型接到分类上
- 38.逻辑回归(2):损失函数与多分类
- 39.---- 手造数据: 5 个样本, 2 个特征; x2 几乎 = 2*x1 (偏差手工指定, 无随机) ----
- 40.讲师例: 向量 "23014" -- 三把范数尺子量同一个向量
- 41.优化方法(1):坐标下降与前向选择
- 42.优化方法(2):前向策略与最小角回归
- 43.拉格朗日乘子法(1):从无约束到等式约束
- 44.拉格朗日乘子法(2):等式约束下的平行条件与拉格朗日函数
- 45.拉格朗日乘子法(3):不等式约束与 KKT 条件
- 46.最大间隔:把分类问题变成优化问题
- 47.拉格朗日对偶问题:把 w 和 b 全部换成 α
- 48.支持向量:边界上的少数派
- 49.SVM 计算过程:一笔到底算完,再走进核方法
- 50.贝叶斯公式:概率即信念
- 51.隐含变量和概率模型:给看不见的原因建模
- 52.EM 算法:期望与最大化的迭代
- 53.琴生不等式:EM 的数学钥匙
- 54.三个硬币问题:EM 的复盘与衔接
- 55.高斯混合模型:用隐变量解释双峰数据
- 56.隐马尔可夫模型
- 57.隐马尔可夫模型(2):前向算法与动态混合模型
- 58.隐马尔可夫模型在 NLP 中的运用
- 59.EM 算法:把「为什么收敛」推到底
- 60.HMM 在 NLP 中的运用:从分词到机器翻译
- 61.HMM 参数估计:Baum-Welch 的三块拉格朗日
- 62.高维下的高斯模型
- 63.主题模型:从词袋到隐含主题
- 64.Dirichlet 分布(1)
- 65.Dirichlet 分布(2)
- 66.统计学习内容回顾
- 67.K-means 算法
- 68.PCA 降维
- 69.SVD 降维
- 70.从聚类到流形
- 71.局部线性嵌入
- 72.随机邻域嵌入:SNE 与 t-SNE
- 73.无参模型:让数据自己说话
- 74.高斯过程:函数空间里的正态分布
- 75.高斯过程(2):噪声、正则化与核的线性组合
- 76.神经网络简介:从感知器到自编码器
- 77.反向传播:把误差一层层传回去
- 78.卷积神经网络(CNN):让特征自己学出来
- 79.循环神经网络(RNN):给网络装上记忆
- 80.深度学习回顾:静态一类,动态一类
- 81.增强学习(1):蒙特卡洛、树搜索与 Q-learning
- 82.强化学习(2):从乌鸦吃核桃到井字棋的 value 回传
SVM 计算过程:一笔到底算完,再走进核方法
约 48 分钟
小象实战讲义 · 机器学习算法精讲
前三节把 SVM 的理论全部搭好了:最大间隔、对偶形式、支持向量。这一节讲师做了一件最踏实的事——拿出一个真正的 SVM 问题,从头到尾用手算完:先用”高中数学”直观地解一遍,再用对偶形式解一遍,两条路到达同一个答案。而算完之后的追问才是真正的压轴:笔算都能解,那我们搞对偶形式到底是为了什么?——答案藏在对偶形式的长相里:它每一块都是内积,而内积,正是通往核方法(处理线性不可分数据)的那扇门。
💡 核心导读
- 直观解法:三点例 (3,3)、(4,3)、(1,1),消掉 、代入消元、求导置零——高中数学解出最优边界。
- 对偶解法:把例子代进对偶形式,展开双重求和、用约束消元、分 case 试探,解出 。
- 殊途同归:对偶解恢复出 、,与直观解分毫不差;—— 不是支持向量。
- 对偶的真正红利:整个对偶形式里,数据只以内积 出现——把它换成核函数 ,就得到 Kernel 方法。
- 核方法初见:二维里怎么都分不开的四组点,用多项式核映射到三维,一个平面干干净净分开。
一个手算可解的 SVM:三点例
讲师开场先抛出那个悬了两节的问题——求最大边界背后的直觉到底是什么——然后话锋一转:SVM 很难算,“这是我不记得是哪本书上的例子,我觉得挺好的,通过一个例子可以给大家更多的理解”。数据三个点:
- 正类():、
- 负类():
画在平面上,两类隔着一大片空地。讲师先给了直觉预告:离边界最近的点, 要大于 0;而绿色的 ,“大家能想象得到,它还不太可能在边界上”。这是一个特别简单的问题——我们自己就可以把边界算出来。当然,不能就这么糊弄过去:我们要认认真真地把它算出来。
直观解法:高中数学就够了
先不走对偶,按定义来。边界最大 模最小 优化 。二维里 。三个约束逐点写出来(把数代进去要 或 ):
第一个技巧:消掉 。把第三式两边取负(),再与第一式相加—— 和 正负约掉,、右端 (讲师的口算:“1 减负 1 是 2”):
第二个技巧:代入消元。最优解一定贴住 (和一旦超过 1,平方和只会更大),于是 ,目标变成单变量:
(讲师:“这就是普通的高中数学,大家不要有什么问题。“)求导置零:;由对称性 。代回第一式:。最优超平面:。 用代码验一遍账:
# 直观解 w=(1/2,1/2), b=-2 验证三约束
import math
w_opt = (0.5, 0.5)
b_opt = -2.0
X = [(3, 3), (4, 3), (1, 1)]
y = [1, 1, -1]
print("直观解 w=(1/2,1/2), b=-2 验证三约束:")
for i in range(3):
f = y[i] * (w_opt[0]*X[i][0] + w_opt[1]*X[i][1] + b_opt)
print(f" x{i+1}{X[i]} (y={y[i]:+d}): y*(w*x+b) = {f:.4f}")
print(f" 超平面: 0.5*x1 + 0.5*x2 - 2 = 0")
print(f" margin = 2/||w|| = {2/math.sqrt(w_opt[0]**2 + w_opt[1]**2):.4f}")直观解 w=(1/2,1/2), b=-2 验证三约束:
x1(3, 3) (y=+1): y*(w*x+b) = 1.0000
x2(4, 3) (y=+1): y*(w*x+b) = 1.5000
x3(1, 1) (y=-1): y*(w*x+b) = 1.0000
超平面: 0.5*x1 + 0.5*x2 - 2 = 0
margin = 2/||w|| = 2.8284、 恰好压在边界上(函数间隔 = 1), 在边界外老远(1.5)。到此为止,直观解完成了使命——但讲师紧接着抛出本节的灵魂设问:“我们直接用这种笔算的形式也可以算,那我们搞那些特别复杂的对偶形式,是为了什么?” 先按下不表,把对偶这条路也走完。
对偶解法:把例子代进去
对偶形式(上一节的成果):,s.t. 、。讲师先给了一个变换自由:求 max 也可以整体取负变成求 min——“这都是可以的”,下面按 min 的口径走。
第一部分 ,三个数据就是 ,没有悬念。第二部分才是重头戏,双重求和逐项展开。先算 (1,1) 项:——;。
这里讲师插了一个对称性小课:拿 三个数演示 的展开——真正的不同项只有 ,而每一个交叉项(、、)都有一个对称的孪生兄弟。所以双重求和里, 这种项会出现两次—— 一次、 一次。接着逐项算内积:
注意 和 这两项前面挂着 : 是负类,所以 ——“因为 3 的类别跟 1、2 不一样,两个 y 相乘一定是负一”。全部加起来:
(交叉项 :、、。)
约束也具体化:( 的 是负的),即 。拿它把 整个替换掉(讲师:“如果有三个 ,可以用 SMO 算法在三维空间里不断固定一个、调整另外两个——数值方法、编程序。但我们这边没法编程序,就用代数把 消掉”):
(讲师:“这个算过好多遍了,应该不会错。”)
先试驻点:,,联立解出:
——违反约束,这个解不对。这正是上一章 KKT 讲过的情形(一维例子 的翻版):驻点不在可行范围内,最优解必然贴在某个边界上——要么 、要么 ,“这两种肯定有一种是正确的”。分 case 测试:
- case A():,求导 ;
- case B():,求导 。
比较两个候选的 值,取小的(我们在求 min):——case B 胜出。最终解:
、—— 果然不是支持向量,讲师开场的直觉预告兑现。最后恢复参数:; 由边界点反解:。全程代码对账:
# 对偶解全程: 驻点违约束 -> 分 case -> 恢复 w, b
X = [(3, 3), (4, 3), (1, 1)] # 同一批三点数据
y = [1, 1, -1]
def S(a1, a2):
# S(a1, a2) = 4a1^2 + 13/2 a2^2 + 10 a1 a2 - 2a1 - 2a2 (已消元 alpha3 = a1 + a2)
return 4*a1**2 + 6.5*a2**2 + 10*a1*a2 - 2*a1 - 2*a2
print("消元后的 S(a1, a2) = 4a1^2 + 6.5a2^2 + 10a1a2 - 2a1 - 2a2")
print()
print("偏导置零的驻点: a1 = 1.5, a2 = -1.0 -> a2 = -1 < 0 违反约束!")
print()
a2_A = 2.0/13.0
print(f"case A (a1=0): 13a2 - 2 = 0 -> a2 = 2/13 = {a2_A:.4f}, S = {S(0, a2_A):.4f}")
a1_B = 0.25
print(f"case B (a2=0): 8a1 - 2 = 0 -> a1 = 1/4 = {a1_B:.4f}, S = {S(a1_B, 0):.4f}")
print(f"S(1/4, 0) = {S(a1_B, 0):.4f} < S(0, 2/13) = {S(0, a2_A):.4f} -> case B 最小, 取 a=(1/4, 0, 1/4)")
print()
alpha = [0.25, 0.0, 0.25]
w = [0.0, 0.0]
for i in range(3):
for d in range(2):
w[d] += alpha[i] * y[i] * X[i][d]
b = y[0] - (w[0]*X[0][0] + w[1]*X[0][1])
print(f"alpha* = {alpha}")
print(f"w* = 1/4*(3,3) + 1/4*(-1)*(1,1) = ({w[0]:.4f}, {w[1]:.4f})")
print(f"b* = y1 - w*x1 = {b:.4f}")
print("与直观解完全一致!")消元后的 S(a1, a2) = 4a1^2 + 6.5a2^2 + 10a1a2 - 2a1 - 2a2
偏导置零的驻点: a1 = 1.5, a2 = -1.0 -> a2 = -1 < 0 违反约束!
case A (a1=0): 13a2 - 2 = 0 -> a2 = 2/13 = 0.1538, S = -0.1538
case B (a2=0): 8a1 - 2 = 0 -> a1 = 1/4 = 0.2500, S = -0.2500
S(1/4, 0) = -0.2500 < S(0, 2/13) = -0.1538 -> case B 最小, 取 a=(1/4, 0, 1/4)
alpha* = [0.25, 0.0, 0.25]
w* = 1/4*(3,3) + 1/4*(-1)*(1,1) = (0.5000, 0.5000)
b* = y1 - w*x1 = -2.0000
与直观解完全一致!两条路,同一个答案:、,超平面 。通过对偶形式,把最好的 解出来;有了 , 和 唾手可得——这就是完整的 SVM 计算过程。
对偶形式换来了什么:全是内积
现在回答那个被按下的设问。回头看对偶形式——讲师指出关键观察:每一块都写成了内积的形式。 是要优化的参数, 就是正一负一,剩下的全部计算,都落在内积 上。所以整个优化方法就变成了:怎么去求这个内积。
核函数由此登场:给它一个记号 ——直接求两者的内积,再通过一个核函数映射到另外一个方向。这一步看似只是换记号,实际打开了一整个新世界——因为我们线性 SVM 有一个与生俱来的前提:数据必须线性可分(“无论要不要最大边界,数据必须可分才行”)。而有些空间里的数据根本不可分——但通过 kernel 的映射,到另外一个空间就变成可分的了。
为什么需要核:二维分不开的四组点
讲师的经典例子(课件 Note_14 同款):紫色四个点 ——内圈;绿色四个点 ——外圈。紫的抱成一团缩在中间,绿的站在四角。在这个平面里,你想找一条直线把两类分开,是不可能的——直觉上的最优边界是一个圆。
解法:构造一个多项式核,把 映射到 :
拿数据走一遍:
# 多项式核映射: 二维不可分 -> 三维线性可分
import math
pur = [(1, 1), (1, -1), (-1, 1), (-1, -1)] # 紫色内圈
grn = [(2, 2), (2, -2), (-2, 2), (-2, -2)] # 绿色外圈
def phi(x1, x2):
return (x1*x1, math.sqrt(2)*x1*x2, x2*x2)
print("多项式核映射 (x1,x2) -> (x1^2, sqrt(2)x1x2, x2^2):")
print("紫色四点:")
for x in pur:
z = phi(*x)
print(f" {x} -> ({z[0]:.0f}, {z[1]:+.4f}, {z[2]:.0f})")
print("绿色四点:")
for x in grn:
z = phi(*x)
print(f" {x} -> ({z[0]:.0f}, {z[1]:+.4f}, {z[2]:.0f})")
print("紫色四个点两两重合成 (1,+-1.4142, 1) 两点; 绿色重合成 (4,+-5.6569, 4) 两点")
print()
ok = all(phi(*x)[0] == 1 for x in pur) and all(phi(*x)[0] == 4 for x in grn)
print(f"分离面 z1 = 2.5: 紫色全部 z1 = 1 < 2.5, 绿色全部 z1 = 4 > 2.5 -> {'一个平面分开' if ok else 'FAIL'}")
print()
print("核技巧对账: K(x,x') = (x.x')^2 vs 三维显式内积 phi(x).phi(x'):")
pairs = [((1,1),(1,1)), ((1,1),(2,2)), ((1,-1),(2,-2)), ((1,1),(1,-1))]
for x, xp in pairs:
dot2 = x[0]*xp[0] + x[1]*xp[1]
K = dot2 ** 2
zx, zxp = phi(*x), phi(*xp)
dot3 = zx[0]*zxp[0] + zx[1]*zxp[1] + zx[2]*zxp[2]
print(f" x={x}, x'={xp}: K = ({dot2})^2 = {K:.4f}, phi 内积 = {dot3:.4f}, 差 = {K - dot3:.1e}")多项式核映射 (x1,x2) -> (x1^2, sqrt(2)x1x2, x2^2):
紫色四点:
(1, 1) -> (1, +1.4142, 1)
(1, -1) -> (1, -1.4142, 1)
(-1, 1) -> (1, -1.4142, 1)
(-1, -1) -> (1, +1.4142, 1)
绿色四点:
(2, 2) -> (4, +5.6569, 4)
(2, -2) -> (4, -5.6569, 4)
(-2, 2) -> (4, -5.6569, 4)
(-2, -2) -> (4, +5.6569, 4)
紫色四个点两两重合成 (1,+-1.4142, 1) 两点; 绿色重合成 (4,+-5.6569, 4) 两点
分离面 z1 = 2.5: 紫色全部 z1 = 1 < 2.5, 绿色全部 z1 = 4 > 2.5 -> 一个平面分开
核技巧对账: K(x,x') = (x.x')^2 vs 三维显式内积 phi(x).phi(x'):
x=(1, 1), x'=(1, 1): K = (2)^2 = 4.0000, phi 内积 = 4.0000, 差 = 0.0e+00
x=(1, 1), x'=(2, 2): K = (4)^2 = 16.0000, phi 内积 = 16.0000, 差 = 0.0e+00
x=(1, -1), x'=(2, -2): K = (4)^2 = 16.0000, phi 内积 = 16.0000, 差 = 0.0e+00
x=(1, 1), x'=(1, -1): K = (0)^2 = 0.0000, phi 内积 = -0.0000, 差 = 4.4e-16三件事在输出里同时兑现:
- 四个点映射成两个点:紫色的 和 映到同一个 , 和 映到 ;绿色同样两两重合到 。重叠反而帮了忙——三维空间里,紫色全落在 、绿色全落在 ,一个平面 干干净净把两类分开。“二维空间里不可分的数据,用一个多项式 kernel 映射到三维空间,就是一个线性可分的问题了。”
- 核技巧(kernel trick):注意最妙的一行对照—— 恰好等于 。也就是说,想知道映射后三维空间里的内积,根本不用真的去算三维坐标,在二维里把 一乘就出来了。这就是”把内积换成核函数的值”的完整含义——对偶形式原封不动,把每个 换成 ,就完成了升级。
- 机器毛边彩蛋:最后一行 数学上恰好为零(两个方向正交),机器上留着 的浮点毛边——老朋友了,看清就好。
讲师的点睛总结: 乘 本身就是一个最简单的线性 kernel——直接相乘;但它还可以变成多项式的形式,还可以变成高斯 kernel 等等。“总之的目的,就是要在另外一个空间里边,把内积形式换成核函数的一个值。“——这就是从 SVM 走向 kernel function、kernel machine 的完整例子。
Kernel Machine 与一段研究史
2000 年初,SVM 火起来正是因为它有很完整的数学表述——把一个机器学习问题变成一个完整的优化问题;而且”原来在某个空间不可分的数据,可以在高维空间里通过 kernel 的映射变成可分”。这个观察带动了一大批核函数研究:用什么样的核函数、对应什么空间、有什么特性、对某些数据分得更好还是更坏。这一族方法的延伸,得名 Kernel Machine。
讲师也给了一段诚实的当代评价:深度学习出来之后,这种方法并不一定是最优良的——现在的实际数据往往量很大,要拟合更复杂的函数关系时,也许深度神经网络会更好,“这一边的研究稍微会有一些减弱,但实际上也是一个非常好的方向”。再往前看,以后还会学到 metric learning(度量学习)——它跟 kernel 映射服从某种关系:都是给定数据、找一个函数把它映射到另外一个空间,让关系在那个空间里更明显。“在机器学习大的范畴里边,这个思想都是类似的。”
📝 动手练一练
- 驻点为何被弃:对偶求解中,偏导置零解出 、,为什么这个解不能用?随后”要么 、要么 “的分 case 逻辑,理论依据是什么?
👉 点击查看参考答案
违反了对偶约束 ——驻点落在可行域之外。这正是 KKT 条件处理过的情形(一维例子 s.t. 中 的翻版):无约束最优不可行时,最优解必然贴在可行域边界上。二维的 情况下,边界就是 或 两条线——分 case 各解一次、比较目标值取更小者,就是把这个逻辑老老实实走了一遍。
- 核映射手算:用本节的多项式核 ,(1) 算出 映射后的三维坐标;(2) 验证 、 这一对满足 。
👉 点击查看参考答案
(1) 。
(2) ,所以 。显式内积:,,。两边一致——这一对方向”垂直”,无论怎么多项式放大都还是垂直。
本章小结
- 三点例全程手算:直观解(消 → → 代入 → 求导 、)与对偶解(展开 → 消元 → 驻点 违约束 → 分 case → )殊途同归。
- 支持向量兑现:——不在边界上的 对 零贡献;, 由边界点反解。
- 对偶的红利:对偶形式每一块都是内积 ——把内积换成核函数 ,线性 SVM 升级为 Kernel 方法,处理线性不可分。
- 多项式核例子:,二维不可分的内圈/外圈四点,映射后四点两两重合、一个平面 分开;核技巧 ——不用去三维,二维直接算。
- 视野: 本身就是最简单的线性核;还可换多项式、高斯核等——这一族方法的延伸叫 Kernel Machine;深度学习兴起后此方向研究减弱但仍是好方向;与 metric learning 思想同源。
📋 行动清单
- 把 的消元过程在纸上完整推一遍——从 代入 开始
- 跑代码块二,自己加第四个点(比如 正类),先猜它的 是多少,再想怎么验证
- 跑代码块三,把绿色的外圈点改成 附近,看映射后 的取值怎么变、分离面还在不在
- 回想讲师开场的直觉之问——“为什么要求最大的边界”——用本节看到的稀疏性(边界只由少数点撑起)组织一遍自己的回答
- 预习下一章:贝叶斯公式——统计学习的另一半地图,从”优化视角”切换到”概率视角”
—— 小象教研组
配套资料 · 直接下载
课件、代码、数据集等配套资料全部免费,点击即可直接下载,无需分享或解锁。
- 下载第11章笔记:最大间隔(Max Margin)