PyTorch10-常见或经典内容解读

十、常见或经典内容解读

10.1 RNN循环神经网络 介绍

10.1.1 什么是seq2seq模型

Seq2Seq(Sequence-to-Sequence,序列到序列) 是深度学习中处理“输入一个序列,输出另一个序列”任务的核心架构。拥有此构架的模型称为seq2seq模型,常用来完成机器翻译任务、文本摘要任务、文本对话任务等。

通俗地说,就是将一个输入序列转换成一个输出序列的转换器,如下面的动图所示。

《Sequence to Sequence Learningwith Neural Networks》、《Learning Phrase Representations using RNN Encoder–Decoder for Statistical Machine Translation》这两篇论文,是 seq2seq 模型的开山之作。它们于 2014 年发布,确立了“输入不定长序列 \(\rightarrow\) 压成向量 \(\rightarrow\) 输出不定长序列”的经典范式,直接开启了深度学习时代自然语言处理(NLP)的新革命。

10.1.2 seq2seq模型如何工作

上图中蓝色区域所示的“Sequence to Sequence Model(简称“seq2seq模型”)”又可以细化为“Encoder(编码器)”、“Decoder(解码器)”,如下所示。 将一个输入序列输给seq2seq模型,seq2seq模型中的Encoder会对输入序列进行编码、提取其中蕴含的信息,得到一个中间产物“context向量(上下文向量)”;这个context向量会被输入给seq2seq模型中的Decoder,Decoder尝试理解context向量并反向解析其中内容,得到预期的一个输出序列。

下面以机器翻译为例(被翻译的法语序列“Je suis étudiant”,预期的翻译结果是英语序列“I am a student”。 seq2seq模型本质上只是把输入序列、输出序列都当成普通的字符序列而已),以更深入细致的角度来分析其seq2seq模型的工作过程。

做机器翻译的seq2seq模型中的编码器和解码器一般采用循环神经网络RNN实现。其中输入序列会被划分成一个个更小的小序列(这个过程叫做分词。分词其实是一个不断发展且复杂的过程,通常使用 word embedding 算法来完成。早期对“法语、英语等印欧语系语言”这种序列,最基础、最传统的分词方式就是以空格作为分词的间隔符号),然后每个小序列用一个向量表示,表示小序列的向量长度依据不同的word embedding 算法而不同,一般来说都是 2的幂,比如是长度为256的向量来表示一个小序列,也可能是512、1024等。

因为一个输入序列会被分割成多个小序列,经过分词与词嵌入表示后,一个输入序列就变成一个二维张量(或者说二维矩阵)的形式。为了呈现方便,假设一个小序列用一个长度为4的向量来表示,则“Je suis étudiant”可能表示如下:

(上图中最右侧的条纹是将同一个向量的多个分量分别用一个格子表示,分量值越大,格式的亮度越大。)

这样得到输入序列的嵌入表示后,将它输入给编码器,编码器对应的RNN执行如下运算过程,直至得到输入序列的最后一个单词对应的隐藏状态向量(hidden state向量),它就是context向量:

  1. 假设序列输入是一个句子,这个句子可以由\(n\)个词表示:\(sentence = \{w_1, w_2,...,w_n\}\)。
  2. RNN首先将句子中的每一个词映射成为一个向量得到一个向量序列:\(X = \{x_1, x_2,...,x_n\}\),每个单词映射得到的向量通常又叫做:word embedding。
  3. 然后在处理第\(t \in [1,n]\)个时间步的序列输入\(x_t\)时,RNN网络的输入和输出可以表示为:\(h_{t} = RNN(x_t, h_{t-1})\)
    • 输入\(x_t\):时间步\(t\)时,RNN正在处理的单词\(w_t\)经过映射得到的向量\(x_t\)。
    • 输入\(h_{t-1}\):上一个时间步\(t-1\)得到的hidden state向量\(h_{t-1}\),同样是一个向量。初始的隐藏状态 \(h_0\) 通常会被初始化为全零向量。
    • 输出:时间步\(t\)时,RNN的输出为\(h_t\) hidden state向量。 这个过程如下面的动图所示:

