Perenial youth.

ECNU 2019 NLP Spring_Seminar 第七讲 Sequence-to-sequence Learning

ECNU 2019 NLP Spring_Seminar 第七讲 Sequence-to-sequence Learning

课件链接:http://speech.ee.ntu.edu.tw/~tlkagk/courses/MLDS_2018/Lecture/Seq%20(v2).pdf

这一讲讲的就是大名鼎鼎的seq-to-seq learning啦。 涉及到详细的auto-encoder结构和传统attention机制,大概是16,17年的时候NLP上state-of-the-art的工作,很重要。

RNN回顾

首先复习一下RNN。在上一讲中,我们说RNN是一个有记忆的network,这一讲中,我们将从另外一个角度去描述它。

每一个RNN单元实际上可以看作一个函数:给定input h和x,output h’和y:

RNN powerful的地方在于,不管input有多么长,都只有一个f,以不变应万变。因此不管序列的长度是多少,RNN的参数都是不会变的。

如何做Deep RNN呢?可以用在\(f_1\)这一层上再叠一层\(f_2\)的RNN:

之前也说过Bidirectional RNN,这里也再回顾一下:

上下两个RNN生成两组output a和c,这时候再找到第三个函数\(f_3\),把a和c作为输入,得到network最终的output。

我们说,RNN由basic function f组成,那么这个f长什么样呢?只要满足h和h’的维度是一样的情况下就行。一个常见的或者basic的设计方式如下:

这里的激活函数常用的是tanh。ReLU在RNN中work的并不是很好

比simple RNN更常见的是LSTM。再来看下LSTM的结构:

上图中\(c_{t-1}\)和\(h_{t-1}\)其实就相当于左边naive RNN中的\(h_{t-1}\)。但是实际上两者还是有不同的。LSTM中的c是\(c_{t-1}\)基础上加出来的,每次变化不大,而h变化则可以非常大。而c的变化不大也代表它可以记住之前很长时间的信息,因此这是LSTM不会gradient vanish的主要原因。

介绍完LSTM的结构,再来仔细介绍一下LSTM如何运作:

如何得到\(z z_i z_f z_o\)。把\(x_t\)和\(h_{t-1}\)串在一起乘以一个transform,再套激活层

还有一个trick是可以把c的信息也串进来。这一招叫做“peephole”。但是有一个问题是w也变大了,也就是参数变多了,可能会overfitting:因此一般都是把w中对应\(c_{t-1}\)的部分变成对角矩阵,以减少参数量:

接下来得到\(c_t, h_t\)和\(y_t\):

可以看到,\(h_{t-1}\)经过一系列操作才能影响到\(h_t\),因此两者之间可能会差距非常大。

两个LSTM连接的过程:

除了Simple RNN和LSTM,再来说一下GRU。GRU是gate recurrent unit的缩写

GRU和LSTM的关系:其实GRU可以看作LSTM中input和forget gate联动的情况。也就是一个开一个关。把GRU中的h看作LSTM中的c。

为什么RNN中的结构要设计成这样呢?有人在语音识别上做过非常多的LSTM的变形的实验:

纵轴代表训练的时间,横轴代表分类的错误率。右边这些竖着的水绿色的点表示没有output的activation function。也就是output的时候不经过tanh。实验表明没有这个结果就是不行。右上块的蓝色的表示没有forget gate。左上的黄色叉叉表示把上一个时间点的z也接进来,结果性能也没有太好,训练时间大大增加。

完整的训练结果。后面灰色的直条图代表参数量。第一个LSTM是传统的。第二个相当于GRU。这个结果表明LSTM中的那些结构都是有必要的。

Sequence Generation

再来正式进入Sequence Generation。所谓sequence generation就是要让机器产生出序列。过去我们做的事情大部分都是分类,很少让机器去产生什么东西。假设让句子output一个sentence,怎么做呢?英文的话,每次output一个word就可以,中文的话,每一个词汇以数个字组成,所以每次可以用字或者词为单位,让RNN每次产生一个字或者词即可。下面我们要说的就是RNN每次怎么产生一个word/character

从每一个RNN单元来说,输入x就是上一个时间步产生的token的embedding表示;输出y就是token的一个概率分布:

生成过程举例:y可以用sample也可以argmax,argmax的话每次产生的句子就会是一样的了

begin of sentence 往往是embedding的第一维

那么上图的f怎么找到呢?也就是要怎么训练这个模型呢:

每次train时的label都是下一个时序的输入

RNN也可以用于图片生成。就是把每个pixel看作句子中的每个词一样:

上面的生成图像的序列顺序是按行遍历,但是实际上,这个不是很合理,比如说第二行的第一个其实不应该看第一行的最后一个,应该看第一行的第一个才对。因此有人针对这一点做了改进,提出了PixelRNN:

Conditional Sequence Generation

