0%

Seq2Seq 模型与 Attention 机制

Seq2Seq 由 Encoder 和 Decoder 组成,通常用于处理序列输入输出的问题。目前 Seq2Seq 模型主要用于机器翻译,语音识别,文本摘要,问答系统等领域。

1. RNN 结构原理及其变种

1.1 RNN 结构原理

在讲 Seq2Seq 之前先讲讲 RNN 的结构及其变种吧。RNN 的基本模型如下如所示。

在图中可以看出,\(t\) 时刻的隐藏层状态 \(h_t\) 等于前一个隐藏层状态 \(h_{t-1}\) 和当前输入 \(x_t\) 的和。而 \(t\) 时刻的输出 \(y_t\) 等于隐藏层状态 \(h_t\) 与权重矩阵 \(V\) 的相乘。由于存在把上一层隐藏层状态 \(h_{t-1}\) 传递给当前隐藏层状态的过程,所以使得 RNN 具有记忆能力。

1.2 RNN 的变种结构

常见的 RNN 结构有 N vs N1 vs NN vs 1,共三种。

1.2.1 N vs N 结构

此结构包含 \(N\) 个输入 \(x_1, x_2, \cdots, x_N\)\(N\) 个输出 \(y_1, y_2, \cdots, y_N\)。在 N vs N 结构中,输入和输出序的列长度都是相等的,通常适用于以下任务:

  • 词性标注、寻来你语言模型
  • 使用之前的词预测下一个词

1.2.2 1 vs N 结构

  • 1 vs N 结构 1
  • 1 vs N 结构 2

此结构只有一个输入 \(x\)\(N\) 个输出 \(y_1, y_2, \cdots, y_N\)。第一种 1 vs N 结构只将输入 \(x\) 传入第一个隐藏层 \(h\),而第二种结构是将 \(x\) 传入所有隐藏层 \(h\)1 vs N 结构适用于一下任务:

  • 图像生成文字,输入 x 就是一张图片,输出就是一段图片的描述文字
  • 根据音乐类别,生成对应的音乐
  • 根据小说类别,生成相应的小说

1.2.3 N vs 1 结构

此结构中,我们有 \(N\) 个输入 \(x_1, x_2, \cdots, x_N\),和一个输出 \(y\)N vs 1 结构适合用于以下任务:

  • 序列分类任务
  • 一段语音、一段文字的类别
  • 句子的情感分析

2. Seq2Seq 模型结构与原理

2.1 Seq2Seq 模型结构

上面了解了 RNN 的基本结构和它的三种变种结构。接下来正式讲一下 Seq2Seq 模型结构。Seq2Seq 模型(也称 Encoder-Decoder 模型)是一个重要的模型,它由两部分组成,分别是 Encoder(编码器) 和 Decoder(解码器)。Encoder 部分负责编码序列的信息,将任意长度的序列信息编码到一个上下文信息向量 \(C\) 里。Decoder 部分负责将上下文信息向量 \(C\) 解码,并结合后面的隐藏层向量等输出序列。由于 Seq2Seq 从 RNN 而来的,所以它的模型结构模型也有很多种,下面是几种比较常见的。

  • 第一种:
  • 第二种:
  • 第三种:

2.2 Encoder 结构

上面三种 Seq2Seq 模型的主要主要区别在于 Decoder,它们的 Encoder 都是相同的。下图是 Encoder 部分结构。

从图中可以看出 Encoder 和 普通 RNN 区别不大。Encoder 接受输入 \(x\),最后只输入一个上下文信息向量 \(C\)。其中的上下文向量 \(C\) 可以采用多种方式进行计算。 \[ \begin{aligned} C & = h_N \\ C & = q(h_N) \\ C & = q(h_1, h_2, \cdots, h_N) \end{aligned} \]

而隐藏层 \(h_N\) 的计算公式如下( \(g\) 为激活函数): \[ \begin{aligned} h_t & = g(W h_{t-1} + U x_t + b_1) \end{aligned} \]

从公式可以看出,\(C\) 可以直接使用最后一个隐藏层状态 \(h_N\) 表示,也可以使用函数 \(q\) 对最后一个隐藏层状态 \(h_N\) 进行线性变换,甚至还可以对所有隐藏层状态 \(h_1, h_2, \cdots, h_N\) 进行线性变换。Encoder 计算得到的上下文信息向量 \(C\) 将用于 Decoder 解码。

2.3 Decoder 结构

Decoder 有很多种,这里主要介绍三种。

  • 第一种:

第一种结构比较简单,不再需要输入 \(x\),将上下文信息向量 \(C\) 以初始隐藏层状态输入到 RNN 中。输出的 \(y_1, y_2, \cdots, y_N\) 就是我们想得到的结果。隐藏层和输出的计算公式如下: \[ \begin{aligned} h'_1 & = g_1(W C + b_1) \\ h'_t & = g_1(W h'_{t-1} + b_1) \\ y'_t & = g_2(V h'_t + b_2) \end{aligned} \]

其中 \(g_1, g_2\) 为激活函数,\(b_1, b_2\) 为偏置

  • 第二种:

第二种结构也不需要输入 \(x\),但是需要将上下文信息向量作为输入传入隐藏层 \(h\),并且初始化 \(h_0\) 作为 Decoder 的初始隐藏层状态。从上图可以看到,每个神经元有相同的输入 \(C\)。Decoder 的隐藏层和输出计算公式: \[ \begin{aligned} h'_t & = g_1(U C + W h'_{t-1} + b_1) \\ y'_t & = g_2(h'_t V + b_2) \end{aligned} \]

其中 \(g_1, g_2\) 为激活函数,\(b_1, b_2\) 为偏置

  • 第三种:

第三种结构在第二种结构基础上做了些改变。初始隐藏层状态还是使用自初始化的隐藏层状态 \(h'_0\)。上下文信息向量 \(C\) 还是作为隐藏层输入,不同的是这次还组合了上一时刻的输出 \(y'_{t-1}\)。也就是说,隐藏层的输入不仅包括上一层隐藏层状态 \(h'_{t-1}\),上下文信息向量 \(C\),而且还包括上一时刻的输出 \(y'_{t-1}\)。所以 \(t\) 时刻的隐藏层和输出计算公式: \[ \begin{aligned} h'_t & = g_1(U C + W h'_{t-1} + V' y'_{t-1} + b_1) \\ y'_t & = g_2(h'_t V + b_2) \end{aligned} \]

