<?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/%E5%8F%8D%E5%90%91%E4%BC%A0%E6%92%AD/</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/%E5%8F%8D%E5%90%91%E4%BC%A0%E6%92%AD/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><item><title>Makemore 完结篇：从 MLP 到 WaveNet，理解语言建模的每一步</title><link>https://zewang0217.github.io/p/makemore-wavenet/</link><pubDate>Sun, 19 Jul 2026 00:00:00 +0000</pubDate><guid>https://zewang0217.github.io/p/makemore-wavenet/</guid><description>&lt;h1 id="makemore-完结篇从-mlp-到-wavenet理解语言建模的每一步"&gt;Makemore 完结篇：从 MLP 到 WaveNet，理解语言建模的每一步
&lt;/h1&gt;
 &lt;blockquote&gt;
 &lt;p&gt;资料源：Andrej Karpathy &amp;ldquo;Building makemore&amp;rdquo; 系列
Part 1-4: &lt;a class="link" href="https://www.youtube.com/watch?v=q8SA3rM6ckI" target="_blank" rel="noopener"
 &gt;https://www.youtube.com/watch?v=q8SA3rM6ckI&lt;/a&gt; (P4 Backprop Ninja)
Part 5: &lt;a class="link" href="https://www.youtube.com/watch?v=t3YJ5hKiMQ0" target="_blank" rel="noopener"
 &gt;https://www.youtube.com/watch?v=t3YJ5hKiMQ0&lt;/a&gt; (Building a WaveNet)
Notebook: &lt;a class="link" href="https://github.com/karpathy/nn-zero-to-hero" target="_blank" rel="noopener"
 &gt;https://github.com/karpathy/nn-zero-to-hero&lt;/a&gt;&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;p&gt;makemore 系列五章，从最简单的 Bigram 统计模型一步步演进到 WaveNet 风格的层次化架构。这篇文章不是逐视频复述，而是&lt;strong&gt;纵向梳理每一层为什么存在、做什么、怎么工作&lt;/strong&gt;——让你看完后对整个 MLP + 语言建模的全流程有系统性的理解。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="全景回顾五章分别讲了什么"&gt;全景回顾：五章分别讲了什么
&lt;/h2&gt;&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;Part&lt;/th&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;P1&lt;/td&gt;
 &lt;td&gt;Bigram 语言模型&lt;/td&gt;
 &lt;td&gt;最简单的&amp;quot;看前一个字符预测下一个&amp;quot;，矩阵统计&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;P2&lt;/td&gt;
 &lt;td&gt;MLP 语言模型&lt;/td&gt;
 &lt;td&gt;Bengio 2003 架构：嵌入 + 线性层 + 交叉熵&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;P3&lt;/td&gt;
 &lt;td&gt;激活函数与 BatchNorm&lt;/td&gt;
 &lt;td&gt;为什么需要 Tanh、初始化技巧、BN 的作用&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;P4&lt;/td&gt;
 &lt;td&gt;Backprop Ninja&lt;/td&gt;
 &lt;td&gt;手动反向传播，理解梯度流动&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;P5&lt;/td&gt;
 &lt;td&gt;WaveNet&lt;/td&gt;
 &lt;td&gt;层次化架构：用树状结构处理长上下文&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h2 id="每一层的角色纵向总结"&gt;每一层的角色（纵向总结）
