循环神经网络(RNN)
概述
现实世界中,很多元素都是相互连接的,比如室外的温度是随着气候的变化而周期性变化的、我们的语言也需要通过上下文来确认所表达的含义。而对这种考虑前后事件来进行思考的方式,BP、CNN、DNN等网络却不再适用,因为它们只考虑“前因”,而不考虑“后果”。于是,就有了现在的循环神经网络,它的本质是:拥有记忆的能力,并且会根据这些记忆的内容来进行推断,即"认知是基于过往的经验和记忆"。
如果你想要预测某一事件时间段内可能发生的片段,那么就需要知道这个片段发生的前提种种因素及发生后的情况,才能给出更符合这个场景下的各种可能的片段。RNN之所以称为循环,是因为它们会对这种事件的每个片段都会执行相同的任务,所有的输出都取决于先前的计算。循环神经网络的提出便是基于记忆模型的想法,期望网络能够记住前面出现的特征。并依据特征推断出后面的结果,而且整体的网络结构不断循环,因此得名为循环神经网络。
应用领域
主要应用于时间序列,事件片段,序列化数据(如自然语言序列)等处理下的场景。
RNN网络结构及原理
循环神经网络结构将网络的输出保存在一个记忆单元中,这个记忆单元和下一次的输入一起进入神经网络中。我们可以看到网路在输入的十号会联合记忆单元一起作为输入,网络不仅输出结构,还会将结果保存到记忆单元中,下图就是一个最简单的循环神经网络在输入时的结构示意图。
RNN可以被看作时同一神经网络的多次赋值,每个神经网络模块会把消息传递给下一个,我们将这个图的结构展开:
可以根据网络结构看出它在处理序列类型的数据上具有天然的优势。因为网络本身就是一个序列结构,这也是所有循环神经网络最本质的结构。
循环神经网络具有特别好的记忆特性,能够将记忆内容应用到当前情景下,但是网络的记忆能力并没有想象的那么有效。记忆最大的问题在于它有遗忘性,我们总是更加清楚地记得最近发生的事情而遗忘很久之前发生的事情,循环神经网络同样有这样的问题。
继续深究原理
在实现代码之前,继续深入了解一下RNN的工作机制。其实也不过是个普通的网络,只不过多了一个hidden_state来保存记忆信息,这个作用就是为了保存以前的状态,也就是我们常说的RNN中保存的记忆状态信息。
对于RNN来说,我们只要记住一个公式:
Xt是我们当前状态的输入值,h(t-1) 就是上面说的要传入的上一个状态的hidden_state,也就是记忆部分。整个网络要训练的部分就是Wih当前状态输入值的权重,Whh 即hidden_state也就是上一个状态的权重还有这两个输入偏置值。这四个值加起来使用tanh进行激活,pytorch是默认使用tanh激活,也可以通过设置使用relu作为激活函数
上面讲的步骤就是用红框圈出的一次计算的过程
这个步骤与普通的神经网络没有任何的区别,而RNN因为多了序列(sequence)这个维度,要使用同一个模型跑n次前向传播,这个n就是我们设置序列的个数。
.
基于Pytorch实现RNN
import torch
class RNN(object):
def __init__(self, input_size, hidden_size):
super().__init__()
self.W_xh = torch.nn.Linear(input_size, hidden_size)
self.W_hh = torch.nn.Linear(hidden_size, hidden_size)
def __call__(self, x, hidden):
return self.step(x, hidden)
def step(self, x, hidden):
h1 = self.W_hh(hidden)
w1 = self.W_xh(x)
out = torch.tanh(h1 + w1)
hidden = self.W_hh.weight
return out, hidden
rnn = RNN(20, 50)
input = torch.randn(32, 20)
h_0 = torch.rand(32, 50)
seq_len = input.shape[0]
for i in range(100):
output, hn = rnn(input[i, :], h_0)
循环网络的向后传播(BPTT)
在向前传播的情况下,RNN的输入随着step前进。在反向传播的情况下,我们“回到过去”改变权重,因此把他叫做时间的反向传播。
通常把整个序列看作一个训练样本,所以总的误差是每个时间步中误差的和。权重在每一个事件步长是相同的,所以可以计算总误差后一起更新。
步骤如下:
1、使用预测输出和实际输出计算交叉熵误差
2、网络按照时间步完全展开
3、对于展开的网络,对于每一个时间步计算权重的梯度
4、因为对于所有时间步来说,权重都一样,所以对于所有的时间步,可以一起得到梯度。而不是像神经网络一样对不同的隐藏层得到不同的梯度。
5、随后对循环神经元的权重进行升级。
RNN展开的网络像一个普通的神经网络,反向传播也类似,只不过是一次得到所有时间步的梯度。但是如果有100个时间步,那么网络展开后将变得非常巨大,所以为了解决这个问题出现了LSTM和GRU这样的结构。
长短时记忆网络(LSTM)
首先,标准的RNN只有一个简单的层结构,而LSTM有4个层结构。
第一层是个忘记层:决定状态中丢弃什么信息
第二层tanh层:用来产生更新值的候选项,说明状态在某些维度上需要加强,在某些维度上需要减弱
第三sigmoid层:它的输出值要乘到tanh层的输出上,起到一个缩放的作用,极端情况下sigmoid输出0说明相应维度上的状态不需要更新
最后一层:决定输出什么,输出值跟状态有关。候选项中的哪些部分最终会被输出由一个sigmoid层来决定。
lstm = torch.nn.LSTM(10, 20, 2)
input = torch.randn(5, 3, 10)
h0 = torch.randn(2, 3, 20)
c0 = torch.rand(2, 3, 20)
output, hn = lstm(input, (h0, c0))
门控循环单元(GRU)
GRU和LSTM最大的不同在于GRU将forget gate和input gate合成了一个updat gate。
gru= torch.nn.GRU(10, 20, 2)
input = torch.randn(5, 3, 10)
h0 = torch.randn(2, 3, 20)
output, hn = gru(input, h0)