Transformer 架构中的位置编码

Transformer 模型需要位置编码来处理序列数据,因为其核心机制——自注意力(Self-Attention)本身并不具备捕捉序列中元素位置信息的能力。自注意力机制允许模型在计算一个元素的表征时考虑到序列中的所有元素,但是它对这些元素是如何排序的一无所知。这与传统的序列处理模型(如循环神经网络 RNN 和长短期记忆网络 LSTM)不同,后者通过逐个元素的递归处理天然地编码了位置信息。

Transformer 模型的设计理念是完全摒弃递归处理,从而允许并行计算,这极大地提高了训练的效率。然而,这也意味着模型丧失了捕捉序列中位置关系的能力。为了解决这个问题,Transformer 引入了位置编码(Positional Encoding)作为补充信息,确保模型能够利用序列中元素的位置信息。

绝对位置编码

绝对位置编码比较简单,研究者一般会将绝对位置信息加到输入中:在输入的第 k 个向量 x k x_k xk 中加入位置向量 p k p_k pk 得到 x k + p k x_k + p_k xk+pk,其中 p k p_k pk 仅与 k 相关。计算 p k p_k pk 的方法一般有两种:

  • 训练式:将位置向量 P 设置为可训练的参数,如 BERT 就将位置向量初始化为 512 x 768 的矩阵,并在训练中更新。但该方法不具备外推性,预训练时设定了句子最大长度后,就无法处理超过该长度的句子。
  • 编码式:《Attention is all you need》中提出的 Sinusoidal 位置编码, p i , 2 t p_{i, 2t} pi,2t 表示位置 k 的向量的第 2i 个分量,d 表示向量维度,它显式地编码了位置信息,且具有一定的外推性。

训练式位置编码

训练式位置编码,顾名思义就是每个位置的位置向量会随着模型一起训练。假设模型最大输入长度为 512,向量维度为 768,我们可以初始化一个 [512, 768] 的位置编码矩阵,该矩阵将参与模型的训练,从而学习得到每个位置所对应的向量表征。

问题:如何为每个位置的词向量注入位置信息呢?

答案:将词向量和位置向量相加即可,q 是词向量, p m p_m pm 是位置向量。

q m = f ( q , m ) = q + p m q_m = f(q, m) = q + p_m qm=f(q,m)=q+pm

训练式位置编码广泛应用于早期的 Transformer 类型的模型,如 BERT、GPT、ALBERT 等。但其缺点是模型不具有长度外推性,因为位置编码矩阵的大小是预设的,若对其进行扩展,将会破坏模型在预训练阶段学习到的位置信息。例如,将 [512, 768] 扩展为 [1024, 768],新扩展的 512 个位置向量缺少训练,无法正确表示 512 ~ 1023 的位置信息。

Sinusoidal 位置编码

Sinusoidal 位置编码是 Google 在论文《Attention is all you need》中提出的一种绝对位置编码。它的形式如下,其中 d 表示词向量的维度,k 表示位置索引,2i 和 2i + 1 表示位置向量的分量索引,例如 P k , 2 i P_{k, 2i} Pk,2i p k , 2 i + 1 p_{k,2i+1} pk,2i+1 分别表示位置 k 的位置向量的第 2i 和第 2i + 1 个分量。

