循环神经网络 RNN
文章目录
- 循环神经网络 RNN
- 一、概述
- 二、背景
- 三、RNN原理
- 3.1 模型结构
- 3.2 前向传播
- 3.3 反向传播BPTT(back-propagation through time)
- 3.4 RNN的分类
- 3.5 RNN的改进
- 双向RNN
- 深度RNN
一、概述
循环神经网络(Recurrent neural network,RNN)是一类以序列(sequence)数据为输入,在序列的演进方向进行递归(recursion)且所有节点(循环单元)按链式连接的递归神经网络。
时间循环神经网络可以描述动态时间行为,RNN将状态在自身网络中循环传递,因此可以接受更广泛的时间序列结构输入。循环神经网络具有记忆性、参数共享并且图灵完备(Turing completeness),因此在对序列的非线性特征进行学习时具有一定优势 。手写识别是最早成功利用RNN的研究结果。
二、背景
传统神经网络只能单独的取处理一个个的输入,前一个输入和后一个输入是完全没有关系的。但是,某些任务需要能够更好的处理序列的信息,即前面的输入和后面的输入是有关系的。
比如,在理解一句话意思时,孤立的理解这句话的每个词是不够的,而需要处理这些词连接起来的整个序列; 处理视频的时候,也不能只单独的去分析每一帧,而要分析这些帧连接起来的整个序列。
所以为了能够更好的处理序列的信息,RNN就诞生了。
三、RNN原理
3.1 模型结构
基础的神经网络只在层与层之间建立了权连接,RNN最大的不同之处就是在层之间的神经元之间也建立的权连接。常规的RNN模型结构如下:
右侧为左侧模型按照时间序列展开之后的模型。
- x(t)x^{(t)}x(t)为在序列索引号t时训练样本的输入;
- h(t)h^{(t)}h(t)为在序列索引号t时模型的隐藏状态,由x(t)x^{(t)}x(t)和h(t−1)h^{(t−1)}h(t−1)共同决定;
- o(t)o^{(t)}o(t)为在序列索引号t时模型的输出,由模型当前的隐藏状态h(t)h^{(t)}h(t)决定;
- L(t)L^{(t)}L(t)为在序列索引号t时模型的损失函数;
- y(t)y^{(t)}y(t)为在序列索引号t时训练样本序列的真实输出;
- UUU是输入层到隐藏层的权重矩阵;
- VVV是隐藏层到输出层的权重矩阵;
- WWW是隐藏层上一次的值作为这一次的输入的权重。
用公式表达为如下形式:
{o(t)=g(V⋅h(t))h(t)=f(U⋅x(t)+W⋅h(t−1))\\begin{cases}o^{(t)} = g(V·h^{(t)}) \\\\h^{(t)} = f(U·x^{(t)}+W·h^{(t-1)})\\end{cases}{o(t)=g(V⋅h(t))h(t)=f(U⋅x(t)+W⋅h(t−1))
3.2 前向传播
对于任意一个序列索引号ttt,隐藏状态h(t)h^{(t)}h(t)由x(t)x^{(t)}x(t)和h(t−1)h^{(t−1)}h(t−1)得到:
h(t)=σ(z(t))=σ(Ux(t)+Wh(t−1)+b)h^{(t)}=\\sigma(z^{(t)})=\\sigma(Ux^{(t)}+Wh^{(t−1)}+b)h(t)=σ(z(t))=σ(Ux(t)+Wh(t−1)+b)
其中σ\\sigmaσ为激活函数,一般为tanh\\tanhtanh,bbb为线性偏置。
序列索引号ttt时模型的输出o(t)o^{(t)}o(t)为:
o(t)=Vh(t)+co^{(t)}=Vh^{(t)}+co(t)=Vh(t)+c
在序列索引号ttt时预测输出为:
y^(t)=σ(o(t))\\widehat{y}^{(t)}=\\sigma(o^{(t)})y(t)=σ(o(t))
其中σ\\sigmaσ为激活函数,通常RNN用于分类,因此一般用softmax函数g(zi)=ezi∑kezkg(z_i)=\\frac{e^{z_i}}{\\sum_k e^{z_k}}g(zi)=∑kezkezi。
3.3 反向传播BPTT(back-propagation through time)
BPTT算法是针对循环层的训练算法,它的基本原理和BP算法一样的:
- 前向计算每个神经元的输出值;
- 反向计算每个神经元的误差项;
- 计算每个权重的梯度
- 用随机梯度下降算法更新权重
在RNN模型中,需要寻优的参数有三个,分别是U、V、W。其中W和U需要追溯之前的历史数据,而V只需关注目前。由于我序列的每个位置都有损失函数,因此最终的损失L为:
L=∑t=1τL(t)L=\\sum_{t=1}^\\tau L^{(t)}L=t=1∑τL(t)
先求对VVV的梯度:
∂L∂V=∑t=1τ∂L(t)∂V=∑t=1τ(y^(t)−y(t))(h(t))T\\frac{\\partial L}{\\partial V} = \\sum_{t=1}^\\tau \\frac{\\partial L^{(t)}}{\\partial V} = \\sum_{t=1}^\\tau (\\widehat{y}^{(t)}-y^{(t)})(h^{(t)})^T∂V∂L=t=1∑τ∂V∂L(t)=t=1∑τ(y(t)−y(t))(h(t))T
再求LLL对W,UW,UW,U的梯度:
∂L(t)∂W=∑t=0τ[∂L(t)∂o(t)∂o(t)∂h(t)(∏j=t+1τ∂h(j)∂h(j−1))∂h(t)∂W]\\frac{\\partial L^{(t)}}{\\partial W} = \\sum_{t=0}^\\tau [\\frac{\\partial L^{(t)}}{\\partial o^{(t)}} \\frac{\\partial o^{(t)}}{\\partial h^{(t)}} (\\prod_{j=t+1}^\\tau \\frac{\\partial h^{(j)}}{\\partial h^{(j-1)}}) \\frac{\\partial h^{(t)}}{\\partial W}]∂W∂L(t)=t=0∑τ[∂o(t)∂L(t)∂h(t)∂o(t)(j=t+1∏τ∂h(j−1)∂h(j))∂W∂h(t)]
∂L(t)∂U=∑t=0τ[∂L(t)∂o(t)∂o(t)∂h(t)(∏j=t+1τ∂h(j)∂h(j−1))∂h(t)∂U]\\frac{\\partial L^{(t)}}{\\partial U} = \\sum_{t=0}^\\tau [\\frac{\\partial L^{(t)}}{\\partial o^{(t)}} \\frac{\\partial o^{(t)}}{\\partial h^{(t)}} (\\prod_{j=t+1}^\\tau \\frac{\\partial h^{(j)}}{\\partial h^{(j-1)}}) \\frac{\\partial h^{(t)}}{\\partial U}]∂U∂L(t)=t=0∑τ[∂o(t)∂L(t)∂h(t)∂o(t)(j=t+1∏τ∂h(j−1)∂h(j))∂U∂h(t)]
查看中间的累乘部分,
∏j=t+1τ∂h(j)∂h(j−1)=∏j=t+1τσ′⋅Ws\\prod_{j=t+1}^\\tau \\frac{\\partial h^{(j)}}{\\partial h^{(j-1)}} = \\prod_{j=t+1}^\\tau \\sigma\’ · W_sj=t+1∏τ∂h(j−1)∂h(j)=j=t+1∏τσ′⋅Ws
而σ(x)\\sigma(x)σ(x)为sigmoidsigmoidsigmoid或者tanhtanhtanh时,容易导致梯度消失,所以此处的激活函数常用ReLU函数,ReLU(x)=max(x,0)ReLU(x) = \\max(x,0)ReLU(x)=max(x,0)。
3.4 RNN的分类
对于RNN来说,根据不同的问题需要不同的输入输出结构(输入TxT_xTx,输出TyT_yTy)。按照TxT_xTx与TyT_yTy的关系,RNN模型包含以下几个类型:
- Many to many:Tx=TyT_x=T_yTx=Ty,输入和输出的长度相同,常用于人名识别,实体识别;
- Many to many:Tx≠TyT_x≠T_yTx=Ty,输入和输出都是序列,但长度却不相同,多见于机器翻译;
- Many to one:Tx>1,Ty=1T_x>1,T_y=1Tx>1,Ty=1,用作对某个序列进行正负判别或者打星操作,如文本分类,情感分类;
- One to many:Tx=1,Ty>1T_x=1,T_y>1Tx=1,Ty>1,应用有语音生成,文本生成;
One to one:Tx=1,Ty=1T_x=1,T_y=1Tx=1,Ty=1,用的不多。
3.5 RNN的改进
双向RNN
不仅可以依赖前面的上下文,还可以依赖后面的上下文。
深度RNN
不仅在隐藏层有前后序列的关系,网络的其它层次也可以具有序列关系。
四、RNN的简单使用
在自然语言处理中,上下文关系对模型有着较大的影响,而RNN比传统的神经网络模型能更好的处理这种关系。将每个单词作为一个特征向量,利用nn.RNN模型对语料进行了文本分类的处理。
首先从文件中读取相应训练集和测试集的语料,利用jieba对文本进行分词,然后利用nltk去停用词。这里nltk_data中并没有中文的停用词,我从网上找了一个中文的停用词文件放在对应的stopwords目录下,并命名为chinese。
def tokenizer(text):sentence = jieba.lcut(text, cut_all=False)stopwords = stopwords.words(\'chinese\')sentence = [_ for _ in sentence if _ not in stopwords]return sentence
利用torchtext包处理预处理好的语料,将所提供的语料集转化为相应的词向量模型。由于每一个词均为一个向量,,作为RNN的输入。
train_set, validation_set = data.TabularDataset.splits(path=\'corpus_data/\',skip_header=True,train=\'corpus_train.csv\',validation=\'validation.csv\',format=\'csv\',fields=[(\'label\', label), (\'text\', text)],)text.build_vocab(train_set, validation_set)
将处理好的词向量输入到RNN模型中进行处理。
self.rnn = nn.RNN(embedding_dim, self.hidden_size, batch_first=True, nonlinearity=\"relu\")self.linear = nn.Linear(self.hidden_size, label_num)def forward(self, x):# 输入x的维度为(batch_size, max_len),x = self.embedding(x) # 经过embedding,x的维度为(batch_size, time_step, embedding_dim)# 隐层初始化# h0维度为(direction_num, batch_size, hidden_size)h0 = torch.rand(1, x.size(0), self.hidden_si4000ze)# out维度为(batch_size, seq_length, hidden_size*direction_num)out, hn = self.rnn(x, h0)# 只需要最后一步的输出状态out = out[:, -1, :]out = self.linear(out)return out
利用训练集对模型进行训练,同时评估训练效果,并利用测试集对模型的准确性进行评估。为了防止偶然性产生的不确定,每一轮迭代会产生100个模型,分别评估其效率,进行调优后再用测试集测试其效率。
在训练初期,准确率大概在45%左右。
多轮迭代之后,模型的准确率可以达到90%以上。
五、总结
-
优点
适合处理序列问题,输入数据的判别和预测与相邻数据有关
- 在序列问题中对短期记忆型的任务的效果更好
- 可以和CNN一起使用得到更好的任务效果
不足
- 梯度消失、梯度爆炸的问题
RNN 几个典型的应用如下:
- 语言模型与文本生成