Bonky
Neither believe nor reject anything, because any other person has rejected or believed it. Heaven has given you a mind for judging truth and error, Use it.
By Thomas Jefferson

seq2seq 以及 attention 模型的 pytorch 实现

原文地址:https://blog.csdn.net/u014514939/article/details/89410425

前言

本系列教程为 pytorch 官网文档翻译。本文对应官网地址:https://pytorch.org/tutorials/intermediate/seq2seq_translation_tutorial.html

系列教程总目录传送门:我是一个传送门

本系列教程对应的 jupyter notebook 可以在我的 Github 仓库下载:

下载地址:https://github.com/Holy-Shine/Pytorch-notebook

本教程我们将会搭建一个网络来将法语翻译成英语。

这可以通过 Sequence to sequence network 简单而又强大的 idea 来实现,该实现包含两个循环神经网络,它们共同工作从而将一个序列转化为另一个序列:编码器网络将输入压缩成矢量;解码器网络将该矢量展开成新的序列。

图 1. Sequence to sequence 网络结构

为了增强模型的表现力,我们同时引入了注意力机制,它使得解码器能够学习到在翻译某个部分的时候该给整个序列多少的注意力

依赖包

1 加载数据文件


本教程的数据文件是几千个英语到法语的翻译句子对。

翻译开源下载地址:http://www.manythings.org/anki/

点击这里下载本教程所需的翻译文件。

解压到当前目录下,得到一个 data/eng-fra.txt文件,该文件每一行是由 tab 分隔开的翻译对:

类似于字符级 RNN 编码字母序列,我们仍然将源语言的每个单词编码为一个 one-hot 向量。与语言中可能存在的几十个字符相比,词语的量显然更大,因此编码向量要大得多。我们在这里做个小小的弊,修剪数据 —- 每种语言我们只采用几千个单词。

图 2. one-hot 编码示例

为了编码词语,我们需要每个单词的唯一索引,以便后续网络的输入和目标的构建。为此我们使用一个名为 Lang 的辅助类,它具有 word→index (word2index)index→word(index2word) 两个字典,以及用于稍后替换稀有单词的每个单词的计数 (word2count)

文件都是 Unicode 编码的,我们需要简单得将字符转化为 ASCII 编码,全部转化为小写字母,并修剪大部分标点符号。

读取数据,我们将文件分割成行,进一步分割成句子对。文件是 English→其他语言 ,所以这里加上一个 reverse 参数,可以用来获取 其他语言→英语 的翻译对。

由于样本句子很多,而我们想加速训练。我们会将数据集修剪成相对简短的句子。这里的最大长度是 10(包括结束标点),同时我们会过滤出 “我是”,”他是” 这种形式的句子 (别忘记之前忽略的撇号, 比如 "I'm" 这种形式)。

完整的数据准备流程如下:

  • 读取文本文件,按行分割,再将每行分割成语句对
  • 归一化文本,过滤内容和长度
  • 根据滤出的句子对创建词语列表

out:

2 Seq2Seq 模型


循环神经网络对一个序列操作,然后将其输出作为下一个子操作的输入。

一个 Sequence to Sequence,或者叫 seq2seq 网络,又或者叫 编码 – 解码网络,包含了两个 RNN 部分:编码器和解码器。编码器网络将输入序列转化为单个向量,解码器读取这个向量,将其转化为序列输出。图 1 展示了这个结构。这里我们回顾下图 1:

图 1. Sequence to sequence 网络结构

与使用单个 RNN 的序列预测不同 (单个 RNN 序列预测的每个输入都对应一个输出), seq2seq 模型使得我们能从序列的长度和顺序中解放出来,这使得其能够成为两种语言之间相互转换的理想选择。

考虑一个句子对:

使用 seq2seq 模型,编码器创建单个向量,在理想情况下,将输入序列的 “语义” 编码为单个向量 — 代表句子的某些 N 维空间中的单个点

编码器

seq2seq 网络的编码器是一个 RNN,它通过阅读输入句子的每个单词,来生成一些值。对每一个输入单词,编码器输出一个向量和一个隐藏状态,并且使用这个隐藏状态作为下一个单词的输入。

图 3. 编码器结构

解码器使用编码器输出的向量作为输入,输出一串单词,从而完成翻译。

简单的解码器

在最简单的解码器中我们仅仅使用编码器最后一步的输出作为输入。这个最后一步的输出有时候被称为 (上下文向量)context vector 因为它编码了整个文本序列。这个上下文向量被用作解码器的初始隐藏状态。

