使用 tf.while_loop (TensorFlow) 从图形中累积输出
Accumulating output from a graph using tf.while_loop (TensorFlow)
长话短说,我有一个 RNN 堆叠在 CNN 之上。
CNN 是单独创建和训练的。为了澄清事情,我们假设 CNN 以 [BATCH SIZE, H, W, C] 占位符的形式接受输入(H = 高度,W = 宽度,C = 通道数)。
现在,当堆叠在 RNN 之上时,组合网络的整体输入将具有以下形状:[BATCH SIZE, TIME SEQUENCE, H, W, C],即小批量中的每个样本由 TIME_SEQUENCE很多图片。此外,时间序列的长度是可变的。有一个名为 sequence_lengths
的单独占位符,其形状为 [BATCH SIZE],其中包含与小批量中每个样本的长度相对应的标量值。 TIME SEQUENCE的值对应最大可能的时序长度,对于长度较小的样本,剩余值补零
我想做什么
我想将 CNN 的输出累积到形状为 [BATCH SIZE, TIME SEQUENCE, 1] 的张量中(最后一个维度只包含 CNN 为每个批次元素的每个时间样本输出的最终分数)这样我就可以将整个信息块转发到堆叠在 CNN 之上的 RNN。棘手的是,我还希望能够将错误从 RNN 反向传播到 CNN(CNN 已经经过预训练,但我想稍微微调一下权重),所以我必须留在图中,即我无法对 session.run()
.
进行任何调用
选项A:
最简单的方法是将整个网络输入张量重塑为 [BATCH SIZE * TIME SEQUENCE, H, W, C]。这样做的问题是 BATCH SIZE * TIME SEQUENCE 可能大到 2000,所以当我试图将那么大的批次输入我的 CNN 时,我必然会 运行 内存不足。无论如何,批量大小对于训练来说太大了。此外,很多序列只是填充零,这会浪费计算。
选项B:
使用tf.while_loop
。我的想法是将单个小批量元素沿时间轴的所有图像视为 CNN 的小批量。本质上,CNN 将在每次迭代中处理大小为 [TIME SEQUENCE, H, W, C] 的批次(不完全是每次 TIME SEQUENCE 许多图像;确切数量将取决于序列长度)。我现在拥有的代码如下所示:
# The output tensor that I want populated
image_output_sequence = tf.Variable(tf.zeros([batch_size, max_sequence_length, 1], tf.float32))
# Counter for the loop. I'll process one batch element per iteration.
# One batch element contains a variable number of images for each time step. All these images will form a minibatch for the CNN.
loop_counter = tf.get_variable('loop_counter', dtype=tf.int32, initializer=0)
# Loop variables that will be passed to the body and cond methods
loop_vars = [input_image_sequence, sequence_lengths, image_output_sequence, loop_counter]
# input_image_sequence: [BATCH SIZE, TIME SEQUENCE, H, W, C]
# sequence_lengths: [BATCH SIZE]
# image_output_sequence: [BATCH SIZE, TIME SEQUENCE, 1]
# abbreviations for vars in loop_vars:
# iis --> input_image_sequence
# sl --> sequence_lengths
# ios --> image_output_sequence
# lc --> loop_counter
def cond(iis, sl, ios, lc):
return tf.less(lc, batch_size)
def body(iis, sl, ios, lc):
seq_len = sl[lc] # the sequence length of the current batch element
cnn_input_batch = iis[lc, :seq_len] # extract the relevant portion (the rest are just padded zeros)
# propagate this 'batch' through the CNN
my_cnn_model.process_input(cnn_input_batch)
# Pad the remaining indices
padding = [[0, 0], [0, batch_size - seq_len]]
padded_cnn_output = tf.pad(cnn_input_batch_features, paddings=padding, mode='CONSTANT', constant_values=0)
# The problematic part: assign these processed values to the output tensor
ios[lc].assign(padded_cnn_features)
return [iis, sl, ios, lc + 1]
_, _, result, _ = tf.while_loop(cond, body, loop_vars, swap_memory=True)
在 my_cnn_model.process_input
中,我只是通过普通 CNN 传递输入。其中创建的所有变量都带有 tf.AUTO_REUSE
,因此应该确保 while 循环为所有循环迭代重复使用相同的权重。
确切问题
image_output_sequence
是一个变量,但是不知怎么的,当tf.while_loop
调用body
方法的时候,就变成了Tensor类型的对象,无法赋值。我收到错误消息:Sliced assignment is only supported for variables
即使我使用另一种格式,比如使用 BATCH SIZE Tensors 的元组,每个维度都具有 [TIME SEQUENCE, H, W, C],这个问题仍然存在。
我也愿意完全重新设计代码,只要能很好地完成工作即可。
解决方案是使用TensorArray
类型的对象,它是专门为解决此类问题而制作的。下面一行:
image_output_sequence = tf.Variable(tf.zeros([batch_size, max_sequence_length, 1], tf.float32))
替换为:
image_output_sequence = tf.TensorArray(size=batch_size, dtype=tf.float32, element_shape=[max_sequence_length, 1], infer_shape=True)
TensorArray
实际上并不要求每个元素都有固定的形状,但对于我来说它是固定的,所以最好强制执行。
然后在 body
函数中,替换为:
ios[lc].assign(padded_cnn_features)
与:
ios = ios.write(lc, padded_cnn_output)
然后在tf.while_loop
语句之后,可以把TensorArray
堆叠起来形成一个正则的Tensor
进一步处理:
stacked_tensor = result.stack()
长话短说,我有一个 RNN 堆叠在 CNN 之上。 CNN 是单独创建和训练的。为了澄清事情,我们假设 CNN 以 [BATCH SIZE, H, W, C] 占位符的形式接受输入(H = 高度,W = 宽度,C = 通道数)。
现在,当堆叠在 RNN 之上时,组合网络的整体输入将具有以下形状:[BATCH SIZE, TIME SEQUENCE, H, W, C],即小批量中的每个样本由 TIME_SEQUENCE很多图片。此外,时间序列的长度是可变的。有一个名为 sequence_lengths
的单独占位符,其形状为 [BATCH SIZE],其中包含与小批量中每个样本的长度相对应的标量值。 TIME SEQUENCE的值对应最大可能的时序长度,对于长度较小的样本,剩余值补零
我想做什么
我想将 CNN 的输出累积到形状为 [BATCH SIZE, TIME SEQUENCE, 1] 的张量中(最后一个维度只包含 CNN 为每个批次元素的每个时间样本输出的最终分数)这样我就可以将整个信息块转发到堆叠在 CNN 之上的 RNN。棘手的是,我还希望能够将错误从 RNN 反向传播到 CNN(CNN 已经经过预训练,但我想稍微微调一下权重),所以我必须留在图中,即我无法对 session.run()
.
选项A: 最简单的方法是将整个网络输入张量重塑为 [BATCH SIZE * TIME SEQUENCE, H, W, C]。这样做的问题是 BATCH SIZE * TIME SEQUENCE 可能大到 2000,所以当我试图将那么大的批次输入我的 CNN 时,我必然会 运行 内存不足。无论如何,批量大小对于训练来说太大了。此外,很多序列只是填充零,这会浪费计算。
选项B: 使用
tf.while_loop
。我的想法是将单个小批量元素沿时间轴的所有图像视为 CNN 的小批量。本质上,CNN 将在每次迭代中处理大小为 [TIME SEQUENCE, H, W, C] 的批次(不完全是每次 TIME SEQUENCE 许多图像;确切数量将取决于序列长度)。我现在拥有的代码如下所示:# The output tensor that I want populated image_output_sequence = tf.Variable(tf.zeros([batch_size, max_sequence_length, 1], tf.float32)) # Counter for the loop. I'll process one batch element per iteration. # One batch element contains a variable number of images for each time step. All these images will form a minibatch for the CNN. loop_counter = tf.get_variable('loop_counter', dtype=tf.int32, initializer=0) # Loop variables that will be passed to the body and cond methods loop_vars = [input_image_sequence, sequence_lengths, image_output_sequence, loop_counter] # input_image_sequence: [BATCH SIZE, TIME SEQUENCE, H, W, C] # sequence_lengths: [BATCH SIZE] # image_output_sequence: [BATCH SIZE, TIME SEQUENCE, 1] # abbreviations for vars in loop_vars: # iis --> input_image_sequence # sl --> sequence_lengths # ios --> image_output_sequence # lc --> loop_counter def cond(iis, sl, ios, lc): return tf.less(lc, batch_size) def body(iis, sl, ios, lc): seq_len = sl[lc] # the sequence length of the current batch element cnn_input_batch = iis[lc, :seq_len] # extract the relevant portion (the rest are just padded zeros) # propagate this 'batch' through the CNN my_cnn_model.process_input(cnn_input_batch) # Pad the remaining indices padding = [[0, 0], [0, batch_size - seq_len]] padded_cnn_output = tf.pad(cnn_input_batch_features, paddings=padding, mode='CONSTANT', constant_values=0) # The problematic part: assign these processed values to the output tensor ios[lc].assign(padded_cnn_features) return [iis, sl, ios, lc + 1] _, _, result, _ = tf.while_loop(cond, body, loop_vars, swap_memory=True)
在 my_cnn_model.process_input
中,我只是通过普通 CNN 传递输入。其中创建的所有变量都带有 tf.AUTO_REUSE
,因此应该确保 while 循环为所有循环迭代重复使用相同的权重。
确切问题
image_output_sequence
是一个变量,但是不知怎么的,当tf.while_loop
调用body
方法的时候,就变成了Tensor类型的对象,无法赋值。我收到错误消息:Sliced assignment is only supported for variables
即使我使用另一种格式,比如使用 BATCH SIZE Tensors 的元组,每个维度都具有 [TIME SEQUENCE, H, W, C],这个问题仍然存在。
我也愿意完全重新设计代码,只要能很好地完成工作即可。
解决方案是使用TensorArray
类型的对象,它是专门为解决此类问题而制作的。下面一行:
image_output_sequence = tf.Variable(tf.zeros([batch_size, max_sequence_length, 1], tf.float32))
替换为:
image_output_sequence = tf.TensorArray(size=batch_size, dtype=tf.float32, element_shape=[max_sequence_length, 1], infer_shape=True)
TensorArray
实际上并不要求每个元素都有固定的形状,但对于我来说它是固定的,所以最好强制执行。
然后在 body
函数中,替换为:
ios[lc].assign(padded_cnn_features)
与:
ios = ios.write(lc, padded_cnn_output)
然后在tf.while_loop
语句之后,可以把TensorArray
堆叠起来形成一个正则的Tensor
进一步处理:
stacked_tensor = result.stack()