原文来源:Medium
作者:Apil Tamang
「雷克世界」编译:嗯~阿童木呀、多啦A亮
众所周知,对于我们来说,循环神经网络(RNN)是确实一个难以理解的神经网络,它们具有一定的神秘性,尤其是对于初学者来说就显得更不可思议了。当人们开始谈论长短期记忆网络(LSTM)或者RNN体系结构的未展开/展开(rolled/unrolled)版本时,我相信,这些讨论肯定会让我们对于其架构等相关知识有一个更为深刻的了解和认识。
我认为,之所以会出现这种情况的原因是,现如今,我们几乎没有材料能够以一种易于视觉化的方式揭示RNN的内部结构。如果对于构建块(building block)是什么,没有一个很好的心理图像认知,我会发现自己往往会一遍又一遍地努力去理解同样的东西。
核心API
现在任何RNN架构的核心都是一个简单的RNN cell或其变体。下面就是用PyTorch创建一个RNN Cell所需要的代码:
rnn_pytorch = nn.RNN(input_size=10, hidden_size=20)
而用Tensorflow创建这样一个RNN Cell所需要的代码应为:
rnn_tensorflow = tf.contrib.BasicRNNCell(num_units=20)
请注意单词Cell的用法。不知何故,术语cell在神经网络中似乎是一个非常陌生词汇,我们习惯于用其通过相互连接来对单个神经元和/或它们在空间中的排列进行可视化。一个RNN cell是如何与神经网络中我们习惯使用的单个神经元的浮动集合相关的呢?这就是我们将在此文的其余部分进行探索的焦点。
传统的观点
一般来说,一个RNN cell应该简单地被认为是将整个网络的一个单元(unit),其中该网络将其某些部分封装在一个同质块(homogeneous block)中。在大多数博客和论文中,RNN cell(块)如下所示:
一个简单的RNN结构(左:标准形式,右:展开形式)
这与传统的完全连接(FC)和卷积网络(conv-nets)的描述截然不同。
神经网络的典型视图(左:完全连接,右:卷积网络)
后台:
如果想要对RNN cell结构有一个透彻的理解,一个的最好方法可能是通过创建一个自己的RNN cell结构!以下是用PyTorch中从头创建的一个基本RNN cell的代码:
class CharLoopModel(nn.Module):
# This is an RNN!
def __init__(self, vocab_size, in, h):
super().__init__()
self.h = h
self.e = nn.Embedding(vocab_size, in)
self.l_in = nn.Linear(in, h)
self.l_hidden = nn.Linear(h, h)
self.l_out = nn.Linear(h, vocab_size)
def forward(self, *cs):
'''
cs (list(list(int))): input signals to propagate forward.
Example: ((23,32,34), (12,24,23), (12, 45,23))
'''
bs = cs[0].size(0)
hidden_state = Variable(torch.zeros(bs, self.h)).cuda()
for c in cs:
inp = F.relu(self.l_in(self.e(c)))
hidden_state = F.tanh(self.l_hidden(hidden_state+inp))
return F.log_softmax(self.l_out(hidden_state), dim=-1)
上面的代码让我们对于RNN Cell内部有了一个较为清晰的认识。请注意,参与形成RNN的两个主要组成部分是self.l_in和self.l_hidden层。通过对__init__和forward方法的分析,我们可以等效地将其表示如下图所示:
从上图可以看出,RNN Cell实际上是一组两个完全连接的线性层。这两层都有'h'神经元,其中'h'是隐藏状态的用户指定维度。而在内部所发生的唯一特别的事情就是,在第一层的输出中增加“隐藏状态”。仅此而已。
隐藏状态在层内(以及RNN cell派生其名称的地方)形成一个环式连接(recurrent connection)。在典型的使用情况中,环式连接使得网络能够记住从上一步中所学到的内容。正是由于这个原因,RNN现如今已经被广泛应用于很多依赖于时间或者序列的问题中。
展开的RNN图
同样,稍加努力,我们就可以想象出展开RNN图。它显示了环式连接如何从一步骤流向另一步骤的,从而使得RNN cell能够展示其类似于记忆的特征。
训练RNN通常一次执行一步。因此,RNN由于不能利用现代硬件的完全并行体系结构而难以训练,这也是被大家所吐槽的。根据问题的性质(以及提出的解决方案),计算只能一步一步前进。以同样的方式,通过RNN cell的反向传播包括在最后一个时间步骤评估梯度,并且一次向后传播一个时间步。
随着时间的推移,RNN cell的计算图展示出来
最后
RNN被广泛用于时间或序列相关的建模问题。它们对于问题所带来的进展,特别是在机器翻译、语音识别和文本摘要等语言建模领域,已经远远优于传统方法。
在其核心,一个RNN cell(或其任何变体)实际上是一个线性密集层的组合,通过一些适度的连接引入了循环的概念。实际上,现代的RNN架构很少使用我们上面研究的基本RNN cell。相反,他们最经常使用LSTM cell,它只是一种引入更多内部环式连接的RNN cell。最后,对基本RNN cell结构的了解可以帮助更直观地理解更复杂的cell以及它们的功能。
致谢
我再一次感谢Jeremy的fastai课程为在这篇文章提供的许多见解,包括从零开始的RNN的PyTorch实现核心代码。github上附带的源代码(https://github.com/fastai/fastai/blob/master/courses/dl1/lesson6-rnn.ipynb)继续演示如何使用此代码作为示例文本语料库,以及探索RNN的附加变体。
我以前也曾经通过纯tensorflow来实现RNN,但是包括演示在内的端到端的实现(https://github.com/apiltamang/tensorflow_rtp_materials/blob/master/week-7/Vanilla_RNN_Using_Embeddings.ipynb)在这里涉及更多。这个实现的关键是使用tensorflow的扫描方法,它基本上执行了计算图的动态展开,从而使这个问题变得相当混乱!
领取专属 10元无门槛券
私享最新 技术干货