<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Transformer on XEDCZQ的博客</title><link>https://xedczq.cn/tags/transformer/</link><description>Recent content in Transformer on XEDCZQ的博客</description><generator>Hugo -- gohugo.io</generator><language>zh-cn</language><lastBuildDate>Fri, 05 Jun 2026 23:10:00 +0800</lastBuildDate><atom:link href="https://xedczq.cn/tags/transformer/index.xml" rel="self" type="application/rss+xml"/><item><title>Transformer 20 步可视化学习笔记</title><link>https://xedczq.cn/post/transformerexplainer/</link><pubDate>Fri, 05 Jun 2026 23:10:00 +0800</pubDate><guid>https://xedczq.cn/post/transformerexplainer/</guid><description>&lt;img src="https://xedczq.cn/img/transformer-explainer/steps/step-03.jpg" alt="Featured image of post Transformer 20 步可视化学习笔记" /&gt;&lt;h1 id="transformer-20-步可视化学习笔记"&gt;&lt;a href="#transformer-20-%e6%ad%a5%e5%8f%af%e8%a7%86%e5%8c%96%e5%ad%a6%e4%b9%a0%e7%ac%94%e8%ae%b0" class="header-anchor"&gt;&lt;/a&gt;Transformer 20 步可视化学习笔记
&lt;/h1&gt;&lt;p&gt;本文参考 &lt;a class="link" href="https://poloclub.github.io/transformer-explainer/" target="_blank" rel="noopener"
 &gt;Transformer Explainer&lt;/a&gt; 的交互式讲解，按它的 20 个步骤整理一篇中文学习笔记。这个网站用 GPT-2 small 作为示例模型，把文本生成过程可视化成从输入 token 到输出概率的完整流水线。&lt;/p&gt;
&lt;p&gt;先记住一句话：&lt;strong&gt;GPT 类 Transformer 的核心任务是下一个 token 预测&lt;/strong&gt;。给定提示词：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Data visualization empowers users to
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;模型要回答的问题是：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;下一个最可能出现的 token 是什么？
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;为了回答这个问题，Transformer 会经历：分词、嵌入、位置编码、多层 Transformer Block、自注意力、MLP、logits、概率分布、采样策略等步骤。&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;本文所有步骤截图均截取自 &lt;a class="link" href="https://poloclub.github.io/transformer-explainer/" target="_blank" rel="noopener"
 &gt;Transformer Explainer&lt;/a&gt;，该项目由 Georgia Tech Polo Club 团队开发。截图用于个人学习笔记，建议结合原网站交互查看。&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id="what-is-transformertransformer-是什么"&gt;&lt;a href="#what-is-transformertransformer-%e6%98%af%e4%bb%80%e4%b9%88" class="header-anchor"&gt;&lt;/a&gt;What is Transformer：Transformer 是什么
&lt;/h2&gt;&lt;p&gt;Transformer 是现代大语言模型最常用的基础架构。GPT、Llama、Gemini 这类文本生成模型，核心都可以理解为 Transformer 架构的扩展版本。&lt;/p&gt;
&lt;p&gt;它最重要的能力不是“背答案”，而是从大量文本中学会语言模式，然后在推理时根据上下文预测下一个 token。这个预测会反复进行：预测一个 token，把它接到原文本后面，再继续预测下一个。&lt;/p&gt;
&lt;p&gt;&lt;img alt="Step 1: What is Transformer" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://xedczq.cn/img/transformer-explainer/steps/step-01.jpg"&gt;&lt;/p&gt;
&lt;p&gt;图源：Transformer Explainer，&lt;a class="link" href="https://poloclub.github.io/transformer-explainer/" target="_blank" rel="noopener"
 &gt;https://poloclub.github.io/transformer-explainer/&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="how-transformers-work文本生成的本质"&gt;&lt;a href="#how-transformers-work%e6%96%87%e6%9c%ac%e7%94%9f%e6%88%90%e7%9a%84%e6%9c%ac%e8%b4%a8" class="header-anchor"&gt;&lt;/a&gt;How Transformers Work：文本生成的本质
