【问题标题】:Tensorflow Changing Batch Size for RNN During Text GenerationTensorflow 在文本生成期间更改 RNN 的批量大小
【发布时间】:2016-11-05 12:12:31
【问题描述】:

我构建了一个普通字符级别的 RNN,并在一些数据上对其进行了训练。到目前为止一切正常。

但现在我想使用模型来生成文本。问题是在这个文本生成阶段,batch_size 为 1,每批的 num_steps 也不同。

这导致了几个错误,我尝试了一些 hacky 修复,但它们不起作用。处理这种情况的通常方法是什么?

编辑:更具体地说,我的输入占位符的形状为 [None, num_steps],但问题在于初始状态不接受 [None, hidden_​​size] 的形状。

【问题讨论】:

    标签: machine-learning tensorflow deep-learning


    【解决方案1】:

    我也处理过同样的问题。您需要处理两个问题。第一个是将批量大小和步长调整为 1。您可以通过将输入序列中的批量和长度维度设置为无来轻松做到这一点。即 [None, None, 128],128 代表 128 个 ascii 字符(尽管您可能只需要字符的一个子集,因此您可以使用更少。)

    处理初始状态是最棘手的。这是因为您需要在调用 session.run() 之间保存它。由于您的 num_steps 为一,并且在每一步开始时将其初始化为零。我建议做的是允许初始状态作为占位符传递并从 session.run() 返回。这样,模型的用户可以在批次之间继续当前状态。最简单的方法是确保您使用的每个 RNN 的 state_is_tupel 都设置为 False,并且您只需从动态 RNN 函数返回最终状态张量。

    我个人不喜欢将 state_is_tupel 设置为 False,因为它已被弃用,所以我编写了自己的代码来展平状态元组。以下代码来自我的项目,用于生成声音。

            batch_size = tf.shape(self.input_sound)[0]
            rnn = tf.nn.rnn_cell.MultiRNNCell([tf.nn.rnn_cell.LSTMCell(self.hidden_size) for _ in range(self.n_hidden)])  
            zero_state = pack_state_tupel(rnn.zero_state(batch_size, tf.float32))
            self.input_state = tf.placeholder_with_default(zero_state, None)
            state = unpack_state_tupel(self.input_state, rnn.state_size)
    
            rnn_input_seq = tf.cond(self.is_training, lambda: self.input_sound[:, :-1], lambda: self.input_sound)
            output, final_state = tf.nn.dynamic_rnn(rnn, rnn_input_seq, initial_state = state)
    
            with tf.variable_scope('output_layer'):
                output = tf.reshape(output, (-1, self.hidden_size))
                W = tf.get_variable('W', (self.hidden_size, self.sample_length))
                b = tf.get_variable('b', (self.sample_length,))
                output = tf.matmul(output, W) + b
                output = tf.reshape(output, (batch_size, -1, self.sample_length))
    
    
            self.output_state = pack_state_tupel(final_state)
            self.output_sound = output
    

    它使用了以下两个函数,它们应该适用于任何类型的 RNN,尽管我只用这个模型对其进行了测试。

    def pack_state_tupel(state_tupel):
        if isinstance(state_tupel, tf.Tensor) or not hasattr(state_tupel, '__iter__'):
            return state_tupel
        else:
            return tf.concat(1, [pack_state_tupel(item) for item in state_tupel])
    
    def unpack_state_tupel(state_tensor, sizes):
        def _unpack_state_tupel(state_tensor_, sizes_, offset_):
            if isinstance(sizes_, tf.Tensor) or not hasattr(sizes_, '__iter__'): 
                return tf.reshape(state_tensor_[:, offset_ : offset_ + sizes_], (-1, sizes_)), offset_ + sizes_
            else:
                result = []
                for size in sizes_:
                    s, offset_ = _unpack_state_tupel(state_tensor_, size, offset_)
                    result.append(s)
                if isinstance(sizes_, tf.nn.rnn_cell.LSTMStateTuple):
                    return tf.nn.rnn_cell.LSTMStateTuple(*result), offset_
                else:
                    return tuple(result), offset_
        return _unpack_state_tupel(state_tensor, sizes, 0)[0]
    

    最后在我的生成函数中看看我如何管理隐藏状态s

    def generate(self, seed, steps):
        def _step(x, s = None):
            feed_dict = {self.input_sound: np.reshape(x, (1, -1, self.sample_length))}
            if s is not None:
                feed_dict[self.input_state] = s
            return self.session.run([self.output_sound, self.output_state], feed_dict)
    
        seed_pad = self.sample_length - len(seed) % self.sample_length
        if seed_pad: seed = np.pad(seed, (seed_pad, 0), 'constant')
    
        y, s = _step(seed)
        y = y[:, -1:]
    
        result = [seed, y.flatten()]
        for _ in range(steps):
            y, s = _step(y, s)
            result.append(y.flatten())
    
        return np.concatenate(result) 
    

    【讨论】:

      【解决方案2】:

      如何使用 tf 的重用。

      class Model():
           def __init__(self,batch_size,reuse)
                self.batch_size = batch_size
                self.reuse = reuse
                self.input_x = tf.placeholder(.....)
                self.input_y = tf.placeholder(.....)
           def inference(self)
                with tf.variable_scope('xxx',reuse=self.reuse)
                     ...
                     cell = tf.contrib.rnn.LSTMCell(xxx,reuse=self.reuse)
                     init_state = cell.zero_state(self.batch_size, dtype=tf.float32)
                     ...
           def train_op(self):
               ....
      
      if __name__ == '__main__':
            train_model = model(batch=128,reuse=False)
            test_model = model(batch=1,reuse=True)
            with tf.Session() as sess:
                 sess.run(train_model.train_op,feed_dict={...})
                 sess.run(test_model.prediction,feed_dict={...})
      

      当然,它看起来像是在 tf 图中定义了 2 个分支,可能看起来不太好。但是如果你不想通过 RNN Cell 的 init_state ,这是一种方法。

      【讨论】:

        【解决方案3】:

        正如 chasep255 解决方案中提到的,两个棘手的部分是:initial_statebatch_sizesequence lenght

        第一个棘手的部分:

        如果我们将batch_sizesequence len 设置为None,我们可以在推理期间对其进行更改。我们的第一步是将输入形状定义为[None, None]

        self.inputs = tf.placeholder(tf.int32, shape=(None, None), name='inputs')
        self.targets = tf.placeholder(tf.int32, shape=(None, None), name='targets')
        

        第二个棘手的部分:

        下一步是定义动态initial_state。对于这部分,如 chasep255 解决方案中所述,我们可以使用placeholder,我们自己将zero_state 传递给RNN。为此,我使用tf.shape API 根据输入序列获取不同的batch size(在我的例子中:self.inp):

         cells = [tf.nn.rnn_cell.GRUCell(self.rnn_size)] * self.layer_size
         rnn_cell = tf.nn.rnn_cell.MultiRNNCell(cells)
         self.init_state = rnn_cell.zero_state(tf.shape(self.inp)[0], tf.float32)
         self.rnn_outputs, self.final_state = tf.nn.dynamic_rnn(rnn_cell, self.inp,
                                                                           initial_state=self.init_state,
                                                                           dtype=tf.float32)
        

        现在在训练中,我跑了两次sess.run()首先,用零值填充 initial_state。为此,我使用了一个大小为 [training_batch_size * hidden_lstm_size] 且值为零的数组将其传递给 placeholder第二,我再次使用传递状态到下一个时间步,placeholder 类似:

          new_state = sess.run(self.initial_state,
                                         feed_dict={self.inputs: np.zeros([self.batch_size_in_train, lstm_hidden_size], dtype=np.int32)})
        
          for x, y in batch_gen:
        
              feed_dict = {
                            self.inputs: x,
                            self.targets: y,
                            self.initial_state: new_state
                        }
                        _, step, new_state, loss = sess.run([self.optimizer, 
                                                           self.global_step, 
                                                           self.final_state, 
                                                           self.loss],
                                                           feed_dict) 
        

        在推理中,我们可以做同样的事情。这次我们用大小[1 * 1] 的零值填充initial_state。我们的推理部分是:

        new_state = sess.run(self.initial_state, feed_dict={self.inputs: np.zeros([1, 1], dtype=np.int32)})
                for i in range(400):
                    x = np.zeros((1, 1))
                    x[0, 0] = c
                    feed_dict = {
                        self.inputs: x,
                        self.keep_prob: 1,
                        self.initial_state: new_state
                    }
                    preds, new_state = sess.run(
                        [self.prediction, self.final_state],
                        feed_dict=feed_dict)
        

        查看完整代码here

        【讨论】:

          猜你喜欢
          • 2017-08-28
          • 1970-01-01
          • 2020-03-06
          • 1970-01-01
          • 2017-11-06
          • 2017-01-19
          • 1970-01-01
          • 2017-10-22
          • 1970-01-01
          相关资源
          最近更新 更多