WheatField
WheatField

Positional Encoding

September 30, 20221555 words, 8 min read
Authors

Positional Embedding 的概念很早就已经提出了,比如在 《Convolutional Sequence to Sequence Learning》 中提取了将绝对位置信息添加到输入中。在 transformer 被广为人知之后,大家对位置编码的关注度也越来越高。

position_embeddings_source

为什么需要位置编码?

Attention 模块无法捕捉输入顺序,无法区分不同位置的 token。对语言来说,句子中词汇的顺序和位置都是非常重要的。它们定义了语法,从而定义了句子的实际语义。在 Transformer 架构中,句子中的 token 流经 Transformer 的编码器、解码器堆栈,模型本身对每个单词没有任何位置信息的。如果一个模型没有位置信息,那么它将无法理解句子中单词的顺序。例如,"Tom likes apple, but hates orange" 和 "Tom hates orange, but likes apple" 两句话的意思是完全不同的。如果模型没有位置信息,那么它将无法理解这两句话的区别。因此,仍然需要一种方法将单词的顺序整合到模型中。想给模型一些位置信息,一个方案是在每个单词中添加一条关于它在句子中位置的信息。

针对这个问题,粗略的讲,有两个选择:

  1. 绝对位置编码 (absolute PE):将位置信息加入到输入序列中,相当于引入索引的嵌入。比如Sinusoidal, Learnable, FLOATER, Complex-order, RoPE。
  2. 相对位置编码 (relative PE):通过微调自注意力运算过程使其能分辨不同 token 之间的相对位置。比如XLNet, T5, DeBERTa, URPE。论文 《Self-Attention with Relative Position Representations》

绝对位置编码

Sinusoidal Positional Encoding

在论文 《Attention is All You Need》 中使用了三角函数进行位置编码。

Sinusoidal Positional Encoding

作者对 learned position embedding 和 sinusoidal position encoding 进行了对比实验,结果没有明显差异。出于对长度外推性和参数量规模的考虑,最终选择了 sinusodial 版本。

Transformer 的位置编码层

给定长度为 LL 的序列,第 kk 个符号的位置编码为

PE(pos,2i)=sin(pos/100002i/dmodel)PE(pos, 2i)=\sin(pos/10000^{2i/d_{model}})

PE(pos,2i+1)=cos(pos/100002i/dmodel)PE(pos, 2i+1)=\cos(pos/10000^{2i/d_{model}})

其中 pos 是实体的位置,ii 是维度的索引,dmodeld_\text{model} 是嵌入层的维度。

在位置编码中除以 100002i/dmodel10000^{2i/d_{model}} 是为了在位置编码的计算中引入一个缩放因子,帮助控制位置编码的数值范围,确保位置编码中的数值在不同维度上具有相对不同的变化率。

编码实现

import numpy as np
def get_position_encoding(seq_len:int, d:int, n=10000):
    """ Get position encoding for a sequence of length seq_len and embedding dimension d.
    """
    pe = np.zeros((seq_len, d))
    for k in range(seq_len):
        for i in np.arange(int(d / 2)):
            denominator = np.power(n, 2 * i / d)
            pe[k, 2 * i] = np.sin(k / denominator)
            pe[k, 2 * i + 1] = np.cos(k / denominator)
    return pe

相对位置编码(RPE, Relative Positional Encoding)

并不直接建模每一个输入标记的位置信息,而是通过相对位置编码来建模。相对位置编码的思想是,每个输入标记的位置信息可以通过它与其他标记的相对位置来表示。例如,对于一个长度为 LL 的序列,第 kk 个标记的位置信息可以通过它与第 k1k-1 个标记的相对位置来表示。

《Self-Attention with Relative Position Representations》 的做法是计算 attention score 和 weighted value 时各加入一个可训练的表示相对位置的参数,并且 multi head 之间可以共享。

zi=j=1Lαij(xjWV+γijV)z_i = \sum_{j=1}^L \alpha_{ij} (x_j W^V + \gamma_{ij}^V)

其中

αij=expeijk=1nexpeik\alpha_{ij} = \frac{\exp{e_{ij}}}{\sum_{k=1}^n \exp{e_{ik}}}

为权重系数(attention score),

eij=(xiWQ)(xjWK+γijK)Tdze_{ij}= \frac{(x_i W^Q)(x_j W^K + \gamma_{ij}^K)^T}{\sqrt{d_z}}

是两个输入xi,xjx_i, x_j的缩放点集,即缩放后的相关性。

论文把输入序列建模成一个有向全连接图,每一个 token 都是图中的一点。两个 token 之间的边的权重是两个 token 的相对位置编码。

γijK=wclipK(ji,k)\gamma_{ij}^K = w_{\text{clip}}^K(j-i, k)

其中 clip(a,b)=max(min(a,b),b)\text{clip}(a, b)=\max(\min(a, b), -b) 表示将 aa 限制在 [b,b][-b, b] 之间,也即是 γijK\gamma_{ij}^K 的取值范围被限制在 (wkK,...,wkK)(w_{-k}^K,..., w_{k}^K) 之间。作者的假设是,token 只需要知道一定范围内的相对位置编码即可,过长是没有必要的。因此,模型只需学习 2k+12k+1 个相对位置编码。

前面也提到了相对位置的参数中多头共享的,这样做可以减少参数量,参数空间从 hn2dhn^2d 可以减少到 n2dn^2d,其中 hh 是 head 数量,nn 是序列长度,dd 是 embedding 维度。

FAQ

  1. Transformer 如何区分出来 PE (positional encoding) 与 WE (word embedding) 的,为什么不使用 concatenate 而是 sum 呢?

A: PE 向量在前面一些维度上数值变化很大,但是在后面的维度上数值接近于非 0 即 1(见下图-source),一种解释是 transformer 在学习过程中,WE 在前面这部分维度上没有编码重要信息,e.g., 词的语义,从这个角度上来看,这跟 concatenate 没有本质区别,但维度更小。

positional_encoding

另一种解释是,在高维空间中,PE, WE 是近似正交的。这种情况下,模型可以学习到 PE, WE 的线性组合,而不是简单的 concatenate。上一种解释是这种解释的特例,PE,WE 分别在部分维度上的数值接近于 0,1,这种情况下,PE, WE 可以是近似正交的。

参考:

  1. BERT 是自学习的吗?直接写死效果效果如何? BERT 使用的是可学习的 positional embedding。

写死的外推性不好。例如,每个位置都按 0, 1, 2, ..., 进行编码,如果序列长度非常长,那么 PE 在数值上占导地位,跟 WE 合并之后,对模型有一定干扰。

  1. PE 跟 positional embedding 区别?
  • 构造上,PE 是硬编码,positional embedding 是可学习的参数。
  1. 还有哪些位置编码的方法?

参考