这样,我们就得到了context向量。再将context向量输入给解码器,解码器根据context向量逐步解析出输出序列。与上面描述的编码器中执行过程类似,解码器会参考context向量、上一个时间步解析出来的输出单词、上一次时间步得到的隐藏状态向量,计算得到当前要输出单词对应的向量,将此向量解析成对应的输出单词,大致过程表述如下:

  1. 刚开始时,解码器并不确定在什么时刻结束,但它一般在在满足如下两种情况时会停止:解析输出单词是”结束符<eos>“、达到输出的最大长度限制(防止模型陷入死循环)。
  2. 在时间步t时,RNN网络的输入与输出可表示为:\(h_{t} = RNN(x_{t-1}, h_{t-1})\)
    • 输入\(x_{t-1}\):上一个时间步\(t-1\)时,RNN预测得到的单词对应的词嵌入向量\(x_{t-1}\)。初始的向量是序列起始符 <sos> 的词嵌入向量。
    • 输入\(h_{t-1}\):上一个时间步\(t-1\)得到的hidden state向量\(h_{t-1}\),同样是一个向量。初始的隐藏状态 \(h_0\) 由context向量初始化得到。
    • 输出1:时间步\(t\)时,RNN的输出为\(h_t\) hidden state向量。
    • 输出2:得到\(h_t\) hidden state向量后,经过全连接层投影、概率归一化、选择Token、词表反查等操作,得到当前时间步的输出单词。此时就把这个输出单词输出给用户,不断重复上述操作,就像模型在一个字一个字回复内容一样。

整个编码与解码过程按时刻分步骤演示如下:

现在我们知道了seq2seq模型的工作过程。但从其构架与工作机制可知(实际效果也验证),其中使用RNN递归编码输入文本序列的方法在面对长输入文本序列时效果会比较差,其中的重要原因有:编码器最后输出的隐藏状态向量(即context向量)无法记忆与容纳原长输入文本序列的全部信息、解码器解码时将原长输入文本序列中所有分词看成一样重要从而没有区分度和关注重点。

这为attention机制出现提供了现实问题环境,下面介绍attention机制、基于RNN的seq2seq模型结合attention机制后会产生什么效果。

10.1.3 Attention机制

为了解决上述两个明显的问题、优化 Seq2Seq 模型的性能,Bahdanau 等人于2014年发表了论文 Neural Machine Translation by Jointly Learning to Align and Translate,其中提出了如何让 Seq2Seq 模型在生成每个目标词时,动态关注输入序列的相关部分;Luong 等人于2015年发表了论文 Effective Approaches to Attention-based Neural Machine Translation ,其中提出了全局、局部注意力及多种计算方案,进一步研究了如何更简单、高效地设计和应用注意力机制。这两篇代表性的论文推动了注意力机制在神经机器翻译中的发展。

101.3.1 什么是Attention机制

Attention(注意力)机制是一种让神经网络根据当前任务的需要,动态关注输入信息中不同部分的机制。

在机器翻译的 Seq2Seq 模型中,传统方法将整个输入序列压缩成一个固定长度的上下文向量,容易造成信息丢失;Attention 则允许解码器在生成每一个目标词时,根据当前的解码状态,计算输入序列各位置的注意力权重,并对编码器输出的隐藏状态进行加权求和,得到当前时间步的上下文向量,再利用该向量生成目标词。这样,模型就能在生成不同词语时关注输入序列中不同的信息,从而更好地捕捉输入与输出之间的对应关系,提高翻译效果。Bahdanau 等人于 2014 年提出的神经机器翻译注意力方法,是这一方向的重要代表,也为后续 Attention 及 Transformer 等模型的发展奠定了基础。

需要了解更多细节,可以继续阅读上述两篇论文及其他更多相关论文。

