tf.nn.conv2d在tensorflow中做什么?


135

我在tf.nn.conv2d 这里查看 tensorflow的文档。但是我不明白它的作用或试图达到的目的。它在文档上说,

#1:将滤镜展平为具有形状的二维矩阵

[filter_height * filter_width * in_channels, output_channels]

现在那是做什么的?是逐元素乘法还是仅矩阵乘法?我也无法理解文档中提到的其他两点。我在下面写了它们:

#2:从输入张量中提取图像补丁,以形成形状的虚拟张量

[batch, out_height, out_width, filter_height * filter_width * in_channels]

#3:对于每个色块,将滤波器矩阵和图像色块向量右乘。

如果有人可以举一个例子,也许有一段代码(极其有用)并解释那里发生了什么以及为什么这样的操作,那将真的很有帮助。

我尝试编码一小部分并打印出操作的形状。不过,我还是不明白。

我尝试过这样的事情:

op = tf.shape(tf.nn.conv2d(tf.random_normal([1,10,10,10]), 
              tf.random_normal([2,10,10,10]), 
              strides=[1, 2, 2, 1], padding='SAME'))

with tf.Session() as sess:
    result = sess.run(op)
    print(result)

我了解卷积神经网络的点点滴滴。我在这里学习。但是在tensorflow上的实现不是我期望的。因此它提出了一个问题。

编辑:所以,我实现了一个简单得多的代码。但是我不知道发生了什么。我的意思是结果是这样的。如果有人能告诉我是什么过程产生此输出的,那将非常有帮助。

input = tf.Variable(tf.random_normal([1,2,2,1]))
filter = tf.Variable(tf.random_normal([1,1,1,1]))

op = tf.nn.conv2d(input, filter, strides=[1, 1, 1, 1], padding='SAME')
init = tf.initialize_all_variables()
with tf.Session() as sess:
    sess.run(init)

    print("input")
    print(input.eval())
    print("filter")
    print(filter.eval())
    print("result")
    result = sess.run(op)
    print(result)

输出

input
[[[[ 1.60314465]
   [-0.55022103]]

  [[ 0.00595062]
   [-0.69889867]]]]
filter
[[[[-0.59594476]]]]
result
[[[[-0.95538563]
   [ 0.32790133]]

  [[-0.00354624]
   [ 0.41650501]]]]

实际上,实际上在中的GPU上默认启用了cudnn tf.nn.conv2d(),因此,除非use_cudnn_on_gpu=False我们明确指定,否则当我们将TF与GPU支持一起使用时,根本不使用所讨论的方法。
gkcn

Answers:


59

计算2D卷积的方法与计算1D卷积的方法类似:将内核滑过输入,计算逐元素乘法并将其求和。但是,它们不是矩阵/输入是数组,而是矩阵。


在最基本的示例中,没有填充和跨度= 1。假设您的inputkernel是: 在此处输入图片说明

使用内核时,您将收到以下输出:在此处输入图片说明,它是通过以下方式计算的:

  • 14 = 4 * 1 + 3 * 0 + 1 * 1 + 2 * 2 + 1 * 1 + 0 * 0 + 1 * 0 + 2 * 0 + 4 * 1
  • 6 = 3 * 1 + 1 * 0 + 0 * 1 + 1 * 2 + 0 * 1 + 1 * 0 + 2 * 0 + 4 * 0 + 1 * 1
  • 6 = 2 * 1 + 1 * 0 + 0 * 1 + 1 * 2 + 2 * 1 + 4 * 0 + 3 * 0 + 1 * 0 + 0 * 1
  • 12 = 1 * 1 + 0 * 0 + 1 * 1 + 2 * 2 + 4 * 1 + 1 * 0 + 1 * 0 + 0 * 0 + 2 * 1

TF的conv2d函数分批计算卷积,并使用略有不同的格式。对于输入,[batch, in_height, in_width, in_channels]对于内核[filter_height, filter_width, in_channels, out_channels]。因此,我们需要以正确的格式提供数据:

import tensorflow as tf
k = tf.constant([
    [1, 0, 1],
    [2, 1, 0],
    [0, 0, 1]
], dtype=tf.float32, name='k')
i = tf.constant([
    [4, 3, 1, 0],
    [2, 1, 0, 1],
    [1, 2, 4, 1],
    [3, 1, 0, 2]
], dtype=tf.float32, name='i')
kernel = tf.reshape(k, [3, 3, 1, 1], name='kernel')
image  = tf.reshape(i, [1, 4, 4, 1], name='image')

