淘先锋技术网

首页 1 2 3 4 5 6 7

Tensorflow提供了很好的API来实现RNN。但是仍然从头开始实现神经网络是一个有用的想法,原因有两个,首先,如果你是一个初学者,你将学到很多关于这个RNN的工作原理。我们使用一个API​​块来显示工作机制是什么。这就是我们试图在幕后实现的原因。其次,如果你想实现一些新的RNN风格,它可以完成你自己的一些操作,那么你必须编写自己的RNN块。我将尝试尽可能简单地演示如何做到这一点。

在我们开始编写自己的RNN层之前,让我们快速了解其工作原理。首先,快速回顾一下我们的多层感知器(MLP)模型。

cf87cb8aa499052eb04880cc20a0bbe8.png

图1:具有两个隐藏层的MLP

这两层之间有3 x 2 = 6个连接。如果我们想为每个连接赋予权重,我们可以创建一个维数为3 x 2的称重矩阵W。该操作可以用数学等式写成

207646258eb14624a4789c25847732ec.png

等式1:无非线性MLP中的分层运算

我们在这个等式中没有使用任何偏差项。如果我们在Li中输入N个数据项,则Li产生矩阵N×3。这与产生N×2的W相乘。这使得Li + 1是一个N×2矩阵。在tensorflow中,用矩阵乘法来理解神经网络的运算是非常重要的。

所以,如果我们有N个数据样本,每个样本都有3个特征,那么开发这样的MLP是很好的。但是,如果我们的数据是时间序列数据怎么办?在这些情况下,每个数据样本也具有一些时间步长。例如,将段落视为数据集。所以每个样本都是。但每行都有多个单词。每行中的单词数是该样本(行)的时间步长。因此,与前一种情况不同,我们的数据集维度现在为N x T x F,其中F是特征数量(如前一种情况中的3)。T是时间步长。这些数据不适合MLP,因为我们应该分别处理每个时间步长来提取更有意义的信息。RNN可以帮助我们这样做,如下操作:

9c1ff1d01632d90a269a06c7bc7cdb78.png

图2:3个时间步长的RNN操作

每个黑框中发生了什么。请注意,实际上只有一个黑框而不是三个黑框。我只是用这种方式来描述它们是如何工作的。这个框是一个有多个节点(比如说H)的简单层(比如MLP)。您可以想象我们只是在循环中应用先前的转换(MLP)。循环执行多少次将由时间步长决定。现在看到一个时间步长的输出被用于下一个时间步长。通过这种方式,RNN 尝试记住时间步长的序列。在数学上,我们可以写为

f23f4bd46f36e9871d823309926c7780.png

等式2:无非线性RNN中的分层运算

这里我假设I是时间t的输入(大小为N×F的矩阵),Yt-1是前一步的输出。因此选择了Wk和U。

现在我们可以毫不费力地开始我们的tensorflow编码。首先,我们为RNN层创建一个类。

class MyRNNCell(object): def __init__(self,nodes,name): self.nodes=nodes self.name=name
b690f1daaf6813a909f30354b192840a.png

self.nodes将告诉我们层中有多少个节点(H)。接下来,我只是创建权重结构。

def build(self,input_shape): self.input_shape=input_shape self.input_dim = self.input_shape[-1] with tf.variable_scope(self.name): self.Wk = tf.get_variable('Wk', shape=[self.input_dim, self.nodes]) self.U = tf.get_variable('Wr', shape=[self.nodes, self.nodes]) self.Ir = tf.get_variable('Initial_state',shape=[self.input_dim,self.nodes],trainable=False)
6632afb75374c8c28a82a764f7b6f5bf.png

您可以为这个函数使用任何名称。我对build这个名称没有什么意见,因为这是用于Keras框架实现的自定义层。您还可以在我们的代码中将F与input_dim关联起来。 完成这步后,我们可以分两步编写RNN的实际操作。首先,我们编写一个在循环执行期间每次都会调用的函数。

def rnn_step(self,previous_output,step_input): ci_out=tf.matmul(step_input,self.Wk,name='kernel_mult') po_out=tf.matmul(previous_output,self.U,name='recurrent_mult') step_output=tf.tanh(tf.add(ci_out,po_out)) return step_output
1a92a12e46463c610d56aefa0b98ea3a.png

如果你看一下RNN的图,会发现这很简单。注意,在每个时间步长(内部循环)中,它会生成一个输出,但需要两个输入(前一个输出和当前的输入)。我们的等式2的第一部分被计算为ci_out,而第二部分被计算为po_out。step_output是对这些部分进行非线性处理后的相加。当我们在一个特殊的tensorflow迭代结构中调用此函数时,此函数的参数顺序必须是这样的。第二步也是最后一步是在循环中实际调用此函数。但是当我们处理tensors时,这个循环将不是正常的for或while循环。就像,

def loop_over_timestep(self,input): input_tm=tf.transpose(input,[1,0,2]) initial_state=tf.matmul(input_tm[0],self.Ir)  output_tm= tf.scan(self.rnn_step,input_tm,initializer=initial_state) output=tf.transpose(output_tm,[1,0,2]) return output
65bf3ce53a46d6cf2759c7ebed3d1637.png

这是我们RNN操作中最重要的部分。因此,我将逐步讨论这个问题。

  • 记住我们的输入数组是3D(N x T x F)。这应作为参数input给函数loop_over_timestep 。请注意,在T的每个步骤中,我们为层提供一个维度为N x F的数组。为了简化此操作,我们将输入time_major,即T应为第一维度。tf.transpose()为我们改变了这些轴。所以input_tm现在的形状为T×N个X F数组。
  • 现在看一下图2中的红色箭头。我们不能简单地在第一步没有两个输入的情况下启动一个循环。由于没有先前的输出,我只是使用第一个时间步长的输入input_tm[0]和一个不可训练的权重self.Ir生成一个随机输出 。
  • 使用tf.scan()执行循环,而不是for和while。这是一个来自tensorflow的很棒的函数。它接受:在每个时间步长调用的函数,在本例中为rnn_step,3D输入数组为input_tm,第一维代表循环变量,并有一个初始化程序为第一个时间步长提供输入。这将返回一个按时间步长上收集的tensors列表。在我们的例子中,由于我们的RNN层中有H个节点,因此其形状应为T x N x H.
  • 最后,我们不会以time_major格式返回3D数组。出于这个原因,我们再次将其转置为N x T x F。

我正在添加另一个函数,以方便使用这个MyRNNCell类。

def MyRNN(nodes,input,name,return_sequence=True): rnncell=psmRNNCell(nodes,name) input_shape=get_layer_shape(input) rnncell.build(input_shape) output=rnncell.loop_over_timestep(input) if(not return_sequence): output = tf.transpose(output, [1, 0, 2]) output = output[-1] return output return output
9faedba303d64ebfb41e688c210b9443.png

请注意,我可以通过简单return_sequence标志返回整个输出序列或仅返回最后一步的输出。

RNN是处理时间序列数据进行分类或回归的重要工具。有许多网络架构以多种方式使用RNN及其变体。我