内容简介
《深度学习基础:从反向传播到残差》是一组共六篇的系列文章,对应《AI 算法工程师学习地图》的第 L3 层。它面向已经有 L0 数学、L1 工具、L2 经典机器学习基础、准备进入 Transformer 与 LLM 的读者,讲的是训练一个深度神经网络时会发生什么、为什么、以及怎么算出来。
它回答的问题是:
一个几十层甚至上百层的网络,为什么能训、什么时候不能训、训不动的时候该看哪个数字?1
深度学习教材通常按”模型”组织:感知机、MLP、CNN、RNN、Transformer。这个系列按训练现象组织:梯度怎么流、为什么会消失或爆炸、优化器在做什么、为什么参数比样本多却不过拟合、卷积与循环各解决了什么又败在哪里。每一个现象都用三步处理——推导(公式从哪来)、算账(代到真实网络是多少)、实验(几十行 NumPy / PyTorch 在 CPU 上复现它)——最后指出它在 Transformer 与 LLM 里的形态。
举一个例子说明这个系列的取法。”残差连接让深网络能训”是每本教材都有的一句话;本系列要把它变成三件可验证的事:(一)推导:没有残差时第 \(l\) 层的梯度是 \(l\) 个 Jacobian 的乘积,任何一个的谱范数系统性地偏离 1 就指数级放大或缩小;有残差时每层的 Jacobian 变成 \(I + J_l\),乘积里多出一条恒等通路。用大白话说就是:梯度从顶层传到底层要连乘 \(l\) 个系数,每个系数只要稍微小于 1,乘一百多次就归零,稍微大于 1 就爆炸;残差给它加了一条”不用乘、直接过”的通路。(二)算账:一个 128 层、每层 Jacobian 谱范数 0.9 的网络,最底层梯度是顶层的 \(0.9^{128} \approx 1.4 \times 10^{-6}\);加残差后不再有这个因子。(三)实验:用 NumPy 搭一个 64 层 MLP,画出有无残差时各层梯度范数的分布,两条曲线差六个数量级。Transformer 的每一层都靠这条恒等通路,Pre-Norm 与 Post-Norm 的全部争论也在这条通路上。
系列覆盖的范围可以概括为五个训练现象与两段历史:
| 问题 | 内容 | 篇 | |
|---|---|---|---|
| 现象一 | 梯度怎么流 | 反向传播、链式法则、激活存储、反向为什么是前向的两倍 | 第一篇 |
| 现象二 | 为什么深了就难训 | 方差在层间的传播、初始化、归一化、残差、Pre/Post-Norm | 第二篇 |
| 现象三 | 优化器在做什么 | SGD、Momentum、Adam / AdamW、学习率调度、梯度裁剪 | 第三篇 |
| 现象四 | 什么情况会过拟合?怎么办? | 正则化、dropout、weight decay、double descent、epoch 数 | 第四篇 |
| 历史一 | 卷积解决了什么 | 参数共享、感受野、LeNet → ResNet、ViT 把图切成 token | 第五篇 |
| 历史二 | 循环解决了什么、败在哪 | RNN、时间反传、LSTM 门控、seq2seq、attention 的诞生 | 第六篇 |
为什么写这个系列?
LLM 时代的深度学习基础没有变少,只是换了名字
Transformer 之后,”深度学习基础”常被当成历史课跳过。但打开任何一份预训练技术报告,遇到的仍是这一层的词:loss spike 与梯度裁剪、warmup 步数、AdamW 的 \(\beta_2\)、weight decay 系数、RMSNorm 与 Pre-Norm、初始化的标准差、\(\mu\)P、z-loss、QK-norm。每一个都是本系列某一篇的主题在大模型上的形态。不懂方差传播,就不理解为什么 Llama 的初始化标准差是 0.02 而 GPT-2 的残差分支要额外除以 \(\sqrt{2L}\);不懂 Adam 的二阶矩,就不理解 warmup 为什么必须;不懂 dropout 的期望等价,就不理解为什么 LLM 预训练几乎不用它。
上面这一串词每一个在本系列里都有出处,总结篇会逐个回收;先给对照表,读完可以回来核对:
| 技术报告里的词 | 它是哪个基础概念在大模型上的形态 | 篇 |
|---|---|---|
| loss spike、梯度裁剪 | 梯度范数的量级与坏 batch;裁剪把一步的总范数压回阈值 | 第三篇 |
| warmup 步数 | Adam 的二阶矩估计在前几步不稳定,实际步长偏大 | 第三篇 |
| AdamW 的 \(\beta_2\)、weight decay 系数 | 二阶矩的记忆长度;解耦的 L2 正则 | 第三、四篇 |
| RMSNorm、Pre-Norm | 方差在层间怎么传播,归一化放在残差分支前还是后 | 第二篇 |
| 初始化标准差 0.02、GPT-2 残差分支除以 \(\sqrt{2L}\) | 残差流的方差随层数线性增长 | 第二篇 |
| \(\mu\)P、z-loss、QK-norm | 规模化之后的三个稳定性补丁:学习率随宽度漂移、lm_head logits 漂移、attention logits 增长 | 第二篇第七章 |
| 预训练不用 dropout、一个 epoch | dropout 的期望等价与数据充足时的正则化需求 | 第四篇 |
从”会调用”到”会诊断”
L1 工具箱系列教的是二十行训练循环。写出它不难,难的是它跑起来之后 loss 不降、突然爆成 NaN、降到一半开始震荡——每种情况该看哪个数字、改哪个开关。这需要知道每个开关背后的公式——”开关”指训练循环里那些能拧的量:学习率、batch size、warmup 步数、梯度裁剪阈值、weight decay、初始化标准差、归一化的位置。每拧一个都对应一条公式:梯度范数曲线说明什么、学习率与 batch 的 scaling 规则从哪来、梯度裁剪剪的是什么。这个系列想给的就是这套诊断能力,而不是又一份 API 说明。
现有材料的断层
- 教材(Goodfellow 等《Deep Learning》、Bishop《Deep Learning》)完整但厚,且成书时 Transformer 尚未成为主线,很多现象没有对到 LLM 上;
- 课程(CS231n、fast.ai)讲直觉与代码,很少把一个现象推到能算出数字;
- 论文每篇只讲一个方法(Kaiming 初始化、LayerNorm、AdamW),假设读者能自己把它们放进同一张图;
- 博客大多止于”残差让深网络能训”这一句,不推、不算、不测。
本系列取的是中间那段路:每个现象都推到公式、算到数字、测到曲线,篇幅控制在教材一章的三分之一。
适合哪些读者?
准备进入 L4 Transformer / LLM 的算法学习者
你已经能写训练循环,接下来要读 Llama、DeepSeek 的技术报告与 《Transformer 与 LLM》系列。本系列是那之前的最后一层基础:读完之后,报告里关于初始化、归一化、优化器、稳定性的每一段都有对应的公式可查。
做后训练但基础是”跳过来”的工程师
你在用 trl 做 SFT / DPO,能跑通,但 loss 曲线怪的时候不知道往哪看。第一至四篇是为此准备的:梯度、稳定性、优化器、正则化,各自的诊断信号与开关。
需要回顾 CNN / RNN 的多模态或序列建模学习者
L7 多模态的 vision encoder 来自 ViT,ViT 来自 CNN 的 patch 化;语音模型里仍有卷积前端与循环结构。第五、六篇把这两条线讲到”它们留给 Transformer 什么遗产”为止。
Infra 工程师,想知道自己在优化的训练过程里发生了什么
你在做训练框架、checkpoint、容错,看到梯度裁剪、混合精度、optimizer state 这些词,想知道它们在算法上的含义。第一篇(反向为什么是前向两倍、激活为什么要存)与第三篇(每参数 8 字节优化器状态从哪来)直接回答。
系列的整体主线
六篇按”先建立梯度的流动,再看它为什么会坏、怎么修,再看用梯度更新参数的方法与它的副作用,最后回看两段历史”的顺序推进:
| 篇 | 主题 | 内容 |
|---|---|---|
| 第一篇 | 反向传播 | 手推两层网络,梯度的形状规则,反向 = 2 × 前向,激活为什么要存 |
| 第二篇 | 初始化、归一化与残差 | 方差怎么在层间传播,深网络为什么难训,残差与 Pre-Norm 怎么修 |
| 第三篇 | 优化器 | 从 SGD 到 AdamW,学习率调度,梯度裁剪,batch 与学习率的 scaling |
| 第四篇 | 正则化与泛化 | dropout、weight decay、早停;参数远多于样本时什么情况会过拟合、怎么办 |
| 第五篇 | CNN | 卷积作为参数共享,感受野,LeNet → ResNet 的遗产,ViT 把图切成 token |
| 第六篇 | RNN | 时间上的反传与梯度消失,LSTM 的门,seq2seq 的瓶颈,attention 的诞生 |
前四篇是训练动力学:一个网络从初始化到收敛,梯度经历了什么、参数怎么动、什么时候停。后两篇是结构史:Transformer 之前的两条主线各解决了什么、留下了什么——残差与归一化来自 CNN 这条线,attention 来自 RNN 这条线。读完第六篇,Transformer 的每一个组件都有了来历,L4 的 04 系列可以直接接上。
三条交织的线索:
| 线索 | 从第一篇到第六篇 |
|---|---|
| 推导线 | 链式法则 → Jacobian 乘积与谱范数 → 更新量的量级 → 期望等价与先验 → 卷积的线性算子形式 → 时间反传 |
| 数字线 | 反向 FLOPs = 2 × 前向 → \(0.9^{128}\) → 每参数 8 字节 → 一个 epoch 就够 → ResNet-50 的 4.1 GFLOPs → RNN 不能并行 |
| LLM 线 | 6ND · 激活重算 → RMSNorm · Pre-Norm · 0.02 → AdamW · warmup · 裁剪到 1.0 → 预训练不用 dropout → ViT / patch → attention → Transformer |
每一篇都用同样的方法:推导公式,代入真实网络算出数字,用几十行代码复现现象,指出它在 LLM 里的形态。
章节结构与分章导读
1. 反向传播:手推一个两层网络
第一篇建立整个系列的对象:梯度。它把反向传播从”框架自动做的事”变成可以手推、手算、手写的东西。
这一篇会覆盖:
- 计算图与链式法则:标量对向量、向量对向量(Jacobian)、标量对矩阵的导数;反向传播就是从 loss 出发沿计算图反向逐节点乘 Jacobian;
- 矩阵求导的形状规则:\(Y = XW\) 时 \(\partial L / \partial W = X^T (\partial L / \partial Y)\)、\(\partial L / \partial X = (\partial L / \partial Y) W^T\)——记住”梯度与被求导的量形状相同”,两条公式可以直接推出来;
- 逐层手推一个两层 MLP(Linear → ReLU → Linear → softmax → 交叉熵)的前向与反向,每一步写出形状;softmax + 交叉熵的梯度 \(p - y\)(L0 数学系列第七篇推过,这里放进完整网络);
- 反向为什么是前向的两倍:每个 Linear 层反向要做两个矩阵乘法(对输入的梯度、对权重的梯度),前向只做一个;由此得到训练 FLOPs \(\approx 6ND\)——04 系列第十篇引用的这个数字在这里推出来;
- 激活为什么要存:反向计算 \(\partial L / \partial W\) 需要前向时的输入 \(X\),所以前向的中间结果必须保留到反向;激活显存与 batch、序列长度、层数成正比,与参数量无关;激活重算(gradient checkpointing)用一次额外前向换掉这份存储;
- 用有限差分验证手推的梯度:梯度检查的方法与精度标准;
- Autograd 做了什么:动态图记录、每个算子的 backward 函数、叶子节点的
.grad累加——只到理解框架行为的程度,实现在 Infra 地图 03 系列第三篇。
核心问题是:
不用框架,能不能手推并手写一个两层网络的反向传播,用有限差分验证到 \(10^{-6}\)?能不能由此说出为什么训练 FLOPs 是 \(6ND\)、为什么激活要存?2
实验:纯 NumPy 实现两层 MLP 的前向、反向与梯度检查;在 MNIST 规模的数据上训到 97% 以上;统计前向与反向的浮点运算次数,验证 2 倍关系。这份代码是后面各篇实验的基座。
2. 训练为什么不稳定:初始化、归一化与残差
第二篇讲深网络最核心的问题:信号与梯度经过很多层之后为什么会消失或爆炸,以及三十年里发展出的三种修法。
这一篇会覆盖:
- 方差的传播:一层 \(y = Wx\) 的输出方差是输入方差的 \(n_{in} \cdot \text{Var}(w)\) 倍;要让方差在层间不变,\(\text{Var}(w) = 1/n_{in}\)——这是 Xavier 初始化的推导;ReLU 砍掉一半,所以 Kaiming 初始化是 \(2/n_{in}\);反向传播里同一个推导给出 \(1/n_{out}\);
- 梯度作为 Jacobian 的乘积:\(L\) 层网络最底层的梯度是 \(L\) 个 Jacobian 的乘积,其谱范数的几何平均偏离 1 就指数级放大或缩小;\(0.9^{128} \approx 1.4 \times 10^{-6}\)、\(1.1^{128} \approx 2 \times 10^{5}\);
- 归一化:BatchNorm(沿 batch 归一)、LayerNorm(沿特征归一)、RMSNorm(去掉均值中心化);三者的公式、参数量、计算量;BatchNorm 为什么在序列模型里不好用(batch 内统计量依赖 padding 与序列长度、推理时要用 running statistics);RMSNorm 比 LayerNorm 省了什么;
- 残差连接:每层的 Jacobian 从 \(J_l\) 变成 \(I + J_l\),乘积展开后有一条恒等通路,梯度不再随深度指数衰减;残差流的方差随层数线性增长,所以 GPT-2 把残差分支的初始化再除以 \(\sqrt{2L}\),深层模型要把这个因子算进去;
- Pre-Norm 与 Post-Norm:归一化放在残差分支之前还是之后,对梯度路径的影响;Post-Norm 效果略好但需要 warmup 且深了难训,Pre-Norm 稳定、当前 LLM 几乎全用它;
- 大模型上的稳定性工具:loss spike 的常见来源、QK-norm、z-loss、\(\mu\)P 的动机(让超参数在不同宽度下可迁移)——只到概念,配方细节在 L4;
- 诊断:看每层激活的方差、每层梯度的范数分布,判断是初始化、归一化还是学习率的问题。
核心问题是:
一个 64 层的 MLP 不加任何技巧为什么训不动?初始化、归一化、残差三样东西各修了哪一段,缺了哪一样会怎样?
实验:用第一篇的 NumPy 基座搭一个 64 层 MLP,画出四种配置(随机初始化 / Kaiming / Kaiming + LayerNorm / Kaiming + LayerNorm + 残差)下各层激活方差与梯度范数随深度的曲线;观察 Pre-Norm 与 Post-Norm 在同一网络上的差别。
3. 优化器:从 SGD 到 AdamW 与学习率调度
第三篇讲有了梯度之后怎么更新参数。它把优化器的每一项拆开,解释各自在解决什么问题、带来什么状态与代价。
这一篇会覆盖:
- SGD 与它的噪声:mini-batch 梯度是全量梯度的无偏估计,方差与 \(1/B\) 成正比;学习率与 batch 的 scaling 规则(线性 scaling、平方根 scaling)的来源与适用范围;
- Momentum:梯度的指数移动平均,在一致的方向上加速、在震荡的方向上抵消;Nesterov 的修正;
- Adam:一阶矩 \(m\) 与二阶矩 \(v\),更新量 \(m / \sqrt{v}\) 让每个参数有自己的有效学习率;偏差修正为什么必须;\(\beta_1 = 0.9\)、\(\beta_2 = 0.95\)(LLM 常用,而不是默认的 0.999)各自意味着多长的记忆窗;\(\epsilon\) 的作用;
- AdamW 与 \(L_2\) 正则的区别:在 loss 里加 \(\frac{\lambda}{2}\|w\|^2\) 会被 \(1/\sqrt{v}\) 缩放,而 decoupled weight decay 直接对参数衰减;两者在 Adam 下不等价的推导;LLM 常用 \(\lambda = 0.1\);
- 优化器状态的账:Adam 每参数两个 fp32 状态 8 字节,加 fp32 主权重 4 字节,是 L1 工具箱系列第三篇”16 字节 / 参数”里的 12;8-bit Adam、Adafactor 等减状态的方法各省多少;
- 学习率调度:warmup 为什么必须(\(v\) 的估计在早期不可靠、更新量过大)、cosine 衰减、WSD(warmup-stable-decay)、衰减到峰值的 10% 还是 0;典型的峰值学习率量级(\(3 \times 10^{-4}\) 级别的预训练、\(10^{-5}\) 级别的 SFT)与它们和模型宽度的关系;
- 梯度裁剪:按全局范数裁剪到 1.0 在做什么,为什么它是 loss spike 的第一道防线;梯度范数曲线怎么读;
- 二阶方法与新优化器:Shampoo、Muon、SOPHIA 一类的动机(用更多曲率信息换更少步数),到知道它们存在、知道它们与 Adam 的差别在哪即可。
核心问题是:
Adam 的两个矩各在做什么?为什么 AdamW 与在 loss 里加 \(L_2\) 不一样?warmup 为什么在 Adam 下几乎不能省?3
实验:在同一个小网络上实现 SGD、Momentum、Adam、AdamW,画训练曲线对比;关掉 warmup 与偏差修正观察前几百步;用不同 batch 验证学习率 scaling 规则的适用范围。
4. 正则化与泛化:为什么参数比样本多却不过拟合
第四篇讲深度学习里最反直觉的现象——参数量远大于样本量的网络照样泛化——以及什么时候这个好运会用完。
这一篇会覆盖:
- 经典视角回顾(L2 经典机器学习系列):容量、偏差 - 方差、过拟合;它在深网络上失效的地方——参数量早已超过样本量,按经典理论应该严重过拟合;
- double descent:测试误差随模型容量先降、再升、越过插值阈值后再降;隐式正则化——SGD 倾向找到平坦、低范数的解;
- 显式正则化:weight decay(作为高斯先验、作为有效学习率的调节);dropout(训练时随机置零、推理时按期望缩放,等价于集成大量子网络);早停(等价于对训练轨迹的约束);数据增强;label smoothing;
- 为什么 LLM 预训练几乎不用 dropout:数据量远大于模型能记住的量、每个样本只见一次(一个 epoch),过拟合不是主要风险;weight decay 仍保留,主要作用变成控制参数范数与稍稍改善优化;
- 什么时候过拟合回来:SFT 几千条数据训多个 epoch 后的逐字记忆;奖励模型的过拟合与 reward hacking(L2 经典机器学习系列);多 epoch 预训练在数据受限时的收益递减(Muennighoff 等 2023:重复到 4 个 epoch 以内几乎无损,之后收益迅速下降);
- 泛化的诊断:训练 loss 与验证 loss 的差、验证 loss 何时开始回升、记忆检测(模型能否逐字复述训练样本)。
核心问题是:
一个参数量是样本量一千倍的网络为什么不过拟合?同一个网络在什么数据规模、多少个 epoch 之后会开始过拟合?4
实验:在小数据集上扫模型宽度,画出 double descent 曲线;在同一网络上对比无正则化 / weight decay / dropout 的训练 - 验证 loss 差;用一个小语言模型对比 1 epoch 与 10 epoch 的记忆程度。
5. CNN:从 LeNet 到 ResNet,再到 ViT
第五篇讲卷积网络——它解决了什么问题、演化到 ResNet 留下了什么、以及 ViT 怎么把它的输入方式带进了 Transformer。
这一篇会覆盖:
- 卷积作为带约束的线性层:局部连接与参数共享,一个 \(3 \times 3 \times C_{in} \times C_{out}\) 的卷积核相当于一个被强约束的全连接矩阵;参数量与 FLOPs 的公式,代入 ResNet-50 得到 25.6M 参数、约 4.1 GFLOPs(224×224,单次前向的乘加计);
- 感受野与层数:感受野随层数线性增长,要看到整张图需要很多层,这是”深”的最初动机;池化与 stride;
- 五个里程碑各解决了什么:LeNet(卷积 + 池化的范式)、AlexNet(ReLU、dropout、GPU 训练)、VGG(小核堆深)、GoogLeNet(多分支与 1×1 卷积)、ResNet(残差让 152 层能训);每一步的参数量与深度;
- ResNet 留给 Transformer 的三样东西:残差连接、归一化(BatchNorm → LayerNorm)、”堆同样的块”这个设计原则;
- 卷积的归纳偏置——平移等变性、局部性——在数据足够多时不再是优势;ViT 把图切成 16×16 的 patch、每个 patch 线性投影成一个 token、加位置编码后送进标准 Transformer;224×224 的图是 196 个 token;ViT 在小数据上不如 CNN、在大数据上反超,这是”归纳偏置 vs 数据量”的一个干净实验;
- 卷积在多模态里的残余:ViT 的 patch embedding 本身就是一个 stride 等于 kernel 的卷积;语音模型的卷积前端;扩散模型的 U-Net。
核心问题是:
一个 3×3 卷积核相当于多大的全连接矩阵?ResNet 的残差与 Transformer 的残差是同一个东西吗?ViT 为什么可以不用卷积?
实验:用 NumPy 实现二维卷积并验证它等价于一个稀疏的全连接矩阵;用 PyTorch 在 CIFAR-10 上对比一个 plain 深网络与加残差的同深度网络;实现 patch embedding,验证它与 stride 卷积输出一致。
6. RNN:从 LSTM 到 attention 的诞生
第六篇讲循环网络这条线。它是 attention 的直接来源:理解 RNN 的失败,才理解 Transformer 赢在哪。
这一篇会覆盖:
- 序列建模的问题设定:变长输入、共享参数、依赖历史;RNN 的状态更新 \(h_t = f(W h_{t-1} + U x_t)\);
- 时间上的反向传播(BPTT):梯度沿时间回传是同一个矩阵 \(W\) 的连乘,第二篇的 Jacobian 乘积在时间维上的版本;梯度消失让 RNN 记不住 20 步之外的东西;梯度爆炸靠裁剪(梯度裁剪最初就是为 RNN 发明的);
- LSTM 与 GRU:用门控让状态可以”加法式”地穿过时间,\(c_t = f_t \odot c_{t-1} + i_t \odot \tilde c_t\) 里的 \(f_t \odot c_{t-1}\) 是残差在时间上的形态;门的参数量;
- seq2seq 与它的瓶颈:encoder 把整个源序列压进一个固定长度的向量,decoder 从它出发生成;长句子上质量下降,因为一个向量装不下;
- attention 的诞生(Bahdanau 等 2014):decoder 每一步对 encoder 的所有隐状态算一个权重、加权求和,绕过固定向量瓶颈;这就是 \(\text{softmax}(q^T k) v\) 的最初形式,只是 \(q, k, v\) 还没有分开命名;
- RNN 的两个致命缺点与 Transformer 的回答:无法并行(\(h_t\) 依赖 \(h_{t-1}\),序列长度决定串行步数)与长依赖衰减(路径长度 \(O(n)\));self-attention 把任意两个位置的路径长度变成 \(O(1)\)、把序列维完全并行,代价是 \(O(n^2)\) 的算量与 KV——04 系列的一切从这里开始;
- RNN 的回声:状态空间模型(Mamba 一类)与线性 attention 试图找回 RNN 的 \(O(n)\) 推理成本,知道它们在权衡什么即可。
核心问题是:
RNN 为什么记不住 20 步之外的东西?LSTM 的遗忘门与残差连接是什么关系?attention 最初是为了解决什么问题被发明的,它又为什么最终取代了发明它的 RNN?
实验:用 NumPy 实现一个 RNN 与 LSTM,在”记住第一个 token 并在 \(T\) 步后输出”的任务上扫 \(T\),画出两者能记住的最长距离;实现 Bahdanau attention 并可视化对齐矩阵;测量 RNN 与 self-attention 在同一序列长度下的前向时间随长度的变化。
7. 系列总结与通关自测
最后一篇不讲新内容:把六篇正文压成一张「问题 → 结论 → 必记数字」的表并逐篇回顾,拎出贯穿全系列的几条线与常见误区,然后给一套三段式通关自测——十道判断与计算、五道跨篇综合、若干道面试题,答案各自折叠,附「读过 / 掌握 / 能教人」的判据。各篇末尾的自测检验的是一篇读懂了没有,这一篇检验的是六篇能不能连起来用;读完正文再做。
贯穿全系列的实践线
本系列的贯穿物是一份几百行的 NumPy 小框架和一组建立在它之上的实验。框架从第一篇的两层 MLP 开始,每篇加一点:
| 篇 | 框架里加的东西 |
|---|---|
| 第一篇 |
|
| 第二篇 |
|
| 第三篇 |
|
| 第四篇 |
|
| 第五篇 | Conv2d 与它的全连接等价形式;patch embedding(PyTorch 对照 CIFAR-10) |
| 第六篇 | RNN 单元 · LSTM 单元 · BPTT;Bahdanau attention |
| 案例脚本 | case_01 … case_06:训练曲线、逐层统计、学习率扫描、double descent、LeNet-5、字符级 LSTM 与对齐矩阵的图;tools/paper_figures.py 从原论文 PDF 裁结构图 |
每篇另有一节来龙去脉(这一样东西是谁、为解决什么问题发明的,前一代败在哪)、一章案例(把本篇的实验放回一个完整的问题里:问题 → 思路 → 代码 → 效果 → 落地还差什么,曲线画出来)和原论文的结构图(LeNet-5、AlexNet、VGG、ResNet 残差块、ViT、LSTM 细胞、seq2seq、Bahdanau attention、Pre-LN vs Post-LN、Adam 的算法框、dropout、double descent——直接引用原图并注明来源):
| 篇 | 案例 | 数字 |
|---|---|---|
| 第一篇 | MNIST 手写数字,120 行 NumPy 从零 | 15 个 epoch 97.6%;与 KNN 2.95% / SVM 1.43% 同一份数据 |
| 第二篇 | 把 MLP 加深到 64 层,七种接法 | 只有 Pre-Norm 在两个学习率下都能训;逐层激活 std / 梯度范数曲线 |
| 第三篇 | 同一个 MLP,四种优化器 × 五个学习率;64 层网络的 warmup | 最优学习率差 300 倍、成绩差不到半个点;lr 1e-2 无 warmup loss 冲到 4.59 |
| 第四篇 | 1,000 张图的过拟合与三种解药;宽度扫描 | 训练 loss 归零、测试 loss 掉头;double descent 的尖峰在宽度 8–16 |
| 第五篇 | 复现 LeNet-5 | 61,706 个参数、5 个 epoch、0.82%——MLP 参数的 1/3、错误率的 1/3;第一层学出边缘检测器 |
| 第六篇 | 字符级 LSTM 写莎士比亚,与 nanoGPT 同预算 | val 1.71 vs 1.66;倒序任务的对齐矩阵 |
全部实验在 CPU 上几分钟内跑完(第五、六篇的 PyTorch 案例在 Apple 芯片 / GPU 上更快),没有 GPU 不影响。框架的目的不是替代 PyTorch,而是让每一个训练现象都能在自己写的、每一行都懂的代码里复现一次;之后回到 PyTorch,loss.backward() 与 optimizer.step() 就不再是黑盒。
与它平行的源码与资料阅读线:
| 篇 | 论文与资料 |
|---|---|
| 第一篇 | Rumelhart 等 1986 Learning representations by back-propagating errors · Karpathy micrograd · PyTorch autograd 文档 How autograd encodes the history |
| 第二篇 | Glorot & Bengio 2010 Xavier · He 等 2015 Kaiming · Ioffe & Szegedy 2015 BatchNorm · Ba 等 2016 LayerNorm · Zhang & Sennrich 2019 RMSNorm · Xiong 等 2020 Pre-LN |
| 第三篇 | Kingma & Ba 2014 Adam · Loshchilov & Hutter 2017 AdamW · Goyal 等 2017 线性 scaling · Hu 等 2024 MiniCPM,WSD |
| 第四篇 | Srivastava 等 2014 dropout · Zhang 等 2017 Understanding deep learning requires rethinking generalization · Nakkiran 等 2019 double descent · Muennighoff 等 2023 数据受限的 scaling |
| 第五篇 | LeCun 等 1998 LeNet · Krizhevsky 等 2012 AlexNet · He 等 2015 ResNet · Dosovitskiy 等 2020 ViT |
| 第六篇 | Hochreiter & Schmidhuber 1997 LSTM · Sutskever 等 2014 seq2seq · Bahdanau 等 2014 attention · Vaswani 等 2017 Transformer 第 1–2 节 |
前置要求与说明
前置要求
- L0 数学系列的四个分支:链式法则、Jacobian、期望与方差、范数与谱范数的概念;
- L1 工具箱系列的 NumPy 形状与广播,会写二十行 PyTorch 训练循环;
- L2 经典机器学习系列的过拟合、偏差 - 方差、正则化的概念。
不要求:了解任何具体的初始化、归一化、优化器方法;有 GPU。
版本与基线
- 实验基线:第一至五篇用 MNIST(几万样本、几十万到几百万参数的 MLP 与小卷积网络),第四篇的语言模型用 Python 标准库源码做字符级语料,第六篇用合成的序列任务;所有数字在普通笔记本 CPU 上几分钟到十几分钟内可复现;
- 配套代码:每篇一个脚本,连同系列贯穿的迷你框架
dlf/与作者机器上的运行输出,在 ai-learning-labs/deep-learning-foundations; - 算账时引用的真实网络:ResNet-50(25.6M 参数)、GPT-2(124M 到 1.5B,\(\sqrt{2L}\) 初始化缩放)、Llama-3-8B(32 层、初始化标准差 0.02、AdamW \(\beta_2 = 0.95\)、weight decay 0.1、裁剪 1.0),超参数取自公开论文与技术报告;
- 论文引用以第一作者与年份标注。
章节目录
- 反向传播:手推一个两层网络
- 训练为什么不稳定:初始化、归一化与残差
- 优化器:从 SGD 到 AdamW 与学习率调度
- 正则化与泛化:为什么参数比样本多却不过拟合
- CNN:从 LeNet 到 ResNet,再到 ViT
- RNN:从 LSTM 到 attention 的诞生
- 系列总结与通关自测
最终目标
读完这套系列之后,面对一个训练中的网络,读者应该能够回答:
| 问题 | 答案在 |
|---|---|
| loss 不降,是梯度没传到还是学习率不对? | 第一篇:梯度检查;第三篇:学习率与 warmup |
| loss 爆成 NaN,先看哪个数字? | 第二篇:逐层激活方差;第三篇:梯度范数与裁剪 |
| 这个网络为什么要 Pre-Norm?初始化为什么是 0.02? | 第二篇:方差传播与残差流 |
| AdamW 的每个超参数在控制什么?状态占多少显存? | 第三篇 |
| SFT 训了 5 个 epoch,模型开始背答案,怎么判断、怎么修? | 第四篇 |
| 预训练为什么一个 epoch、不用 dropout? | 第四篇 |
| ViT 的 patch embedding 与卷积是什么关系? | 第五篇 |
| attention 为什么能取代 RNN?代价是什么? | 第六篇 |
最终目标是三种能力:
- 推导能力:面对一个训练现象,能写出它背后的公式(Jacobian 乘积、更新量的量级、期望等价);
- 诊断能力:面对一条不对的曲线,知道看哪个数字、动哪个开关、预期改善多少;
- 衔接能力:读 Transformer 与 LLM 的技术报告时,每一个关于初始化、归一化、优化器、稳定性的段落都能对到本系列的某一节。
这一层是 L4 之前的最后一层基础。它不新,但没有它,后面每一层的”为什么”都只能靠记。
-
能训是因为三件事让信号与梯度在逐层传递中保持尺度:初始化(Xavier / Kaiming 让每层方差守恒)、归一化(BatchNorm / LayerNorm / RMSNorm 把激活钉回单位尺度)、残差(每层 Jacobian 变成 \(I+J_l\),梯度多一条恒等通路——128 层、每层谱范数 0.9 的无残差网络底层梯度只有顶层的 \(1.4\times10^{-6}\))。不能训的时候通常是其中一项失效——初始化尺度错、没有 warmup 让 Adam 在前几百步方差估计不准、Post-Norm 深层不稳、学习率过大。该看的数字:每层梯度范数随深度的分布(是否跨数量级)、激活的均值 / 方差逐层变化、全局梯度范数与 loss 曲线(spike 前梯度范数先涨)、attention logit 的最大值——第二、三篇各给出这些曲线正常与异常的形态。 ↩
-
能,第一篇就是这件事:两层 MLP 的前向 \(h=\sigma(W_1x+b_1)\)、\(\hat y=W_2h+b_2\),反向按链式法则从 loss 往回:\(\partial L/\partial W_2 = \delta_2 h^\top\)、\(\delta_1 = (W_2^\top\delta_2)\odot\sigma'(z_1)\)、\(\partial L/\partial W_1=\delta_1 x^\top\),用中心差分 \((L(\theta+\epsilon)-L(\theta-\epsilon))/2\epsilon\) 逐分量对拍,相对误差到 \(10^{-6}\) 量级(float64)。由此可见:前向每个参数做一次乘加(\(2N\) FLOPs / token),反向要算两样——对输入的梯度 \(W^\top\delta\) 与对权重的梯度 \(\delta x^\top\),各 \(2N\),所以反向 = 2 × 前向,训练一个 token 共 \(6N\),\(D\) 个 token 就是 \(6ND\);而 \(\partial L/\partial W = \delta\, x^\top\) 需要前向的输入 \(x\)(激活),所以激活必须存到反向用完为止,这就是激活显存与 \(B\times T\) 成正比、以及激活重算(checkpointing)用算力换显存的根源。 ↩
-
一阶矩 \(m\) 是梯度的指数移动平均(动量,平滑方向);二阶矩 \(v\) 是梯度平方的指数移动平均,用 \(1/\sqrt{v}\) 给每个参数单独缩放步长——梯度常年很小的参数步子大、常年很大的步子小,所以 Adam 对特征尺度与学习率不敏感。AdamW 与在 loss 里加 \(L_2\) 不一样,因为 \(L_2\) 项的梯度 \(\lambda w\) 会进入 \(m\) 与 \(v\)、被 \(1/\sqrt v\) 一起缩放——梯度大的参数几乎不衰减;AdamW 把 \(-\eta\lambda w\) 直接加在参数更新上、绕开自适应缩放,衰减对所有参数一致,这才是真正的 weight decay(在 SGD 下两者才等价)。warmup 在 Adam 下几乎不能省,因为前几十步 \(v\) 只由极少样本估计、偏差修正后仍噪声极大,\(1/\sqrt v\) 会把某些参数的步长放大几个数量级,训练在开头就被打坏(RAdam 的分析即此);线性 warmup 让 \(v\) 先积累足够样本。 ↩
-
因为过参数化网络在梯度下降下有隐式正则化:在能拟合训练集的无数个解里,SGD 从小初始化出发会找到范数小、「简单」的那个(线性模型里就是最小范数解),泛化误差随宽度增加先升后降——double descent:在参数量 ≈ 样本量的插值阈值附近最差,继续加宽反而变好。会开始过拟合的条件是数据规模相对任务太小、或重复 epoch 太多:第四篇的实验里,小语言模型训 1 epoch 的训练 / 验证 loss 几乎重合,训到 10 epoch 时验证 loss 回升、模型开始逐字记忆训练文本;预训练的实践是数据过一遍(≤ 4 epoch 以内收益接近新数据,之后急剧下降),weight decay 与 dropout 把训练 - 验证差距压小但不改变这个趋势。 ↩
本文由 arganzheng 创作,采用 CC BY 4.0 许可协议。在保留原文作者、署名以及完整原文链接(https://arganzheng.life/deep-learning-foundations.html)的前提下,欢迎各种形式的转载、翻译或商业引用。
COMMENTS
评论存放在 GitHub Discussions, 用 GitHub 账号登录即可发表,支持 Markdown。 想针对正文某句话说?选中那段文字,点浮出的「评论」即可划线评论;觉得哪里写错了,发表时勾上「同时提交 Issue」。 有人回复你时 GitHub 会按你的通知设置发邮件,不用守在这里。