在TensorFlow中获取dynamic_rnn的最后输出

16

我有一个形状为[batch, None, dim]的三维张量,其中第二个维度即时间步是未知的。我使用dynamic_rnn来处理这样的输入,就像下面的代码片段一样:

import numpy as np
import tensorflow as tf

batch = 2
dim = 3
hidden = 4

lengths = tf.placeholder(dtype=tf.int32, shape=[batch])
inputs = tf.placeholder(dtype=tf.float32, shape=[batch, None, dim])
cell = tf.nn.rnn_cell.GRUCell(hidden)
cell_state = cell.zero_state(batch, tf.float32)
output, _ = tf.nn.dynamic_rnn(cell, inputs, lengths, initial_state=cell_state)

实际上,使用一些实际数字运行此片段后,我得到了一些合理的结果:

inputs_ = np.asarray([[[0, 0, 0], [1, 1, 1], [2, 2, 2], [3, 3, 3]],
                    [[6, 6, 6], [7, 7, 7], [8, 8, 8], [9, 9, 9]]],
                    dtype=np.int32)
lengths_ = np.asarray([3, 1], dtype=np.int32)

with tf.Session() as sess:
    sess.run(tf.global_variables_initializer())
    output_ = sess.run(output, {inputs: inputs_, lengths: lengths_})
    print(output_)

输出结果为:

[[[ 0.          0.          0.          0.        ]
  [ 0.02188676 -0.01294564  0.05340237 -0.47148666]
  [ 0.0343586  -0.02243731  0.0870839  -0.89869428]
  [ 0.          0.          0.          0.        ]]

 [[ 0.00284752 -0.00315077  0.00108094 -0.99883419]
  [ 0.          0.          0.          0.        ]
  [ 0.          0.          0.          0.        ]
  [ 0.          0.          0.          0.        ]]]

有没有一种方法可以获取动态循环神经网络的最后一个相关输出并得到形状为[batch, 1, hidden]的3D张量?谢谢!


2
我喜欢你的代码风格!这是一个很好的问题,可以学习神经网络的工作原理。谢谢。 - O.rka
被接受的答案不是首选方法。请查看最后@ShaoTang的答案。 - Rahul
@Rahul 感谢您指出。不管怎样,看起来ShaoTang指向状态,而问题特别涉及输出。或者我错过了什么? - petrux
据我所知,state(根据TF命名规则)是表示单元内部状态的向量元组,通过时间步传递,而问题关注的是单元的最终输出。顺便说一下,状态大小并不一定与输出相同。 - petrux
如果官方文档中能够包含这样的示例来演示行为就好了。 - figs_and_nuts
@pertux 我认为LSTM有一个元组输出用于最后状态,其中包含了最后的状态和输出,所以你可以索引输出。 - Rahul
4个回答

14

这就是gather_nd的用途!

def extract_axis_1(data, ind):
    """
    Get specified elements along the first axis of tensor.
    :param data: Tensorflow tensor that will be subsetted.
    :param ind: Indices to take (one for each element along axis 0 of data).
    :return: Subsetted tensor.
    """

    batch_range = tf.range(tf.shape(data)[0])
    indices = tf.stack([batch_range, ind], axis=1)
    res = tf.gather_nd(data, indices)

    return res

对于你的情况:

output = extract_axis_1(output, lengths - 1)

现在output是一个维度为[batch_size, num_cells]的张量。


太好了!我尝试编辑答案并添加所有代码,以便它是自包含的,但我收到了一个奇怪的消息,说“编辑队列已满”。无论如何,感谢您的回答! - petrux

9
从以下两个来源中, http://www.wildml.com/2016/08/rnns-in-tensorflow-a-practical-guide-and-undocumented-features/
这篇文章介绍了如何在Tensorflow中使用循环神经网络(RNNs)并讨论了一些未记录的特性。
outputs, last_states = tf.nn.dynamic_rnn(
cell=cell,
dtype=tf.float64,
sequence_length=X_lengths,
inputs=X)

或者https://github.com/ageron/handson-ml/blob/master/14_recurrent_neural_networks.ipynb

