Skip to content

位置编码

卷积具有局部性,天然地注意到了元素之间的相对位置。而基于自注意力的Transformer模型则对位置不敏感,因此必须要把元素的位置信息加入到嵌入向量中。

绝对位置编码

绝对位置编码直接将位置信息加到文本的嵌入向量中,被早期的BERT、GPT-2等模型中使用。绝对位置编码又可以分为可学习的和固定的两种。

  • 可学习绝对位置编码:直接对不同的位置随机初始化一个位置嵌入(Position Embedding),加到文本的嵌入向量上输入给模型,作为参数进行训练。这种方法引入了大量的可学习参数,需要大量的数据才能训练。

  • 固定的绝对位置编码:代表是《Attention is All You Need》论文中的三角位置编码。

位置pos对应的位置向量在偶数位和奇数位的值分别为:

PE(pos,2i)=sin(pos100002i/dmodel)PE(pos,2i+1)=cos(pos100002i/dmodel)

其中dmodel是位置编码的长度,i[0,1,...,(dmodel1)/2]。采用这种设计,pos+k位置的位置编码可以用pos位置的位置编码线性表示,体现了其相对位置关系。

这里的相对位置关系指的是两个Token之间的,而绝对位置编码中每个位置的编码是固定的。相对位置编码则直接考虑两个Token之间的相对位置。

证明:需要用到三角公式,定义wi=1100002i/dmodel

PE(pos+k,2i)=sin(wi(pos+k))=sin(wipos)cos(wik)+cos(wipos)sin(wik)PE(pos+k,2i+1)=cos(wi(pos+k))=cos(wipos)cos(wik)sin(wipos)sin(wik)PE(pos+k,2i)=cos(wik)PE(pos,2i)+sin(wik)PE(pos,2i+1)PE(pos+k,2i+1)=cos(wik)PE(pos,2i+1)sin(wik)PE(pos,2i)

为了计算pos+kpos之间的距离,可以通过计算它们之间的内积:

PEposPEpos+k=i=0d/21sin(wipos)sin(wi(pos+k))+cos(wipos)cos(wi(pos+k))=i=0d/21cos(wi(pos(pos+k)))=i=0d/21cos(wik)

可以看到pos+kpos之间的内积随着相对距离的增加而减小,符合文本中Token之间一般距离越远关系越弱的原理。但是由于相对距离的对称性,三角位置编码无法区分方向,即pos+kposposkpos之间的距离是一样的。实现三角位置编码的代码如下:

python
class PositionalEncoding(nn.Module):
    def __init__(self, d_model, dropout, max_len=5000):
        super().__init__()
        self.dropout = nn.Dropout(p=dropout)  # 初始化dropout层
        
        # 计算位置编码并将其存储在pe张量中
        pe = torch.zeros(max_len, d_model)                # 创建一个max_len x d_model的全零张量
        # 生成0到max_len-1的整数序列,并添加一个维度
        position = torch.arange(0, max_len).unsqueeze(1)
        # 计算div_term,用于缩放不同位置的正弦和余弦函数
        div_term = torch.exp(torch.arange(0, d_model, 2) *
                             -(math.log(10000.0) / d_model))
        # 对于d_model的偶数索引,使用正弦函数;对于奇数索引,使用余弦函数
        pe[:, 0::2] = torch.sin(position * div_term)
        pe[:, 1::2] = torch.cos(position * div_term)
        pe = pe.unsqueeze(0)                  # 在第一个维度添加一个维度,以便进行批处理
        
    # 定义前向传播函数
    def forward(self, x):
        # 将输入x与对应的位置编码相加
        x = x + self.pe[:, : x.size(1)]
        # 应用dropout层并返回结果
        return self.dropout(x)

总结:绝对位置编码实现简单,存在以下缺点:

  • 尽管能包含一定的相对位置信息,但是这种信息仅仅保存在位置编码内部,在计算自注意力时,这种位置信息就被破坏了。

  • 一个Token的位置编码是什么由其在句子中的绝对位置决定,但是真正重要的往往不是绝对位置,而是它与其他Token之间的关系。

  • 对输入的长度敏感,一旦输入变化则需要重新调整。

相对位置编码

相对位置编码将两个Token的相对位置信息添加到对应的注意力值中。

ALiBi(Attention with Linear Biases)

在计算注意力时,对前边位置的分数进行惩罚,如图所示:

