构建带注意力机制的 Seq2Seq 模型用于语言翻译

Bahdanau 等人在 2014 年提出的注意力机制显著改进了序列到序列(seq2seq)模型。在本篇文章中,您将学习如何构建和训练一个带注意力机制的 seq2seq 模型来进行语言翻译,重点关注:

  • 注意力机制为何至关重要
  • 如何在 seq2seq 模型中实现注意力

让我们开始吧。

构建带注意力机制的 Seq2Seq 模型用于语言翻译
照片由 Esther T 拍摄。部分权利保留。

概述

本文分为四个部分;它们是:

  • 为何注意力很重要:基础 Seq2Seq 模型的局限性
  • 实现带注意力的 Seq2Seq 模型
  • 训练和评估模型
  • 使用模型

为何注意力很重要:基础 Seq2Seq 模型的局限性

传统的 seq2seq 模型采用编码器-解码器架构,其中编码器将输入序列压缩成一个单一的上下文向量,解码器然后利用该向量生成输出序列。这种方法有一个关键的局限性:无论输出序列的长度如何,解码器都必须依赖于这个单一的上下文向量。

这对于长序列来说会变得很麻烦,因为模型难以保留序列早期部分的重要细节。以英语到法语翻译为例:解码器使用上下文向量作为其初始状态来生成第一个词元,然后将每个前一个输出作为后续词元的输入。随着隐藏状态的更新,解码器会逐渐丢失原始上下文向量的信息。

注意力机制通过以下方式解决此问题:

  • 在生成过程中让解码器能够访问所有编码器的隐藏状态
  • 允许模型为每个输出词元聚焦于相关的输入部分
  • 消除对单一上下文向量的依赖

实现带注意力的 Seq2Seq 模型

让我们按照 Bahdanau 等人 (2014) 的方法来实现一个带注意力的 seq2seq 模型。您将使用 GRU(门控循环单元)模块而不是 LSTM,因为它们更简单且训练速度更快,同时保持了相当的性能。

与训练数据集相同,并且与 上一篇文章中的普通 seq2seq 模型中的编码器类似,编码器的实现如下:

dropout 模块通过应用于嵌入层输出来防止过拟合。RNN 使用 nn.GRU 并设置 batch_first=True 来接受形状为 (batch_size, seq_len, embedding_dim) 的输入。编码器的 forward() 方法返回:

  • 一个形状为 (batch_size, seq_len, hidden_dim) 的 3D 张量,包含 RNN 输出
  • 一个形状为 (1, batch_size, hidden_dim) 的 2D 张量,包含最终的隐藏状态

Bahdanau 注意力机制与现代 Transformer 注意力不同。这是它的实现:

注意力机制在数学上定义为:

$$
y = \textrm{softmax}\big(W^V \tanh(W^Q Q + W^K K)\big) K
$$

与缩放点积注意力不同,它使用查询和键的求和投影。

使用 Bahdanau 注意力模块,解码器的实现如下:

解码器的 forward() 方法需要三个输入:一个单词输入序列、最新的 RNN 隐藏状态以及编码器的完整输出序列。它将使用注意力机制对齐输入词元与编码器的输出序列,以生成解码器的上下文向量。然后,该上下文向量与输入词元一起,通过 GRU 模块用于生成下一个词元。最后,输出被投影到一个与词汇表大小相同的logit向量。

然后,通过连接编码器和解码器模块来构建 seq2seq 模型,如下所示:

Seq2seq 模型在训练过程中采用教师强制(teacher forcing)策略,即使用真实目标词元(而不是上一步的解码器输出)作为输入来加速学习。在此实现中,编码器仅调用一次,而解码器则被调用多次以生成输出序列。

训练和评估模型

使用您在上一节中创建的模块,您可以初始化一个 seq2seq 模型:

训练循环与 上一篇文章中的非常相似。

训练过程利用交叉熵损失来比较输出的 logits 和真实的法语翻译。解码器从 [start] 开始,一次预测一个 token。由于训练数据包含填充和特殊 token,我们比较 outputfr_ids[:, 1:] 来进行对齐。请注意,[pad] token 被包含在损失计算中,但您可以在创建损失函数时通过指定 ignore_index 参数来跳过它。

模型训练 50 个 epoch。每五个 epoch 进行一次评估。由于您没有单独的测试集,可以使用训练数据进行评估。您应该将模型切换到评估模式,并在 torch.no_grad() 下使用模型,以避免计算梯度。

使用模型

一个训练良好的模型通常能达到大约 0.1 的平均交叉熵损失。虽然上一节中的训练循环概述了如何使用模型,但由于 Seq2SeqRNN 类的 forward() 方法是为训练而创建的,因此您应该在推理时单独使用编码器和解码器。以下是如何使用训练好的模型进行翻译:

在推理过程中,您在每一步将序列长度为 1、批次大小为 1 的张量传递给解码器。解码器将为您提供一个序列长度为 1、批次大小为 1 的 logits 向量。您使用 argmax() 来解码输出 token ID。此输出 token 然后用作循环下一次迭代的输入,直到生成 [end] token 或达到最大长度。

下面的样本输出展示了模型的性能:

为了进一步提高模型的性能,您可以:

  • 增加分词器的词汇量大小
  • 修改模型架构,例如,使用更大的嵌入维度、更大的隐藏状态维度或更多的 GRU 层。
  • 改进训练过程,例如,调整学习率、 epoch 数、使用不同的优化器,或为评估使用单独的测试集。

为了完整起见,以下是您在此帖子中创建的完整代码:

请注意,上面的代码在解码器和编码器中使用了 GRU 作为 RNN 模块。您也可以使用其他 RNN 模块,例如 LSTM 或双向 RNN。您只需将编码器和解码器中的 nn.GRU 模块替换为不同的模块即可。下面是使用 LSTM 和缩放点积注意力实现的编码器和解码器。您可以替换上面的实现,代码应该可以正常运行。

进一步阅读

以下是一些您可能会觉得有用的资源:

总结

在这篇文章中,您学习了如何为英法翻译构建和训练一个基于注意力的序列到序列模型。具体来说,您学习了:

  • 如何构建一个带有 GRU 的编码器-解码器架构
  • 实现注意力机制以帮助模型关注相关的输入
  • 在 PyTorch 中构建一个完整的翻译模型
  • 使用教师强制进行有效训练

注意力机制通过在生成过程中允许动态关注相关的输入部分,显著提高了翻译质量。

暂无评论。

留下回复

Machine Learning Mastery 是 Guiding Tech Media 的一部分,Guiding Tech Media 是一家领先的数字媒体出版商,专注于帮助人们了解技术。访问我们的公司网站以了解更多关于我们的使命和团队的信息。