0%

DL笔记(9)Recurrent Neural Network(RNN)

1. Example Application

先来看一个例子:Slot Filling(槽填充)。这是个NLP领域的知识点,这里作一下简单介绍,参考知乎文章槽填充(Slot Filling)的定义、用途、意义及其他

One way of making sense of a piece of text is to tag the words or tokens which carry meaning to the sentences. In the field of Natural Language Processing, this problem is known as Semantic Slot Filling.——mc.ai

槽填充是指从大规模的语料库中抽取给定实体(query)的被明确定义的属性(slot types)的值(slot fillers)。

填槽指的是为了让用户意图转化为用户明确的指令而补全信息的过程。

Slot Filling的用途或意义可以是:用于任务型对话;作为意图识别的关键字;作为下一步对话的提示信息。

设想一个订票系统,它听到用户说:“ i would like to arrive Taipei on November 2nd”,你的系统有一些slot(如有一个slot是Destination,一个slot是time of arrival),系统要自动知道这边的每一个词汇是属于哪一个slot,比如Taipei属于Destination这个slot,November 2nd属于time of arrival这个slot。

这个问题当然可以使用一个feedforward neural network来解,叠一个feedforward neural network,input是一个词汇(把Taipei变成一个vector)输入到这个neural network。

以下是把词汇用向量来表示的方法:

1-of-N encoding

Beyond 1-of-N encoding

如果只是用1-of-N encoding来描述一个词汇的话会遇到一些问题,因为有很多词汇你可能都没有见过,所以需要在1-of-N encoding里面多加dimension,图中左边绿色条最后一个dimension代表other。不是在我们词言有的词汇就归类到other里面去(如Gandalf,Sauron归类到other里面去)。或者可以用每一个词汇的字母来表示它的vector,比如apple,apple里面有出现app、ppl、ple,那在这个vector里面对应到app,ple,ppl的dimension就是1,而其他都为0。

假设我们已经解决了把词汇表示为vector,然后把这个vector输入到feedforward neural network里,在这个task里面,我们希望output是一个probability distribution。这个probability distribution代表着我们现在input词汇属于每一个slot的几率,比如Taipei属于destination的几率和Taipei属于time of departure的几率。

但这会遇到一些feedforward neural network没办法解决的问题。假设现在有一个使用者说:“arrive Taipei on November 2nd”(arrive-other,Taipei-dest, on-other,November-time,2nd-time)。那现在有人说:”leave Taipei on November 2nd”,这时候Taipei就变成了“place of departure”,它应该是出发地而不是目的地。但是对于feedforward neural network来说,input一样的东西output就应该是一样的东西(input “Taipei”,output要么是destination几率最高,要么就是place of departure几率最高),没有办法一会让出发地的几率最高,一会让它目的地几率最高。

那如果今天我们的neural network是有记忆力的,它记得看红色的Taipei之前它就已经看过arrive这个词汇;它记得看绿色的Taipei之前,它就已经看过leave这个词汇,那么它就可以根据上下文产生不同的output。如果让我们的neural network是有记忆力的话,它就可以解决在两句不同的话中,input相同的词汇,得到的output不同,这样一个问题。

2. RNN

这种有记忆的neural network就叫做Recurrent Neural network,RNN(循环神经网络)。在RNN里面,每一次hidden layer的neuron产生output的时候,这些output会被存到memory里去(下图中用蓝色方块表示memory)。那下一次当有input时,这些neuron不只是考虑input$x_1,x_2$,还会考虑存到memory里的值$a_1,a_2$。

通过下面的例子来理解RNN的原理,设下图中的RNN中的neuron的weight都是1,bias都是0,若input sequence为$[1,1]^T,[1,1]^T,[2,2]^T$,给定memory中的初始值$a_1=a_2=0$,那么output sequence为:

  • input $[1,1]^T$,$x_1=x_2=1$,$a_1=a_2=0$;hidden layer得到 $a_1=a_2=x_1+x_2+a_1+a_2=2$;output $y_1=y_2=a_1+a_2=4$;
  • input $[1,1]^T$,$x_1=x_2=1$,$a_1=a_2=2$;hidden layer得到 $a_1=a_2=6$;output $y_1=y_2=12$;
  • input $[2,2]^T$,$x_1=x_2=2$,$a_1=a_2=6$;hidden layer得到 $a_1=a_2=16$;output $y_1=y_2=32$;

需要注意的是:对于RNN,input sequence的顺序并不是independent的, 是会对输出的结果造成影响的,所以在RNN中我们要考虑input sequence的order。

那现在用RNN来处理Slot Filling这个问题的流程大致是这样的:顾客说”arrive Taipei on November 2nd”,arrive转换为vector输入到network;然后经过hidden layer得到$a^1$,它是hidden layer的输出,也是个vector;由$a^1$在output layer得到输出$y^1$,$y^1$是arrive属于每一个slot的几率。$a^1$会被存储到memory中,接下来Taipei转换为输入的vector $x^2$,hidden layer会同时考虑$a^1$和$x^2$,得到$a^2$,然后得到$y^2$,$y^2$是Taipei属于每个slot的几率。之后就以此类推。注意图中并不是三个network,而是同一个network在三个不同的时间点被使用,同样的weight用同样的颜色表示。

这样就可以解决之前那个问题了,如果两次分别输入的是arrive Taipei和leave Taipei,由于arrive和leave的不同,导致hidden layer得到$a^1$不同,由于在输入Taipei时,hidden layer会同时考虑$a^1$和$x^2$,那两次得到的$y^2$就很会不同,从而把语义区分开。

当然RNN的架构是可以根据具体的需要来设计的,上图中的hidden layer只有一层,当然也可以做成Deep Recurrent Neural Network。

Elman network &Jordan network

RNN有很多种变形,上面讲到的叫Elman Network,它是把hidden layer得到的值存起来,在下一个时间点再读出来;有另外一种RNN叫作Jordan Network,它每次存的是network的output值,在下一个时间点再读出来。(据说Jordan Network的效果可能好一下,理解是因为output y是有target的,我们清楚是把什么东西放进了memory;而hidden layer是没有target的,很难控制说它能学到什么hidden layer information)。

Bidirectional neural network

RNN还可以是双向的,上面讲的RNN,input一个句子,是从句首一直读到句尾。假设句子里的每一个词汇由$x_t$表示,RNN就是先读$x_t$,再读$x_{t+1}$,再读$x_{t+2}$。那它的读取方向也可以是反过来的,可以先读$x_{t+2}$,再读$x_{t+1}$,再读$x_t$。我们可以同时train一个正向的RNN和一个逆向的RNN,然后把这两个RNN的hidden layer得到的值拿出来,都接给一个output layer得到最后的$y_t$,比如正向的network在input$x_t$的时候hidden layer得到的值,跟逆向的network在input $x_t$时hidden layer得到值,都输入到output layer中得到$y_t$,然后以此类推得到$y_{t+1},y_{t+2}$。