&lt;/h2&gt;&lt;p&gt;Transformer 生成文本时并不是一次性写完整段话，而是逐步生成。每一步都在做同一个任务：&lt;strong&gt;根据已有上下文，预测下一个 token 的概率分布&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;例如当前输入是：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Data visualization empowers users to
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;模型可能认为下一个 token 是 &lt;code&gt;visualize&lt;/code&gt; 的概率最高，也可能给 &lt;code&gt;create&lt;/code&gt;、&lt;code&gt;see&lt;/code&gt;、&lt;code&gt;make&lt;/code&gt; 等 token 分配较高概率。最终输出哪个 token，还会受到 temperature、top-k、top-p 等采样参数影响。&lt;/p&gt;
&lt;p&gt;&lt;img alt="Step 2: How Transformers Work" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://xedczq.cn/img/transformer-explainer/steps/step-02.jpg"&gt;&lt;/p&gt;
&lt;p&gt;图源：Transformer Explainer&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="transformer-architecture整体架构"&gt;&lt;a href="#transformer-architecture%e6%95%b4%e4%bd%93%e6%9e%b6%e6%9e%84" class="header-anchor"&gt;&lt;/a&gt;Transformer Architecture：整体架构
&lt;/h2&gt;&lt;p&gt;一个文本生成 Transformer 可以拆成三大部分：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Embedding&lt;/strong&gt;：把人类文本变成模型能处理的向量。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Transformer Blocks&lt;/strong&gt;：反复加工每个 token 的表示，核心包括 Self-Attention 和 MLP。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Output Probabilities&lt;/strong&gt;：把最终向量变成词表中每个 token 的概率。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;从宏观上看，信息流是：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;文本输入 -&amp;gt; token -&amp;gt; embedding -&amp;gt; 多层 Transformer Block -&amp;gt; logits -&amp;gt; 概率 -&amp;gt; 采样下一个 token
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;img alt="Step 3: Transformer Architecture" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://xedczq.cn/img/transformer-explainer/steps/step-03.jpg"&gt;&lt;/p&gt;
&lt;p&gt;图源：Transformer Explainer&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="embedding把文本变成向量"&gt;&lt;a href="#embedding%e6%8a%8a%e6%96%87%e6%9c%ac%e5%8f%98%e6%88%90%e5%90%91%e9%87%8f" class="header-anchor"&gt;&lt;/a&gt;Embedding：把文本变成向量
&lt;/h2&gt;&lt;p&gt;模型不能直接理解字符串。Embedding 的作用是把每个 token 转成一串数字，也就是向量。这个向量不是随便编码的，而是在训练过程中学出来的。&lt;/p&gt;
&lt;p&gt;如果两个 token 经常出现在相似语境中，它们的 embedding 往往会在高维空间中更接近。可以把 embedding 理解成模型内部的“词义坐标”。&lt;/p&gt;
&lt;p&gt;GPT-2 small 的隐藏维度是 768，所以每个 token 会被表示成一个 768 维向量。&lt;/p&gt;
&lt;p&gt;&lt;img alt="Step 4: Embedding" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://xedczq.cn/img/transformer-explainer/steps/step-04.jpg"&gt;&lt;/p&gt;
&lt;p&gt;图源：Transformer Explainer&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="token-embedding分词与-token-查表"&gt;&lt;a href="#token-embedding%e5%88%86%e8%af%8d%e4%b8%8e-token-%e6%9f%a5%e8%a1%a8" class="header-anchor"&gt;&lt;/a&gt;Token Embedding：分词与 token 查表
&lt;/h2&gt;&lt;p&gt;Tokenization 会把输入文本切成 token。token 可以是完整单词，也可以是子词。例如示例里的 &lt;code&gt;empowers&lt;/code&gt; 被切成了 &lt;code&gt;em&lt;/code&gt; 和 &lt;code&gt;powers&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;每个 token 都有一个唯一 ID。GPT-2 的词表大小是 50,257，所以 token embedding 矩阵大致是：&lt;/p&gt;
$$
50257 \times 768
$$&lt;p&gt;模型拿到 token ID 后，会去这个大矩阵里查出对应的 768 维向量。&lt;/p&gt;
&lt;p&gt;&lt;img alt="Step 5: Token Embedding" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://xedczq.cn/img/transformer-explainer/steps/step-05.jpg"&gt;&lt;/p&gt;
&lt;p&gt;图源：Transformer Explainer&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="positional-encoding注入位置信息"&gt;&lt;a href="#positional-encoding%e6%b3%a8%e5%85%a5%e4%bd%8d%e7%bd%ae%e4%bf%a1%e6%81%af" class="header-anchor"&gt;&lt;/a&gt;Positional Encoding：注入位置信息
&lt;/h2&gt;&lt;p&gt;Self-Attention 本身不天然知道顺序。如果只给模型一组 token 向量，它并不知道哪个 token 在前、哪个在后。&lt;/p&gt;
&lt;p&gt;所以需要位置编码。GPT-2 使用可学习的位置 embedding，把 token 的语义向量和位置向量相加：&lt;/p&gt;
$$
x_i = \text{TokenEmbedding}_i + \text{PositionEmbedding}_i
$$&lt;p&gt;这样模型既知道“这个 token 是什么”，也知道“它在第几个位置”。&lt;/p&gt;
&lt;p&gt;&lt;img alt="Step 6: Positional Encoding" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://xedczq.cn/img/transformer-explainer/steps/step-06.jpg"&gt;&lt;/p&gt;
&lt;p&gt;图源：Transformer Explainer&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="repetitive-transformer-blocks重复堆叠的-transformer-block"&gt;&lt;a href="#repetitive-transformer-blocks%e9%87%8d%e5%a4%8d%e5%a0%86%e5%8f%a0%e7%9a%84-transformer-block" class="header-anchor"&gt;&lt;/a&gt;Repetitive Transformer Blocks：重复堆叠的 Transformer Block
&lt;/h2&gt;&lt;p&gt;Embedding 只是输入表示，还不是充分理解后的语义表示。真正的上下文建模发生在 Transformer Block 中。&lt;/p&gt;
&lt;p&gt;GPT-2 small 有 12 个 Transformer Block。每个 block 大致包含：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Multi-Head Self-Attention：让 token 之间交换信息。&lt;/li&gt;
&lt;li&gt;MLP：对每个 token 的表示做非线性加工。&lt;/li&gt;
&lt;li&gt;Residual、LayerNorm、Dropout：让训练更稳定，泛化更好。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;多层堆叠的意义是：底层更偏局部和词法信息，高层更容易形成复杂语义和任务相关表示。&lt;/p&gt;
&lt;p&gt;&lt;img alt="Step 7: Repetitive Transformer Blocks" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://xedczq.cn/img/transformer-explainer/steps/step-07.jpg"&gt;&lt;/p&gt;
&lt;p&gt;图源：Transformer Explainer&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="multi-head-self-attention多头自注意力"&gt;&lt;a href="#multi-head-self-attention%e5%a4%9a%e5%a4%b4%e8%87%aa%e6%b3%a8%e6%84%8f%e5%8a%9b" class="header-anchor"&gt;&lt;/a&gt;Multi-Head Self Attention：多头自注意力
&lt;/h2&gt;&lt;p&gt;Self-Attention 的目标是让每个 token 根据上下文更新自己。比如 &lt;code&gt;to&lt;/code&gt; 这个 token 在不同句子里含义不同，它需要“看”前面的 &lt;code&gt;Data visualization empowers users&lt;/code&gt;，才能形成更准确的表示。&lt;/p&gt;
&lt;p&gt;Multi-Head 的含义是：模型不是只用一种注意力视角，而是并行使用多个 head。GPT-2 small 有 12 个 attention heads。不同 head 可以学习不同关系，例如语法关系、短距离搭配、长距离语义依赖等。&lt;/p&gt;
&lt;p&gt;&lt;img alt="Step 8: Multi-Head Self Attention" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://xedczq.cn/img/transformer-explainer/steps/step-08.jpg"&gt;&lt;/p&gt;
&lt;p&gt;图源：Transformer Explainer&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="query-key-valueqkv-是什么"&gt;&lt;a href="#query-key-valueqkv-%e6%98%af%e4%bb%80%e4%b9%88" class="header-anchor"&gt;&lt;/a&gt;Query, Key, Value：Q、K、V 是什么
&lt;/h2&gt;&lt;p&gt;Self-Attention 会把每个 token 的输入向量分别映射成三个向量：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Query (Q)&lt;/strong&gt;：当前 token 想查什么信息。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Key (K)&lt;/strong&gt;：每个 token 能被别人匹配到的特征。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Value (V)&lt;/strong&gt;：真正要被聚合传递的信息内容。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;它们来自线性变换：&lt;/p&gt;
$$
Q = XW_Q,\quad K = XW_K,\quad V = XW_V
$$&lt;p&gt;一个通俗类比是搜索引擎：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Query 是搜索词。&lt;/li&gt;
&lt;li&gt;Key 是网页标题或索引。&lt;/li&gt;
&lt;li&gt;Value 是网页正文内容。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;先用 Query 和 Key 算相关性，再根据相关性加权读取 Value。&lt;/p&gt;
&lt;p&gt;&lt;img alt="Step 9: Query, Key, Value" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://xedczq.cn/img/transformer-explainer/steps/step-09.jpg"&gt;&lt;/p&gt;
&lt;p&gt;图源：Transformer Explainer&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="multi-head把-qkv-切成多个头"&gt;&lt;a href="#multi-head%e6%8a%8a-qkv-%e5%88%87%e6%88%90%e5%a4%9a%e4%b8%aa%e5%a4%b4" class="header-anchor"&gt;&lt;/a&gt;Multi-head：把 Q/K/V 切成多个头
&lt;/h2&gt;&lt;p&gt;GPT-2 small 的 embedding 维度是 768，attention head 数是 12，所以每个 head 处理的维度通常是：&lt;/p&gt;
$$
768 / 12 = 64
$$&lt;p&gt;多头机制的好处是并行学习多种关系。一个 head 可能关注相邻词，另一个 head 可能关注主谓关系，还有 head 可能关注更远处的语义提示。&lt;/p&gt;
&lt;p&gt;多个 head 不是重复劳动，而是让模型拥有多个“观察角度”。&lt;/p&gt;
&lt;p&gt;&lt;img alt="Step 10: Multi-head" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://xedczq.cn/img/transformer-explainer/steps/step-10.jpg"&gt;&lt;/p&gt;
&lt;p&gt;图源：Transformer Explainer&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="masked-self-attention带掩码的自注意力"&gt;&lt;a href="#masked-self-attention%e5%b8%a6%e6%8e%a9%e7%a0%81%e7%9a%84%e8%87%aa%e6%b3%a8%e6%84%8f%e5%8a%9b" class="header-anchor"&gt;&lt;/a&gt;Masked Self Attention：带掩码的自注意力
&lt;/h2&gt;&lt;p&gt;GPT 这类模型是从左到右生成文本的。预测当前位置时，不能偷看未来 token，所以要使用 causal mask，也叫 masked self-attention。&lt;/p&gt;
&lt;p&gt;核心计算公式是：&lt;/p&gt;
$$
\text{Attention}(Q,K,V)=\text{softmax}\left(\frac{QK^T}{\sqrt{d_k}} + M\right)V
$$&lt;p&gt;其中：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;$QK^T$：计算 token 两两之间的相似度。&lt;/li&gt;
&lt;li&gt;$\sqrt{d_k}$：缩放因子，避免点积值过大导致 softmax 过尖。&lt;/li&gt;
&lt;li&gt;$M$：mask 矩阵，把未来位置设为 $-\infty$。&lt;/li&gt;
&lt;li&gt;softmax：把分数变成概率。&lt;/li&gt;
&lt;li&gt;乘以 $V$：按注意力权重汇总信息。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img alt="Step 11: Masked Self Attention" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://xedczq.cn/img/transformer-explainer/steps/step-11.jpg"&gt;&lt;/p&gt;
&lt;p&gt;图源：Transformer Explainer&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="attention-output--concatenation注意力输出与拼接"&gt;&lt;a href="#attention-output--concatenation%e6%b3%a8%e6%84%8f%e5%8a%9b%e8%be%93%e5%87%ba%e4%b8%8e%e6%8b%bc%e6%8e%a5" class="header-anchor"&gt;&lt;/a&gt;Attention Output &amp;amp; Concatenation：注意力输出与拼接
&lt;/h2&gt;&lt;p&gt;每个 head 都会输出一份上下文增强后的 token 表示。因为 GPT-2 small 有 12 个 head，所以会得到 12 份结果。&lt;/p&gt;
&lt;p&gt;接下来模型会把这些 head 的输出拼接起来，再经过一次线性投影，回到原来的隐藏维度 768：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;head_1, head_2, ..., head_12 -&amp;gt; concat -&amp;gt; linear projection
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;这一步的意义是：先让多个 head 分别提取信息，再把多种视角融合成一个统一表示。&lt;/p&gt;
&lt;p&gt;&lt;img alt="Step 12: Attention Output &amp; Concatenation" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://xedczq.cn/img/transformer-explainer/steps/step-12.jpg"&gt;&lt;/p&gt;
&lt;p&gt;图源：Transformer Explainer&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="mlp逐-token-的非线性加工"&gt;&lt;a href="#mlp%e9%80%90-token-%e7%9a%84%e9%9d%9e%e7%ba%bf%e6%80%a7%e5%8a%a0%e5%b7%a5" class="header-anchor"&gt;&lt;/a&gt;MLP：逐 token 的非线性加工
&lt;/h2&gt;&lt;p&gt;Attention 负责 token 之间的信息流动，MLP 负责对每个 token 自己的表示进行加工。&lt;/p&gt;
&lt;p&gt;GPT-2 的 MLP 通常包含两层线性变换，中间接 GELU 激活：&lt;/p&gt;
$$
\text{MLP}(x)=W_2\cdot \text{GELU}(W_1x+b_1)+b_2
$$&lt;p&gt;第一层会把维度从 768 扩展到 3072，第二层再压回 768。扩展维度可以让模型在更高维空间中表达更复杂的特征。&lt;/p&gt;
&lt;p&gt;注意：MLP 不像 Attention 那样跨 token 交流信息，它是对每个 token 独立处理。&lt;/p&gt;
&lt;p&gt;&lt;img alt="Step 13: MLP" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://xedczq.cn/img/transformer-explainer/steps/step-13.jpg"&gt;&lt;/p&gt;
&lt;p&gt;图源：Transformer Explainer&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="output-logit输出-logits"&gt;&lt;a href="#output-logit%e8%be%93%e5%87%ba-logits" class="header-anchor"&gt;&lt;/a&gt;Output Logit：输出 logits
&lt;/h2&gt;&lt;p&gt;经过所有 Transformer Blocks 后，模型会拿最后一个位置的输出向量去预测下一个 token。&lt;/p&gt;
&lt;p&gt;这个向量会经过最终线性层，映射到词表大小：&lt;/p&gt;
$$
\text{logits}=h_{\text{last}}W_{\text{vocab}}+b
$$&lt;p&gt;GPT-2 的词表大小是 50,257，所以 logits 是一个长度为 50,257 的向量。每个数对应一个候选 token 的原始分数。&lt;/p&gt;
&lt;p&gt;logit 不是概率。它可以是任意实数，还需要经过 softmax 才能变成概率分布。&lt;/p&gt;
&lt;p&gt;&lt;img alt="Step 14: Output Logit" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://xedczq.cn/img/transformer-explainer/steps/step-14.jpg"&gt;&lt;/p&gt;
&lt;p&gt;图源：Transformer Explainer&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="probabilities从-logits-到概率"&gt;&lt;a href="#probabilities%e4%bb%8e-logits-%e5%88%b0%e6%a6%82%e7%8e%87" class="header-anchor"&gt;&lt;/a&gt;Probabilities：从 logits 到概率
&lt;/h2&gt;&lt;p&gt;Softmax 会把 logits 转换成概率：&lt;/p&gt;
$$
p_i=\frac{e^{z_i}}{\sum_j e^{z_j}}
$$&lt;p&gt;转换后有两个特点：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;每个 token 的概率都在 0 到 1 之间。&lt;/li&gt;
&lt;li&gt;所有 token 的概率加起来等于 1。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;图中可以看到，示例输入后，模型认为 &lt;code&gt;visualize&lt;/code&gt;、&lt;code&gt;create&lt;/code&gt;、&lt;code&gt;see&lt;/code&gt;、&lt;code&gt;make&lt;/code&gt; 等 token 是比较可能的下一个 token。&lt;/p&gt;
&lt;p&gt;&lt;img alt="Step 15: Probabilities" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://xedczq.cn/img/transformer-explainer/steps/step-15.jpg"&gt;&lt;/p&gt;
&lt;p&gt;图源：Transformer Explainer&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="temperature温度控制生成随机性"&gt;&lt;a href="#temperature%e6%b8%a9%e5%ba%a6%e6%8e%a7%e5%88%b6%e7%94%9f%e6%88%90%e9%9a%8f%e6%9c%ba%e6%80%a7" class="header-anchor"&gt;&lt;/a&gt;Temperature：温度控制生成随机性
&lt;/h2&gt;&lt;p&gt;Temperature 会在 softmax 前缩放 logits：&lt;/p&gt;
$$
p_i=\frac{\exp(z_i/T)}{\sum_j \exp(z_j/T)}
$$&lt;p&gt;其中 $T$ 是 temperature：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;$T &amp;lt; 1$：概率分布更尖锐，高分 token 更容易被选中，输出更稳定。&lt;/li&gt;
&lt;li&gt;$T = 1$：不额外调整 logits。&lt;/li&gt;
&lt;li&gt;$T &amp;gt; 1$：概率分布更平坦，低概率 token 也有更多机会被选中，输出更多样。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;通俗地说，temperature 越低越保守，越高越发散。&lt;/p&gt;
&lt;p&gt;&lt;img alt="Step 16: Temperature" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://xedczq.cn/img/transformer-explainer/steps/step-16.jpg"&gt;&lt;/p&gt;
&lt;p&gt;图源：Transformer Explainer&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="sampling-strategy采样策略"&gt;&lt;a href="#sampling-strategy%e9%87%87%e6%a0%b7%e7%ad%96%e7%95%a5" class="header-anchor"&gt;&lt;/a&gt;Sampling Strategy：采样策略
&lt;/h2&gt;&lt;p&gt;得到概率分布后，模型还要决定如何选下一个 token。常见策略有：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Greedy Search&lt;/strong&gt;：永远选概率最高的 token，稳定但容易死板。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Top-k&lt;/strong&gt;：只保留概率最高的 k 个 token，再从中采样。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Top-p&lt;/strong&gt;：保留累计概率达到 p 的最小 token 集合，也叫 nucleus sampling。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Top-k 更像固定候选池，Top-p 更像动态候选池。实际使用时，temperature 和 top-k/top-p 经常一起调。&lt;/p&gt;
&lt;p&gt;&lt;img alt="Step 17: Sampling Strategy" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://xedczq.cn/img/transformer-explainer/steps/step-17.jpg"&gt;&lt;/p&gt;
&lt;p&gt;图源：Transformer Explainer&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="residual-connection残差连接"&gt;&lt;a href="#residual-connection%e6%ae%8b%e5%b7%ae%e8%bf%9e%e6%8e%a5" class="header-anchor"&gt;&lt;/a&gt;Residual Connection：残差连接
&lt;/h2&gt;&lt;p&gt;残差连接会把某一层的输入直接加到输出上：&lt;/p&gt;
$$
y = x + F(x)
$$&lt;p&gt;它的作用是保留原始信息，并让梯度更容易穿过深层网络。如果没有残差连接，模型层数很深时，训练会更困难，早期层的信息也更容易丢失。&lt;/p&gt;
&lt;p&gt;在 Transformer 中，Attention 和 MLP 周围通常都有残差连接。&lt;/p&gt;
&lt;p&gt;&lt;img alt="Step 18: Residual Connection" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://xedczq.cn/img/transformer-explainer/steps/step-18.jpg"&gt;&lt;/p&gt;
&lt;p&gt;图源：Transformer Explainer&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="layer-normalization层归一化"&gt;&lt;a href="#layer-normalization%e5%b1%82%e5%bd%92%e4%b8%80%e5%8c%96" class="header-anchor"&gt;&lt;/a&gt;Layer Normalization：层归一化
&lt;/h2&gt;&lt;p&gt;Layer Normalization 会对一个 token 向量内部的数值做归一化，使均值和方差更稳定：&lt;/p&gt;
$$
\text{LayerNorm}(x)=\gamma\frac{x-\mu}{\sqrt{\sigma^2+\epsilon}}+\beta
$$&lt;p&gt;它能减少训练不稳定，让每一层输入分布更可控。GPT-2 使用的是 pre-norm 风格：在进入 Attention 和 MLP 前先做 LayerNorm。&lt;/p&gt;
&lt;p&gt;通俗理解：LayerNorm 像是在每次进入关键计算前，先把数值尺度整理到比较合适的范围。&lt;/p&gt;
&lt;p&gt;&lt;img alt="Step 19: Layer Normalization" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://xedczq.cn/img/transformer-explainer/steps/step-19.jpg"&gt;&lt;/p&gt;
&lt;p&gt;图源：Transformer Explainer&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="dropout训练时的随机失活"&gt;&lt;a href="#dropout%e8%ae%ad%e7%bb%83%e6%97%b6%e7%9a%84%e9%9a%8f%e6%9c%ba%e5%a4%b1%e6%b4%bb" class="header-anchor"&gt;&lt;/a&gt;Dropout：训练时的随机失活
&lt;/h2&gt;&lt;p&gt;Dropout 是训练阶段的正则化方法，会随机把一部分连接或激活置零，避免模型过度依赖某些局部特征。&lt;/p&gt;
&lt;p&gt;它的直觉是：训练时不要让模型每次都走完全相同的路径，迫使它学到更稳健的表示。&lt;/p&gt;
&lt;p&gt;需要注意：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Dropout 主要用于训练。&lt;/li&gt;
&lt;li&gt;推理时 Dropout 会关闭。&lt;/li&gt;
&lt;li&gt;很多新一代大模型因为训练数据极大，Dropout 使用得比早期模型更少。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img alt="Step 20: Dropout" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://xedczq.cn/img/transformer-explainer/steps/step-20.jpg"&gt;&lt;/p&gt;
&lt;p&gt;图源：Transformer Explainer&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="一张流程图总结"&gt;&lt;a href="#%e4%b8%80%e5%bc%a0%e6%b5%81%e7%a8%8b%e5%9b%be%e6%80%bb%e7%bb%93" class="header-anchor"&gt;&lt;/a&gt;一张流程图总结
&lt;/h2&gt;&lt;p&gt;可以把 GPT 类 Transformer 的推理流程压缩成下面这条链路：&lt;/p&gt;
&lt;pre class="mermaid" style="visibility:hidden"&gt;flowchart LR
 A["输入文本"] --&gt; B["Tokenization"]
 B --&gt; C["Token Embedding"]
 C --&gt; D["Positional Encoding"]
 D --&gt; E["Transformer Block x N"]
 E --&gt; F["Multi-Head Self-Attention"]
 F --&gt; G["MLP"]
 G --&gt; H["Final Linear"]
 H --&gt; I["Logits"]
 I --&gt; J["Softmax Probabilities"]
 J --&gt; K["Temperature / Top-k / Top-p"]
 K --&gt; L["采样下一个 token"]&lt;/pre&gt;&lt;hr&gt;
