蛋壳侠 發表於 2026-2-2 13:53:00

吴恩达深度学习课程五:自然语言处理 第三周:序列模型与注意力机制(三)注意力机制

<p>此分类用于记录吴恩达深度学习课程的学习笔记,目前已完结,点击进入全集目录<br>
课程相关信息链接如下:</p>
<ol>
<li>原课程视频链接:[双语字幕]吴恩达深度学习deeplearning.ai</li>
<li>github课程资料,含课件与笔记:吴恩达深度学习教学资料</li>
<li>课程配套练习(中英)与答案:吴恩达深度学习课后习题与答案</li>
</ol>
<p>本篇为第五课第三周的内容,3.7到3.8的内容。</p>
<hr>
<p>本周为第五课的第三周内容,与 CV 相对应的,这一课所有内容的中心只有一个:<strong>自然语言处理(Natural Language Processing,NLP)</strong>。<br>
应用在深度学习里,它是专门用来进行<strong>文本与序列信息建模</strong>的模型和技术,本质上是在全连接网络与统计语言模型基础上的一次“结构化特化”,也是人工智能中<strong>最贴近人类思维表达方式</strong>的重要研究方向之一。<br>
<strong>这一整节课同样涉及大量需要反复消化的内容,横跨机器学习、概率统计、线性代数以及语言学直觉。</strong><br>
语言不像图像那样“直观可见”,更多是抽象符号与上下文关系的组合,因此<strong>理解门槛反而更高</strong>。<br>
因此,我同样会尽量补足必要的背景知识,尽可能用比喻和实例降低理解难度。<br>
本周的内容关于<strong>序列模型和注意力机制</strong>,这里的序列模型其实是<strong>指多对多非等长模型</strong>,这类模型往往更加复杂,其应用领域也更加贴近工业和实际,自然也会衍生相关的模型和技术。而注意力机制则让模型在长序列中学会主动分配信息权重,而不是被动地一路传递。二者结合,为 Transformer 等现代架构奠定了基础。</p>
<p>本篇的内容关于<strong>注意力机制</strong>,这是如今 NLP 乃至整个深度学习领域的核心技术之一。</p>
<h1 id="1注意力机制的思想">1.注意力机制的思想</h1>
<h2 id="11-传统编码解码框架的局限">1.1 传统编码解码框架的局限</h2>
<p>在前面的编码解码架构中我们已经了解到: 编码器的任务是将整个输入序列映射为一个固定长度的向量表示,而解码器则在此基础上逐步生成输出序列。<br>
也就是说,无论编码器采用的是 RNN、LSTM 还是 GRU,其最终都需要将<strong>长度可变的输入序列压缩为一个定长表示</strong>。<br>
这种设计在形式上简洁,但在实际使用中很快暴露出一个问题,我们称之为 <strong>信息瓶颈(information bottleneck)</strong>。<br>
<strong>即当输入序列较长、结构较复杂时,这种“整体压缩—再逐步解码”的方式不可避免地会丢失细节信息,且这种损失会随着序列长度增长而被放大。</strong></p>
<p>这不难理解,对比来说,就像我们去背课文,编码就是记忆的过程,而解码就是背诵的结果。显然,要背的内容越长,复述起来就越困难。<br>
<img src="https://img2024.cnblogs.com/blog/3708248/202602/3708248-20260202134500434-152196471.png" alt="image.png" loading="lazy"><br>
并且,这一框架仍存在我们之前在预测任务中提到过的问题:<strong>解码器在生成每一个输出词时,使用的是整句内容,但在机器翻译这类任务中,不同输出位置所依赖的输入信息显然是不同的。</strong><br>
例如,在生成目标语言中的某个名词时,真正相关的往往只是源语言中的局部片段,而非整句语义的表示结果。</p>
<p>正是在这一背景下,注意力机制被提出,其开创性工作在 2014 年发表的论文:Neural Machine Translation by Jointly Learning to Align and Translate。论文首次在编码解码框架中显式引入“对齐(alignment)”的概念,使解码器在每一个时间步都可以<strong>动态地从输入序列中选择相关信息</strong>,而不再依赖单一的全局向量表示。</p>
<h2 id="12-注意力机制与-lstm--gru">1.2 注意力机制与 LSTM / GRU</h2>
<p>在正式介绍注意力机制之前,有必要先进行一点概念上的澄清。<br>
在刚刚的讨论中我们提到,注意力机制的核心作用在于:<strong>使解码器在每一个时间步,都可以动态地从输入序列中选择与当前输出相关的局部信息。</strong><br>
乍一看,这一点似乎与我们此前在门控机制中介绍的 LSTM、GRU 的思想十分相似:它们都针对长序列情况,并同样强调对信息的保留与遗忘。<br>
但事实上,<strong>二者解决的是不同层面的问题</strong>。</p>
<p>首先,LSTM 与 GRU 关注的是<strong>单个序列在时间维度上的状态传递问题</strong>:在标准 RNN 中,隐藏状态需要在时间步之间不断递推,长距离依赖往往会在传播过程中被削弱甚至丢失。<br>
门控机制正是为此而设计的:通过输入门、遗忘门或更新门,模型可以决定<strong>哪些历史信息应该被保留下来,哪些可以被丢弃</strong>。</p>
<p>因此,LSTM / GRU 回答的问题是:<strong>“当前时刻的隐藏状态,应该如何从过去的状态中更新而来?”</strong><br>
<strong>简单点说,它们的工作是:如何得到更好的信息。</strong></p>
<p>相比之下,注意力机制关注的并不是“状态如何沿时间传播”,而是<strong>已有信息如何被使用</strong>。<br>
在编码解码框架中,编码器已经生成了一整组隐藏状态,用于刻画输入序列在各个位置上的表示。<br>
而注意力机制所做的,是在解码的每一个时间步,根据当前解码状态,<strong>在这组表示中显式建模输入与输出之间的关联关系,并据此对不同位置的信息进行聚合</strong>。最终得到”<strong>对当前步的输出,哪些输入更有用</strong>“。<br>
<img src="https://img2024.cnblogs.com/blog/3708248/202602/3708248-20260202135128660-1006470109.png" alt="image.png" loading="lazy"><br>
总结一下:</p>
<ul>
<li>LSTM / GRU 解决的是<strong>信息如何在时间维度上传递与保存</strong>。</li>
<li>注意力机制解决的是<strong>在一组已有表示中,如何进行信息选择与对齐</strong>。</li>
</ul>
<p>也正因如此,在注意力机制最初被提出时,它并不是用来取代 LSTM 或 GRU 的,而是<strong>叠加在它们之上使用</strong>。</p>
<p>了解了注意力机制的原理后,现在就来看看其实现:</p>
<h1 id="2注意力机制的实现">2.注意力机制的实现</h1>
<p>从整体上看,注意力机制并没有引入新的递推结构,也不改变原有的编码与解码流程,而是<strong>在解码的每一个时间步,引入了一次“基于当前状态的信息检索过程”</strong>。<br>
这一过程的核心思想可以概括为三步:</p>
<ol>
<li><strong>度量相关性</strong>:计算当前解码状态与输入序列中各个位置之间的匹配程度。</li>
<li><strong>分配权重</strong>:将这些匹配程度归一化,得到一组注意力权重。</li>
<li><strong>加权汇聚</strong>:根据权重,对输入表示进行加权求和,形成上下文向量。</li>
</ol>
<p>下面我们依次展开:</p>
<h2 id="21-对齐分数如何度量相关性">2.1 对齐分数:如何度量“相关性”</h2>
<p>首先,设编码器对输入序列产生了一组隐藏状态:</p>
<p></p><div class="math display">\[\{\mathbf{a}^{&lt;1&gt;}, \mathbf{a}^{&lt;2&gt;}, \dots, \mathbf{a}^{&lt;i&gt;}\}
\]</div><p></p><p>其中 <span class="math inline">\(\mathbf{a}^{&lt;i&gt;}\)</span> 表示<strong>输入序列在第 <span class="math inline">\(i\)</span> 个位置上的编码表示</strong>。<br>
同样,对于解码器,在解码的第每一个时间步,同样会产生隐藏状态:</p>
<p></p><div class="math display">\[\{\mathbf{s}^{&lt;1&gt;}, \mathbf{s}^{&lt;2&gt;}, \dots, \mathbf{s}^{&lt;t&gt;}\}
\]</div><p></p><p><span class="math inline">\(\mathbf{s}^{&lt;t&gt;}\)</span> 表示<strong>在解码的第 <span class="math inline">\(t\)</span> 个时间步,解码器当前的隐藏状态</strong>。<br>
注意力机制的第一步,就是<strong>计算每一步解码与每一步编码之间的相关性分数</strong>,公式表示为:</p>
<p></p><div class="math display">\[e^{&lt;t,i&gt;} = \text{score}(\mathbf{s}^{&lt;t&gt;}, \mathbf{a}^{&lt;i&gt;})\quad t = 1,2,\dots,T_y,; i = 1,2,\dots,T_x
\]</div><p></p><p>其中,<span class="math inline">\(T_y\)</span> 是输出序列长度、<span class="math inline">\(T_x\)</span> 是输入序列长度。</p>
<p>这个 score 函数的具体形式并不是唯一的,它只需要满足一个基本要求:<strong>能够反映当前输出状态与某个输入位置之间的匹配程度。</strong><br>
在原论文中,使用的是一个简单的前馈网络来完成这一部分:</p>
<p></p><div class="math display">\[e^{&lt;t,i&gt;} = \mathbf{v}^\top \tanh\left(
\mathbf{W}_s \mathbf{s}^{&lt;t&gt;} + \mathbf{W}_a \mathbf{a}^{&lt;i&gt;}
\right)
\]</div><p></p><p>其中 <span class="math inline">\(\mathbf{W}_s, \mathbf{W}_a, \mathbf{v}\)</span> 为可学习参数,<span class="math inline">\(\mathbf{v}\)</span> 的作用是通过向量内积将网络输出的向量映射为一个标量分数,来量化表示”相关性“。<br>
另一种更简单、计算效率更高的形式是<strong>直接使用向量内积</strong>:</p>
<p></p><div class="math display">\[e^{&lt;t,i&gt;} = \mathbf{s}^{&lt;t&gt;\top} \mathbf{a}^{&lt;i&gt;}
\]</div><p></p><p>当解码器状态与某个输入位置的表示方向越接近,内积越大,对齐分数也就越高。<br>
但无论采用哪一种形式,其语义都是一致的:<strong>这个输入位置,对当前输出有多重要?</strong><br>
<img src="https://img2024.cnblogs.com/blog/3708248/202602/3708248-20260202135128581-2140162443.png" alt="image.png" loading="lazy"><br>
来看一个实例:<br>
假设输入序列长度 <span class="math inline">\(T_x=3\)</span>,输出序列长度 <span class="math inline">\(T_y=2\)</span>。为了简化,用 <strong>二维向量</strong>表示隐藏状态:</p>
<ol>
<li>编码器隐藏状态:</li>
</ol>
<p></p><div class="math display">\[    \mathbf{a}^{&lt;1&gt;} = \begin{bmatrix}1 \\ 0\end{bmatrix}, \quad
    \mathbf{a}^{&lt;2&gt;} = \begin{bmatrix}0 \\ 1\end{bmatrix}, \quad
    \mathbf{a}^{&lt;3&gt;} = \begin{bmatrix}1 \\ 1\end{bmatrix}
\]</div><p></p><ol start="2">
<li>解码器第 1 个时间步隐藏状态:</li>
</ol>
<p></p><div class="math display">\[    \mathbf{s}^{&lt;1&gt;} = \begin{bmatrix}0.8 \\ 0.2\end{bmatrix}
\]</div><p></p><ol start="3">
<li>解码器第 2 个时间步隐藏状态:</li>
</ol>
<p></p><div class="math display">\[    \mathbf{s}^{&lt;2&gt;} = \begin{bmatrix}0.1 \\ 0.9\end{bmatrix}
\]</div><p></p><p>使用 <strong>点积注意力</strong> 计算对齐分数如下:</p>
<ul>
<li>对 <span class="math inline">\(t=1\)</span>:</li>
</ul>
<p></p><div class="math display">\[    \begin{aligned}
    e^{&lt;1,1&gt;} &amp;= \cdot = 0.8 \\
    e^{&lt;1,2&gt;} &amp;= \cdot = 0.2 \\
    e^{&lt;1,3&gt;} &amp;= \cdot = 1.0
    \end{aligned}
\]</div><p></p><ul>
<li>对 <span class="math inline">\(t=2\)</span>:</li>
</ul>
<p></p><div class="math display">\[    \begin{aligned}
    e^{&lt;2,1&gt;} &amp;= \cdot = 0.1 \\
    e^{&lt;2,2&gt;} &amp;= \cdot = 0.9 \\
    e^{&lt;2,3&gt;} &amp;= \cdot = 1.0
    \end{aligned}
\]</div><p></p><p>继续下一步:</p>
<h2 id="22-转换注意力权重从相关性到概率分布">2.2 转换注意力权重:从相关性到概率分布</h2>
<p>由于相关性分数本身并不具备可比性,下一步需要将其转换为一组归一化权重,最常见的做法,是在输入维度上施加 softmax 操作:</p>
<p></p><div class="math display">\[\alpha^{&lt;t,i&gt;} = \frac{\exp(e^{&lt;t,i&gt;})}{\sum_{k=1}^{T_x} \exp(e^{&lt;t,k&gt;})}
\]</div><p></p><p>此时,<span class="math inline">\(\alpha^{&lt;t,i&gt;}\)</span> 可以被解释为:<strong>在解码第 <span class="math inline">\(t\)</span> 个输出时,对输入位置 <span class="math inline">\(i\)</span> 的注意力分配程度。</strong><br>
这一步并不复杂,使用 <span class="math inline">\(\exp\)</span> 的作用是 <strong>是确保相关性为正数,并通过指数放大差异</strong>。<br>
我们继续刚刚的例子计算概率分布如下:</p>
<table>
<thead>
<tr>
<th><span class="math inline">\(t\)</span></th>
<th><span class="math inline">\(i\)</span></th>
<th><span class="math inline">\(e^{&lt;t,i&gt;}\)</span></th>
<th><span class="math inline">\(\exp(e^{&lt;t,i&gt;})\)</span></th>
<th><span class="math inline">\(\sum_k \exp(e^{&lt;t,k&gt;})\)</span></th>
<th><span class="math inline">\(\alpha^{&lt;t,i&gt;} = \frac{\exp(e^{&lt;t,i&gt;})}{\sum_k \exp(e^{&lt;t,k&gt;})}\)</span></th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>1</td>
<td>0.8</td>
<td>2.2255</td>
<td>6.1652</td>
<td>0.361</td>
</tr>
<tr>
<td>1</td>
<td>2</td>
<td>0.2</td>
<td>1.2214</td>
<td>6.1652</td>
<td>0.198</td>
</tr>
<tr>
<td>1</td>
<td>3</td>
<td>1.0</td>
<td>2.7183</td>
<td>6.1652</td>
<td>0.441</td>
</tr>
<tr>
<td>2</td>
<td>1</td>
<td>0.1</td>
<td>1.1052</td>
<td>6.2831</td>
<td>0.176</td>
</tr>
<tr>
<td>2</td>
<td>2</td>
<td>0.9</td>
<td>2.4596</td>
<td>6.2831</td>
<td>0.391</td>
</tr>
<tr>
<td>2</td>
<td>3</td>
<td>1.0</td>
<td>2.7183</td>
<td>6.2831</td>
<td>0.433</td>
</tr>
</tbody>
</table>
<p>这样,每个 <span class="math inline">\(\alpha^{&lt;t,i&gt;}\)</span> 都是一个 <strong>非负数</strong>,且行内总和为 1,并且指数函数放大了较大的 <span class="math inline">\(e^{&lt;t,i&gt;}\)</span>,弱化了较小的 <span class="math inline">\(e^{&lt;t,i&gt;}\)</span>,保证注意力更集中在相关位置。<br>
最终,归一化后得到的 <span class="math inline">\(\alpha^{&lt;t,i&gt;}\)</span> 就是 <strong>解码器在第 <span class="math inline">\(t\)</span> 步对输入第 <span class="math inline">\(i\)</span> 个位置的关注权重</strong>。<br>
<img src="https://img2024.cnblogs.com/blog/3708248/202602/3708248-20260202134500767-885947606.png" alt="image.png" loading="lazy"></p>
<p>下面就是最后一步:</p>
<h2 id="23-生成上下文向量">2.3 生成上下文向量</h2>
<p>有了注意力权重 <span class="math inline">\(\alpha^{&lt;t,i&gt;}\)</span> 后,下一步就是<strong>根据这些权重对编码器的隐藏状态进行加权求和</strong>,得到一个<strong>上下文向量(context vector)</strong>,用于辅助解码器生成当前输出。<br>
公式如下:</p>
<p></p><div class="math display">\[\mathbf{c}^{&lt;t&gt;} = \sum_{i=1}^{T_x} \alpha^{&lt;t,i&gt;} \mathbf{a}^{&lt;i&gt;}
\]</div><p></p><p>其中:</p>
<ul>
<li><span class="math inline">\(\mathbf{c}^{&lt;t&gt;}\)</span> 表示解码器在第 <span class="math inline">\(t\)</span> 个时间步的上下文向量;</li>
<li><span class="math inline">\(\alpha^{&lt;t,i&gt;}\)</span> 是第 <span class="math inline">\(t\)</span> 个输出对第 <span class="math inline">\(i\)</span> 个输入位置的注意力权重;</li>
<li><span class="math inline">\(\mathbf{a}^{&lt;i&gt;}\)</span> 是编码器在第 <span class="math inline">\(i\)</span> 个输入位置的隐藏状态。</li>
</ul>
<p>通过公式就可以得到上下文向量的语义:<strong>解码器在当前步“关注的输入信息的加权平均”</strong>。 权重越高的输入位置,对上下文的贡献越大。<br>
<img src="https://img2024.cnblogs.com/blog/3708248/202602/3708248-20260202134947044-506445960.png" alt="image.png" loading="lazy"><br>
继续例子:</p>
<ul>
<li>对 <span class="math inline">\(t=1\)</span>:</li>
</ul>
<p></p><div class="math display">\[\mathbf{c}^{&lt;1&gt;} = 0.361 \begin{bmatrix}1\\0\end{bmatrix} + 0.198 \begin{bmatrix}0\\1\end{bmatrix} + 0.441 \begin{bmatrix}1\\1\end{bmatrix}
= \begin{bmatrix}0.802\\0.639\end{bmatrix}
\]</div><p></p><ul>
<li>对 <span class="math inline">\(t=2\)</span>:</li>
</ul>
<p></p><div class="math display">\[\mathbf{c}^{&lt;2&gt;} = 0.176 \begin{bmatrix}1\\0\end{bmatrix} + 0.391 \begin{bmatrix}0\\1\end{bmatrix} + 0.433 \begin{bmatrix}1\\1\end{bmatrix}
= \begin{bmatrix}0.609\\0.824\end{bmatrix}
\]</div><p></p><p>这两个上下文向量就可以<strong>与解码器的隐藏状态 <span class="math inline">\(\mathbf{s}^{&lt;t&gt;}\)</span> 结合</strong>,作为生成最终输出的依据。</p>
<h2 id="24-输出预测">2.4 输出预测</h2>
<p>在实际模型中,解码器通常会将上下文向量与当前隐藏状态拼接或通过线性变换融合:</p>
<p></p><div class="math display">\[\tilde{\mathbf{s}}^{&lt;t&gt;}=tanh⁡(W_c)
\]</div><p></p><p>然后再输入到输出层预测概率分布:</p>
<p></p><div class="math display">\[\hat{\mathbf{y}}^{&lt;t&gt;} = \text{softmax}(\mathbf{W}_o \tilde{\mathbf{s}}^{&lt;t&gt;} + \mathbf{b}_o)
\]</div><p></p><p>最终:</p>
<ul>
<li><span class="math inline">\(\tilde{\mathbf{s}}^{&lt;t&gt;}\)</span> 是融合后的解码器状态。</li>
<li><span class="math inline">\(\mathbf{W}_c, \mathbf{W}_o, \mathbf{b}_o\)</span> 是可学习参数。</li>
<li><span class="math inline">\(\hat{\mathbf{y}}^{&lt;t&gt;}\)</span> 即该步最终输出概率分布。</li>
</ul>
<p>这样,我们就得到了加入注意力机制的序列模型的完整传播过程:<br>
<img src="https://img2024.cnblogs.com/blog/3708248/202602/3708248-20260202134946727-2058621405.png" alt="image.png" loading="lazy"><br>
这样,通过注意力机制,解码器的参考就不仅仅是单一的整句向量,而是可以在传播中,动态地学习到,哪些输入对当前步更重要,从而提高模型性能。</p>
<p>在原始注意力机制之后,注意力的思想迅速发展并成为深度学习序列建模中的核心模块。注意力机制的核心价值在于:<strong>动态分配信息权重,让模型在处理长序列或复杂结构时,更加高效、可解释、性能更强</strong>。这也是 Transformer 系列及大语言模型成功的基础之一。</p>
<h1 id="3-总结">3. 总结</h1>
<table>
<thead>
<tr>
<th>概念</th>
<th>原理</th>
<th>比喻</th>
</tr>
</thead>
<tbody>
<tr>
<td>编码解码架构(Encoder-Decoder)</td>
<td>将整个输入序列压缩为一个固定长度向量,解码器基于该向量逐步生成输出</td>
<td>背课文:记住整篇内容再复述,序列越长越难</td>
</tr>
<tr>
<td>信息瓶颈(Information Bottleneck)</td>
<td>长序列或复杂结构在压缩成定长表示时会丢失细节信息,损失随长度增长被放大</td>
<td>背长篇课文容易遗漏细节</td>
</tr>
<tr>
<td>注意力机制(Attention)</td>
<td>解码器在每步动态从输入序列中选择与当前输出相关的信息,通过计算相关性、归一化权重、加权求和生成上下文向量</td>
<td>看书时只关注当前要回答的问题所在的段落,而不是整本书</td>
</tr>
<tr>
<td>对齐分数(Alignment Score)</td>
<td>度量当前解码状态与输入各位置的匹配程度,可用前馈网络或向量点积实现</td>
<td>判断哪段记忆对当前问题最相关</td>
</tr>
<tr>
<td>注意力权重(Attention Weights)</td>
<td>将对齐分数通过 softmax 转换为概率分布,表示当前输出对各输入位置的关注程度</td>
<td>分配注意力资源:更重要的部分获得更多关注</td>
</tr>
<tr>
<td>上下文向量(Context Vector)</td>
<td>对输入表示加权求和,得到解码器当前步参考的综合信息</td>
<td>当前问题的答案依赖的重点信息汇总</td>
</tr>
<tr>
<td>输出预测</td>
<td>将上下文向量与解码器隐藏状态融合,输入输出层生成最终概率分布</td>
<td>用精选的重点信息结合记忆生成回答</td>
</tr>
<tr>
<td>核心价值</td>
<td>动态分配信息权重,提高长序列处理能力和模型可解释性</td>
<td>聚焦关键内容,避免整句或整篇平均处理导致效率低和信息丢失</td>
</tr>
</tbody>
</table><br><br>
来源:https://www.cnblogs.com/Goblinscholar/p/19563950
頁: [1]
查看完整版本: 吴恩达深度学习课程五:自然语言处理 第三周:序列模型与注意力机制(三)注意力机制