用Bidirectional neural network的好处是,neural在产生output的时候,它看的范围是比较广的。如果你只有正向的network,再产生$y_t,y_{t+1}$的时候,你的network只看过$x_1$到$x_{t+1}$的input。但Bidirectional neural network,在产生$y_t,y_{t+1}$的时候,你的network不只看过$x_1$到$x_{t+1}$的input,它也看了从句尾$x_N$到$x_{t+1}$的input,就等于整个input的sequence。那对于第一节中讲到的slot filling的例子,你的network就等于看了整个sentence后,才决定每一个词汇的slot应该是什么。这样会比看sentence的一半还要得到更好的performance。

3. LSTM

3.1 What is LSTM

上面讲到的memory方式是最简单的,我们可以随时把值存到memory去,也可以把值读出来。但目前最常用的memory方式是Long Short-term Memory(LSTM,长时间的短期记忆)

Long Short-term Memory有三个gate,当某个neural的output想要被写到memory cell里面的时候,必须通过一个input Gate,input Gate被打开的时候,你才能把值写到memory cell里面,而input Gate是打开还是关起来是neural network自己学的。这个cell输出的地方有一个output Gate,output Gate决定其他的neural可不可以从这个memory里面把值读出来,output Gate关闭时不能读出,打开时才可以把值读出来。跟input Gate一样,output Gate何时打开或关闭,是由network自己学到的。第三个gate叫做forget Gate,forget Gate决定什么时候memory cell要把过去记得的东西“忘掉”,即将memory存的值清0再去存新的值,当然forget Gate何时把存在memory的值忘掉,也是由network自己学到的。

那整个LSTM你可以看成,它有四个input 1个output,这四个input中,一个是想要被存在memory cell的值(但它不一定存的进去)还有操控input Gate的讯号,操控output Gate的讯号,操控forget Gate的讯号,最终只会得到一个output。

(冷知识:这个“-”应该在short-term中间,是长时间的短期记忆。想想我们之前看的Recurrent Neural Network,它的memory在每一个时间点都会被洗掉,只要有新的input进来,每一个时间点都会把memory 洗掉,所以的short-term是非常short的,但如果是Long Short-term Memory,它记得会比较久一点(只要forget Gate不要决定要忘记,它的值就会被存起来。)

下面来跟详细地看一下LSTM的memory cell的formulation。

假设要被存到cell的input是z,input gate的输入叫做$z_i$(一个scalar),forget gate的输入叫做$z_f$,output gate的输入叫做$z_o$,得到的output 记为$a$。假设cell里面有这四个输入之前,它里面已经存了值$c$。

假设要输入的部分为$z$,那三个gate分别是由$z_i,z_f,z_0$所操控的,每个gate里是activation function,通常会选择sigmoid function,选择sigmoid function的意义是它的值是介在0到1之间的。这个0到1之间的值代表了这个gate被打开的程度(如果这个f的output是1,表示为被打开的状态,反之代表这个gate是关起来的)。

z通过activation function得到$g(z)$,$z_i$通过input Gate的activation function得到$f(z_i)$,把$g(z)$乘以$f(z_i)$得到$g(z)f(z_i)$,$z_f$通过forget Gate的sigmoid function得到$f(z_f)$。接下来把存到memory里面的值$c$乘以$f(z_f)$得到$cf(z_f)$,然后加起来$c’=g(z)f(z_i)+cf(z_f)$,那么$c’$就是重新存到memory里面的值。

所以根据目前的运算,$f(z_i)$会cortrol$g(z)$可不可以make snese,假设$f(z_i)$为0,那$g(z)f(z_i)$就等于0,那$g(z)$就没什么用了,如果$f(z_i)$f等于1,就相当于是把$g(z)$当做输入) 。而$f(z_f)$则会决定要不要把存在memory的值洗掉,假设$f(z_f)$为1(forget gate 开启的时候),这时$c$还会被记得,如果$f(z_f)$等于0(forget gate关闭的时候),$cf(z_f)$等于0,原来的$c$相当于被洗掉了。然后把这个两个值加起来$c’=g(z)f(z_i)+cf(z_f)$。(forget gate的开关跟我们的直觉有些相反,那这个forget gate打开的时候代表的是记得,关闭的时候代表的是遗忘)。

$c’$通过另一个avtivation function得到$h(c’)$,将$h(c’)$乘以$f(z_o)$得到$a = h(c’)f(z_o)$,output gate受$f(z_o)$control,$f(z_o)$等于1的话,就说明$h(c’)$能通过,$f(z_o)$等于0的话,说明memory里面存在的值没有办法通过output gate被读取出来。

3.2 LSTM - Example

下面通过一个例子来更直观地理解LSTM。假设我们的network里面只有一个LSTM的cell,input都是三维的vector,output都是一维的。三维的vector跟output还有memory的关系是:

  • 当第二个dimension $x_2$的值是1时,$x_1$的值就会被add到memory里;
  • 当$x_2$的值是-1时,就会reset the memory;
  • 当$x_3$的值为1时,才会把memory中的值output出来。

假设我们原来存到memory里面的值是0,当第二个input的$x_2$值是1,3会被存到memory里面去。第四个输入的$x_2$等于1,所以4会被add到memory里面去,memory中存的值变为7。第六个输入的$x_3$等于1,这时memory中的7会被输出。第七个dimension的$x_2$的值为-1,memory里面的值会被洗掉变为0。第八个dimension的$x_2$的值为1,所以把6存进去,下一个input的$x_3$的值为1,所以把6输出。

那接下来我们做一下实际的运算。下图是一个memory cell。cell的四个input是由:input的三维vector和bias($x_1,x_2,x_3,1$)乘以不同的四组权重得到$z,z_i,z_f,z_o$,

这些权重和bias用train data通过Gradient Descent的方式训练得到的。 这里假设我们已经知道这些值是多少了,并且假设$g(z)=z$和$h(c’)=c’$,三个Gate都是sigmoid function,memory中的$c$的初始值为0。

我们先来大致分析一下这个LSTM cell在运算时会发生什么。对于$z$,$x_1$乘以1,其他的元素乘以0,那就是直接把$x_1$当做输入。在input gate时,$x_2$乘以100,bias乘以-10,$x_2$值很小时,通常input gate是关闭的,若$x_2$的值较大,如大于1,$z_i$会是一个正值,代表input gate会被打开 。forget gate通常是会被打开的,既它平常会一直记得东西,只有当$x_2$的值为一个很大的负值时,$f(z_f)$接近0,才会把forget gate关起来。output gate平常是被关闭的,因为bias项乘-1得到一个很大的负值,那当$x_3$是一个很大的正值时,output Gate会打开。

那给定一组input sequence会得到什么样的输出呢,我们一步一步来看。

