RNN应用案例
智慧订票系统
例如一些智慧客服或者智慧订票系统里,往往需要槽填充。
什么是槽填充?
系统里有两个槽位,分别叫做目的地和到达时间。假如一个人对订票系统说“ i would like to arrive Taipei on November 2nd”,那么系统要自动知道每个词汇属于哪一个槽位,例如“Taipei”属于目的地这个槽位,“November 2nd”属于到达时间这个槽位,其他词汇不属于任何槽位。
如何进行槽填充?
思考是否可以用一个前馈神经网络来解
input一个词汇,例如把“Taipei”转化为向量丢到神经网络里去
如何把词汇转化为向量?
最简单的方式是1-of-N encoding,类似独热编码
如何把词汇转化为向量?
dimension for “Other”:
有时候只用1-of-N encoding描述一个词汇会出现问题,因为向量是根据词典制作的,如果出现了不在词典里的新词汇,原来的向量就没法表达,那么在原来向量里添加一个新的维度“other”,所有遇到的新词汇都归到“other”类。比如“Gandalf、Sauron”不在词典里,就归为“other”类。
Word hashing:
也可以用某个词汇的字母来表示成词向量,如果用n-gram(n元)字母表示的话,就不会出现不在词典中的新词汇问题。比如一个词汇“apple”,用三元字母可表示为“app”,“ppl”,“ple”,那么"apple"在词向量里的“app”、“ppl”、“ple”维度都为1,其余维度为0
把词汇表示为词向量后,作为input丢到前馈神经网络里去,在槽填充这个任务里,希望输出是一个概率分布(属于哪个槽的概率)。
例如上图Taipei属于槽“目的地”的概率、属于槽"出发时间"的概率等等。
光有前馈神经网络是不能够做槽填充的
为什么不够?
假设一个使用者说"arrive Taipei on November 2nd",arrive是other,Taipei是目的地,on是other,November是时间,2nd也是时间。另外一个使用者说"leave Taipei on November 2nd",那Taipei就应该是出发地而不是目的地。但是对前馈神经网络来说,输入一样,输出也是一样的,不能让Taipei的输出即是目的地又是出发地。这个时候我们就希望神经网络是有记忆力的,在看到红色Taipei的时候,记得Taipei前的arrive,看到绿色Taipei的时候记得leave,可以根据词语的上下文,产生不同的输出。那有记忆的神经网络就做到相同输入,不同输出。
什么是RNN
有记忆的神经网络叫做RNN循环神经网络
每次隐藏层里的神经元产生输出的时候,这个输出会被存到memory里去(上图蓝色方块表示memory)。下一次有输入的时候,神经元不是只考虑\(\large x_1,x_2\) ,还会考虑存在memory里的值,即\(\large a_1,a_2\)也会影响神经元的输出。
举个例子
上图网络所有的权重为1,所有的神经元没有偏置,假设所有的激活函数都是线性激活函数(让计算不要太复杂)。
现在假设输入是一个序列\(\begin{bmatrix} 1 \\ 1 \\ \end{bmatrix}\begin{bmatrix} 1 \\ 1 \\ \end{bmatrix}\begin{bmatrix} 2 \\ 2 \\ \end{bmatrix} \cdots\),输入到循环神经网络里。在开始使用RNN前,要先设置memory的初始值,现在假设初始值为0,0。输入1,1后,上图第一个绿色神经元除了连接到输入1,1外,还连接到memory 0,0,那么输出等于2,第二个绿色神经元输出因为2,那么两个红色神经元的输出都为4。
输入[1,1],输出[4,4]
接下来RNN会把绿色神经元的输出存在memory里去,memory里的值更新为2
再输入[1,1],这时候绿色神经元的输入为4个,[1,1]和[2,2],权重都为1,所以输出为2+2+1+1=6,最后红色神经元的输出为6+6=12
所以对RNN来说,就算是同样的输入,也可能会有不同的输入,因为存在memory里的值是不一样的
因为绿色神经元的输出是[6,6],被存到memory里去,memory的值更新为6
接下来输入[2,2],绿色神经元考虑4个输入,[2,2]和[6,6],输出为6+6+2+2=16,那么红色神经元的输出为16+16=32
做RNN的时候有一件很重要的事情是,RNN认为输入的序列不是独立的,如果调换输入序列的顺序(比如把\(\begin{bmatrix} 2 \\ 2 \\ \end{bmatrix}\)换到最前面),那么输出就会完全不一样,所以RNN会考虑输入序列的顺序
RNN架构
用RNN做槽填充
有一个使用者说“arrive Taipei on November 2nd”,则把arrive变成一个向量丢到神经网络里,隐藏层的输出为\(\large a^1\) (是一排神经元的输出,是一个向量),根据\(\large a^1\) 产生\(\large y^1\) (arrive属于哪一个槽的概率),然后\(\large a^1\)被存到memory里去。
接下来输入表示Taipei的向量,隐藏层会同时考虑Taipei和memory \(\large a^1\),得到\(\large a^2\),再根据\(\large a^2\)得到\(\large y^2\)(Taipei属于哪一个槽的概率),然后\(\large a^2\)被存到memory里去。
后面的词语处理类推之前的过程,输入表示on的向量,隐藏层同时考虑输入和memory \(\large a^2\),得到\(\large a^3\)再得到\(\large y^3\)(on属于哪一个槽的概率)。
这里要注意,有人看到上图会觉得有3个网络,其实是同一个网络在3个不同的时间点被使用了3次。上图同样的weight用相同的颜色表示。
有了memory之后,输入相同,就可以有不同输出了。例如上图的Taipei,红色Taipei前面接的是leave,绿色Taipei前面接的是arrive,因为leave和arrive的向量不同,所以memory 的值不同,隐藏层的输出也会不同。
其他RNN架构
Elman Network
保存隐藏层的输出值,在下一个时间点被隐藏层使用
RNN的架构是可以任意设计的,可以变得很深
叠加隐藏层,每个隐藏层的输出都被存在memory里,在下一个时间点的隐藏层会读取上一个时间点保存的memory 值。
Jordan Network
保存整个网络的输出值,在下一个时间点被隐藏层使用
据说Jordan Network 可以得到更好的效果,因为隐藏层是没有学习目标的,学什么样的隐藏信息很难控制(学到的东西放到memory里),但是\(y\)(输出层)是有学习目标的,可以比较清楚地知道存在memory里的是什么样的东西
双向RNN
RNN可以是双向的
之前input一个句子是从句首读到句尾,假设词语用\(x^t\)表示,那就是先读\(x^t\),再读\(x^{t+1}\),再读\(x^{t+2}\)
读取方向也可以反过来,先读\(x^{t+2}\),再读\(x^{t+1}\),再读\(x^{t}\),可以同时训练一个正向的RNN和一个反向的RNN,然后把两个RNN的隐藏层拿出来,接到同一个输出层得到最后的\(y\)
如上图,正向RNN的\(x^t\)的输出和反向RNN的\(x^t\)的输出,都输入到一个输出层得到\(y^t\)
用双向RNN有什么好处?
双向RNN在产生输出的时候,看得范围是比较广的。如果只有正向RNN,在输出\(y^{t+1}\)时,RNN只看过\(x^1\)到\(x^{t+1}\)的部分,但是双向RNN在输出\(y^{t+1}\)时,不仅看了\(x^1\)到\(x^{t+1}\)的所有输入,而且看了句尾到\(x^{t+1}\)的所有输入,等于看了整个输入序列,再去决定词汇的槽位是什么,这样效果比只看一半句子会好。
LSTM
之前的RNN是比较简单的版本,随时可以把值存到memory中,也可以随时读取memory中的值
现在比较常用的memory称为Long Short-term Memory(长短期记忆单元),比较复杂有3个门
- 输入门:隐藏层的值要存到memory时,要先通过输入门,被打开时才可以把值保存到memory,门是打开还是关闭是神经元自己学习的。
- 输出门:输出的地方也有一个门,表示其他的神经元可不可以从memory里读取值,只有被打开的时候才可以读取,输出门什么时候打开关闭也是神经元自己学习的。
- 遗忘门:第三个门,表示什么时候memory要把过去保存的东西忘记,或者什么时候要把保存的东西做一些格式化,格式化掉还是保存下来也是神经元自己学习的。
一个LSTM memory cell,可以看成有4个输入,1个输出。4个输入分别是
- 想要被存到memory cell里的值(由输入门控制是否保存)
- 操控输入门的信号
- 操控输入门的信号
- 操控遗忘门的信号
一个小小的冷知识,Long Short-term里的划线应该被放在哪里?
有些人把划线放在Long和Short之间,这是没有意义的,应该放在Short和term之间
因为LSTM的memory还是一个比较短期的记忆(只是稍微长一点的短期记忆),RNN的memory在每个时间点都会被更新掉,只保存前一个时间点的东西,LSTM可以记得稍微长一点的东西,只要遗忘门不要决定遗忘掉
详细的LSTM cell 公式如上图所示。
- \(\large z\)是输入
- \(\large z_i\)(一个数值)是操控输入门的信号
- \(\large z_f\)是操控遗忘门的信号
- \(\large z_o\)是操控输出门的信号
- 最后得到一个输出\(\large a\)
假设一个单元里,有上面4个输入(\(\large z,z_i,z_f,z_o\))之前,已经保存了值\(\large c\)
\(\large z_i,z_f,z_o\)通过的激活函数通常选择sigmoid函数(取值0-1,代表门被打开的程度),激活函数输出为1代表门是处于打开的状态,为0表示门处于关闭的状态
计算当前时刻要保留的新信息\(\large g(z)f(z_i)\)
- \(\large z\)通过一个激活函数得到\(\large g(z)\) ,\(\large z_i\)通过另一个激活函数得到\(\large f(z_i)\)(当前时刻信息保留程度)
- 把\(\large g(z)\)乘以\(\large f(z_i)\)
接下来计算当前时刻的保留信息(新的memory值)\(\large c'=g(z)f(z_i)+cf(z_f)\)
- \(\large z_f\)通过另一个激活函数得到\(\large f(z_f)\)(上一个时刻的信息保留程度)
- 把存在memory 里的\(\large c\)乘上\(\large f(z_f)\) (上一时刻要保留的信息)
- 计算\(\large c'=g(z)f(z_i)+cf(z_f)\) (当前时刻要保留的新信息+上一时刻要保留的信息)
- \(\large c'\)是新的保存在memory里的值(当前时刻的保留信息)
根据到目前为止的计算发现
- \(\large f(z_i)\)在控制\(\large g(z)\),\(\large f(z_i)\)=0就好像没有输入,\(\large f(z_i)\)=1就好像直接把\(\large g(z)\)输入
- \(\large f(z_f)\)决定要不要把之前存在memory里的\(\large c\)洗掉,\(\large f(z_f)\)=1意思是保留之前的值,\(\large f(z_f)\)=0意思是洗掉之前保留的值
遗忘门跟我们直觉的想法是相反的,遗忘门打开的时候表示记得,关闭的时候表示遗忘
最后计算要输出的信息\(\large a=h(c')f(z_o)\)
- \(\large c'\)通过\(h\)得到\(\large h(c')\)
- 遗忘门受\(\large z_o\)操控,\(\large z_o\)通过\(f\)得到\(\large f(z_o)\) ,\(\large f(z_o)\)=0表示memory的值无法通过输出门
- 把\(\large h(c')\) 和\(\large f(z_o)\) 乘起来(当前时刻要输出的信息)
LSTM计算例子
一个LSTM例子
在网络里只有一个LSTM的单元,输入是三维的向量,输出是一维的向量
三维的输入和输出、memory的值有什么关系?
假设
- 第2个维度\(\large x_2\)的值为1的时候,\(\large x_1\)的值被保存到memory里
- \(\large x_2\)的值为-1的时候,memory的值被遗忘(重置)
- \(\large x_3\)为1的时候,输出门被打开,才可以有输出
如上所示的,假设原来memory的值为0
- 第2个时间点的\(\large x_2=1\),3被存到memory里,则第3个时间点的memory值为3
- 第4个时间点的\(\large x_2=1\),所以4被存到memory,则第5个时间点的memory为7
- 第6个时间点的\(\large x_3=1\),所以输出memory的值7
- 第7个时间点的\(\large x_2=-1\),memory的值被重置,则第8个时间的memory为0
- 第8个时间点的\(\large x_2=1\),6被存到memory,则第9个时间的memory为6
- 第9个时间点的\(\large x_2=1\),memory的值为6,且\(\large x_3=1\),所以输出memory的值6
实际做一下运算,上图是一个LSTM的memory cell,有4个输入值
4个输入值为:
- \(\large z\):三维向量值+偏置的一个线性转换
- \(\large z_i\):三维向量值+偏置的一个线性转换
- \(\large z_f\):三维向量值+偏置的一个线性转换
- \(\large z_o\):三维向量值+偏置的一个线性转换
\(x_1、x_2、x_3\)的权重和偏置是通过训练数据(梯度下降)学习的,现在先假设我们已经知道了
来分析下可能得到的结果
在input部分,转换公式为 \(1 \times x_1\) ,就是把\(x_1\)当做input
- 在输入门部分,转换公式为\(100 \times x_2-10 \times 1\),如果\(x_2\)没有值,由于偏置为-10,通过激活函数后的值接近于0,代表门是关闭的,只有\(x_2\)有值(比如1)的时候,激活函数的值接近于1,代表门是开的
- 在遗忘门门部分,转换公式为\(100 \times x_2+10 \times 1\),偏置是10,代表输出门平时是打开的,只有\(x_2\)取一个很大的负值的之后,门才会关闭
- 在输出门部分,转换公式为\(100 \times x_3-10 \times 1\),偏置是-10,代表输出门平时是关闭的,只有\(x_3\)取一个很大的正值的之后,门才会打开
假设\(\large g\)和\(\large h\)都是线性的,这样计算比较方便,memory 初始值为0
input第一个向量\(\begin{bmatrix} 3 \\ 1 \\ 0\end{bmatrix}\)
- 向量转化后输出为3
- 输入门\(\approx1\),是打开的
- 通过输入门的输出为\(1 \times 3 =3\)
- 遗忘门\(\approx1\),是打开的
- 通过遗忘门,memory更新为\(0 \times 1+3=3\)
- memory通过线性函数转化后仍为3
- 输出门\(\approx 0\),是关闭的
- 无法通过输出门,最后的输出为0
接下来input 向量\(\begin{bmatrix} 4 \\ 1 \\ 0\end{bmatrix}\)
- 向量转化后输出为4
- 输入门\(\approx1\),是打开的
- 通过输入门的输出为\(1 \times 4 =4\)
- 遗忘门\(\approx1\),是打开的
- 通过遗忘门,memory更新为\(1 \times 3+4=7\)
- 输出门\(\approx 0\),是关闭的
- 无法通过输出门,最后的输出为0
接下来input 向量\(\begin{bmatrix} 2 \\ 0 \\ 0\end{bmatrix}\)
- 向量转化后输出为2
- 输入门\(\approx 0\),是关闭的
- 通过输入门的输出为\(0 \times 2 =0\)
- 遗忘门\(\approx1\),是打开的
- 通过遗忘门,memory更新为\(1 \times 7+0=7\)
- 输出门\(\approx 0\),是关闭的
- 无法通过输出门,最后的输出为0
接下来input 向量\(\begin{bmatrix} 1 \\ 0 \\ 1\end{bmatrix}\)
- 向量转化后输出为1
- 输入门\(\approx 0\),是关闭的
- 通过输入门的输出为\(0 \times 1 =0\)
- 遗忘门\(\approx1\),是打开的
- 通过遗忘门,memory更新为\(1\times 7+0=7\)
- 输出门\(\approx 1\),是打开的
- 通过输出门,最后的输出为\(7\times 1=7\)
最后input 向量\(\begin{bmatrix} 3 \\ -1 \\ 0\end{bmatrix}\)
- 向量转化后输出为3
- 输入门\(\approx 0\),是关闭的
- 通过输入门的输出为\(0 \times 3 =0\)
- 遗忘门\(\approx 0\),是关闭的
- 通过遗忘门,memory更新为\(0 \times 7+0=0\)
- 输出门\(\approx 0\),是关闭的
- 无法通过输出门,最后的输出为0
会有问题,这个东西和一般的神经网络很不像啊
LSTM和一般的神经网络有什么关系?
在一般的神经网络里有很多的神经元,会把input 乘上很多不同的权重,作为不同神经元的输入,每个神经元都是一个函数,输入一个值,输出另一个值。
把LSTM的memory cell 想成是一个神经元
现在input \(\large x_1,x_2\)会乘上不同的权重,当做LSTM的不同的输入
例如上图第一个LSTM
- \(\large x_1,x_2\)乘上一组权重产生一个值,去操控输出门
- \(\large x_1,x_2\)乘上一组权重产生一个值,去操控输出门
- \(\large x_1,x_2\)乘上一组权重产生一个值,去操控遗忘门
对第二个LSTM也一样
所以刚才讲过LSTM有4个input,1个output,对LSTM来说4个input是不一样的。在原来的神经元里,就是1个input,1个output。
假设LSTM cell的数量和一般神经网络的神经元数量相同,那LSTM需要的参数量就是一般神经网络的4倍。
LSTM看起来也不像RNN,那它跟RNN有什么关系
LSTM跟RNN的关系是什么?
假设有一整排的LSTM cell,每个cell的memory都存了一个值
把所有memory值接起来变成一个向量,写成\(\large c^{t-1}\) ,memory cell里存的memory 值代表向量的一个维度
在时间点\(\large t\) input一个向量\(\large x^t\)
-
\(\large x^t\)首先乘上一个线性的转换函数(一个矩阵),变成另外一个向量\(\large z\)
\(\large z\) 的每个维度是每个LSTM cell的input,第一维丢给第一个cell,第二维丢给第二个cell,以此类推
-
\(\large x^t\)再乘上另一个线性的转换函数(一个矩阵)得到\(\large z^i\)
\(\large z^i\) 的每个维度是每个LSTM cell的输入门的input值(来操控输入门打开的程度)
-
\(\large x^t\)再乘上另一个线性的转换函数(一个矩阵)得到\(\large z^f\)
\(\large z^f\) 的每个维度是每个LSTM cell的遗忘门的input值(来操控遗忘门打开的程度)
-
\(\large x^t\)再乘上另一个线性的转换函数(一个矩阵)得到\(\large z^o\)
\(\large z^o\) 的每个维度是每个LSTM cell的输出门的input值(来操控输出门打开的程度)
得到4个向量,每个向量的维度和cell 个数相同,那么4个向量合起来就会去操控所有memory cell的运作。
注意4个\(\large z\)都是向量,input到每个cell里的只是向量中的一个维度,那么丢进不同cell的是不同维度的数据
虽然每个cell input的数据不一样,但是可以被一起运算
所有的cell怎么被一起运算?
\(\large z^i\)通过激活函数的值,要乘以\(\large z\),即计算当前时刻要保留的新信息
\(\large z^f\)通过激活函数的值,要乘以memory 保存的值,即计算上一时刻要保留的信息
\(\large z^o\)通过激活函数的值,即保留信息的输出程度
然后当前时刻要保留的新信息+上一时刻要保留的信息,再通过一个激活函数得到一个值,(这个值)乘(保留信息的输出程度)得到最后的输出\(\large y^t\)
当前时刻要保留的新信息+上一时刻要保留的信息=当前时刻要保留的信息(memory里要存的值\(\large c^t\))
当前时刻要保留的信息 \(\times\) 保留信息的输出程度=要输出的信息(\(\large y^t\))
计算过程反复继续下去,在下一个时间点input \(\large x^{t+1}\)
上图不是LSTM的最终形态,只是一个简单的版本
真正的LSTM会把输出接进来,把隐藏层的输出当做下一个时间点的input ,下一个时间点操控门的值不只是只看\(x^{t+1}\),还会看\(h^t\)。
还会加一个叫“peephole”的东西,peephole会把存在memory cell里的值也拉过来。所以在计算4个\(\large z\)的时候,同时考虑了\(\large x,h,c\) 。\(\large x,h,c\)并在一起乘上4个不同的transform,得到4个不同的\(\large z\)向量,再去操控LSTM的门。
LSTM通常不会只有一层,多层如上图所示
RNN架构如何学习
之前说过,model要学习的话,要定义一个损失函数来评估model的参数是好是坏,选一个让损失函数最小的参数。
在RNN里如何确定损失函数?
假设我们的任务是做槽填充,给你一些句子的训练数据(词语和label)
把“arrive"input进RNN得到一个输出\(\large y^1\) ,\(\large y^1\)会和一个reference 向量计算交叉熵。希望丢进去arrive后输出的\(\large y^1\),对应到other=1,其余为0的reference 向量。reference 向量的维度是槽位的数目,有40个槽位,则reference 向量的维度为40。
把Taipei(\(x^2\))input进去,希望\(y^2\)和reference 向量的距离越近越好,reference 向量里dest槽位为1,其余槽位为为0。这里要注意,input Taipei之前一定要先input arrive,不然就永远不知道memory里的值是多少了。
在训练的时候,不能把word sequence打散来看,word sequence仍要当成一个整体
所以损失函数就是每一个时间点输出和reference 向量的交叉熵之和,去最小化。
有了损失函数后怎么做?
也是使用梯度下降,现在有损失函数\(\large L\) ,使用梯度下降更新网络里的每一个参数\(\large w\)
在前馈神经网络里,为了实现高效率的梯度下降,设计出了BP算法
在RNN里,也有一种高效率的计算方式叫BPTT(Back propagation through time),是BP的进阶算法,和BP很类似,由于RNN是在时间序列上运作,所以BPTT考虑了时间的信息。
RNN学习中的问题
不幸的是RNN的训练是比较困难的
一般我们在训练的时候,希望学习误差可以像上图蓝线一样,随着迭代次数增加,误差减小,但是在训练RNN时,会出现上图绿线的情况,随着迭代次数增加,训练误差剧烈抖动,甚至出现NAN
这个时候会有什么想法呢?
第一个想法是程序有BUG啊
这个时候会有什么想法呢?
如上图纵轴是Total Loss,x轴和y轴是两个参数\(\large w_1,w_2\)
发现RNN的误差超平面非常崎岖,有些地方非常平坦,有些地方非常陡峭,就像悬崖峭壁一样。
RNN的误差超平面非常崎岖会造成什么样的问题?
假设橙点(-2.2和-2.0之间的那个点)为初始点,使用梯度下降调整参数,可能跳过悬崖导致Loss暴增(上下剧烈震荡)。有时候会正好处于悬崖脚下,之前的梯度很小(学习率调地比较大),而该点的梯度很大,再乘以一个比较大的学习率,参数就飞出去了(更新过多)变成NAN,程序就出错了。
怎么处理误差超平面非常崎岖的问题?
使用裁剪,当梯度大于某一个阈值时就进行裁剪,比如当梯度大于15时裁剪为15。那就算是在悬崖脚上,使用的梯度也不会太大,参数不会飞出去。
为什么RNN会有这种奇特的特性?
是不是可能来自于sigmoid函数,之前讲ReLU的时候,说过梯度消失的问题,并且是从sigmoid函数来的。可能想到使用Relu试试,但在实际训练RNN时,很少使用Relu,因为Relu的效果也是比较差的,所以激活函数不是这个问题的关键点。
有一个直观的方法来看一个梯度的大小,把某个参数做个小小的变化,看对输出的变化有多大,就可以知道这个参数梯度的大小。
举一个简单的RNN例子:
有一个最简单的RNN,只有一个线性的神经元,input没有偏置,input权重为1,output权重也为1,transition部分的权重为\(\large w\) (memory 接到神经元的权重为\(\large w\)) 。
现在input [1 0 0 0 0 0 ...],第一个时间段为1,接下来都是0,那最后一个时间点的输出\(y^{1000}=w^{999}\) 。
\(\large w\)是要训练的参数,需要知道它的梯度大小,即改变\(\large w\)的值,对输出有多大的影响。如果\(\large w=1\),那\(y^{1000}=1\) ,当\(\large w=1.01\)时,\(y^{1000}\approx 20751\)。意味着\(\large w\)有一点小小的变化,对output的影响非常大,所以\(\large w\)有很大的梯度。可能会想把它的学习率设小一点,梯度大也没什么关系。
但是事实上,如果把\(\large w\)设成0.99,那\(y^{1000} \approx 0\),如果把\(\large w\)设成0.01,\(y^{1000}\)还是约等于0。也就是说,在1这个地方有很大的梯度,但在0.99这个地方梯度就变得非常小,这时候又需要一个很大学习率。这样就说明误差超平面很崎岖,设定学习率很麻烦,因为梯度时大时小,而且在非常短的区域内,梯度就会有很大的变化。
从这个例子看,RNN的问题来自于在transition的时候,不同时间点反复使用同样的东西(从memory接到神经元的\(\large w\) )。导致\(\large w\)一有变化,有可能对梯度没有影响(0.99和0.01的情况),也有可能造成天崩地裂的影响(1.01的情况),造成梯度时大时小。
所以RNN不容易训练的问题不是来自于激活函数,而是来自于它有time sequence,同样的\(\large w\)在不同的时间点被反复使用。
如何解决RNN学习中的问题
LSTM
有什么技巧可以解决RNN训练的问题?
现在最广泛使用的技巧就是LSTM,LSTM可以把误差超平面比较平坦的地方拿掉,可以解决梯度消失的问题,但是不会解决梯度爆炸的问题,有些地方仍然非常崎岖(变化非常剧烈)。LSTM让大部分的地方变化都很剧烈,就可以一直使用较小的学习率。
为什么LSTM可以处理梯度消失问题?
RNN和LSTM处理memory 的操作是不一样的
RNN在每个时间点,memory 的信息会被洗掉,然后存入当前时间点的信息
LSTM在每个时间点,是把原来memory的值乘上一个值(上一时刻信息保留程度),再加上当前时刻要保留的新信息,作为新的memory的值。LSTM的memory 和 input 是相加的。
如果weight可以影响memory的值,这种影响在RNN中的每个时间点会被重置掉,而在LSTM中影响会遗留下来,除非遗忘门关闭(=0,意味着洗掉上一个时刻保留信息),所以可以处理梯度消失问题。
可是LSTM有遗忘门啊,遗忘门关闭的时候,就不能遗留影响了啊?
LSTM第一个版本是没有遗忘门的,设计出来就是为了处理梯度消失问题,遗忘门是后来加上去的。
现在有说,要给遗忘门设置一个比较大的偏置,确保大部分情况下不是关闭的,只有少数情况会关闭洗掉memory的值。
GRU
现在有另外一个用门操控memory cell的方法叫做门控循环单元(GRU),GRU只有2个门,因此需要的参数比LSTM少1/3,参数少在训练的时候会更鲁棒。如果在训练LSTM的时候觉得过拟合比较严重,那可以试一下GRU。
GRU为什么可以少用一个门?
精神就是旧的不去,新的不来,把输入门和遗忘门连动起来。当输入门打开的时候,遗忘门自动关闭(=0), 存在memory的值会被洗掉,当遗忘门打开的时候,输入门关闭。要把存在memory的值清掉,才可以把新的值放进去
更多其他的方法
还有很多其他技术可以用来处理梯度消失问题,比如Clockwise RNN和SCRN等等。
还有一个,使用一般RNN,但是用单位矩阵初始化为transition的weight,然后使用ReLU作为激活函数,可以得到比LSTM好的效果。如果transition的weight是随机初始化的,那么使用ReLU会比使用sigmoid的效果差。
RNN的应用
在前面槽填充的例子里,假设input和output的数量一样多,input几个word就给几个word槽的label
Many to One
情感分析
RNN可以做到更复杂的事情,input一个序列,output一个向量,这样就可以做情感分析
比如公司想要知道产品在网络上的评论是正面的还是负面的。
如何做情感分析?
学习一个RNN,input是一个character sequence,RNN把这个sequence读一遍,在最后一个时间点把隐藏层输出拿出来,再通过几个transform,最后得到情感分析的预测结果。
关键词提取
收集一堆训练数据集(许多文档),每个文档都有label(哪些词语是关键词),那就可以直接训练一个RNN。这个RNN把document word sequences作为input,然后通过嵌入层,用RNN把这个文档读一次,把最后一个时间点的output拿出来做attention,把重要的信息抽出来,再丢到前馈神经网络里去,得到最后的output。
Many to Many
语音识别
前面的应用是多对一的,RNN也可以多对多。比如input/output 都是sequences,但是output sequences比input sequences短。
什么样的任务是output sequences短,input sequences长呢?
比如语音识别,input是一串acoustic feature sequence(声学特征序列),output是character sequence。
语音是一段声音讯号,一般处理声音讯号的方式就是每隔一小段时间,就用一个向量表示。一小段时间通常很短,比如0.01秒。
如果是使用槽填充的RNN,那把这一串input丢进去,只能告诉你每一个向量对应哪一个character。如果是中文的语音识别,output 的target就是所有可能的中文词汇,常用的有8000个。input的每一个向量对应的时间很短,通常是0.01秒,所以通常是好多个向量对应同一个character,识别的结果就如上图所示(好好好棒棒棒棒棒)
这不是语音识别的结果啊,怎么办?
有一招叫做trimming(修剪),把重复的东西拿掉,结果变成好棒,但这样有个问题,没办法识别好棒棒。
CTC
怎么把好棒和好棒棒分开?
使用CTC,即我们在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拿掉就变成好棒棒。
CTC怎么训练?
训练数据告诉你,这一串acoustic feature对应到一串character sequence,但是不会告诉你“好”是第几个frame对应到第几个frame,“棒”是第几个frame对应到第几个frame。
要穷举所有可能的alignment(队列),然后全部训练。
穷举太多了,有没有什么办法减少一点,自己查资料了解。
上图是文献上CTC得到的一个结果,在做英文语音识别的时候,RNN的output target就是character(英文字母)加上空白,当字母之间有边界时,就自动使用空白间隔。
举个例子:
第一个frame就是output H,第二个frame output null,第三个frame output null,第四个output I,第五个frame output s,接下来output _ 代表空白......。最后把null的部分拿掉,那语音识别的结果就是HIS FRIEND'S。你不需要告诉机器说HIS是一个词汇,FRIEND'S是一个词汇,机器通过训练数据自己学会这件事情。
用CTC,就算训练数据里没有出现的词汇,也有机会把它正确识别出来。
sequence to sequence
机器翻译
在sequence to sequence learning里面,RNN的input和output都是sequence,但是两个sequence的长度是不一样的。讲CTC的时候,input比较长,output比较短。在sequence to sequence中,不确定input和output谁比较长。
现在要做的机器翻译是,input英文word sequence,翻译成中文的character sequence,并且不知道英文和中文谁比较长。
假设输入的是“machine learning”,用RNN读过去,在最后一个时间点memory存了所有input的整个sequence的信息。
接下来让机器output一个character(机),把机当做input,再把memory里的值读进来,会output 器。机要怎么接,有很多细末的技巧。
在下一个时间点,器之后output 学,学之后output 习......,第一次看到model不知道什么时候停止。
要怎么阻止机器不断地产生词汇呢?
要多加一个symbol叫做 断,所以机器不只output所有可能的character,还有一个可能的output 断。习后面是“===”(断)的话,就停止。可能会疑惑这个东西怎么训练,神奇的是可以训练且有用的,也会用在语音识别上,直接inpout acoustic feature sequence,然后会output character sequence,但是这个方法没有CTC强。在翻译上,据说使用这个方法,可以达到state of the art(最高水平)。
sequence to sequence learning 假设是做翻译的话,input某种语言的文字,翻译成另外一种语言的文字。有没有可能直接input某种语言的声音讯号,output另一种语言的文字,完全不做语音识别?
是可以行得通的
比如直接input法文的声音讯号,然后model直接得到识别的结果。
假设要把台语转成英文,台语的语言识别不好做,因为台语没有一个标准的文字系统,要招人来label台语的文字也有点麻烦。使用上面的技术,只要收集台语的声音讯号和对应的英文翻译就可以了,就不需要台语语音识别的结果(不需要文字),就可以做翻译了。
句法分析树
现在还可以用sequence to sequence 的技术,甚至可以做到beyond sequence,这个技术也被用在句法分析树里。
什么是句法分析树
让机器看一个句子,然后得到句子语法的结构树。
为什么可以把树状图描述成一个sequence?
root是S,sequence为 \((S(NP\ NNP)_{NP}(VP\ VBZ(NP\ DT\ NN)_{NP})_{VP}\ .)_S\)
过去要用结构化学习的技术,现在有了sequence to sequence 的技术,只要把上图的树状图描述成一个sequence,那就只要学习一个sequence to sequence 的model,output就是句法分析树。
如果的output sequence 不符合语法结构怎么办?
如果只记得加左括号,忘了加右括号。不会这样,LSTM有记忆力,不会忘记加上右括号。
之前讲过词向量,如果把一篇文章表示成一个向量的话,往往会用bag-of-word(词袋),会忽略掉词序的信息。
举例:
有一个词语序列为white bloodcells destroying an infection,另一个词语序列是an infection destroying white blood cells,这两句话的意思是相反的,但是使用bag-of-word描述的话,它们的bag-of-word是完全一样的,有6个一模一样的词汇,但是一句话是正面的,另一句话是负面的。
可以用sequence to sequence auto-encoder这种做法,在考虑词语序列顺序的情况下,把一篇文章变成一个向量。
sequence to sequence auto-encoder怎么做?
input一个word sequence,通过一个RNN把它变成一个embedded的向量,再把这个embedded向量当成decoder的输入,让decoder长回一个一摸一样的句子。
如果RNN可以做到这个事情,那encoding的这个向量,就代表这个input sequence里的重要信息,所以decoder才能根据encoder的向量,把信息decoder回来。
训练sequence to sequence auto-encoder是不需要label data的,只需要收集大量的文章,直接训练就好了。
sequence to sequence auto-encoder还有另外一个版本叫做skip-thought。如果是用Seq2Seq auto encoder,input和output都是同一个句子,得到的code比较容易表达语法的意思,如果用skip-thought,output target 是下一个句子,容易得到语义的意思。
这个结构甚至可以是阶层的
- 可以每一个句子都先得到一个向量(变成sentence sequence)。比如Mary was hungry .得到一个向量,she didn't find any food得到一个向量。(Encode-Word)
- 再把这些向量加起来,变成一个整个document high level的向量(Encode-Sentence)
- 再用这个document high level的向量去产生一串sentence的向量(Decode-Sentence)
- 再根据每一个sentence向量解回word sequence(Decode-Word)
所以这是一个4层的LSTM
- 从word变成sentence sequence(Encode-Word)
- 再变成document level的东西(Encode-Sentence)
- 再解回sentence sequence(Decode-Sentence)
- 再解回word sequence(Decode-Word)
语音处理
Seq2Seq auto encoder也可以用在语音上,可以把一段音频段变成一段固定长度的向量。
比如上图有一堆声音讯号,长长短短都不一样,把它们变成向量的话,可能dog/dogs的向量比较接近,可能never/ever 的向量比较接近。
这个称之为音频的word to vector,word to vector是把一个word变成一个vector,这边是把一段声音讯号,变成一个向量。
Seq2Seq auto encoder在语音上有什么用?
可以拿来做语音的搜寻,有一个声音的database,然后你说美国白宫,不需要做语音识别,直接比对声音讯号的相似度,机器就从database里找到有美国白宫的部分
这个怎么做?
-
有一个audio的数据库,把这个数据库做分段,每一段用audio segment to vector的技术变成一个vector。
-
现在使用者输入一个Query(也是语音的),通过audio segment to vector的技术,把这段声音讯号也变成向量。
-
接下来计算它们的相似度,得到搜索的结果
怎么把一个音频段变成一个向量?
- 先把音频段抽成声学特征序列
- 把声学特征序列丢到RNN里,RNN每个角色就是一个encoder,最后时间点存在memory里的值代表了整个input的声音讯号的信息
存在memory里的值,是一个用来表示一整段声音讯号的向量
只有上图左下的RNN encoder没办法训练,同时还要训练一个RNN的decoder,decoder的input是encoder存在memory里的值,然后产生一个声学特征序列。
希望\(y_1\)和\(x_1\)越近越好,然后根据\(y_1\)再产生\(y_2,y_3,y_4\) ,训练的targe是\(y_1\)到\(y_4\)跟\(x_1\)到\(x_4\)越接近越好。
训练的时候RNN的encoder和RNN的decoder共同学习的(一起训练),只有单独一个是没法训练的。
上图是实验上一些有趣的结果。图上每个点都是一段声音讯号,声音讯号用sequence to sequence encoder 技术变成平面上一个向量。
会发现fear的位置在左上角,near的位置在右下角,中间会有一段关系,fame的位置在左上角,name的位置在右下角,中间也会有一段关系。
把fear的f换成n,把fame的f换成n,它们word vector的变化方向是一样的。不过这边的vector还没有办法考虑分段语义的信息。
聊天机器人
一个sequence to sequence learning (不是sequence to sequence auto-encoder)的demo,用来训练一个聊天机器人。收集很多对话,比如说电影的台词,如果电影台词里面,有一个人说"how are you",另外一个人接"i am fine",那就告诉机器说,当这个sequence to sequence learning的input是"how are you"的时候,model的output就要是"i am fine",你可以收集这种数据,让后让机器去训练。
我们收集了40000句的电视影集和美国总统大选辩论的句子,然后让机器去学这个sequence to sequence的model。
Attention-based Model
除了RNN之外,还有另外一种用到memory的神经网络,叫做attention-base model,可以想成是RNN的一个进阶版本。
我们知道人的大脑,有非常强的记忆力,可以记得非常多的东西,比如同时记得早餐吃了什么、10年前的夏天发生了什么、在这几门课学到的东西。当有人问你什么是deep learning的时候,你的大脑就会提取重要的信息,然后把这些信息组织起来产生答案,但是你的大脑会自动忽略掉哪些无关的事情,比如10年前夏天发生的事情等等。
其实机器也可以做到类似的事情,机器也可以有很大的记忆容量,可以有一个很大的database,在这个database里面,每一个向量代表某种信息,被存在机器的记忆里面。
当你输入一个input的时候,这个input会被丢进一个中央处理器(可能是一个DNN/RNN),这个中央处理器,会操控一个读头控制器(reading head controller),去决定这个reading head放的位置。然后机器再从这个reading head放的位置,去读取信息出来,产生最后的output。
这个model还有2.0的版本,它回去操控一个writing head controller,决定writing head放的位置,然后机器会把它的信息通过这个writing head写进它的database里面。所以它不只有读的功能,还可以把资讯(discover出来的东西)写进它的memory里去。这个就是大名鼎鼎的neural turing machine。
现在attention-based model常常被用在阅读理解上,让机器读一堆文章,文章里的每句话变成一个向量存起来,每个向量代表某一句话的语义。
接下来,你问机器一个问题,比如"玉山有多高"之类的,这个问题被丢进中央处理器里,中央处理器去控制一个reading head controller,决定database里哪些句子是跟中央处理器有关的。假设机器发现这个句子(上图最下方第一个橙色箭头所示的向量)与现在的问题有关,就把reading head放在这个地方,然后把信息读到中央处理器里,这个读取信息的过程可以是反复的(重复数次),也就是说机器并不只是从一个地方读取信息,在某一个位置读取信息后,还可以换个位置接着读取信息,然后把所有读到的信息收集起来,最终给一个答案。
上图是facebook AI research在QA question answer test上的一个实验结果,这个test比较简单,有很多临时产生的文章和一些简单的问题,我们需要回答这些问题。要做的事情就是读过这5个句子,然后问它"what color is Greg?",它会得出一个正确的答案“yellow”。
你可以从机器attention的位置,也就是reading head的位置,看出机器的思路。上图蓝色代表机器reading head放置的位置。Hop1/2/3代表时间,在第一个时间点,机器先把它的reading head 放在Greg is a frog,提取Greg is a frog这个信息,接下来再提取Brian is a frog这个信息,接下来再提取Brian is a yellow的信息,最后得到结论说,Greg的颜色是yellow。这些事情是机器自动学习出来的,也就是机器要attend在哪一个位置,是通过神经网络自己去学习的,不是写程序去告诉机器先看哪个句子,在看哪个句子。
那也可以做visual question answering(视觉问答),让机器看一张图,然后问它一个问题“这是什么?”,如果正确回答是香蕉的话,它就超越部分人类了。
如何做视觉问答?
让机器看一张图,通过CNN,可以把图的每一小块区域用一个向量表示。接下来输入一个Query,然后这个Query被丢到中央处理器里,中央处理器去操控reading head controller,然后这个reading head controller决定要读取资讯的位置,看看这个图片的什么位置是跟现在输入的问题是有关的。然后把信息读到中央处理器里面,这个读取的过程可能有好几个步骤,机器会分好几次把信息读到中央处理器里面,最后得到答案。
也可以做语音的问答,比如说在语音处理实验室,让机器做托福的听力测试。让机器听一段声音,然后问他问题,然后从4个正确选项里面,机器选出正确答案。
用的model architecture(模型架构)跟我们刚才看到的大同小异。让机器先读一下问题,然后对问题做语义分析得到问题的语义。声音的部分先用语音识别转成文字,再做文字语义分析,机器了解了问题的语义和音频的语义之后,就可以做attention,决定这个音频里面,哪些部分是和回答问题有关的。这个就好像是画重点一样,机器根据它画的重点产生答案。甚至可以回过头去修正它产生的答案,经过几个过程之后,机器得到最终的答案,并计算和其他选项的相似度,然后选择相似度最高的选项。
这整个task就是一个大的神经网络,处理语音识别之外,问题语义的部分、音频语义的部分都是神经网络,所以它们都是共同训练的,只要给机器一些托福的训练资料。
上图是实验的一些结果。让random猜,正确率是25%,你会发现有两个方法远比25%强,这个是很重要的信息。这边这5个方法,都是native的方法,就是完全不管文章的内容,直接看问题和选项猜答案。你发现说,如果选最短的那个选项,正确率就有35%。如果你分析四个选项的语义,做那个sequence to sequence auto encoder,去把每个选项的语义找出来,然后去看某一个选项跟另外三个选项在语义上的相似度,如果某个选项和另外三个选项在语义上的相似度比较高的话,把它选出来,这样有35%的正确率。这跟你的直觉是相反的,直觉通常会觉得说,应该寻一个选项,语义与另外三个选项是不像的,不过人家早就知道你会这么做了,所以是个计中计,如果选择最不像的那个选项反而正确和random差不多。
你可以用memory network,可以得到39%的正确率。
用我们刚才讲的model,在有语音识别错误的情况下,最好可以做到50%的正确率。
这部分参考结构化序列标注
我们将了deep learning,也讲了structured learning,它们中间有什么样的关系呢?
HMM,CRF,structured perceptron,SVM,它们可以做的事情,比如说做pos taking,input一个sequence,output一个sequence。RNN/LSTM也可以做到一样的事情。
使用deep learning的技术和structured learning的技术有什么不同呢?
假如我们用的是单向RNN或者LSTM,当你在make decision的时候,你只看了sentence的一半,而如果你用structured learning的话,通过维特比算法,考虑的是整个句子。从这个角度看,也许HMM、CRF......还是占有一些优势的,但是这个优势并没有很明显,因为RNN/LSTM等等,可以做双向的,也可以考虑整个句子的信息。在HMM/CRF里面,你可以很明确地去考虑label和label之间的关系。比如你今天再做推理的时候,在用维特比算法求解的时候,你可以直接把你要的约束加到维特比算法里去。你可以说,希望每一个label出现的时候,都要连续出现5次,这件事情你可以轻易地用维特比算法做到,因为你可以修改维特比算法,让机器在选择分数最高的句子的时候,排除掉不符合约束的结果。但是如果是RNN和LSTM的话,直接加入一个约束进去是比较难的,没有办法要求RNN说连续output某个label 5次,你可以让它看这种训练数据去学习,但这比较麻烦,维特比算法可以直接告诉机器做什么事。
HMM,CRF,结构化感知机/SVM都也可以是deep的,但是做deep的学习很困难。之前讲过它们都是线性的,因为我们定的评估函数是线性的,如果不是线性的话再训练会很麻烦,是线性的才能套用之前讲过的方法来做推理和训练。
deep非常重要,如果只用线性model,函数空间就那么大,就算你可以直接最小化一个error的上界也没用,因为所有的函数都是坏的。
这部分参考结构化序列标注
你可以说底部(input的特征)先通过RNN/LSTM,output再作为HMM、CRF......的input。如此就可以同时享有deep的好处和结构化学习的好处。
HMM/CRF可以用梯度下降学习,其实结构化感知机/SVM可以用梯度下降训练。所以你可以把深度学习部分跟结构化学习的部分结合起来一起用梯度下降训练。
这部分参考结构化序列标注
在语音上常常把深度学习和结构化学习结合起来。常常见到的模型是深度学习的model:CNN/LSTM/DNN加上HMM的组合。
RNN做的事情是取代了发射部分,原来HMM里面发射概率就是简单的统计。
怎么取代呢?
一般RNN input一个声学特征,output声学特征属于每一个state的概率。对这个概率做一下转换,如上图右边所示。
其实加上HMM,在语音识别里面是很有帮助的。就算是你用RNN做语音辨识的时候,常常会遇到一个问题,假设是一个一个frame丢到RNN,然后问frame属于哪一个form。它往往会产生一些怪怪的结果,比如说因为一个form往往是蔓延好多个frame,所以理论上你应该看到说第一个frame是a,第二、第三、第四、第五个frame也是a,然后接下来换成b、b、b。但是如果你用RNN的时候,RNN每一个产生的label都是独立的,所以可能突然改成b又改回成a,你会发现它很容易出现这个现象。
如果有这样一个比赛的话,你就会发现RNN有点弱,如果手动改掉前后不一样的output,你就可以得到大概2%的进步。
如果你加上HMM的话,就不会有这种情况,HMM会帮你把这种状况修正掉,所以加上HMM是有用的。对RNN来说,训练的时候是一个一个frame分开考虑的,所以今天假如不同的错误对语音识别的结果影响很大(如果我们把b改成错在其他地方,对语音辨识的结果影响就很小),但RNN不知道这件事情,对它来说,在这边放一个错误跟在另一边放一个错误是一样的。
这部分参考结构化序列标注
现在也很流行用双向LSTM+CRF/结构化SVM做槽填充。先用双向LSTM抽出特征,再把这些特征定义为CRF或者结构化SVM里面用到的特征向量。CRF或者结构化SVM都是线性的model,要先抽\(\phi(x,y)\),然后学习一个weight \(w\)。
有人会问说结构化学习到底是否practical,要解三个问题,其中Q2:推理那个问题往往是很困难的,要arg,穷举所有的\(y\)看哪一个可以让你的值最大,要解一个最优化的问题。这个最优化问题的解大部分情况都没有好的solution,序列标注是少数有好的solution的状况。所以会让人觉得说结构化学习的用途没有那么广泛,但未来未必是这样子。
想想之前讲过的GAN,我认为GAN就是一种结构化学习。如果你把discriminator看作是evaluation function(Q1),最困难的Q2要解一个推理的问题,要穷举所有我们未知的东西,看谁可以让我们的evaluation function最大,这一步往往很困难,因为\(x\)的可能性太多了,但事实上这个东西它就可以是generator,我们可以想generator不就是给一个从高斯取样出现的noise,output一个\(x\)吗?它output的这个object不是就是可以让discriminator分辨不出来的那个object吗?如果discriminator就是evaluation function的话,它output的那个object就是可以让evaluation function的值很大的那个object。所以这个generator其实就是在解这个问题,generator的output就是这个argmax的output。所以你可以把generator当作是在解inference这个问题。Q3你已经知道了,怎么训练GAN就是Q3的solution,实时上GAN的训练跟结构化SVM那些方法的训练不觉得有异曲同工之妙吗?