查看全课大纲(第 41 / 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 回传
优化方法(1):坐标下降与前向选择
约 17 分钟
小象实战讲义 · 机器学习算法精讲
上一节结尾留了话:L1 范数在零点不可导,梯度下降做不出梯度,“会用其他的方法来求这个 LASSO 的优化”。这一节就兑现——介绍几种定量的计算方法。要优化的目标没变,还是上一节那个 ;变的是”怎么走”。
💡 核心导读
- 方法一:坐标下降(coordinate descent)——与梯度下降对照:一个根据梯度下降,一个根据坐标轴下降。
- 机制:固定其他所有维度,在单根坐标轴上搜到最低点 stop、固定,换下一根轴,再搜——路径走成”之”字。
- 起点是个 trick:可以从与 相关性最大的维度开始,更有效率——但影响有限,按顺序搜照样能到。
- 方法二:前向选择(forward selection)——从最小二乘的几何出发: 在特征平面上的投影,一个方向一个方向地搭出来。
- 共同代价:一个维度一个维度地走,计算复杂度摆在那里;维度特别大时,先选谁是有学问的。
方法一:坐标下降
先摆对照:我们原先的方法叫 gradient descent,根据梯度进行下降;这个叫 coordinate descent,根据坐标轴下降。
道理非常简单。假设是二维空间,参数只有 、,损失函数 的等高线一圈圈摆在那里。我们先随便取一个起点,在给定的这个点上,看沿哪个方向值最小——比如沿 这根轴更低,那就先沿 走到这根轴上的最低点,stop,把这个维度的值固定住;然后换 的方向,在 这根轴上再搜最低点,再 stop、再固定;回过头来再搜 ……如此往复。走出来的路径是一个”之”字:横一步、竖一步、再横一步,可以没完地继续走下去,直到走不动为止。
一般化到 维也是一样: 有 个维度,先调整 ,再调整 ,一路到 ,循环往复——每次只在某一个维度上找最小值,找到了就固定,再选另一个维度继续搜。
从哪个维度开始? 有个技巧:可以先求各个 与 之间的 correlation(相关度/相似度),从相似度最大的那个维度开始,使搜索更有效率。但注意讲师的原话:这样做”最后的影响是有限的”——它是个 trick,不是必需品。从 顺着往下走、甚至随机走,也都能取到最优值。所以坐标下降的性格是:比较粗暴、比较简单,但不需要任何其他技巧。它的代价是每一个维度都要去搜索一遍。
把这套”逐轴找最低点”跑起来。先看普通最小二乘损失(),再看 LASSO 损失(,绝对值的尖角照样搜得动):
# ---- 4 个样本, 两个特征相关 (内积 9): 坐标下降会走出看得见的"之"字 ----
X = [
[1.0, 1.0],
[1.0, 2.0],
[2.0, 1.0],
[2.0, 2.0],
]
y = [3.2, 4.8, 3.8, 6.2]
def J(t1, t2, lam):
s = 0.0
for row, yi in zip(X, y):
s += (row[0] * t1 + row[1] * t2 - yi) ** 2
s *= 0.5
return s + lam * (abs(t1) + abs(t2))
def axis_min(t_other, axis, lam):
# 固定另一坐标后, 轴上的损失 = 两段抛物线拼一个尖角(绝对值项)。
# 最低点必在三个候选之一: 正半轴顶点 / 负半轴顶点 / 尖角零点 -- 逐个代入 J 挑最小。
c = d = 0.0
for row, yi in zip(X, y):
xj = row[axis - 1]
r = yi - row[2 - axis] * t_other # 固定另一坐标后的残差
c += xj * r
d += xj * xj
cand = [0.0]
for z in (c - lam, c + lam):
v = z / d
if v > 1e-15 or v < -1e-15:
cand.append(v)
def J_axis(v):
a, b = (v, t_other) if axis == 1 else (t_other, v)
return J(a, b, lam)
return min(cand, key=J_axis)
def coordinate_descent(lam, max_rounds=300):
t1 = t2 = 0.0
snaps = {}
for r in range(1, max_rounds + 1):
old = (t1, t2)
t1 = axis_min(t2, 1, lam)
t2 = axis_min(t1, 2, lam)
if r in (1, 2, 3, 5, 10, 20, 50):
snaps[r] = (t1, t2)
if max(abs(t1 - old[0]), abs(t2 - old[1])) < 1e-12:
return t1, t2, r, snaps
return t1, t2, max_rounds, snaps
print("数据 (x1, x2 -> y), 两个特征相关 (内积 9):")
for row, yi in zip(X, y):
print(f" ({row[0]:.0f}, {row[1]:.0f}) -> {yi}")
t1, t2, r0, s0 = coordinate_descent(0.0)
print("\nlambda = 0.0 (普通最小二乘损失):")
for r, (a, b) in sorted(s0.items()):
print(f" 轮 {r:2d}: theta = ({a:.3f}, {b:.3f}) J = {J(a, b, 0.0):.3f}")
print(f" 轮 {r0}: 收敛, theta = ({t1:.6f}, {t2:.6f})")
XTX = [[sum(X[i][a] * X[i][b] for i in range(4)) for b in range(2)] for a in range(2)]
XTy = [sum(X[i][a] * y[i] for i in range(4)) for a in range(2)]
check = [XTX[0][0] * t1 + XTX[0][1] * t2 - XTy[0], XTX[1][0] * t1 + XTX[1][1] * t2 - XTy[1]]
print(f" 验证: (X^T X)theta - X^T y = ({check[0]:.1e}, {check[1]:.1e}) <- 站在正规方程的零点上")
t1, t2, r5, s5 = coordinate_descent(0.5)
print("\nlambda = 0.5 (LASSO 损失, 同一套轴搜索照样跑得动):")
for r, (a, b) in sorted(s5.items()):
if r in (1, 2, 3, 5, 10, 20):
print(f" 轮 {r:2d}: theta = ({a:.3f}, {b:.3f})")
print(f" 轮 {r5}: 收敛, theta = ({t1:.6f}, {t2:.6f}) <- 比正规方程解恰好各小 0.026")数据 (x1, x2 -> y), 两个特征相关 (内积 9):
(1, 1) -> 3.2
(1, 2) -> 4.8
(2, 1) -> 3.8
(2, 2) -> 6.2
lambda = 0.0 (普通最小二乘损失):
轮 1: theta = (2.800, 0.380) J = 3.158
轮 2: theta = (2.458, 0.688) J = 2.099
轮 3: theta = (2.181, 0.937) J = 1.405
轮 5: theta = (1.775, 1.303) J = 0.650
轮 10: theta = (1.270, 1.757) J = 0.149
轮 20: theta = (1.033, 1.970) J = 0.081
轮 50: theta = (1.000, 2.000) J = 0.080
轮 129: 收敛, theta = (1.000000, 2.000000)
验证: (X^T X)theta - X^T y = (6.6e-12, 3.6e-15) <- 站在正规方程的零点上
lambda = 0.5 (LASSO 损失, 同一套轴搜索照样跑得动):
轮 1: theta = (2.750, 0.375)
轮 2: theta = (2.413, 0.679)
轮 3: theta = (2.139, 0.925)
轮 5: theta = (1.738, 1.286)
轮 10: theta = (1.240, 1.734)
轮 20: theta = (1.006, 1.945)
轮 128: 收敛, theta = (0.973684, 1.973684) <- 比正规方程解恰好各小 0.026读这张路径表。“之”字: 第一步冲到 2.800(过冲), 被它拽到 0.380(欠着);下一轮 回落到 2.458, 升到 0.688——两个维度互相牵扯,因为这两个特征相关(内积 9)。每一步 J 都在降:3.158 → 2.099 → … → 0.080,单调往下,从不停顿。终点双保险: 时 129 轮后精确站在正规方程的解 上,代回 只剩 量级的浮点毛边; 时同一套循环在 LASSO 损失上照样收敛,解变成 ——比正规方程解恰好各小 0.026,绝对值惩罚把两个系数各削了一刀。这正是”求 LASSO 的优化”的第一种方法:不需要梯度,只要每根轴上找得到最低点。
方法二:前向选择
第二个方法叫 forward selection,前向选择。要理解它,必须记得上次课讨论最小二乘时的向量空间视角——当时讲它,就是为了给这一课打基础。
回顾一下。给定数据矩阵 ,它的列向量 、 张成一个平面;我们希望学到的值是 ,但 压根就不在 构成的平面里——无论怎么线性组合都构造不出等于 的向量。我们能求的是 在这个平面上的投影 :它完全由 构造出来,而残差 必须与整个平面垂直。这正是最小二乘解的几何意义(第 8 章)。把矩阵形式写全(讲师特意把常数列也写上):
按列看,它说的就是 ——把两个向量各自等比伸缩,再相加,凑出目标向量(省略常数列本质上是一样的)。
前向选择做的事:不是一口气求出 、,而是一个方向一个方向地搭。第一步,先看 和 哪个跟 的关系更近;假设 更近,就先在 的方向上找一个合适的 ,让 落在 的投影途中。做完这一步,它们之间仍然有距离。第二步,在 的方向上再延伸一段 ——最后延到的那个点,正好是 在平面上的投影,残差最小。跑一遍(这份演示数据里 与 严格正交, 带着一段平面外的分量):
# ---- 前向选择: 4 个样本, x1 与 x2 严格正交, y 带一段平面外的分量 ----
x1 = [1.0, 1.0, -1.0, -1.0]
x2 = [1.0, -1.0, 1.0, -1.0]
y = [3.1, 1.1, -0.9, -2.9]
def dot(a, b):
return sum(u * v for u, v in zip(a, b))
def scale(v, s):
return [s * u for u in v]
def sub(a, b):
return [u - v for u, v in zip(a, b)]
print("数据: x1 = (1,1,-1,-1), x2 = (1,-1,1,-1) (正交), y = (3.1, 1.1, -0.9, -2.9)")
print(f"第一步 选择: <x1,y> = {dot(x1, y):.1f}, <x2,y> = {dot(x2, y):.1f} -> 选 x1 (跟 y 关系更近)")
theta1 = dot(x1, y) / dot(x1, x1)
pred = scale(x1, theta1)
res = sub(y, pred)
print(f"第一步 投影: theta1 = {dot(x1, y):.1f} / {dot(x1, x1):.1f} = {theta1:.3f}")
print(f" 预测 = {[round(v, 1) for v in pred]}, 残差 = {[round(v, 1) for v in res]}")
theta2 = dot(x2, res) / dot(x2, x2)
pred2 = [a + b for a, b in zip(pred, scale(x2, theta2))]
res2 = sub(y, pred2)
print(f"第二步 延伸: 残差与 x2 的内积 = {dot(x2, res):.1f}, theta2 = {dot(x2, res):.1f} / {dot(x2, x2):.1f} = {theta2:.3f}")
print(f" 预测 = {[round(v, 1) for v in pred2]}, 残差 = {[round(v, 1) for v in res2]}")
print(f"\n验证 残差垂直于整个平面: <残差,x1> = {dot(res2, x1):.3f}, <残差,x2> = {dot(res2, x2):.3f}")
print(f"最终: y_hat = {theta1:.1f}*x1 + {theta2:.1f}*x2, 残差平方和 = {dot(res2, res2):.2f}")数据: x1 = (1,1,-1,-1), x2 = (1,-1,1,-1) (正交), y = (3.1, 1.1, -0.9, -2.9)
第一步 选择: <x1,y> = 8.0, <x2,y> = 4.0 -> 选 x1 (跟 y 关系更近)
第一步 投影: theta1 = 8.0 / 4.0 = 2.000
预测 = [2.0, 2.0, -2.0, -2.0], 残差 = [1.1, -0.9, 1.1, -0.9]
第二步 延伸: 残差与 x2 的内积 = 4.0, theta2 = 4.0 / 4.0 = 1.000
预测 = [3.0, 1.0, -1.0, -3.0], 残差 = [0.1, 0.1, 0.1, 0.1]
验证 残差垂直于整个平面: <残差,x1> = 0.000, <残差,x2> = -0.000
最终: y_hat = 2.0*x1 + 1.0*x2, 残差平方和 = 0.04三处观察。选择看相关: 与 的内积 8.0, 只有 4.0——第一步选 ,投影系数 。延伸看残差:剩下的残差 与 的内积是 4.0,——搭完两步,,残差 正是 平面外的那段分量。垂直性的数值兑现:残差与 、 的内积分别是 0.000 和 −0.000——数学上精确为零,机器上带 量级的毛边(那个 −0.000 就是毛边压出来的负零,第 8 章见过同款)。
实践注:这份演示数据里 与 垂直,两步投影恰好一次就落在 的整体投影上;特征不垂直时,第二步会把第一步的落点拽偏——想精确落位,就得像坐标下降那样一轮一轮来回走。两种方法在这里悄悄接上了头。
代价与收尾
前向选择的问题也很直白:它需要一个维度一个维度地求、一个方向一个方向地走,存在计算复杂度的问题。维度比较小的时候,这是很合适的方法;维度特别大的时候,先选择哪一个、后选择哪一个,也是有学问的——要看每一个维度跟 之间的 correlation。(本节视频到这里收尾,更多优化方法下一节继续。)
📝 动手练一练
- 两种下降的本质区别:梯度下降和坐标下降,在”往哪儿走”这件事上有什么本质不同?对 LASSO 这种零点不可导的目标,为什么坐标下降照样能走?
👉 点击查看参考答案
- 梯度下降:每次计算损失关于所有参数的梯度,全体参数沿梯度的反方向同时挪一小步(步长 )——它需要梯度处处存在。
- 坐标下降:每次只挑一根坐标轴,固定其他参数,沿这根轴走到最低点 stop;换下一根轴再走——它不需要整体梯度,只要单根轴上”能找到最低点”。
- 对 LASSO:L1 范数在零点不可导,整体梯度做不出来,梯度下降失灵;但固定其他坐标后,单变量损失是两段抛物线拼一个尖角,最低点照样找得到(演示代码里就是代入三个候选挑最小)——所以坐标下降能走。
- 前向选择手算:三个样本,、(两者正交),。按前向选择的步骤手算:第一步该选哪个特征? 是多少?第二步的 呢?最终残差是多少,验证它与两个特征都垂直。
👉 点击查看参考答案
第一步:,——选 。投影:,预测 ,残差 。
第二步:残差与 的内积 ,,。
最终残差 ;验证垂直: ✓, ✓——正是 在特征平面外的那段分量,残差平方和 。
本章小结
- 定位:LASSO 的 L1 在零点不可导、梯度下降失灵——本节给出两种”用其他方法”的定量解法。
- 坐标下降:与梯度下降对照(根据梯度 vs 根据坐标轴);固定其余维度、单轴搜到最低 stop、换轴再搜,路径走”之”字;可以从与 相关性最大的维度起步(trick,影响有限);粗暴、简单、不需要其他技巧。
- 数值实证:相关特征上 129 轮收敛到正规方程解 (代回只剩浮点毛边);LASSO 损失同一套循环收敛到 ,比正规方程解恰好各小 0.026。
- 前向选择:最小二乘几何的延伸—— 不在特征平面内,先选与 关系更近的方向投影,再沿下一方向延伸,最终 落在 的投影上、残差 垂直于平面(数值验证内积为 0)。
- 代价:一个维度一个维度走,计算复杂度摆着;维度特别大时,先选谁有学问(看每个维度与 的 correlation)。
📋 行动清单
- 用自己的话讲清”根据梯度下降”与”根据坐标轴下降”的区别,以及为什么后者不怕绝对值的尖角
- 跑代码一:把起点从 改成 ,观察”之”字路径殊途同归
- 跑代码一:把 改成 ,看两个系数被削掉多少、会不会有谁恰好归零
- 跑代码二:给 再加一段平面外分量(如 ),验证最终残差恰好等于它、且仍与两特征垂直
- 预习下一节《优化方法(2)》
—— 小象教研组
配套资料 · 直接下载
课件、代码、数据集等配套资料全部免费,点击即可直接下载,无需分享或解锁。