(1) $\begin{bmatrix} x_1&x_2&x_3\end{bmatrix}^T=\begin{bmatrix} 3& 1& 0\end{bmatrix}^T$

$z=\begin{bmatrix}1 &0&0&0\end{bmatrix}\cdot\begin{bmatrix} x_1\\x_2\\x_3\\1\end{bmatrix}=\begin{bmatrix}1 &0&0&0\end{bmatrix}\cdot\begin{bmatrix} 3\\1\\0\\1\end{bmatrix} =3$;$g(z)=z=3$;

$z_i=\begin{bmatrix}0 & 100 &0 & -10\end{bmatrix}\cdot\begin{bmatrix} x_1\\x_2\\x_3\\1\end{bmatrix}=\begin{bmatrix}0 & 100 &0 & -10\end{bmatrix}\cdot\begin{bmatrix} 3\\1\\0\\1\end{bmatrix} =90$;$f(z_i)=\sigma(90)\approx1$;

$z_f=\begin{bmatrix}0 & 100 &0 & 10\end{bmatrix}\cdot\begin{bmatrix} x_1\\x_2\\x_3\\1\end{bmatrix}=\begin{bmatrix}0 & 100 &0 & 10\end{bmatrix}\cdot\begin{bmatrix} 3\\1\\0\\1\end{bmatrix} =110$;$f(z_f)=\sigma(110)\approx1$;

Memory:$c’=g(z)f(z_i)+cf(z_f)=3\cdot1+0\cdot1=3$;$h(c’)=c’=3$;

$z_o=\begin{bmatrix}0 & 0 &100 & -10\end{bmatrix}\cdot\begin{bmatrix} x_1\\x_2\\x_3\\1\end{bmatrix}=\begin{bmatrix}0 & 0 & 100 & -10\end{bmatrix}\cdot\begin{bmatrix} 3\\1\\0\\1\end{bmatrix} =-10$;$f(z_f)=\sigma(-10)\approx 0$;

Output:$a = h(c’)f(z_o)=3\cdot 0=0$

(2)$\begin{bmatrix} x_1&x_2&x_3\end{bmatrix}^T=\begin{bmatrix} 4& 1& 0\end{bmatrix}^T$

$z=\begin{bmatrix}1 &0&0&0\end{bmatrix}\cdot\begin{bmatrix} x_1\\x_2\\x_3\\1\end{bmatrix}=\begin{bmatrix}1 &0&0&0\end{bmatrix}\cdot\begin{bmatrix} 4\\1\\0\\1\end{bmatrix} =4$;$g(z)=z=4$;

$z_i=\begin{bmatrix}0 & 100 &0 & -10\end{bmatrix}\cdot\begin{bmatrix} x_1\\x_2\\x_3\\1\end{bmatrix}=\begin{bmatrix}0 & 100 &0 & -10\end{bmatrix}\cdot\begin{bmatrix} 4\\1\\0\\1\end{bmatrix} =90$;$f(z_i)=\sigma(90)\approx1$;

$z_f=\begin{bmatrix}0 & 100 &0 & 10\end{bmatrix}\cdot\begin{bmatrix} x_1\\x_2\\x_3\\1\end{bmatrix}=\begin{bmatrix}0 & 100 &0 & 10\end{bmatrix}\cdot\begin{bmatrix} 4\\1\\0\\1\end{bmatrix} =110$;$f(z_f)=\sigma(110)\approx1$;

Memory:$c’=g(z)f(z_i)+cf(z_f)=4\cdot1+3\cdot1=7$;$h(c’)=c’=7$;

$z_o=\begin{bmatrix}0 & 0 &100 & -10\end{bmatrix}\cdot\begin{bmatrix} x_1\\x_2\\x_3\\1\end{bmatrix}=\begin{bmatrix}0 & 0 & 100 & -10\end{bmatrix}\cdot\begin{bmatrix} 4\\1\\0\\1\end{bmatrix} =-10$;$f(z_f)=\sigma(-10)\approx 0$;

Output:$a = h(c’)f(z_o)=7\cdot 0=0$

(3)$\begin{bmatrix} x_1&x_2&x_3\end{bmatrix}^T=\begin{bmatrix} 2& 0& 0\end{bmatrix}^T$

$z=\begin{bmatrix}1 &0&0&0\end{bmatrix}\cdot\begin{bmatrix} x_1\\x_2\\x_3\\1\end{bmatrix}=\begin{bmatrix}1 &0&0&0\end{bmatrix}\cdot\begin{bmatrix} 2\\0\\0\\1\end{bmatrix} =2$;$g(z)=z=2$;

$z_i=\begin{bmatrix}0 & 100 &0 & -10\end{bmatrix}\cdot\begin{bmatrix} x_1\\x_2\\x_3\\1\end{bmatrix}=\begin{bmatrix}0 & 100 &0 & -10\end{bmatrix}\cdot\begin{bmatrix} 2\\0\\0\\1\end{bmatrix} =-10$;$f(z_i)=\sigma(-10)\approx 0$;

$z_f=\begin{bmatrix}0 & 100 &0 & 10\end{bmatrix}\cdot\begin{bmatrix} x_1\\x_2\\x_3\\1\end{bmatrix}=\begin{bmatrix}0 & 100 &0 & 10\end{bmatrix}\cdot\begin{bmatrix} 2\\0\\0\\1\end{bmatrix} =10$;$f(z_f)=\sigma(10)\approx1$;

Memory:$c’=g(z)f(z_i)+cf(z_f)=2 \cdot 0 + 7 \cdot 1=7$;$h(c’)=c’=7$;

$z_o=\begin{bmatrix}0 & 0 &100 & -10\end{bmatrix}\cdot\begin{bmatrix} x_1\\x_2\\x_3\\1\end{bmatrix}=\begin{bmatrix}0 & 0 & 100 & -10\end{bmatrix}\cdot\begin{bmatrix} 2 \\ 0 \\0 \\1\end{bmatrix} =-10$;$f(z_f)=\sigma(-10)\approx 0$;

Output:$a = h(c’)f(z_o)=7\cdot 0=0$

(4)$\begin{bmatrix} x_1&x_2&x_3\end{bmatrix}^T=\begin{bmatrix} 1& 0& 1\end{bmatrix}^T$

$z=\begin{bmatrix}1 &0&0&0\end{bmatrix}\cdot\begin{bmatrix} x_1\\x_2\\x_3\\1\end{bmatrix}=\begin{bmatrix}1 &0&0&0\end{bmatrix}\cdot\begin{bmatrix} 1 \\0 \\1\\1\end{bmatrix} =1$;$g(z)=z=1$;

$z_i=\begin{bmatrix}0 & 100 &0 & -10\end{bmatrix}\cdot\begin{bmatrix} x_1\\x_2\\x_3\\1\end{bmatrix}=\begin{bmatrix}0 & 100 &0 & -10\end{bmatrix}\cdot\begin{bmatrix} 1 \\0 \\1 \\1\end{bmatrix} =-10$;$f(z_i)=\sigma(-10)\approx 0$;