101.3.2 基于RNN的seq2seq模型结合注意力机制

仍以上述法语“Je suis étudiant”翻译成英文“I am a student”示例进行讲解。

首先相关实践已经验证知道,在时间步7时,注意力机制使得解码器在生成待输出单词(其实是"student")时,最关注输入序列中的法文单词”étudiant“(即给予它最大的注意力。对输入序列中其他法文单词也会给予不同的注意力): 这放大了输入序列中相关部分的信号(或说重要性),相比没有注意力机制时,此时的seq2seq模型能取得更好的生成效果。

我们先从抽象概念级别来看,注意力模型与传统seq2seq模型主要有如下两点区别:

  • 首先,注意力模型中的编码器会传递更多的数据给解码器。前面说过,(传统)seq2seq模型的编码器只会将最后一个隐藏状态传递给解码器,注意力模型中的编码器把所有时间步的隐藏状态传递给解码器。如下所示,是注意力模型时的情况。
  • 其次,注意力模型的编码器在每次生成输出前会做一些额外的、与注意力分数相关操作。具体地,在解码过程中,为了关注输入序列中与当前时间步中相关的那部分内容,解码器会做如下操作:
    1. 编码器中每个 hidden state(隐藏层状态)都对应到输入句子中一个单词,解码器要查看所有接收到的编码器的 hidden state(隐藏层状态)。
    2. 根据上一个时间步\(t_{n-1}\)输出单词的词嵌入向量\(x_{n-1}\),以及上一个时间步得到的解码器隐藏状态\(h_{n-1}\),计算出当前时间步的隐藏状态\(h_n\):\(h_n=\operatorname{RNN}(x_{n-1},h_{n-1})\)。 根据编码器所有hidden state(隐藏层状态)与当前时间步的隐藏状态\(h_n\),计算出编码器所有hidden state(隐藏层状态)的注意力分数(我们先忽略这个分数的计算过程)。
    3. 将注意力分数经过softmax归一化得到注意力权重(编码器有多少个隐藏状态,此处得到的注意力分数与注意力权重就有多少个分量)。
    4. 将每个 hidden state(隐藏层状态)乘以所对应的注意力权重分量,从而能够让高分对应的 hidden state(隐藏层状态)会被放大,而低分对应的 hidden state(隐藏层状态)会被缩小。
    5. 将所有hidden state根据对应权重进行加权求和,得到对应时间步的context向量。 可见,上述注意力机制就是找权重、执行加权求和以及这一整套流程连起来,其中关键在于找到注意力权重。

基于上述attention分析,基于RNN的seq2seq模型结合了注意力机制后,其解码器上时间步执行的典型操作(如下是一种典型的 Attention-RNN Seq2Seq 实现方式,并非所有注意力模型都严格采用相同结构与相同实现。)可以描述如下:

  1. 准备解码器 RNN 的输入。 输入包括两部分,输入1是,上一个时间步\(t_{n-1}\)输出单词的词嵌入向量\(x_{n-1}\),解码器中第一次要输出时(此例中为时间步\(t_4\)),\(x_{n-1}\)为起始符<SOS>对应的词嵌入向量;输入2是,上一个时间步得到的解码器隐藏状态\(h_{n-1}\),解码器中第一次要输出时(此例中为时间步\(t_4\)),通常从编码器最后一个隐藏状态初始化得到初始隐藏状态,后续使用当前时间步得到的前一隐藏状态。
  2. RNN 计算当前隐藏状态。 解码器根据\(x_{n-1}\)和\(h_{n-1}\),计算第 n 个时间步的隐藏状态\(h_n\):\(h_n=\operatorname{RNN}(x_{n-1},h_{n-1})\) 。
  3. 计算注意力权重和 context 向量\(C_n\)。 使用编码器处理三个法语单词得到的隐藏状态\(h_1,h_2,h_3\),以及当前解码器隐藏状态\(h_n\),计算注意力分数。经过 Softmax 得到注意力权重\(\alpha_{n,1}\),\(\alpha_{n,2}\),\(\alpha_{n,3}\),再对编码器所有隐藏状态加权求和,得到当前时间步的 context 向量:\(C_n=\sum_{i=1}^{3}\alpha_{n,i}h_i\) ,此示例中原输入序列只分成3个单词、故编码器共得到3个隐藏状态。其中,\(C_n\) 反映了模型在生成当前单词时,对输入序列不同位置的关注程度。它与 \(C_{n-1}\) 不一定相同。
  4. 拼接解码器隐藏状态和 context 向量。 将\(h_n\)与\(C_n\)拼接,得到组合向量: \(z_n=[h_n;C_n]\) 。
  5. 将组合向量输入前馈神经网络。 前馈神经网络对\(z_n\)进行变换,得到输出词表中各个候选 token 对应的 logits,再通过 Softmax 得到概率分布。
  6. 确定当前时间步的输出单词。 模型根据概率分布和解码策略选择当前输出 token。对于这个翻译示例,假设模型在\(t_4\)、\(t_5\) 分别预测出 I、am。
  7. 进入下一个时间步。 将前时间步的输出单词对应的词嵌入作为下一时间步的输入,并使用当前隐藏状态\(h_n\)作为下一时间步的前一隐藏状态,继续生成后续单词。按照预期的翻译序列,seq2seq模型在时间步依次生成I、am、 a、student,最后生成结束符 <END>。