上面说的序列生成只是简单地生成了随机的句子,并不能根据某个情景让它生成特定的句子;例如我们拿唐诗三百首训练,这个模型最后也只能随机地生成一些诗词而已。其实我们可以做到让机器根据情景(即特定的输入),生成特定的输出,也就是Conditional Sequence Generation。应用场景有看图生成句子(Image Caption Generation),聊天机器人等等。

怎么做呢?以Image Caption Generation为例:先把image通过CNN变成一个vector,然后把这个vector和句子同时作为输入:

为了怕机器遗忘,每个时间步都把图片的vector丢进去

类似的,在翻译或者chat-bot:

这里的Encoder和Decoder是jointly learning的。

上面这种结构就是sequence2sequence learning:input是一个sequence,output也是sequence。也称为encoder-decoder architect。

但是seq2seq可能有一个问题,比如在chat-bot中:如果只看上一次使用者的输入的话,可能会重复:比如机器先说hello,人说hi,机器只看到hi,于是又回一个hi。。。那么这个问题如何解决呢?让机器去把之前的信息也看了就可以了:

Dynamic Conditional Generation(attention based)

前面说的encoder-decoder模型有一个问题,就是说,decoder可能没有足够的能力去解码原来的句子信息。很容易想到,我们喂给decoder的只是一个vector而已,如果我们把整个document变成仅仅一个vector,那么想从这个vector中解码出信息是挺困难的。因此我们希望decoder每次吃不同的information。怎么做呢?这由decoder自己决定获取哪部分信息,也就是decoder自己决定把注意力放在哪一部分上。

假设encoder在每一个hidden state都有一个h,就是说每一个step RNN都会output一个h。我们假设有一个databse把所有的h都存下来,那么每次decoder都从这个databse搜寻它想要的信息。

举个例子,在机器翻译中的过程如下:其中,\(z^0\)相当于搜寻database的关键字。我们拿z和每一个h算匹配的程度match。算match的方法有:

  1. h和z是vector的情况算cosine(inner product)
  2. match本身是一个小的NN,input为z和h的拼接,输出一个scalar
  3. \(\alpha = h^T W z\)

需要强调的是,如果match本身是有参数的话(例如上面的2,3),match的参数是和seq2seq一起learn的。

再把每一个的匹配度\(\alpha * h\)再求和:

\(z_1\)可以由\(c_0 z_0\)得到

注意,seq2seq model中,每一次产生的h都会存下来(相当于一个矩阵),与一般的RNN不同。

再举个Image Caption Generation的例子:

先把一张图片切碎,每一小块都丢到CNN中,变成很多vector。类似的,\(z_0和每个vector算match\)

我们也可以根据每一块图片的权重来看句子output是根据什么的:

要如何衡量产生的output的好坏呢?有一种方法是靠判断输出和标准答案中有多少个词是一样的,显然这个办法有很多弊端。实际上主要还是得靠人来分辨。。没有太好的办法

Tips for Generation

可否对attention进行一些限制?假设我们的attention权重不太好(每个词中某一张图的权重都比较大)。这时候可以给attention下一个正则化:

另外,在training和testing时,其实有一点不同:training时,我们每次都是拿正确label喂给下一个时间步的input,但是testing时我们每次不知道当前时间步的正确label。因此只好拿sample出的结果当作正确的。但是testing可能会有错的。这件事叫exposure bias。这可能会导致一步错,步步错。

如何解决这个问题呢?首先的想法是training时也拿预测的喂给下一步,也就是和testing对应起来。这种想法可以,但是却很难train起来:

假设第一个真实label是A,但是output是B,那么后面都是拿第一步是B为前提在train;但随着迭代,第一个的output一旦反转了,等于前面都白train了。

另外一种想法叫做Scheduled Sampling。也就是每次随机(这个概率要自己调)选取output和真实label喂给下一步。

我们前面开始所说的train的方法是greedy的,也就是基于argmax的(image caption generation最好argmax,但是chat-bot最好sample)。但是实际上这并不一定是最终几率最大的情况,例如:

选绿色的明显比红色的好,但是机器因为argmax会选红色

为了解决这个问题,可以采用beam search。也就是不用argmax,而是多看几个概率也很大的。看的个数就是beam size。值得注意的是,train时我们不用beam search,在testing时采用。

那么可不可以不用beam search,直接把distribution丢进去呢?这样可能会输出的句子很睿智

另外再说一下evaluation的问题。我们一般minimize的是每一个step的cross-entropy。可能在train到一定程度的时候,句子还是很睿智,需要再train很久才能生成人的观感很好的句子:

因此,不应该考虑每一个词汇的cross-entropy,而是要考虑整个句子。比较两个句子的差距而不是每一个词有没有做对。这时候就会有一个新的问题:这个新定义的R不好微分,没法train呀!这时候就该reinforcement learning硬train一发了。我们可以把这个task当作reinforcement learning的reward用rl来做。

xinyu