|
在自然语言处理 (NLP) 领域中,Transformer 模型从根本上重塑了我们处理序列到序列任务的方法。然而,与传统的递归神经网络 (RNN) 或卷积神经网络 (CNN) 不同,Transformer 缺乏对令牌顺序的固有感知。在本篇文章中,我们将了解到位置编码(Positional Encoding)在Transformer 模型中的重要性,它是把序列顺序嵌入 Transformer 模型的关键技术。
什么是位置编码,在模型的输入输出时,为什么我们首先需要它呢? 首先,我们明白单词的位置和顺序是任何语言的重要组成部分。它们定义了句子的语法,从而定义了句子的实际语义。递归神经网络 (RNN)本质上会考虑单词的顺序,他们按顺序逐字解析句子。这会将单词的顺序集成到 RNN 的主干中。 但是 Transformer 架构放弃了递归机制,转而采用多头自注意力机制。避免 RNN 的递归方法将导致训练时间的大幅加快。从理论上讲,它可以在句子中捕获更长的依赖项。 由于句子中的每个单词同时流经 Transformer 的编码器/解码器堆栈,因此模型本身对每个单词没有任何位置和顺序感。因此,仍然需要一种方法将单词的顺序合并到我们的模型中。位置编码的引入就是为了解决这个问题,它为每个词加入位置信息,让模型知道每个词在句子中的位置。
让我们通过一个简单的例子来说明Transformer框架中位置编码的作用: 假设我们有一个英文句子:"The cat sat on the mat",并且我们使用Transformer模型来处理这个句子。Transformer模型的输入是一个词嵌入(word embedding)的序列,其中每个词都被转换成了一个固定维度的向量。如果没有位置编码,模型将无法区分这些词的顺序,因为它的自注意力机制在理论上是无序的。 步骤1:词嵌入 首先,我们将句子中的每个词转换成词嵌入向量。假设每个词嵌入的维度是4维(实际中通常是几百维),我们得到以下6个词嵌入向量(每个词一个): The:[0.25,0.50,0.75,1.00]cat:[0.60,0.40,0.20,0.00]sat:[0.90,0.10,0.30,0.70]on:[0.45,0.55,0.65,0.35]the:[0.25,0.50,0.75,1.00]mat:[0.80,0.20,0.10,0.90]
步骤2:添加位置编码 接下来,我们为每个词嵌入向量添加位置编码。假设我们使用正弦/余弦位置编码,对于4维的词嵌入,我们可能得到以下位置编码: Position1:[0.000,0.100,0.200,0.300](The的位置编码)Position2:[0.100,0.200,0.000,0.100](cat的位置编码)Position3:[0.200,0.000,0.100,0.200](sat的位置编码)Position4:[0.300,0.100,0.200,0.000](on的位置编码)Position5:[0.400,0.000,0.300,0.100](the的位置编码)Position6:[0.500,0.100,0.000,0.300](mat的位置编码)
步骤3:结合词嵌入和位置编码 我们将每个词嵌入与其对应的位置编码相加,得到最终的输入向量: The:[0.25+0.000,0.50+0.100,0.75+0.200,1.00+0.300]=[0.25,0.60,0.95,1.30]cat:[0.60+0.100,0.40+0.200,0.20+0.000,0.00+0.100]=[0.70,0.60,0.20,0.10]sat:[0.90+0.200,0.10+0.000,0.30+0.100,0.70+0.200]=[1.10,0.10,0.40,0.90]on:[0.45+0.300,0.55+0.100,0.65+0.200,0.35+0.000]=[0.75,0.65,0.85,0.35]the:[0.25+0.400,0.50+0.000,0.75+0.300,1.00+0.100]=[0.65,0.50,1.05,1.10]mat:[0.80+0.500,0.20+0.100,0.10+0.000,0.90+0.300]=[1.30,0.30,0.10,1.20]
步骤4:自注意力机制 在自注意力机制中,模型会计算每个词与其他所有词的注意力分数。由于我们已经为每个词添加了位置编码,模型现在可以区分不同词的位置,并据此计算注意力分数。例如,"cat"这个词的编码会告诉模型它紧随"The"之后,而"sat"则在"cat"之后,这种顺序信息对于理解句子结构至关重要。 步骤5:输出 经过多层Transformer处理后,模型最终输出每个词的表示,这些表示包含了词义和它们在句子中的位置信息,这对于后续的任务(如翻译、文本摘要等)是非常重要的。 通过以上例子,我们可以看到位置编码在Transformer模型中的作用是为模型提供词序信息,使得模型能够捕捉到句子的结构和语义,这对于处理序列数据是非常关键的。
以下是使用PyTorch创建Transformer模型中的位置编码的代码示例。这里我们将使用正弦和余弦函数来生成位置编码。import torchimport torch.nnas nnimport mathclass PositionalEncoding(nn.Module): def__init__(self, d_model, max_len=5000): super(PositionalEncoding, self).__init__() # 创建一个足够大的位置编码矩阵,以避免位置超出范围 pe = torch.zeros(max_len, d_model) position = torch.arange(0, max_len, dtype=torch.float).unsqueeze(1) div_term = torch.exp(torch.arange(0, d_model,2).float() * (-math.log(10000.0) / d_model)) pe[:,0::2] = torch.sin(position * div_term) pe[:,1::2] = torch.cos(position * div_term) pe = pe.unsqueeze(0).transpose(0,1) # 将位置编码矩阵注册为模型的缓冲区,这样它就不会被视为模型参数 self.register_buffer('pe', pe) defforward(self, x): # 将位置编码添加到输入的嵌入向量中 x = x + self.pe[:x.size(0), :] return x
# 假设我们的词嵌入维度是512,最大序列长度是100d_model =512max_len =100positional_encoding =PositionalEncoding(d_model, max_len)
# 假设我们有一个随机生成的词嵌入张量,形状为[seq_len, batch_size, d_model]seq_len, batch_size =60,32x = torch.randn(seq_len, batch_size, d_model)
# 将位置编码添加到词嵌入中x =positional_encoding(x)print(x.shape) # 输出的形状应该是[seq_len, batch_size, d_model]
在这段代码中,我们定义了一个`PositionalEncoding`类,它继承自`nn.Module`。这个类的构造函数接受模型的维度`d_model`和最大序列长度`max_len`。在`forward`方法中,我们将位置编码添加到输入的词嵌入张量`x`中。 请注意,这个位置编码是可学习的,但在实际的Transformer模型中,位置编码通常是固定的,不参与训练。这里为了简化,我们将位置编码作为模型的一部分,但在实际应用中,你可能会将位置编码作为一个固定的张量添加到词嵌入中。
|