make_RNN_more_efficient
1、stacked RNN
3个技巧提升RNN的效果。
第一个是Stacked RNN。stack RNN为多层RNN, 可以把很多全连接层堆叠起来, 构成一个multilayer percepter, 也可把很多卷积层堆叠起来, 构成一个深度卷积网络。同样的道理也可以把很多RNN堆叠起来构成一个多层RNN网络。神经网络的每一步都会更新状态h, 新算出来的h有两个copy, 一份送到下一个时刻, 另外一份作为输出。这一层输出的状态h成为了上一层的输入。再解释一下, 最底层的RNN的输入是词向量x, 这层RNN会输出每一步的状态向量h, 这些输出的状态向量又成为了第二次RNN的输入。
第二次RNN有自己的模型参数, 会更新和输出自己的状态向量h, 第二层输出的状态向量h又成为了第三层RNN的输入。
一共有三层RNN, 最上层的状态向量是最终的输出, 可以用最后一个状态ht看成是从最底层的输入I love the movie so much中提取的特征向量。
使用keras实现多层LSTM, 这里用了三层LSTM层。第一层的输出会成为第二层的输入, 所以第一层的return_sequences要设置为true, 要输出所有的状态向量h。 同样的道理第二次的return sequence也要设置为true, 要输出所有的状态向量h,这些状态向量会成为下一层LSTM的输入。第三层也是最后一层LSTM的return_sequence设置为false, 只需要输入最后一个状态向量就行, 可以把前面所有的状态向量都扔掉,最后一层是全连接层, 拿第三层LSTM的状态向量作为输入, 这一层输出分类结果。
1 | from tensorflow.keras.layers import LSTM, Embedding, Dense |
上图所示为神经网络的概要, embedding层的输出是500乘以32的矩阵, 500的意思是每条电影评论有500个单词, 每个词用32维的向量表示, 这个500×32的句子成为了第一层LSTM的输入第一层LSTM的输出是500×32的句子, 这里的32是状态向量h的维度。由于设置return sequence为true, 所以第一层LSTM的输出看所有的500个状态, 这个500×32的矩阵又成为了第二层LSTM的输入。第二层LSTM输出了500×32的句子, 它又成了第三层LSTM的输入。设置第三层LSTM的return sequence为false, 所以第三层LSTM的输出是一个32维的向量, 它是第三层LSTM的最后一个状态。它相当于从输入的500个单词里提取的特征向量。这4个数字都是32, 其实只是一个巧合, 完全可以让这4个32分别取不同的值。
2、Bidirectional RNN
RNN跟人的阅读习惯一模一样, 从左往右逐个单词阅读, 人阅读的过程在大脑里面积累提取出的信息。RNN阅读的过程在状态向量h中积累信息,读完一段电影评论就知道是正面还是负面评论。人类总是从左往右, 从前往后阅读, 但这只是我们的阅读习惯而已。对RNN来说, 从前往后或从后往前阅读并没有太大的区别。
所以很自然的想法就是训练两条RNN, 一条从左往右, 另一条从右往左,两条RNN完全独立, 不共享参数,也不共享状态。两条RNN各自输出自己的状态向量, 然后把它们的状态向量做concatination, 记作向量y。如果有多层RNN, 就把输出的这些向量y作为上一层RNN的输入。
如果只有一层, 把这些y向量丢掉就可以了。只保留两条链最后的状态向量, 分别是ht和ht‘。把这两个状态向量的concatination作为从输入文字中提取的特征向量,根据它来判断电影评论是正面还是负面的。双向RNN总比单向RNN的效果好。原因可能是这样的, 不管是simpleRNN还是LSTM, 这些RNN模型都会或多或少的忘掉早先的输入。如果让RNN从左往右阅读, 那么最后一个状态ht可能会遗忘掉左边的输入, 如果让RNN从右往左阅读, 最后一个状态ht‘往往可能会记作靠左边的输入。将ht和ht’结合起来, 这样RNN就不会遗忘最开始看到的词了。
下面是双向LSTM的编程实现, 具体实现只需要在标准的LSTM层外面包裹一个Bidrectional层。这样LSTM就变成双向的了。这就是实现双向LSTM唯一需要改变的地方。双向LSTM只会保留两条链最后的状态, 输出两个状态向量的concatination, 其余状态向量都被扔掉了。
1 | from tensorflow.keras.models import Sequential |
下面是打印出来的神经网络的概要。这里设置状态向量h的维度是32, 而且return_sequence为false, 所以这一层的输出是两条链最后的两个状态向量。两个都是32维, 叠加起来就是64维。可以算一下这一层的参数的数量, 参数数量比单向LSTM要多一倍。这是因为两条链各自输出自己的参数模型。
其实我们可以发现embedding层的参数量远大于LSTM以及Dense层的参数量。再多的改进似乎对整体的模型效果并没有带来太大的提升, 因此另外一个提升LSTM的方法是pretrain预训练。
3、预训练
预训练在深度学习中非常常用。比如在训练卷积网络时, 如果网络太大而训练集不够大, 那么可以在imagenet等大数据上做预训练, 这样可以让神经网络有比较好的初始化, 也可以避免overfitting。训练RNN的时候也是一样的道理。比如这个神经网络中的embedding层有32万个参数, 而我们只有2万个训练样本, 这个embedding层太大了, 会导致模型overfitting, 解决方法就是对embedding层做预训练。
预训练是这样做的, 首先找一个更大的数据集, 可以是情感分析的数据, 也可以是其他类型的数据。但是任务最好是接近情感分析的任务,也就是说最好学出来的词向量带有正面或负面的情感。两个任务越相似, 预训练之后的transfer效果就越好。有了大数据集之后要搭建一个神经网络, 这个神经网络的结构是什么样的都可以, 甚至不用是RNN只有这个神经网络有embedding层就行, 然后就是在大数据集上训练这个神经网络。
训练完成之后, 把上面的层全部丢掉, 只保留embedding层和训练好的模型参数。
然后再搭建我们自己的RNN网络。这个新的RNN网络跟之前用于预训练的神经网络可以有不同的结构。搭好之后, 新的RNN层和全连接层都是随机初始化的,而下面的embedding层的参数是预训练出来的。要把embedding层的参数固定住,不要训练这个embedding层, 只训练其他的层。
总结:
SimpleRNN和LSTM都是两种不同的RNN, 通常使用LSTM而不是SimpleRNN。
尽可能使用Bi-RNN而不是RNN
Stack RNN效果往往比单一的RNN效果要好
embedding层采用预训练的方式获取。