然后用以下公式计算卷积:

res = tf.squeeze(tf.nn.conv2d(image, kernel, [1, 1, 1, 1], "VALID"))
# VALID means no padding
with tf.Session() as sess:
   print sess.run(res)

并且将等于我们手工计算的结果。


有关填充/跨距的示例,请在此处查看


很好的例子,但是有些链接坏了。
silgon

1
@silgon可悲的是,这是因为SO决定不支持他们最初创建和发布的文档功能。
萨尔瓦多·达利

161

好的,我认为这是解释这一切的最简单方法。


您的示例是1张图片,大小为2x2,带有1个通道。您有1个尺寸为1x1的过滤器和1个通道(尺寸为高度x宽度x通道x过滤器数)。

对于这种简单情况,所得的2x2、1通道图像(尺寸1x2x2x1,图像数量x高x宽xx通道)是将滤波器值乘以图像的每个像素的结果。


现在让我们尝试更多渠道:

input = tf.Variable(tf.random_normal([1,3,3,5]))
filter = tf.Variable(tf.random_normal([1,1,5,1]))

op = tf.nn.conv2d(input, filter, strides=[1, 1, 1, 1], padding='VALID')

这里的3x3图像和1x1滤镜分别具有5个通道。生成的图像将是具有1个通道的3x3(尺寸为1x3x3x1),其中每个像素的值是滤镜通道与输入图像中相应像素的点积。


现在带有3x3滤镜

input = tf.Variable(tf.random_normal([1,3,3,5]))
filter = tf.Variable(tf.random_normal([3,3,5,1]))

op = tf.nn.conv2d(input, filter, strides=[1, 1, 1, 1], padding='VALID')

在这里,我们得到一个1x1图像,带有1个通道(大小为1x1x1x1)。该值是9个5元素点积的总和。但是您可以将其称为45元素点积。


现在有了更大的形象

input = tf.Variable(tf.random_normal([1,5,5,5]))
filter = tf.Variable(tf.random_normal([3,3,5,1]))

op = tf.nn.conv2d(input, filter, strides=[1, 1, 1, 1], padding='VALID')

输出为3x3 1通道图像(大小为1x3x3x1)。每个值都是9个5元素点积的总和。

通过将滤镜居中于输入图像的9个中心像素之一上来进行每个输出,从而使任何滤镜都不伸出。x下面的s表示每个输出像素的滤镜中心。

.....
.xxx.
.xxx.
.xxx.
.....

现在使用“ SAME”填充:

input = tf.Variable(tf.random_normal([1,5,5,5]))
filter = tf.Variable(tf.random_normal([3,3,5,1]))

op = tf.nn.conv2d(input, filter, strides=[1, 1, 1, 1], padding='SAME')

这给出了一个5x5的输出图像(大小为1x5x5x1)。这是通过将滤镜居中放置在图像上的每个位置来完成的。

滤镜超出图像边缘的任何5元素点积的值均为零。

因此,角仅是4个5元素点积的总和。


现在有多个过滤器。

input = tf.Variable(tf.random_normal([1,5,5,5]))
filter = tf.Variable(tf.random_normal([3,3,5,7]))

op = tf.nn.conv2d(input, filter, strides=[1, 1, 1, 1], padding='SAME')

这仍然会提供5x5的输出图像,但具有7个通道(大小为1x5x5x7)。每个通道由集合中的一个过滤器产生。


现在大步向前2,2:

input = tf.Variable(tf.random_normal([1,5,5,5]))
filter = tf.Variable(tf.random_normal([3,3,5,7]))

op = tf.nn.conv2d(input, filter, strides=[1, 2, 2, 1], padding='SAME')

现在结果仍然有7个通道,但只有3x3(大小为1x3x3x7)。

这是因为,滤镜不是以图像上的每个点为中心,而是以宽度2的步长(步幅)以图像上的每个其他点为中心x。下面的代表每个输出像素的滤镜中心。输入图像。

x.x.x
.....
x.x.x
.....
x.x.x

当然,输入的第一维是图像数,因此您可以将其应用于10张图像的批处理中,例如:

