Seq2Seq和Attention


2020.09.25: 本质部分的猜想被证实.

2020.09.21: 更新Attention的本质.

2020.09.19: 在接触了更多NLP内容后, 发现Attention是一个有特殊表征意义的结构, 以后会加入更深的理解.

Seq2Seq和Attention

Seq2Seq和Attention被广泛的应用于RNN中, 当然现在不单单只是在NLP中使用, CV领域也有很多应用.

RNN回顾

在之前的循环神经网络小结中对RNN进行了介绍. 在此简单回顾一下.

RNN是针对时序序列数据而诞生的神经网络, 其输入是时序数据, 每个时刻$t$ 都会有一个相应的输出和隐藏状态. 对于$t \in T$, 有输入$[x_{1}, x_{2}, \ldots, x_{t}, \ldots, x_{T}]$ 和输出$[y_{1}, y_{2}, \ldots, y_{t}, \ldots, y_{T}]$ . 在经典RNN结构中给出的输入和输出是相同大小的.

当前时刻隐藏状态$h_t$ 随着下个时刻$t+1$ 的输出$x_{t+1}$一起被共同作为下个时刻的输入.

Sequence to Sequence(Encoder-Decoder)

Seq2Seq作为一种不限制输入和输出的序列长度的结构, 被广泛应用于机器翻译, 文本摘要, 阅读理解, 语音识别等任务中. 在Seq2Seq结构中, 编码器Encoder把所有的输入序列都编码成一个统一的语义向量, 保存在hidden state中, 然后再由解码器Decoder解码. 这种结构其实在介绍RNN结构时提到过. Seq2Seq和Encoder-Decoder描述的是同一种结构.

这种结构使得输入和输出与经典的RNN结构不同, 输入和输出的数据维度可以不同. 在解码器Decoder解码的过程中, 反复将上一个时刻$t-1$ 的输出作为当前时刻$t$ 的输入, 循环解码, 直到输出停止字符才停止.

下面以机器翻译为例, 来看一种Seq2Seq结构的一种经典实现方式. 将中文的”早上好”通过seq2seq转换成英文的”Good morning”.

  1. 将”早上好”通过Encoder编码, 从$t=1$ 时刻到$t=3$ 时刻通过RNN反复完成语义向量的编码, 将$t=3$ 时刻最终的隐藏状态$h_3$ 作为语义向量.
  2. $h_3$ 作为Decoder的初始隐藏状态$h_0$, 并在$t=1$ 时刻输入标识开始解码的特殊标识符<start>, 开始解码. 不断的将上一时刻输出作为当前时刻输入进行解码, 最终输出停止字符<stop>时, 预测解码停止.

$t$ 时刻时, Decoder中的数据流流向如下所示:

  1. RNN先被输入大小为$[n_i, 1]$ 的向量$x_t$, 即红点.
  2. 结合传过来的语义向量, 加上这个时刻的输入, 经过RNN后输出大小为$[n_o, 1]$ 的向量$y_t$, 即蓝点.
  3. 在获取了输出向量后, 获取输出向量所对应的字符, 这就需要结合一层全连接层和激活函数(主要是Softmax), 将输出向量变为$y_t’$, 大小为$[n_c, 1]$, 即图中的黄点. $n_c$代表字典中的总字符数. 从$y_t’$ 中找到概率最大的字符index, 即图中橘红色点.
  4. 将分类后获取的字符index做一次Embedding(之前的NLP相关那篇文章中有提到过Word2vec), 输入给下个时刻, 如此循环.

当然也有人将语义向量作为Decoder的输入, 而非隐藏状态提供给Decoder. Seq2Seq只是代表一种结构, 而非某种具体的实现方法.

但是Seq2Seq有许多的缺点:

  • 最大的局限性: 编码和解码之间的唯一联系是固定长度的语义向量.
  • 编码要把整个序列的信息压缩进一个固定长度的语义向量.
  • 语义向量无法完全表达整个序列的信息.
  • 先输入的内容携带的信息, 会被后输入的信息稀释掉, 或者被覆盖掉. 信息的量越大, 损失就越大.
  • 输入序列越长, 这样的现象越严重, 这样使得在Decoder解码时一开始就没有获得足够的输入序列信息, 解码效果会打折扣.