很明显,可以直接从dynamic_rnn调用的第二个输出中提取last_states。它将给您跨所有层的last_states(在LSTM中,它由LSTMStateTuple组成),而输出包含最后一层中的所有状态。


3
上帝啊,我也对TensorFlow文档感到极度恼火。 - figs_and_nuts
1
如果您正在使用dropout,则将应用于dynamic_rnn返回的第一个输出,而不是第二个输出。请参见此处 - figs_and_nuts

5

好的 - 看起来实际上有一个更简单的解决方案。正如@Shao Tang和@Rahul提到的那样,最好的方法是通过访问最终单元状态来完成这个问题。原因在于:

  • 如果您查看GRUCell源代码(如下所示),您将看到单元维护的“状态”实际上就是隐藏层权重本身。因此,当tf.nn.dynamic_rnn返回最终状态时,它实际上返回的是您感兴趣的最终隐藏层权重。为了证明这一点,我刚刚调整了您的设置并得到了结果:

GRUCell Call (rnn_cell_impl.py):

def call(self, inputs, state):
"""Gated recurrent unit (GRU) with nunits cells."""
if self._gate_linear is None:
      bias_ones = self._bias_initializer
if self._bias_initializer is None:
        bias_ones = init_ops.constant_initializer(1.0, dtype=inputs.dtype)
with vs.variable_scope("gates"):  # Reset gate and update gate.
self._gate_linear = _Linear(
            [inputs, state],
2 * self._num_units,
True,
bias_initializer=bias_ones,
kernel_initializer=self._kernel_initializer)
    value = math_ops.sigmoid(self._gate_linear([inputs, state]))
    r, u = array_ops.split(value=value, num_or_size_splits=2, axis=1)
    r_state = r * state
if self._candidate_linear is None:
with vs.variable_scope("candidate"):
self._candidate_linear = _Linear(
            [inputs, r_state],
self._num_units,
True,
bias_initializer=self._bias_initializer,
kernel_initializer=self._kernel_initializer)
    c = self._activation(self._candidate_linear([inputs, r_state]))
    new_h = u * state + (1 - u) * c
return new_h, new_h

解决方案:

import numpy as np
import tensorflow as tf

batch = 2
dim = 3
hidden = 4

lengths = tf.placeholder(dtype=tf.int32, shape=[batch])
inputs = tf.placeholder(dtype=tf.float32, shape=[batch, None, dim])
cell = tf.nn.rnn_cell.GRUCell(hidden)
cell_state = cell.zero_state(batch, tf.float32)
output, state = tf.nn.dynamic_rnn(cell, inputs, lengths, initial_state=cell_state)

inputs_ = np.asarray([[[0, 0, 0], [1, 1, 1], [2, 2, 2], [3, 3, 3]],
                    [[6, 6, 6], [7, 7, 7], [8, 8, 8], [9, 9, 9]]],
                    dtype=np.int32)
lengths_ = np.asarray([3, 1], dtype=np.int32)

with tf.Session() as sess:
    sess.run(tf.global_variables_initializer())
    output_, state_ = sess.run([output, state], {inputs: inputs_, lengths: lengths_})
    print (output_)
    print (state_)

输出:

[[[ 0.          0.          0.          0.        ]
  [-0.24305521 -0.15512943  0.06614969  0.16873555]
  [-0.62767833 -0.30741733  0.14819752  0.44313088]
  [ 0.          0.          0.          0.        ]]

 [[-0.99152333 -0.1006391   0.28767768  0.76360202]
  [ 0.          0.          0.          0.        ]
  [ 0.          0.          0.          0.        ]
  [ 0.          0.          0.          0.        ]]]
[[-0.62767833 -0.30741733  0.14819752  0.44313088]
 [-0.99152333 -0.1006391   0.28767768  0.76360202]]

对于使用LSTMCell(另一种流行的选项)的其他读者,事情有些不同。LSTMCell以不同的方式维护状态 - 单元格状态可以是元组或实际单元格状态和隐藏状态的串联版本。因此,要访问最终的隐藏权重,您可以在单元格初始化期间将is_state_tuple设置为True,最终状态将是一个元组:(最终单元格状态,最终隐藏权重)。所以,在这种情况下,

