Press "Enter" to skip to content

循环神经网络的原理分析

本站内容均来自兴趣收集,如不慎侵害的您的相关权益,请留言告知,我们将尽快删除.谢谢.

原理介绍

 

循环神经网络在大多数人看来其实很简单。的确原理是非常简单的,但是代码的实现细节却没有那幺简单。往往纸上谈兵容易,实战起来才发现跟理论是有差距的。本文重点介绍RNN的原理,之后基于PyTorch框架,介绍RNN类的输入和输出,重点在于理解RNN的原理。结尾我们发现PyTorch的RNN模块其实只计算了隐藏层矩阵。

 

下图是一个简单RNN的单元图, x_t 为t时刻的输入, y_t 为t时刻的输出, h_t 为隐藏层输出, W_{hh}, W_{ih} 为隐藏层和输入层的权重, V 为输出层权重。

 

 

h_t = \tanh(W_{hh}h_{t-1}+W_{ih}x_t) \\ y_t = V h_t

 

上面就是循环神经网络单元的计算公式。接下来我们来看PyTorch的 实现代码

 

代码实现

 

定义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的数据;

 

* h_0 为隐藏状态的初始值,由于是单向网络,网络层数数为2,所以第一个值是2, 第二个值是batch_size=3,第三个数20是hidden_size。

 

* h_n 为隐藏层的输出状态,其shape与 h_0 相同,

 

* 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。回到上面的公式:

 

h_t = \tanh(W_{hh} h_{t-1} + W_{ih} x_t)

 

根据上面的例子,输入 x_t 的shape为[5, 3, 10],而 W_{ih} 的shape是[20, 10],根据矩阵的乘法应该是 W_{ih} 的转置乘以 x_t ,得到[5, 3, 20]。再来看第一项 W_{hh} 的shape为[20, 20], 而 h_{t-1} 的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拼接。也就是说 h_{t} 的shape似乎不是[1, 3, 20],

 

也就是说根据上面的公式, h_t 的shape不知道怎幺得到的,于是我去查看源码,发现这部分的矩阵是由C/C++完成的。既然查看源码有点麻烦,那就只能猜了。根据上面打印的参数来看,隐藏层应该是输出层的一部分,因为只有8个矩阵,于是比较一下最后一个输出output[-1, :, :]和最后一个隐藏层的参数hn[-1, :, :],结果发现是一致的。

 

output[-1:,:,:] == hn[-1, :, :]
# 返回结果都是True

 

那幺PyTorch中RNN类真实的计算网络图应该就是下面这个了(手绘的,还请凑合看)

 

也就是说隐藏层是网络最后一层的输出,而输出层是包含中间状态的隐藏层。

 

总结

 

跟上面的RNN计算公式相比,隐藏层的输出再接一个线性层就是完整的RNN了。这种RNN有一个小问题,就是每个输出 y_t 都是独立的,如果用RNN来做序列的分类问题,没有影响,只要取最后一个输出层,做分类就可以了。如果是一个序列标注问题,就应该考虑输出之间的关系了,最简单的可以考虑 y_t=f(y_{t-1}, h_t) ,或者双向的 y_t=f(y_{t_1}, y_{t+1}, h_t) .除此之外甚至可以接CRF作为序列标注的输出层。

Be First to Comment

发表评论

电子邮件地址不会被公开。 必填项已用*标注