整个过程的时间步对应关系如下:

时间步 执行主体 处理或输出内容
(t_1) 编码器 处理 Je,得到 (h_1)
(t_2) 编码器 处理 suis,得到 (h_2)
(t_3) 编码器 处理 étudiant,得到 (h_3)
(t_4) 解码器 输出 I,得到 (h_4、C_4)
(t_5) 解码器 输出 am,得到 (h_5、C_5)
(t_6) 解码器 输出 a
(t_7) 解码器 输出 student
(t_8) 解码器 输出 <END>,结束解码

关键区别: 编码器的 \(t_1、t_2、t_3\) 用于处理输入序列;解码器的 \(t_4\) 及之后的时间步用于生成输出序列。每个解码时间步都会根据当前隐藏状态重新计算注意力权重和 context 向量,而不是一直使用同一个 context 向量。

最后,采用可视化机制能可视化注意力机制,看看在解码器在每个时间步关注了输入序列的哪些部分: 可以看到,在解码器逐步骤分别输出I、am、 a、student时,它最关注编码器中的隐藏状态分别是:hidden state #1、hidden state #2、 hidden state #3、hidden state #4,相应地它最关注输入序列中单词分别是:Je、suis、 suis、étudiant。颜色越深,表示越关注。

10.1.4 参考资料

  1. Jay Alammar博客:Visualizing A Neural Machine Translation Model
  2. datawhale开源文章:图解attention
  3. YouTube视频 A friendly introduction to Recurrent Neural Networks
  4. 网页文章 ZhikangNiu 介绍RNN的文章

发现一个在macOS上开源好用的”将视频转成git动态图“的软件:Gifski。 在应用商店中安装即可使用。

10.2 Transformer构架 解读

Transformer构架源自谷歌于2017年中发表的一篇论文:Attention Is All You Need 。原论文通过谷歌自家开源的TensorFlow框架实现了相关源码:tensor2tensor 。由于框架过于庞大、可读性不好、生态发展等原因,后来哈佛大学 NLP 实验室(Harvard NLP)的学者们用现代、简洁、直观的 PyTorch 重新实现一遍,并且采用“文学编程”的方式,把论文段落和代码逐行对齐,且源码实现中给出了可读性很好的注释,源码实现见:annotated-transformer 。

参考资料

  1. datawhale开源文章:Transformer架构

PyTorch10-常见或经典内容解读
https://jiangsanyin.github.io/2026/10/21/PyTorch10-常见或经典内容解读/
作者
sanyinjiang
发布于
2026年10月21日
许可协议