一. Transformer 模型火爆原因:
1. 模型简单易懂. encoder 和decoder 模块高度相似且相通.
2. encoder 容易并行,模型训练速度快.
3. 效果拔群,在NMT等领域都取得了state-of-the-art的效果.
二. Transformer 模型:
Transformer 模型拿一个序列,生成另一个序列. 打开这个模型,我们会看到其中包含2个部分. encoders 和decoders.
其中 encoders 和decoders 都是两个堆叠架构,一层层同质的结构堆叠到一起, 组成了编码器和解码器.
首先,打开每个encoder:
每一个encoder都包含了一个自注意力(self-attention) 层 和一个Feed Forward Neural Network.
encoder的输入首先会经过一个self-attention层. self-attention层的作用是让每个单词可以看到自己和其他单词的关系.并且将自己转换成一个与所有单词相关的, focus在自己身上的词向量.self-attention之后的输出会再经过一层feed-forward神经网络.每个位置的输出被同样的feed forwardnetwork 处理.
decoder也有同样的self-attention 和feed-forward结构,但是在这两层之间还有一层encoder-decoder attention 层, 帮助decoder 关注到某一些特别需要关注的encoder位置.
Tensor 变化
1. embedding. (此处是 512维度)
2. 每一个embedding 好之后的单词,进入一个2层的encoder.
这里,我们可以看到Transformer的一个重要的特性, 每个位置的单词有自己的encoder 路径. 他们在self-attention层时 建立连接 互相产依赖性. 在前向神经网络层没有依赖性.因此 在前向神经网络层时 多个路径可以并行运行.
编码器
Self-Attention 机制
我们考虑用Transformer模型翻译下面这句话: '' The animal didn't cross the street because it was too tired". 当翻译到it时, 我们知道 it 指代的是 animal 而不是street. 所以, 如果有办法可以让 it 对应位置的embedding 适当包含 animal 的信息,就会非常有用. self-attention的出现就是为了完成这一任务.
如下图所示. self attention 会让单词it 和 某些单词发生比较强的联系, 得到比较高的attention 分数.
Self-attention 细节
第一步: 为了实现self-attention, 每个输入的位置需要产生三个向量, 分别是 Query, Key, Value 向量. 这些向量都是由输入embedding 通过3个matrices(也就是线性变化)产生的.
注意到在Transformer架构中, 这些新的向量比原来的输入向量要小, 原来的向量是512维, 转变后的三个向量都是64维.
第二步是计算分数. 当我们在用self-attention encode某个位置上的某个单词的时候, 我们希望知道这个单词对应的句子上其他单词的分数. 其他单词所得到的分数表示了当我们encode 当前单词的时候, 应该放多少的关注度在其余的每个单词上. 或者说,其他单词和我当前单词有多大的相关性或者相似性.
在transformer模型中,这个分数由 query vector 和 key vector做点积(dot product)所得的结果. 所以说, 当我们在对第一个单词做self-attention处理的时候,第一个单词的分数是 q_1和k_1的点积, 第二个分数是 q_1和k_2的分数.
第三步和第四步 是将这些分数除以8. 8 这个数是64开方, 也就是key vector的维度的开方. 据说这么做可以稳定模型的gradient. 然后我们将这些分数传入softmax层 产生一些符合概率分布的probability scores.
这个score 就表示了在处理当前单词的时候我们应该分配多少关注度给其他单词.
第五步时将每个value vector 乘以他们各自的attention score.
第六步是把这些weighted value vectors 相加,成为当前单词的vector表示.
得到了self-attention 生成的词向量之后,我们就可以将他们传入feed-forward network了.
Self attention 中的矩阵运算
首先, 我们要对每一个词向量计算Query, Key, 和value 矩阵. 我们把句子中的每个词向量拼接到一起变成一个矩阵X, 然后乘以不同 的矩阵做线性变换(WQ, WK, WV)
然后我们就用矩阵乘法实现上面介绍过得 Self-Attention机制.
Multi-Headed attention
在论文中, 每个embedding vector 并不止产生一个key, value, query vectors, 而是产生若干组这样的vectors, 称之为'multi-headed' attention. 这么做有几个好处:
1. 模型有更强的能力产生不同的attention 机制, focus在不同的单词上.
2. attention layer 有多个不同的 'representation space'.
每个attention head 最终都产生了一个matrix表示这个句子中的所有词向量. 在transformer模型中,我们产生了八个matrices. 我们知道self attention 之后就是一个feed-forward network. 那么我们是否需要做8次feed-forward network 运算呢? 事实上是不用的, 我们只需要将这8个matrices拼接到一起,然后乘以另外一个权重矩阵WO 输出Z, 然后做前向神经网络运算就可以了.
综合起来, 我们可以用下面一张图表示self-attention模块所做的事情.
数字表示的句子->词嵌入->线性变换得到8组QKV-> 计算attention->拼接z矩阵,线性变换得到输出矩阵Z(用于前向神经网络运算)
现在我们有了attention, 那么我们来看下 当编码 ' it '时, 各个位置的attention.
如果我们把所有的attention加起来,得到下图
Positional Encoding
到目前为止,我们的模型完全没有考虑单词的顺序. 即使我们将句子中单词的顺序完全打乱,对于transformer 这个模型来说,并没有什么区别. 为了加入句子中单词的顺序信息,我们引入了一个概念叫做positional encoding.
如果我们假设输入的embedding是4个维度的,那么他们的positional encodings 大概长下面这样.
下面这张图的每一行表示一个positional encoding vector. 第一行表示第一个单词的positional encoding, 以此类推. 每行都有512个-1 到1 之间的数字, 我们用颜色标记了这些vectors.
The formula for positional encoding is described in the paper (section 3.5). You can see the code for generating positional encodings inget_timing_signal_1d(). This is not the only possible method for positional encoding. It, however, gives the advantage of being able to scale to unseen lengths of sequences (e.g. if our trained model is asked to translate a sentence longer than any of those in our training set).
Residuals
另外一个细节是, encoder中的每一层都包含了一个residual connection 和 layer-normalization 如下图所示.
下面这张图是更详细的vector表示。
Decode 也是同样的构架, 如果我们把encoder 和decoder 放到一起就是下图.
解码器
encoder 从处理输入序列开始, encoder的最后一个层的输出 转成一系列的attention vectors K和V. K 和 V会被decoder 作为解码的原料.
在解码的过程中,解码器每一步会输出一个token。一直循环往复,直到它输出了一个特殊的end of sequence token,表示解码结束了。
The output of each step is fed to the bottom decoder in the next time step, and the decoders bubble up their decoding results just like the encoders did. And just like we did with the encoder inputs, we embed and add positional encoding to those decoder inputs to indicate the position of each word.
decoder的self attention机制与encoder稍有不同。在decoder当中,self attention层只能看到之前已经解码的文字。我们只需要把当前输出位置之后的单词全都mask掉(softmax层之前全都设置成-inf)即可。
Encoder-Decoder Attention层和普通的multiheaded self-attention一样,除了它的Queries完全来自下面的decoder层,然后Key和Value来自encoder的输出向量。
最后的线性层 和 softmax 层
decoder 最后输出一个浮点向量. 我们如何将他们转成词呢? 这就是最后的线性层和softmax层的主要工作.
线性层是一个简单的全连接层, 将解码器的最后输出映射到一个非常大的logits向量上. 假设模型已知有1万个单词(输出词表) 从训练集中学到的. 那么, logits 向量就有10000维, 每一个值表示是某个单词的可能倾向值.
softmax层将这些分数转成概率值(都是正值,且加和为1), 最高概率处的index就是输出单词.
模型训练
现在我们已经了解了一个训练完毕的Transformer的前向过程,顺道看下训练的概念也是非常有用的。在训练时,模型将经历上述的前向过程,当我们在标记训练集上训练时,可以对比预测输出与实际输出。为了可视化,假设输出一共只有6个单词(“a”,
“am”, “i”, “thanks”, “student”, “eos”)
模型的词表是在训练之前的预处理中生成的.
一旦定义了词表,我们就能够构造一个同维度的向量来表示每个单词,比如one-hot编码,下面举例编码“am”。
举例采用one-hot编码输出词表
下面让我们讨论下模型的loss损失,在训练过程中用来优化的指标,指导学习得到一个非常准确的模型。
损失函数
我们用一个简单的例子来示范训练,比如翻译“merci”为“thanks”。那意味着输出的概率分布指向单词“thanks”,但是由于模型未训练是随机初始化的,不太可能就是期望的输出。
由于模型参数是随机初始化的,未训练的模型输出随机值。我们可以对比真实输出,然后利用误差后传调整模型权重,使得输出更接近与真实输出。如何对比两个概率分布呢?简单采用cross-entropy或者Kullback-Leibler divergence中的一种。鉴于这是个极其简单的例子,更真实的情况是,使用一个句子作为输入。比如,输入是“je suis étudiant”,期望输出是“i am a student”。在这个例子下,我们期望模型输出连续的概率分布满足如下条件:
每个概率分布都与词表同维度
第一个概率分布对“i”具有最高的预测概率值。
第二个概率分布对“am”具有最高的预测概率值。
一直到第五个输出指向""标记。
对一个句子而言,训练模型的目标概率分布.
在足够大的训练集上训练足够时间之后,我们期望产生的概率分布如下所示:
训练好之后,模型的输出是我们期望的翻译。当然,这并不意味着这一过程是来自训练集。注意,每个位置都能有值,即便与输出近乎无关,这也是softmax对训练有帮助的地方。现在,因为模型每步只产生一组输出,假设模型选择最高概率,扔掉其他的部分,这是种产生预测结果的方法,叫做greedy解码。另外一种方法是beam search,每一步仅保留最头部高概率的两个输出,根据这俩输出再预测下一步,再保留头部高概率的两个输出,重复直到预测结束.
更多资料
Attention Is All You Need
Transformer博客文章 Transformer: A Novel Neural Network Architecture for Language Understanding
Tensor2Tensor announcement.
Jupyter Notebook provided as part of the Tensor2Tensor repo
Tensor2Tensor repo.