$z_f=\begin{bmatrix}0 & 100 &0 & 10\end{bmatrix}\cdot\begin{bmatrix} x_1\\x_2\\x_3\\1\end{bmatrix}=\begin{bmatrix}0 & 100 &0 & 10\end{bmatrix}\cdot\begin{bmatrix} 1\\0\\1\\1\end{bmatrix} =10$;$f(z_f)=\sigma(10)\approx1$;

Memory:$c’=g(z)f(z_i)+cf(z_f)=1 \cdot 0 + 7 \cdot 1=7$;$h(c’)=c’=7$;

$z_o=\begin{bmatrix}0 & 0 &100 & -10\end{bmatrix}\cdot\begin{bmatrix} x_1\\x_2\\x_3\\1\end{bmatrix}=\begin{bmatrix}0 & 0 & 100 & -10\end{bmatrix}\cdot\begin{bmatrix} 1 \\ 0 \\1 \\1\end{bmatrix} =90$;$f(z_f)=\sigma(90)\approx 1$;

Output:$a = h(c’)f(z_o)=7\cdot 1= 7$

(5)$\begin{bmatrix} x_1&x_2&x_3\end{bmatrix}^T=\begin{bmatrix} 3& -1& 0\end{bmatrix}^T$

$z=\begin{bmatrix}1 &0&0&0\end{bmatrix}\cdot\begin{bmatrix} x_1\\x_2\\x_3\\1\end{bmatrix}=\begin{bmatrix}1 &0&0&0\end{bmatrix}\cdot\begin{bmatrix} 3 \-1 \\0\\1\end{bmatrix} =3$;$g(z)=z=3$;

$z_i=\begin{bmatrix}0 & 100 &0 & -10\end{bmatrix}\cdot\begin{bmatrix} x_1\\x_2\\x_3\\1\end{bmatrix}=\begin{bmatrix}0 & 100 &0 & -10\end{bmatrix}\cdot\begin{bmatrix} 3 \-1 \\0 \\1\end{bmatrix} =-110$;$f(z_i)=\sigma(-110)\approx 0$;

$z_f=\begin{bmatrix}0 & 100 &0 & 10\end{bmatrix}\cdot\begin{bmatrix} x_1\\x_2\\x_3\\1\end{bmatrix}=\begin{bmatrix}0 & 100 &0 & 10\end{bmatrix}\cdot\begin{bmatrix} 3 \-1 \\0\\1\end{bmatrix} =-90$;$f(z_f)=\sigma(-90)\approx 0$;

Memory:$c’=g(z)f(z_i)+cf(z_f)=3 \cdot 0 + 7 \cdot 0=0$;$h(c’)=c’=0$;

$z_o=\begin{bmatrix}0 & 0 &100 & -10\end{bmatrix}\cdot\begin{bmatrix} x_1\\x_2\\x_3\\1\end{bmatrix}=\begin{bmatrix}0 & 0 & 100 & -10\end{bmatrix}\cdot\begin{bmatrix} 3 \-1 \\0 \\1\end{bmatrix} =-10$;$f(z_f)=\sigma(-10)\approx 0$;

Output:$a = h(c’)f(z_o)=0\cdot 0= 0$

所以最终得到的output sequence为:$0,0,0,7,0$

3.3 Replace the neurons with LSTM

到这里你可能会想这个LSTM跟我们的neural network有什么样的关系呢[・ヘ・?]。可以这样来理解,在原来的neural network里会有很多的neuron,我们把input乘以不同的weight当做不同neuron的输入,每一个neuron都是一个function,输入一个值然后输出一个值。但是如果是LSTM的话,其实你只要把这一整个memory cell当成是一个neuron就好了。

你做的事情其实就是原来简单的neuron换成LSTM,现在的input($x_1,x_2$)会乘以不同的weight当做LSTM的输入(假设我们这个hidden layer只有两个neuron)。input($x_1,x_2$)会乘以不同的weight当做底下的input $z$,乘以不同的weight操控input gate,乘以不同的weigh去操控forget gate,乘以不同的weight会去操控output gate。

在原来的neural network里每个neuron是有一个input一个output。而LSTM有四个input和一个output,对于LSTM来说,这四个input是不一样的。所以LSTM需要的参数量(假设你现在用的neural的数目跟LSTM是一样的)是一般neural network的四倍。

那这个LSTM跟Recurrent Neural Network的关系是什么呢,借助下面的图来理解一下。

假设我们现在有一整排的LSTM cell,这些LSTM里面的memory都存了一个值,把所有的值接起来就变成了vector,写为 $c^{t-1}$。在时刻t,input一个vector $x^t$,这个vector首先会乘上一matrix,即经过一个linear transform变成一个vector $z$,$z$的dimension等于LSTM cell的数目,其中每一个元素分别是一个个cell的“底下”的input $z$。

同样的道理,$x^t$会乘上另外的一个transform得到$z^i$,然后这个$z^i$zi的dimension也跟cell的数目一样,$z^i$的每一个dimension会分别去操控这些cell的input gate,forget gate 跟output gate也都是一样的道理。所以我们把$x^t$乘以四个不同的transform得到四个不同的vector $z^f,z^i,z,z^o$,四个vector的dimension跟cell的数目一样,这四个vector合起来就会去操控这些memory cell运算(上小节中讲到的运算过程中的$z,z_i,z_f,z_o$是scalar,现在换成vector即可完成下图中的运算)。

经过这一轮运算后memory中存放的是$c^t$,输出$y^t$,将这个process反复的进行,在下一个时间点input $x^{t+1}$,把$f(z)$跟input gate相乘,把forget gate跟存在memory里面的值相乘,然后将前面两个值再相加起来,在乘上output gate的值,然后得到下一个时间点的输出$y^{t+1}$。

但是这远远不是LSTM的最终形态 ∑(゚Д゚ノ)ノ,LSTM会把上一个时间的$h^t$接进下一个时间的input,也就说下一个时间点操控这些gate的值不是只看input $x^{t+1}$,还看前一个时间点的$h^t$。LSTM还可以把上一时间的存在memory cell的值$c^t$接进下一时间的input,这叫做“peephole”,这样就同时考虑了$x^{t+1},h^t,c^t$,把这三个vector合在一起乘上不同的transform得到四个不同的vector再放进LSTM中运算。

当然LSTM也可以有很多层,即Multiple-layer LSTM。目前人们使用RNN,通常就是在用LSTM。

Keras支持三种RNN:LSTM,GRU,SimpleRNN。GRU是LSTM稍微简化的版本,它只有两个gate,虽然少了一个gate,但是performance跟LSTM差不多,少了1/3的参数,比较不容易overfitting。文章第二节中讲的那种RNN就是simple RNN。

4. How to train RNN

4.1 BPTT

