Transformer模型学习笔记

Lab Machine Learning Basic

Transformer 模型学习笔记

论文地址Attention Is All You Need
Nice Blog for illustrating Transformer Model
seq2seq with attention

提出的特点

  • RNN无法并行地去处理一个序列, 因为每个hidden state \(h_i\)都是依赖于上一个hidden state \(h_{i-1}\)以及input. 所以就要一个step接一个step的去循环, 对于很长的序列训练起来就很耗时. Transformer 模型利用Attention机制去捕获全局的input与output之间的依赖性, 实质上就是将整条序列看作一个input向量, 也就避免了循环神经网络中的"循环". 实质上算是对RNN循环过程的一个展开吧.
  • 完全使用Attention机制, 没有使用序列对齐的循环(sequence-aligned recurrence)或者卷积层

背景知识

Self attention的优势
  • Gated RNNs 虽然在结构上能够记录前 \(n - 1\) 个token的信息, 但实际上, 随着序列变长, 最早的token信息会变得很少, 这就会失去他的准确性, 这在翻译任务中就显得非常要命, 例如, 在English-to-French的翻译里, output的第一个词大概率是依赖于input开始的部分, 这样很可能会得到很差的结果. 而transformer模型似乎是靠着更大的存储和算力来强行将前 \(n\) 个token利用attention融合起来. 这样看来, 似乎是对症下药, 实验上也得到了很好的结果.
  • 而具体的self attention会在模型架构中介绍
Word Embedding
Blog for introducing Word2Vec

我自己的对Word2Vec的学习笔记

模型架构

其实Transformer模型的改进应该去对比seq2seq with attention模型

  • 首先在seq2seq模型里,用的是最初的encoder-decoder思想, 拿翻译任务来说, input就是一个句子, encoder需要一个单词一个单词(token)的去encode, 也就是扔进一个RNNs, 每次得到一个hidden state, 句子全输进去了, 最后得到的hidden state就可以作为整个句子的representation. 这个思想很简单, 给我的是不定长的, 那我就把他搞成一个固定长度的hidden state. 之后拿着这个representation当作decoder的input, 再一个单词一个单词的预测. 前后都是RNN.

    乍一看貌似还是挺好的, 但问题是RNN的记忆机制和梯度问题解决的不是那么好, 句子长了前面的单词他就忘记了. 而且翻译这个任务也确实需要一种attention ,output的某一个部分会很大程度依赖于input中的一部分.

  • 加了attention的seq2seq似乎就考虑到了这种依赖关系, attention机制也很好的做到了这一点. 至于整体做法, 上面不是说encoder内不断地生成hidden state吗, 那我们就把它们全都取出来作为decoder的input, 这样就不用太担心记忆的问题了, 毕竟你把它们都拿出来了. 然后每个output预测值会利用attention机制去给这些hidden state附上注意力的权重, 来更好地完成任务.

  • 再到transformer. 这样纵向的来看, 似乎改进的地方确实如原论文所讲, 去掉了所有的循环连接. 完全用attention来解决. 这样做就需要在一些地方进行调整.

Attention

虽然字面上讲的attention, 似乎是个很熟悉的概念, 但实际在具体的实现上是另一种更加抽象的机制.

一般来讲,这个问题是想输入两种序列, \(S= \{S_1, S_2, S_3 \dots S_n \}\) 以及 \(T =\{T_1, T_2, T_3 \dots T_m \}\) 我想输出\(T\) 对于\(S\) 的attention, 具体的可以说就是一个function, \(Attention_{S_i}(T_j),\quad i= 1,2\dots n\)

,然后有 \[ \sum_{j} Attention_{S_i}(T_j)=1, \quad i = 1,2,\dots n \] 就可以,值越大就越重要. 那么整个T对于 \(S_i\) 的representation就是 \(T_{s_i} = \sum_{j} Attention_{S_i} (T_j)\times Value(T_j)\) 这么一个加权平均

这里的元素就是一些embedding, 一些向量.

Attention的求法类似于一种查询. 每个 \(S_i\) 都会对应一个query向量 \(\boldsymbol{q_i}\) 每个 \(T_i\) 又对应一个键值 \(\boldsymbol{k_i}\) 以供"查询", 查到的结果就是两个向量的点积 \(a_{ij} = \boldsymbol{q_i} \cdot \boldsymbol{k_j}\) , (假设这里的两个向量的维数都是 \(d_k\) ).

最后的Attention就是再加上一个softmax \[ Attention_{S_i}(T_j) = \frac{e^{\boldsymbol{q_i} \cdot \boldsymbol{k_j}^T}}{\sum_j e^{\boldsymbol{q_i} \cdot \boldsymbol{k_j}^T}} \]

每个 \(T_j\) 又会对应一个Value向量 \(v_j\) (维度可以和前面两个向量不同, 记为\(d_v\))用以获得representation. 最后得到的就是 \[ Representation_{for\ S_i} = softmax(\boldsymbol{q}_{1\times d_k}\cdot \boldsymbol K_{m \times d_k}^T) \boldsymbol V_{m\times d_k} \] 进一步可以获得 \(T\)\(S\) 的表示 \[ softmax(\boldsymbol{Q}_{n\times d_k}\cdot \boldsymbol K_{m \times d_k}^T) \boldsymbol V_{m\times d_v} \] 为了"having more stable gradients" , 在\(\boldsymbol{Q}\cdot \boldsymbol K^T\)这里还要除以一个因子, 默认是 \(\sqrt{d_k}\) ,

然后又变成了 \[ softmax(\frac{\boldsymbol{Q}\cdot \boldsymbol K^T}{\sqrt{d_k}}) \boldsymbol V \] 有人要问了, 你说的这些query,key和value向量都咋求呢.

  • 用三个线性映射(矩阵) \(W^Q,W^K,W^V\)

线性映射哪来的呢

  • 学出来的
a beast with multihead

这样一组attention可能注意力太集中, 看不全, 那我们就让他有多个"头", 注意力分散点, 看得更全

tensor2tensor上有个示例, 演示的就是attention

一个比较经典的例子 翻译句子 The animal didn't cross the street because it was too tired

这里的 it 应该代指 The animal, 但是对模型来说, 他也可能是说the street.

attention with one head

这里可以看到, 模型确实被it的代指给弄晕了,但还好animal处的颜色比street的地方要深,说明他的权值要大

attention with one head(2)

但并不是所有的attention都能学到对应的部分.

解决办法就是, 我们用多个attention去拼接成最终想要的representation. 具体的, 我们得到的value向量不是 \(d_v\) 维的吗, 假设我们有 \(h\) 个head, 那么就把向量分为\(h\) 个维度为 \(d_v / h\) 的向量, 每个用各自的线性映射得到 \(h\) 组不同的 \(\boldsymbol{Q,\, K,\, V_{m \times (d_v/h)}}\) 去求各自的value(attention结构图片来自这个blog)

multihead attention

最后一般还会再乘上一个矩阵 \(W^O\),来得到最后的输出

attention with multihead

用上了多个head, 我们就能同时去关注不同的区域, 获得更准确的表述

attention with 3 heads

整体架构

前面花了较长篇幅讲了Attention机制, 这里再看下它是如何被用在Transformer中的

transformer_model architecture

在上面的架构图中包含encoder以及decoder的结构(结构图来自原论文)

左边是encoder, 他的特点就是直接将整条序列直接放进网络层中.

工作流程就是, 首先把要处理的序列input输入, 再获得它的embeddings. 然后依次进入每个encoder层,

Comments