Tensorflow:小批量中每个样本使用不同滤波器的卷积

10
我想在tensorflow中进行一次2D卷积操作,该操作的滤波器依赖于迷你批次中的样本。如果迷你批次中的样本数量未知,有什么好的方法可以做到这点吗?
具体而言,我有输入数据inp,形状为MB x H x W x Channels,还有形如MB x fh x fw x Channels x OutChannels的过滤器F
假设 inp = tf.placeholder('float', [None, H, W, channels_img], name='img_input')
我想执行tf.nn.conv2d(inp, F, strides=[1, 1, 1, 1]),但是这不被允许,因为F不能有一个迷你批次维度。有什么解决这个问题的办法吗?

1
也许您可以使用 tf.expand_dims 添加一个“虚拟小批量维度”,然后使用 tf.nn.conv3d,其中过滤器深度与批量大小匹配。不确定在可变批量大小的情况下效果如何。 - Robert Lacok
@RobertLacok听起来是个好主意。唯一的问题是,如果我这样做,我不知道新空间维度(小批量维度)的大小。但我会尝试...也许它依然有效... - patapouf_ai
我想你需要知道它的上限,并使用该维度初始化权重(过滤器)。然后在运行时,您可以执行类似于 batch_size = tf.shape(input)[0] 的操作来推断维度并仅使用过滤器的一个切片。这只是一个建议,我从未尝试过这样的事情,因此可能会引起问题。 - Robert Lacok
@RobertLacok,我认为我成功地使用了tf.nn.conv3d。如果你想写一个答案,我会接受它。 - patapouf_ai
1
好的,谢谢您的回复。如果您在途中发现更多信息,请随时进行编辑。 - Robert Lacok
显示剩余2条评论
4个回答

6
我认为所提出的技巧实际上并不正确。使用tf.conv3d()层时,输入会在深度(=实际批处理)维度上进行卷积,然后沿着生成的特征图求和。对于padding ='SAME',输出的数量恰好等于批处理大小,从而使人产生了误解!编辑:我认为通过“黑客”深度卷积进行不同小批量元素的卷积的一种可能的方法涉及到假设已知批处理大小MB:
inp = tf.placeholder(tf.float32, [MB, H, W, channels_img])

# F has shape (MB, fh, fw, channels, out_channels)
# REM: with the notation in the question, we need: channels_img==channels

F = tf.transpose(F, [1, 2, 0, 3, 4])
F = tf.reshape(F, [fh, fw, channels*MB, out_channels)

inp_r = tf.transpose(inp, [1, 2, 0, 3]) # shape (H, W, MB, channels_img)
inp_r = tf.reshape(inp, [1, H, W, MB*channels_img])

out = tf.nn.depthwise_conv2d(
          inp_r,
          filter=F,
          strides=[1, 1, 1, 1],
          padding='VALID') # here no requirement about padding being 'VALID', use whatever you want. 
# Now out shape is (1, H, W, MB*channels*out_channels)

out = tf.reshape(out, [H, W, MB, channels, out_channels) # careful about the order of depthwise conv out_channels!
out = tf.transpose(out, [2, 0, 1, 3, 4])
out = tf.reduce_sum(out, axis=3)

# out shape is now (MB, H, W, out_channels)

如果MB未知,应该可以使用tf.shape()动态确定它(我想)


我修改了答案,因为我混淆了一些维度/转置... - drasros
以下是关于编程的相关内容的翻译:这里是我进行测试的gist(使用 1d convs,但这并不会有任何影响)。 - drasros
2
请注意,这个(本来很好的)答案在当前形式下是错误的。如果 padding = "VALID",那么 out = tf.reshape(out, [H, W, MB, channels, out_channels) 这一行应该改为 out = tf.reshape(out, [H-fh+1, W-fw+1, MB, channels, out_channels)。如果您使用 padding = "SAME",则您的格式是正确的。请参见我的答案,以获得正确的格式,同时处理两种情况。 - Žiga Sajovic
有一个小错误:应该是 inp_r = tf.reshape(inp_r, [1, H, W, MB*channels_img]) 而不是 inp_r = tf.reshape(inp, [1, H, W, MB*channels_img]) - bastak

5
您可以按照以下方式使用tf.map_fn
inp = tf.placeholder(tf.float32, [None, h, w, c_in]) 
def single_conv(tupl):
    x, kernel = tupl
    return tf.nn.conv2d(x, kernel, strides=(1, 1, 1, 1), padding='VALID')
# Assume kernels shape is [tf.shape(inp)[0], fh, fw, c_in, c_out]
batch_wise_conv = tf.squeeze(tf.map_fn(
    single_conv, (tf.expand_dims(inp, 1), kernels), dtype=tf.float32),
    axis=1
)

在使用map_fn时,指定dtype非常重要。基本上,这个解决方案定义了batch_dim_size 2D卷积操作。


4

被接受的答案在处理维度时有些问题,因为当padding = "VALID"时,它们会发生改变(他处理时把它们当作padding="SAME"一样)。因此,在一般情况下,代码会崩溃,因为存在不匹配。我附上他已经更正的代码,两种情况都正确地处理。

inp = tf.placeholder(tf.float32, [MB, H, W, channels_img])

# F has shape (MB, fh, fw, channels, out_channels)
# REM: with the notation in the question, we need: channels_img==channels

F = tf.transpose(F, [1, 2, 0, 3, 4])
F = tf.reshape(F, [fh, fw, channels*MB, out_channels)

inp_r = tf.transpose(inp, [1, 2, 0, 3]) # shape (H, W, MB, channels_img)
inp_r = tf.reshape(inp_r, [1, H, W, MB*channels_img])

padding = "VALID" #or "SAME"
out = tf.nn.depthwise_conv2d(
          inp_r,
          filter=F,
          strides=[1, 1, 1, 1],
          padding=padding) # here no requirement about padding being 'VALID', use whatever you want. 
# Now out shape is (1, H-fh+1, W-fw+1, MB*channels*out_channels), because we used "VALID"

if padding == "SAME":
    out = tf.reshape(out, [H, W, MB, channels, out_channels)
if padding == "VALID":
    out = tf.reshape(out, [H-fh+1, W-fw+1, MB, channels, out_channels)
out = tf.transpose(out, [2, 0, 1, 3, 4])
out = tf.reduce_sum(out, axis=3)

# out shape is now (MB, H-fh+1, W-fw+1, out_channels)

3
增加一个额外的维度是绕过此问题的方法,可以使用 <canvas> 元素。
tf.expand_dims(inp, 0)

为了创建一个“虚假”的批量大小,然后使用。
tf.nn.conv3d()

针对过滤器深度与批次大小相匹配的操作。这将导致每个过滤器只与每个批次中的一个样本进行卷积。

不幸的是,这种方法无法解决可变批量大小问题,只能解决卷积问题。


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