深度学习 · 第一次训练 · 约90–120分钟 · Python 3.11+标准库

你会写backward,参数真的学会了吗?

三行循环写对了,却只见一条平线。你把epoch翻倍,还是不知道是没梯度、没更新,还是学习率太小。这一课先让一个只有25个参数的小网络真实学习,再让它故意坏掉。你会保存数据、梯度、参数差、验证指标和恢复状态;每个结论都能从自己的运行重算。

先修是函数、列表、循环与一点导数。准备不足时先做Python与shape检查;链式法则不熟时接五参数反向传播例子。本包不依赖NumPy/PyTorch,也没有预写loss动画;标准库直接执行前向、手写反传和momentum SGD。完成后再把相同职责接到PyTorch。

让第一份实验有可比较的合同

数据是二维坐标。两轴同号时y=1,异号时y=0:第一、第三象限与另外两个象限分开。一个直线难以把这两个对角区域同时分出来,因此我们用隐藏层学习非线性表示。128个合成点已落成文件,train/validation/test分别80/24/24,四象限在每个集合中平衡;数据生成种子2044,划分固定。它们不是现实业务样本。

运行前锁定:二分类阈值0.5,主指标验证accuracy,诊断指标BCE loss与混淆矩阵;初始化seed7、学习率0.05、momentum0.8、400步预算。多数类基线只能从train统计,本例平衡且平票预测0,validation accuracy应为0.5。test不参加训练或选择,先不打开。400步是预先固定预算,本例不声称自动选到了最佳checkpoint。

下载完整ZIP,解压后进入包含train.py与data的目录。先写预测:网络能否超过0.5?损失下降就保证未来自然数据上好用吗?

cd cpu-mlp
python3 -B train.py train --steps 400 --out runs/base
python3 -B -m unittest discover -s tests -v

Windows可用py -3。应有12条测试通过。report.json保存数据hash、预算、baseline与验证结果;history.jsonl保存真实训练记录;checkpoint.json保存继续训练的状态。若命令报错,先核对文件夹与解释器,不把环境失败记成模型失败。完整排错见README。

一个网络怎样把误差传回25个参数

输入x有两个数。隐藏层6个单元,每个计算zⱼ=wⱼ₁x₁+wⱼ₂x₂+bⱼ,hⱼ=tanh(zⱼ)。输出logit为a=Σvⱼhⱼ+c,概率p=sigmoid(a)。参数共12个输入权重、6个隐藏偏置、6个输出权重、1个输出偏置,合计25。batch只是把若干样本损失平均,不新增参数。

二元交叉熵为−y log p−(1−y)log(1−p)。代码用等价的稳定logit写法避免极端概率下log(0)。反向从∂L/∂a=p−y开始:输出权重梯度为(p−y)hⱼ;隐藏层收到(p−y)vⱼ,再乘tanh的局部导数1−hⱼ²;最后乘输入x得到各权重梯度。一个batch的每个样本贡献相加后除以batch大小。把这些公式与train.py的loss_and_grad逐行对上。

2到6到1网络参数量与反向信号的计算顺序x:两个坐标batch不改参数量6个tanh隐藏单元12权重 + 6偏置1个sigmoid输出6权重 + 1偏置反向:(p−y) → vⱼ → (1−hⱼ²) → x
先保存前向中间值,再沿相反方向乘局部导数。所有参数都使用同一轮旧值得到的梯度后同时更新。

停下来核对:若p=0.7、y=1,输出logit梯度是−0.3。输出偏置沿负梯度更新时会增加,使同样输入的概率倾向上升;若学习率过大,这个局部方向仍不保证整个网络损失每步下降。12条测试中的中心差分逐一检查25个参数:移动ε=1e−5左右各算一次loss,与手写梯度比较。测试小网络是为了抓公式错误,不是拿有限差分当训练算法。

读一次真实运行,不只看成功标签

默认运行的validation loss从约0.744076降到0.061626,accuracy为1,混淆矩阵TN=12、TP=12、FP=FN=0;baseline为0.5。这说明网络在这份固定小验证集上学到了区分象限的模式。样本小、任务干净,不说明它能识别照片、回答事实或适应任意分布。换种子和新数据后仍要重新测量。

seed7实际日志:400步内训练与验证BCE loss下降,横轴step,纵轴loss
这张图来自默认实验history.jsonl的实际点。你可以从自己的文件重画;有了新运行,就不能拿这张图代替自己的证据。