&lt;/h2&gt;&lt;p&gt;从输入到输出，按数据流顺序：&lt;/p&gt;
&lt;h3 id="1-嵌入层embedding"&gt;1. 嵌入层（Embedding）
&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;做什么&lt;/strong&gt;：把离散的字符索引（0-26）映射为连续向量。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;为什么需要&lt;/strong&gt;：字符本身是 categorical 变量，没有数值意义。嵌入层学习每个字符的&amp;quot;语义向量&amp;quot;——相似字符的向量在空间中靠近。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;怎么工作&lt;/strong&gt;：&lt;code&gt;C&lt;/code&gt; 是一个 &lt;code&gt;(vocab_size, n_embd)&lt;/code&gt; 的矩阵。&lt;code&gt;C[Xb]&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;/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;emb&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;C&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Xb&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="c1"&gt;# (32, 8, 24) — batch=32, 8个字符, 每字符24维&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;：嵌入层等价于一个 one-hot → Linear 映射，但更高效。每个字符学到一个分布式的特征表示。&lt;/p&gt;
&lt;h3 id="2-flatten--拼接"&gt;2. Flatten / 拼接
&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;做什么&lt;/strong&gt;：把 (B, T, C) 的张量展平成 (B, T*C) 或 (B, T/n, C*n)。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;为什么需要&lt;/strong&gt;：线性层要求输入是 2D 的 (batch, features)。需要把时间维和特征维合并。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;两种策略&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Flat MLP&lt;/strong&gt;: 把所有 8 个字符拼成一个长向量 → &lt;code&gt;(B, 8*24) = (B, 192)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Hierarchical&lt;/strong&gt;: 每次合并 2 个 → &lt;code&gt;(B, 4, 48)&lt;/code&gt; → &lt;code&gt;(B, 2, 96)&lt;/code&gt; → &lt;code&gt;(B, 192)&lt;/code&gt;&lt;/li&gt;
&lt;/ul&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;span class="lnt"&gt;7
&lt;/span&gt;&lt;span class="lnt"&gt;8
&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;# FlattenConsecutive(n): 把相邻 n 个在时间维合并&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# (B, T, C) → (B, T//n, C*n)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="fm"&gt;__call__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;x&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;B&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;C&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&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;x&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;view&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;B&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="o"&gt;//&lt;/span&gt;&lt;span class="bp"&gt;self&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="n"&gt;C&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="bp"&gt;self&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&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&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;1&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="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;x&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;squeeze&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="c1"&gt;# 最后一个时间维也被合并，变成 2D&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;x&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;h3 id="3-线性层linear"&gt;3. 线性层（Linear）
&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;做什么&lt;/strong&gt;：&lt;code&gt;y = x @ W + b&lt;/code&gt;，仿射变换。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;为什么需要&lt;/strong&gt;：神经网络的&amp;quot;学习&amp;quot;主要发生在这里。线性层通过调整权重矩阵，提取特征的不同组合。&lt;/p&gt;
&lt;p&gt;&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;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&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="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Linear&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;def&lt;/span&gt; &lt;span class="fm"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fan_in&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fan_out&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="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;weight&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;randn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fan_in&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fan_out&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;fan_in&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="mf"&gt;0.5&lt;/span&gt; &lt;span class="c1"&gt;# Kaiming init&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bias&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&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fan_out&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&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="fm"&gt;__call__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;x&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="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;out&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="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;weight&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bias&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="kc"&gt;None&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="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;out&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bias&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;out&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;：线性层的每一列是一个&amp;quot;检测器&amp;quot;——它学习在输入中寻找某种模式。多列组合起来形成特征空间。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;规模问题&lt;/strong&gt;：Flat MLP 中，上下文越长，线性层输入维越大。context=8, n_embd=10 时输入是 80 维；context=8, n_embd=24 时输入是 192 维。hidden_size 需要相应增长，参数规模爆炸。&lt;/p&gt;
&lt;h3 id="4-batchnorm-层"&gt;4. BatchNorm 层
&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;做什么&lt;/strong&gt;：对每个特征列做标准化：减去均值、除以标准差 → 缩放平移。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;为什么需要&lt;/strong&gt;：三个原因：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;控制激活值的分布&lt;/strong&gt;——防止 Tanh 层的输入落入饱和区（梯度消失）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;允许更高的学习率&lt;/strong&gt;——标准化后梯度更稳定&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;减少对初始化的敏感度&lt;/strong&gt;——不管前一层的输出怎么变，BN 都把它归一化到标准分布&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&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;/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;xmean&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="n"&gt;dim&lt;/span&gt;&lt;span class="o"&gt;=&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="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;# 跨 batch + 时间维求均值&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;xvar&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="n"&gt;dim&lt;/span&gt;&lt;span class="o"&gt;=&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="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;# 跨 batch + 时间维求方差&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;xhat&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;xmean&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="n"&gt;xvar&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;eps&lt;/span&gt;&lt;span class="p"&gt;)&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;out&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;xhat&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;&lt;strong&gt;对 3D 张量的处理&lt;/strong&gt;：当 &lt;code&gt;x&lt;/code&gt; 是 &lt;code&gt;(B, T, C)&lt;/code&gt; 时，BN 需要在 &lt;code&gt;(0, 1)&lt;/code&gt; 维上求统计量（batch + 时间），而不是只对 batch。这是 WaveNet 的关键适配——时间维的每个位置共享同样的 BN 参数。&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="k"&gt;if&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;ndim&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;2&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;dim&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="c1"&gt;# (B, C) — 对 batch 求统计量&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;elif&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;ndim&lt;/span&gt; &lt;span class="o"&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;dim&lt;/span&gt; &lt;span class="o"&gt;=&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="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# (B, T, C) — 对 batch + 时间求统计量&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;h3 id="5-tanh-激活函数"&gt;5. Tanh 激活函数
&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;做什么&lt;/strong&gt;：引入非线性。&lt;code&gt;tanh(x) = (e^x - e^{-x}) / (e^x + e^{-x})&lt;/code&gt;，输出范围 (-1, 1)。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;为什么需要&lt;/strong&gt;：没有激活函数的线性层堆叠等价于一个线性层。非线性是神经网络表达能力的来源。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;为什么是 Tanh 而不是 ReLU&lt;/strong&gt;：Tanh 是中心对称的（zero-centered），输出在 BN 之后分布更稳定。ReLU 虽然更常见（CV 领域），但在小规模语言模型中 Tanh 效果更好。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;梯度消失问题&lt;/strong&gt;：Tanh 的导数 = &lt;code&gt;1 - tanh(x)²&lt;/code&gt;。当 x 很大时，tanh(x) ≈ ±1，导数 ≈ 0 → 梯度消失。BN 的存在就是为了防止这种情况。&lt;/p&gt;
&lt;h3 id="6-softmax--cross-entropy-loss"&gt;6. Softmax + Cross-Entropy Loss
&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;做什么&lt;/strong&gt;：Softmax 把 logits 转成概率分布。Cross-Entropy 衡量预测分布和真实分布的差异。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;简化的梯度形式&lt;/strong&gt;（来自 P4 的推导）：&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;F&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;softmax&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;logits&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="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;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="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;Yb&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="n"&gt;n&lt;/span&gt; &lt;span class="c1"&gt;# 除以 batch 大小&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;hr&gt;
&lt;h2 id="flat-mlp-的问题上下文越长参数越多"&gt;Flat MLP 的问题：上下文越长，参数越多
&lt;/h2&gt;&lt;p&gt;回顾 Part 2-4 的网络结构：&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-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Embedding(27,10) → Flatten(8) → Linear(80,300) → BN → Tanh → Linear(300,27)
&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;当 context=3 时，Linear 输入是 &lt;code&gt;10*3=30&lt;/code&gt; 维，没问题。&lt;/p&gt;
&lt;p&gt;当 context=8 时，Linear 输入是 &lt;code&gt;10*8=80&lt;/code&gt; 维。如果 n_embd 更大（比如 24），那就是 &lt;code&gt;24*8=192&lt;/code&gt; 维。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;线性层的参数量 = fan_in * fan_out&lt;/strong&gt;。输入维每增加一倍，参数量也翻倍。更长的上下文需要更大的网络——这是 Flat MLP 的致命伤。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="part-5-的解法wavenet-层次化架构"&gt;Part 5 的解法：WaveNet 层次化架构
&lt;/h2&gt;&lt;p&gt;WaveNet（原用于音频生成）的核心思想：&lt;strong&gt;不用一层处理所有上下文，而是逐级合并。&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id="架构对比"&gt;架构对比
&lt;/h3&gt;&lt;p&gt;下图对比了 Flat MLP 和 Hierarchical WaveNet 的结构：&lt;/p&gt;
&lt;p&gt;&lt;img class="gallery-image" data-flex-basis="343px" data-flex-grow="143" height="1716" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://zewang0217.github.io/p/makemore-wavenet/fig01_flat_vs_hier.png" srcset="https://zewang0217.github.io/p/makemore-wavenet/fig01_flat_vs_hier_hu_ca9154dc22c4ff5e.png 800w, https://zewang0217.github.io/p/makemore-wavenet/fig01_flat_vs_hier_hu_54dd10a36d3f7054.png 1600w, https://zewang0217.github.io/p/makemore-wavenet/fig01_flat_vs_hier.png 2454w" width="2454"&gt;&lt;/p&gt;
&lt;p&gt;两个架构最终都输出 &lt;code&gt;(B, 27)&lt;/code&gt; 给 softmax，但中间结构完全不同。&lt;/p&gt;
&lt;h3 id="树状合并过程"&gt;树状合并过程
&lt;/h3&gt;&lt;p&gt;层次化架构的核心是 &lt;strong&gt;FlattenConsecutive&lt;/strong&gt;——每次合并相邻两个时间步：&lt;/p&gt;
&lt;p&gt;&lt;img class="gallery-image" data-flex-basis="224px" data-flex-grow="93" height="1914" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://zewang0217.github.io/p/makemore-wavenet/fig02_tree_structure.png" srcset="https://zewang0217.github.io/p/makemore-wavenet/fig02_tree_structure_hu_9e10a172a199ef45.png 800w, https://zewang0217.github.io/p/makemore-wavenet/fig02_tree_structure_hu_7e9dbd94f9138e11.png 1600w, https://zewang0217.github.io/p/makemore-wavenet/fig02_tree_structure.png 1788w" width="1788"&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-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;初始: (B, 8, 24) — 8 个字符，各 24 维
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;L1: (B, 4, 128) — Flatten(2): 4 组，每组 48→128 维
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;L2: (B, 2, 128) — Flatten(2): 2 组，每组 256→128 维
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;L3: (B, 128) — Flatten(2) + squeeze: 1 组，256→128 维 → 拍平
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;输出: (B, 27) — Linear 128→27
&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;：每一层看到的&amp;quot;视野&amp;quot;变大了，但通道数也变大了。底层负责局部模式（两个字符的组合），顶层负责全局模式（整个 8 字符上下文）。&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;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;Flat MLP&lt;/td&gt;
 &lt;td&gt;~100K+&lt;/td&gt;
 &lt;td&gt;8&lt;/td&gt;
 &lt;td&gt;10&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Hierarchical&lt;/td&gt;
 &lt;td&gt;&lt;strong&gt;76,579&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;8&lt;/td&gt;
 &lt;td&gt;24&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;层次化架构的嵌入维更大（24 vs 10），但总参数量反而更少。这就是渐进合并的威力。&lt;/p&gt;