&lt;h2 id="和-rnn-的关键区别"&gt;&lt;a href="#%e5%92%8c-rnn-%e7%9a%84%e5%85%b3%e9%94%ae%e5%8c%ba%e5%88%ab" class="header-anchor"&gt;&lt;/a&gt;和 RNN 的关键区别
&lt;/h2&gt;&lt;p&gt;结合之前的 RNN 学习，可以这样理解二者差异：&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;对比点&lt;/th&gt;
 &lt;th&gt;RNN&lt;/th&gt;
 &lt;th&gt;Transformer&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;Self-Attention 让 token 直接互相读取&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;并行能力&lt;/td&gt;
 &lt;td&gt;时间步依赖强，难并行&lt;/td&gt;
 &lt;td&gt;同层 token 可并行计算&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;显式保留整段 token 表示&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;大模型训练&lt;/td&gt;
 &lt;td&gt;扩展效率较差&lt;/td&gt;
 &lt;td&gt;更适合 GPU/TPU 大规模矩阵计算&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;这也是为什么现代 LLM 主流选择 Transformer：它不仅建模能力强，而且工程上更适合大规模预训练。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="学习来源"&gt;&lt;a href="#%e5%ad%a6%e4%b9%a0%e6%9d%a5%e6%ba%90" class="header-anchor"&gt;&lt;/a&gt;学习来源
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://poloclub.github.io/transformer-explainer/" target="_blank" rel="noopener"
 &gt;Transformer Explainer: LLM Transformer Model Visually Explained&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://github.com/poloclub/transformer-explainer" target="_blank" rel="noopener"
 &gt;Transformer Explainer GitHub Repository&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://arxiv.org/abs/2408.04619" target="_blank" rel="noopener"
 &gt;Transformer Explainer Paper, arXiv:2408.04619&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Vaswani et al., &lt;a class="link" href="https://arxiv.org/abs/1706.03762" target="_blank" rel="noopener"
 &gt;Attention Is All You Need&lt;/a&gt;, 2017&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>RNN 循环神经网络学习笔记</title><link>https://xedczq.cn/post/rnn/</link><pubDate>Thu, 04 Jun 2026 00:00:00 +0800</pubDate><guid>https://xedczq.cn/post/rnn/</guid><description>&lt;img src="https://xedczq.cn/img/rnn/diags.jpeg" alt="Featured image of post RNN 循环神经网络学习笔记" /&gt;&lt;h1 id="rnn-循环神经网络学习笔记"&gt;&lt;a href="#rnn-%e5%be%aa%e7%8e%af%e7%a5%9e%e7%bb%8f%e7%bd%91%e7%bb%9c%e5%ad%a6%e4%b9%a0%e7%ac%94%e8%ae%b0" class="header-anchor"&gt;&lt;/a&gt;RNN 循环神经网络学习笔记