_, (_, h) = tf.nn.dynamic_rnn(cell, inputs, lengths, initial_state=cell_state)

将给你最终的权重。

参考文献: Tensorflow LSTM中的c_state和m_state https://github.com/tensorflow/tensorflow/blob/438604fc885208ee05f9eef2d0f2c630e1360a83/tensorflow/python/ops/rnn_cell_impl.py#L308 https://github.com/tensorflow/tensorflow/blob/438604fc885208ee05f9eef2d0f2c630e1360a83/tensorflow/python/ops/rnn_cell_impl.py#L415


2

实际上,解决方案并不难。我实现了以下代码:

slices = []
for index, l in enumerate(tf.unstack(lengths)):
    slice = tf.slice(rnn_out, begin=[index, l - 1, 0], size=[1, 1, 3])
    slices.append(slice)
last = tf.concat(0, slices)

因此,完整的代码片段如下:

import numpy as np
import tensorflow as tf

batch = 2
dim = 3
hidden = 4

lengths = tf.placeholder(dtype=tf.int32, shape=[batch])
inputs = tf.placeholder(dtype=tf.float32, shape=[batch, None, dim])
cell = tf.nn.rnn_cell.GRUCell(hidden)
cell_state = cell.zero_state(batch, tf.float32)
output, _ = tf.nn.dynamic_rnn(cell, inputs, lengths, initial_state=cell_state)

inputs_ = np.asarray([[[0, 0, 0], [1, 1, 1], [2, 2, 2], [3, 3, 3]],
                    [[6, 6, 6], [7, 7, 7], [8, 8, 8], [9, 9, 9]]],
                    dtype=np.int32)
lengths_ = np.asarray([3, 1], dtype=np.int32)

slices = []
for index, l in enumerate(tf.unstack(lengths)):
    slice = tf.slice(output, begin=[index, l - 1, 0], size=[1, 1, 3])
    slices.append(slice)
last = tf.concat(0, slices)

with tf.Session() as sess:
    sess.run(tf.global_variables_initializer())
    outputs = sess.run([output, last], {inputs: inputs_, lengths: lengths_})
    print 'RNN output:'
    print(outputs[0])
    print
    print 'last relevant output:'
    print(outputs[1])

输出结果:

RNN output:
[[[ 0.          0.          0.          0.        ]
 [-0.06667092 -0.09284072  0.01098599 -0.03676109]
 [-0.09101103 -0.19828682  0.03546784 -0.08721405]
 [ 0.          0.          0.          0.        ]]

[[-0.00025157 -0.05704876  0.05527233 -0.03741353]
 [ 0.          0.          0.          0.        ]
 [ 0.          0.          0.          0.        ]
 [ 0.          0.          0.          0.        ]]]

last relevant output:
[[[-0.09101103 -0.19828682  0.03546784]]

 [[-0.00025157 -0.05704876  0.05527233]]]

1
我会使用tf.unstacks()代替tf.unpack(),因为tf.unpack已经过时了: https://www.tensorflow.org/api_docs/python/array_ops/slicing_and_joining#unpack - pabaldonedo
你可以使用tf.gather避免使用for循环,如此处所述:https://dev59.com/y5Xfa4cB1Zd3GeqPbjWt,并且该方法已被适应于同一问题,参考:http://stackoverflow.com/questions/41267829/retrieving-last-value-of-lstm-sequence-in-tensorflow/41273843#41273843。 - pabaldonedo
@pabaldonedo 我尝试使用 tf.gather 函数解决问题,但是我收到了一个警告,说 将稀疏的 IndexedSlices 转换为未知形状的密集张量。这可能会消耗大量内存。 我在网上搜索后发现,这可能是由于 tf.gather 引起的。回到我的解决方案,问题得到了解决。有什么提示吗? - petrux
这可能是因为 tf.gather 事先不知道张量的大小。例如,如果输入张量的形状为 [None, 10],那么 None(在执行图时动态设置的维度)将导致 tf.gather 出现警告。如果您有一个固定的 batch_size,比如说 5,并且将输入张量替换为 [5,10],我认为警告会消失。 - pabaldonedo
tf.slicesize 参数的最后一个维度应该是 hidden,即 4 吗? - Zhao

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接