【问题标题】:Tensorflow RNN-LSTM - reset hidden stateTensorflow RNN-LSTM - 重置隐藏状态
【发布时间】:2017-08-01 06:55:25
【问题描述】:

我正在构建一个用于语言识别的全状态 LSTM。 在有状态的情况下,我可以用较小的文件训练网络,一个新的批次就像讨论中的下一个句子。 然而,为了使网络得到适当的训练,我需要在一些批次之间重置 LSTM 的隐藏状态。

我正在使用一个变量来存储 LSTM 的 hidden_​​state 以提高性能:

    with tf.variable_scope('Hidden_state'):
        hidden_state = tf.get_variable("hidden_state", [self.num_layers, 2, self.batch_size, self.hidden_size],
                                       tf.float32, initializer=tf.constant_initializer(0.0), trainable=False)
        # Arrange it to a tuple of LSTMStateTuple as needed
        l = tf.unstack(hidden_state, axis=0)
        rnn_tuple_state = tuple([tf.contrib.rnn.LSTMStateTuple(l[idx][0], l[idx][1])
                                for idx in range(self.num_layers)])

    # Build the RNN
    with tf.name_scope('LSTM'):
        rnn_output, _ = tf.nn.dynamic_rnn(cell, rnn_inputs, sequence_length=input_seq_lengths,
                                          initial_state=rnn_tuple_state, time_major=True)

现在我对如何重置隐藏状态感到困惑。我尝试了两种解决方案,但都不起作用:

第一个解决方案

重置“hidden_​​state”变量:

rnn_state_zero_op = hidden_state.assign(tf.zeros_like(hidden_state))

它确实有效,我认为这是因为在运行 rnn_state_zero_op 操作后,unstack 和 tuple 构造没有“重新播放”到图中。

第二种解决方案

LSTMStateTuple vs cell.zero_state() for RNN in Tensorflow 之后,我尝试使用以下命令重置单元格状态:

rnn_state_zero_op = cell.zero_state(self.batch_size, tf.float32)

它似乎也不起作用。

问题

我想到了另一个解决方案,但它充其量只是猜测:我没有保留 tf.nn.dynamic_rnn 返回的状态,我已经想到了,但我得到了一个元组,但我找不到方法构建一个操作来重置元组。

在这一点上,我不得不承认我不太了解 tensorflow 的内部工作原理,以及是否有可能做我想做的事情。 有合适的方法吗?

谢谢!