&lt;/h1&gt;&lt;p&gt;本文是学习 Andrej Karpathy 经典博客 &lt;a class="link" href="https://karpathy.github.io/2015/05/21/rnn-effectiveness/" target="_blank" rel="noopener"
 &gt;The Unreasonable Effectiveness of Recurrent Neural Networks&lt;/a&gt; 时整理的中文笔记。原文发表于 2015 年，重点用字符级语言模型展示 RNN/LSTM 为什么能从原始文本中学到拼写、格式、结构、局部语法，甚至一些可解释的“状态记忆”。&lt;/p&gt;
&lt;p&gt;这篇笔记的主线是：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;RNN 为什么适合处理序列&lt;/li&gt;
&lt;li&gt;RNN 的核心公式和计算过程&lt;/li&gt;
&lt;li&gt;字符级语言模型如何训练与采样&lt;/li&gt;
&lt;li&gt;Karpathy 原文中的经典实验说明了什么&lt;/li&gt;
&lt;li&gt;为什么后来 RNN 在主流 NLP 中被 Transformer 取代&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="rnn-解决什么问题"&gt;&lt;a href="#rnn-%e8%a7%a3%e5%86%b3%e4%bb%80%e4%b9%88%e9%97%ae%e9%a2%98" class="header-anchor"&gt;&lt;/a&gt;RNN 解决什么问题
&lt;/h2&gt;&lt;p&gt;普通前馈神经网络通常假设输入和输出是固定长度的向量，例如输入一张图片，输出一个分类结果。但很多真实任务天然是序列：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;文本：一句话由多个 token 或字符组成&lt;/li&gt;
&lt;li&gt;语音：声音帧按时间排列&lt;/li&gt;
&lt;li&gt;视频：画面帧按时间排列&lt;/li&gt;
&lt;li&gt;翻译：输入一句话，输出另一种语言的一句话&lt;/li&gt;
&lt;li&gt;生成任务：前面生成的内容会影响后面生成什么&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;RNN 的关键思想是：&lt;strong&gt;模型在处理当前输入时，不只看当前输入，还维护一个隐藏状态，用来压缩过去的上下文。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="RNN 处理不同序列任务的模式" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://xedczq.cn/img/rnn/diags.jpeg"&gt;&lt;/p&gt;
&lt;p&gt;图源：Andrej Karpathy, &lt;a class="link" href="https://karpathy.github.io/2015/05/21/rnn-effectiveness/" target="_blank" rel="noopener"
 &gt;The Unreasonable Effectiveness of Recurrent Neural Networks&lt;/a&gt;。红色表示输入，蓝色表示输出，绿色表示循环状态。它展示了固定输入输出、序列输出、序列输入、序列到序列、同步序列输入输出等常见模式。&lt;/p&gt;
