<?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/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0/</link><description>Recent content in 深度学习 on Zewang's Blog</description><generator>Hugo -- gohugo.io</generator><language>zh</language><lastBuildDate>Sun, 16 Aug 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://zewang0217.github.io/tags/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0/index.xml" rel="self" type="application/rss+xml"/><item><title>华为 8.5 AI 方向机考复盘：60 分的选择题，错在哪些知识盲区</title><link>https://zewang0217.github.io/notes/huawei-ai-exam-20260805-review/</link><pubDate>Sun, 16 Aug 2026 00:00:00 +0000</pubDate><guid>https://zewang0217.github.io/notes/huawei-ai-exam-20260805-review/</guid><description>&lt;h1 id="华为-85-ai-方向机考复盘60-分的选择题错在哪些知识盲区"&gt;华为 8.5 AI 方向机考复盘：60 分的选择题，错在哪些知识盲区
&lt;/h1&gt;
 &lt;blockquote&gt;
 &lt;p&gt;题目与官方解析来源：CodeFun2000.com（塔子哥）——华为机考 AI 方向 2026-08-05 场次，AI 算法/应用开发/数据科学等 AI 开头岗位统考，不区分部门。
我的成绩：选择题 20 题对 10 题，60/150（15×6 分 + 5×12 分）。&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;h2 id="写在前面这场考试在考什么"&gt;写在前面：这场考试在考什么
&lt;/h2&gt;&lt;p&gt;这套卷子的知识面比我想象的宽。传统 ML 数学基础（贝叶斯、插值、SVD、余弦相似度）有，但更重的是&lt;strong&gt;训练工程实践和大模型前沿&lt;/strong&gt;：warmup、梯度裁剪、混合精度、端侧推理峰值内存、GSPO、Householder、梯度零空间、在线学习——这些是我平时刷算法题完全接触不到的东西。&lt;/p&gt;
&lt;p&gt;我的成绩结构很能说明问题：&lt;strong&gt;对的全是 6 分基础题，5 道 12 分大题全错&lt;/strong&gt;。60 分 = 10 × 6 分，一道 12 分题都没拿到。这说明我的知识结构是&amp;quot;会做题，不懂训练与前沿&amp;quot;——基础数学和概念推导还凑合，但凡涉及深度学习工程和大模型 RL 的就露馅。&lt;/p&gt;
&lt;p&gt;先说清楚一个诚实声明：转贴时不少选择题的公式数值没有带过来，但后来我从微信公众号原文的底层 HTML（mdnice 的 &lt;code&gt;data-formula&lt;/code&gt; 属性）里把 LaTeX 源码全部提取出来了，&lt;strong&gt;本文所有数值与官方解析一致&lt;/strong&gt;，不是编造的。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="一编程题复盘"&gt;一、编程题复盘
&lt;/h2&gt;&lt;h3 id="第-1-题基站空间重叠区域识别150-分模拟"&gt;第 1 题：基站空间重叠区域识别（150 分，模拟）
&lt;/h3&gt;&lt;p&gt;这道题是 DBSCAN 的&lt;strong&gt;判定子集&lt;/strong&gt;：只标记每个点的属性（核心点/边界点/噪声点），不做簇扩展和簇编号，所以是个纯模拟题。&lt;/p&gt;
&lt;p&gt;核心定义只有三条：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;空间邻域&lt;/strong&gt;：两基站曼哈顿距离 $d = |x_i - x_j| + |y_i - y_j| \le \varepsilon$ 即互为邻域。&lt;strong&gt;邻域包含自身&lt;/strong&gt;——这是最容易漏的点，每个点的邻居数至少为 1。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;核心点&lt;/strong&gt;：邻域内基站总数 $\ge m$（邻域数量门限）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;边界点&lt;/strong&gt;：自己不是核心点，但邻域内至少有一个核心点。否则是噪声点。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;两阶段模拟：第一遍 $O(n^2)$ 统计每个点的邻居数、标记核心点；第二遍非核心点去查核心点集合，距离 $\le \varepsilon$ 就是边界点。总复杂度 $O(n^2)$。原题数据范围：$N \in [1, 2000]$、$\varepsilon \in [0, 10000]$、$MinPts \in [1, N]$、坐标 $[-10000, 10000]$——$O(N^2)$ 最多 400 万对距离计算，完全没问题。&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;span class="lnt"&gt;18
&lt;/span&gt;&lt;span class="lnt"&gt;19
&lt;/span&gt;&lt;span class="lnt"&gt;20
&lt;/span&gt;&lt;span class="lnt"&gt;21
&lt;/span&gt;&lt;span class="lnt"&gt;22
&lt;/span&gt;&lt;span class="lnt"&gt;23
&lt;/span&gt;&lt;span class="lnt"&gt;24
&lt;/span&gt;&lt;span class="lnt"&gt;25
&lt;/span&gt;&lt;span class="lnt"&gt;26
&lt;/span&gt;&lt;span class="lnt"&gt;27
&lt;/span&gt;&lt;span class="lnt"&gt;28
&lt;/span&gt;&lt;span class="lnt"&gt;29
&lt;/span&gt;&lt;span class="lnt"&gt;30
&lt;/span&gt;&lt;span class="lnt"&gt;31
&lt;/span&gt;&lt;span class="lnt"&gt;32
&lt;/span&gt;&lt;span class="lnt"&gt;33
&lt;/span&gt;&lt;span class="lnt"&gt;34
&lt;/span&gt;&lt;span class="lnt"&gt;35
&lt;/span&gt;&lt;span class="lnt"&gt;36
&lt;/span&gt;&lt;span class="lnt"&gt;37
&lt;/span&gt;&lt;span class="lnt"&gt;38
&lt;/span&gt;&lt;span class="lnt"&gt;39
&lt;/span&gt;&lt;span class="lnt"&gt;40
&lt;/span&gt;&lt;span class="lnt"&gt;41
&lt;/span&gt;&lt;span class="lnt"&gt;42
&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="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;sys&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="nf"&gt;solve&lt;/span&gt;&lt;span class="p"&gt;(&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;e&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;m&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="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&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;core&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;n&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="c1"&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;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;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="n"&gt;cnt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&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="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="n"&gt;d&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;abs&lt;/span&gt;&lt;span class="p"&gt;(&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;i&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="o"&gt;-&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;j&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="o"&gt;+&lt;/span&gt; &lt;span class="nb"&gt;abs&lt;/span&gt;&lt;span class="p"&gt;(&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;i&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="n"&gt;p&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 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="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;e&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;cnt&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;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;if&lt;/span&gt; &lt;span class="n"&gt;cnt&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;m&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;core&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;True&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="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;ans&lt;/span&gt; &lt;span class="o"&gt;=&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="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;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;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;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;core&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&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;ans&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;else&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="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;core&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;d&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;abs&lt;/span&gt;&lt;span class="p"&gt;(&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;i&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="o"&gt;-&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;j&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="o"&gt;+&lt;/span&gt; &lt;span class="nb"&gt;abs&lt;/span&gt;&lt;span class="p"&gt;(&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;i&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="n"&gt;p&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 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="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;e&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;ans&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&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&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;break&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;ans&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="nf"&gt;main&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;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stdin&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;buffer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;split&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="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;data&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;data&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;data&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&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="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;k&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&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;_&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;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="n"&gt;p&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;data&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;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;k&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;k&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stdout&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;solve&lt;/span&gt;&lt;span class="p"&gt;(&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;e&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;m&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;if&lt;/span&gt; &lt;span class="vm"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;__main__&amp;#34;&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;main&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;这道题在 CodeFun2000 上的原题是 &lt;strong&gt;P5198&lt;/strong&gt;（https://codefun2000.com/ide/P5198），可以上去刷题评测。我自己用 C++ 又写了一遍（机考常用语言，逻辑和 Python 版完全一致）：&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;span class="lnt"&gt;18
&lt;/span&gt;&lt;span class="lnt"&gt;19
&lt;/span&gt;&lt;span class="lnt"&gt;20
&lt;/span&gt;&lt;span class="lnt"&gt;21
&lt;/span&gt;&lt;span class="lnt"&gt;22
&lt;/span&gt;&lt;span class="lnt"&gt;23
&lt;/span&gt;&lt;span class="lnt"&gt;24
&lt;/span&gt;&lt;span class="lnt"&gt;25
&lt;/span&gt;&lt;span class="lnt"&gt;26
&lt;/span&gt;&lt;span class="lnt"&gt;27
&lt;/span&gt;&lt;span class="lnt"&gt;28
&lt;/span&gt;&lt;span class="lnt"&gt;29
&lt;/span&gt;&lt;span class="lnt"&gt;30
&lt;/span&gt;&lt;span class="lnt"&gt;31
&lt;/span&gt;&lt;span class="lnt"&gt;32
&lt;/span&gt;&lt;span class="lnt"&gt;33
&lt;/span&gt;&lt;span class="lnt"&gt;34
&lt;/span&gt;&lt;span class="lnt"&gt;35
&lt;/span&gt;&lt;span class="lnt"&gt;36
&lt;/span&gt;&lt;span class="lnt"&gt;37
&lt;/span&gt;&lt;span class="lnt"&gt;38
&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-cpp" data-lang="cpp"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// 我的 C++ 版 —— CodeFun2000 P5198
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;#include&lt;/span&gt; &lt;span class="cpf"&gt;&amp;lt;iostream&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;#include&lt;/span&gt; &lt;span class="cpf"&gt;&amp;lt;vector&amp;gt;&lt;/span&gt;&lt;span class="cp"&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;using&lt;/span&gt; &lt;span class="k"&gt;namespace&lt;/span&gt; &lt;span class="n"&gt;std&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;vector&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;solve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;vector&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;pair&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&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="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;=&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;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;vector&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;core&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="nb"&gt;false&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&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="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&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;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&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;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&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="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;cnt&lt;/span&gt; &lt;span class="o"&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="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;j&lt;/span&gt; &lt;span class="o"&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;j&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&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;j&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&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="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;dist&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;abs&lt;/span&gt;&lt;span class="p"&gt;(&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;i&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;first&lt;/span&gt; &lt;span class="o"&gt;-&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;j&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;first&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;abs&lt;/span&gt;&lt;span class="p"&gt;(&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;i&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;second&lt;/span&gt; &lt;span class="o"&gt;-&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;j&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;second&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="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dist&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;cnt&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="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="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cnt&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;core&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;true&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;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="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;vector&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;ans&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="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="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&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;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&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;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&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="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;core&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="n"&gt;ans&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&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="k"&gt;else&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="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;j&lt;/span&gt; &lt;span class="o"&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;j&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&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;j&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&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="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;core&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 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="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;dist&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;abs&lt;/span&gt;&lt;span class="p"&gt;(&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;i&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;first&lt;/span&gt; &lt;span class="o"&gt;-&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;j&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;first&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;abs&lt;/span&gt;&lt;span class="p"&gt;(&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;i&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;second&lt;/span&gt; &lt;span class="o"&gt;-&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;j&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;second&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="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dist&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&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;ans&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&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="k"&gt;break&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;span class="line"&gt;&lt;span class="cl"&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;span class="line"&gt;&lt;span class="cl"&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;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;ans&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;（main 函数读入 n/e/m 和坐标，逐行输出 &lt;code&gt;solve&lt;/code&gt; 的结果即可。）&lt;/p&gt;
&lt;p&gt;这道题本身不难，值得记住的是它背后的 DBSCAN：核心点做种子、密度可达做簇扩展。机考只考了属性判定，但理解完整算法才能应对变体。&lt;/p&gt;
&lt;h3 id="第-2-题多-agent-协作任务调度300-分dp-最优次优状态"&gt;第 2 题：多 Agent 协作任务调度（300 分，DP 最优+次优状态）
&lt;/h3&gt;&lt;p&gt;这是本场含金量最高的一道题，也是我想重点记的题。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;问题&lt;/strong&gt;：$n$ 个 Agent，第 $i$ 个每次调用推进 $p_i$ 个百分点、花费 $c_i$ 代价，同一 Agent 可调用任意次但&lt;strong&gt;不能连续两次调用同一个&lt;/strong&gt;，进度 $\ge 100$ 视为完成，求最小总代价。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;为什么朴素 DP 不够&lt;/strong&gt;。最直接的思路是 $dp[s][i]$ = 进度 $s$、最后一次调 Agent $i$ 的最小代价，转移时枚举上一个 Agent $j \ne i$。复杂度 $O(100 \cdot n^2)$，n 稍大就超时。核心矛盾是：转移时我们根本不需要&amp;quot;每个 Agent 的完整代价&amp;quot;，只需要知道&amp;quot;上一个状态最后调的 Agent 是谁&amp;quot;——但朴素 DP 被迫把全部信息都存了下来。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;关键洞察：每个进度只需要保留两个状态&lt;/strong&gt;。对于进度 $s$，设：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;$best_1[s]$：最小代价，$id_1[s]$ 是它最后调的 Agent；&lt;/li&gt;
&lt;li&gt;$best_2[s]$：代价次小、且 $id_2[s] \ne id_1[s]$ 的状态。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;为什么两个就够？因为转移的&lt;strong&gt;唯一约束&lt;/strong&gt;是&amp;quot;下一个 Agent $i$ 不能等于上一个状态的最后 Agent&amp;quot;。所以当我要从进度 $s$ 调 Agent $i$ 时：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;若 $id_1[s] \ne i$：直接用最优状态 $best_1[s]$；&lt;/li&gt;
&lt;li&gt;若 $id_1[s] = i$：只能用次优状态 $best_2[s]$（它的最后 Agent 必然不是 $i$）。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;也就是说，$best_2$ 是专门为&amp;quot;$best_1$ 的最后 Agent 恰好是我们要调的那个&amp;quot;这种冲突准备的备胎。这是经典的&amp;quot;最优+次优&amp;quot;DP 技巧——把 $O(n^2)$ 的枚举压成 $O(1)$ 的取备胎，总复杂度降到 $O(100 \cdot n)$。&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;span class="lnt"&gt;18
&lt;/span&gt;&lt;span class="lnt"&gt;19
&lt;/span&gt;&lt;span class="lnt"&gt;20
&lt;/span&gt;&lt;span class="lnt"&gt;21
&lt;/span&gt;&lt;span class="lnt"&gt;22
&lt;/span&gt;&lt;span class="lnt"&gt;23
&lt;/span&gt;&lt;span class="lnt"&gt;24
&lt;/span&gt;&lt;span class="lnt"&gt;25
&lt;/span&gt;&lt;span class="lnt"&gt;26
&lt;/span&gt;&lt;span class="lnt"&gt;27
&lt;/span&gt;&lt;span class="lnt"&gt;28
&lt;/span&gt;&lt;span class="lnt"&gt;29
&lt;/span&gt;&lt;span class="lnt"&gt;30
&lt;/span&gt;&lt;span class="lnt"&gt;31
&lt;/span&gt;&lt;span class="lnt"&gt;32
&lt;/span&gt;&lt;span class="lnt"&gt;33
&lt;/span&gt;&lt;span class="lnt"&gt;34
&lt;/span&gt;&lt;span class="lnt"&gt;35
&lt;/span&gt;&lt;span class="lnt"&gt;36
&lt;/span&gt;&lt;span class="lnt"&gt;37
&lt;/span&gt;&lt;span class="lnt"&gt;38
&lt;/span&gt;&lt;span class="lnt"&gt;39
&lt;/span&gt;&lt;span class="lnt"&gt;40
&lt;/span&gt;&lt;span class="lnt"&gt;41
&lt;/span&gt;&lt;span class="lnt"&gt;42
&lt;/span&gt;&lt;span class="lnt"&gt;43
&lt;/span&gt;&lt;span class="lnt"&gt;44
&lt;/span&gt;&lt;span class="lnt"&gt;45
&lt;/span&gt;&lt;span class="lnt"&gt;46
&lt;/span&gt;&lt;span class="lnt"&gt;47
&lt;/span&gt;&lt;span class="lnt"&gt;48
&lt;/span&gt;&lt;span class="lnt"&gt;49
&lt;/span&gt;&lt;span class="lnt"&gt;50
&lt;/span&gt;&lt;span class="lnt"&gt;51
&lt;/span&gt;&lt;span class="lnt"&gt;52
&lt;/span&gt;&lt;span class="lnt"&gt;53
&lt;/span&gt;&lt;span class="lnt"&gt;54
&lt;/span&gt;&lt;span class="lnt"&gt;55
&lt;/span&gt;&lt;span class="lnt"&gt;56
&lt;/span&gt;&lt;span class="lnt"&gt;57
&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="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;sys&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;INF&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt; &lt;span class="mi"&gt;30&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="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;best1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;best2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;id1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;id2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cost&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;agent&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="s2"&gt;&amp;#34;&amp;#34;&amp;#34;把「以 agent 结尾、代价 cost」的新状态并入 pos 进度的两个最优状态。&amp;#34;&amp;#34;&amp;#34;&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;id1&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;agent&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;cost&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;best1&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;pos&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;best1&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cost&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&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;id2&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;agent&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;cost&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;best2&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;pos&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;best2&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cost&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;best2&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;best1&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;pos&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;best1&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;best2&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;best2&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;best1&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;pos&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;id1&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;id2&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;id2&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;id1&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;pos&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;return&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;cost&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;best1&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;pos&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;best2&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;id2&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;best1&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;id1&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;pos&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;best1&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;id1&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cost&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;agent&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;cost&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;best2&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;pos&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;best2&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;id2&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cost&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;agent&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="nf"&gt;min_cost&lt;/span&gt;&lt;span class="p"&gt;(&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;c&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="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&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;best1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;INF&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;101&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;best2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;INF&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;101&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;id1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&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 class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;101&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;id2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&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 class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;101&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;best1&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="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="c1"&gt;# 进度 0，代价 0，-1 表示还没调用过任何 Agent&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;id1&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="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&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;s&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;100&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;best1&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;INF&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;continue&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;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;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="n"&gt;base&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;best1&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;id1&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="n"&gt;best2&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;s&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;base&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;INF&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;continue&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;+&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;i&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="c1"&gt;# 进度超过 100 统一压缩为 100&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;best1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;best2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;id1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;id2&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;base&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;i&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;i&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;return&lt;/span&gt; &lt;span class="n"&gt;best1&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;best1&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;INF&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&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="nf"&gt;main&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;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stdin&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;buffer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;split&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;data&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="n"&gt;p&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="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="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="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="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;pos&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;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;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;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="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&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="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;pos&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;pos&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;min_cost&lt;/span&gt;&lt;span class="p"&gt;(&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;c&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;if&lt;/span&gt; &lt;span class="vm"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;__main__&amp;#34;&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;main&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;注意两个边界：进度超过 100 统一压到 100（题目允许超额完成）；单 Agent 时无法完成任务输出 -1（样例 1 就是）。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="二选择题复盘按考点分组"&gt;二、选择题复盘（按考点分组）
&lt;/h2&gt;&lt;p&gt;我把 20 题按知识域分组，对题简讲、错题详讲。✅ = 我答对，❌ = 我答错。&lt;/p&gt;
&lt;h3 id="1-数学基础"&gt;1. 数学基础
&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;#1 Softmax 数值稳定（✅）&lt;/strong&gt;&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;当 $x_i$ 较大（如 $&gt;1000$）时直接算 $e^{x_i}$ 会数值溢出，正确的稳定化实现是：&lt;strong&gt;对所有 $x_i$ 减去最大值&lt;/strong&gt; $x_i' = x_i - \max(x)$ 再算指数。&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;p&gt;这题我会。关键在于 exp 的缩放不变性：分子分母同乘 $e^{-M}$ 不改变比值——&lt;/p&gt;
$$\text{softmax}(x_i) = \frac{e^{x_i}}{\sum_j e^{x_j}} = \frac{e^{x_i - M}}{\sum_j e^{x_j - M}}, \quad M = \max_j x_j$$&lt;p&gt;减去最大值后最大的指数项是 $e^0 = 1$，其余都小于 1，不会溢出。FP64 只是缓解不是根治，L2 归一化会改变结果，除以最大值没用——只有减最大值是对的。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;#4 欧氏距离（✅）&lt;/strong&gt;：两个嵌入向量 $u, v$ 的欧氏距离是 $\|u - v\|_2 = \sqrt{\sum_i (u_i - v_i)^2}$，距离越小越相似。原题给 $v_x = [2, 3]^T$、$v_y = [4, 1]^T$：&lt;/p&gt;
$$\|v_x - v_y\|_2 = \sqrt{(2-4)^2 + (3-1)^2} = \sqrt{4 + 4} = \sqrt{8}$$&lt;p&gt;答案 D。干扰项 $\sqrt{5}$ 是把差的平方算成差的绝对值平方，$\sqrt{13}$、$\sqrt{10}$ 是把向量模当距离。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;#8 余弦相似度（✅）&lt;/strong&gt;：$\cos(u, v) = \dfrac{u \cdot v}{\|u\| \|v\|}$，只看方向不看长度。原题 $x = (1, 0, 1)$、$y = (1, 1, 0)$：&lt;/p&gt;
$$x^T y = 1 \times 1 + 0 \times 1 + 1 \times 0 = 1, \qquad \|x\| = \|y\| = \sqrt{1^2 + 1^2} = \sqrt{2}$$$$\cos\theta = \frac{1}{\sqrt{2} \times \sqrt{2}} = \frac{1}{2} = 0.5$$&lt;p&gt;答案 A。干扰项 0.707 是 $\frac{1}{\sqrt{2}}$——把分母错算成一个模。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;#9 二次插值（✅）&lt;/strong&gt;：给定 3 个点可唯一确定二次多项式，代值求解即可（拉格朗日或待定系数法）。原题三点 $(0, 1), (1, 2), (2, 5)$，求 $x = 1.5$ 处值：&lt;/p&gt;
$$p(x) = x^2 + 1, \qquad p(1.5) = 1.5^2 + 1 = 2.25 + 1 = 3.25$$&lt;p&gt;答案 D。这个题最稳的做法是把三点代入 $p(x) = ax^2 + bx + c$ 解出系数——$p(0)=c=1$、$p(1)=a+b+c=2$、$p(2)=4a+2b+c=5$，得 $a=1, b=0, c=1$。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;#7 贝叶斯（✅）&lt;/strong&gt;：经典的两公司次品率题。设 A 供应比例 $P(A)$、次品率 $P(D|A)$，B 同理，抽到次品来自 B 的概率是&lt;/p&gt;
$$P(B|D) = \frac{P(D|B)\,P(B)}{P(D|A)\,P(A) + P(D|B)\,P(B)}$$&lt;p&gt;原题数值：A 公司供应 70%、次品率 2%，B 公司供应 30%、次品率 4%。抽到次品来自 B 的概率&lt;/p&gt;
$$P(B|D) = \frac{P(D|B)\,P(B)}{P(D|A)\,P(A) + P(D|B)\,P(B)} = \frac{0.04 \times 0.3}{0.02 \times 0.7 + 0.04 \times 0.3} = \frac{0.012}{0.026} \approx 0.4615$$&lt;p&gt;答案 C，约 46.2%。注意分子是&amp;quot;B 供应的 30% 里次品的 4%&amp;quot;，分母是两家各自贡献的次品总量——这就是全概率公式的分母。陷阱：&lt;strong&gt;不要&lt;/strong&gt;直接用 $P(D|B) = 4\%$ 当答案，那忽略了 A 公司的次品贡献。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;#6 SVD 存储空间减少比例（❌ 我不该错）&lt;/strong&gt;&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;用户-短视频评分矩阵 $m \times n$，SVD 保留秩 $k$，存储减少比例约多少？&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;p&gt;这是我最懊恼的一道——公式我是知道的，但考场上算错了。&lt;/p&gt;
&lt;p&gt;SVD 压缩的原理：$A_{m\times n} \approx U_{m\times k}\, \Sigma_{k\times k}\, V^T_{k\times n}$，存储量从 $mn$ 降到 $k(m+n)+k \approx k(m+n)$。减少比例&lt;/p&gt;
$$\text{减少比例} = 1 - \frac{k(m+n)}{mn}$$&lt;p&gt;原题数值：矩阵 $1000 \times 800$，保留秩 $k = 50$。原矩阵存 $1000 \times 800 = 800000$ 个元素；SVD 后存 $U$（$1000 \times 50$）+ $\Sigma$（$50$）+ $V^T$（$50 \times 800$）= $50000 + 50 + 40000 = 90050$。减少比例&lt;/p&gt;
$$1 - \frac{90050}{800000} = 0.8874 \approx 88.74\%$$&lt;p&gt;答案 A。为什么 SVD 能省空间？因为原始矩阵存的是 $mn$ 个元素，而低秩近似只存&amp;quot;分解出来的骨架&amp;quot;：左奇异向量、奇异值、右奇异向量，三者加起来远小于 $mn$（当 $k \ll \min(m,n)$）。这题的教训是：&lt;strong&gt;公式会背 ≠ 考场算对&lt;/strong&gt;，SVD 存储公式要能当场推一遍而不是凭记忆。&lt;/p&gt;
&lt;h3 id="2-机器学习基础"&gt;2. 机器学习基础
&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;#11 MLE（✅）&lt;/strong&gt;：最大似然估计就是&amp;quot;找一个参数 $\theta$，让已观测数据出现的可能性最大&amp;quot;——选 D，逆向思考的表述。这题是概念题，没难度。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;#14 集中趋势（✅）&lt;/strong&gt;：描述中心位置用均值（B）；偏度描述不对称，标准差和方差描述离散程度。概念题。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;#5 反向传播（✅）&lt;/strong&gt;：单神经元 $y = wx + b$，恒等激活，MSE 损失。链式法则：&lt;/p&gt;
$$\frac{\partial L}{\partial w} = \frac{\partial L}{\partial y} \cdot \frac{\partial y}{\partial w} = (y - t) \cdot x$$&lt;p&gt;原题数值：$x = 2, w = 1, b = 0, t = 5$，$L = \frac{1}{2}(y - t)^2$。输出 $y = wx + b = 2$，链式法则：&lt;/p&gt;
$$\frac{\partial L}{\partial w} = \frac{\partial L}{\partial y} \cdot \frac{\partial y}{\partial w} = (y - t) \cdot x = (2 - 5) \times 2 = -6$$&lt;p&gt;答案 C。基础反传导数题，会链式法则就能做；$L$ 带 $\frac{1}{2}$ 系数是为了求导后约掉，这是 MSE 的常见写法。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;#3 HRL 分层强化学习 + 级联惩罚（✅）&lt;/strong&gt;&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;任务 A→B→C→D 严格顺序依赖，B 失败则 C、D 跳过。A 成功、B 失败，求总奖励。级联惩罚 = 被跳过子任务基础奖励的平均值的负值，只加在失败子任务上。&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;p&gt;这题我理解了规则就能算对。通式：若 B 失败（A 已成功），总奖励为&lt;/p&gt;
$$R = R_A + P_B - \frac{R_C + R_D}{2}$$&lt;p&gt;套用原题数值：$R_A = 5$，$R_B = 8$，$P_B = -\frac{R_B}{4} = -2$，$R_C = 12$，$R_D = 16$，级联惩罚为 $-\frac{R_C + R_D}{2} = -\frac{12+16}{2} = -14$。总奖励&lt;/p&gt;
$$R = 5 + (-2) + (-14) = -11$$&lt;p&gt;答案 B。关键陷阱是：&lt;strong&gt;被跳过的 C、D 本身不获得任何奖励或惩罚&lt;/strong&gt;，只是它们的奖励平均值作为级联惩罚扣在 B 头上（注意惩罚是负值累加，不是&amp;quot;扣 C、D 各一半&amp;quot;）。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;#12 逻辑回归的评价指标（❌ 基础盲区）&lt;/strong&gt;&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;逻辑回归性能评价&lt;strong&gt;不包括&lt;/strong&gt;：A. Log Loss B. AUC C. Accuracy D. 均方误差（MSE）。答案 D。&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;p&gt;这题错得很基础。逻辑回归是分类模型，评价用准确率、AUC、对数损失；MSE 是回归指标。当时我大概是&amp;quot;看着四个选项都觉得眼熟&amp;quot;就选错了——这是概念没建起&amp;quot;模型类型 → 评价指标&amp;quot;的映射表。&lt;/p&gt;
&lt;p&gt;更值得记的 WHY：逻辑回归训练&lt;strong&gt;不用 MSE 而用交叉熵&lt;/strong&gt;，有两个原因。一是凸性——MSE 对 sigmoid 复合后是非凸函数，交叉熵是凸的，保证收敛到全局最优；二是梯度特性——MSE 在 sigmoid 饱和区梯度趋近于零（梯度消失），交叉熵的梯度 $\hat{y} - y$ 与激活饱和无关。评价指标和损失函数是两回事，但背后的逻辑是相通的。&lt;/p&gt;
&lt;h3 id="3-深度学习与训练工程4-题全错最大失分区"&gt;3. 深度学习与训练工程（4 题全错，最大失分区）
&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;#13 端侧推理峰值内存（❌）&lt;/strong&gt;&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;哪项因素最可能导致实际峰值内存显著超过模型文件大小？答案 A：中间激活、临时 buffer 和多分支并发执行。&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;p&gt;模型文件只存权重参数，但推理时内存 = 权重 + &lt;strong&gt;中间激活值&lt;/strong&gt; + 算子临时 buffer + 并发分支结果。激活值才是内存大头：一个 $1 \times 3 \times H \times W$ 的中间特征图动辄几十 MB，多头/多分支并行时还要同时存多份。所以&amp;quot;模型 100MB，跑起来峰值 1GB+&amp;ldquo;是常态。我当时选了别的选项，本质是没建立&amp;quot;静态权重 vs 动态激活&amp;quot;的内存心智模型。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;#17 早期停止（❌ 多选题）&lt;/strong&gt;&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;正确说法：A. 可设 patience 允许连续几轮不下降；B. 根据验证集表现选最佳轮数模型；C. 验证集最优则测试集一定最好（错）；D. 限制训练时间从而限制可学习的数据量（错）。答案 A、B。&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;p&gt;C 的陷阱是&amp;quot;一定&amp;rdquo;——验证集最优不能保证测试集最优（可能有轻微过拟合到验证集）。D 的陷阱更隐蔽：早期停止限制的是&lt;strong&gt;继续拟合训练数据的程度&lt;/strong&gt;（epoch 数），不是&amp;quot;能用的数据量&amp;quot;——数据量是固定的，它限制的是在数据上反复学多少遍。多选陷阱往往藏在绝对化表述（&amp;ldquo;一定&amp;rdquo;）和概念偷换（&amp;ldquo;训练时间&amp;rdquo; vs &amp;ldquo;数据量&amp;rdquo;）里。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;#18 梯度矩阵的秩与零空间（❌ 多选题）&lt;/strong&gt;&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;已知卷积网络梯度矩阵 $A \in \mathbb{R}^{1000 \times 2000}$，秩 $r(A) = 500$。说法：A. 零空间维度高 → 存在多个方向使梯度投影为零，一阶变化为零，易陷入平坦区/鞍点；B. 零空间维越高 → 有效信息维度越低，无效方向越多；C. 该梯度矩阵零空间维度 = 1500；D. 增大 batch 可直接降低零空间维度（错）。答案 A、B、C。&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;p&gt;核心是&lt;strong&gt;秩—零空间定理&lt;/strong&gt;（Rank-Nullity Theorem）：对 $1000 \times 2000$ 矩阵，$\text{rank}(A) + \text{nullity}(A) = 2000$（列数），所以 $\dim N(A) = 2000 - 500 = 1500$——这就是 C 的答案，2000 个参数方向里 1500 个是&amp;quot;无效方向&amp;quot;。零空间里的方向 $v$ 满足 $Av = 0$，意味着沿 $v$ 更新参数时所有样本的梯度贡献都是零——损失函数沿这些方向的一阶变化为零，梯度下降在这些方向上看不到&amp;quot;下坡路&amp;quot;，所以容易卡在鞍点或平坦区。这就是&amp;quot;梯度冗余 → 优化困难&amp;quot;的机理。D 错在：batch 大小影响梯度估计的噪声和矩阵结构，但不能保证零空间维度降低。&lt;/p&gt;
&lt;p&gt;这题是线代定理和深度学习优化的交叉题，答错说明我对&amp;quot;零空间&amp;quot;只有名词印象，没有几何直觉。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;#20 大模型训练稳定性（❌ 多选题）&lt;/strong&gt;&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;常用手段：A. 学习率 warmup；C. 梯度裁剪；D. 混合精度（FP16/BF16）。B. 固定学习率全程（错）。答案 A、C、D。&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;warmup&lt;/strong&gt;：训练初期学习率从 0 线性爬升。为什么需要？因为初始权重下 loss 曲面陡峭、梯度的统计量不可靠，一步迈太大直接发散；等梯度统计稳定了再放开学习率。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;梯度裁剪&lt;/strong&gt;：梯度范数超过阈值就缩放回阈值。对抗梯度爆炸（RNN/长序列尤其需要）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;混合精度&lt;/strong&gt;：FP16/BF16 存权重和激活、FP32 存主权重，配合 loss scaling。省显存、提速度，BF16 的动态范围大所以大模型训练更常用。&lt;/li&gt;
&lt;li&gt;固定学习率从头到尾在真实训练里基本不存在——通常会配 warmup + 余弦退火或别的调度。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这题是纯工程知识，我完全没接触过训练大模型，全错不冤。&lt;/p&gt;
&lt;h3 id="4-大模型--多模态--rl2-题错"&gt;4. 大模型 / 多模态 / RL（2 题错）
&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;#2 VQA（✅）&lt;/strong&gt;：视觉问答，输入是图像 + 文本问题，输出是文本答案。多模态入门概念，我会。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;#10 RAG 余弦相似度匹配等级（❌）&lt;/strong&gt;&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;用户查询向量 vs 历史故障案例向量，算余弦相似度保留 3 位小数，再判断匹配等级（极高/高/中等阈值）。答案：余弦相似度 ≈ 0.989，属于极高匹配。&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;p&gt;原题数值：查询向量 $q = [0.8, 1.2, 1.6, 2.0]$，历史案例向量 $c = [1.0, 0.8, 1.4, 1.8]$；阈值：$\ge 0.95$ 极高匹配、$0.85 \sim 0.95$ 高匹配、$&lt; 0.85$ 中等匹配。完整计算：&lt;/p&gt;
$$q^T c = 0.8 \times 1.0 + 1.2 \times 0.8 + 1.6 \times 1.4 + 2.0 \times 1.8 = 0.8 + 0.96 + 2.24 + 3.6 = 7.6$$$$\|q\| = \sqrt{0.8^2 + 1.2^2 + 1.6^2 + 2.0^2} = \sqrt{8.64}, \qquad \|c\| = \sqrt{1.0^2 + 0.8^2 + 1.4^2 + 1.8^2} = \sqrt{6.84}$$$$\cos\theta = \frac{q^T c}{\|q\|\|c\|} = \frac{7.6}{\sqrt{8.64} \times \sqrt{6.84}} \approx 0.989$$&lt;p&gt;答案 B：$\approx 0.989$，$\ge 0.95$ 属于极高匹配。这是&amp;quot;计算 + 阈值判断&amp;quot;两段式题——我公式会但考场上算错，教训是这种题要&lt;strong&gt;一步步写&lt;/strong&gt;：内积、两个模、比值、对照阈值，中间任何一步粗心都会丢分。RAG 检索的核心就是 embedding 向量相似度，这是大模型应用的必考方向，需要练熟。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;#15 GSPO 序列级重要性采样（❌）&lt;/strong&gt;&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;GSPO 引入序列级（sequence-level）重要性采样比率的主要原因：A. 减少长序列中 token 级比率的高方差问题。答案 A。&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;p&gt;GSPO = &lt;strong&gt;Group Sequence Policy Optimization&lt;/strong&gt;（Qwen3 系列使用的 RL 训练算法，arXiv:2507.18071）。这是我完全没接触过的前沿，考场上只能蒙。现在补上了：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;在 GRPO 里，重要性采样比率是 token 级的：$r_t = \dfrac{\pi_\theta(y_t|x)}{\pi_{\theta_{old}}(y_t|x)}$。&lt;/li&gt;
&lt;li&gt;序列级比率把整个序列的似然比开 $|y_i|$ 次方（长度归一化的几何平均）：&lt;/li&gt;
&lt;/ul&gt;
$$s_i(\theta) = \left( \frac{\pi_\theta(y_i|x)}{\pi_{\theta_{old}}(y_i|x)} \right)^{1/|y_i|} = \left( \prod_{t=1}^{|y_i|} r_{i,t} \right)^{1/|y_i|}$$&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;为什么要序列级&lt;/strong&gt;：token 级比率在长序列里是逐 token 相乘累积的，方差随序列长度爆炸（乘法累积噪声），而序列级比率对齐了&amp;quot;奖励在序列级给出&amp;quot;的事实，方差更稳定，还天然免疫 MoE 专家路由变化的扰动。所以 GSPO 训练长思维链模型比 GRPO 稳定。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;一句话记忆：&lt;strong&gt;token 级比率是&amp;quot;有偏但低方差&amp;quot;的简化，序列级比率用长度归一化换稳定性——GSPO 选后者。&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id="5-数值线性代数1-题错"&gt;5. 数值线性代数（1 题错）
&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;#16 Householder 变换（❌ 多选题）&lt;/strong&gt;&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;正确说法：A. Householder 矩阵是正交矩阵；C. Householder QR 比 Gram-Schmidt 数值更稳定；D. Householder 矩阵是对称矩阵。B. 行列式为 +1（错，是 −1）。答案 A、C、D。&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;p&gt;Householder 反射矩阵：$H = I - \dfrac{2vv^T}{v^T v}$（$v$ 是单位向量时简化为 $I - 2vv^T$）。性质：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;对称&lt;/strong&gt;：$H^T = H$；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;正交&lt;/strong&gt;：$H^T H = H^2 = I$；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;行列式 = −1&lt;/strong&gt;：它是一次反射（镜面变换），反射改变手性，所以行列式是 −1 而不是 +1。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;它把一个向量反射到坐标轴方向，用于 QR 分解时比 Gram-Schmidt 数值稳定——Gram-Schmidt 的经典形式在列向量近似线性相关时会丢失正交性（重正交化开销大），Householder 用反射矩阵逐列消元，数值更稳。当时我连&amp;quot;反射 = 行列式 −1&amp;quot;这个直觉都没有，更别说 QR 稳定性对比了。&lt;/p&gt;
&lt;h3 id="6-在线学习1-题错"&gt;6. 在线学习（1 题错）
&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;#19 在线逻辑回归（❌ 多选题）&lt;/strong&gt;&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;正确说法：A. 每次一个样本更新 = batch size 1 的 SGD；B. AdaGrad 自适应学习率在稀疏特征场景优于固定学习率；C. 在线学习比批量训练更能适应概念漂移。D. 固定学习率保证收敛到全局最优（错）。答案 A、B、C。&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;A 是定义：在线学习一次一个样本，就是 $batch=1$ 的随机梯度下降。&lt;/li&gt;
&lt;li&gt;B：AdaGrad 对每个参数单独维护累计梯度平方，出现少的特征（稀疏）保持较大学习率，所以适合稀疏场景——固定学习率做不到这种自适应。&lt;/li&gt;
&lt;li&gt;C：在线学习持续用新数据更新，分布变了模型跟着变；批量训练用旧数据拟合的模型对概念漂移反应慢。&lt;/li&gt;
&lt;li&gt;D：固定学习率参数会在最优点附近震荡，不保证严格收敛。&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="三失分结构60-分是怎么丢的"&gt;三、失分结构：60 分是怎么丢的
&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;th&gt;我的结果&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;数学基础&lt;/td&gt;
 &lt;td&gt;#1 #4 #7 #8 #9 #6&lt;/td&gt;
 &lt;td&gt;5/6（SVD 计算失误）&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;机器学习基础&lt;/td&gt;
 &lt;td&gt;#11 #14 #5 #3 #12&lt;/td&gt;
 &lt;td&gt;4/5（逻辑回归指标盲区）&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;深度学习与训练工程&lt;/td&gt;
 &lt;td&gt;#13 #17 #18 #20&lt;/td&gt;
 &lt;td&gt;0/4 ❌&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;大模型 / 多模态 / RL&lt;/td&gt;
 &lt;td&gt;#2 #10 #15&lt;/td&gt;
 &lt;td&gt;1/3（VQA 对，RAG 算错，GSPO 盲区）&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;数值线性代数&lt;/td&gt;
 &lt;td&gt;#16&lt;/td&gt;
 &lt;td&gt;0/1 ❌&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;在线学习&lt;/td&gt;
 &lt;td&gt;#19&lt;/td&gt;
 &lt;td&gt;0/1 ❌&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;三条结论：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;基础概念是稳的&lt;/strong&gt;：贝叶斯、插值、Softmax、反传、MLE 这些传统 ML 数学题全对，说明大学数学底子还在。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;计算题要练&amp;quot;考场态&amp;quot;&lt;/strong&gt;：#6 SVD、#10 RAG 余弦都是&amp;quot;公式会、当场算错&amp;quot;，以后这类题必须动笔一步步写，不心算。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;真正的失分大头是知识盲区&lt;/strong&gt;：训练工程（warmup/混合精度/端侧内存）、大模型对齐（GSPO）、数值线代（Householder/零空间）、在线学习——这四块加起来丢了 5 道 12 分大题中的 4 道。这些是 AI 岗位和普通开发岗的&lt;strong&gt;分水岭知识&lt;/strong&gt;，也是我接下来要补的重点。&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h2 id="四接下来要补的知识点清单"&gt;四、接下来要补的知识点清单
&lt;/h2&gt;&lt;p&gt;按优先级和投入产出排序，这些是我后面要逐个补的：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;P0（必考高频，先补）&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;数值线性代数&lt;/strong&gt;：SVD 低秩近似与存储压缩（会推导）、Householder 反射与 QR、秩—零空间定理及几何直觉（零空间 = 无效更新方向）。来源：任何一本数值分析教材的前三章 + 线性代数复习。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;训练稳定性工程&lt;/strong&gt;：学习率 warmup 与调度（余弦退火）、梯度裁剪、混合精度训练（FP16/BF16 + loss scaling）、端侧/训练峰值内存构成（权重 vs 激活 vs buffer）。来源：大模型训练综述/实践文章。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;分类模型评估全家桶&lt;/strong&gt;：Accuracy / Precision / Recall / F1 / AUC / Log Loss 的适用场景，以及&amp;quot;逻辑回归为什么用交叉熵不用 MSE&amp;quot;（凸性 + 梯度消失）。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;P1（AI 岗位特色，补了才有区分度）&lt;/strong&gt;&lt;/p&gt;
&lt;ol start="4"&gt;
&lt;li&gt;&lt;strong&gt;大模型 RL 对齐入门&lt;/strong&gt;：GRPO → GSPO 的演进（token 级 vs 序列级重要性采样、长度归一化、为什么长思维链训练更稳）。来源：arXiv:2507.18071（GSPO）+ 配套解读。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;在线学习与自适应优化&lt;/strong&gt;：SGD 家族、AdaGrad / RMSProp / Adam 的动机与适用场景、概念漂移。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;P2（算法题技巧，顺手练）&lt;/strong&gt;&lt;/p&gt;
&lt;ol start="6"&gt;
&lt;li&gt;&lt;strong&gt;DP&amp;quot;最优+次优状态&amp;quot;技巧&lt;/strong&gt;：记住&amp;quot;转移约束只跟最优状态的某个属性冲突时，维护两个最优状态即可&amp;quot;这个模式，多 Agent 调度、股票交易类题都会用到。&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h2 id="来源"&gt;来源
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;题目与官方解析：CodeFun2000.com 华为机考 AI 方向 8 月 5 日场次（塔子哥），原文 &lt;a class="link" href="https://mp.weixin.qq.com/s/3H3agcturofiMn9pOriZOg" target="_blank" rel="noopener"
 &gt;https://mp.weixin.qq.com/s/3H3agcturofiMn9pOriZOg&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;第 1 题（基站空间重叠区域识别）原题刷题入口：https://codefun2000.com/ide/P5198&lt;/li&gt;
&lt;li&gt;GSPO 论文：Group Sequence Policy Optimization（Qwen3），https://arxiv.org/abs/2507.18071&lt;/li&gt;
&lt;li&gt;备注：选择题 #3-#10 的数值已从微信公众号原文（mdnice 的 data-formula 属性）提取补全，与官方解析一致。&lt;/li&gt;
&lt;/ul&gt;</description></item><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><item><title>makemore Part 3：激活函数、梯度消失与 Batch Normalization</title><link>https://zewang0217.github.io/p/makemore-part3-activations-batchnorm/</link><pubDate>Sat, 18 Jul 2026 00:00:00 +0000</pubDate><guid>https://zewang0217.github.io/p/makemore-part3-activations-batchnorm/</guid><description>&lt;h1 id="makemore-part-3激活函数梯度消失与-batch-normalization"&gt;makemore Part 3：激活函数、梯度消失与 Batch Normalization
&lt;/h1&gt;
 &lt;blockquote&gt;
 &lt;p&gt;资料源：Andrej Karpathy - Building makemore Part 3: Activations &amp;amp; Gradients, BatchNorm（2026-07-18）
前篇：&lt;a class="link" href="https://zewang0217.github.io/post/makemore-mlp/" &gt;makemore Part 2：MLP 语言模型&lt;/a&gt;&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;h2 id="part-2-的遗留问题"&gt;Part 2 的遗留问题
&lt;/h2&gt;&lt;p&gt;Part 2 我们按照 Bengio 2003 的论文，实现了一个 MLP 字符级语言模型——输入几个历史字符的 embedding，拼接后过一层隐藏层（tanh），最后用 softmax 预测下一个字符。&lt;/p&gt;
&lt;p&gt;当时我们初始化模型后直接开始训练，损失确实在下降，模型也确实在工作。但我们&lt;strong&gt;没有检查的一件事&lt;/strong&gt;是：网络各层的激活值和梯度分布是什么样的？它们健康吗？&lt;/p&gt;
&lt;p&gt;答案是：&lt;strong&gt;大概率不健康&lt;/strong&gt;。这就是 Part 3 要解决的问题。&lt;/p&gt;
&lt;p&gt;回顾一下 Part 2 的 MLP 架构：&lt;/p&gt;
&lt;p&gt;&lt;img class="gallery-image" data-flex-basis="144px" data-flex-grow="60" height="3300" 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-part3-activations-batchnorm/mlp-architecture.png" srcset="https://zewang0217.github.io/p/makemore-part3-activations-batchnorm/mlp-architecture_hu_601e899fd11c25b7.png 800w, https://zewang0217.github.io/p/makemore-part3-activations-batchnorm/mlp-architecture_hu_e0e438d297cb1995.png 1600w, https://zewang0217.github.io/p/makemore-part3-activations-batchnorm/mlp-architecture.png 1980w" width="1980"&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;图：makemore MLP 架构。&lt;/strong&gt; 输入 3 个字符 → Embedding 表查稠密向量 → 拼接 → 隐藏层（线性变换 + tanh）→ 输出层（线性变换 + softmax）→ 交叉熵损失。&lt;/p&gt;
&lt;h2 id="一个理想初始化的标准"&gt;一个理想初始化的标准
&lt;/h2&gt;&lt;p&gt;当我们初始化一个神经网络时，我们希望&lt;strong&gt;输出层的概率分布在训练初期接近 uniform&lt;/strong&gt;。对于字符级语言模型（27 个字符），这意味着 softmax 输出的每个类别的概率应该在 1/27 ≈ 3.7% 左右，对应的交叉熵损失应该是 &lt;code&gt;-ln(1/27) ≈ 3.3&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;如果初始损失远低于 3.3，说明模型一开始就&amp;quot;太自信&amp;quot;了，这通常意味着某些 logits 异常大；如果远高于 3.3，说明输出分布太分散。&lt;/p&gt;
&lt;p&gt;但保持初始输出的均匀分布只是第一步。真正的问题是：&lt;strong&gt;随着网络加深，激活值的分布会逐层漂移&lt;/strong&gt;。&lt;/p&gt;
&lt;h2 id="tanh-饱和与梯度消失"&gt;Tanh 饱和与梯度消失
&lt;/h2&gt;&lt;p&gt;Tanh 函数的值域是 (-1, 1)，它有一个关键特性：&lt;strong&gt;当输入绝对值很大时（&amp;gt;2 或 &amp;lt;-2），tanh 进入饱和区，输出几乎不变，梯度趋近于 0&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-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;tanh(x) 的梯度 = 1 - tanh(x)²
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;当 x=0 时，梯度 = 1
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;当 x=2 时，梯度 ≈ 0.07
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;当 x=3 时，梯度 ≈ 0.01
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;当 x=5 时，梯度 ≈ 0.0002
&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;code&gt;h = Wx + b&lt;/code&gt;，然后过 tanh。如果 W 的初始值太大，h 的方差会很大，导致 tanh 输入进入饱和区。这一层退化为&amp;quot;要么 1 要么 -1&amp;quot;的开关，梯度几乎为零。&lt;/p&gt;
&lt;p&gt;更糟的是，梯度会随着层数&lt;strong&gt;指数级衰减&lt;/strong&gt;：每一层把梯度缩小一点，N 层之后就消失了。这就是&lt;strong&gt;梯度消失&lt;/strong&gt;——深层网络的经典问题。&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;梯度消失不是&amp;quot;训练变慢&amp;quot;，而是&amp;quot;底层几乎不再学习&amp;quot;。前面几层的权重基本不动，只有最后几层在变化。&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;h3 id="权重初始化的改进"&gt;权重初始化的改进
&lt;/h3&gt;&lt;p&gt;一个直接的思路：&lt;strong&gt;控制权重初始化的大小，让各层激活值的方差保持稳定&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;Kaiming He 等人发现：对于使用 ReLU 的网络，权重应该从 &lt;code&gt;N(0, 2/fan_in)&lt;/code&gt; 初始化；对于 tanh/Sigmoid，Xavier/Glorot 初始化建议用 &lt;code&gt;N(0, 1/fan_in)&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;为什么是 &lt;code&gt;1/fan_in&lt;/code&gt;？考虑一个简单的线性层 &lt;code&gt;y = Wx&lt;/code&gt;，其中 x 的均值为 0，方差为 1。W 有 fan_in 个输入。y 的方差大约是 &lt;code&gt;fan_in * Var(W)&lt;/code&gt;。要 y 的方差保持为 1，需要 &lt;code&gt;Var(W) = 1/fan_in&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;用 Kaiming 初始化后，各层激活值的分布确实更稳定了——tanh 的输入不会一上来就飞到饱和区，梯度也能正常流通。&lt;/p&gt;
&lt;p&gt;但 Kaiming 初始化不是银弹。它只是一个&lt;strong&gt;静态的修补&lt;/strong&gt;——当你确定网络结构、深度、激活函数后，算一个合适的初始值。一旦网络深度变化、层类型变化（Linear → CNN → Attention），你得重新算。&lt;/p&gt;
&lt;p&gt;有没有更动态、更通用的方案？&lt;/p&gt;
&lt;h2 id="batch-normalization2015"&gt;Batch Normalization（2015）
&lt;/h2&gt;&lt;p&gt;Batch Normalization（BN）是 Google 在 2015 年提出的，一篇论文彻底改变了深层网络的训练方式。&lt;/p&gt;
&lt;h3 id="bn-的核心思想"&gt;BN 的核心思想
&lt;/h3&gt;&lt;p&gt;每一层之前，显式地把激活值拉回到&lt;strong&gt;均值为 0、方差为 1 的标准正态分布&lt;/strong&gt;，然后再送给下一层。&lt;/p&gt;
&lt;p&gt;具体来说，对于一个 batch 的激活值 &lt;code&gt;x&lt;/code&gt;（维度是 &lt;code&gt;batch_size × features&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;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-gdscript3" data-lang="gdscript3"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;μ&lt;/span&gt;&lt;span class="n"&gt;_B&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;x&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="err"&gt;σ²&lt;/span&gt;&lt;span class="n"&gt;_B&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;var&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 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;x̂&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="err"&gt;μ&lt;/span&gt;&lt;span class="n"&gt;_B&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="err"&gt;√&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;σ²&lt;/span&gt;&lt;span class="n"&gt;_B&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="err"&gt;ε&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="err"&gt;γ&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="err"&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;strong&gt;归一化之后又加了一个可学习的缩放和平移&lt;/strong&gt;。为什么？&lt;/p&gt;
&lt;p&gt;如果不加这一层，强制输出为 N(0,1) 可能限制了网络的表达能力——网络可能想学到一个非标准正态的分布。&lt;code&gt;γ&lt;/code&gt; 和 &lt;code&gt;β&lt;/code&gt; 给了网络&amp;quot;反悔&amp;quot;的能力：如果网络发现 N(0,1) 不好，可以学回原来的分布。&lt;/p&gt;
&lt;p&gt;实际上，&lt;code&gt;γ&lt;/code&gt; 初始化为 1，&lt;code&gt;β&lt;/code&gt; 初始化为 0，所以 BN 一开始做的是纯归一化，然后网络在训练过程中慢慢调整 &lt;code&gt;γ&lt;/code&gt; 和 &lt;code&gt;β&lt;/code&gt;。&lt;/p&gt;
&lt;h3 id="bn-解决了什么问题"&gt;BN 解决了什么问题？
&lt;/h3&gt;&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;梯度消失&lt;/strong&gt;：tanh 的输入被拉到 0 附近，永远在 tanh 的线性区域工作（梯度最大），不会再进入饱和区。这使得我们可以训练更深的网络，&lt;strong&gt;而不需要小心翼翼地调初始化&lt;/strong&gt;。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;对初始化不敏感&lt;/strong&gt;：有了 BN，用什么样的初始化差别不大。BN 会自动修正激活值的分布。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;允许更大的学习率&lt;/strong&gt;：激活值分布稳定 → 梯度分布稳定 → 可以用更大的学习率加速训练。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="bn-的正则化效果副效应"&gt;BN 的正则化效果（副效应）
&lt;/h3&gt;&lt;p&gt;这是一个&amp;quot;意外收获&amp;quot;：&lt;strong&gt;BN 有正则化效果&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;原因：每个样本在一个 batch 中的归一化依赖于同一 batch 中的其他样本。具体用哪些样本来算均值/方差，每个 epoch 都不同（因为 shuffle）。这相当于在训练过程中引入了噪声——同一个样本每次经过 BN 时的&amp;quot;语境&amp;quot;不同。&lt;/p&gt;
&lt;p&gt;这种噪声让网络不容易过拟合到特定样本的精确特征。类似于 Dropout，但来源不同：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Dropout&lt;/strong&gt;：显式随机丢弃神经元&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;BN 的正则化&lt;/strong&gt;：隐式通过 batch 统计量的随机性引入&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这也是为什么很多任务中，去掉 BN 后需要加 Dropout 或其他正则化手段才能维持同样的泛化性能。&lt;/p&gt;
&lt;h3 id="bn-的耦合问题"&gt;BN 的耦合问题
&lt;/h3&gt;&lt;p&gt;BN 有一个不易察觉的代价：&lt;strong&gt;样本耦合&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;没有 BN 时，每个样本独立处理，batch 只是效率优化。有了 BN，算 mean/std 依赖整个 batch——样本 A 的输出耦合了样本 B、C、D。同一样本在不同 batch 里输出不同（因为&amp;quot;同桌&amp;quot;不同），梯度也变得耦合。&lt;/p&gt;
&lt;p&gt;这就是为什么后来的人更倾向用 Layer Normalization（不做 batch 维度的归一化，只做特征维度的归一化，每个样本独立）。&lt;/p&gt;
&lt;h3 id="训练-vs-推理bn-的两副面孔"&gt;训练 vs 推理：BN 的两副面孔
&lt;/h3&gt;&lt;p&gt;训练时，BN 使用&lt;strong&gt;当前 batch 的均值/方差&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;推理时，没有 batch 的概念（可能一次只来一个样本），不能再用 batch 统计量。所以 BN 在训练过程中维护&lt;strong&gt;running mean&lt;/strong&gt; 和 &lt;strong&gt;running variance&lt;/strong&gt;——对所有 batch 的均值/方差做指数移动平均（EMA）。&lt;/p&gt;
&lt;p&gt;推理时直接用 running 统计量，不再依赖 batch 大小。类比：训练时每次考试按这班同学的成绩来评分，推理时用过去一学期的平均分标准评分。&lt;/p&gt;
&lt;h2 id="训练过程的健康检查"&gt;训练过程的健康检查
&lt;/h2&gt;&lt;p&gt;Karpathy 在代码中做了大量的&lt;strong&gt;可视化&lt;/strong&gt;——这是理解网络是否在健康训练的核心手段：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;前向传播的激活值直方图&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;对每一层，在 forward 之后画出激活值的分布&lt;/li&gt;
&lt;li&gt;健康的分布（钟形）：接近 N(0,1)，没有大片饱和区域&lt;/li&gt;
&lt;li&gt;不健康的分布：集中在 ±1 附近（tanh 饱和），或者方差发散&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;反向传播的梯度直方图&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;各层梯度应该在相似的尺度上&lt;/li&gt;
&lt;li&gt;如果第一层的梯度比最后一层小 1000 倍，梯度消失了&lt;/li&gt;
&lt;li&gt;如果第一层的梯度比最后一层大 1000 倍，梯度爆炸了&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;调整 - 直方图变化 - 诊断的链条&lt;/strong&gt;：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;发现 W 太大 → 所有层激活值直方图两头高中间空（饱和）→ 梯度≈0&lt;/li&gt;
&lt;li&gt;把 W 乘 0.1 → 激活值缩回中间 → 钟形恢复 → 梯度开始流通&lt;/li&gt;
&lt;li&gt;加 BN → 所有层自动稳定 → 不需要手动试&lt;/li&gt;
&lt;li&gt;看梯度直方图 → 检查各层梯度是否同数量级&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;手动调 W → 看直方图 → 再调 → 再看&amp;hellip; 这个试错循环就是 BN 和 Kaiming 初始化要替代的。&lt;/p&gt;
&lt;h3 id="一个令人困惑的发现"&gt;一个令人困惑的发现
&lt;/h3&gt;&lt;p&gt;在 MLP 中，&lt;strong&gt;最后一层（输出层）的梯度往往比前面的隐藏层大几个数量级&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;原因：输出层直接连接着 softmax + 交叉熵损失。损失函数对输出层 logits 的梯度就是 &lt;code&gt;(softmax - target)&lt;/code&gt;，这是一个直接的误差信号。而前面层的梯度需要经过多层 tanh 的链式法则，每一层放大或缩小梯度，最终传到前面的梯度被大幅衰减。&lt;/p&gt;
&lt;p&gt;这说明：即使有了 BN，输出层和隐藏层之间的梯度尺度仍然不匹配。需要&lt;strong&gt;对最后一层用更小的学习率&lt;/strong&gt;，或者给不同的层设置不同的学习率。&lt;/p&gt;
&lt;h2 id="从-bn-到更多归一化层"&gt;从 BN 到更多归一化层
&lt;/h2&gt;&lt;p&gt;BN 工作得很好，但它有一个局限：&lt;strong&gt;依赖 batch 维度&lt;/strong&gt;。当 batch size 很小（比如 2 或 4）时，batch 统计量不稳定；当 batch size = 1 时（在线学习），BN 完全无法工作。&lt;/p&gt;
&lt;p&gt;这引出了一系列后续工作：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Layer Normalization（LN）&lt;/strong&gt;：对每个样本的所有特征做归一化，不依赖 batch。Transformer 用的就是 LN&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Instance Normalization（IN）&lt;/strong&gt;：对每个样本的每个通道做归一化。风格迁移常用&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Group Normalization（GN）&lt;/strong&gt;：介于 LN 和 IN 之间，把特征分成几组做归一化。适用于 batch size 小的场景&lt;/li&gt;
&lt;/ul&gt;

 &lt;blockquote&gt;
 &lt;p&gt;一个有趣的历史事实：LN 比 BN 早几个月被提出，但 BN 的影响力远大于 LN，因为 BN 的论文把故事讲得更好——它抓住了&amp;quot;internal covariate shift&amp;quot;这个痛点。直到 Transformer 出现，LN 才重新被重视。&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;h2 id="小结"&gt;小结
&lt;/h2&gt;&lt;p&gt;Part 3 的核心教训是：&lt;strong&gt;不要假设你的网络在健康地训练&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;三个层层递进的方案：&lt;/p&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;&lt;strong&gt;手动调初始化&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;缩小 W 的 std，让 tanh 落在线性区&lt;/td&gt;
 &lt;td&gt;深度/层类型变了得重新试&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;Kaiming/Xavier 初始化&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;根据 fan_in 算合适的 std&lt;/td&gt;
 &lt;td&gt;静态的，不动态适应&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;Batch Normalization&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;每层动态归一化 + 可学习缩放平移&lt;/td&gt;
 &lt;td&gt;依赖 batch，耦合样本&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;本质：&lt;/strong&gt; 激活值和梯度的统计特性决定网络能不能正常训练。调初始化、做 BN、做 ResNet，归根结底都在做同一件事——让激活值分布稳定，让梯度顺畅流通。&lt;/p&gt;
&lt;p&gt;Batch Normalization 是 makemore 系列的一个重要转折点——解决了&amp;quot;训练深不深得下去&amp;quot;的问题，为后面 Part 4（Backprop Ninja）和 Part 5（WaveNet）的复杂网络铺平了道路。&lt;/p&gt;
&lt;h2 id="思考与延伸"&gt;思考与延伸
&lt;/h2&gt;
 &lt;blockquote&gt;
 &lt;p&gt;&lt;strong&gt;理解激活函数的关键&lt;/strong&gt;：tanh 把直线掰弯，让神经元从&amp;quot;音量旋钮&amp;quot;变成&amp;quot;软开关&amp;quot;。没有激活函数，叠多少层都等价于一层。&lt;/p&gt;

 &lt;/blockquote&gt;

 &lt;blockquote&gt;
 &lt;p&gt;&lt;strong&gt;梯度消失的直觉&lt;/strong&gt;：不是&amp;quot;训练变慢&amp;quot;，是&amp;quot;前面层在空转&amp;quot;。梯度传不到的地方，参数不更新，深层网络的有效深度比层数浅得多。&lt;/p&gt;

 &lt;/blockquote&gt;

 &lt;blockquote&gt;
 &lt;p&gt;&lt;strong&gt;BN 的&amp;quot;反悔权&amp;quot;设计很巧妙&lt;/strong&gt;：先强制归一化到 N(0,1)，再用 γ 和 β 允许网络学回去。初始化=1 和 0，所以一开始做纯归一化，需要时再偏离。&lt;/p&gt;

 &lt;/blockquote&gt;

 &lt;blockquote&gt;
 &lt;p&gt;&lt;strong&gt;BN 的耦合是它最不直观的代价&lt;/strong&gt;：样本 A 的输出取决于 batch 里的 B、C、D——&amp;ldquo;同桌&amp;quot;不同，输出不同。这让调试变得复杂，也是后来 LN 被重用的原因。&lt;/p&gt;

 &lt;/blockquote&gt;

 &lt;blockquote&gt;
 &lt;p&gt;&lt;strong&gt;直方图可视化是核心调试手段&lt;/strong&gt;：一层层看激活值和梯度的分布，而不是只看最终 loss。各层梯度在同一数量级 → 健康；越靠近输入层越小 → 梯度消失。&lt;/p&gt;

 &lt;/blockquote&gt;</description></item></channel></rss>