Image

  • 传统的绝对位置编码在训练时会为每个位置分配一个固定的向量,模型可能会过度拟合这些特定长度的模式。而ALiBi通过在注意力分数计算中直接使用线性偏置,减少了模型对特定序列长度的依赖,从而提高了对未见过的序列长度的泛化能力。

  • ALiBi的位置偏差随距离线性增长,这种设计让模型在处理不同长度的序列时,可以自然地根据距离调整注意力权重,无需显式学习位置编码的复杂周期性结构。

  • 由于线性偏置的引入直接与序列中元素的位置相关,没有固定大小的编码矩阵限制,理论上模型可以更容易地处理任意长度的序列,从而展现出良好的长度外推性能。

  • 通过直接在注意力分数上施加与距离相关的线性惩罚,ALiBi鼓励模型关注更近的位置,同时不完全排除远处的依赖,从而在一定程度上平衡了局部和全局依赖的学习,这对于处理长序列尤其有利。

XLNET

三角位置编码在计算注意力时的表示如下:

At,s=qtTks=(xt+pt)TWQTWK(xs+ps)At,s=xtTWQTWKxs+xtTWQTWKps+ptTWQTWKxs+ptTWQTWKpspt=[,sint100002n/d,cost100002n/d,]TRd

从绝对位置编码出发:

qiTkj=xiWQTWKxj+xiWQTWKpjT+piWQTWKxjT+piWQTWKpjT

XLNET将pj替换成相对位置向量Rijpi替换成可训练的向量uv

xiWQTWKxjT+xiWQTWKRijT+uWQTWKxjT+vWQTWKRijTrts=[,sints100002n/d,costs100002n/d,]TRd

T5

(11)式中的每一项可以理解为"输入-输入"、"输入-位置"、"位置-输入"、"位置-位置"四项注意力的组合。由于输入信息与位置信息应该是独立(解耦)的,它们不应该有过多的交互,所以"输入-位置"、"位置-输入"两项注意力可以删掉,"位置-位置"实际上是一个依赖于(t,s)的一个标量。

此外,通过固定的桶函数b(ts),将ts[128,128]压缩至[0,31],再对每个b(ts)训练对应的偏移量rb(ts)

At,s=xtTWQTWKxs+rb(ts)

DeBERTa

与T5相反,扔掉"位置-位置"一项,只保留剩下三项。通过δ(t,s)ts直接截断在区间(k,k]内。

At,s=xtTWQTWKxs+xtTWQTWKPδ(t,s)+xsTWKTWQPδ(s,t)

DeBERTa在Softmax时校正系数为3d,不是默认的d。此外,指出NLP的大多数任务可能都只需要相对位置信息,但确实有些场景下绝对位置信息更有帮助,于是它将整个模型分为两部分来理解。以Base版的MLM预训练模型为例,它一共有13层,前11层只使用相对位置编码,这部分称为Encoder,后面2层加入绝对位置信息,这部分它称之为Decoder;至于下游任务的微调阶段,则是使用前11层的Encoder加上1层的Decoder来进行。

旋转位置编码 RoPE

RoPE实现了绝对位置编码和相对位置编码的统一,它通过绝对位置编码的形式,实现了相对位置编码的效果。

RoPE将输入序列的位置信息通过旋转操作嵌入到自注意力的计算中,为不同位置的Token分配差异化旋转角度,使位置信息与Token语义特征深度融合,显著增强模型对长序列的建模能力及对相对位置关系的敏感度。RoPE的频率(base)是可学习的,在自注意力公式中结合了明确的相对位置依赖性。

RoPE保持了序列长度的灵活性、随相对距离的增加而衰减的Token间依赖性。其原理如下图,针对词嵌入维度dmodel为2的情况,xm表示经过RoPE后的结果:

Image

从内积的角度推导

xm=Wqxmeimθ=(Wqxm)eimθ=qmeimθxn=Wkxneinθ=(Wkxn)einθ=kneinθxmTxn=(qm1qm2)(cos((mn)θ)sin((mn)θ)sin((mn)θ)cos((mn)θ))(kn1kn2)

其中 qm=(Wq11Wq12Wq21Wq22)(xm1xm2)=(qm1qm2)

二维向量可以表示成虚数形式:qm=qm1+iqm2

由欧拉公式eimθ=cos(mθ)+isin(mθ),且i2=1

