简介
这篇博客的主要内容是对谷歌提出的transformer 进行论文解读,包含算法复杂度的分析。对应的论文是 “Attention is all you need", 链接如下 https://arxiv.org/pdf/1706.03762.pdf 。
选择这篇论文的原因有三点。
1. 这篇论文达到了 new the state-of-the-art result, 应该是现在做神经翻译里最好的BLUE结果。
2. 这篇文章提出的算法另辟蹊径,没有采取大热的RNN/LSTM/GRU的结构,而是使用attention layer 和全连接层,达到了较好的效果,并且解决了 RNN/LSTM/GRU 里的long dependency problem 。
3. 这篇文章的算法解决了传统RNN 训练并行度的问题,并降低了计算复杂度。
接下来会按照 "Attention is all you need" 论文中的逻辑, 逐个模块介绍, 希望能对大家有所帮助。原文写在我的笔记上。
https://shimo.im/docs/gmRW4WV2mjoXzKA1/
模型结构
Fig.1 The transformer architecture
上面这个Fig.1 就是谷歌提出的transformer 的架构。这其中左半部分是 encoder 右半部分是 decoder.
Encoder: 这里面有 N=6 个 一样的layers, 每一层包含了两个sub-layers. 第一个sub-layer 就是多头注意力层(multi-head attention layer) 然后是一个简单的全连接层。 这里还有一个残差连接 (residual connection), 在这个基础上, 还有一个layer norm. 这里的注意力层会在下文详细解释。
Decoder: 这里同样是有六个一样的Layer是,但是这里的layer 和encoder 不一样, 这里的layer 包含了三个sub-layers, 其中有 一个self-attention layer, encoder-decoder attention layer 最后是一个全连接层。 前两个sub-layer 都是基于multi-head attention layer. 这里有个特别点就是masking, masking 的作用就是防止在训练的时候 使用未来的输出的单词。 比如训练时, 第一个单词是不能参考第二个单词的生成结果的。 Masking就会把这个信息变成0, 用来保证预测位置 i 的信息只能基于比 i 小的输出。
Attention
Scaled dot-product attention
这里就详细讨论scaled dot-product attention. 在原文里, 这个算法是通过queriies, keys and values 的形式描述的, 非常抽象。这里我用了一张CMU NLP 课里的图来解释, Q(queries), K (keys) and V(Values), 其中 Key and values 一般对应同样的 vector, K=V 而Query vecotor 是对应目标句子的 word vector.
Fig. 2 Attention process (source:ttp://phontron.com/class/nn4nlp2017/assets/slides/nn4nlp-09-attention.pdf)
Fig. 2 里的quary vector 来自 decoder state, key/value vector来自所有的encoder state. 具体的操作有三个步骤。
1. 每个query-key 会做出一个点乘的运算过程
2. 最后会使用soft max 把他们归一。
3. 再到最后会乘以V (values) 用来当做attention vector.
这里的数学表达式如下。
如果用Numpy 来写, scaled dot-product attention, 内容如下
这个在实际呢, 是一个tensor dot product. 对于新手来说tensor dot product 可能还有些陌生。 对于一维向量的点乘(dot product), 结果是一个 标量scalar, 对于一个高纬度的tensor dot product, 结果就不那么好理解了。 在numpy 里的 numpy.dot 解释 如下,
If a is an N-D array and b is an M-D array (where M>=2), it is a sum product over the last axis of a and the second-to-last axis of b:
dot(a, b)[i,j,k,m] = sum(a[i,j,:] * b[k,:,m]), source: https://docs.scipy.org/doc/numpy 1.14.0/reference/generated/numpy.dot.html
举个例子, 比如 张量(tensor)a 是一个四维矩阵,维度是[3,4,5,6], 张量 b 也是一个四维矩阵, 维度是[5,4,6,3], 那么 dot(a,b) 的维度就是 [3,4,5,5,4,3].
这里面如果考虑计算复杂度呢, c 里的每一个元素 都是通过 sum(a[i,j,:] * b[k,:,m])计算得来,对于上图的例子,就需要6次乘法 5 次加法, 总共11 FLOPs, 那这样的张量dot product 就是11*3*4*5*5*4*3 FLOPs的运算量了。
Multi-head attention
上面的scaled dot-product attention, 看起来还有点简单, 网络的表达能力还有一些简单,所以提出了多头注意力机制(multi-head attention)。参考文献是[3] 用图说明。
Fig. 3 Convolution and Self-attention difference (source: https://nlp.stanford.edu/seminar/details/lkaiser.pdf)
这里的self-attention, 就是上文中query (Q), keys (K) and Values (V) 都是相同的, 表达同一段句子 (Q=K=V). Fig. 3 右图说明了, 对于self-attention, target node (生成的那个点) 实际上和 输入中的任意一点的距离是相同的。 这点是可以用来获得 long path dependency的, 在翻译句子里尤为重要。 而convolution, 因为有卷积窗口的 问题, 任意两点的局距离是 Log_k(n), 其中k 是卷积窗口的大小,而n句子长度。 所以说这个self-attention 可以解决 翻译句子 source -target 对齐的问题。 一般有三种注意力方式, 如下图所示。
Fig. 4 三种注意力方式 (source: https://nlp.stanford.edu/seminar/details/lkaiser.pdf)
这里的masked decoder self attention, 就是为了防止 当前的单词生成 对未来的单词产生依赖性。
Fig. 5 多头注意力机制(multi-head attention)和卷积
多头注意力机制很棒啊。 首先each head, 是可以并行计算的, 然后每个head 都有自己对应的weight, 实现不同的线性转换, 这样每个head 也就有了自己特别的表达信息。 所以Fig. 5 里的每个连接 是用彩色表示的。
那multi-head attention 是怎么实现的呢。 在执行self-attention 之前 Q, K, V 都会先乘以一个矩阵 做linear project, 把他投影到维度num_head*(d_q, d_k, d_v)。 这个地方举个例子就比较容易说明了, 比如以前的head 维度是 64, 我需要8 个head, 那我就用线性转换, 把我的输入变成的维度变成 512, 再reshape 一下。
具体就是, 我单个self head attention 的维度是 [1, 16, 512] 对应的是[batch, seq_length, head_dim]。 那我现在做multi-head attention, 那我就先把512 通过linear transform 变成64,(multiplied by 512*64 mixtrx) 那得到了 [1,16,64], 同样的操作作进行八次, reshape, 就是 [1, 8, 16,64]. 这样multi-head attention 就实现了。 接下来继续按照Fig.2 中介绍的三步骤来实现 attention。
我们回到multi-head attention 论文中的讨论,
Fig. 6 Multi-head attention
有了前文的解释,Fig. 6 中的原文讨论就容易理解的多了。 第一段就是我上文的讨论, 说出了 a single attention head 的问题。 d_model 就是 512, d_k= d_v= 8. Q 是 [1, 16, 512] 维度, 而且W_i^Q 的维度是 [512, 64].
这里有个潜在的问题, 计算复杂度变高了。 比如Q, [1,16,512], 512 前文说是head dimension, 其实是有物理含义的, 对于第一个attention 模块, 512 表达的是 word vector length, 就是用多长的vector 来代表一个单词。 而这个计算最终导致, 增加了O(n*d^2) 的复杂度, 其中 n 是sequence length, d 是word vector representation dimension.
Positional Embedding
讨论到现在, 还没有信息来表达单词和单词相关的位置关系。 这里, 谷歌用了sine 和cosine 函数来表达。 先上截图。
Fig. 7 positional embedding
这里使用了两个构造函数, sin, cosine. pos 用来表示单词的位置信息, 比如 第一个单词啦, 第二个单词什么的。 而 i 用来表达dimension。 现在的例子里, d_{model} 是512, 那 i 应该是 0 到255. 这里呢, 为了好说明, 如果2i= d_{model}, PE 的函数就是sin(pos/10000), 那它的波长就是10000*2pi, 如果i =0, 那么他的波长 就是2pi. 这样的sin, cosin 的函数 是可以通过线性关系互相表达的。
就是这么回事啦。
Auto recursive decoding
在Transformer里有一个概念就是auto recursive decoding. 这个并不好理解, 论文也没有详细讨论,让我思考许久。好在谷歌博客做了一个动图, 一目了然。
Fig. 8 Encoding-decoding的动态过程 source: https://ai.googleblog.com/2017/08/transformer-novel-neural-network.htm
Fig. 8 这个图的encoding 过程, 主要是self attention, 有三层。 接下来是decoding过程, 也是有三层, 第一个预测结果 <start> 符号, 是完全通过encoding 里的attention vector 做出的决策。 而第二个预测结果Je, 是基于encoding attention vector & <start> attention vector 做出的决策。按照这个逻辑,新翻译的单词不仅仅依赖 encoding attention vector, 也依赖过去翻译好的单词的attention vector。 随着翻译出来的句子越来越多,翻译下一个单词的运算量也就会相应增加。 如果详细分析,复杂度是 (n^2d), 其中n是翻译句子的长度,d是word vector 的维度。
计算复杂度
计算复杂度对比
这里n, 是 sequence length, d 是word representation dimension. 通常 n<<d, 一个句子的长度 n 一般是3 ---30, 但是一个word representation dimension,大概300, 也有更多的, 比如512.
后续
实验结果我就先不分析了, 如果需要,请留言, 我再找时间。欢迎大家留言讨论。
参考文献
[1] Michal Chromiak Blog, https://mchromiak.github.io/articles/2017/Sep/01/Primer-NN/#attention-basis
[2] Google Blog, https://ai.googleblog.com/2017/08/transformer-novel-neural-network.html
[3] Tensor2tensor transformer, https://nlp.stanford.edu/seminar/details/lkaiser.pdf
作者:涛涛江水向坡流
链接:https://www.jianshu.com/p/ef41302edeef
共同学习,写下你的评论
评论加载中...
作者其他优质文章