tf.nn.conv2d 在 tensorflow 中做了什么?
What does tf.nn.conv2d do in tensorflow?
我正在查看有关 tf.nn.conv2d
here 的 tensorflow 文档。但我无法理解它的作用或它试图实现的目标。它在文档上说,
#1 : Flattens the filter to a 2-D matrix with shape
[filter_height * filter_width * in_channels, output_channels]
.
那有什么用呢?是逐元素乘法还是简单的矩阵乘法?我也无法理解文档中提到的其他两点。我把它们写在下面:
# 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)
我了解卷积神经网络的点点滴滴。我研究了它们 here。但是在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]]]]
好的,我认为这是解释这一切的最简单方法。
您的示例是 1 张图像,大小为 2x2,具有 1 个通道。您有 1 个过滤器,大小为 1x1,还有 1 个通道(大小为高度 x 宽度 x 通道 x 过滤器数量)。
对于这个简单的例子,生成的 2x2、1 通道图像(大小 1x2x2x1,图像数量 x 高 x 宽 x x 通道)是将过滤器值乘以图像的每个像素的结果。
现在让我们尝试更多频道:
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 个通道。生成的图像将是 3x3 和 1 个通道(大小 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 个中心像素之一上进行的,因此过滤器的 none 突出。下面的 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)
2D 卷积的计算方式与计算 1D convolution 的方式类似:将内核滑动到输入上,计算逐元素乘法并将它们相加。但是你的 kernel/input 不是数组,这里是矩阵。
在最基本的示例中,没有填充且步幅为 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)
并且会相当于我们手算的那个。
只是为了补充其他答案,你应该想到
中的参数
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 个输出特征图之一,我们简单地执行过滤器立方体与图像立方体的逐点乘法,然后我们对 channels/depth 维度的结果求和(这里是3), 折叠成 2d (100,100) 特征图。对每个过滤器立方体执行此操作,您将获得 7 个 2D 特征图。
我尝试实现 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,结果非常接近(但这个实现速度较慢)。希望对你有帮助。
除了其他答案之外,conv2d 操作在 c++ (cpu) 或 cuda 中运行,用于需要以某种方式展平和重塑数据并使用 gemmBLAS 或 cuBLAS(cuda) 矩阵乘法的 gpu 机器。
当您尝试图像分类时,它会通过图片进行抽搐,thuis 函数具有执行此操作所需的所有参数。
当你基本上可以选择过滤维度的时候。大步前进。填充。在使用它之前需要了解卷积的概念
此解释补充:
我对 keras.conv2d 中的 filter 参数有些怀疑,因为当我了解到我应该设置自己的过滤器设计时。但是这个参数告诉有多少过滤器要测试,keras 本身会尝试找到最好的过滤器权重。
我正在查看有关 tf.nn.conv2d
here 的 tensorflow 文档。但我无法理解它的作用或它试图实现的目标。它在文档上说,
#1 : Flattens the filter to a 2-D matrix with shape
[filter_height * filter_width * in_channels, output_channels]
.
那有什么用呢?是逐元素乘法还是简单的矩阵乘法?我也无法理解文档中提到的其他两点。我把它们写在下面:
# 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)
我了解卷积神经网络的点点滴滴。我研究了它们 here。但是在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]]]]
好的,我认为这是解释这一切的最简单方法。
您的示例是 1 张图像,大小为 2x2,具有 1 个通道。您有 1 个过滤器,大小为 1x1,还有 1 个通道(大小为高度 x 宽度 x 通道 x 过滤器数量)。
对于这个简单的例子,生成的 2x2、1 通道图像(大小 1x2x2x1,图像数量 x 高 x 宽 x x 通道)是将过滤器值乘以图像的每个像素的结果。
现在让我们尝试更多频道:
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 个通道。生成的图像将是 3x3 和 1 个通道(大小 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 个中心像素之一上进行的,因此过滤器的 none 突出。下面的 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)
2D 卷积的计算方式与计算 1D convolution 的方式类似:将内核滑动到输入上,计算逐元素乘法并将它们相加。但是你的 kernel/input 不是数组,这里是矩阵。
在最基本的示例中,没有填充且步幅为 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)
并且会相当于我们手算的那个。
只是为了补充其他答案,你应该想到
中的参数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 个输出特征图之一,我们简单地执行过滤器立方体与图像立方体的逐点乘法,然后我们对 channels/depth 维度的结果求和(这里是3), 折叠成 2d (100,100) 特征图。对每个过滤器立方体执行此操作,您将获得 7 个 2D 特征图。
我尝试实现 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,结果非常接近(但这个实现速度较慢)。希望对你有帮助。
除了其他答案之外,conv2d 操作在 c++ (cpu) 或 cuda 中运行,用于需要以某种方式展平和重塑数据并使用 gemmBLAS 或 cuBLAS(cuda) 矩阵乘法的 gpu 机器。
当您尝试图像分类时,它会通过图片进行抽搐,thuis 函数具有执行此操作所需的所有参数。
当你基本上可以选择过滤维度的时候。大步前进。填充。在使用它之前需要了解卷积的概念
此解释补充:
我对 keras.conv2d 中的 filter 参数有些怀疑,因为当我了解到我应该设置自己的过滤器设计时。但是这个参数告诉有多少过滤器要测试,keras 本身会尝试找到最好的过滤器权重。