&lt;p&gt;可以把 RNN 理解成一个不断被调用的 &lt;code&gt;step&lt;/code&gt; 函数：&lt;/p&gt;
&lt;div class="highlight"&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;rnn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;RNN&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;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rnn&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;step&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;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;每调用一次 &lt;code&gt;step(x)&lt;/code&gt;，RNN 会读取当前输入 &lt;code&gt;x_t&lt;/code&gt;，结合之前的隐藏状态 &lt;code&gt;h_{t-1}&lt;/code&gt;，更新出新的隐藏状态 &lt;code&gt;h_t&lt;/code&gt;，并产生当前输出 &lt;code&gt;y_t&lt;/code&gt;。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="vanilla-rnn-的核心公式"&gt;&lt;a href="#vanilla-rnn-%e7%9a%84%e6%a0%b8%e5%bf%83%e5%85%ac%e5%bc%8f" class="header-anchor"&gt;&lt;/a&gt;Vanilla RNN 的核心公式
&lt;/h2&gt;&lt;p&gt;最基础的 RNN 更新公式是：&lt;/p&gt;
$$
h_t = \tanh(W_{hh}h_{t-1} + W_{xh}x_t + b_h)
$$$$
y_t = W_{hy}h_t + b_y
$$&lt;p&gt;其中：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;$x_t$：第 $t$ 个时间步的输入&lt;/li&gt;
&lt;li&gt;$h_{t-1}$：上一个时间步的隐藏状态&lt;/li&gt;
&lt;li&gt;$h_t$：当前时间步的隐藏状态&lt;/li&gt;
&lt;li&gt;$W_{xh}$：输入到隐藏状态的权重&lt;/li&gt;
&lt;li&gt;$W_{hh}$：隐藏状态到隐藏状态的循环权重&lt;/li&gt;
&lt;li&gt;$W_{hy}$：隐藏状态到输出的权重&lt;/li&gt;
&lt;li&gt;$\tanh$：非线性激活函数，把值压到 $[-1, 1]$&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;对应到代码，大致是：&lt;/p&gt;
&lt;div class="highlight"&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;RNN&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="nf"&gt;step&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;h&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tanh&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dot&lt;/span&gt;&lt;span class="p"&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;W_hh&lt;/span&gt;&lt;span class="p"&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;h&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dot&lt;/span&gt;&lt;span class="p"&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;W_xh&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;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dot&lt;/span&gt;&lt;span class="p"&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;W_hy&lt;/span&gt;&lt;span class="p"&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;h&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 class="n"&gt;y&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;学习时最重要的是理解 &lt;code&gt;h&lt;/code&gt; 的意义：它不是手写规则，而是模型在训练中自己学出来的“上下文摘要”。如果输入是文本，&lt;code&gt;h&lt;/code&gt; 可能会携带当前是否在引号内、是否在 URL 内、某个括号是否已打开、前面出现了哪些词等信息。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="为什么-rnn-可以建模上下文"&gt;&lt;a href="#%e4%b8%ba%e4%bb%80%e4%b9%88-rnn-%e5%8f%af%e4%bb%a5%e5%bb%ba%e6%a8%a1%e4%b8%8a%e4%b8%8b%e6%96%87" class="header-anchor"&gt;&lt;/a&gt;为什么 RNN 可以建模上下文
&lt;/h2&gt;&lt;p&gt;以字符串 &lt;code&gt;hello&lt;/code&gt; 为例。假设词表只有 &lt;code&gt;h, e, l, o&lt;/code&gt; 四个字符，训练时输入可以是：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;输入：h e l l
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;目标：e l l o
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;注意第一个 &lt;code&gt;l&lt;/code&gt; 后面的目标是 &lt;code&gt;l&lt;/code&gt;，第二个 &lt;code&gt;l&lt;/code&gt; 后面的目标是 &lt;code&gt;o&lt;/code&gt;。如果模型只看当前字符，两个时间步输入都一样，无法判断下一个字符应该是什么。RNN 必须利用隐藏状态记录“前面已经看到了什么”。&lt;/p&gt;
&lt;p&gt;&lt;img alt="字符级 RNN 预测下一个字符" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://xedczq.cn/img/rnn/charseq.jpeg"&gt;&lt;/p&gt;
&lt;p&gt;图源：Andrej Karpathy, &lt;a class="link" href="https://karpathy.github.io/2015/05/21/rnn-effectiveness/" target="_blank" rel="noopener"
 &gt;The Unreasonable Effectiveness of Recurrent Neural Networks&lt;/a&gt;。图中模型逐字符读入 &lt;code&gt;hell&lt;/code&gt;，每一步输出对下一个字符的打分，绿色目标表示希望模型提高的正确字符分数。&lt;/p&gt;
