文本是时序型数据,词与词之间的顺序关系往往影响整个句子的含义。这里我整理了一些顺序不同,含义不同的例子

传统的RNN模型在处理句子时,以序列的模式逐个处理句子中的词语,这使得词语的顺序信息在处理过程中被天然的保存下来了,并不需要额外的处理。

而对于Transformer来说,由于句子中的词语都是同时进入网络进行处理,顺序信息在输入网络时就已丢失。因此,Transformer是需要额外的处理来告知每个词语的相对位置的。论文《Attention is all you need》https://arxiv.org/abs/1706.03762 中提到的一个Positional Encoding(位置编码)公式如下,它能将表示位置信息的编码添加到输入中,让网络知道每个词的位置和顺序。

位置向量公式

Transformer的作者设计了一种三角函数位置编码方式,为每个不同位置的单词(token)单独生成一个位置向量(或者叫位置嵌入,即position embedding,缩写为PE)。

下面通过公式理解三角函数式位置编码:

理解Transformer的位置编码_词向量

从上面公式中可以看出:

理解Transformer的位置编码_编码方式_02

为什么位置编码也是向量?

在Transformer中,每个词向量都是一个固定长度的向量,而位置编码是为了在不同位置的词向量之间加入位置信息,以便模型能够更好地理解输入序列的顺序。

理解Transformer的位置编码_三角函数_03

于是多维位置编码是一个与词向量具有相同长度的向量,它的每个元素都是一个实数,用于表示该位置的位置信息。例如,对于一个长度为4的序列,其词向量和位置编码向量可能如下所示:

词向量:[0.1, 0.2, 0.3, 0.4]
位置编码向量:[0.1, 0.2, 0.3, 0.4]

在Transformer中,词向量和位置编码向量是相加的,以便在不同位置的词向量之间加入位置信息。例如,对于上面的例子,它们的和将是:

[0.2, 0.4, 0.6, 0.8]

Transformer模型中,单词的Embedding的维度一般是512或者768,所以位置编码也需要这么多维度。

为什么用包含各频率的正弦和余弦对?

位置编码存储的是一个包含各频率的正弦和余弦对,这样做有两个好处:

  1. 可以使得不同位置的编码向量之间有一定的规律性,比如相邻位置之间的差异较小,而距离较远的位置之间的差异较大
    这是由正弦和余弦函数的连续性和单调性保证的,即对于任意两个相邻的位置,它们对应的编码向量在每一个维度上都只有微小的变化,而对于任意两个距离较远的位置,它们对应的编码向量在每一个维度上都有较大的差异。
  2. 可以使得编码向量在任意维度上都能保持唯一性,即不同位置在同一个维度上不会有相同的值。
    这是由正弦和余弦函数的周期性相位差保证的,即对于任意两个不同的位置,它们对应的编码向量在每一个维度上都不相等。

底数10000,它是不是越大越好?

底数越大,位置向量能表示的序列就越长,这是大底数的好处。

但是,底数大,意味着在-1到+1的范围内向量的取值越密集,造成两个位置的向量距离越近,这对后续的Self-Attention模块来说是不利的,因为它需要经历更多的训练次数才能准确地找到每个位置的信息,或者说,才能准确地区分不同的位置。

长序列需要长编码。但这样又会增加计算量,特别是长编码会影响模型的训练时间。所以,那个底数并非是越大越好。

可视化的位置编码

假如有50个词,每个词的位置编码有128维,三角函数的值域空间是【-1,1】,值从大到小颜色从黄色到蓝色,下面颜色代表对应位置词的位置编码:

理解Transformer的位置编码_编码方式_04


PE将正弦函数用于偶数嵌入索引i,余弦函数用于奇数嵌入索引i。这就是为什么在上图中会看到交错的棋盘格模式。

对应代码,经过简化,便于理解位置编码。

import numpy as np
import matplotlib.pyplot as plot

def getPositionEncoding(seq_len,dim,n=10000):
  PE = np.zeros(shape=(seq_len,dim))
  for pos in range(seq_len):
    for i in range(int(dim/2)):
      denominator = np.power(n, 2*i/dim)
      PE[pos,2*i] = np.sin(pos/denominator)
      PE[pos,2*i+1] = np.cos(pos/denominator)

  return PE

PE = getPositionEncoding(seq_len=50, dim=128, n=10000)
print(PE)

caxes = plot.matshow(PE,interpolation ='nearest')
plot.colorbar(caxes) 
plot.show()

从上面效果图可以看出,这个三角函数式位置编码满足以下四个特点:

  • 语句中每个词的位置编码是唯一的;
  • 不同长度的句子中任意相邻两个词的间隔距离是一致的;
  • 模型可以很容易处理更长的语句,并且值有界;
  • 位置编码是确定性的。

这样就可以适配我们顺序不同,含义不同的语言

其它方式的位置编码

在 Transformer 模型中,位置编码有两种方式:固定的位置编码和可学习的位置编码。

  • 固定的位置编码是通过某种公式计算得到的(如本文前面说的),而不是通过训练得到的。这种位置编码可以表示单词在序列中的相对或绝对位置,因此可以保存单词在序列中的位置信息。
  • 可学习的位置编码是通过训练得到的,因此可以根据输入序列的不同而变化。可学习的位置编码可以更好地适应不同的输入序列,但是需要更多的参数,因此需要更多的计算资源。

最近的一些研究表明,固定的位置编码和可学习的位置编码的效果差别不大,但是可学习的位置编码可以更好地适应不同的输入序列。