那如何训练一个RNN model呢,我们之前讲过如果要做learning的话,要定义一个cost function来evaluate model的好坏,选一个parameter让loss 最小。那在Recurrent Neural Network里面,要怎么定义这个loss呢,下面先举个比价直观的例子。

假设现在要做的是Slot Filling,train data是给一些sentence,sentence的label是其中的每个word分别属于哪个Slot。比如上图中这歌Training Sentence,告诉machine第一个word它是属于other slot,“Taipei是”Destination slot,”on”属于other slot,“November”和“2nd”属于time slot,

然后把“arrive”输入到RNN,RNN会得到一个output $y^1$,接下来$y^1$会和它的reference vector(other这个Slot的vector)来计算cross entropy。可以这样来定义reference vector,其长度等于slot的数目,某一个slot对应的vector中某一个dimension为1,其余为0。那我们就希望输入”arrive”后得到的$y^1$与”other”对应的reference vector越接近越好。

对于training RNN要注意的事是,我们不能把sentence的word顺序打散后输入到RNN中,而必须要按照原来的sequence order输入到RNN中,因为sequence order会影响memory中存的值,进而影响output $y$。

RNN的损失函数,即output和reference vector的entropy的和,就是要最小化的对象。这里也是用梯度下降来做,有了定义好的loss function $L$,要update这个neural network里面的某个参数$w$,就是计算$L$对$w$的偏微分,偏微分计算出来以后,就用梯度下降的方法去update里面的参数。在讲feedforward neural network的时候,可以用一个有效率的求梯度的方法,即Backpropagation(反向传播)。那Recurrent Neural Network领域,有开发一套Backpropagation的进阶版,叫做Backpropagation through time(BPTT)。它跟Backpropagation是很类似的,只是RNN它是在high sequence上运作,所以BPTT要考虑时间上的information。

4.2 Difficulty in training RNN

不幸的是,RNN的training是比较困难的。一般在做training的时候,你会期待learning curve是像下图中蓝色这条线(纵轴是total loss,横轴是epoch的数目),即随着epoch的数目越来越多,随着参数不断的update,loss会慢慢的下降最后趋向收敛。但是不幸的是在训练Recurrent Neural Network的时候,你有时候会看到绿色这条线。如果是第一次train Recurrent Neural Network的人,看到绿色这条learning curve非常剧烈的抖动,到某个地方变成NaN,肯定会想这程序有bug啊(╬ ̄皿 ̄)。

Razvan分析了下RNN的性质,发现RNN的error surface的变化是非常陡峭的/崎岖的(error surface有一些地方非常的平坦,一些地方非常的陡峭,就像是悬崖峭壁一样)。这样会造成什么样的问题呢?假设橙色的点当做初始点,用Gradient Descent开始调整你的参数,updata参数时,可能会刚好跳过一个悬崖,这时loss会突然爆长,loss会非常上下剧烈的震荡)。更惨的情况是,如果在悬崖上之前的gradient会很小,那learning rate可能已经调的很大的,当下一步update时正好踩过悬崖,gradient爆长,很大的gradient乘上很大的learning rate结果参数就会update很多,参数就“飞”出去了。

