上周把多层感知机、权重衰减和Dropout跑通了,但训练循环里的loss.backward()仍像一个黑盒。本周接着学习《动手学深度学习》第4章4.7至4.9,整理前向传播、反向传播、参数初始化和分布偏移,并把4.10的房价预测案例完整跑了一遍。重点是弄清梯度从哪里来,以及真实数据怎样进入模型。

这篇复盘主要记录

  • 前向传播、反向传播和参数更新分别做什么
  • 梯度为什么会消失或爆炸,初始化怎样影响训练
  • 训练和使用模型时,数据分布变化意味着什么
  • 房价数据预处理、5折验证与真实训练结果

一、从训练循环回到计算图

前向传播按输入到输出的顺序计算预测和损失;反向传播从目标函数出发,沿计算图反方向应用链式法则,得到各个参数的梯度;优化器再使用梯度更新参数。这三步有先后顺序,也有不同职责。

optimizer.zero_grad()       # 清除上一批的梯度
predictions = model(X)      # 前向传播
loss = loss_function(predictions, y)
loss.backward()             # 计算梯度
optimizer.step()            # 更新参数

backward()不会自动替我们做梯度下降。PyTorch默认累加梯度,所以通常还需要先清零。反向传播会用到前向计算的中间结果,这也是训练通常比单纯预测需要更多内存的原因之一。

二、手算一次反向传播

4.7以一个不含偏置的单隐藏层网络说明计算过程。沿用书中的列向量写法,输入先经过隐藏层权重和ReLU,再进入输出层:

z = W₁x,h = ReLU(z),o = W₂h

本次数值示例选择半平方误差,并加入L2正则项:

J = ½||o − y||² + λ/2 × (||W₁||² + ||W₂||²)

输出层梯度由误差与隐藏层输出的外积得到,再加上正则项的梯度。把误差传回隐藏层时,先乘输出层权重的转置,再乘ReLU的导数。

output_gradient = prediction - target
manual_output = torch.outer(output_gradient, hidden)
manual_output += penalty_coefficient * output_weights

hidden_gradient = output_weights.T @ output_gradient
hidden_gradient *= (pre_activation > 0).to(x.dtype)
manual_hidden = torch.outer(hidden_gradient, x)
manual_hidden += penalty_coefficient * hidden_weights

使用同一组输入和权重调用自动求导,两层梯度都与手算结果一致,目标函数值为0.1322。这个对照把“损失对输出的影响”和“输出对参数的影响”连了起来。不过,复杂网络的矩阵维度和多条依赖路径还需要继续练习。

三、sigmoid为什么可能让梯度越来越小

sigmoid的导数为σ(x)(1−σ(x)),最大值只有0.25。输入绝对值较大时,它进入饱和区,导数更加接近0。本次在x=−8处计算到的导数约为0.000335。

sigmoid函数与导数曲线,饱和区导数接近零
图1:sigmoid及其导数。输入远离0时,函数趋于饱和,局部梯度变小。

链式法则需要把各层的局部导数组合起来。反复乘上小数可能让靠近输入层的梯度变得很小,使参数更新缓慢。这里解释的是一种机制;实际网络还会受到权重矩阵和结构的影响,不能只根据一个激活函数断言某次训练一定出现梯度消失。

四、随机矩阵连乘与数值尺度

书中用100个4×4随机矩阵连乘,展示数值可能怎样快速增长。本次固定随机种子42,未缩放矩阵连乘后的Frobenius范数约为1.91×10²³。脚本另外增加了每个矩阵除以√4的对照。

100次随机矩阵连乘的范数,比较未缩放和缩放后的数值尺度
图2:同一组随机矩阵在未缩放与缩放情况下的连乘范数。纵轴采用对数尺度;这是数值示例,不是实际网络的梯度监测。

反向传播也涉及多次矩阵乘法,因此权重尺度会影响梯度尺度。缩放对照帮助观察这一点,但不能保证所有随机连乘都稳定,更不能替代对真实训练中损失和梯度的检查。

五、初始化不只是随便放几个随机数

如果同一隐藏层的神经元使用完全相同的权重、偏置,后续连接也对称,它们就会给出相同输出并得到相同梯度。本次示例中三个隐藏单元经过一次更新后三行权重仍然相同,没有学出不同的表示。

随机初始化可以打破这种对称性,但随机数的尺度也需要控制。Xavier初始化按照输入和输出宽度设置方差:

Var(w) = 2 / (输入宽度 + 输出宽度)

weights = torch.empty(256, 128)
torch.nn.init.xavier_uniform_(weights)
expected_variance = 2 / (128 + 256)

理论方差约0.005208,本次样本方差约0.005214。初始化方案需要结合激活函数选择;理解这个尺度关系是起点,方差推导和不同激活函数下的选择还没有完全熟练。