{ p k , 2 i = sin ⁡ ( k / 1000 0 2 i / d ) p k , 2 i + 1 = cos ⁡ ( k / 1000 0 2 i / d ) \left\{\begin{array}{l}\boldsymbol{p}_{k, 2 i}=\sin \left(k / 10000^{2 i / d}\right) \\ \boldsymbol{p}_{k, 2 i+1}=\cos \left(k / 10000^{2 i / d}\right)\end{array}\right. {pk,2i=sin(k/100002i/d)pk,2i+1=cos(k/100002i/d)

从数学形式来看,这是一个具有周期性的三角函数式位置编码,每个分量都是正弦或余弦函数,所以每个分量的数值都具有周期性,并且越靠后的分量,波长越长,频率越低。

Sinusoidal 位置编码具有远程衰减的性质,具体表现为:对于两个相同的词向量,如果它们之间的距离越近,则它们的内积分数越高,反之则越低。如下图所示,随机初始化两个向量 q 和 k,将 q 固定在位置 0 上,k 的位置从 0 开始逐步变大,依次计算 q 和 k 之间的内积。可以发现,随着 q 和 k 之间的相对距离增加,它们之间的内积分数震荡衰减。

相对位置编码

绝对位置编码计算 Attention 矩阵,m 和 n 分别表示两个位置。

q m = W q ( x m + p m ) k n = W k ( x n + p n ) (1) q_m = W_q(x_m + p_m) \quad k_n = W_k(x_n + p_n) \tag{1} qm=Wq(xm+pm)kn=Wk(xn+pn)(1)

q m k n T = W q x m x n T W k T + W q x m p n T W k T + W q p m x n T W k T + W q p m p n T W k T (2) q_mk_n^T = W_q x_m x_n^T W_k^T + W_q x_m p_n^T W_k^T + W_q p_m x_n^T W_k^T + W_q p_m p_n^T W_k^T \tag{2} qmknT=WqxmxnTWkT+WqxmpnTWkT+WqpmxnTWkT+WqpmpnTWkT(2)

可以看到公式(2)中,第一项与位置信息无关,第二项至第四项和位置信息相关。因此,研究者通常是直接修改第二项至第四项的内容,直接在 attention 矩阵中添加相对位置信息。常见的有以下几种方式:

  • XLNET 式:将第二项至第四项都做了改变,将 p n p_n pn 替换成 sinusoidal 生成式编码 R ^ n − m \hat{R}_{n-m} R^nm,将 p m p_m pm 换成两个可以训练的向量 u 和 v。

    q m k n T = W q x m x n T W k T + W q x m R ^ n − m T W k T + W q u x n T W k T + W q v R ^ n − m T W k T (3) q_mk_n^T = W_q x_m x_n^T W_k^T + W_q x_m \hat{R}_{n-m}^T W_k^T + W_q u x_n^T W_k^T + W_q v \hat{R}_{n-m}^T W_k^T \tag{3} qmknT=WqxmxnTWkT+WqxmR^nmTWkT+WquxnTWkT+WqvR^nmTWkT(3)

  • T5 式:这篇论文的作者认为输入和位置间不应过多的交互,因此将第二项和第三项删除,将第四项替换为一个可学习的偏置 b m , n b_{m, n} bm,n,在 attention 矩阵的基础上加一个可训练的偏置项。

    q m k n T = W q x m x n T W k T + b m , n (4) q_m k_n^T = W_q x_m x_n^T W_k^T + b_{m, n} \tag{4} qmknT=WqxmxnTWkT+bm,n(4)

  • DeBerta 式:和 T5 的构造相反,它舍弃了第四项,保留了第二项和第三项,并将位置信息替换成了相对位置编码 R ^ n − m \hat{R}_{n-m} R^nm

    q m k n T = W q x m x n T W k T + W q x m R ^ n − m T W k T + W q R ^ n − m x n T W k T (5) q_mk_n^T = W_q x_m x_n^T W_k^T + W_q x_m \hat{R}_{n-m}^T W_k^T + W_q \hat{R}_{n-m} x_n^T W_k^T \tag{5} qmknT=WqxmxnTWkT+WqxmR^nmTWkT+WqR^nmxnTWkT(5)

Attention 的核心运算是内积,所以我们希望经过内积的结果能够带有相对信息。也就是说 q m q_m qm k n k_n kn 的内积仅与输入 x m x_m xm x n x_n xn 和它们的相对位置 m - n 有关,那么我们可以假设存在函数 g,使得:

q m k n T = g ( x m , x n , m − n ) q_m k_n^T = g(x_m, x_n, m - n) qmknT=g(xm,xn,mn)

此时,我们的目标就是找到这个函数 g,具体方法可参考下文的“相对位置编码方法”。

相对位置编码方法

关于相对位置编码方法,后续会将相应的博客粘贴在此处。

Q&A

请问什么是可学习位置编码,它是如何工作的?

可学习位置编码(Learnable Positional Encoding)允许模型在训练过程中学习位置编码。在这种方法中,每个位置都关联一个可学习的参数向量(即位置向量),这些位置向量作为模型参数的一部分,通过反向传播和梯度下降等优化算法进行调整。这样,模型可以根据任务的具体需求自适应地学习位置信息。

可学习位置编码(训练式位置编码)的工作原理如下:

  1. 初始化:在模型初始化时,为序列中的每个可能位置创建一个位置向量(类似于词嵌入中的单词向量),这些位置向量是随机初始化的。
  2. 嵌入加和:在处理输入序列时,将每个元素的词嵌入与其对应位置的位置向量相加,以此来融合位置信息和内容信息。例如,在 BERT 中,将词向量、位置向量和 Segment 向量三者相加
  3. 训练更新:在模型训练过程中,通过反向传播来更新位置向量,从而使模型能够学习到最佳的位置编码。
  4. 序列处理:经过位置编码的序列数据输入到模型中,模型通过自注意力机制以及后续的前馈神经网络处理这些数据,位置编码帮助模型理解元素间的相对或绝对位置。
解释 Transformer 中位置编码的维度与模型维度(d_model)的关系

位置编码通常是一个与序列元素同等长度的向量,它与序列元素的嵌入向量(Embedding Vector)进行逐元素相加操作,从而将位置信息融入到元素的表示中。为了完成这个操作,位置编码的维度必须与模型的嵌入维度(也称为模型维度或d_model)相同。

d_model 是Transformer模型中所有子层和嵌入层输出的特征维度。这个维度是模型设计时确定的一个超参数。例如,在原始的Transformer模型中,d_model 被设置为512。这意味着嵌入层会将输入词汇映射到一个512维的空间中,所有的编码器和解码器层也会处理这样维度的特征。

位置编码可以采用不同的方式生成,最常见的方法是使用正弦和余弦函数交替来计算每个位置的位置编码,如下所示:

P E ( p o s , 2 i ) = sin ⁡ ( p o s 1000 0 2 i / d model ) PE_{(pos, 2i)} = \sin\left(\frac{pos}{10000^{2i/d_{\text{model}}}}\right) PE(pos,2i)=sin(100002i/dmodelpos)

P E ( p o s , 2 i + 1 ) = cos ⁡ ( p o s 1000 0 2 i / d model ) PE_{(pos, 2i+1)} = \cos\left(\frac{pos}{10000^{2i/d_{\text{model}}}}\right) PE(pos,2i+1)=cos(100002i/dmodelpos)

其中, p o s pos pos 是序列中元素的位置(从0开始), i i i 是维度的索引, d model d_{\text{model}} dmodel 是模型的维度。这些函数的周期性质使得模型能够学会区分不同位置的元素。

在实际操作中,我们会预先计算好一个足够长的位置编码矩阵,并根据实际序列长度从中截取相应长度的编码。由于位置编码矩阵的维度是 [序列长度, d_model],它可以与任何长度相同、维度为d_model的序列嵌入矩阵相加,因而位置信息便被整合到了序列嵌入中。这种整合使得Transformer可以处理有序的序列数据。

例如,在 PyTorch 中为 Transformer 模型添加固定位置编码。

import torch
import math
import torch.nn as nn

class PositionalEncoding(nn.Module):
    def __init__(self, d_model, max_len=5000):
        super(PositionalEncoding, self).__init__()

        # 创建一个长为max_len,宽为d_model的位置编码矩阵
        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)
        
    def forward(self, x):
        # 将位置编码与输入相加
        x = x + self.pe[:x.size(0), :]
        return x
        
# 假设我们的模型维度d_model为512
d_model = 512
max_len = 1000
pos_encoder = PositionalEncoding(d_model, max_len)
        
# 假设输入的嵌入维度是 (seq_len, batch_size, d_model)
input_embed = torch.randn(50, 32, d_model) # 示例输入
        
# 添加位置编码
encoded_input = pos_encoder(input_embed)

在这个例子中,PositionalEncoding 类首先在构造函数中计算了一个固定的位置编码矩阵,并将其作为模型的一个内部状态。在前向传播方法 forward 中,它将这个位置编码添加到输入的嵌入向量上。需要注意的是,这个实现假设输入的维度是 (seq_len, batch_size, d_model),这是Transformer模型常用的输入格式。

为什么相对位置编码具有外推性?

相对位置编码具有外推性(extrapolation ability)的原因在于它不是将位置信息编码为与序列中的绝对位置直接相关的固定模式,而是将注意力权重或其他模型组件与元素之间的相对距离联系起来。这样,即使在训练时未见过的更长的序列中,模型仍然能够根据学到的相对位置模式进行推断。以下是相对位置编码具有外推性的一些原因:

  1. 相对距离的一致性:在相对位置编码中,相对距离是一致的,无论元素在序列中的绝对位置如何。因此,模型可以学习到“两个单词相隔一个位置”这样的模式,并将其应用于序列的任何部分。
  2. 长度不变性:由于相对位置编码关注的是相对距离而非绝对位置,因此它天然不受输入序列长度的影响。这使得模型能够处理比训练时见过的序列更长的序列,而不会丧失对位置关系的理解。
  3. 泛化能力:相对位置编码使得模型更容易泛化到不同的上下文中,因为它依赖于元素之间的相对关系,而这些关系在不同的文本中是普遍存在的。
  4. 动态编码:在某些实现中,相对位置信息是动态计算的,而不是像传统的绝对位置编码那样是固定的。这意味着模型可以根据需要为任何给定的序列位置对生成编码,从而提供更大的灵活性和外推能力。
  5. 适应性强:相对位置编码通常使模型能够更好地适应序列操作,如切割、拼接或排序,因为这些操作不会影响元素之间的相对位置关系。
  • 10
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值