训练与验证一起下降只是一个信号。若训练下降、验证回升,过拟合是候选解释,也要检查切分变化和验证变换;若两条平,别立即说“数据太少”。先检查下面两个量:grad_norm说明这一轮有多少梯度,parameter_delta说明优化器是否真的改了参数。它们把视觉上相同的平线分开。

三个平线,三个不同根因

python3 -B train.py train --steps 40 --fault no-step --out runs/no-step
python3 -B train.py train --steps 40 --fault no-backward --out runs/no-backward
python3 -B train.py train --steps 40 --fault tiny-lr --out runs/tiny
python3 -B train.py train --steps 40 --fault accumulate --out runs/accumulate
第40步真实信号;同一数据、初始化和预算
故障grad_normparameter_delta先判断什么
no-step约0.289270有梯度,参数没更新
no-backward00梯度buffer没得到反向结果
tiny-lr约0.28927约2.68×10⁻⁹更新确实发生,但几乎看不见
accumulate约3.50099约0.80231旧梯度未清,步子被累加改变

本例accumulate的train loss约1.31260,比起点更差。忘清零的直接机制是梯度累加,它可能导致不同曲线,不应该固定配对“平线”。PyTorch的zero_grad/backward/step分别是清旧梯度、填新梯度、更新参数;本实验把这三种动作显式写成列表操作。修复时保留故障记录,再跑同一输入,不要改数据让失败消失。

验证顺序也不是一句口诀:本包的前向确定、没有Dropout/BatchNorm,完整验证集按样本正确聚合,所以反转样本顺序不会改变accuracy/混淆矩阵,loss只有浮点求和级别误差。引入随机变换、batch状态或只测一个batch后,比较规则会变化。固定验证数据、eval行为和指标定义,才是可比较的基础。

保存权重,为什么还不等于恢复训练

momentum SGD保留上一次更新的速度:v←0.8v+g,θ←θ−ηv。只恢复θ却把v清零,下一步会走另一条路。数据shuffle的随机状态、当前排列、样本游标和step也属于训练状态。checkpoint都保存了;12条测试用37步保存、新进程加载后到53步,与连续53步比较完整状态。

python3 -B train.py train --steps 200 --out runs/part
python3 -B train.py train --resume runs/part/checkpoint.json --steps 400 --out runs/resumed

这里400是最终总步数。对比base与resumed的params、velocity、cursor、order和rng_state,同环境应一致。训练/验证hash改变时,恢复会拒绝:你已经换了实验,不能把两个协议拼成一次。方案与预算冻结后,才运行最终测试:

python3 -B train.py evaluate --checkpoint runs/base/checkpoint.json --split test --out runs/base/test-report.json

如要依据test继续改模型,这个集合就变成开发集,应重新建立保留协议。test-report.json记录本次test数据hash与checkpoint hash,可以追溯到被冻结的模型。保存结果不是获得一张适用于所有任务的证书。

你独立做:换一个任务,再修一处故障

先在自己副本把三个集合的标签改成x1>0,声明新的任务,保留坐标和固定划分;从新state训练,重算多数类、验证混淆矩阵,不能恢复旧checkpoint。接着选no-step或no-backward制造一次失败,用grad_norm与parameter_delta区分,恢复后再跑完整测试。

交付一页协议卡、数据hash、基线/验证报告、一次故障修复记录、两条恢复日志、冻结后的test报告,以及你认为模型不适用的输入。合格作品能说明“参数为什么动了、分数如何计算、恢复保存了什么”;只交一条下降曲线不够。

做完再核对答案与验收

同号任务需要非线性边界,x1>0任务已有直线边界;不能因此声称隐藏层始终必要。25参数由12+6+6+1得到。no-step有梯度而参数差0,no-backward二者0,极小LR参数差很小但非零。恢复必须含momentum、步数、数据位置和RNG;反转完整验证顺序不改变混淆矩阵。新任务的数据hash变化,必须开新实验。验收还要保留正常训练成功,避免“一律不更新”这种伪修复。

下一步从Ch08训练优化扩展到PyTorch,再选CNN错误分析或小语言模型;先闭合这次训练,不必把所有架构一起学完。

PyTorch Autograd官方教程:带着三个故障信号读梯度累加;checkpoint官方教程:读完写出权重推理与训练恢复所需状态的差别。