作者是用工程的思想来解决了这个问题,这一招就是Clipping(当Gradient大于某一个threshold的时候,不要让它超过那个threshold,比如设定threshold为15,那当gradient大于15时,让gradient等于15。因为gradient不会太大,所以你要做Clipping的时候,就算是“踩着这个悬崖上”,也不飞出来,而是会updaye到一个比较近的地方,这样还可以继续做RNN的training。

为什么RNN会有这种奇特的特性呢。有人觉得是来自sigmoid function,我们之前讲Relu activation function的时候,讲过一个问题gradient vanish,这个问题是从sigmoid function来的,RNN会有很平滑的error surface是因为来自于gradient vanish,但其实问题并不是出在sigmoid function上,把RNN中的sigmoid function换成Relu, performance通常是比较差的,所以在train RNN时很少用Relu做activation function。

你应该会从BRTT的式子中更直观的看出为什么会有这个问题。当然也有更直观的方法来知道一个gradient的大小:把某一个参数做小小的变化,看它对network output的变化有多大,就可以估算出这个参数的gradient的大小。

举一个很简单的RNN例子,它只有一个neuron,这个neuron是linear。input没有bias,input的weight是1,output的weight也是1,transition的weight是w。也就是说从memory接到neuron的input的weight是w。

假设给neural network的input sequence是$1,0,0,0,\dots$,共1000个数,很容易计算出最后的output $y^{1000}$是$w^{999}$。现在假设$w$是要learn的参数,我们想要知道它的gradient,即当我们改变$w$的值时候,对neural的output有多大的影响。

现在假设$w=1$,那现在$y^{1000}=1$;当$w=1.01$时,$y^{1000}\approx 20000$,可见当$w \in (1, 1+\epsilon)$时,$w$有一点小小的变化,就会对output产生很大的影响,所以这时$w$有很大的gradient,那这时我们把learning rate设小一点就好了。

但当$w=0.99$时,那$y^{1000}\approx0$;把$w$做一个较大的变化,设$w=0.01$,则有$y^{1000}\approx0$。可见当$w\in(0,1-\epsilon)$时,gradient就突然变得非常非常的小,这个时候我们就需要一个很大的learning rate。这种gradient的突变,使得error surface很崎岖,设置learning rate很麻烦。

从这个例子可以看出RNN training的问题其实来自它把同样的东西在transition的时候反复使用。$w$变化时,它完全可能没有造成任何影响,而一旦造成影响,影响都是“天崩地裂”的。所以RNN不好训练的原因是来自于它有high sequence,同样的weight在不同的时间点被反复的使用,而不是来自activation function。

4.3 Helpful Techniques for RNN

有什么技巧可以帮助我们解决这个问题呢?其实广泛被使用的技巧就是LSTM,LSTM可以让你的error surface不要那么崎岖。它可以做到的事情是,它会把那些平坦的地方拿掉,解决gradient vanish的问题,但不能解决gradient explode的问题。有些地方还是非常的崎岖的,有些地方仍然是变化非常剧烈的,但是不会有特别平坦的地方。做LSTM时,error surface大部分地方变化的很剧烈,所以你可以放心的把learning rate设置的小一点,保证在learning rate很小的情况下进行训练。

那为什么LSTM 可以解决梯度消失的问题,避免gradient特别小呢?RNN跟LSTM在面对memory的时候,它处理的操作其实是不一样的。在RNN里面,在每一个时间点,memory里面的值都是会被洗掉,neuron的output都要memory里面去,所以在每一个时间点,memory里面的值都是会被覆盖掉。但是在LSTM里面不一样,它是把原来memory里面的值乘上一个值再把input的值加起来放到cell里面。所以它的memory input是相加的。

所以LSTM和RNN不同的是,RNN在每个时间点的值都会被format掉,所以只要这个影响被format掉它就消失了。但是在LSTM里面,一旦对memory造成影响,那影响一直会被留着,memory一旦有改变,只会把新的东西加进来,不会把原来的值洗掉,除非forget gate要把memory的值洗掉,所以它不会有gradient vanishing的问题。

那你可能会想现在有forget gate,仍然会把memory的值洗掉。其实LSTM的第一个版本就是为了解决gradient vanishing的问题,所以它是没有forget gate,后来的版本中才加入了forget gate。甚至现在有个说法是:在训练LSTM的时候要给forget gate特别大的bias,你要确保forget gate在多数的情况下都是开启的(即不会洗掉memory),只要少数的情况是关闭的(洗掉memory)。

还有另外一个版本用gate操控memory cell,叫做Gates Recurrent Unit(GRU),LSTM有三个Gate,而GRU有两个gate,所以GRU需要的参数是比较少的。因为它需要的参数量比较少,它在training时是比较鲁棒的。如果在train LSTM时,你觉得overfitting的情况很严重,不妨可以试下GRU。GRU的思想就是:“旧的不去,新的不来”。它会把input gate跟forget gate联动起来,当input gate打开的时候,forget gate会自动的关闭(format存在memory里面的值),当forget gate没有要format里面的值(即forget gate开启),input gate就会被关起来,也就是说你要把memory里面的值清掉,才能把新的值放进来。

还有其他的technique可以handle gradient vanishing的问题。比如说clockwise RNN或者说是Structurally Constrained Recurrent Network (SCRN)等等。有一个蛮有趣的paper是这样的:一般的RNN用identity matrix(单位矩阵)来initialized transformation weight+ReLU activaton function它可以得到很好的performance。刚才不是说用ReLU的performance会比较拉胯,一般train的方法initiaed weight是random,那ReLU跟sigmoid function来比的话,sigmoid performance 会比较好。但是用了identity matrix来initialized的话,用ReLU performance会比较好。

5. More Applications

RNN有很多的application,前面举得那个solt filling的例子中,我们假设input跟output的数目是一样的,我们给input中的每一个word一个slot label。其实RNN可以做到更复杂的事情。

(下面老师举的例子基本都是NLP领域的,我还了解甚少,所以只根据老师上课讲的内容作一些简单的介绍,可能会有些纰漏。而且老师2020年的课程内容跳跃性挺大的,需要结合2017年的ML课程一起看,RNN这节课中有些知识点是在之前的2017年课程里讲到的,我需要看过之后再来对这篇笔记做一个补充更新)

5.1. Many to one

RNN可以做到更复杂的事情,比如说input是一个sequence,output是一个vector,这种Many to one 的常见的应用有sentiment analysis(情感分析)。sentiment analysis现在有很广泛的应用,比如某家公司想要知道,他们的产品在网上的评价是positive 还是negative。他们可能会写一个爬虫,把跟他们产品有关的文章都爬下来。那这一篇一篇的看太累了,所以你可以用一个machine learning 的方法learn一个classifier去分类哪些document的评价是正向的,哪些document是负向的。那你就可以去train一个Recurrent Neural Network,input是character sequence,然后RNN把这个sequence读过一遍,在最后一个时间点,把hidden layer拿出来,在通过几个transform,然后你就可以得到最后的sentiment analysis(这是一个分类的问题,但是因为input是sequence,所以用RNN来处理)。

还可以用RNN来作key term extraction(关键词抽取)。key term extraction意思是给machine看一个文章,machine要预测出这篇文章有哪些关键词。如果你能够收集到一些training data(一些document,这些document都有label,哪些词汇是关键词,那就可以直接train一个RNN),RNN把document当做input,通过Embedding layer,然后用RNN把这个document读过一次,然后把出现在最后一个时间点的output拿过来做attention,你可以把这样的information抽出来再丢到feedforward neural network得到最后的output。

5.2. Many to Many

5.2.1. Output is shorter - Speech Recognition

RNN的输入输出也可以是多对多的,比如input和output都是sequence,但是output sequence比input sequence短的时候,RNN可以处理这个问题。Speech Recognition(语音辨识)就是这样input sequence长,output sequence短的问题。比如在语音辨识这个任务里面input是acoustic sequence(说一句话,这句话就是一段声音讯号)。一般处理声音讯号的方式是,在这个声音讯号里面,每隔一小段时间,就把它用vector来表示。这个一小段时间是很短的(比如说,0.01秒),那output sequence是character sequence。

如果你是原来的simple RNN(slot filling的那个RNN),把这一串input丢进去,它只能做到告诉你每一个vector对应到哪一个character。比如对于一个中文的语音辨识系统,那你的output target理论上就是这个世界上所有可能中文的词汇,常用的可能是八千个,那你RNN classifier的数目可能就是八千个。虽然很大,但也是有办法做的。但是充其量只能做到:每一个声音的vector会得到一个character。每一个input对应的时间间隔是很小的(0.01秒),所以通常是好多个vector对应到同一个character,那对一句内容是“好棒”的语音,辨识结果可能为“好好好棒棒棒棒棒”,那这显然不是语音辨识正确的结果。为了解决这个问题有一招叫做“Trimming”(修剪,切除的意思,这里指把重复的东西拿掉),就变成“好棒”。但这样也会有一个较严重的问题,因为它没有辨识一些有含义的叠词,比如“好棒棒”(老师说“好棒棒”是贬义的,好机车哦)。

那要怎么把“好棒”跟“好棒棒”分开来呢,有一招叫做Connectionist temporal classification(CTC),主要是解决神经网络label 和output 不对齐的问题(Alignment problem)。它的思想可以这样理解:在output时候不只是output所有中文的character,还会output一个符号,叫做”null””(没有任何东西)。所以我今天input一段acoustic feature sequence,它的output是“好 null null 棒 null null null null”,然后我就把“null”的部分拿掉,它就变成“好棒”。如果我们输入另外一个sequence,它的output是“好 null null 棒 null 棒 null null”,然后把“null”拿掉,所以它的output就是“好棒棒”。这样就可以解决叠字的问题了。

CTC在做training的时候,你的train data就会告诉这一串acoustic features对应到这一串character sequence,但它不会告诉你说“好”是对应第几个character 到第几个character。这时可以穷举所有可能的alignments。简单来说就是,我们不知道“好”对应到那几个character,“棒”对应到哪几个character。假设我们所有的状况都是可能的。可能第一个是“好 null 棒 null null null”,可能是“好 null null 棒 null null”,也可能是“好 null null null 棒 null”。我们不知道哪个是对的,那假设全部都是对的。在training的时候,全部都当做是正确的,然后一起train。当然也有更巧妙的算法可以解决这个问题,这里就不细讲了。

在做英文辨识的时候,你的RNN output target就是character(英文的字母+空白)。直接output字母,然后如果字和字之间有boundary,就自动有空白。看下面的例子,第一个frame是output h,第二个frame是output null,第三个frame是output null,第四个frame是output I等等。如果你看到output是这样子话,那最后把“null”的地方拿掉,那这句话的辨识结果就是“HIS FRIEND’S”。你不需要告诉machine说:”HIS”是一个词汇,“FRIEND’s”是一个词汇,machine通过training data会自己学到这件事情。

据说Google的语音辨识系统已经全面换成CTC。如果你用CTC来做语音辨识的话,就算是有某一个词汇(比如是:英文中人名,地名)在training data中从来没有出现过,machine也是有机会把它辨识出来。

5.2.2. No limitation - Sequence to sequence learning

RNN还有一个应用叫做sequence to sequence learning,在sequence to sequence learning里面,RNN的input跟output都是sequence(但是两者的长度是不一样的)。刚在在CTC时,input比较长,output比较短。在这边我们要考虑的是不确定input跟output谁比较长谁比较短。

比如做machine translation(机器翻译),input英文word sequence把它翻译成中文的character sequence。那我们并不知道说,英文跟中文谁比较长谁比较短(有可能是output比较长,output比较短)。

假如现在input一个 “machine learning” ,然后用RNN读过去,然后在最后一个时间点,这个memory里面就存了所有input sequence的information。接下来你让machine “吐”一个character(机),然后就让它output下一个character,把之前的output出来的character当做input,再把memory里面的值读进来,它就会output “器”。那这个“机”怎么接到这个地方呢,有很多支支节节的技巧,还有很多不同的变形。在下一个时间input “器”,output“学”,然后output“习”,然后一直output下去,machine没办法知道它何时要停下来。

要怎么告诉machine何时停止呢?你可以多加一个symbol “===”,代表停止,那现在manchine不只是output可能的character,它还有一个可能的output 是“===”。这样我们的utput sequence中“习”后面是“===”(断)的话,就停下来了。那这中方法也是可以train的起来的。

还有篇papre是这样做的,对于sequence to sequence learning,我们原来是input 某种语言的文字,output是翻译成另外一种语言的文字(假设是做机器翻译的话)。那我们有没有可能直接input某种语言的声音讯号,output另外一种语言的文字呢?我们完全不做语音辨识。比如说你要把英文句子的语音翻译成中文句子的文本,你就可以收集一大堆英文句子的语音,以及它对应的中文翻译,完全不要做语音辨识,直接把英文的声音讯号丢到这个model里面去,看它能不能output正确的中文。这一招居然是行得通的。假设你今天要把台语转成英文,但是台语的语音辨识系统不好做,因为台语根本就没有standard文字系统,所以这项技术可以成功的话,未来你在训练台语转英文语音辨识系统的时候,你只需要收集台语的声音讯号跟它的英文翻译就可以刻了。你就不需要台语语音辨识的结果,你也不需要知道台语的文字,也可以做这件事。

Beyond Sequence

利用sequence to sequence的技术,甚至可以做到Beyond Sequence。这个技术也被用到syntactic parsing(句法分析)。synthetic parsing是指,让machine看一个句子,它要得到这个句子的结构树,得到一个树状的结构。过去你可能要用structured learning的技术能够解这个问题,但是现在有了 sequence to sequence learning的技术以后,只要把这个树状图描述成一个sequence(具体看图中 john has a dog)。你就可以直接learn 一个sequence to sequence model,它的output就是syntactic parsing tree。这个是可以train的起来的,performance也很好。你可能会担心machine的output的sequence不符合文法结构,比如它记得加左括号,却忘记加右括号,神奇的地方就是LSTM不会忘记右括号。

Sequence - to - sequence Auto - encoder - Text

当我们要将一个document表示成一个vector时,往往会用bag-of-word的方法,用这个方法的时候,往往会忽略掉 word order information。举例来说,有一个word sequence是“white blood cells destroying an infection”,另外一个word sequence是:“an infection destroying white blood cells”,这两句话的意思完全是相反的。但是用bag-of-word的方法来描述的话,它们的bag-of-word完全是一样的。虽然这两个word sequence里面有完全一摸一样的六个词汇,但因为词汇的order是不一样的,所以他们的意思一个变成positive,一个变成negative,它们的意思是很不一样的。那现在我们可以用sequence to sequence Auto-encoder这种做法来考虑word sequence order的情况下,把一个document变成一个vector。

我们可以input一个word sequence,通过Recurrent Neural Network变成一个invalid vector,然后把这个invalid vector当做decoder的输入,然后让这个decoder,找回一模一样的句子。如果今天Recurrent Neural Network可以做到这件事情的话,那Encoding这个vector就代表这个input sequence里面重要的information。在trian Sequence-to-sequence Auto-encoder的时候,不需要label data,你只需要收集大量的文章,然后直接train下去就好了。

Sequence-to-sequence 还有另外一个版本叫skip thought,如果用Sequence-to-sequence的,输入输出都是同一个句子,如果用skip thought的话,输出的目标就会是下一个句子,用sequence-to-sequence得到的结果通常容易表达,如果要得到语义的意思的,那么skip thought会得到比较好的结果。这个结构甚至可以是hierarchical,每一个句子都先得到一个vector(Mary was hungry得到一个vector,she didn’t find any food得到一个vector),然后把这些vector加起来,然后变成一个整个 document high label vector,在让这整个vector去产生一串sentence vector,在根据每一个sentence vector再去解回word sequence。这是一个四层的LSTM(从word 变成sentence sequence ,变成document lable,再解回sentence sequence,再解回word sequence。

Sequence - to - sequence Auto - encoder - Speech

这种Sequence to sequence Auto encoder的方法也可以用到语音上,在语音上,它可以把一段audio segment变成一个fixed length vector。比如说,左边有一段声音讯号,长长短短都不一样,那你把他们变成vector的话,可能dog跟dogs比较接近,never和ever比较接近,可以称之为audio auto vector。一般的auto vector它是把word变成vector,这里是把一段声音讯号变成一个vector。

这个东西有什么用呢?它可以做很多的事。比如可以拿来做语音的搜寻,假设你有一个声音的data base(比如说,上课的录音,你想要找跟美国白宫有关的东西,然后你说一句话-“美国白宫”,machine不需要做语音辨识,直接比对声音讯号的相似度,machine就可以从data base里面把提到“美国白宫”的部分找出来)。它的实现过程可以是这样的:你先把一个audio data base做segmentation,切成一段一段的。然后每一个段用刚才讲的audio segment to vector这个技术,通通变成vector。然后现再输入一个spoken query,可以通过audio segment to vector技术也变成vector,接下来计算它们的相似程度,找出最相似的就是要搜寻的结果。

那么如何把一个audio segment变成一个vector呢?把audio segment抽成acoustic features,然后把它输入到RNN里面去,这个RNN的角色就是Encoder,它读过acoustic features之后,最后一个时间点它存在memory里面的值就代表了input声音讯号的information。它存到memory里面的值是一个vector,这个其实就是我们要拿来表示整段声音讯号的vector。

但是只要RNN Encoder没有办法去train,同时你还要train一个RNN Decoder。Decoder的作用是把Encoder得到的值存到memory里面的值,拿进来当做input,然后产生一个acoustic features sequence。然后希望这个$y_1$跟$x_1$越接近越好。然后再根据$y_1$产生$y_2$,以此类推。今天训练的target$y_1,y_2,y_3,y_4$跟$x_1,x_2,x_3,x_4$越接近越好。在训练的时候,RNN Encoder跟RNN Decoder是一起train的。

我们在实验上得到一些有趣的结果,下图中的每个点其实都是一段声音讯号,你把声音讯号用刚才讲的 Sequence-to-sequence Auto-encoder技术变成平面上一个vector。发现说:fear这个位置在左上角,near的位置在右下角,他们中间这样的关系(fame在左上角,name在右下角)。发现把fear的开头f换成n,跟fame的开头f换成n,它们的word vector的变化方向是一样的。现在这个技术还没有把语义加进去。

我们可以用Sequence-to-sequence Auto-encoder来做很多有意思的工作,比如训练一个chat-bot(聊天机器人)。你可以收集很多的对话,比如说电影的台词,在电影中有一个台词是“How are you”,另外一个人接“I am fine”。那就告诉machine说这个sequence to sequence learning当它input是“How are you”的时候,这个model的output就要是“I am fine”。你可以收集到这种data,然后就让machine去 train。这里我们就收集了四万句和美国总统辩论的句子,然后让machine去学这个sequence to sequence这个model。

5.3. Attention - based Model

现在除了RNN以外,还有另外一种有用到memory的network,叫做Attention-based Model(基于注意力的模型),这个可以想成是RNN的进阶的版本。我们知道人的大脑有非常强的记忆力,所以你可以记得非常非常多的东西。比如说,你现在同时记得早餐吃了什么,同时记得10年前夏天发生的事,同时记得在这几门课中学到的东西。那当然有人问你说什么是deep learning的时候,那你的脑中会去提取重要的information,然后再把这些information组织起来,产生答案。但是你的脑中会自动忽略那些无关的事情,比如说,10年前夏天发生的事情等等。

其实machine也可以做到类似的事情,machine也可以有很大的记忆的容量。它可以有很大的data base,在这个data base里面,每一个vector就代表了某种information被存在machine的记忆里面。当你输入一个input的时候,这个input会被丢进一个中央处理器,这个中央处理器可能是一个DNN/RNN,那这个中央处理器会操控一个Reading Head Controller,这个Reading Head Controller会去决定这个reading head放的位置。machine再从这个reading head 的位置去读取information,然后产生最后的output。

这个model还有一个2.0的版本,它会去操控writing head controller。这个writing head controller会去决定writing head 放的位置。然后machine会去把它的information通过这个writing head写进它的data base里面。所以,它不仅有读的功能,还可以discover出来的东西写入它的memory里面去。这个就是大名鼎鼎的Neural Turing Machine(神经图灵机)

Reading Comprehension

Attention-based Model 常常用在Reading Comprehension(阅读理解)里面。所谓Reading Comprehension就是让machine读一堆document,然后把这些document里面的内容(每一句话)变成一个vector。每一个vector就代表了每一句话的语义。比如你现在想问machine一个问题,然后这个问题被丢进中央处理器里面,那这个中央处理器去控制一个reading head controller,去决定现在在这个data base里面哪些句子是跟中央处理器有关的。假设machine发现这个句子是跟现在的问题是有关的,就把reading head放到这个地方,把information 读到中央处理器中。读取information这个过程可以是重复数次,也就是说machine并不会从一个地方读取information,它先从这里读取information以后,它还可以换一个位置读取information。它把所有读到的information收集起来,最后给你一个最终的答案。

下图是在baby corpus上的结果,baby corpus是一个Q&A的一个简单的测试。我们需要做的事就是读过这五个句子,然后说:what color is Grey?,得到正确的答案。那你可以从machine attention的位置(也就是reading head 的位置)看出machine的思路。图中蓝色代表了machine reading head 的位置,Hop1,Hop2,Hop3代表的是时间,在第一个时间点,machine先把它的reading head放在“greg is a frog”,把这个information提取出来。接下来提取“brian is a frog” information ,再提取“brian is yellow”information。最后它得到结论说:greg 的颜色是yellow。这些事情是machine自动learn出来的,machine attention在哪个位置,这些通过neural network学到该怎么做,要先看哪个句子,再看哪个句子,这些都是machine自动去决定的。

Visual Question Answering

Attention-Based model也可以做Visual Question Answering(VQA),比如让machine看下面的那张图片,问它这是什么,要它可以正确回答说是香蕉。

这个Visual Question Answering可以这样来实现:先让machine看一张图,然后通过CNN你可以把这张图的一小块region用一小块的vector来表示。接下来,输入一个query,这个query被丢到中央处理器中,这个中央处理器去操控reading head controller,这个reading head controller决定读取的位置(是跟现在输入的问题是有关系的),这个读取的process可能要好几个步骤,machine会分好几次把information读到中央处理器,最后得到答案。

Speech Question Answering

Attention-Based model也可以做Speech Question Answering 。比如说:在语音处理实验上我们让machine做TOEFL Listening Comprehension Test 。让machine听一段声音,然后问它问题,从四个选项里面,machine选择出正确的选项,那machine做的事情其实是跟人类考生做的是一样的。

那用的Model Architecture跟我们上面讲到的其实大同小异,你让machine先读一个question,然后把question做语义的分析得到question的语义,声音的部分是让语音辨识先转成文字,在把这些文字做语义的分析,得到这段文字的语义。那machine了解question的语义然后就可以做attention,决定在audio story里面哪些部分是回答问题有关的。这就像画重点一样,machine画的重点就是答案,它也可以回头去修正它产生的答案。经过几个process以后,machine最后得到的答案跟其他几个选项计算相似度,然后看哪一个想项的相似度最高,它就选那一个选项。那整个test就是一个大的neural network,语音辨识,question semantic部分和audio semantic部分都是neural network,它们都是可以训练的。

下图是五种naive的方法得到的结果方法,也就是完全不管文章的内容,直接看问题跟选项就猜答案。random 的正确率是25 percent,有两个方法要比25%要好的:每次都选最短的那个选项就会得到35%的正确率;如果分析四个选项的semantic,用sequence-to-sequence autoencoder,去把一个选项的semantic找出来,然后再去看某个选项和另外三个选项的相似度,每次都选择相似度最高的那个选项,这样做正确率有36%左右。

另外还可以用memory network的方法,可以得到39.2 %正确率;如果用我们上面讲到的那个model的话,可以做到48.8%正确率。

(后面还有一部分内容是比较RNN和Structured learning,这部分等我学习了Structured learning后再来补充。)