&lt;p&gt;训练目标通常是每个时间步的交叉熵损失：&lt;/p&gt;
$$
\mathcal{L} = -\sum_t \log p(x_{t+1}\mid x_{\le t})
$$&lt;p&gt;其中：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;$x_{\le t}$ 表示当前位置之前和当前位置的上下文&lt;/li&gt;
&lt;li&gt;$p(x_{t+1}\mid x_{\le t})$ 表示模型基于历史上下文预测下一个字符的概率&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;训练完成后，生成文本的流程是：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;给模型一个起始字符或起始文本&lt;/li&gt;
&lt;li&gt;得到下一个字符的概率分布&lt;/li&gt;
&lt;li&gt;从分布里采样一个字符&lt;/li&gt;
&lt;li&gt;把采样出的字符再喂回模型&lt;/li&gt;
&lt;li&gt;重复以上过程&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;这就是字符级语言模型最朴素的生成方式。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="bpttrnn-如何训练"&gt;&lt;a href="#bpttrnn-%e5%a6%82%e4%bd%95%e8%ae%ad%e7%bb%83" class="header-anchor"&gt;&lt;/a&gt;BPTT：RNN 如何训练
&lt;/h2&gt;&lt;p&gt;RNN 每个时间步复用同一组参数。训练时会把循环结构按时间展开，然后做反向传播，这叫 &lt;strong&gt;Backpropagation Through Time, BPTT&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;如果序列很长，完整展开会很贵，所以常用 &lt;strong&gt;Truncated BPTT&lt;/strong&gt;，只往回传播固定长度。例如 Karpathy 原文的 Paul Graham 实验中使用了长度为 100 个字符的截断 BPTT。&lt;/p&gt;
&lt;p&gt;RNN 训练的难点主要来自长链式梯度：&lt;/p&gt;
$$
\frac{\partial \mathcal{L}}{\partial h_{t-k}}
$$&lt;p&gt;需要经过很多次矩阵乘法和非线性函数。当链条很长时，梯度可能：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;越传越小：梯度消失，模型难以学习长期依赖&lt;/li&gt;
&lt;li&gt;越传越大：梯度爆炸，训练不稳定&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这也是后来 LSTM、GRU 被大量使用的原因。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="lstm更强的循环单元"&gt;&lt;a href="#lstm%e6%9b%b4%e5%bc%ba%e7%9a%84%e5%be%aa%e7%8e%af%e5%8d%95%e5%85%83" class="header-anchor"&gt;&lt;/a&gt;LSTM：更强的循环单元
&lt;/h2&gt;&lt;p&gt;Karpathy 原文里的实验实际使用的是 LSTM。LSTM 仍然属于 RNN 家族，但它把隐藏状态更新设计得更复杂，引入门控机制，让模型更容易保留或遗忘信息。&lt;/p&gt;
&lt;p&gt;LSTM 的典型组件包括：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;遗忘门：决定旧信息保留多少&lt;/li&gt;
&lt;li&gt;输入门：决定新信息写入多少&lt;/li&gt;
&lt;li&gt;输出门：决定当前状态暴露给输出多少&lt;/li&gt;
&lt;li&gt;细胞状态：提供更稳定的信息通道&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;直觉上，Vanilla RNN 每一步都把旧状态和新输入混在一起重新压缩，长期信息很容易被覆盖；LSTM 给模型提供了“写入、保留、读取”的机制，所以更适合长序列。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="karpathy-原文中的经典实验"&gt;&lt;a href="#karpathy-%e5%8e%9f%e6%96%87%e4%b8%ad%e7%9a%84%e7%bb%8f%e5%85%b8%e5%ae%9e%e9%aa%8c" class="header-anchor"&gt;&lt;/a&gt;Karpathy 原文中的经典实验
&lt;/h2&gt;&lt;h3 id="模型从字符中学会结构"&gt;&lt;a href="#%e6%a8%a1%e5%9e%8b%e4%bb%8e%e5%ad%97%e7%ac%a6%e4%b8%ad%e5%ad%a6%e4%bc%9a%e7%bb%93%e6%9e%84" class="header-anchor"&gt;&lt;/a&gt;模型从字符中学会结构
&lt;/h3&gt;&lt;p&gt;原文展示了把 RNN/LSTM 训练在不同文本上的效果，包括：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Paul Graham 文章&lt;/li&gt;
&lt;li&gt;Shakespeare 剧本&lt;/li&gt;
&lt;li&gt;Wikipedia Markdown/XML&lt;/li&gt;
&lt;li&gt;代数几何 LaTeX&lt;/li&gt;
&lt;li&gt;Linux 源码&lt;/li&gt;
&lt;li&gt;婴儿名字列表&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这些实验的共同点是：模型没有显式的词典、语法规则、Markdown 规则、XML 树规则或 C 语言规则，只是在做“预测下一个字符”。但训练后，它能生成看起来像原数据分布的内容。&lt;/p&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;词层：常见词、名字、变量名&lt;/li&gt;
&lt;li&gt;句法层：引号、括号、缩进、标签闭合&lt;/li&gt;
&lt;li&gt;风格层：莎士比亚式台词、维基百科式条目、源码注释&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="训练过程中的能力演化"&gt;&lt;a href="#%e8%ae%ad%e7%bb%83%e8%bf%87%e7%a8%8b%e4%b8%ad%e7%9a%84%e8%83%bd%e5%8a%9b%e6%bc%94%e5%8c%96" class="header-anchor"&gt;&lt;/a&gt;训练过程中的能力演化
&lt;/h3&gt;&lt;p&gt;Karpathy 用《战争与和平》做例子，展示了采样文本随训练迭代逐步变化：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;早期：几乎是随机字符，但开始出现空格&lt;/li&gt;
&lt;li&gt;中期：出现短词、句号、引号等局部结构&lt;/li&gt;
&lt;li&gt;后期：出现更像英文的单词、名字和句子形式&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这给我的理解是：RNN 不是一下子学会“语言”，而是先学最局部、最高频的模式，再逐渐形成更长范围的依赖。&lt;/p&gt;
&lt;h3 id="隐藏单元学出了可解释状态"&gt;&lt;a href="#%e9%9a%90%e8%97%8f%e5%8d%95%e5%85%83%e5%ad%a6%e5%87%ba%e4%ba%86%e5%8f%af%e8%a7%a3%e9%87%8a%e7%8a%b6%e6%80%81" class="header-anchor"&gt;&lt;/a&gt;隐藏单元学出了可解释状态
&lt;/h3&gt;&lt;p&gt;原文最经典的部分之一，是可视化 LSTM 隐藏单元的激活。某些神经元会在 URL 内激活，某些会在 &lt;code&gt;[[...]]&lt;/code&gt; 这类 Markdown 链接环境中激活，还有一些神经元像是在跟踪引号区域。&lt;/p&gt;
&lt;p&gt;&lt;img alt="LSTM 神经元对 URL 区域的激活" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://xedczq.cn/img/rnn/under1.jpeg"&gt;&lt;/p&gt;
&lt;p&gt;图源：Andrej Karpathy, &lt;a class="link" href="https://karpathy.github.io/2015/05/21/rnn-effectiveness/" target="_blank" rel="noopener"
 &gt;The Unreasonable Effectiveness of Recurrent Neural Networks&lt;/a&gt;。该图展示了一个隐藏单元在 URL 区域明显激活，说明模型可能学到了“当前是否处在 URL 中”的内部状态。&lt;/p&gt;
&lt;p&gt;&lt;img alt="LSTM 神经元对 Markdown 链接区域的激活" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://xedczq.cn/img/rnn/under2.jpeg"&gt;&lt;/p&gt;
&lt;p&gt;图源：Andrej Karpathy, &lt;a class="link" href="https://karpathy.github.io/2015/05/21/rnn-effectiveness/" target="_blank" rel="noopener"
 &gt;The Unreasonable Effectiveness of Recurrent Neural Networks&lt;/a&gt;。该图展示了隐藏单元对 &lt;code&gt;[[...]]&lt;/code&gt; Markdown 环境的响应。&lt;/p&gt;