qmeimθ=(qm1+iqm2)(cos(mθ)+isin(mθ))=(qm1cos(mθ)qm2sin(mθ))+i(qm2cos(mθ)+qm1sin(mθ))

再写成向量形式:

=[qm1cos(mθ)qm2sin(mθ),qm2cos(mθ)+qm1sin(mθ)]=(cos(mθ)sin(mθ)sin(mθ)cos(mθ))(qm1qm2)

代入到内积中:

xmTxn=((cos(mθ)sin(mθ)sin(mθ)cos(mθ))(qm1qm2))T(cos(nθ)sin(nθ)sin(nθ)cos(nθ))(kn1kn2)=(qm1qm2)(cos(mθ)sin(mθ)sin(mθ)cos(mθ))(cos(nθ)sin(nθ)sin(nθ)cos(nθ))(kn1kn2)=(qm1qm2)(cos((mn)θ)sin((mn)θ)sin((mn)θ)cos((mn)θ))(kn1kn2)

对于多维的旋转位置编码,可以简化为以下形式:

可以看到矩阵中有很多元素是0,如果用矩阵乘法,其实有很多计算是无用的。

(cosmθ0sinmθ00000sinmθ0cosmθ0000000cosmθ1sinmθ10000sinmθ1cosmθ1000000cosmθd/21sinmθd/210000sinmθd/21cosmθd/21)(q0q1q2q3qd2qd1)(q0q1q2q3qd2qd1)(cosmθ0cosmθ0cosmθ1cosmθ1cosmθd/21cosmθd/21)+(q1q0q3q2qd1qd2)(sinmθ0sinmθ0sinmθ1sinmθ1sinmθd/21sinmθd/21)θi=100002i/d,i[1,2,...,d2]

d一定是偶数。

结合代码来看,在ChatGLM中,因为内积计算与顺序无关,巧妙地将所有负数和正数分开。

Image

从公式的角度推导

要计算mn之间的距离,假设查询向量qm和键向量kn之间的内积可以用函数g(xm,xn,mn)表示,函数g(xm,xn,mn)的定义如下:

g(xm,xn,mn)=Re[(Wqxm)(Wkxn)ei(mn)θ]

表示共轭复数:kn=kn1+ikn2kn=kn1ikn2

由欧拉公式ei(mn)θ=cos((mn)θ)+isin((mn)θ)

=Re[qmknei(mn)θ]=Re[(qm1+iqm2)(kn1ikn2)(cos((mn)θ)+isin((mn)θ))]=Re[((qm1kn1+qm2kn2)+i(qm2kn1qm1kn2))(cos((mn)θ)+isin((mn)θ))]=Re[((qm1kn1+qm2kn2)cos((mn)θ)(qm2kn1qm1kn2)sin((mn)θ)+(qm1kn1+qm2kn2)isin((mn)θ)+i(qm2kn1qm1kn2)cos((mn)θ)]

不要复数,只保留实数部分:

=(qm1kn1+qm2kn2)cos((mn)θ)(qm2kn1qm1kn2)sin((mn)θ)

总结下RoPE的流程:首先计算得到QK矩阵,然后对QK向量的元素按顺序,两两一组应用RoPE,例如:

f(x,m)=[(x0+ix1)eimθ0,(x2+ix3)eimθ1,,(xd2+ixd1)eimθd/21]

那么对于mn之间的考虑位置距离的注意力就是:

a(m,n)=Ref(q,m),f(k,n)=Re[j=0d/21(q2j+iq2j+1)(k2jik2j+1)ei(mn)θj]=j=0d/21(q2jk2j+q2j+1k2j+1)cos((mn)θj)+(q2jk2j+1q2j+1k2j)sin((mn)θj)

得到的注意力随mn之间的距离增大而减小,注意对V矩阵不需要应用RoPE

改进:xPOS

RoPE能扩展到任意长度,但其外推性能较差:虽然RoPE可以拓展到任意长度,但对于语言建模等生成任务,无法在测试长序列性能时,维持其在训练长度序列上的表现。xPos在旋转的基础上,在旋转角度向量的每个维度上都包含了独特的指数衰减因子,以及Blockwise Causal Attention,让模型忽略相距较远的语义:

At,s=Re[n=1d/2(ζnteitθnq~t(n))ζnseisθnk~s(n)]=n=1d/2Re[q~t(n)k~s(n)ζnstei(st)θn]ζn=1+γ2n/d+γ,γ=0.4,θn=100002n/d