六、模型上线后为什么可能失效

4.9讨论环境与分布偏移。训练数据和实际使用时的数据未必来自同一种分布,模型在旧测试集上的表现也就未必能代表新环境。

类型变化的部分保持不变的假设
协变量偏移输入分布P(x)给定输入后的标签分布P(y|x)
标签偏移标签分布P(y)给定标签后的输入分布P(x|y)
概念偏移输入与标签的关系P(y|x)不要求输入分布也发生变化

本节主要讲概念,我补充了一个合成数据示例:在y=2x+1附近拟合线性模型,只把输入均值从0移到3,MSE从约0.0094变为0.0103;把关系改成y=−2x+1后,MSE升至约15.5768。

这个例子里的线性关系在整个输入范围都成立,所以协变量偏移没有明显破坏预测;它并不说明协变量偏移普遍无害。脚本没有模拟标签偏移,需要把这部分定义与实际样例继续对应起来。

七、把真实房价数据送进模型

房价案例比Fashion-MNIST更接近常见表格任务。训练数据有1460行、81列,其中包含Id和目标SalePrice;测试数据有1459行、80列,没有房价标签。

  1. 移除Id,不把SalePrice混入输入特征。
  2. 对数值特征标准化,再把缺失值填为0。
  3. 对类别特征做独热编码,同时保留类别缺失的指示列。
  4. 明确转换成float32张量,得到331维输入特征。
numeric_names = all_features.select_dtypes(include="number").columns
numeric = all_features[numeric_names]
deviations = numeric.std().replace(0, np.nan)
all_features[numeric_names] = (
    (numeric - numeric.mean()) / deviations
).fillna(0)
all_features = pd.get_dummies(all_features, dummy_na=True, dtype=float)

常量列和全缺失数值列需要处理零或缺失标准差;较新pandas中的独热编码也要明确转换为浮点类型。自动测试覆盖了缺失值、常量列、全空列和测试集新增类别,确认输出维度对齐且没有非有限值。

八、房价基线与5折交叉验证

模型沿用书中的单层线性回归,用Adam训练,学习率5、权重衰减0、批量大小64,每折训练100轮。训练目标是原始房价的均方误差;评估时使用log RMSE,两者不能混为一谈。

log RMSE = √mean[(log(预测价格) − log(真实价格))²]

计算评估指标前,将小于1的预测值裁剪为1,避免对数出现非法值。对数误差更接近相对价格偏差:同样差10万元,对低价房和高价房的意义不同。

验证折训练log RMSE验证log RMSE
第1折0.1695540.156764
第2折0.1625250.190751
第3折0.1638220.168261
第4折0.1684050.154810
第5折0.1633090.182757
平均0.1655230.170669
房价预测第1折100轮训练的训练与验证log RMSE曲线
图3:第1折的实际训练与验证曲线,从保存的JSON记录绘制,纵轴采用对数尺度。其他折的最终结果见上表。

最后在全部1460条训练数据上重新训练100轮,训练log RMSE约0.162252,并生成1459条本地预测。已确认预测Id唯一、价格均为有限正数;本次没有提交Kaggle,也没有官方测试评分。

九、代码组织与本周验证

仍然沿用之前的独立脚本结构:01至05是计算图、稳定性和分布偏移示例,06至09依次完成房价预处理、交叉验证、全量预测和绘图。公共函数放在week13_basics.py,每个脚本都可以单独运行。

4项自动测试全部通过,检查了手算梯度、预处理边界、分折覆盖与隔离、无效分折参数。9个示例也都实际运行过。这里的验证确认了代码运行与这些行为,但不能替代更多模型实验和真实环境评估。

十、还不熟的地方与下周计划

  1. 复杂计算图。简单两层网络能逐步算出梯度,但更深的网络和矩阵维度还需要继续手推。
  2. 初始化推导。知道初始化会影响尺度和对称性,Xavier方差推导及其与激活函数的配合还不熟。
  3. 模型选择。房价实验主要沿用书中参数,尚未系统比较学习率、权重衰减或不同模型。
  1. 01
    继续房价案例

    比较学习率与权重衰减,练习逐折拟合预处理,分析训练和验证误差。

  2. 02
    学习深度学习计算

    开始第5章,练习模型构造、参数管理和延后初始化。

  3. 03
    复习计算图

    继续手推小网络梯度,把数值稳定性与真实训练过程对应起来。

计算图链式法则初始化分布偏移验证

这周最有帮助的变化,是把调用框架函数与背后的数学过程连起来,再用真实表格数据练习完整建模流程。能跑出一个结果是起点,说明结果怎样得到、评估条件是什么,以及哪些问题还没有解决,同样需要认真记录。