邱锡鹏《神经网络与深度学习》课后题推导与numpy实现复盘
《神经网络与深度学习》这本书我刷过两轮。第一轮只做概念题把答案在心里过一遍就算完成第二轮才老老实实把每一个推导写在纸上把编程题用 numpy 从头重写了一遍。两轮下来的结论很直接邱锡鹏老师这本书的课后题不是用来对答案的它的真实作用是把你符号习惯和维度直觉上的漏洞一个个揪出来。很多同学刷完感觉看懂了一到自己动手写反向传播、算卷积输出尺寸、推参数初始化方差的时候就卡住问题基本都出在这一步——答案看的是别人的推导结果而没有形成自己能复现的推导链路。这篇整理面向三类人正在跟这本书自学、需要一份推导思路参考的准备深度学习相关课程期末和考研复试的以及从 MATLAB 工具箱体系转过来、想搞清楚底层公式的从业者。下面这些内容是我自己做完题之后的复盘笔记包含推导主线、手算示例、容易写错的地方以及编程题的提交标准。所有数学符号统一按列向量约定章节编号按我手上的印次不同版本可能有微调核对时以你手上的目录为准。1. 先想清楚这本习题集值得刷哪几章1.1 按题型密度给章节排个优先级这本书的覆盖面很宽从线性模型一直铺到深度强化学习和序列生成。全刷一遍的时间成本很高而且后几章的习题难度跳跃明显。我的做法是先按习题类型密度排优先级推导题密集的章节优先纯概念复述的章节放在通勤时间看。章节主要内容习题类型建议投入第2章机器学习概述、泛化误差、偏差方差概念题、简单推导必做概念题的坑比想象中多第3章线性回归、logistic 回归、softmax 回归推导题密集必做全书的推导地基第4章前馈神经网络、反向传播、初始化推导题、手算题必做性价比最高的一章第5章卷积神经网络、参数与计算量计算题、概念题必做计算题几乎年年考第6章循环神经网络、BPTT、LSTM推导题建议做练链式法则的延伸第7章网络优化与正则化证明题、比较题必做工程上用得上第8章注意力机制与外部记忆概念为主选做重点看公式结构第9章及以后无监督学习、概率图、生成模型、强化学习偏难、篇幅长按需先看目录再决定判断依据很朴素前三章的推导工具链式法则、矩阵求导、最大似然会在后面所有章节反复出现这几章的题做透了后面大多数推导题只是换个符号重新走一遍流程。反过来如果第3章的 softmax 雅可比矩阵还没推利索直接冲第7章的优化器证明题大概率是抄答案。1.2 我的答案整理格式三栏笔记法很多人整理答案就是把书上的答案抄一遍或者把别人博客里的推导截图贴进笔记。这种笔记的复用价值几乎为零。我用的是一个三栏结构用 Markdown 加 LaTeX 写每道题占三块第一栏题目条件与出处。写清楚已知什么、求什么、用到了哪些前置结论。这一步是为了防止看答案时觉得显然自己做时不知道该从哪出发。第二栏完整推导。一行一行写不许跳步。凡是从上一行到下一行用到了恒等式变形、换元、放缩、求导规则都在行尾用括号标注用了什么。第三栏我当时卡在哪。这一栏才是真正值钱的地方。比如我一开始把 ∂L/∂W 写成了 δ 左乘 h维度对不上、我以为 sigmoid 的导数可以直接代 f(z)(1-f(z))忘了这一步的前提是同一层的输出。第三栏写多了之后会发现一个规律卡点高度集中在少数几类问题上其中出现频率最高的一类就是符号和维度没统一。所以下一节先把这个前置问题解决掉。2. 符号与维度整理答案前必须先统一的三件事2.1 全部改成列向量约定这本书用的是列向量约定x 是 d×1 的列向量全连接层的线性变换写成 W x其中 W 是 d_out × d_in。批量计算时堆成矩阵 X形状是 d_in × N前向传播就是 W X。这个约定本身没问题问题是很多人一边看书上的公式一边按 NumPy 里样本在前、特征在后的形状写代码两套习惯混着用转置符号就开始乱飞。我的处理方式是推导一律按列向量约定写在纸上代码里单独用一个注释块记录矩阵视角的形状和代码视角的形状的对应关系。比如# 数学约定: X: (d_in, N) W: (d_out, d_in) Z W X b # 代码视角: 样本在行上 x_batch: (N, d_in) - 需要 x_batch W.T b这一行注释看起来琐碎但在你三天后回头看自己的实现时能省下大量时间。判断矩阵乘法的方向其实有个笨办法只看输出形状对不对不对就转置。2.2 批量维度的位置决定了广播怎么写偏置的加法是最典型的广播陷阱。Z W X b 里b 是 d_out × 1加到一个 d_out × N 的矩阵上靠的是广播机制把 b 沿列方向复制 N 次。数学上这是把同一个偏置向量加到每个样本上代码上它是一次广播。如果你把 b 定义成 (d_out,) 的一维数组在某些形状下会静默地广播到错误的位置结果不报错但结果全错。我见过最隐蔽的一次错误是在计算批量梯度时把 db 写成了dz2.sum(axis1)忘了 keepdims得到一个 (d_out,) 的一维数组。后面做参数更新时 (d_out, 1) 和 (d_out,) 相加又触发了广播变成一个 (d_out, d_out) 的矩阵形状检查没做的话要等几轮迭代后发现参数数量莫名其妙变多才会察觉。结论就一句话所有和批量有关的求和一律带 keepdimsTrue。2.3 矩阵求导的两套写法别混用矩阵求导有分母布局和分子布局两套约定同一个表达式的形状可能差一个转置。这本书里用的是分母布局也就是对向量 y 求标量 L 的导数记成 ∂L/∂y形状和 y 一致。但你随手搜到的很多博客用的是分子布局直接抄很容易翻车。我的做法是不依赖布局约定而是走标量—向量化—维度检查三步先取单个元素写标量形式的偏导比如 ∂L/∂w_ij把标量结果按索引关系拼成矩阵或向量形式最后校验形状是否和自变量一致不一致就转置。第三步不是形式主义。带批量维度的层里绝大多数推导错误都会在形状校验这一步被抓出来。常见表达式结果形状记忆方式∂(wᵀx)/∂w与 w 同形结果是列向量∂(xᵀAx)/∂x与 x 同形对称时等于 2Ax∂L/∂WL 为标量与 W 同形形状永远跟着分母∂(Wx)/∂xW线性映射的雅可比就是矩阵本身3. 线性模型部分解析解、梯度与概率解释3.1 最小二乘的两个等价视角第三章里最常见的推导题是从平方误差出发求最优参数。设 X 是 d×Ny 是 N×1目标函数 L(w) ‖Xᵀw − y‖²。把范数展开成 (Xᵀw − y)ᵀ(Xᵀw − y)对 w 求导2X(Xᵀw − y) 0得到正规方程 XXᵀw Xy当 XXᵀ 可逆时 w (XXᵀ)⁻¹Xy。这里容易踩的坑有两条。第一忘记书里的 X 是特征在行、样本在列的排法如果按另一种排法抄X 和 Xᵀ 需要对调最后得到的闭式解形式就差了转置。第二很多人只记住结论不写展开过程一旦题目改成加权最小二乘或者带约束的形式就写不动了。我的建议是把展开和求导写两遍第二遍刻意不看第一遍。3.2 logistic 回归的梯度为什么总写成 (σ − y)x二元分类下 p(y1|x) σ(wᵀx)用负对数似然做损失L −[y log σ (1−y) log(1−σ)]。对 w 求导的关键一步是利用 sigmoid 的导数性质 σ σ(1−σ)∂L/∂w −[y(1−σ) − (1−y)σ]x (σ − y)x这个结果形式极其简洁原因是它恰好和线性回归里的 (ŷ − y)x 完全一致。这个一致性不是巧合它来自指数族分布的一般结论当输出层用对数似然对应的连接函数时代价函数的梯度都退化成预测误差乘输入。理解这一点之后softmax 回归的梯度写成 (ŷ − y)x 就不用死记了。3.3 softmax 的雅可比矩阵与外积形式设 z 是 logits 向量ŷᵢ e^{zᵢ}/Σₖe^{zₖ}。求 ∂ŷᵢ/∂zⱼ 要分两种情况i j 时用商的求导法则得到 ŷᵢ(1 − ŷᵢ)i ≠ j 时得到 −ŷᵢŷⱼ。写成矩阵形式就是∂ŷ/∂z diag(ŷ) − ŷŷᵀ把两个分支合并成一句对角阵减去外积是这道题最重要的记忆锚点。进一步如果再套一层交叉熵损失 L −Σᵢ yᵢ log ŷᵢ把雅可比矩阵代进去化简会得到∂L/∂z ŷ − y也就是说雅可比矩阵在这个组合里被消掉了大半最终结果和 logistic 回归长得一模一样。做这道题的时候建议保留中间步骤因为有些考试题会单独问如果损失换成平方误差梯度是否还这么干净——答案是否定的会多出一个雅可比因子这正好说明为什么分类任务默认用交叉熵而不是平方误差。3.4 从线性回归到岭回归加上一项之后的闭式解加上 L2 正则项之后目标函数变成 ‖Xᵀw − y‖² λ‖w‖²对 w 求导得 2XXᵀw − 2Xy 2λw 0解出 w (XXᵀ λI)⁻¹Xy。这道题的价值不在于公式本身而在于两个性质一是加上 λI 之后括号里的矩阵一定可逆半正定加上正定所以不用再担心共线性导致解不存在二是 λ → 0 时退化回普通最小二乘λ → ∞ 时 w 趋向零。把 w 写成 (XXᵀ λI)⁻¹Xy 之后还可以顺手做一个特征值分解的视角如果 XXᵀ 的特征值是 sᵢ那么 (XXᵀ λI)⁻¹ 的特征值就是 1/(sᵢ λ)。λ 对小的 sᵢ 影响最大也就是对数据里信息量最少的那几个方向收缩最狠。这个视角在后面的正则化和网络优化章节会再次出现。4. 反向传播相关习题把链式法则写成可执行的递推4.1 误差项 δ 的递推与四个基本公式第4章的推导题基本围绕同一个核心定义第 l 层的误差项 δ⁽ˡ⁾ ∂L/∂z⁽ˡ⁾然后把它递推到下一层。以全连接网络为例a⁽ˡ⁾ f(z⁽ˡ⁾)z⁽ˡ⁾ W⁽ˡ⁾a⁽ˡ⁻¹⁾ b⁽ˡ⁾可以得到四条公式输出层误差δ⁽ᴸ⁾ ∂L/∂a⁽ᴸ⁾ ⊙ f(z⁽ᴸ⁾)层间递推δ⁽ˡ⁾ (W⁽ˡ⁺¹⁾ᵀδ⁽ˡ⁺¹⁾) ⊙ f(z⁽ˡ⁾)参数梯度∂L/∂W⁽ˡ⁾ δ⁽ˡ⁾(a⁽ˡ⁻¹⁾)ᵀ∂L/∂b⁽ˡ⁾ δ⁽ˡ⁾批量形式δ⁽ˡ⁾ 的每一列对应一个样本参数梯度把样本维求和这四条公式里第二条是绝大多数人写错的地方错误形式一般是漏掉 ⊙ f(z⁽ˡ⁾)或者把 Wᵀ 写成 W。用维度检查可以立刻发现δ⁽ˡ⁾ 的形状是 n_l × NW⁽ˡ⁺¹⁾ 的形状是 n_{l1} × n_l所以 W⁽ˡ⁺¹⁾ᵀδ⁽ˡ⁺¹⁾ 的形状是 n_l × N正好对得上。写成 W 的话形状直接不匹配。4.2 一个两层网络的手算示例光看公式不够手算一次才能记住。构造一个 2-2-1 的网络输入 x (1, 1)ᵀ隐层用 tanh输出层用 sigmoid目标 y 1。前向W⁽¹⁾ [[0.5, −0.3], [0.2, 0.4]]b⁽¹⁾ 0得 z⁽¹⁾ (0.2, 0.6)ᵀh tanh(z⁽¹⁾) (0.19738, 0.53705)ᵀ。第二层权重 w⁽²⁾ (0.6, −0.8)b⁽²⁾ 0得 z⁽²⁾ 0.6×0.19738 − 0.8×0.53705 −0.31121输出 ŷ σ(−0.31121) 0.42283损失 L −log 0.42283 0.8607。反向δ⁽²⁾ ŷ − y −0.57717。然后算 δ⁽¹⁾需要 tanh 的导数 1 − h²两部分分别是 1 − 0.19738² 0.96104 和 1 − 0.53705² 0.71158。于是δ⁽¹⁾ (w⁽²⁾ ⊙ (1 − h²)) × δ⁽²⁾ (0.6×0.96104, −0.8×0.71158) × (−0.57717) (−0.33283, 0.32857)参数梯度∂L/∂w⁽²⁾ δ⁽²⁾hᵀ (−0.11393, −0.30999)∂L/∂W⁽¹⁾ δ⁽¹⁾xᵀ [[−0.33283, −0.33283], [0.32857, 0.32857]]。这一步算完你会对误差项在层间传播时要乘当前层的激活导数这件事形成肌肉记忆。后面遇到卷积层、循环层本质只是把 ⊙ f 换成对应的结构。4.3 梯度消失的量化从连乘项到激活函数的选择梯度消失的证明题思路是看层间递推连乘之后的形式。把 δ⁽ˡ⁾ 展开δ⁽ˡ⁾ (W⁽ˡ⁺¹⁾ᵀ … W⁽ᴸ⁾ᵀ δ⁽ᴸ⁾) ⊙ f(z⁽ˡ⁾) ⊙ … ⊙ f(z⁽ᴸ⁻¹⁾)问题的关键是这个连乘的规模。用 sigmoid 时 f 的最大值是 0.25也就是每穿过一层至少乘一次小于 0.25 的因子穿过十层就是 0.25¹⁰ 量级即使用 tanh在饱和区导数同样接近零。而权重矩阵的谱范数如果也不大连乘整体会指数衰减。这道题的答题要点是分两步先说明连乘形式再分别讨论激活函数导数上界和权重矩阵的范数这两个来源。只写因为 sigmoid 导数小会被扣分因为它只解释了其中一半。另外要顺带提一句缓解手段——换 ReLU正区间导数恒为 1、用残差连接给梯度提供恒等shortcut、做合理的初始化让每层方差大致守恒这三点正好对应后面几节的内容。4.4 参数初始化方差的推导初始化题的核心是控制信号方差。设输入 a 的各个分量独立、均值为零、方差为 σ²_a权重独立同分布、均值为零、方差为 σ²_w那么 z Σᵢ wᵢaᵢ 的方差是 σ²_z n_in σ²_w σ²_a。要让输出方差等于输入方差需要 σ²_w 1/n_in这叫前向保持。反过来考虑反向的误差项从后往前传播时对应的维度是 n_out同理需要 σ²_w 1/n_out。两个条件不能同时满足取折中就得到 Xavier 初始化σ²_w 2/(n_in n_out)如果用 ReLU负半轴输出为零相当于一半的激活被砍掉方差减半所以为了补回来要放大一倍得到 He 初始化σ²_w 2/n_in这道题的失分点通常在两处一是没写清楚为什么用 2/(n_inn_out)直接背公式二是没解释 ReLU 情况下的两倍来源。另外把 σ²_w 换算成均匀分布采样区间时要注意均匀分布 U(−a, a) 的方差是 a²/3所以 a √(3σ²_w)这一步在代码里写错的人不少。5. 卷积与循环章节公式题一律先手算一遍5.1 输出尺寸、参数量、计算量的速算第5章的计算题重复率很高核心就三个公式。输出尺寸给定输入边长 H、卷积核 K、填充 P、步长 S输出是H_out ⌊(H 2P − K)/S⌋ 1参数量单层卷积是 C_out × (C_in × K × K 1)。计算量按乘加次数算是 H_out × W_out × C_out × C_in × K × K折算成浮点运算还要乘 2。例子输入配置输出参数量AlexNet 首层224K11, S4, P25564×(3×1211)23,296ResNet 首层224K7, S2, P311264×(3×491)9,4723×3 同尺寸卷积56K3, S1, P156256×(256×91)589,8241×1 降维56K1, S1, P05664×(256×11)16,448从表里能看出一个工程直觉3×3 卷积的参数量按 C_in × C_out × 9 增长所以在通道数上千的层里单个卷积层就是几十万参数。1×1 卷积的作用不是提取空间特征而是调整通道数这也是很多网络里先用 1×1 降通道再做 3×3 的原因。计算量可以再验算一个输入 224×224×364 个 3×3 卷积P1、S1输出 224×224×64乘加次数是 224×224×64×3×9 ≈ 8.67×10⁷约 86.7M MACs。这类估算题要养成写单位的习惯写成约 0.87 亿次乘加比写一串数字更清楚。5.2 感受野的递推与常见错解感受野的递推有两个量当前感受野 r 和相邻特征点之间的跳距 j初始 r 1、j 1。每经过一层r ← r (K − 1) × j然后 j ← j × S。举个例子5×5 卷积S1接 3×3 卷积S2再接 3×3 卷积S1初始 r1, j1过 5×5r 1 4×1 5j 1过 3×3 S2r 5 2×1 7j 2过 3×3 S1r 7 2×2 11j 2最终感受野 11。错解通常有两种一种是直接算 1 4 2 2 9忽略了跳距的累积另一种是每层都把 j 重置为 1。记住跳距会累积越靠后的层每扩张一格代价越大这句话就不容易错。随手可以验证一个经典结论三层连续的 3×3 卷积S1感受野是 1 2 2 2 7正好等于一个 7×7 卷积的感受野但参数量是 3×3² 27 对 7² 49而且多两次非线性。这就是 3×3 堆叠流行的原因之一。5.3 图像任务为什么不用全连接前馈网络这道概念题在作业和考试里出现的频率极高答题要落到三个层面上缺一个都不完整。参数规模层面假设输入是 224×224×3第一个全连接隐层设 1000 个神经元参数量是 150,528 × 1000 ≈ 1.5 亿仅一层就吃满显存而一个 64 通道的 3×3 卷积只有两万多参数。因为全连接层对每个位置都配了独立的权重而卷积层在所有位置共享同一组核。结构先验层面图像有两个统计特性——局部相关性和平移等变性。卷积通过局部连接和权重共享把这两个先验直接编码进模型结构里等于给模型省掉了自己从数据里学出平移不变性这一大笔样本开销。层级特征层面卷积加池化的堆叠天然形成边缘—纹理—部件—物体的层级表示浅层感受野小、关注局部细节深层感受野大、关注整体语义。全连接层每一层都看全图反而丢掉了这种由局部到整体的归纳偏置。补一句加分项如果一定要用全连接处理图像也不是完全不行早期做法是把图像拉平成向量接 MLP只是需要的数据量和参数规模都大得多且对位置变化不鲁棒。5.4 RNN 的 BPTT 与梯度爆炸的判据循环网络的推导是把上一章的反向传播沿时间展开。以 h_t tanh(W h_{t−1} U x_t b) 为例误差项沿时间反向递推δ_t Vᵀ(ŷ_t − y_t) Wᵀ diag(1 − h_{t1}²) δ_{t1}参数梯度是把所有时间步加起来∂L/∂W Σ_t δ_t h_{t−1}ᵀ。梯度爆炸和消失的判据来自递推里的雅可比矩阵 J_t Wᵀ diag(1 − h_t²)。梯度沿时间传播 k 步涉及 J 的连乘。如果 J 的最大奇异值大于 1梯度随步数指数增长出现爆炸小于 1 则指数衰减。工程上最直接的处置是梯度裁剪把整个参数梯度的范数按比例缩放到阈值以内结构上则用 LSTM 或 GRU 的加性门控路径来替代纯乘法路径。踩坑提醒计算 BPTT 的时候δ_t 里同时包含来自当前时刻输出的直接贡献和来自下一时刻的反向贡献两部分。只写后一项是最常见的漏项错误会导致训练时梯度偏小、收敛偏慢但因为不报错非常难查。5.5 LSTM 门控公式的记忆锚点LSTM 的公式看着长其实可以按三个门 一条记忆线来记遗忘门 f_t 决定旧记忆 c_{t−1} 保留多少输入门 i_t 决定候选记忆 c̃_t 写入多少输出门 o_t 决定记忆暴露给隐状态 h_t 的部分记忆线的更新是 c_t f_t ⊙ c_{t−1} i_t ⊙ c̃_t这是一条加性通路。关键在于那条加法。c_t 对 c_{t−1} 的偏导里有一项直接是 f_t不需要连乘 W 和激活导数。如果 f_t 接近 1梯度可以近乎无损地跨过很多时间步这就是它能缓解梯度消失的结构性原因。答题时把这一点写出来比罗列公式更有说服力。6. 优化与正则化最容易看着会、写着错的一章6.1 L2 正则就是高斯先验这道证明题的标准做法是从最大后验估计出发。参数后验的对数是log p(w|D) ∝ log p(D|w) log p(w)假设参数先验是零均值高斯 p(w) N(0, σ_w²I)那么 log p(w) −‖w‖²/(2σ_w²) 常数。取负号做最小化得到L_MAP L_NLL ‖w‖²/(2σ_w²)对比常见的写法 L λ‖w‖²/2可以看出 λ 1/σ_w²。也就是说正则化系数越大对应的先验方差越小先验越强、参数被压得越狠。同理可以推 L1 正则对应拉普拉斯先验这也是 L1 更容易产生稀疏解的原因——拉普拉斯分布在零点有尖峰。6.2 Dropout 的集成解释与推理期缩放Dropout 的题目一般问两件事它为什么能正则化以及推理阶段怎么处理。集成视角下训练时按概率 p 随机屏蔽神经元等价于在指数多个子网络里各采样一次并共享参数。推理时用全部连接并对权重按 p 缩放是在近似这些子网络预测的期望。对线性模型这个近似是精确的随机屏蔽等价于把输入乘上一个期望为 p 的随机掩码所以推理时输出要乘 p 才对得上。实践中更常见的做法是反向 Dropout也就是训练时把保留的激活除以 p推理时不做任何缩放。这样推理路径干净不会因为忘记缩放而出现输出整体偏移。这也是主流框架里的默认实现方式但要注意如果用自己写的 numpy 实现一定要想清楚用的是哪一种否则训练和推理的尺度会对不上表现为现象就是验证集指标明显低于训练集。6.3 BatchNorm 训练与推理的差异批归一化的公式本身不难算批内均值 μ_B 和方差 σ_B²标准化后做仿射变换 y γx̂ β。真正要写清楚的是两个阶段的行为差异。训练阶段用当前批次的统计量同时用滑动平均维护一份全局统计量running_mean ← (1 − m)·running_mean m·μ_B。推理阶段不再看当前批次而是直接用这份全局统计量。原因很直接推理时输入可能只有一条样本批内方差在数学上就没有意义。有一道常见题问为什么 BN 能加速训练答案至少有两条一是它把每层输入的分布拉回相对固定的范围减少了内部协变量偏移二是从优化角度看它对参数做了尺度不变性处理使得损失曲面在不同方向的曲率更接近允许更大的学习率。第二条更接近现代的解释推荐一起写上。阶段统计量来源是否更新滑动统计是否需要批次训练当前批次是是且批次不宜过小推理滑动平均否否可单样本6.4 优化器更新公式对照表优化器部分的习题经常要求写公式并做对比整理成表格最省事。方法状态量更新参数更新标准梯度下降无θ ← θ − ηg动量法v ← αv − ηgθ ← θ vNesterov在 θ αv 处取梯度θ ← θ vAdaGradG ← G g⊙gθ ← θ − ηg/(√Gε)RMSPropG ← βG (1−β)g⊙gθ ← θ − ηg/(√Gε)Adamm,v 加偏差修正后 m̂、v̂θ ← θ − ηm̂/(√v̂ε)Adam 的偏差修正经常被问初始化时 m 和 v 都是零向量前几步的估计会偏向零需要除以 (1 − β^t) 来校正。这个细节不写清楚公式只有一半。实操上还有一个知识点值得写进笔记Adam 里给权重做 L2 正则和把梯度加进 g 里不等价后者会被自适应学习率按比例缩放效果变弱。这就是 AdamW 出现的原因——把权重衰减从梯度里摘出来直接作用在参数更新上。7. 编程题怎么交才算合格7.1 纯 numpy 两层网络的最小骨架编程题的评分点通常不是准确率而是实现是否干净、梯度是否正确。下面这个骨架我用了很多次输入是 (d_in, N)标签是 one-hot 的 (d_out, N)全程保持列向量约定。import numpy as np def init(d_in, d_h, d_out, seed0): rng np.random.RandomState(seed) return { W1: rng.randn(d_h, d_in) * np.sqrt(2.0 / d_in), b1: np.zeros((d_h, 1)), W2: rng.randn(d_out, d_h) * np.sqrt(2.0 / d_h), b2: np.zeros((d_out, 1)), } def forward(params, X): z1 params[W1] X params[b1] a1 np.maximum(z1, 0.0) # ReLU z2 params[W2] a1 params[b2] z2 z2 - z2.max(axis0, keepdimsTrue) # 数值防溢出 e np.exp(z2) p e / e.sum(axis0, keepdimsTrue) return p, (X, z1, a1, p) def backward(params, cache, Y): X, z1, a1, p cache N X.shape[1] dz2 (p - Y) / N dW2 dz2 a1.T db2 dz2.sum(axis1, keepdimsTrue) dz1 (params[W2].T dz2) * (z1 0) dW1 dz1 X.T db1 dz1.sum(axis1, keepdimsTrue) return {W1: dW1, b1: db1, W2: dW2, b2: db2}两处细节值得单独说。softmax 之前的减最大值操作不是可选项指数遇到较大的 logits 会直接溢出成 inf除以 inf 得到 nan训练直接崩掉。除以 N 的位置也很关键放在 dz2 上等价于对批内样本取平均损失放在后面某一步再除会导致学习率随批量大小漂移。7.2 梯度检查数值梯度与相对误差写完反向传播第一件事是做梯度检查。用中心差分近似def numeric_grad(f, theta, eps1e-4): g np.zeros_like(theta) it np.nditer(theta, flags[multi_index]) while not it.finished: i it.multi_index old theta[i] theta[i] old eps; fp f(theta) theta[i] old - eps; fm f(theta) theta[i] old g[i] (fp - fm) / (2 * eps) it.iternext() return g def rel_err(a, b): return np.abs(a - b) / max(1e-8, np.abs(a) np.abs(b))判断标准按经验是相对误差在 1e-7 以下说明基本正确1e-4 到 1e-7 之间要留意超过 1e-2 基本可以确定实现有 bug。做检查时记得把网络规模调小比如两层、每层几个神经元、批量几条样本并且关掉 Dropout 之类的随机操作否则差分结果会被随机性污染。eps 也不能取太小1e-4 到 1e-6 是比较常用的范围太小会被浮点精度吃掉有效位。7.3 从 MATLAB 工具箱思维切过来要注意什么不少人是先用 MATLAB 的神经网络工具箱做拟合曲线入门的那个体系里网络结构用一行配置就能建起来训练过程被封装成一次调用。切到公式推导和手写实现时思维上要转三个弯。第一工具箱里你面对的是层类型 超参的配置表单而手写实现里你面对的是权重矩阵、偏置向量和它们的梯度形状必须自己对。第二MATLAB 默认是按列存数据特征在行这一点和书上的列向量约定其实是一致的所以在 MATLAB 里推导顺手的人切到 NumPy 时反而容易因为样本排布习惯不同而出错。第三工具箱会替你处理数据归一化、权重初始化、训练轮次这些事手写的时候每一项都要显式决定少一项就可能不收敛。反过来说MATLAB 那套经验也不是白费的你在调拟合曲线时形成的看到损失曲线形状就知道是学习率大了还是初始化不好的直觉在手写实现里照样有效。7.4 训练不收敛时的排查顺序我自己的排查链路是固定的按这个顺序走基本能在十几分钟内定位问题先查数据和标签是否对齐。最常见的问题是 one-hot 的类别顺序和输出层通道顺序不一致表现为准确率卡在随机水平。做法是取一条样本打印预测分布看最大值是否落在正确类别上。再做梯度检查。上一步没问题就直接跑数值梯度看相对误差。梯度错了后面所有调参都是在错误的基础上瞎试。接着做小样本过拟合测试。取 20 条样本关掉所有正则化和数据增强看模型能不能把训练损失压到接近零。压不下去说明模型容量或实现有问题能压下去但验证集不涨才轮到讨论正则化和数据量。然后看学习率。损失曲线震荡不下就是学习率偏大下降极其缓慢是偏小。可以先跑几组不同量级的学习率做粗筛。最后才看初始化和归一化。这一步顺序放在最后是因为前四步的问题更常见也更致命。踩过的坑里有一个印象很深有一次训练损失一直在一某个值附近打转查了两个小时才发现是数据加载时把标签和特征的顺序弄反了模型在学一个不可能的任务。从那以后我给自己定了个规矩任何训练脚本跑起来之前先写两行代码打印一个批次的形状和几条样本肉眼确认一遍。8. 我的答案复查清单与资料搭配8.1 提交前的十条自检整理完一份答案我会按下面这张清单过一遍。这个习惯帮我抓出过大量低级错误。每一个矩阵乘法两边的形状是否都写过一遍输出形状是否与预期一致批量维度上的求和是否都带了 keepdims求导结果是否做过分量检验至少代入一个具体的 i、j 数值验证交叉熵、softmax、log 相关的位置是否有防溢出处理反向传播的递推是否包含了所有来源的贡献尤其是循环网络里的直接项参数初始化是否按激活函数选了对应的缩放系数训练和推理阶段的差异化操作Dropout、BN是否分别写清楚公式中的符号是否与代码变量一一对应包括下标从 0 还是从 1 开始有没有跳步。凡是从 A 到 B 需要两步以上变形的都补上中间式最后通读一遍问自己如果一周后只看这份笔记能不能独立复现整个推导8.2 和哪些资料搭配效果更好这本书的理论密度高单靠它一个人啃会有断层。我自己的搭配方式是理论推导以这本书为主线代码实现部分补《动手学深度学习》梯度计算和底层细节补《深度学习入门》那本偏动手的小册子整体直觉和优化技巧部分可以参考公开的深度学习课程。这样组合的好处是理论和代码能互相验证手写一遍之后再回头看公式理解会明显加深。另外提一个容易忽略的点不同教材的符号习惯差别很大混着看的时候要给自己留一个符号对照表。比如有的资料把批量维度放在第 0 维有的放在最后一维有的把权重矩阵写成 Wᵀx 的形式。看到一个新公式先花十秒确认它的形状约定比事后调试半天要划算。我在实际操作中的体会是吃透这本书的课后题最大的收获不是记住了一堆结论而是养成了一个固定的动作——写任何公式之前先确认维度写完之后先做数值验证。这个动作在后面看论文、复现模型的时候省下的时间远比刷题花掉的多。
上一篇/下一篇内容由系统自动关联
返回资讯列表 →