<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>自动求导 on Zewang's Blog</title><link>https://zewang0217.github.io/tags/%E8%87%AA%E5%8A%A8%E6%B1%82%E5%AF%BC/</link><description>Recent content in 自动求导 on Zewang's Blog</description><generator>Hugo -- gohugo.io</generator><language>zh</language><lastBuildDate>Sun, 19 Jul 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://zewang0217.github.io/tags/%E8%87%AA%E5%8A%A8%E6%B1%82%E5%AF%BC/index.xml" rel="self" type="application/rss+xml"/><item><title>Backprop Ninja：手动反向传播修炼指南</title><link>https://zewang0217.github.io/p/backprop-ninja-manual/</link><pubDate>Sun, 19 Jul 2026 00:00:00 +0000</pubDate><guid>https://zewang0217.github.io/p/backprop-ninja-manual/</guid><description>&lt;h1 id="backprop-ninja手动反向传播修炼指南"&gt;Backprop Ninja：手动反向传播修炼指南
&lt;/h1&gt;
 &lt;blockquote&gt;
 &lt;p&gt;资料源：Andrej Karpathy &amp;ldquo;Building makemore Part 4: Becoming a Backprop Ninja&amp;rdquo;
YouTube：https://www.youtube.com/watch?v=q8SA3rM6ckI
Notebook：https://colab.research.google.com/drive/1WV2oi2fh9XXyldh02wupFQX0wh5ZC-z-&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;h2 id="为什么手动做反向传播"&gt;为什么手动做反向传播？
&lt;/h2&gt;&lt;p&gt;Karpathy 把反向传播称为 &lt;strong&gt;&amp;ldquo;leaky abstraction&amp;rdquo;（有漏洞的抽象）&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;PyTorch 的 &lt;code&gt;loss.backward()&lt;/code&gt; 让梯度计算变成了魔法——你搭好网络，调一下 API，梯度就自动出来了。看起来好像&amp;quot;堆砌可微分的乐高积木&amp;quot;就能工作，但现实是：&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;&amp;ldquo;It will magically not work or not work optimally, and you will need to understand how it works under the hood if you&amp;rsquo;re hoping to debug it.&amp;rdquo;&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;p&gt;当你遇到梯度爆炸、梯度消失、训练不收敛时，&lt;code&gt;loss.backward()&lt;/code&gt; 不会告诉你哪里出了问题。你需要理解梯度是怎么流经计算图的。&lt;/p&gt;
&lt;p&gt;这个视频（和之前的 micrograd）的核心理念：&lt;strong&gt;理解 autograd 内部机制，才能有效调试和优化神经网络。&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id="先修知识"&gt;先修知识
&lt;/h2&gt;&lt;p&gt;视频基于 Part 3 构建的 2 层 MLP（带 BatchNorm），用于字符级语言建模：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;输入&lt;/strong&gt;：32 个样本，每样本 3 个字符（上下文长度）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;词嵌入&lt;/strong&gt;：27 个字符 → 10 维向量&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;隐藏层&lt;/strong&gt;：30 维 → 64 个神经元&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;输出&lt;/strong&gt;：64 → 27（词汇表大小）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;完整数据流如下图所示（前向从左到右，参数在右侧）：&lt;/p&gt;
&lt;p&gt;&lt;img class="gallery-image" data-flex-basis="250px" data-flex-grow="104" height="2430" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://zewang0217.github.io/p/backprop-ninja-manual/fig01_overall_flow.png" srcset="https://zewang0217.github.io/p/backprop-ninja-manual/fig01_overall_flow_hu_a8e47354f1d8ac05.png 800w, https://zewang0217.github.io/p/backprop-ninja-manual/fig01_overall_flow_hu_22c52514cd34ebe3.png 1600w, https://zewang0217.github.io/p/backprop-ninja-manual/fig01_overall_flow.png 2532w" width="2532"&gt;&lt;/p&gt;
&lt;p&gt;这个网络我们之前用 &lt;code&gt;loss.backward()&lt;/code&gt; 自动算梯度。现在我们要&lt;strong&gt;关掉 autograd&lt;/strong&gt;，手写每一步的反向传播。&lt;/p&gt;
&lt;h2 id="exercise-1cross-entropy-loss-反向传播"&gt;Exercise 1：Cross-Entropy Loss 反向传播
&lt;/h2&gt;&lt;h3 id="softmax-的梯度"&gt;Softmax 的梯度
&lt;/h3&gt;&lt;p&gt;输出层是 softmax + 负对数似然。先回顾正向传播：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 正向&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;logits&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;h2&lt;/span&gt; &lt;span class="c1"&gt;# (32, 27) batch=32, vocab=27&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;counts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;logits&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;exp&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c1"&gt;# 取指数&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;probs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;counts&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;counts&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;keepdim&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# 归一化 → 概率&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;loss&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;probs&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;ys&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mean&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c1"&gt;# 交叉熵&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;反向传播需要计算 &lt;code&gt;dlogits&lt;/code&gt;。这里有两个关键点：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1. 广播（Broadcasting）&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;counts.sum(1, keepdim=True)&lt;/code&gt; 的形状是 &lt;code&gt;(32, 1)&lt;/code&gt;，而 &lt;code&gt;counts&lt;/code&gt; 是 &lt;code&gt;(32, 27)&lt;/code&gt;。PyTorch 会自动把 &lt;code&gt;(32, 1)&lt;/code&gt; 广播到 &lt;code&gt;(32, 27)&lt;/code&gt;——这意味着一行求和值被复制了 27 份。&lt;/p&gt;
&lt;p&gt;反向传播时，广播的反向操作是 &lt;strong&gt;sum&lt;/strong&gt;：梯度需要累加回被广播的维度。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2. Softmax 梯度的简洁形式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;经过链式法则推导，cross-entropy + softmax 的反向传播可以简化为一个极其优雅的形式：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;dlogits&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;probs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;copy&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;dlogits&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;ys&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;-=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="c1"&gt;# 正确类别的概率减 1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;dlogits&lt;/span&gt; &lt;span class="o"&gt;/=&lt;/span&gt; &lt;span class="mi"&gt;32&lt;/span&gt; &lt;span class="c1"&gt;# 除以 batch size（因为 loss 取了 mean）&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;下图展示了从 &lt;code&gt;logits&lt;/code&gt; 到 &lt;code&gt;loss&lt;/code&gt; 的 8 步正向操作，以及在 Exercise 2 中如何一步合并：&lt;/p&gt;
&lt;p&gt;&lt;img class="gallery-image" data-flex-basis="219px" data-flex-grow="91" height="1965" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://zewang0217.github.io/p/backprop-ninja-manual/fig02_ce_gradient.png" srcset="https://zewang0217.github.io/p/backprop-ninja-manual/fig02_ce_gradient_hu_f507a2d6185d1014.png 800w, https://zewang0217.github.io/p/backprop-ninja-manual/fig02_ce_gradient_hu_383a8394772c3c78.png 1600w, https://zewang0217.github.io/p/backprop-ninja-manual/fig02_ce_gradient.png 1797w" width="1797"&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;直觉&lt;/strong&gt;：如果你的模型预测了 &lt;code&gt;[0.2, 0.5, 0.3]&lt;/code&gt;，而正确答案是第二个类别（索引 1），那么梯度就是 &lt;code&gt;[0.2, -0.5, 0.3]&lt;/code&gt;。负号告诉模型&amp;quot;提高这个类别的分数&amp;quot;，正号说&amp;quot;降低其他类别的分数&amp;quot;。&lt;/p&gt;
&lt;p&gt;所有梯度之和为 0——这不是巧合，这是 softmax 输出概率和为 1 的必然结果。&lt;/p&gt;
&lt;h2 id="exercise-2线性层和-tanh-的反向传播"&gt;Exercise 2：线性层和 Tanh 的反向传播
&lt;/h2&gt;&lt;h3 id="matmul-反向传播"&gt;MatMul 反向传播
&lt;/h3&gt;&lt;p&gt;对于 &lt;code&gt;h = x @ W + b&lt;/code&gt;，需要计算三个梯度：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 正向：h_preact = emb @ W1 + b1 形状： (32, 64) = (32, 30) @ (30, 64) + (64,)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 反向：&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;dW1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;emb&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt; &lt;span class="n"&gt;dh_preact&lt;/span&gt; &lt;span class="c1"&gt;# (30, 64) = (30, 32) @ (32, 64)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;demb&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dh_preact&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt; &lt;span class="n"&gt;W1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt; &lt;span class="c1"&gt;# (32, 30) = (32, 64) @ (64, 30)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;db1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dh_preact&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# (64,) —— 广播的反向操作&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;&lt;strong&gt;关键直觉&lt;/strong&gt;：矩阵乘法 $C = A @ B$，梯度是：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;$dA = dC @ B^T$ —— 误差从输出沿 B 的转置回流到 A&lt;/li&gt;
&lt;li&gt;$dB = A^T @ dC$ —— 误差从输出沿 A 的转置回流到 B&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="tanh-反向传播"&gt;Tanh 反向传播
&lt;/h3&gt;&lt;p&gt;Tanh 的导数是 $1 - \tanh(x)^2$：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 正向：h = tanh(h_preact)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 反向：dh_preact = (1 - h²) * dh # 逐元素相乘&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;这就是为什么 Tanh 激活容易梯度消失：当 &lt;code&gt;h&lt;/code&gt; 接近 ±1 时，$1 - h^2$ 接近 0，梯度被&amp;quot;扼杀&amp;quot;。&lt;/p&gt;
&lt;h3 id="嵌入层embedding反向传播"&gt;嵌入层（Embedding）反向传播
&lt;/h3&gt;&lt;p&gt;嵌入表 &lt;code&gt;C&lt;/code&gt; 是 &lt;code&gt;(27, 30)&lt;/code&gt;（27 个字符，每个 30 维）。正向：从 &lt;code&gt;C&lt;/code&gt; 中按索引查表。&lt;/p&gt;
&lt;p&gt;反向：梯度的形状是 &lt;code&gt;(32, 3, 30)&lt;/code&gt;（每个样本有 3 个字符，每个 30 维）。需要把梯度&lt;strong&gt;累加&lt;/strong&gt;回嵌入表：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;dC&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;zeros_like&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;C&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# (27, 30)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;j&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;ix&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Xb&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;dC&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;ix&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;demb&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;&lt;strong&gt;注意&lt;/strong&gt;：如果多个样本索引到同一个嵌入向量，梯度要累加，不是覆盖。&lt;/p&gt;
&lt;h2 id="插曲batchnorm-的-bessel-校正"&gt;插曲：BatchNorm 的 Bessel 校正
&lt;/h2&gt;&lt;p&gt;BatchNorm 的正向传播：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;mu&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mean&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;keepdim&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# 列均值&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;sigma_sq&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;keepdim&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# 列方差&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;x_hat&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;mu&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;sigma_sq&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sqrt&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c1"&gt;# 归一化&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;gamma&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;x_hat&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;beta&lt;/span&gt; &lt;span class="c1"&gt;# 缩放平移&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Karpathy 指出一个常见的&amp;quot;小 bug&amp;quot;：PyTorch 的 &lt;code&gt;x.var()&lt;/code&gt; 默认使用了 &lt;strong&gt;Bessel&amp;rsquo;s correction&lt;/strong&gt;（除以 $n-1$ 而非 $n$），用于无偏估计总体方差。&lt;/p&gt;
&lt;p&gt;但在 BatchNorm 中这是&lt;strong&gt;不正确的&lt;/strong&gt;——你希望归一化的是当前 batch 的方差，而不是总体的。除以 $n$ 得到的&amp;quot;有偏&amp;quot;估计才是正确的。&lt;/p&gt;
&lt;p&gt;这个 bug 在实际中影响不大（batch size 大时，$n/(n-1) \approx 1$），但原理上值得理解。&lt;/p&gt;
&lt;h2 id="exercise-3batchnorm-反向传播"&gt;Exercise 3：BatchNorm 反向传播
&lt;/h2&gt;&lt;p&gt;这是视频中最复杂的部分。BatchNorm 的前向有 8 个原子步骤，反向需要从 &lt;code&gt;dhpreact&lt;/code&gt; 逆推回 &lt;code&gt;dhprebn&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;难点在于 &lt;code&gt;hprebn&lt;/code&gt; 出现在三条路径中：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;直接路径&lt;/strong&gt;：&lt;code&gt;hprebn → bndiff → bnraw → hpreact&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;均值路径&lt;/strong&gt;：&lt;code&gt;hprebn → bnmeani → bndiff → ... → hpreact&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;方差路径&lt;/strong&gt;：&lt;code&gt;hprebn → bnvar → bnvar_inv → bnraw → hpreact&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;下图展示了这三条路径如何在 &lt;code&gt;dhprebn&lt;/code&gt; 处汇合：&lt;/p&gt;
&lt;p&gt;&lt;img class="gallery-image" data-flex-basis="267px" data-flex-grow="111" height="1860" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://zewang0217.github.io/p/backprop-ninja-manual/fig03_bn_backward.png" srcset="https://zewang0217.github.io/p/backprop-ninja-manual/fig03_bn_backward_hu_9e898cf5c4979d16.png 800w, https://zewang0217.github.io/p/backprop-ninja-manual/fig03_bn_backward_hu_61434878f8cbd399.png 1600w, https://zewang0217.github.io/p/backprop-ninja-manual/fig03_bn_backward.png 2076w" width="2076"&gt;&lt;/p&gt;
&lt;p&gt;反向传播的推导分三步：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;第一步：dgamma 和 dbeta（最简单）&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;dbeta&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dy&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# (64,)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;dgamma&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dy&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;x_hat&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# (64,)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;&lt;strong&gt;第二步：dx_hat（中间步骤）&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;dx_hat&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dy&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;gamma&lt;/span&gt; &lt;span class="c1"&gt;# (32, 64)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;&lt;strong&gt;第三步：dx（三条路径累加）&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;x&lt;/code&gt; 通过三条路径影响输出，最终的梯度公式为：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;N&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;shape&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="c1"&gt;# batch size&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;dx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;N&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;sigma_sq&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sqrt&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;N&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;dx_hat&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;dx_hat&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;x_hat&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dx_hat&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;x_hat&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;这个公式的三个项对应三条路径：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;路径 1&lt;/strong&gt;（直接）：$N \cdot d\hat{x}$ —— 标准化后直接传递&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;路径 2&lt;/strong&gt;（通过 mu）：$-\sum d\hat{x}$ —— 减去均值影响&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;路径 3&lt;/strong&gt;（通过 var）：$-\hat{x} \cdot \sum (d\hat{x} \cdot \hat{x})$ —— 减去方差影响&lt;/li&gt;
&lt;/ul&gt;

 &lt;blockquote&gt;
 &lt;p&gt;&lt;strong&gt;直觉&lt;/strong&gt;：BatchNorm 的反向传播本质上是&lt;strong&gt;从梯度中移除均值和方差的影响&lt;/strong&gt;，就像正向传播从数据中移除均值和方差一样。&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;h2 id="exercise-4整合验证"&gt;Exercise 4：整合验证