其中 \(g_1, g_2\) 为激活函数,\(b_1, b_2\) 为偏置,\(V'\) 是权重矩阵,用于对输出 \(y'_{t-1}\) 进行线性变换(也可以不用,不用的话就使用 \(y'_{t-1}\) 或者 \(V h'_{t-1}\) 替换)。

3. Seq2Seq 训练技巧 —— Teaching Forcing

3.1 Teaching Forcing 方法

Teaching Forcing 是用于训练阶段。在第三种结构中,上一时刻输出作为当前时刻的输入,如果在训练过程中输出带有错误,那 么会把这个错误带到下一个神经元的输出,也就是说把错误传递下去。这样就会出现严重的问题。

而 Teaching Forcing 可以在一定程度上缓解这种问题。使用第三种结构的 Seq2Seq 在训练时,每个隐藏层的输出不一定作为下个隐藏层的输入,而是直接使用标准答案(ground truth)的对应上一项作为下一个状态的输入。举例子,输入一个句子 “I want to run”,翻译成“我想跑步”。不使用 Teaching Forcing 时,是这样的:

使用 Teaching Forcing 时,是这样的:

3.2 Teacher Forcing 的缺点

因为依赖标签数据,在训练过程中,模型会有较好的效果,但是在测试的时候因为不能得到 ground truth 的支持。所以如果目前生成的序列在训练过程中有很大不同,模型就会变得脆弱。

3.3 解决办法

使用集束搜索(Beam Search)。集束搜索方法不用于训练的过程,而是用在测试的。在每一个神经元中,我们都选取当前输出概率值最大的 top k 个输出传递到下一个神经元。下一个神经元分别用这 k 个输出,计算出 L 个单词的概率 (L 为词汇表大小),然后在 kL 个结果中得到 top k 个最大的输出,重复这一步骤。

4. seq2seq 存在什么问题

  • 忽略了输入序列 \(x\) 的长度:当输入句子长度很长,特别是比训练集中最初的句子长度还长时,模型的性能急剧下降;
  • 对输入序列 \(x\) 缺乏区分度:输入 \(x\) 编码成一个固定的长度,对句子中每个词都赋予相同的权重,这样做没有区分度,往往是模型性能下降。

5. Seq2Seq 与 Attention

在 Seq2Seq 模型中,Encoder 将输入句子的所有信息编码到一个固定长度且不变的上下文信息向量 \(C\) 中,而在 Decoder 解码的过程中一直使用这个向量 \(C\) ,这存在着不少缺陷。比如,RNN 存在长序列梯度消失的问题。对于较长的句子,我们很难希望于把长序列输入中的信息存储在上下文信息向量 \(C\) 中而不失真。而 Attention(即注意力机制)是一种将模型的注意力放在当前翻译单词上的一种机制。比如在翻译 "I want to run",当翻译到 "我" 时,把注意力放在源句子的 "I" 上;翻译到 "跑步" 时,把注意力放在源句子的 "run" 上。这样当我们 Decoder 预测目标翻译的时候,就可以看到 Encoder 的所有信息,而不仅局限于原来模型中定长的隐藏向量,并且不会丧失长程的信息。

上图所示了 Attention 与 Seq2Seq 的结构图。在 \(t\) 时刻计算 Attention 时,它的计算过程时这样的:

  • 提取 Encoder 的所有隐藏层状态 \(H_{N \times V} = \{ h_1, h_2, h_3, \cdots, h_N \}\),其中 \(N\) 为隐藏层个数,\(V\) 为隐藏层状态的向量长度。
  • 获取 \(t\) 时刻 Decoder 隐藏层状态 \(h'_t\),然后根据公式 \(\alpha = softmax(f(Q, K))\),计算 \(\alpha\) 数值。其中 \(Q\)\(h_t\)\(K\)\(H_{N \times V}\)\(\alpha\) 的维度为 \(1 \times N\)\(f\) 为相关性系数的计算函数,比如:
    • 点乘: \(f(Q, K) = QK^T\)
    • 加权点乘: \(f(Q, K) = Q W K^T\)
    • 加和: \(f(Q, K) = tanh(W_1 Q + W_2 K)\)
  • \(\alpha\) 乘以 \(H_{N \times V}\) 得到 Attention 值 \(C_\alpha\)\(C_\alpha = \alpha H_{N \times V}\)。其中的 \(C_\alpha\) 的维度为 \(1 \times V\)
  • 最后把 \(C_\alpha\) 作为输入的一部分传入 Decoder 的隐藏层状态并进行下一轮计算。下一轮(也就是 \(t+1\) 时刻)的隐藏层和输出计算公式为(其中 \(g_1, g_2\) 为激活函数, \(b_1, b_2\) 为偏置): \[ \begin{aligned} h'_{t+1} & = g_1(U C_\alpha + W h'_t + V' y'_t + b_1) \\ y'_{t+1} & = g_2(h'_{t+1} V + b_2) \end{aligned} \]

参考