【问题讨论】:

    标签: tensorflow stateful rnn


    【解决方案1】:

    感谢this answer to another question,我能够找到一种方法来完全控制 RNN 的内部状态是否(以及何时)应重置为 0。

    首先你需要定义一些变量来存储RNN的状态,这样你就可以控制它了:

    with tf.variable_scope('Hidden_state'):
        state_variables = []
        for state_c, state_h in cell.zero_state(self.batch_size, tf.float32):
            state_variables.append(tf.nn.rnn_cell.LSTMStateTuple(
                tf.Variable(state_c, trainable=False),
                tf.Variable(state_h, trainable=False)))
        # Return as a tuple, so that it can be fed to dynamic_rnn as an initial state
        rnn_tuple_state = tuple(state_variables)
    

    请注意,此版本直接定义了 LSTM 使用的变量,这比我的问题中的版本要好得多,因为您不必取消堆叠和构建元组,这会在图中添加一些您无法运行的操作明确的。

    第二次构建 RNN 并检索最终状态:

    # Build the RNN
    with tf.name_scope('LSTM'):
        rnn_output, new_states = tf.nn.dynamic_rnn(cell, rnn_inputs,
                                                   sequence_length=input_seq_lengths,
                                                   initial_state=rnn_tuple_state,
                                                   time_major=True)
    

    所以现在你有了 RNN 的新内部状态。您可以定义两个操作来管理它。

    第一个将更新下一批的变量。所以在下一批中,RNN 的“initial_state”将被输入上一批的最终状态:

    # Define an op to keep the hidden state between batches
    update_ops = []
    for state_variable, new_state in zip(rnn_tuple_state, new_states):
        # Assign the new state to the state variables on this layer
        update_ops.extend([state_variable[0].assign(new_state[0]),
                           state_variable[1].assign(new_state[1])])
    # Return a tuple in order to combine all update_ops into a single operation.
    # The tuple's actual value should not be used.
    rnn_keep_state_op = tf.tuple(update_ops)
    

    您应该在想要运行批处理并保持内部状态的任何时候将此操作添加到您的会话中。

    当心:如果您在调用此操作的情况下运行第 1 批,则第 2 批将以第 1 批的最终状态开始,但如果您在运行第 2 批时不再次调用它,则第 3 批将也从第 1 批最终状态开始。我的建议是每次运行 RNN 时都添加这个操作。

    第二个操作将用于将 RNN 的内部状态重置为零:

    # Define an op to reset the hidden state to zeros
    update_ops = []
    for state_variable in rnn_tuple_state:
        # Assign the new state to the state variables on this layer
        update_ops.extend([state_variable[0].assign(tf.zeros_like(state_variable[0])),
                           state_variable[1].assign(tf.zeros_like(state_variable[1]))])
    # Return a tuple in order to combine all update_ops into a single operation.
    # The tuple's actual value should not be used.
    rnn_state_zero_op = tf.tuple(update_ops)
    

    只要你想重置内部状态,你就可以调用这个操作。

    【讨论】:

    • 谢谢。这很有帮助!你能解决使用可变批量大小的问题吗?
    • 嗨,我从来没有调查过这个问题。在我的用例中,只有当我到达训练集的末尾时才会遇到问题。我已经通过填充我的输入解决了这个问题。这是我的代码:github.com/inikdom/rnn-speech/blob/dev/models/AcousticModel.py 您应该查看第 142 到 153 行中的填充。也许它可以提供帮助。
    • 我正在打电话,所以稍后我会检查代码。不过,我有一个快速的问题。您正在用零填充短序列。但我说的是批量大小。假设您有 107 个不同的序列,并且您的批次大小为 10。所以您将有 11 个批次,但最后一批(第 11 个)的批次大小为 7。您是否还为仅包含 0 的最后一批创建了额外的 3 个序列?
    • 没错,我所指的填充是关于为最后一批创建附加序列。它由零组成,并且 input_length 也为零,以免影响学习。
    【解决方案2】:

    一个 LSTM 层的简化版 AMairesse 帖子:

    zero_state = tf.zeros(shape=[1, units[-1]])
    self.c_state = tf.Variable(zero_state, trainable=False)
    self.h_state = tf.Variable(zero_state, trainable=False)
    self.init_encoder = tf.nn.rnn_cell.LSTMStateTuple(self.c_state, self.h_state)
    
    self.output_encoder, self.state_encoder = tf.nn.dynamic_rnn(cell_encoder, layer, initial_state=self.init_encoder)
    
    # save or reset states
    self.update_ops += [self.c_state.assign(self.state_encoder.c, use_locking=True)]
    self.update_ops += [self.h_state.assign(self.state_encoder.h, use_locking=True)]
    

    或者您可以使用替换 init_encoder 在 step == 0 处重置状态(您需要将 self.step_tf 作为占位符传递给 session.run()):

    self.step_tf = tf.placeholder_with_default(tf.constant(-1, dtype=tf.int64), shape=[], name="step")
    
    self.init_encoder = tf.cond(tf.equal(self.step_tf, 0),
      true_fn=lambda: tf.nn.rnn_cell.LSTMStateTuple(zero_state, zero_state),
      false_fn=lambda: tf.nn.rnn_cell.LSTMStateTuple(self.c_state, self.h_state))
    

    【讨论】: