问题描述:tensorflow中tf.nn.conv2d的作用/用途是什么?
在看关于tf.nn.conv2d
(这里)的tensorflow文档时,不明白它的作用或试图达到的目的。文档上说,
#1 : Flattens the filter to a 2-D matrix with shape
[filter_height * filter_width * in_channels, output_channels]
.
这是什么意思呢?是element-wise乘法还是仅矩阵乘法?我也无法理解文档中提到的其他两点:
# 2: Extracts image patches from the the input tensor to form a virtual tensor of shape
[batch, out_height, out_width, filter_height * filter_width * in_channels]
.# 3: For each patch, right-multiplies the filter matrix and the image patch vector.
如果有人可以举一个例子,或给一段代码(极其有用)并解释那里发生了什么以及为什么这样的操作,那将真的很有帮助。
我尝试编码一小部分并打印出操作的形状。不过,我还是不明白。
我尝试过这样的事情:
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)
另外此,我也实现了一个简单得多的代码。结果如下,但是我不知道发生了什么。
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]]]]
最佳回答(简单说明)
二维卷积的计算方法与计算1D 卷积的方法类似:将内核滑过输入,计算element-wise乘法并将其相加。但是它们核核输入不是是数组,而是矩阵。
在最基本的示例中,没有padding(填充)且strip= 1。假设您的input
和kernel
是:
使用内核时,您将得到以下输出:,它是通过以下方式计算的:
- 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)
这将等于我们手工计算的结果。
有padding和stride的例子,见这里:examples with padding/strides。
次佳回答(由简入繁)
这里提供解释这个问题的简单方法。
示例是1张图片,大小为2×2,带有1个通道。有1个尺寸为1×1的过滤器和1个通道(尺寸为高度x宽度x通道x过滤器数)。
对于这种简单情况,所得到的2×2、1通道图像(尺寸1x2x2x1,图像数量x高x宽x x通道)是将滤波器值乘以图像的每个像素的结果。
现在让我们尝试更多channel(通道):
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')
这里的3×3图像和1×1滤镜分别具有5个通道。生成的图像将是具有1个通道的3×3(尺寸为1x3x3x1),其中每个像素的值是filter通道与输入图像中相应像素的点积。
现在有3×3 filter
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')
在这里,我们得到一个1×1图像,带有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')
输出为3×3 1通道图像(大小为1x3x3x1)。每个值都是9个5元素点积的总和。
通过将filter居中于输入图像的9个中心像素之一上来进行每个输出,从而使任何filter都不伸出。下面的x
表示每个输出像素的滤镜中心。
.....
.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')
这给出了5×5的输出图像(大小为1x5x5x1)。这是通过将filter居中放置在图像上的每个位置来完成的。
filter超出图像边的任何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')
这仍然会提供5×5的输出图像,但具有7个通道(大小为1x5x5x7)。每个通道由集合中的一个Filter产生。
现在使用stride=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个通道,但只有3×3(大小为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)
Conv2d的代码示例
我试图实现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检查,结果非常接近(但此实现速度较慢)。我希望这可以帮助你。
图示:
参考资料