&lt;p&gt;&lt;img alt="更压缩的神经元激活可视化" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://xedczq.cn/img/rnn/pane1.png"&gt;&lt;/p&gt;
&lt;p&gt;图源：Andrej Karpathy, &lt;a class="link" href="https://karpathy.github.io/2015/05/21/rnn-effectiveness/" target="_blank" rel="noopener"
 &gt;The Unreasonable Effectiveness of Recurrent Neural Networks&lt;/a&gt;。这类可视化说明，一部分隐藏单元会学出人类可以解释的状态检测功能。&lt;/p&gt;
&lt;p&gt;这里的重点不是说每个神经元都有明确语义，而是说明端到端训练可以让模型自己发现对任务有用的中间状态。对于“预测下一个字符”来说，知道自己是否在 URL、括号、引号中，确实会提高预测准确率。&lt;/p&gt;
&lt;h3 id="rnn-也能处理非传统序列任务"&gt;&lt;a href="#rnn-%e4%b9%9f%e8%83%bd%e5%a4%84%e7%90%86%e9%9d%9e%e4%bc%a0%e7%bb%9f%e5%ba%8f%e5%88%97%e4%bb%bb%e5%8a%a1" class="header-anchor"&gt;&lt;/a&gt;RNN 也能处理非传统序列任务
&lt;/h3&gt;&lt;p&gt;Karpathy 原文还提到，即使数据本身不是序列，也可以把处理过程设计成序列。例如模型可以一步步移动注意力读取图片，或者一步步在画布上生成图像。&lt;/p&gt;
&lt;p&gt;&lt;img alt="RNN 逐步读取门牌数字" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://xedczq.cn/img/rnn/house_read.gif"&gt;&lt;/p&gt;
&lt;p&gt;图源：Andrej Karpathy, &lt;a class="link" href="https://karpathy.github.io/2015/05/21/rnn-effectiveness/" target="_blank" rel="noopener"
 &gt;The Unreasonable Effectiveness of Recurrent Neural Networks&lt;/a&gt;。左图相关实验来自 Recurrent Models of Visual Attention。&lt;/p&gt;
&lt;p&gt;&lt;img alt="RNN 逐步生成门牌数字" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://xedczq.cn/img/rnn/house_generate.gif"&gt;&lt;/p&gt;
&lt;p&gt;图源：Andrej Karpathy, &lt;a class="link" href="https://karpathy.github.io/2015/05/21/rnn-effectiveness/" target="_blank" rel="noopener"
 &gt;The Unreasonable Effectiveness of Recurrent Neural Networks&lt;/a&gt;。右图相关实验来自 DRAW: A Recurrent Neural Network For Image Generation。&lt;/p&gt;
&lt;p&gt;这给了一个重要视角：RNN 不只是“处理序列数据”，也可以表示“顺序执行的计算过程”。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="采样温度生成结果为什么会变"&gt;&lt;a href="#%e9%87%87%e6%a0%b7%e6%b8%a9%e5%ba%a6%e7%94%9f%e6%88%90%e7%bb%93%e6%9e%9c%e4%b8%ba%e4%bb%80%e4%b9%88%e4%bc%9a%e5%8f%98" class="header-anchor"&gt;&lt;/a&gt;采样温度：生成结果为什么会变
&lt;/h2&gt;&lt;p&gt;字符级语言模型输出的是下一个字符的概率分布。采样时常用温度系数调整分布：&lt;/p&gt;
$$
p_i = \frac{\exp(z_i / T)}{\sum_j \exp(z_j / T)}
$$&lt;p&gt;其中：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;$z_i$ 是第 $i$ 个字符的 logit&lt;/li&gt;
&lt;li&gt;$T$ 是温度&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;温度的影响：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;$T &amp;lt; 1$：分布更尖锐，模型更保守，更容易重复高概率模式&lt;/li&gt;
&lt;li&gt;$T = 1$：正常采样&lt;/li&gt;
&lt;li&gt;$T &amp;gt; 1$：分布更平坦，输出更多样，但错误也更多&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;所以生成模型的“创造性”和“稳定性”经常是一个权衡。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="rnn-的优点"&gt;&lt;a href="#rnn-%e7%9a%84%e4%bc%98%e7%82%b9" class="header-anchor"&gt;&lt;/a&gt;RNN 的优点
&lt;/h2&gt;&lt;p&gt;RNN 的优势可以概括为：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;天然适合流式输入：数据一个时间步一个时间步到来时，RNN 可以持续更新状态&lt;/li&gt;
&lt;li&gt;参数共享：同一个 &lt;code&gt;step&lt;/code&gt; 函数复用在任意长度序列上&lt;/li&gt;
&lt;li&gt;状态压缩：隐藏状态可以作为过去上下文的摘要&lt;/li&gt;
&lt;li&gt;生成直觉简单：预测下一个 token，再把输出喂回模型&lt;/li&gt;
&lt;li&gt;对小模型和特定时序任务仍有价值：例如传感器序列、实时语音、边缘设备上的低延迟任务&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="rnn-的局限"&gt;&lt;a href="#rnn-%e7%9a%84%e5%b1%80%e9%99%90" class="header-anchor"&gt;&lt;/a&gt;RNN 的局限
&lt;/h2&gt;&lt;p&gt;RNN 的主要问题也很清楚：&lt;/p&gt;
&lt;h3 id="序列计算难并行"&gt;&lt;a href="#%e5%ba%8f%e5%88%97%e8%ae%a1%e7%ae%97%e9%9a%be%e5%b9%b6%e8%a1%8c" class="header-anchor"&gt;&lt;/a&gt;序列计算难并行
&lt;/h3&gt;&lt;p&gt;RNN 必须先算出 $h_{t-1}$，才能计算 $h_t$。这意味着一个序列内部的时间步很难完全并行。&lt;/p&gt;
&lt;p&gt;对于短序列影响不大，但在大规模语言模型训练中，数据量和模型规模都很大，无法充分利用 GPU/TPU 并行能力会成为核心瓶颈。&lt;/p&gt;
&lt;h3 id="长距离依赖困难"&gt;&lt;a href="#%e9%95%bf%e8%b7%9d%e7%a6%bb%e4%be%9d%e8%b5%96%e5%9b%b0%e9%9a%be" class="header-anchor"&gt;&lt;/a&gt;长距离依赖困难
&lt;/h3&gt;&lt;p&gt;理论上，隐藏状态可以携带所有历史信息；实践中，固定长度的向量很难无损压缩长上下文。越早的信息经过越多次状态更新，越容易被覆盖或衰减。&lt;/p&gt;
&lt;p&gt;LSTM/GRU 缓解了这个问题，但没有彻底解决。&lt;/p&gt;
&lt;h3 id="信息通路太长"&gt;&lt;a href="#%e4%bf%a1%e6%81%af%e9%80%9a%e8%b7%af%e5%a4%aa%e9%95%bf" class="header-anchor"&gt;&lt;/a&gt;信息通路太长
&lt;/h3&gt;&lt;p&gt;如果第 1 个 token 要影响第 1000 个 token，RNN 的信息需要穿过约 1000 次递归更新。路径越长，优化越难，信息越容易损失。&lt;/p&gt;
&lt;h3 id="94-隐藏状态是瓶颈"&gt;&lt;a href="#94-%e9%9a%90%e8%97%8f%e7%8a%b6%e6%80%81%e6%98%af%e7%93%b6%e9%a2%88" class="header-anchor"&gt;&lt;/a&gt;9.4 隐藏状态是瓶颈
&lt;/h3&gt;&lt;p&gt;RNN 把过去压缩进一个隐藏向量。这个向量既要存储上下文，又要参与下一步计算。Karpathy 原文在展望部分也提到，RNN 会把表示容量和每步计算量耦合在一起：隐藏状态越大，每一步矩阵乘法成本越高。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="为什么-rnn-被-transformer-取代"&gt;&lt;a href="#%e4%b8%ba%e4%bb%80%e4%b9%88-rnn-%e8%a2%ab-transformer-%e5%8f%96%e4%bb%a3" class="header-anchor"&gt;&lt;/a&gt;为什么 RNN 被 Transformer 取代
&lt;/h2&gt;&lt;p&gt;Transformer 不是因为“RNN 完全没用”才取代它，而是因为在大规模 NLP 任务上，Transformer 的工程特性和建模能力更适合扩展。&lt;/p&gt;
&lt;h3 id="transformer-更容易并行训练"&gt;&lt;a href="#transformer-%e6%9b%b4%e5%ae%b9%e6%98%93%e5%b9%b6%e8%a1%8c%e8%ae%ad%e7%bb%83" class="header-anchor"&gt;&lt;/a&gt;Transformer 更容易并行训练
&lt;/h3&gt;&lt;p&gt;RNN 按时间步递推：&lt;/p&gt;
$$
h_t = f(h_{t-1}, x_t)
$$&lt;p&gt;Transformer 的 self-attention 可以在同一层里同时计算序列中所有位置之间的关系。训练时，一个 batch 内的 token 表示可以大量矩阵化并行。&lt;/p&gt;
&lt;p&gt;这正是 &lt;a class="link" href="https://arxiv.org/abs/1706.03762" target="_blank" rel="noopener"
 &gt;Attention Is All You Need&lt;/a&gt; 的核心动机之一：去掉 recurrence 和 convolution，仅使用 attention 构建序列转导模型，从而提高并行化程度并减少训练时间。&lt;/p&gt;
