在文本生成期间,Tensorflow更改RNN的批量大小

问题描述 投票:4回答:3

我构建了一个vanilla角色级别的RNN并对一些数据进行了训练。一切都很好,直到那里。

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

这导致了几个错误,我尝试了一些hacky修复,但它们无法正常工作。处理这个问题的常用方法是什么?

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

machine-learning tensorflow deep-learning
3个回答
4
投票

我已经解决了同样的问题。您需要处理两个问题。第一种方法是将批量大小和步长调整为1.您可以通过将输入序列中的批次和长度尺寸设置为无来轻松完成此操作。即[无,无,128],128代表128个ascii字符(尽管你可能使用较少,因为你可能只需要一个字符的子集。)

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

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

        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) 

0
投票

如何使用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,那就是一种方式。


0
投票

正如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) 

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

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

© www.soinside.com 2019 - 2024. All rights reserved.