循环神经网络的原理分析
Posted 数学编程
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了循环神经网络的原理分析相关的知识,希望对你有一定的参考价值。
原理介绍
循环神经网络在大多数人看来其实很简单。的确原理是非常简单的,但是代码的实现细节却没有那么简单。往往纸上谈兵容易,实战起来才发现跟理论是有差距的。本文重点介绍RNN的原理,之后基于PyTorch框架,介绍RNN类的输入和输出,重点在于理解RNN的原理。结尾我们发现PyTorch的RNN模块其实只计算了隐藏层矩阵。
下图是一个简单RNN的单元图, 为t时刻的输入, 为t时刻的输出, 为隐藏层输出, 为隐藏层和输入层的权重, 为输出层权重。
上面就是循环神经网络单元的计算公式。接下来我们来看PyTorch的实现代码[1]。
代码实现
定义RNN的输入特征数为10,输出特征数为20,2个隐藏层的神经网络。代码如下:
inputs = t.randn(5, 3, 10)
rnn = t.nn.RNN(10, 20, 2) # 这里是两层网络
h0 = t.randn(2, 3, 20) # 两层都初始化h0
output, hn = rnn(inputs, h0)
print(output.shape, hn.shape)
# (torch.Size([5, 3, 20]), torch.Size([2, 3, 20]))
解释一下输入和输出数据。
-
inputs
为输入数据[5, 3, 10],其中序列的长度为5,特征数为10,3个batch的数据; -
为隐藏状态的初始值,由于是单向网络,网络层数数为2,所以第一个值是2, 第二个值是batch_size=3,第三个数20是hidden_size。 -
为隐藏层的输出状态,其shape与 相同, -
output
为神经网络的输出,shape就是[5, 3, 20]了。
每个参数的含义如下:
inputs ==> [src_len,batch_size, input_size]
h0 ==> [num_layers * num_directions, batch_size, hidden_size]
output ==> [src_len, batch_size, hidden_size]
在某些情况下只需要RNN的最后一层输出,只需要取output[-1, :, :]
就是最后一层的输出了。
进一步分析代码
文章到这里其实RNN的原理已经介绍完了,但是权重的shape还没有介绍。对于上面的例子可以输出权重的shape:
for params in rnn.parameters():
print(params.shape)
# torch.Size([20, 10])
# torch.Size([20, 20])
# torch.Size([20])
# torch.Size([20])
# torch.Size([20, 20])
# torch.Size([20, 20])
# torch.Size([20])
# torch.Size([20])
有两层网络,因此可以考到有8个权重值,一维的[20]是bias值,[20, 10]是输入层的权重,第二个[20, 20]为隐藏层的权重,之后隐藏层权重都是[20, 20]了。到这里似乎还没有发现问题,继续往下分析,我们再来看看每一步计算的shape。回到上面的公式:
根据上面的例子,输入 的shape为[5, 3, 10],而 的shape是[20, 10],根据矩阵的乘法应该是 的转置乘以 ,得到[5, 3, 20]。再来看第一项 的shape为[20, 20], 而 的shape看起来是[2, 3, 20],其实第一个参数2表示num_layers * num_directions,参与计算的shape为[1, 3, 20],做矩阵乘法以后仍然是[1, 3, 20]。shape([5, 3, 20])+shape([1, 3, 20]) = shape([5, 3, 20]),注意这里是两个矩阵相加,不是cat拼接。也就是说 的shape似乎不是[1, 3, 20],
也就是说根据上面的公式, 的shape不知道怎么得到的,于是我去查看源码,发现这部分的矩阵是由C/C++完成的。既然查看源码有点麻烦,那就只能猜了。根据上面打印的参数来看,隐藏层应该是输出层的一部分,因为只有8个矩阵,于是比较一下最后一个输出output[-1, :, :]和最后一个隐藏层的参数hn[-1, :, :],结果发现是一致的。
output[-1:,:,:] == hn[-1, :, :]
# 返回结果都是True
那么PyTorch中RNN类真实的计算网络图应该就是下面这个了(手绘的,还请凑合看)
也就是说隐藏层是网络最后一层的输出,而输出层是包含中间状态的隐藏层。
总结
跟上面的RNN计算公式相比,隐藏层的输出再接一个线性层就是完整的RNN了。这种RNN有一个小问题,就是每个输出 都是独立的,如果用RNN来做序列的分类问题,没有影响,只要取最后一个输出层,做分类就可以了。如果是一个序列标注问题,就应该考虑输出之间的关系了,最简单的可以考虑 ,或者双向的 .除此之外甚至可以接CRF作为序列标注的输出层。
参考资料
PyTorch文档: https://pytorch.org/docs/stable/generated/torch.nn.RNN.html#torch.nn.RNN
以上是关于循环神经网络的原理分析的主要内容,如果未能解决你的问题,请参考以下文章
Pytorch网络训练流程的作用原理:源码分析optimizer.zero_grad()loss.backward()optimizer.step()
Pytorch网络训练流程的作用原理:源码分析optimizer.zero_grad()loss.backward()optimizer.step()