input = tf.Variable(tf.random_normal([10,5,5,5]))
filter = tf.Variable(tf.random_normal([3,3,5,7]))

op = tf.nn.conv2d(input, filter, strides=[1, 2, 2, 1], padding='SAME')

这将对每个图像独立执行相同的操作,从而得到10张图像的堆栈(大小为10x3x3x7)


@ZijunLost不,文档指出第一个和最后一个元素必须为Must have strides[0] = strides[3] = 1. For the most common case of the same horizontal and vertices strides, strides = [1, stride, stride, 1].
1。– JohnAllen

这是基于Toeplitz矩阵的卷积实现吗?
gkcn

关于这一点:“这仍然给出5x5的输出图像,但是具有7个通道(大小为1x5x5x7)。每个通道是由集合中的一个过滤器产生的。”,我仍然很难理解这7个通道的来源?您是什么意思“集合中的过滤器”?谢谢。
derek

@mdaoust嗨,关于您的第二个示例where the 3x3 image and the 1x1 filter each have 5 channels,我发现结果与手动计算的点积不同。
Tgn Yang 2015年

1
@derek我有同样的问题,“ output_channel”是否与“ filters”相同???如果是这样,为什么在tensorflow文档中将它们命名为“ output_channel”?

11

只需添加其他答案,您就应该考虑其中的参数

filter = tf.Variable(tf.random_normal([3,3,5,7]))

为“ 5”,对应于每个滤波器中的通道数。每个滤镜是一个3d立方体,深度为5。您的滤镜深度必须与输入图像的深度相对应。最后一个参数7应该被认为是批次中的过滤器数量。只需忘记这是4D,而是想象您有一组或一组7个滤镜。您要做的是创建7个尺寸为(3,3,5)的过滤器多维数据集。

由于卷积变成了逐点乘法,因此在傅立叶域中更容易可视化。对于尺寸为(100,100,3)的输入图像,您可以将滤镜尺寸重写为

filter = tf.Variable(tf.random_normal([100,100,3,7]))

为了获得7个输出特征图之一,我们只需对滤镜立方体与图像立方体进行逐点乘法,然后将通道/深度维度(这里为3)的结果求和,折叠为2d (100,100)功能图。对每个滤镜立方体执行此操作,您将获得7个2D特征图。


8

我试图实现conv2d(供我学习)。好吧,我写道:

def conv(ix, w):
   # filter shape: [filter_height, filter_width, in_channels, out_channels]
   # flatten filters
   filter_height = int(w.shape[0])
   filter_width = int(w.shape[1])
   in_channels = int(w.shape[2])
   out_channels = int(w.shape[3])
   ix_height = int(ix.shape[1])
   ix_width = int(ix.shape[2])
   ix_channels = int(ix.shape[3])
   filter_shape = [filter_height, filter_width, in_channels, out_channels]
   flat_w = tf.reshape(w, [filter_height * filter_width * in_channels, out_channels])
   patches = tf.extract_image_patches(
       ix,
       ksizes=[1, filter_height, filter_width, 1],
       strides=[1, 1, 1, 1],
       rates=[1, 1, 1, 1],
       padding='SAME'
   )
   patches_reshaped = tf.reshape(patches, [-1, ix_height, ix_width, filter_height * filter_width * ix_channels])
   feature_maps = []
   for i in range(out_channels):
       feature_map = tf.reduce_sum(tf.multiply(flat_w[:, i], patches_reshaped), axis=3, keep_dims=True)
       feature_maps.append(feature_map)
   features = tf.concat(feature_maps, axis=3)
   return features

希望我做得正确。经MNIST检查,结果非常接近(但此实现速度较慢)。我希望这可以帮助你。


0

除了其他答案,conv2d操作还以gpu机器的c ++(cpu)或cuda操作,要求以某种方式展平和重塑数据并使用gemmBLAS或cuBLAS(cuda)矩阵乘法。


因此,在内存中,卷积实际上是作为矩阵乘法执行的,这解释了为什么较大的图像不一定要运行会花费较大的计算时间,而是更有可能遇到OOM(内存不足)错误的原因。您能向我解释为什么3D卷积比2D卷积更不高效/更高效?例如,与[B * C,H,W,D]上的2D转换相比,在[B,H,W,D,C]上进行3D转换。当然,它们的计算成本相同吗?
SomePhysicsStudent
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.