&lt;h3 id="实现代码"&gt;实现代码
&lt;/h3&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;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&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_embd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;24&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_hidden&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;128&lt;/span&gt;
&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;&lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Sequential&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;Embedding&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;vocab_size&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;n_embd&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="c1"&gt;# (B,8,24)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;FlattenConsecutive&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;Linear&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;48&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;n_hidden&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;bias&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;BatchNorm1d&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n_hidden&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;Tanh&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="c1"&gt;# (B,4,128)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;FlattenConsecutive&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;Linear&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;256&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;n_hidden&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;bias&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;BatchNorm1d&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n_hidden&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;Tanh&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="c1"&gt;# (B,2,128)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;FlattenConsecutive&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;Linear&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;256&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;n_hidden&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;bias&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;BatchNorm1d&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n_hidden&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;Tanh&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="c1"&gt;# (B,128)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;Linear&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n_hidden&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;vocab_size&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="c1"&gt;# (B,27)&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;code&gt;model.layers[-1].weight *= 0.1&lt;/code&gt;，让初始预测更均匀（不自信），防止训练早期 loss 爆炸。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="训练过程"&gt;训练过程
&lt;/h2&gt;&lt;p&gt;训练代码和之前完全一样——SGD + 学习率衰减：&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;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;span class="lnt"&gt;15
&lt;/span&gt;&lt;span class="lnt"&gt;16
&lt;/span&gt;&lt;span class="lnt"&gt;17
&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;max_steps&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;200000&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;batch_size&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;32&lt;/span&gt;
&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;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&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="n"&gt;max_steps&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;torch&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;randint&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;Xtr&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="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;batch_size&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;Xb&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Yb&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Xtr&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="n"&gt;Ytr&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&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; &lt;span class="n"&gt;logits&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Xb&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;loss&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;F&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cross_entropy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;logits&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Yb&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&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="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;parameters&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;grad&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;None&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="n"&gt;backward&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&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;lr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.1&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;150000&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="mf"&gt;0.01&lt;/span&gt; &lt;span class="c1"&gt;# step decay&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="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;parameters&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="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;lr&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;grad&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;为什么需要？后期 loss 进入平稳区，小学习率有助于精细调整。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="逐形状解读每一层对-b-t-c-做了什么"&gt;逐形状解读：每一层对 (B, T, C) 做了什么
&lt;/h2&gt;&lt;p&gt;理解层次化架构的关键是看懂每一层对张量形状 &lt;code&gt;(B, T, C)&lt;/code&gt; 的变换。&lt;/p&gt;
&lt;p&gt;完整形状变化链（以 batch=4, context=8, n_embd=10, n_hidden=200 为例）：&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;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;span class="lnt"&gt;15
&lt;/span&gt;&lt;span class="lnt"&gt;16
&lt;/span&gt;&lt;span class="lnt"&gt;17
&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;Embedding → (4, 8, 10) 4样本，8字符，每字符10维
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;FlattenConsecutive(2) → (4, 4, 20) 相邻2字符合并→4组，每组20维
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Linear(20→200) → (4, 4, 200) 每组特征从20扩到200
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;BatchNorm1d → (4, 4, 200) 标准化数值分布
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Tanh → (4, 4, 200) 非线性
&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;FlattenConsecutive(2) → (4, 2, 400) 相邻2组合并→2组，每组400维
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Linear(400→200) → (4, 2, 200) 每组特征从400压缩到200
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;BatchNorm1d → (4, 2, 200) 标准化
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Tanh → (4, 2, 200) 非线性
&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;FlattenConsecutive(2) → (4, 400) squeeze掉时间维
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Linear(400→200) → (4, 200) 压缩到200维
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;BatchNorm1d → (4, 200) 标准化
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Tanh → (4, 200) 非线性
&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;Linear(200→27) → (4, 27) 输出：每个样本对27字符打分
&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;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;th&gt;含义&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;第1维&lt;/td&gt;
 &lt;td&gt;B (batch)&lt;/td&gt;
 &lt;td&gt;一次处理几个样本&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;第2维&lt;/td&gt;
 &lt;td&gt;T (time/token)&lt;/td&gt;
 &lt;td&gt;字符位置数，逐步减少&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;第3维&lt;/td&gt;
 &lt;td&gt;C (channel/feature)&lt;/td&gt;
 &lt;td&gt;每个位置的信息&amp;quot;宽度&amp;quot;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id="每个操作的规律"&gt;每个操作的规律