Attention机制

这里说的Attention都是软注意力机制.

正是为了弥补基础的Encoder-Decoder的局限性, 提出了Attention. 因为语义向量表达信息的缺失性, 遗忘性, 以及向量长度的不可变性, Attention想要利用Encoder的隐藏状态$h_t$ 来解决语义向量存在的弊病. 当然, Attention不单单广泛的应用在NLP领域, 在CV领域也常有应用.

Attention结构

Attention的实质就是在Decoder的输入端, 将Encoder的隐藏状态加权信息, 也作为输入的一部分, 提供给Decoder. 换句话说, Encoder不再传递最后一个时间步的隐藏状态, 而是将所有时间步的隐藏状态加权提供给Decoder.

假设Encoder经过了3个时间步, 对应的隐藏状态分别是$h_1, h_2, h_3$, 分别的输入是”早”, “上”, “好”, 那么Decoder在$t=1$ 时刻通过三个不同的权重$w_{11}, w_{12}, w_{13}$ 能加权计算出一个向量$c_1$.
$$
c_{1}=h_{1} \cdot w_{11}+h_{2} \cdot w_{12}+h_{3} \cdot w_{13}
$$
将$c_1$ 和上一个状态拼接在一起形成一个新的向量, 一起输入到Decoder中, 计算结果:
$$
\bar{h}_{0} \leftarrow \operatorname{concat}\left(\bar{h}_{0}, c_{1}\right)=\operatorname{concat}\left(h_{3}, c_{1}\right)
$$
这样, 在Decoder的$t=1$ 时刻, 就应该根据之前Encoder的隐藏状态加权得到一个新的向量$c_2$, 和Decoder的上一个隐藏状态一起输入得到计算结果:
$$
\begin{array}{c}
c_{2}=h_{1} \cdot w_{21}+h_{2} \cdot w_{22}+h_{3} \cdot w_{23} \\
\bar{h}_{1} \leftarrow \operatorname{concat}\left(\bar{h}_{1}, c_{2}\right)
\end{array}
$$

在Decoder的$t=2$ 和$t=3$ 时刻同理, 由于权重不同, 所以Decoder在解码时对隐藏状态关注的部分就不同, 权重越大注意力越强. 比如, 在翻译”好”时, 关注点应该在$h_3$ 上, 所以对应的$w_{13}$ 就应该比$w_{11}$ 和$w_{12}$ 大得多.

如果把隐藏状态和权重视为一层神经层, 那么就可以看做Encoder和Decoder之间引入了一层跨越时间步的神经层.

Attention又有 Bahdanau AttentionLuongAttention 等多种实现. 这里说一下LuongAttention.

LuongAttention

重新定义符号, 用$\bar{h}_{s}$ 代表Encoder的隐藏状态, $h_t$ 代表Decoder的隐藏状态, $\tilde{h}_{t}$代表Attention Layer输出的最终Decoder状态.

