查看全课大纲(第 19 / 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 回传
梯度下降(2)
约 33 分钟
小象实战讲义 · 机器学习算法精讲
上一节我们把更新规则 立了起来,也用三档 亲眼看过了步子太大与太小的后果。但有一个地方始终没打开:更新式里的偏导 ,从头到尾是个黑盒——用讲师的话说,我们实际上只求了一半。本节就把这一半补上:亲手推出线性回归里两个参数的梯度,把整套方法推广到多变量情形,直面实战中的特征缩放问题,最后认识 batch 家族,以及梯度下降与最小二乘的关系。
💡 核心导读
- 打开黑盒:完整推导 、 的偏导——链式法则上场,4.2 节那个 的伏笔在此兑现。
- 同时更新:两个参数必须在同一个点上量出梯度、一起更新——用代码看清”同时”与”顺序”走的是两条路。
- 推广到多变量:、通用更新公式,以及参数的”权重”语义——用房价的四特征读懂每个系数。
- 特征缩放:尺度不齐会把碗压得特别扁;两种归一化把它扶圆。
- 更大的图景:batch / mini-batch / 随机梯度下降,与”最小二乘 vs 梯度下降”的对照。
打开黑盒:求出两个偏导
先把设定摆齐。假设函数 (对第 个数据,就是 ),代价函数沿用上一章的:
更新要分两条写:
现在把两个偏导真正解出来。先复习一个求导工具——链式法则:复合函数 对 求导,等于 ——先在外层求导,再乘上里层的导数。
对 求偏导。 把 的定义代入,外层是平方函数、里层是 :
注意两件事。第一,平方求导翻出来的 ,恰好和分母里的 约掉——4.2 节说那个 是”为了数学上的方便”,方便就兑现在这里,系数干净地变成 。第二,里层对 求导:,前一项与 无关, 对自己求导是 ——乘 等于没乘,直接省去:
对 求偏导。 流程完全一样,唯一的区别在里层: 对 求导,得到的不是 而是 :
于是两条更新规则完整成型:
读一遍它的意思:每条数据先算出误差 ; 的更新用误差本身, 的更新用误差乘上对应的特征值。初始时随机给定 、,梯度就能算出来;有了梯度就能更新;不断迭代,就能修出越来越好的参数。
同时更新:两个参数必须一起动
上一节立过规矩:更新必须同时(simultaneous)。这里把原因说透: 的更新式里用到了 (它藏在 里), 的更新式里也用到了 ——两个式子互指”当前值”。所以正确做法是:在当前这组 上把两个梯度都算完,再一起赋值;不能先更新 ,再拿新 去算 的梯度。用代码把两条路摆在一起看:
# 演示一:同时更新 vs 顺序更新——同一个起点、同一个 alpha,走出两条路
data = [(1, 2), (2, 4), (3, 5)]
def h(t0, t1, x):
return t0 + t1 * x
def J(t0, t1):
return sum((h(t0, t1, x) - y) ** 2 for x, y in data) / (2 * len(data))
def grads(t0, t1):
n = len(data)
g0 = sum(h(t0, t1, x) - y for x, y in data) / n
g1 = sum((h(t0, t1, x) - y) * x for x, y in data) / n
return g0, g1
alpha = 0.3
t0s, t1s = 0.0, 0.0 # 同时更新(正确的梯度下降)
t0q, t1q = 0.0, 0.0 # 顺序更新(错误示范)
for it in range(1, 11):
# 正确:在当前点同时量出两个梯度,一起赋值
g0, g1 = grads(t0s, t1s)
t0s, t1s = t0s - alpha * g0, t1s - alpha * g1
# 错误:先把 theta0 更新掉,再拿新 theta0 去算 theta1 的梯度
g0, _ = grads(t0q, t1q)
t0q = t0q - alpha * g0
_, g1 = grads(t0q, t1q)
t1q = t1q - alpha * g1
if it in (1, 2, 10):
print(f"轮{it:2d} 同时: ({t0s:.4f}, {t1s:.4f}) J={J(t0s, t1s):.4f} "
f"顺序: ({t0q:.4f}, {t1q:.4f}) J={J(t0q, t1q):.4f}")轮 1 同时: (1.1000, 2.5000) J=3.3217 顺序: (1.1000, 1.8400) J=0.6861
轮 2 同时: (0.3700, 0.8400) J=1.4798 顺序: (0.7660, 1.3044) J=0.0831
轮10 同时: (0.6517, 1.4767) J=0.0298 顺序: (0.7851, 1.4499) J=0.0288从第 1 轮起,两条轨迹就分道扬镳:顺序更新在算 的梯度时,用的是”新 + 旧 “拼出来的点——那个点从来不在同时更新的轨道上。说句公道话:在这个温和的凸玩具问题里,两条路最后都收敛了(顺序更新这个走法其实是”逐坐标更新”那族算法的近亲,凸问题对它也很宽容);但它是另一个算法。梯度下降的定义就是:在当前点上,把所有梯度一起量出来,一起走。规矩要守在定义上,而不是守在运气上。
推广到多变量:h = θᵀx
到目前为止我们玩的都是二维参数(、)的简单情形。回想最一般的线性框架:多变量线性回归(multivariate linear regression)的假设函数是
讲师用卖房子把它讲活了。美国的房子有这些特征: 是面积(平方英尺), 是有几个卧室, 是有几层(一层、两层、三层——如果没想错,大家都喜欢要三层的), 是房龄(有 45 年的、40 年的、30 年的)。于是
其中 恒等于 ,所以 就是 里那个单独的 ——本质上就是一个线性组合。
参数的”权重”语义
学出来的 非常好读,它就是每个特征的权重:
- 正负号 = 相关方向:面积越大价格越高,所以 应该是正数;房龄越大越不值钱,所以 应该是负数。正值是 positive correlated,负值是 negative correlated。
- 绝对值 = 贡献程度:如果 比 、 都大,说明这套房价里看的最重要的特征是面积。房间四个还是五个,区别往往没有面积那么大,系数就相对小。
- 讲师给了个试金石:30 年跟 31 年的房子价格相差很大,说明 很大、人们很在意新旧;31 年跟 30 年没太大区别,说明 很小。
这种线性组合是非常常用的一类模型:给你一个高维向量,每个特征对目标”有多重要、往哪个方向推”,全部写在一串系数里。不过要留一个心眼:系数的大小还受特征单位的影响——面积以平方英尺计、数值动辄上千,“每平方英尺多少钱”的系数天然显得小;房间数只有个位数,“每间卧室多少钱”的系数天然显得大。想严格比较特征的重要性,得先把尺度统一——这正好是下一节的伏笔。
通用更新公式
多变量的梯度下降,形式跟二维如出一辙,只是对每个 各写一条:
代价函数不变,只是 换成了高维向量。求偏导的过程和刚才一模一样:外层平方配 照样约掉,里层 对 的导数就是 。于是:
这里 要读仔细: 是第 个数据,本身是一个 维向量 ; 就是它在第 个方向上的分量。验证一下一致性: 时 ,公式退化回 那条更新式——分毫不差。
特征缩放:把压扁的碗扶圆
迭代学习真正跑起来,还有很多比想象中复杂的实际问题(practical issue),第一个就是特征缩放(feature scaling)——各个特征的”尺度”不齐。
回头看房价的例子:面积从 700 到 3000 平方英尺,数值是几千;卧室两三个、层数一到三,是个位数;房龄 10 到 50 年,是几十。如果直接用原始数值,面积这一维会dominate(统治)其他分量的变化——反映到代价函数的等高线上,碗被压得特别扁、特别狭长,梯度的寻找会走得很辛苦。做一次缩放(归一化),把每个维度都拉到相近的尺度,碗变圆了,最优值就好找多了。两种最简单的做法:
- A 法(压进 0~1):。比如面积这一维,,一套 1500 平方英尺的房子归一化后就是 。所有维度都落进 。
- B 法(均值归一化,以 0 为中心):, 是该维的均值。结果有正有负,大致对称地分布在 0 附近。
用一份手工构造的房价数据(价格恰好由一条线性函数精确生成,便于验证)把两种处境跑一遍:
# 演示二:四特征房价上的多变量梯度下降——原始尺度 vs 均值归一化
houses = [
# (面积平方英尺, 卧室数, 层数, 房龄年, 价格美元)
(700, 5, 1, 10, 117000), (3000, 2, 3, 50, 320000),
(1500, 3, 2, 30, 181000), (2000, 4, 1, 25, 230000),
(1000, 2, 3, 40, 128000), (2500, 5, 2, 20, 299000),
(1200, 4, 2, 45, 144000), (2800, 3, 1, 15, 313000),
(1800, 2, 3, 35, 212000), (2200, 5, 3, 30, 271000),
(900, 3, 1, 20, 119000), (2600, 4, 2, 50, 280000),
]
y = [p for a, r, f, g, p in houses]
X_raw = [[1, a, r, f, g] for a, r, f, g, p in houses] # x0 恒为 1
def gd(X, alpha, iters):
m, n = len(X), len(X[0])
theta = [0.0] * n
for _ in range(iters):
errs = [sum(theta[j] * X[i][j] for j in range(n)) - y[i] for i in range(m)]
# 通用公式的直接实现:先在当前点算好全部梯度,再同时更新
theta = [theta[j] - alpha * sum(errs[i] * X[i][j] for i in range(m)) / m
for j in range(n)]
cost = sum((sum(theta[j] * X[i][j] for j in range(n)) - y[i]) ** 2
for i in range(m)) / (2 * m)
return theta, cost
J0 = sum(p * p for p in y) / (2 * len(y))
print(f"初始 J(参数全零)= {J0:.4e}")
print()
theta_raw, J_raw = gd(X_raw, alpha=1e-7, iters=2000)
print("原始特征(alpha 只敢用 1e-7,跑 2000 轮):")
print(f" J = {J_raw:.4e},学到的参数 = {[round(t, 2) for t in theta_raw]}")
print()
# B 法均值归一化:每个特征减均值、除以极差(x0 恒为 1 的列不动)
m, n = len(X_raw), len(X_raw[0])
means = [sum(row[j] for row in X_raw) / m for j in range(n)]
lo = [min(row[j] for row in X_raw) for j in range(n)]
hi = [max(row[j] for row in X_raw) for j in range(n)]
X_b = [[1.0 if j == 0 else (row[j] - means[j]) / (hi[j] - lo[j]) for j in range(n)]
for row in X_raw]
theta_b, J_b = gd(X_b, alpha=0.5, iters=2000)
print("均值归一化后(alpha 可以放心用 0.5,跑 2000 轮):")
print(f" J = {J_b:.4e},学到的参数 = {[round(t, 2) for t in theta_b]}")初始 J(参数全零)= 2.6549e+10
原始特征(alpha 只敢用 1e-7,跑 2000 轮):
J = 1.4125e+08,学到的参数 = [1.07, 114.89, 5.32, 1.6, 6.96]
均值归一化后(alpha 可以放心用 0.5,跑 2000 轮):
J = 3.9264e-21,学到的参数 = [217833.33, 230000.0, 15000.0, 20000.0, -32000.0]对照着读,特征缩放的价值一目了然:
- 原始特征:面积的数值上千, 只敢取 (再大就发散);跑了 2000 轮, 从 磨到 ——降了两个数量级,但离最优(这套数据可以精确拟合,最优是 0)还远得很。看学到的参数更直观:面积系数冲到了 115 附近(它”单打独斗”的最优值),而卧室、层数的系数才爬到 5.3 和 1.6(真实值分别是 5000 和 10000),基本还躺在起点。这就是被压扁的碗:一个方向早就滑到底了,其他方向还在半山腰慢慢挪。
- 归一化之后: 可以放心用 0.5,同样 2000 轮, 直接压到 量级——浮点意义下的零。学到的参数可以手工验证:B 法缩放后, 的真实值恰好是”原系数 × 该维极差”(面积 ,卧室 ,层数 ,房龄 ),截距项等于均值特征处的预测值。而参数的语义此刻完全兑现:面积一项的系数 230000 遥遥领先——尺度统一后,谁重要一目了然;房龄系数为负——越老越不值钱。
batch、mini-batch 与随机梯度下降
再介绍一个实用视角。我们至今用的梯度下降,叫 batch(批量) 的方法——“batch”就是一摞、一堆数据:看更新公式就知道,每一轮更新都要把全部 条数据的误差都算一遍。数据有多少,计算量就跟着涨多少——复杂度是 。
数据量特别大的时候怎么办?两个方向:
- 随机梯度下降(stochastic gradient descent,SGD):不把所有数据都算一遍,而是按数据的概率分布采出一部分样本,用这些采样来估计梯度方向——运算量立刻减下来。
- mini-batch(小批量):把数据分成很多小堆,每次用一小堆算一次更新——深度学习训练里的标配。
这两位以后在具体应用里还会正式登场,这里先混个脸熟。
回到起点:这个问题到底是什么
梯度下降讲完,回头看我们最初的问题到底长什么样。现在有好多数据:、……一直到 ——输入和标签全都给了,这是一个监督学习(supervised learning)问题;而且它不是分类(classification),是回归/预测(regression / prediction):给定一个新的 ,学到的线性函数在它身上取的值,就是预测的 。
有意思的是,这个问题在数学上早就有”完美解”:线性代数里有一个特别传统的算法叫最小二乘法(least mean square),直接通过矩阵乘法就能把最优参数一次求出来。那梯度下降的价值在哪?
- 最小二乘是数学上完美的解——前提是你得会它,而且问题规模允许你把矩阵算动。
- 梯度下降是用计算的方式找出来的、非常 practical 的解:哪怕你不会最小二乘,随便给定一个 、,靠着”量梯度、走一步”不断循环,也能找到最好的拟合。这也是我们如何从数学一步步过渡到计算机科学的生动一例。
最后送一个讲师的彩蛋:类似梯度下降这种迭代方法,其实牛顿时代就有了——可惜牛顿的时代没有计算机,也没办法写这种不断循环的程序来解决这些问题。想法早就躺在那里,是计算把它唤醒的。
📝 动手练一练
- 读懂一组系数:假设在原始特征(面积/平方英尺、卧室数、层数、房龄/年)上学到的模型参数为:、、、、。请回答: ① 哪个特征与价格负相关? ② 面积每增加 100 平方英尺,预测价格大约增加多少? ③ 如果市场上 30 年和 31 年房龄的房子能差出明显的价格,你预计 偏大还是偏小? ④ 比 小得多,为什么不能就此断定”卧室数比面积重要”?
👉 点击查看参考答案
① 房龄(:越老越不值钱)。② 美元。③ 偏大——本节的试金石:系数绝对值衡量该特征对目标的贡献程度,一年之差都值钱,说明人们很在意新旧。④ 系数大小受特征单位和取值范围影响:面积动辄上千平方英尺,“每平方英尺 60 美元”累积起来贡献巨大(一套 2300 平方英尺的房子差出 13.8 万美元);卧室数只有 2~5,“每间 4500 美元”撑死差 1.35 万。要严格比较特征重要性,得先统一尺度——这正是特征缩放的用武之地。
- 手算两种归一化:面积样本为 (平方英尺,极差 2300、均值 1850)。请手算:① A 法 的四个结果;② B 法 的四个结果;③ 说说两组结果各自的分布形态。
👉 点击查看参考答案
① A 法:,,,——全部落在 。
② B 法:,,,——以 0 为中心、正负对称。
③ A 法的结果全部非负、贴着 区间;B 法有正有负、关于 0 大致对称。两种都把各维度的尺度拉平了,B 法额外把数据中心挪到了 0——演示二里用的正是 B 法。
本章小结
本节把梯度下降的”另一半”补齐了。我们亲手推出 、 的偏导(链式法则 + 与 2 相约),确认了两个参数必须同时更新,并把更新规则推广到多变量线性回归:,每个参数 按通用公式 更新。参数的”权重语义”让我们能读懂模型(正负=相关方向、绝对值=贡献程度),而特征缩放解决了各维度尺度不齐把碗压扁的问题——同样 2000 轮,原始特征卡在半路,归一化后直捣最优。我们还认识了 batch / mini-batch / SGD 一家,以及梯度下降与最小二乘的分工:一个是数学上的完美解,一个是算出来的实用解(最小二乘的正片在第 8 章)。至于结尾那个”牛顿时代就有类似方法”的彩蛋——下一节的牛顿方法,正是它的正面登场。
📋 行动清单
学完本节,你可以立即:
- 合上讲义推一遍:从 出发,独立推出 与 ,卡住的地方回头看链式法则。
- 亲手缩放一次:找一份身边的数据(身高体重、气温销量都行),按 A 法和 B 法各归一化一遍,感受尺度的变化。
- 读一个真实模型:搜索一个公开的线性回归案例,看它的系数正负与大小,用本节的”权重语义”讲给同事听。
—— 小象教研组
配套资料 · 直接下载
课件、代码、数据集等配套资料全部免费,点击即可直接下载,无需分享或解锁。