&lt;h3 id="长距离依赖路径更短"&gt;&lt;a href="#%e9%95%bf%e8%b7%9d%e7%a6%bb%e4%be%9d%e8%b5%96%e8%b7%af%e5%be%84%e6%9b%b4%e7%9f%ad" class="header-anchor"&gt;&lt;/a&gt;长距离依赖路径更短
&lt;/h3&gt;&lt;p&gt;在 RNN 中，远距离 token 之间的信息需要经过很多时间步传递。Transformer 的 self-attention 让任意两个位置可以在一层内直接建立联系。&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;两个远距离 token 的信息路径&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;RNN&lt;/td&gt;
 &lt;td&gt;$O(n)$&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;CNN&lt;/td&gt;
 &lt;td&gt;取决于卷积层数和感受野&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Transformer self-attention&lt;/td&gt;
 &lt;td&gt;$O(1)$&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;路径更短通常意味着更容易学习长距离依赖。&lt;/p&gt;
&lt;h3 id="attention-显式访问上下文"&gt;&lt;a href="#attention-%e6%98%be%e5%bc%8f%e8%ae%bf%e9%97%ae%e4%b8%8a%e4%b8%8b%e6%96%87" class="header-anchor"&gt;&lt;/a&gt;Attention 显式访问上下文
&lt;/h3&gt;&lt;p&gt;RNN 依赖隐藏状态压缩历史，而 Transformer 的 attention 会为当前位置动态读取其它位置的信息：&lt;/p&gt;
$$
\text{Attention}(Q, K, V) = \text{softmax}\left(\frac{QK^T}{\sqrt{d_k}}\right)V
$$&lt;p&gt;这相当于让模型在生成每个表示时，直接根据相关性从上下文中取信息，而不是完全依赖一个递推压缩状态。&lt;/p&gt;
&lt;h3 id="transformer-更符合大模型扩展规律"&gt;&lt;a href="#transformer-%e6%9b%b4%e7%ac%a6%e5%90%88%e5%a4%a7%e6%a8%a1%e5%9e%8b%e6%89%a9%e5%b1%95%e8%a7%84%e5%be%8b" class="header-anchor"&gt;&lt;/a&gt;Transformer 更符合大模型扩展规律
&lt;/h3&gt;&lt;p&gt;现代大语言模型依赖大数据、大参数、大算力。Transformer 的矩阵乘法密集、并行友好，能更好地吃满硬件；RNN 的时间步依赖限制了吞吐。&lt;/p&gt;
&lt;p&gt;所以在大规模预训练语言模型时代，Transformer 的优势不仅是算法效果，也包括硬件效率、训练稳定性、生态工具和可扩展性。&lt;/p&gt;
&lt;h3 id="105-但-rnn-没有完全消失"&gt;&lt;a href="#105-%e4%bd%86-rnn-%e6%b2%a1%e6%9c%89%e5%ae%8c%e5%85%a8%e6%b6%88%e5%a4%b1" class="header-anchor"&gt;&lt;/a&gt;10.5 但 RNN 没有完全消失
&lt;/h3&gt;&lt;p&gt;RNN 在一些场景仍有价值：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;流式推理：输入持续到来，不想反复重算全部上下文&lt;/li&gt;
&lt;li&gt;低延迟边缘任务：小模型、固定状态、推理成本可控&lt;/li&gt;
&lt;li&gt;时间序列任务：某些传感器或控制任务不一定需要完整 self-attention&lt;/li&gt;
&lt;li&gt;新架构研究：一些状态空间模型、线性注意力、RWKV 类模型又重新吸收了 recurrence 的思想&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;所以更准确的说法是：&lt;strong&gt;Transformer 在主流 NLP 和大模型训练中取代了传统 RNN/LSTM，但“循环状态”这个思想仍然在很多新架构中继续存在。&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="学习小结"&gt;&lt;a href="#%e5%ad%a6%e4%b9%a0%e5%b0%8f%e7%bb%93" class="header-anchor"&gt;&lt;/a&gt;学习小结
&lt;/h2&gt;&lt;p&gt;RNN 的核心不是复杂公式，而是一个简单但强大的抽象：&lt;strong&gt;用同一个函数反复处理序列，并用隐藏状态携带过去的信息。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Karpathy 的文章经典之处在于，它没有先堆很多理论，而是用字符级生成实验说明：只要训练目标设计得足够通用，模型会为了完成预测任务，自发学到拼写、格式、括号、引号、URL、代码结构等多层模式。&lt;/p&gt;
&lt;p&gt;但 RNN 的递推结构也决定了它在大规模训练中有天然瓶颈：难并行、长依赖难优化、隐藏状态压缩能力有限。Transformer 通过 self-attention 让序列位置之间直接交互，并大幅提升并行训练能力，因此成为现代 NLP 和大语言模型的主流架构。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="学习来源"&gt;&lt;a href="#%e5%ad%a6%e4%b9%a0%e6%9d%a5%e6%ba%90" class="header-anchor"&gt;&lt;/a&gt;学习来源
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;Andrej Karpathy, &lt;a class="link" href="https://karpathy.github.io/2015/05/21/rnn-effectiveness/" target="_blank" rel="noopener"
 &gt;The Unreasonable Effectiveness of Recurrent Neural Networks&lt;/a&gt;, 2015-05-21&lt;/li&gt;
&lt;li&gt;Andrej Karpathy, &lt;a class="link" href="https://github.com/karpathy/char-rnn" target="_blank" rel="noopener"
 &gt;char-rnn GitHub repository&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Andrej Karpathy, &lt;a class="link" href="https://gist.github.com/karpathy/d4dee566867f8291f086" target="_blank" rel="noopener"
 &gt;Minimal character-level language model with a Vanilla Recurrent Neural Network, in Python/numpy&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Ashish Vaswani et al., &lt;a class="link" href="https://arxiv.org/abs/1706.03762" target="_blank" rel="noopener"
 &gt;Attention Is All You Need&lt;/a&gt;, 2017&lt;/li&gt;
&lt;/ul&gt;</description></item></channel></rss>