首先要计算权重. 如果Decoder在$t$ 时刻的隐藏状态为$h_t$, Encoder每一个隐藏状态$\bar{h}_{s}$ 的权重为$a_{t}$, 则权重是由规则$\mathrm {score}$ 经过$\mathrm {Softmax}$ 函数后得出的:
$$
a_{t}(s)=\frac{\exp \left(\operatorname{score}\left(h_{t}, \bar{h}_{s}\right)\right)}{\sum_{s^{\prime}} \exp \left(\operatorname{score}\left(h_{t}, \bar{h}_{s^{\prime}}\right)\right)}
$$
在luong Attention中, $\mathrm {score}$ 可以通过多种方式来计算:
$$
\operatorname{score}\left(h_{t}, \bar{h}_{s}\right)=\left\{\begin{array}{ll}
h_{t}^{T} \bar{h}_{s} & \text { Dot } \\
h_{t}^{T} W_{a} \bar{h}_{s} & \text { General } \\
v_{a}^{T} \tanh \left(W_{a} \cdot \operatorname{concat}\left(h_{t}, \bar{h}_{s}\right)\right) & \text { Concat }
\end{array}\right.
$$
$\mathrm {Dot}$ 指的是向量内积, $\mathrm {General}$ 是再通过乘以权重矩阵$W_a$ 进行计算. 一般情况下来说$\mathrm {General}$ 要好于$\mathrm {Dot}$.

然后将计算得来的权重与Encoder的隐藏状态进行加权求和, 生成新的向量$c_t$.
$$
c_{t}=\sum_{s} a_{t}(s) \cdot \bar{h}_{s}
$$
接着将加权后的向量$c_t$ 与原始Decoder的隐藏状态$h_t$ 拼接在一起.
$$
\tilde{h}_{t}=\tanh \left(W_{c} \cdot \operatorname{concat}\left(c_{t}, h_{t}\right)\right)=\tanh \left(W_{c} \cdot\left[c_{t} ; h_{t}\right]\right)
$$
因为是拼接, 所以向量$c_t$ 和$h_t$ 拼接后的大小一定会发生变化. 如果想恢复成原来的形状则需要再乘一个恢复矩阵$W_c$ (也是用来重置大小的全连接层), 当然也可以不恢复, 只是会导致Decoder 的每个Cell大小逐渐变大.

最后, 对引入注意力的Decoder的 $\tilde{h}_{t}$ 经过一次线性运算后得到输出.
$$
y_{t}=W_{h o} \tilde{h}_{t}+b_{h o}
$$

也可以根据需要将新生成的状态$\tilde{h}_{t}$ 送入RNN继续学习. 上述过程提到的矩阵$W_a$, $W_c$, $W_{ho}$ 均通过学习得来.

Attention的真正本质

该部分为Transformer前置知识.

图和部分内容出自浅谈Attention机制的理解.

虽然在Seq2Seq中, Attention可以看做是跨越时间步的神经层, 但只有抛弃掉开始接触的RNN和Seq2Seq, 才能看到Attention本身, 目前我们对Attention的理解是基于RNN和Seq2Seq的, 对这两种结构做了捆绑后并不能很好的看清它. 在抛弃了Seq2Seq后, 我们仍然说Attention的本质是对某些数据分配某些权重参数, 然后再对它们进行合并.

比较广泛的一个说法是, Attention的本质是一个查询(Query)到一系列键值对(<Key - Value>)的映射. 有些人将它比作软寻址的过程, 也是一样的道理, 当有Query = key的查询时, 根据内容的重要性, 从每个Value中取出所需要的内容, 再通过某种方式合成起来形成输出.

假设Key和Value是不同语言中一一对应的单词, 如果根据Key能够产生一个与Value有关的概率分布, 不难发现, 这个过程和传统机器翻译中做的短语对齐起到的功能是类似的.

如果在目标中进行某数据的查找, 对于一个Query, 通过计算Query与所有Key之间的相似度或相关性, 再通过归一化得到与Value对应的概率分布(权重), 将其与对应的Value加权求和就得到了输出.

如果Key = Value 则被称为普通模式, Key != Value 被称为键值对模式. 目前在大多数NLP研究中, Key和Value是相同的.

这个结论我看很多人提到过, 我表示不理解, 想了很久目前也只有一个猜测, 我个人认为与应用领域有关.

重点在于, 之所以有Key-Value的映射结构, 很有可能在某些领域中Key的信息和对应的Value信息不是完全相同的, 这就意味着Key只用来生成权重系数, 而Value作为与Key不相同的语义信息可能有其他含义.

可能上面写的比较散, 做个小总结, 工作机制有三步:

  1. 计算Query与Key的相似度, 得到权值, 常用的相似度函数有点积,拼接,感知机等, 当然也可以不是相似度, 是其他的打分函数.
  2. 对权值进行各种Softmax归一化.
  3. 用归一化得来的权值与Value加权求和.

因此, 有Attention:
$$
\begin{aligned}
& \alpha _i = \operatorname {softmax}(\operatorname {similarity}(QK^T)) \\
& attention((K, V), Q) = \sum_{i=1}^N \alpha_i v_i \quad(v_i \in V)
\end{aligned}
$$

注: 该公式在Transformer中还会再次出现.

如果把<Key - Value>这个映射称为字典, 那么Attention能够根据之前形成的字典轻松捕捉局部和长期依赖. 另外, Attention也可以看做是一种基于全连接的图模型, 只是连接权重是动态生成的.

总结

下面来总结一下, 图片出自Visualizing A Neural Machine Translation Model.

在Encoder如何获取经过Attention加权后的向量:

从Decoder得到输出的计算过程:

优缺点分析

Attention优点:

  • 在机器翻译时, 让生词不只是关注全局的语义向量, 增加了”注意力范围“. 表示接下来输出的词要重点关注输入序列种的哪些部分. 根据关注的区域来产生下一个输出.
  • 不要求Encoder将所有信息全输入在一个固定长度的向量中.
  • 将输入编码成一个向量的序列, 解码时, 每一步选择性的从序列中挑一个子集进行处理.
  • 在每一个输出时, 能够充分利用输入携带的信息, 每个语义向量不一样, 注意力焦点不一样.

Attention缺点:

  • 需要为每个输入输出组合分别计算Attention, 50个单词的输出输出序列需要计算2500个attention.
  • attention在决定专注于某个方面之前, 需要遍历一遍记忆再决定下一个输出是以什么.
  • 纯粹的Attention机制并不能提供时序数据之间的位置信息, Transformer就是一个很好的例子.

可视化分析

如果将上述输入$x_t$ 对输出$y_t$ 的权重$a_{t}(s)$ 做一张热力图, 就能看出当预测某个单词时, 对句子其他部分的侧重程度, 也就是注意力:

能很明显的看出, 当翻译某个词时, 不考虑语态的情况下, 翻译和它邻近的几个词有强大的联系, 这也就是Attention的最直观体现.

下图揭露了句子中前后单词之间的联系, 颜色深浅表示联系的强弱, 并且对于不同的任务, Attention能够学习到不同的注意力结构. 图片出自Attention Is All You Need(也是Transformer的论文).

在CV中, Attention同样有它的作用, 图片出自Show, Attend and Tell.

向RNN加入额外信息

Attention机制其实就是将的Encoder的隐藏层状态加权后获得权重向量$c_t$, 额外加入到Decoder中, 从而使得网络有更完整的信息流.

如果有额外信息$z_t$ 想要添加到Decoder中, 那么主要有以下三种方式:

  • Add: 直接将额外信息$z_t$ 叠加在输出$y_t$ 上.
    $$
    y_{t} \leftarrow y_{t}+z_{t}
    $$

  • Concat: 将额外信息$z_t$ 拼接在隐藏层的隐藏装填$h_t$, 然后通过全连接恢复维度, luong attention中就是用的这种方法.
    $$
    h_{t} \leftarrow W_{c} \cdot \operatorname{concat}\left(h_{t}, z_{t}\right)
    $$

  • Mlp: 直接添加一个对额外信息$z$ 的神经层.
    $$
    \begin{array}{l}
    h_{t}^{z h}=W_{z h} \cdot z_{t}+b_{z h} \\
    h_{t} \leftarrow \tanh \left(h_{t}^{i h}+h_{t}^{h h}+h_{t}^{z t}\right) \\
    \quad=\tanh \left(\left(W_{i h} \cdot x_{t}+b_{i h}\right)+\left(W_{h h} \cdot h_{t-1}+b_{h h}\right)+\left(W_{z h} \cdot z_{t}+b_{z h}\right)\right)
    \end{array}
    $$


文章作者: DaNing
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 DaNing !
评论
 上一篇
KMP算法 KMP算法
KMP算法串字符串是一种特殊的线性表, 其逻辑结构与线性表相同, 只是在数据类型上进行了约束, 要求元素全是字符类型. 串可以顺序存储, 链式存储, 或者堆存储. 堆结合了顺序和链式的优点, 实际在构造串也是采用的堆结构来存储, 能够方便动
2020-09-02
下一篇 
C++之模板 C++之模板
C++之模板函数模板在C++中, 模板被用于设计可重用的软件, 模板提供了将通用数据类型作为参数的能力.比如有时, 在求一个最大值时, 不得不因为不同的数据类型而写许多除了数据类型外完全一致的代码: int maxValue(int va
2020-08-23
  目录