&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;FlattenConsecutive(n)&lt;/strong&gt;: &lt;code&gt;(B, T, C) → (B, T/n, C×n)&lt;/code&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;时间步 T 除以 n，特征 C 乘以 n&lt;/li&gt;
&lt;li&gt;信息重新排列但没有压缩&lt;/li&gt;
&lt;li&gt;最终当 T=1 时 squeeze 掉时间维，变成 2D&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Linear&lt;/strong&gt;: &lt;code&gt;(B, T, C_in) → (B, T, C_out)&lt;/code&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;对每个时间步独立做 &lt;code&gt;x @ W + b&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;时间步 T 不变&lt;/li&gt;
&lt;li&gt;C_in → C_out 可以是升维（扩展信息）或降维（压缩信息）&lt;/li&gt;
&lt;li&gt;所有时间步共享同一个权重矩阵 W&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;BatchNorm1d&lt;/strong&gt;: &lt;code&gt;(B, T, C) → (B, T, C)&lt;/code&gt; 形状不变&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;跨 batch 和时间的维度求统计量&lt;/li&gt;
&lt;li&gt;把数值拉回到均值为0、方差为1的分布&lt;/li&gt;
&lt;li&gt;用可学习的 γ 和 β 恢复表达能力&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Tanh&lt;/strong&gt;: 形状不变，数值被夹到 (-1, 1)&lt;/p&gt;
&lt;h3 id="树状合并的直觉"&gt;树状合并的直觉
&lt;/h3&gt;&lt;p&gt;把 8 个字符逐步合并到 1 个向量的过程：&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;span class="lnt"&gt;7
&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;c0 c1 c2 c3 c4 c5 c6 c7 ← 8个字符位置
&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; 组0 组1 组2 组3 ← T=4, C翻倍
&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; 组0 组1 ← T=2, C再翻倍
&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; 组0 ← T=1, squeeze成2D
&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;hr&gt;
&lt;h2 id="flat-mlp-vs-hierarchical深层理解"&gt;Flat MLP vs Hierarchical：深层理解
&lt;/h2&gt;&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;维度&lt;/th&gt;
 &lt;th&gt;Flat MLP&lt;/th&gt;
 &lt;th&gt;Hierarchical (WaveNet)&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;特征提取&lt;/td&gt;
 &lt;td&gt;一层提取所有上下文特征&lt;/td&gt;
 &lt;td&gt;逐层提取，底层局部→顶层全局&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;参数效率&lt;/td&gt;
 &lt;td&gt;差（输入维 × hidden 大）&lt;/td&gt;
 &lt;td&gt;好（逐级合并，共享参数模式）&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;数学本质&lt;/td&gt;
 &lt;td&gt;全连接：每个输入特征有权重&lt;/td&gt;
 &lt;td&gt;卷积式：每个时间步共享权重&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;扩展性&lt;/td&gt;
 &lt;td&gt;上下文增 → 参数暴增&lt;/td&gt;
 &lt;td&gt;上下文增 → 增加层级即可&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;信息流&lt;/td&gt;
 &lt;td&gt;所有位置直接连接输出&lt;/td&gt;
 &lt;td&gt;树状：相邻位置先合并，逐级向上&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;Flat MLP 本质是&amp;quot;长程全连接&amp;quot;&lt;/strong&gt;——8 个位置的 80 个输入全部连接到 300 个隐藏神经元。这给模型很大的表达能力，但参数效率低。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Hierarchical 本质是&amp;quot;局部→全局&amp;quot;的渐进抽象&lt;/strong&gt;——底层只看到 2 个字符的局部组合，顶层看到完整的 8 字符上下文。这和人处理语言的方式更接近：先理解词的形态，再理解短语，最后理解句子。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="总结语言建模中每一层的存在理由"&gt;总结：语言建模中每一层的存在理由
&lt;/h2&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;Embedding&lt;/td&gt;
 &lt;td&gt;把离散符号变成连续语义空间&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Flatten / FlattenConsecutive&lt;/td&gt;
 &lt;td&gt;把时间结构变成特征结构，供线性层处理&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Linear&lt;/td&gt;
 &lt;td&gt;学习特征之间的加权组合&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;BatchNorm&lt;/td&gt;
 &lt;td&gt;防止激活值失控，稳定训练&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Tanh&lt;/td&gt;
 &lt;td&gt;引入非线性 + 中心对称分布&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Softmax + CE&lt;/td&gt;
 &lt;td&gt;把分数转换成概率 + 衡量预测质量&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;五章系列的核心演进逻辑&lt;/strong&gt;：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;从统计（Bigram）到学习（MLP）&lt;/li&gt;
&lt;li&gt;从手动（梯度）到自动（autograd）再回到手动（Backprop Ninja）&lt;/li&gt;
&lt;li&gt;从平面（Flat）到层次（WaveNet）&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;每解决一个问题，下一个问题就浮现出来——这正是深度学习最迷人的地方。&lt;/p&gt;</description></item></channel></rss>