&lt;/h2&gt;&lt;p&gt;最后，把所有梯度填入参数更新，验证手动计算的梯度与 PyTorch autograd 的结果一致（数值容差内）。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 参数更新（SGD）&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;learning_rate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;g&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;zip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;parameters&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;grads&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;-=&lt;/span&gt; &lt;span class="n"&gt;learning_rate&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;g&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Karpathy 的 notebook 验证了所有 26 个张量（包括中间变量）的梯度都 exact 或 approximate 匹配：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;logprobs | exact: True | approximate: True | maxdiff: 0.0
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;probs | exact: True | approximate: True | maxdiff: 0.0
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;...
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;W2 | exact: True | approximate: True | maxdiff: 0.0
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;hprebn | exact: True | approximate: True | maxdiff: 0.0
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;C | exact: True | approximate: True | maxdiff: 0.0
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h2 id="总结"&gt;总结
&lt;/h2&gt;&lt;p&gt;通过手动执行反向传播，你能获得三个关键洞察：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;广播的反向操作是 sum&lt;/strong&gt;——每次正向广播，反向都要求和。这是 PyTorch 中最容易被忽略的梯度规则&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Softmax + CrossEntropy 的梯度出奇地简洁&lt;/strong&gt;——$\text{softmax}(x) - \text{onehot}(y)$ 的形式背后隐藏了大量链式法则的推导&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;BatchNorm 的反向传播是对梯度的&amp;quot;二次标准化&amp;quot;&lt;/strong&gt;——移除梯度中的均值和方差分量，就像正向移除数据中的均值和方差一样&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Karpathy 的核心主张：&lt;strong&gt;autograd 让你更高效，不理解它让你更脆弱。&lt;/strong&gt; 当你的神经网络表现异常时，知道梯度从哪里来、往哪里去，是调试的第一道防线。&lt;/p&gt;
&lt;h3 id="四句话记住整个流程"&gt;四句话记住整个流程
&lt;/h3&gt;&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;操作&lt;/th&gt;
 &lt;th&gt;反向规则&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;y = x @ W + b&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;dx = dy @ W.T&lt;/code&gt;, &lt;code&gt;dW = x.T @ dy&lt;/code&gt;, &lt;code&gt;db = dy.sum(0)&lt;/code&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;y = tanh(x)&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;dx = (1 - y²) * dy&lt;/code&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;y = softmax(x)&lt;/code&gt; 后接 CE loss&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;dx = (softmax - one_hot) / N&lt;/code&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;广播（如 &lt;code&gt;scaler * tensor&lt;/code&gt;）&lt;/td&gt;
 &lt;td&gt;对广播源求和&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;</description></item></channel></rss>