一个 3 参数模型,损失从 1.3118 降到 0.0838,600 轮 34.30 毫秒。 解析梯度与数值梯度的最大相对误差 9.62e-07。学习率 0.05 收敛到 0.077,0.1 直接发散。
上一节比完两条路线,这一节拆开网络内部:一次前向算出预测,一次反向算出「每个参数该往哪挪」。
一、前向:把输入一路算到输出
前向传播就是「按公式算」。Demo 用一个三参数模型:y 等于 w3 乘 x 的三次方,加 w1 乘 x,再加 b。 真值是 w3 = 0.8、w1 = -1.5、b = 0。模型从全零开始,看能不能把这三个数学回来。 注意这里把 x 的三次方当成一个「特征」——这正是上一章讲的「手工特征」;只是这次权重是学出来的。 真实网络里,前向是几十层的矩阵乘法加激活函数,道理完全一样:一层一层往后算,最后得到一个预测值。
二、反向:算出每个参数的梯度
有了预测值就能算损失(本例是均方误差)。 反向传播要回答的问题是:每个参数动一点点,损失会变多少。 Demo 手推了三个偏导:对 w3 是 2 乘残差乘 x 三次方的均值,对 w1 是 2 乘残差乘 x 的均值, 对 b 是 2 乘残差的均值。 三项的差别只在「乘了什么」——这就是链式法则的体现:越靠前的参数,链越长,乘的项越多。 更新规则一句话:参数减去「学习率乘梯度」。梯度指向上坡,减去它就是下坡。
三、必须先做梯度检验
Demo 用两种办法各算一次梯度:解析法(手推公式)和数值法(把参数挪动一点点,看损失变化多少再除)。 结果:解析 -0.023123 / 0.065719 / 0.000004,数值 -0.023123 / 0.065719 / 0.000004, 最大相对误差 9.62e-07。 两条完全独立的路算出同一个数,说明反向传播写对了。 这一步为什么必须做?因为梯度写错了,损失照样会下降——只是降到错的地方。 你会看到一条漂亮的下降曲线,然后得到一个错模型。这种 bug 靠看损失曲线发现不了。
四、学习率:步子大小决定生死
同一份数据、同一个初值,只换学习率,跑 300 轮: - 0.002:损失 0.41360,w3 只到 0.353——太慢,没走完。 - 0.010:损失 0.13628,w3 到 0.616。 - 0.050:损失 0.07700,w3 到 0.804——最接近真值 0.8。 - 0.100:发散,参数冲到天文数字。 这不是 bug,是步长超过了损失曲面的稳定区间。三次方特征让梯度很大,步子一放大就冲出去了。 真实训练里学习率是最重要的超参数之一,原因就在这里:小了不起作用,大了直接崩。
五、动手 Demo:前向传播和反向传播:一个能对账的梯度
造 200 条来自三次多项式加噪声的数据; 写一个三参数模型,手推解析梯度做 600 轮梯度下降,每 100 轮记录损失、梯度模长与参数; 再用中心差分数值求一次梯度做对照;最后换四个学习率各跑 300 轮。
def forward(p, x):
w3, w1, b = p
return w3 * x ** 3 + w1 * x + b
def grad(p, x, y):
r = forward(p, x) - y
return np.array([2 * np.mean(r * x ** 3),
2 * np.mean(r * x),
2 * np.mean(r)])
p = p - lr * grad(p, x, y)
g_num = (loss(pp, x, y) - loss(pm, x, y)) / (2 * eps)
实跑输出:
--------------------------------------------------------------------
前向传播和反向传播:一个能对账的梯度
--------------------------------------------------------------------
模型 y = w3·x³ + w1·x + b(3 个参数)
真值 w3 = 0.8,w1 = -1.5,b = 0
样本 200 个,x 落在 -2 到 2
轮 损失 梯度模长 w3 w1 b
-----------------------------------------------
0 1.3118 5.566 0.000 0.000 0.000
100 0.3289 0.426 0.414 -0.400 0.024
200 0.1992 0.297 0.533 -0.738 0.026
300 0.1363 0.207 0.616 -0.973 0.027
400 0.1058 0.144 0.674 -1.138 0.027
500 0.0909 0.100 0.714 -1.252 0.027
600 0.0838 0.070 0.742 -1.331 0.027
600 轮后的参数 w3 = 0.7424,w1 = -1.3321,b = 0.0266
最终损失 0.08371
600 轮总耗时 31.50 毫秒
解析梯度 -0.023123 / 0.065719 / 0.000004
数值梯度 -0.023123 / 0.065719 / 0.000004
最大相对误差 9.62e-07
换学习率再跑 300 轮(初值都是 0):
学习率 300 轮后损失 w3
----------------------------
0.002 0.41360 0.353
0.010 0.13628 0.616
0.050 0.07700 0.804
0.100 发散 -
梯度检验那两行一致到小数点后七八位,说明反向传播写对了。
先做这一步再训练,是手写网络的固定动作——梯度错了损失照样下降,
只是降到错的地方,很难发现。
学习率那四行是另一件事:同一个模型,步子大了会直接冲出去,
损失变成天文数字。这不是 bug,是步长超过了损失曲面的稳定区间。
600 轮 × 200 个样本 × 3 个参数,只用 31.50 毫秒。
真实网络是几亿次乘加,要靠 GPU 并行——这就是深度学习等到 GPU 的原因。
损失那六行:1.3118 → 0.3289 → 0.1992 → 0.1363 → 0.1058 → 0.0909 → 0.0838。 梯度模长同步从 5.566 掉到 0.070。 600 轮后参数 w3 = 0.7424、w1 = -1.3321、b = 0.0266,真值是 0.8 / -1.5 / 0。 都往对的方向去了,但还没完全到位——600 轮不够。 梯度检验那一对数字完全相同,最大相对误差 9.62e-07,这就是「反向传播写对了」的证据。 学习率那四行是本节最有用的:0.05 收敛最好(损失 0.07700、w3 0.804),0.1 直接发散。 同一个模型,只差一个数字。
30 秒实验
- 把 600 轮改成 3000 轮,看参数能不能精确回到 0.8 和 -1.5。
- 故意把某个偏导的系数写错(比如 w3 那一项少乘一个 2),跑一次梯度检验。
- 想一想:为什么「损失在下降」不能证明「梯度写对了」?
常见误区
- 「损失下降就说明训练正常」——梯度错了损失也会降,只是降到错的地方。
- 「学习率随便设个 0.1」——本例 0.1 直接发散。
- 「反向传播很玄」——本质就是链式法则,一层层把「该乘什么」传回去。
自测三问(点开对答案)
1. 反向传播要回答的核心问题是什么? —— 每个参数动一点点,损失会变多少——也就是梯度。 2. 梯度检验怎么做? —— 用中心差分算数值梯度,和解析梯度对比,误差应小到 1e-6 以下。 3. 学习率太大会发生什么? —— 参数一步跨过最优点并越走越远,损失发散。原理讲完,下面进实战。第一个实例:用扩散模型生成图像。
- ☐ (本节完,下一节 5.2.1 实例1:使用AI生成绘画作品)