在解码的每一步,解码器接受一个词语和一个隐藏状态。初始的输入词语是单词开始标记<SOS>,初始隐藏状态是上文提到的上下文向量。

图 4. 简单的解码器结构

我鼓励你训练和观察这个模型的结果,但为了节省空间,我们将 “直捣黄龙” 并引入注意机制。

注意力的解码器

如果仅仅是在编码器和解码器之间传递上下文向量, 对单个向量来说,表达整个句子是很困难的。

注意力机制允许解码器在解码的每一步获得一个关于原始句子的注意量 (即是说,这一步的翻译需要在原始句子的不同部分投入多少的注意力)。首先我们计算一个注意力权重的集合。这些权重将和编码器的输出向量相乘来获得一个权重敏感的输出。这个步骤的结果 (在代码里为attn_applied) 应包含有关输入序列特定部分的信息,从而帮助解码器选择正确的输出字。

图 5. 注意力结构

使用一个前馈层 attn 来计算注意力权重,它使用解码器的输入和隐藏状态作为输入。因为训练数据中的句子长短不一,因此要创建和训练这一层,就必须选择最长的句子长度。最大长度的句子将使用所有注意力量,而较短的句子将仅使用前几个。

图 6. 注意力解码器结构

通过使用相对位置方法,还有其他形式的注意力可以解决长度限制问题。参考 Effective Approaches to Attention-based Neural Machine Translation

3 训练


准备训练数据

对每个训练样本对,我们需要一个输入张量 (输入句子中每个词语在词典中的位置) 和目标张量(目标句子中每个词语在词典中的的位置)。在创建这些向量的同时,我们给两个都句子都加上 EOS 作为结束词

训练模型

我们将句子输入到编码器网络中,同时跟踪其每一步的输出和最后一步的隐藏状态。然后解码器网络接受一个 <SOS> 作为第一步的输入,编码器最后一步的隐藏状态作为其初始隐藏状态。

这里有个选择问题:关于解码器的每一步,我们到底使用目标字母直接输入,还是使用前一步的网络预测结果作为输入?

Teacher forcing 概念:使用真实目标输出作为下一个输入,而不是使用解码器的预测作为下一个输入。使用 teacher forcing 可以使网络更快地收敛,但是当受过训练的网络被运用时,它可能表现出不稳定性。

你可以观察下网络的输出,读起来看似语法相关,但是实际上远离正确的翻译 — 直觉上网络学到了如何表示输出语法,并且一旦’Teacher‘告诉它前面几个单词,它就会 “提取” 含义,但它还没有正确地学习如何从翻译中创建句子。

由于 PyTorchautograd 为我们提供了自由度,我们可以随意选择使用 teacher forcing 或不使用通过简单的 if 语句。将 teacher_forcing_ratio 来控制使用的概率

下面是一个辅助函数,用于打印经过的时间和估计的剩余时间,通过给定的当前时间和进度%。

完整的训练流程如下:

  • 计时器打开
  • 初始化优化器和评估函数
  • 创建训练对
  • 开始跟踪损失

然后我们会多次调用 train ,间断打印进度(例子的百分比,到目前为止的时间,估计的时间)和平均损失。

可视化结果

4 评估


评估与训练大致相同,但没有目标,因此我们只需将解码器的预测反馈给每个步骤。每次它预测一个单词时我们都会将它添加到输出字符串中,如果它预测了 EOS 标记,我们就会停止。我们还存储解码器的注意力输出以供稍后显示。

我们可以从训练集中评估随机句子并打印输入,目标和输出以做出一些主观质量判断:

5 训练与评估


有了所有这些辅助函数(它看起来像是额外的工作,但它使得更容易运行多个实验)我们实际上可以初始化网络并开始训练。

请记住,输入句子被严重过滤。对于这个小数据集,我们可以使用 256 个隐藏节点和单个 GRU 层的相对较小的网络。在 MacBook CPU 上大约 40 分钟后,我们将得到一些合理的结果。

out:

evaluateRandomly(encoder1, attn_decoder1)

out:

6 可视化注意力

注意机制的一个很有用的特性是其高度可解释的输出。因为它用于对输入序列的特定编码器输出进行加权,所以我们可以想象在每个时间步长看网络最关注的位置。

你可以简单地运行plt.matshow(attentions) 以将注意力输出显示为矩阵,其中列是输入步骤,行是输出步骤:

out:

为了更好地展示结果,我们给轴添加上标签

out:

Share

You may also like...

发表评论

电子邮件地址不会被公开。 必填项已用*标注