举个例⼦讲下transformer的输⼊输出细节及其他
最近由于⼯作需要,将的相关资料看了下,⽹上很多关于transformer的讲解,但是很多都只讲了整个架构,涉及到的细节都讲的不是很清楚,在此将⾃⼰关于某些细节的体 会写出来,⼤家⼀起学习探讨下。
下图是transformer的原始架构图,就不细讲了。
主要讲下数据从输⼊到encoder到decoder输出这个过程中的流程(以为例⼦):
对于机器翻译来说,⼀个样本是由原始句⼦和翻译后的句⼦组成的。⽐如原始句⼦是: “我爱”,那么翻译后是 ’i love machine learning‘。 则该⼀个样本就是由“我爱机器学习”和 "i love machine learning" 组成。
这个样本的原始句⼦的单词长度是length=4,即‘我’ ‘爱’ ‘机器’ ‘学习’。经过embedding后每个词的embedding向量是512。那么“我爱机器学习”这个句⼦的embedding后的维度是[4,512 ] (若是批量输⼊,则embedding后的维度是[batch, 4, 512])。
padding
因为每个样本的原始句⼦的长度是不⼀样的,那么怎么能统⼀输⼊到encoder呢。此时padding操作登场了,假设样本中句⼦的最⼤长度是10,那么对于长度不⾜10的句⼦,需要补⾜到10个长度,shape就变为[10, 512], 补全的位置上的embedding数值⾃然就是0了
Padding Mask
对于输⼊序列⼀般我们都要进⾏padding补齐,也就是说设定⼀个统⼀长度N,在较短的序列后⾯填充0到长度为N。对于那些补零的数据来说,我们的attention机制不应该把注意⼒放在这些位置上,所以我们需要进⾏⼀些处理。具体的做法是,把这些位置的值加上⼀个⾮常⼤的负数(负⽆穷),这样经过softmax后,这些位置的权重就会接近0。Transformer的padding mask实际上是⼀个张量,每个值都是⼀个Boolean,值为false的地⽅就是要进⾏处理的地⽅。
Positional Embedding
得到补全后的句⼦embedding向量后,直接输⼊encoder的话,那么是没有考虑到句⼦中的位置顺序关系的。此时需要再加⼀个位置向量,位置向量在模型训练中有特定的⽅式,可以表⽰每个词的位置或者不同词之间的距离;总之,核⼼思想是在attention计算时提供有效的距离信息。
关于positional embedding ,⽂章提出两种⽅法:
1.Learned Positional Embedding ,这个是绝对位置编码,即直接对不同的位置随机初始化⼀个postion embedding,这个postion embedding作为参数进⾏训练。
2.Sinusoidal Position Embedding ,相对位置编码,即三⾓函数编码。
下⾯详细讲下Sinusoidal Position Embedding 三⾓函数编码。
Positional Embedding和句⼦embedding是add操作,那么⾃然其shape是相同的也是[10, 512] 。
Sinusoidal Positional Embedding具体怎么得来呢,我们可以先思考下,使⽤绝对位置编码,不同位置对应的positional embedding固然不同,但是位置1和位置2的距离⽐位置3和位置10的距离更近,位置1和位置2与位置3和位置4都只相差1。
这些关于位置的相对含义,模型能够通过绝对位置编码参数学习到吗?此外使⽤Learned Positional Embedding编码,位置之间没有约束关系,我们只能期待它隐式地学到,是否有更合理的⽅法能够显⽰的让模型理解位置的相对关系呢?
肯定是有的,⾸先由下述公式得到Embedding值:
对于句⼦中的每⼀个字,其位置pos∈[0,1,2,…,9](假设每句话10个字), 每个字是N(512)维向量,维度 i (i∈[ 0,1,2,3,4,..N])带⼊函数
由于正弦函数能够表达相对位置信息,那么对每个positional embedding进⾏ sin 或者cos激活,可能效果更好,那就再将偶数列上的embedding值⽤sin()函数激活,奇数列的embedding值⽤cos()函数激活得到的具体⽰意图如下:
这样使⽤三⾓函数设计的好处是位置 i 处的单词的psotional embedding可以被位置 i+k 处单词的psotional embedding线性表⽰,反应两处单词的其相对位置关系。此外位置i和i+k的psotional embedding内积会随着相对位置的递增⽽减⼩,从⽽表征位置的相对距离。
但是不难发现,由于距离的对称性,Sinusoidal Position Encoding虽然能够反映相对位置的距离关系,但是⽆法区分i和i+j的⽅向。即pe(i)*pe(i+j) =pe(i)*pe(i-k) (具体解释参见引⽤链接1)
另外,从参数维度上,使⽤三⾓函数Position Encoding不会引⼊额外参数,Learned Positional Embedding增加的参数量会随序列语句长度线性增长。在可扩展性上,Learned Positional Embedding可扩展性较差,只能表征在max_seq_length以内的位置,⽽三⾓函数Position Encoding没有这样的限制,可扩展性更强。
attention
关于attention操作,⽹上讲的很多,也很简单,就不写了。不过需要值得注意的⼀点是,单头attention 的 Q/K/V 的shape和多头attention 的每个头的Qi/Ki/Vi的⼤⼩是不⼀样的,假如单头attention 的 Q/K/V的参数矩阵WQ/WK/WV的shape分别是[512, 512](此处假设encoder的输⼊和输出是⼀样的shape),那么多头attention (假设8个头)的每个头的Qi/Ki/Vi的参数矩阵WQi/WKi/WVi⼤⼩是[512, 512/8].
FeedForward
很简单,略
add/Norm
经过add/norm后的隐藏输出的shape也是[10,512]。(当然你也可以规定为[10, x],那么Q/K/V的参数矩阵shape就需要变⼀下)
encoder输⼊输出
让我们从输⼊开始,再从头理⼀遍单个encoder这个过程:
输⼊x
x 做⼀个层归⼀化: x1 = norm(x)
进⼊多头self-attention: x2 = self_attention(x1)
残差加成:x3 = x + x2
再做个层归⼀化:x4 = norm(x3)
经过前馈⽹络: x5 = feed_forward(x4)
残差加成: x6 = x3 + x5
输出x6
以上就是⼀个Encoder组件所做的全部⼯作了
2.decoder
注意encoder的输出并没直接作为decoder的直接输⼊。
训练的时候,1.初始decoder的time step为1时(也就是第⼀次接收输⼊),其输⼊为⼀个特殊的token,可能是⽬标序列开始的token(如
<BOS>),也可能是源序列结尾的token(如<EOS>),也可能是其它视任务⽽定的输⼊等等,不同源码中可能有微⼩的差异,其⽬标则是预测翻译后的第1个单词(token)是什么;2.然后<BOS>和预测出来的第1个单词⼀起,再次作为decoder的输⼊,得到第2个预测单词;3后续依此类推;
具体的例⼦如下:
样本:“我/爱/机器/学习”和 "i/ love /machine/ learning"
训练: 1. 把“我/爱/机器/学习”embedding后输⼊到encoder⾥去,最后⼀层的encoder最终输出的outputs [10, 512](假设我们采⽤的embedding长度为512,⽽且batch size = 1),此outputs 乘以新的参数矩阵,可以作为decoder⾥每⼀层⽤到的K和V;
2. 将<bos>作为decoder的初始输⼊,将decoder的最⼤概率输出词 A1和‘i’做cross entropy计算error。
3. 将<bos>,"i" 作为decoder的输⼊,将decoder的最⼤概率输出词 A2 和‘love’做cross entropy计算error。
4. 将<bos>,"i","love" 作为decoder的输⼊,将decoder的最⼤概率输出词A3和'machine' 做cross entropy计算error。
5. 将<bos>,"i","love ","machine" 作为decoder的输⼊,将decoder最⼤概率输出词A4和‘learning’做cross entropy计算error。
6. 将<bos>,"i","love ","machine","learning" 作为decoder的输⼊,将decoder最⼤概率输出词A5和终⽌符</s>做cross entropy 计算error。
Sequence Mask
上述训练过程是挨个单词串⾏进⾏的,那么能不能并⾏进⾏呢,当然可以。可以看到上述单个句⼦训练时候,输⼊到 decoder的分别是
<bos>
<bos>,"i"
<bos>,"i","love"
<bos>,"i","love ","machine"
<bos>,"i","love ","machine","learning"
decoder
那么为何不将这些输⼊组成矩阵,进⾏输⼊呢?这些输⼊组成矩阵形式如下:
【<bos>
<bos>,"i"
<bos>,"i","love"
<bos>,"i","love ","machine"
<bos>,"i","love ","machine","learning" 】
怎么操作得到这个矩阵呢?
将decoder在上述2-6步次的输⼊补全为⼀个完整的句⼦
【<bos>,"i","love ","machine","learning"
<bos>,"i","love ","machine","learning"
<bos>,"i","love ","machine","learning"
<bos>,"i","love ","machine","learning"
<bos>,"i","love ","machine","learning"】
然后将上述矩阵矩阵乘以⼀个 mask矩阵
【1 0 0 0 0
1 1 0 0 0
1 1 1 0 0
1 1 1 1 0
1 1 1 1 1 】
这样是不是就得到了
【<bos>