如何用Tensorflow实现RNN?本文将带你进一步研究

更多深度文章,请关注:https://yq.aliyun.com/cloud


在这篇文章中,我将介绍与构建循环神经网络最相关的tensorflow API。 tensorflow文档很好地解释了如何构建标准的RNN,但是对于构建个性化的RNN而言,它还是有不足之处的。

我将使用Chung等人在Hierarchical Multiscale Recurrent Neural Networks中描述的网络,作为一个非递归神经网络的示例。在github上有一个开源的网络实现。

基础

在本节中,我将简要介绍在tensorflow中创建标准RNN的工具。

标准的RNN单元

如果你需要标准RNN,GRU或LSTM,则你可以使用tensorflow。 API包含这些预先写入的单元,所有这些都扩展了基类RNNCell。 他们关于RNN的教程中给出了如何使用这些单元的良好概述,所以我不会花太多时间在这里。 如果你在tensorflow中完全不了解RNN,则可能需要先阅读该教程才能继续。

值得一提的是他们使用的是MultiRNNCell。 这是一个类,它用一个延伸RNNCell的对象列表构成。 它用于创建多层RNN,其中最低层在输入中被馈送,然后每个后续层被馈送到先前层的输出,并且最后一层的输出是在给定时间步长的网络的输出。 如果要以不同的方式在图层之间传递信息,则需要一个自定义的多单元格。我们稍后再谈这个。

请注意,MutliRNNCell本身也扩展了RNNCell,因此可以在任何其他RNNCell可以使用的地方使用。

动态展开的RNNs

我们发现RNNs展开时最容易考虑到。 在tensorflow中,这意味着我们为每个时间步骤重建一个相同的计算图,并将隐藏的状态从一个时间步骤前进到下一个手动传递。

这是采用tensorflow教程模型的方法。 这种方法的优点在于它易于思考,而且是灵活的。 如果要在传递之前以任何方式处理隐藏状态,你可以轻松地将多个节点放在图形中。

有两个主要的缺点。首先,以这种方式组成的图必须是固定长度的,这意味着你必须重建不同长度信号的图形,或者用0填充它们。两种解决办法都不好。

第二,大型图形需要更长的时间才能构建和使用更多的RAM。 根据你的计算环境的限制,这可能是令人望而生畏的。

tf.dynamic_rnn函数将你的RNNCell转换为动态生成的图形,将图形从一个时间步骤传递到下一个时间段,并跟踪输出。 如果你有其他需求,tf.scan可以更灵活地提供类似的用途,我们将在后面看到。

新型RNN配置

在本节中,我将回顾一些可用于创建具有较少标准架构的RNN的选项。

直接扩展RNNCell

如果你需要与任何标准实现方式有所不同的网络,你可以直接扩展RNNCell。 你可以使用你自己的子类与上述的MultiRNNCell类以及DropoutWrapper和其他预定义的RNNCell包装器。

扩展RNNCell意味着至少覆盖state_size属性,output_size属性和调用方法。 Tensorflow的预构建单元内部表示状态作为信号张量或张量的元组。 如果是单一的张量,则进入单元格后会被细分为隐藏状态(或任何情况),然后最终将新状态粘在一起。

我发现将单元格状态视为元组更简单。 在这种情况下,state_size属性只是一个元组,其长度取决于您跟踪哪一个。 output_size是单元格输出的长度。 调用函数是您的单元格的逻辑空间。 RNNCell的__call__方法将包装您的调用方法,并帮助实现范围界定和其他逻辑。

为了使你的子类成为有效的RNNCell,调用方法必须接受参数input和state,并返回一个输出元组new_state,其中state和new_state必须具有相同的形式。

请注意,如果你构建一个新的RNNCell,你想要与tensorflow中已经存在的tensorflow变量一起使用,则可以在__init__方法中将_reuse = True参数传递给父构造函数。 如果这些变量已经存在但是你没有通过_reuse = True,那么你会收到一个错误,因为tensorflow既不会覆盖现有的变量,也不会在没有显式指令的情况下重用它们。

作为参考,HMLSTMCell类是用于表示上述分层和多尺度RNN的一个单元的RNNCell。 其实施涵盖以上所有要点。

该代码还在rnn_cell_impl模块中使用了一个未记录的函数,叫做_linear,这个函数在RNNCell子类中大部分被使用。 这有点冒险,因为它显然不是为了外部使用,但它是一个有用的小函数,用来处理矩阵乘法及增加权重和偏差。

不寻常的多层RNN

如果你正在构建一个多层次的RNN,其中层不会简单地将其输出从一层到另一层传递,那么必须构建你自己的MultiCell版本。 很像内置的MultiRNNCell,你的多单元应该扩展RNNCell。

在这种情况下,单元格状态将是一个列表,其中每个元素是与其索引对应的图层的单元格状态。

在两种情况下编写自己的多单元格是有用的。 首先,在你想要在一层的结果之前做一些事情,然后再将它传递到下一层的单元格,但是你不想执行最低层的操作(否则你可以把它建进单元)。

第二,如果有上一个时间步骤的信息需要在不同的层之间进行分配,那么这是非常有用的,但这并不完全符合从一个时间点到下一个阶段的状态。

例如,在分层多通道LSTM中,每个单元格希望从其上一层的层上接收隐藏状态,作为其输入的一部分。 这不符合堆叠RNN的标准思想,所以我们不能使用通常的MultiRNNCell。 作为参考,这里是MultiHMLSTMCell的实现。

用tf.scan构建动态RNN

分层多尺度LSTM网络要求将隐藏状态馈送到某些输出网络。 我们已经看到,这个HMLSTM网络不能整齐地适应tensorflowRNN范例,由于它如何处理层之间的传递信息; 现在我们又碰到了另一个障碍。 我们需要通过另一个网络传递所有图层的隐藏状态,以获得我们真正关心的输出,而不是考虑最后一层输出的网络输出。 不仅如此,我们关心一些被视为单元状态的指示神经元的价值。

由于这些原因,我们不能使用tf.dynamic_rnn网络,它只返回每个时间步长和最终状态的输出。

相反,tf.scan具有任意函数和有序的元素集合。 然后将该函数应用于集合中的每个元素,跟踪一些累加器。 它在进程的每个步骤返回累加器的值的有序集合。

这对于更个性化的RNN是完美的。 因为你可以定义函数,所以可以选择输入、输出和状态。之后,你可以在每个时间步骤中获得状态的完整记录,而不仅仅是输出。

在HMLSTM的情况下,我们使用这些状态来跟踪边界检测,并且我们还对它们进行映射以获得最终的预测。

这里是参考代码。

结论

在这篇文章中,我们研究了在tensorflow中处理RNN的标准工具,并探讨了一些更灵活的选项,以便在这些工具不足时使用。

本文由北邮@爱可可-爱生活老师推荐,阿里云云栖社区组织翻译。

文章原标题《Writing RNNs in Tensorflow》,

作者:Noam Finkelstein 译者:TIAMO_ZN审阅:袁虎

相关推荐