整体结构
transformer是一个encoeder decoder结构, 像是seq2seq一样, 但seq2seq 最大的问题在于将 Encoder 端的所有信息压缩到一个固定长度的向量中,并将其作为 Decoder 端首个隐藏状态的输入,来预测 Decoder 端第一个单词 (token) 的隐藏状态。在输入序列比较长的时候,这样做显然会损失 Encoder 端的很多信息,而且这样一股脑的把该固定向量送入 Decoder 端,Decoder 端不能够关注到其想要关注的信息。Transformer 不但对 seq2seq 模型这两点缺点有了实质性的改进 (多头交互式 attention 模块),而且还引入了 self-attention 模块,让源序列和目标序列首先 “自关联” 起来,这样的话,源序列和目标序列自身的 embedding 表示所蕴含的信息更加丰富,而且后续的 FFN 层也增强了模型的表达能力,并且 Transformer 并行计算的能力远远超过了 seq2seq 系列模型。 来源 我们接下来逐一解释。
1. Positional Encoding $ Input Embedding
Transformer 是以字作为输入,将字进行字嵌入之后,再与位置嵌入进行相加(不是拼接,就是单纯的对应位置上的数值进行加和) 由于 Transformer 模型没有循环神经网络的迭代操作,所以我们必须提供每个字的位置信息给 Transformer,这样它才能识别出语言中的顺序关系。 就比如一句话,I love you , 这里每个字都是有位置信息的,是字而不是字母。就是图中红框部分,为每个字添加位置信息。 Embedding是因为一开始seq序列中的每个字并没有一个向量来表示它, 一开始的输入维度假如是[batch_size sequence_length] , 经过embedding后变为[batch_size sequence_length embedding_dim] , 如图中绿框部分 因为是位置信息与embedding信息进行加和, 所以其维度是一致的, 都是[batch_size sequence_length embedding_dim]
如果让我们从 0 开始设计一个 Positional Encoding,比较容易想到的第一个方法是取 [0,1] 之间的数分配给每个字,其中 0 给第一个字,1 给最后一个字,具体公式就是
P
E
=
p
o
s
T
?
1
PE\text=\frac{pos}{T-1}
PE=T?1pos?,
p
o
s
∈
[
0
,
T
-
1
]
pos∈[0, T\text-1]
pos∈[0,T-1]。 这样做的问题在于,假设在较短文本中任意两个字位置编码的差为 0.0333,同时在某一个较长文本中也有两个字的位置编码的差是 0.0333。假设较短文本总共 30 个字,那么较短文本中的这两个字其实是相邻的;假设较长文本总共 90 个字,那么较长文本中这两个字中间实际上隔了两个字。这显然是不合适的,因为相同的差值,在不同的句子中却不是同一个含义
另一个想法是线性的给每个时间步分配一个数字,也就是说,第一个单词被赋予 1,第二个单词被赋予 2,依此类推。这种方式也有很大的问题:1. 它比一般的字嵌入的数值要大,难免会抢了字嵌入的「风头」,对模型可能有一定的干扰;2. 最后一个字比第一个字大太多,和字嵌入合并后难免会出现特征在数值上的倾斜
理想情况下,位置嵌入的设计应该满足以下条件:
- 它应该为每个字输出唯一的编码
- 不同长度的句子之间,任何两个字之间的差值应该保持一致
- 它的值应该是有界的
Transformer中的位置嵌入不是一个数字, ,而是一个包含句子中特定位置信息的
d
d
d 维向量, 因为要和embedding后相加, 所以
d
d
d 其实就是embedding的维度. 这部分的公式如下: pos表示单词在句子中的绝对位置,pos=0,1,2…,例如:Jerry在"Tom chase Jerry"中的pos=2;dmodel表示词向量的维度,在这里dmodel=512;2i和2i+1表示奇偶性,i表示词向量中的第几维,例如这里dmodel=512,故i=0,1,2…255。 注意i的取值范围, 小于一半, 很容易理解. 这样每个索引就会分配一个d维的位置向量
最终结果如图:
|