<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Encoder-Decoder on Jeanphilo Blog</title><link>https://jeanphilogong.github.io/jeanblog/zh/tags/encoder-decoder/</link><description>Recent content in Encoder-Decoder on Jeanphilo Blog</description><generator>Hugo -- 0.168.0</generator><language>zh-cn</language><lastBuildDate>Thu, 23 Apr 2026 15:51:40 +0800</lastBuildDate><atom:link href="https://jeanphilogong.github.io/jeanblog/zh/tags/encoder-decoder/index.xml" rel="self" type="application/rss+xml"/><item><title>Attention-Based Seq2Seq：为什么会自然过渡到 Transformer</title><link>https://jeanphilogong.github.io/jeanblog/zh/ai/llm/attention-based-seq2seq-to-transformer/</link><pubDate>Thu, 23 Apr 2026 15:51:40 +0800</pubDate><guid>https://jeanphilogong.github.io/jeanblog/zh/ai/llm/attention-based-seq2seq-to-transformer/</guid><description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;副标题 / 摘要&lt;/strong&gt;
这篇文章专门解释一个关键过渡：为什么 fixed-length 的 seq2seq 很快会不够用，attention-based seq2seq 是怎么补上“按需读取源序列”这个能力的，以及这个思路为什么几乎会自然长成 Transformer。最后会收束到一份最小可运行的 PyTorch GRU + additive attention 实现。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="从苹果为什么老翻不准开始"&gt;从“苹果”为什么老翻不准开始&lt;/h2&gt;
&lt;p&gt;还是用这个最小翻译任务：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;src: &amp;lt;bos&amp;gt; I really love green apples &amp;lt;eos&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;tgt: &amp;lt;bos&amp;gt; 我 真的 喜欢 青 苹果 &amp;lt;eos&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;当 decoder 走到要生成“苹果”这一步时，最理想的行为其实很明确：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;它应该重点回头看源序列里的 &lt;code&gt;apples&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;它可能顺手也看一眼 &lt;code&gt;green&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;它不能只依赖一个已经被反复压缩过很多轮的最终隐藏状态&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;如果你用上一篇里那个最小 seq2seq：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;encoder 把整句读完&lt;/li&gt;
&lt;li&gt;只把最后一个 &lt;code&gt;hidden_enc&lt;/code&gt; 交给 decoder&lt;/li&gt;
&lt;li&gt;decoder 后面每一步都只靠这个固定长度状态和自己的历史&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;那么句子一长，这里就会出现一个很具体的问题：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;decoder 明明需要“现在按需去看源序列的某几个位置”，但 fixed-length seq2seq 只给了它“一次性打包好的整句摘要”。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;这就是 attention-based seq2seq 出现的真实压力。
它不是为了“概念更高级”，而是因为 decoder 在每个时间步都需要&lt;strong&gt;重新决定自己该看源序列的哪里&lt;/strong&gt;。&lt;/p&gt;
&lt;h2 id="快速掌握地图"&gt;快速掌握地图&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;fixed-length seq2seq：&lt;code&gt;encoder outputs -&amp;gt; 丢弃大部分，只保留 final hidden&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;attention-based seq2seq：&lt;code&gt;decoder step t -&amp;gt; 对所有 encoder outputs 打分 -&amp;gt; 加权求和得到 context_t&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;核心收益：不同目标位置可以读取不同源位置&lt;/li&gt;
&lt;li&gt;仍然存在的限制：encoder 和 decoder 还是循环结构，时间上依然串行&lt;/li&gt;
&lt;li&gt;通向 Transformer 的关键桥：&lt;code&gt;decoder state 作为 query，encoder outputs 作为 memory&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="这篇文章重点深挖的两个概念"&gt;这篇文章重点深挖的两个概念&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;对齐分数与上下文向量&lt;/strong&gt;：decoder 怎样在每一步决定“该看源序列哪里”&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;从 attention-based seq2seq 到 Transformer 的结构映射&lt;/strong&gt;：哪些东西被保留了，哪些东西被替换了&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="大师级心智模型"&gt;大师级心智模型&lt;/h2&gt;
&lt;p&gt;fixed-length seq2seq 的核心假设是：&lt;/p&gt;</description></item><item><title>Seq2Seq 与 Encoder-Decoder：从翻译任务到最小可运行 PyTorch 实现</title><link>https://jeanphilogong.github.io/jeanblog/zh/ai/llm/seq2seq-encoder-decoder-pytorch-minimal/</link><pubDate>Thu, 23 Apr 2026 15:27:55 +0800</pubDate><guid>https://jeanphilogong.github.io/jeanblog/zh/ai/llm/seq2seq-encoder-decoder-pytorch-minimal/</guid><description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;副标题 / 摘要&lt;/strong&gt;
这篇文章不把 seq2seq 和 encoder-decoder 当成术语表来讲，而是从一个最小翻译任务出发，解释为什么“输入一段序列、输出另一段序列”会自然逼出编码器和解码器的分工，最后收束成一份最小可运行的 PyTorch GRU 实现。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="从一个最小翻译任务开始"&gt;从一个最小翻译任务开始&lt;/h2&gt;
&lt;p&gt;假设源序列是：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;lt;bos&amp;gt; I love apples &amp;lt;eos&amp;gt;
&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" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;lt;bos&amp;gt; 我 喜欢 苹果 &amp;lt;eos&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&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;/ul&gt;
&lt;p&gt;如果你只用一个普通分类器把源句子映射成一个类别，这个任务做不成。
因为这里的输出不是一个固定标签，而是长度可变的目标序列。&lt;/p&gt;
&lt;p&gt;所以这里天然会逼出一个更具体的数据流：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;先把源序列读完，压成可传递的状态&lt;/li&gt;
&lt;li&gt;再从 &lt;code&gt;&amp;lt;bos&amp;gt;&lt;/code&gt; 开始，逐步生成目标序列&lt;/li&gt;
&lt;li&gt;每生成一步，都要同时依赖“源侧信息”和“目标侧历史”&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;这就是 &lt;code&gt;sequence-to-sequence&lt;/code&gt; 的最小问题形态。
&lt;code&gt;seq2seq&lt;/code&gt; 说的是任务：输入一段序列，输出另一段序列。
&lt;code&gt;encoder-decoder&lt;/code&gt; 说的是实现：先编码输入，再逐步解码输出。&lt;/p&gt;
&lt;p&gt;下面不先堆名词，直接按这个压力把代码一步一步长出来。&lt;/p&gt;
&lt;h2 id="快速掌握地图"&gt;快速掌握地图&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;问题形态：&lt;code&gt;src -&amp;gt; encoder -&amp;gt; hidden/context -&amp;gt; decoder -&amp;gt; logits&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;核心目标：学习条件概率 &lt;code&gt;p(y_t | y_{&amp;lt;t}, x)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;最小实现：&lt;code&gt;Embedding + GRU Encoder + GRU Decoder + output projection&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;何时适用：翻译、摘要、改写、问答这类“输入序列 -&amp;gt; 输出序列”任务&lt;/li&gt;
&lt;li&gt;明显局限：如果所有源信息都被压进一个固定长度向量，长句子会吃力&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="这篇文章重点深挖的两个概念"&gt;这篇文章重点深挖的两个概念&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;隐藏状态交接&lt;/strong&gt;：encoder 到底把什么交给 decoder&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;右移目标序列与 teacher forcing&lt;/strong&gt;：训练时 decoder 为什么不能直接喂完整真实答案&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="大师级心智模型"&gt;大师级心智模型&lt;/h2&gt;
&lt;p&gt;这类模型的核心抽象不是“两个 RNN 拼起来”，而是：&lt;/p&gt;</description></item><item><title>Transformer 结构推导：一步一步搭出最小可运行 PyTorch 实现</title><link>https://jeanphilogong.github.io/jeanblog/zh/ai/llm/transformer-architecture-overview/</link><pubDate>Thu, 23 Apr 2026 10:13:23 +0800</pubDate><guid>https://jeanphilogong.github.io/jeanblog/zh/ai/llm/transformer-architecture-overview/</guid><description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;副标题 / 摘要&lt;/strong&gt;
这篇文章不把 Transformer 当成一个现成黑盒来介绍，而是直接从一个最小翻译任务开始，让需要的结构一层一层长出来，最后收束成一份最小可运行的 PyTorch encoder-decoder Transformer。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="从一个最小翻译任务开始"&gt;从一个最小翻译任务开始&lt;/h2&gt;
&lt;p&gt;假设源序列是：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;lt;bos&amp;gt; I love apples &amp;lt;eos&amp;gt;
&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" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;lt;bos&amp;gt; 我 喜欢 苹果
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;当模型要生成“苹果”时：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;它不能看目标序列里未来还没生成的位置&lt;/li&gt;
&lt;li&gt;它需要重点读取源序列中的 &lt;code&gt;apples&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;它可能还需要参考前面的“我 喜欢”来决定当前词&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;所以这里天然会逼出三件事：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;目标侧必须有&lt;strong&gt;因果约束&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;源侧和目标侧都需要&lt;strong&gt;全局读取&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;解码器不仅要读自己，还要读编码器输出&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;RNN 和 CNN 也能处理序列，但它们在长距离依赖和全并行训练上都有明显限制。
所以这里真正要解决的，不只是“做一个更深的网络”，而是让任意位置能直接交互，并且显式控制信息流方向。&lt;/p&gt;
&lt;p&gt;下面开始按这个压力一步一步长代码。&lt;/p&gt;
&lt;h2 id="step-1先有输入表示但先不谈注意力"&gt;Step 1：先有输入表示，但先不谈注意力&lt;/h2&gt;
&lt;p&gt;先看一个已经分词并编号后的最小输入：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;lt;bos&amp;gt; I love apples &amp;lt;eos&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;-&amp;gt; [1, 15, 982, 204, 2]
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;这里的 &lt;code&gt;1, 15, 982, 204, 2&lt;/code&gt; 还不是模型已经理解后的表示。
它们只是词表里的编号，作用更接近“标签”或“学号”：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;15&lt;/code&gt; 比 &lt;code&gt;204&lt;/code&gt; 小，不代表它们语义上更接近&lt;/li&gt;
&lt;li&gt;直接拿这种离散编号去做线性变换或点积，含义也不对&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;所以第一步只解决一个更具体的问题：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;怎样把“词表编号”变成“模型后面可以继续计算的一组连续数值”？&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;这里再引入两个词：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;token id&lt;/code&gt;：分词后查词表得到的离散编号&lt;/li&gt;
&lt;li&gt;&lt;code&gt;embedding&lt;/code&gt;：把每个离散编号映射成长度为 &lt;code&gt;d_model&lt;/code&gt; 的可学习向量&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;先写最小版，不额外引入别的机制：&lt;/p&gt;</description></item></channel></rss>