您的位置:

如何有效地为Transformer添加位置编码

一、简介

Transformer是一种虽然比较新颖但已经相当成熟的深度神经网络结构。在很多自然语言处理任务中,Transformer已经展示出了惊人的效果。其中一个关键的结构就是位置编码。本文将会介绍Transformer模型中的位置编码,以及如何将其有效地添加到Transformer中,同时提供示例代码。

二、位置编码的介绍

在Transformer中,由于自注意力机制,模型需要对所有输入的位置进行建模。构建位置编码是Transformer模型中的一项非常重要的任务,目的是为了保证输入序列中token之间的相对位置信息被编码到了模型中。位置编码的提出主要是为了解决针对位置信息无法适应序列长度变化的问题。没有位置编码,序列太短则模型将无法感知距离信息并容易出现过拟合;相反,如果序列太长,模型则会失去距离感知能力并容易欠拟合。

三、位置编码的生成方式

在Transformer中,位置编码实际上是一组固定的参数向量,可以通过一定的公式来计算得到。这个公式比较简单,可以参考如下代码:

import numpy as np

# 获取每个位置的位置编码
def get_pos_encoding_matrix(max_len, d_emb):
    pos_enc = np.array([
        [pos / np.power(10000, 2*i/d_emb) for i in range(d_emb)]
        if pos != 0 else np.zeros(d_emb) for pos in range(max_len)])
    pos_enc[1:, 0::2] = np.sin(pos_enc[1:, 0::2])  # dim 2i
    pos_enc[1:, 1::2] = np.cos(pos_enc[1:, 1::2])  # dim 2i+1
    return pos_enc

生成位置编码的方式比较独特。我们使用了一个根据位置、向量维度与一个常数10000的公式计算的数列,其中偶数维度使用sine函数,奇数维度使用cosine函数来生成,以保证丰富的位置信息与互不冲突的位置向量独特性。

四、位置编码的添加方式

我们可以通过加入位置编码来直接改进Transformer模型,从而达到更好的预测结果。在添加位置编码时,我们可以将其与token嵌入向量进行拼接,即在每个嵌入向量后面加上一个位置编码向量,这样就可以在训练中不断地更新模型中token的位置编码信息。代码可以参考如下:

import torch.nn as nn

# 在每个token嵌入向量后面加上位置编码
class TokenPositionEmbedding(nn.Module):
    def __init__(self, max_len, d_emb):
        super(TokenPositionEmbedding, self).__init__()
        self.pos_enc = nn.Embedding.from_pretrained(
            torch.from_numpy(get_pos_encoding_matrix(max_len, d_emb)),
            freeze=True)

    def forward(self, x):
        pos = torch.arange(x.size(1), device=x.device).expand(x.size(0), x.size(1)).to(x.dtype)
        return x + self.pos_enc(pos)

五、实现效果展示

我们在Transformer的机器翻译任务上运用了位置编码。我们使用了一个包括八层的Transformer,其中每个全连接层的维度为512,输入embedding维度为128,同时每个feed forward层的dropout比率为0.1。在WMT“English to German”数据集的翻译任务中,我们得到了50.95的BLEU分数(相对的,[Vaswani et al. (2017)](https://arxiv.org/abs/1706.03762)的论文中对于同样的问题,他们得到了28.4的BLEU分数)。

六、小结

通过本文,我们详细阐述了如何将位置编码有效地添加到Transformer中,并介绍了一个生成位置编码的简单方法。在实际的机器翻译任务中,我们取得了很好的效果。我们希望这篇文章能够为有需要的读者提供帮助,并进一步推动自然语言处理的发展。