在张量流中 tf.n.conv2d 做什么?

我在看关于 tf.nn.conv2d 给你的张量流的文件。但我不明白它在做什么或者它想达到什么目的。文件上说,

# 1: 将滤波器平坦化为具有形状的二维矩阵

[filter_height * filter_width * in_channels, output_channels].

那是干什么的?这是元素的乘法还是纯粹的矩阵乘法?我也不能理解文件中提到的另外两点。我把它们写在下面:

# 2: 从输入张量中提取图像斑块,形成一个虚拟的形状张量

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

对于每个补丁,右乘滤波器矩阵和图像补丁向量。

如果有人能给出一个例子,一段代码(非常有帮助) ,并解释这里发生了什么,以及为什么操作是这样的,那将是非常有帮助的。

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

我试过这样的方法:

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]]]]
89603 次浏览

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


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

对于这个简单的例子,得到的2x2,1通道图像(大小1x2x2x1,图像数 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的图像,有一个通道(大小为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')

输出是一个3x31通道的图像(大小为1x3x3x1)。 每个值都是9,5元素点乘的和。

每个输出都是通过将滤波器集中在输入图像的9个中心像素中的一个上来完成的,因此没有一个滤波器突出来。下面的 x表示每个输出像素的过滤中心。

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

现在加上“相同”的填充:

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)

计算二维卷积的方法与计算 一维卷积的方法类似: 将内核滑过输入,计算元素乘法并求和。但这里的内核/输入不是数组,而是矩阵。


在最基本的例子中,没有填充和 stride = 1。让我们假设您的 inputkernel是: enter image description here

当您使用您的内核时,您将收到以下输出: < img src = “ https://i.stack.imgur.com/TPhBi.png”alt = “ enter image description here”>,它是按以下方式计算的:

  • 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 的 翻译函数分批计算卷积,并使用略有不同的格式。对于输入,它是 [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’对应于每个过滤器中的通道数。每个过滤器是一个三维立方体,深度为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 特征映射。

我试图实现 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 中运行,用于 gpu 机器,这些机器需要以特定的方式对数据进行平面化和重塑,并使用 gemmBLAS 或 cuBLAS (cuda)矩阵乘法。

当你尝试例如图像分类的时候,它通过图像执行震颤,这个函数有所有需要的参数。

当你基本上可以选择过滤器尺寸。跨越。填充物。以前用它需要了解卷积的概念

这种解释补充说明:

Kera Conv2d 拥有自己的过滤器

我对 keras.conv2d 中的 过滤器参数有些怀疑,因为当我了解到我应该设置自己的过滤器设计时。但是这个参数告诉我们要测试多少个过滤器,而 Keras 本身将尝试找到最佳的过滤器权重。