1 循环神经网络
循环神经网络(Recurrent Neural Network,RNN)是一类具有短期记忆能力的神经网络。在循环神经网络中,神经元不但可以接受其它神经元的信息,也可以接受自身的信息,形成具有环路的网络结构。和前馈神经网络相比,循环神经网络更加符合生物神经网络的结构。循环神经网络已经被广泛应用在语音识别、语言模型以及自然语言生成等任务上。循环神经网络的参数学习可以通过随时间反向传播算法(BPTT)[Werbos, 1990]来学习。随时间反向传播算法即按照时间的逆 序将错误信息一步步地往前传递。当输入序列比较长时,会存在梯度爆炸和消失问题[Bengio et al., 1994, Hochreiter and Schmidhuber, 1997, Hochreiter et al., 2001],也称为长程依赖问题。(摘抄:《神经网络与深度学习》)
2 基本循环神经网络结构图
如果把上面有W的那个带箭头的圈去掉,它就变成了最普通的全连接神经网络。
- x是一个向量,它表示输入层的值;
- s是一个向量,它表示隐藏层的值;
- U是输入层到隐藏层的权重矩阵;
- o也是一个向量,它表示输出层的值;
- V是隐藏层到输出层的权重矩阵。那
- 权重矩阵 W就是隐藏层上一次的值作为这一次的输入的权重。因为循环神经网络的隐藏层的值s不仅仅取决于当前这次的输入x,还取决于上一次隐藏层的值s。
3 BPTT
随时间反向传播(Backpropagation Through Time,BPTT)算法的主要 思想是通过类似前馈神经网络的错误反向传播算法 [Werbos, 1990] 来进行计算 梯度。 BPTT 算法将循环神经网络看作是一个展开的多层前馈网络,其中“每一 层”对应循环网络中的“每个时刻”。这样,循环神经网络就可以按按照前 馈网络中的反向传播算法进行计算参数梯度。在“展开”的前馈网络中,所有层 的参数是共享的,因此参数的真实梯度是所有“展开层”的参数梯度之和。
BPTT算法是针对循环层的训练算法,它的基本原理和BP算法是一样的,也包含同样的三个步骤:
- 前向计算每个神经元的输出值;
- 反向计算每个神经元的误差项值,它是误差函数E对神经元j的加权输入的偏导数;
- 计算每个权重的梯度。
最后再用随机梯度下降算法更新权重。
4 RNN问题
常见问题:梯度爆炸、梯度消失
梯度爆炸解决方法:
- 权重衰减:通过给参数增加 ℓ1 或 ℓ2 范数的正则化项来限制参数的取值范 围,从而使得γ ≤ 1
- 梯度截断:当梯度的模大于一定 阈值时,就将它截断成为一个较小的数
梯度消失解决方法:
- 合理的初始化权重值。初始化权重,使每个神经元尽可能不要取极大或极小值,以躲开梯度消失的区域。
- 使用relu代替sigmoid和tanh作为激活函数。
- 使用其他结构的RNNs,比如长短时记忆网络(LTSM)和Gated Recurrent Unit(GRU),这是最流行的做法。
5 双向RNN
对于语言模型来说,很多时候光看前面的词是不够的,比如下面这句话:
我的手机坏了,我打算____一部新手机。
可以想象,如果我们只看横线前面的词,手机坏了,那么我是打算修一修?换一部新的?还是大哭一场?这些都是无法确定的。但如果我们也看到了横线后面的词是『一部新手机』,那么,横线上的词填『买』的概率就大得多了。
在上面的句子中,可以看到,一个时刻的输出不但和过去时刻的信息有关,也和后续时刻的信息有关。比如给定一个句子,其中一个词的词性由它的上下文决定,即包含 左右两边的信息。因此,在这些任务中,我们可以增加一个按照时间的逆序来传 递信息的网络层,来增强网络的能力。 双向循环神经网络(Bidirectional Recurrent Neural Network,Bi-RNN)由 两层循环神经网络组成,它们的输入相同,只是信息传递的方向不同。假设第1层按时间顺序,第2层按时间逆序,在时刻t时的隐状态定义为 和
6 递归神经网络
有时候把句子看做是词的序列是不够的,比如下面这句话『两个外语学院的学生』:
上图显示了这句话的两个不同的语法解析树。可以看出来这句话有歧义,不同的语法解析树则对应了不同的意思。一个是『两个外语学院的/学生』,也就是学生可能有许多,但他们来自于两所外语学校;另一个是『两个/外语学院的学生』,也就是只有两个学生,他们是外语学院的。为了能够让模型区分出两个不同的意思,我们的模型必须能够按照树结构去处理信息,而不是序列,这就是递归神经网络的作用。当面对按照树/图结构处理信息更有效的任务时,递归神经网络通常都会获得不错的结果。
递归神经网络的一般结构为树状的层次结构,如图(a)所示,当递归神经网络的结构退化为线性序列结构(图b)时,递归神经网络就 等价于简单循环网络。分别对应上面的例子的两个结构。
7 LSTM
长短期记忆(Long Short-Term Memory,LSTM)网络 [Gers et al., 2000, Hochreiter and Schmidhuber, 1997]是循环神经网络的一个变体。
LSTM网络主要改进在以下两个方面:
- 新的内部状态:LSTM 网络引入一个新的内部状态(internal state)专门进行 线性的循环信息传递,同时(非线性)输出信息给隐藏层的外部状态。
- 门控机制:LSTM网络引入门控机制(Gating Mechanism)来控制信息传递的路径。
下面是它的详细说明:
长短时记忆网络的思路比较简单。原始RNN的隐藏层只有一个状态,即h,它对于短期的输入非常敏感。那么,假如我们再增加一个状态,即c,让它来保存长期的状态,那么问题不就解决了么?如下图所示:
新增加的状态c,称为单元状态(cell state)。我们把上图按照时间维度展开:
上图仅仅是一个示意图,我们可以看出,在t时刻,LSTM的输入有三个:当前时刻网络的输入值、上一时刻LSTM的输出值、以及上一时刻的单元状态;LSTM的输出有两个:当前时刻LSTM输出值、和当前时刻的单元状态。注意都是向量。
LSTM的关键,就是怎样控制长期状态c。在这里,LSTM的思路是使用三个控制开关。
- 第一个开关,负责控制继续保存长期状态c;
- 第二个开关,负责控制把即时状态输入到长期状态c;
- 第三个开关,负责控制是否把长期状态c作为当前的LSTM的输出。
分别对应以下三个门:
- 输入门 :控制当前时刻的候选状态 有多少信息需要保存;
- 遗忘门 :控制上一个时刻的内部状态 需要遗忘多少信息;
- 输出门:控制当前时刻的内部状态ct有多少信息需要输出给外部状态。
三个开关的作用如下图所示:
8 门控循环单元网络
门控循环单元(Gated Recurrent Unit,GRU)网络 [Cho et al., 2014, Chung et al., 2014]是一种比LSTM网络更加简单的循环神经网络。
GRU对LSTM做了两个大改动:
- 将输入门、遗忘门、输出门变为两个门:更新门(Update Gate)和重置门(Reset Gate)。
- 将单元状态与输出合并为一个状态。
假设训练集中所有文本/序列的长度统一为n,我们需要对文本进行分词,并使用词嵌入得到每个词固定维度的向量表示。对于每一个输入文本/序列,我们可以在RNN的每一个时间步长上输入文本中一个单词的向量表示,计算当前时间步长上的隐藏状态,然后用于当前时间步骤的输出以及传递给下一个时间步长并和下一个单词的词向量一起作为RNN单元输入,然后再计算下一个时间步长上RNN的隐藏状态,以此重复...直到处理完输入文本中的每一个单词,由于输入文本的长度为n,所以要经历n个时间步长。
基于RNN的文本分类模型非常灵活,有多种多样的结构。下面是两种典型的结构。
- structure v1: embedding--->BiLSTM--->concat final dropout/average all output--->softmax layer
一般取前向/反向LSTM在最后一个时间步长上隐藏状态,然后进行拼接,在经过一个softmax层(输出层使用softmax激活函数)进行一个多分类;或者取前向/反向LSTM在每一个时间步长上的隐藏状态,对每一个时间步长上的两个隐藏状态进行拼接,然后对所有时间步长上拼接后的隐藏状态取均值,再经过一个softmax层(输出层使用softmax激活函数)进行一个多分类(2分类的话使用sigmoid激活函数)。
- structure v2: embedding--->BiLSTM--->(dropout)--->concat dropout--->UniLSTM--->(dropout)-->softmax layer
与之前结构不同的是,在双向LSTM(上图不太准确,底层应该是一个双向LSTM)的基础上又堆叠了一个单向的LSTM。把双向LSTM在每一个时间步长上的两个隐藏状态进行拼接,作为上层单向LSTM每一个时间步长上的一个输入,最后取上层单向LSTM最后一个时间步长上的隐藏状态,再经过一个softmax层(输出层使用softamx激活函数,2分类的话则使用sigmoid)进行一个多分类。
构建模型代码:
import tensorflow as tf
from tensorflow import keras
import numpy as np
imdb = keras.datasets.imdb
(train_data, train_labels), (test_data, test_labels) = imdb.load_data(num_words=10000)
print(sum(list(map(lambda x:len(x),train_data)))/len(train_data))
print(sum(list(map(lambda x:len(x),test_data)))/len(test_data))
max_len = 240
train_data = keras.preprocessing.sequence.pad_sequences(train_data,
value=word_index["<PAD>"],
padding='post',
maxlen=max_len)
test_data = keras.preprocessing.sequence.pad_sequences(test_data,
value=word_index["<PAD>"],
padding='post',
maxlen=max_len)
vocab_size = 10000
model = keras.Sequential()
model.add(keras.layers.Embedding(vocab_size, 128))
model.add(keras.layers.LSTM(128,dropout=0.2))
model.add(keras.layers.Dense(16, activation=tf.nn.relu))
model.add(keras.layers.Dense(1, activation=tf.nn.sigmoid))
model.add(keras.layers.Activation('sigmoid'))
model.summary()
model.compile(optimizer='adam',
loss='binary_crossentropy',
metrics=['acc'])
x_val = train_data[:10000]
partial_x_train = train_data[10000:]
y_val = train_labels[:10000]
partial_y_train = train_labels[10000:]
history = model.fit(partial_x_train,
partial_y_train,
epochs=40,
batch_size=512,
validation_data=(x_val, y_val),
verbose=1)
results = model.evaluate(test_data, test_labels)
print(results)
10 RCNN原理
TextRNN可以获取上下文信息,但是单向RNN是有偏的模型(biased model),后面的词占得重要性更大。TextCNN是无偏的模型(unbiased model),能够通过最大池化获得最重要的特征,但是特征提取器大小固定,设定小了容易造成信息丢失,设定大了造成巨大的参数空间。
为了解决TextRNN和TextCNN的模型缺陷,循环卷积神经网络(Recurrent Convolutional Neural Networks,RCNN)提出了:
- 双向循环结构:比传统的基于窗口的神经网络噪声要小,能够最大化地提取上下文信息;
- max-pooling池化层:自动决策哪个特征更有重要作用。
推荐文章:
Recurrent Convolutional Neural Networks for Text Classification
一文读懂文本分类基线深度模型从fastText、TextCNN、TextRNN、TextRCNN、HAN到Transformer