首页 文章

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

提问于
浏览
109

我正在查看关于 tf.nn.conv2d here的张量流的文档 . 但我无法理解它的作用或它想要实现的目标 . 它在文档上说,

#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)

我理解卷积神经网络的点点滴滴 . 我研究了他们here . 但是,tensorflow的实现并不是我的预期 . 所以它提出了这个问题 .

EDIT :所以,我实现了一个更简单的代码 . 但我可以't figure out what'继续 . 我的意思是结果是这样的 . 如果有人能告诉我什么过程产生这个输出,那将是非常有帮助的 .

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]]]]

5 回答

  • 40

    2D卷积的计算方法类似于计算1D convolution:在输入上滑动内核,计算逐元素乘法并求它们 . 但是你的内核/输入不是数组,而是矩阵 .


    在最基本的示例中,没有填充和stride = 1 . 我们假设你的 inputkernel 是:
    enter image description here

    当您使用内核时,您将收到以下输出:
    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的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)
    

    并且将等同于我们手工计算的那个 .


    对于examples with padding/strides, take a look here .

  • 8

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


    您的示例是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个中心像素之一来进行的,这样滤波器就不会突出 . 下面的 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')
    

    这给出了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)

  • 0

    只是为了添加其他答案,你应该考虑参数

    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,结果非常接近(但实施速度较慢) . 我希望这可以帮助你 .

  • 150

    除了其他答案之外,conv2d操作在c(cpu)或cuda中运行,用于需要以某种方式展平和重塑数据并使用gemmBLAS或cuBLAS(cuda)矩阵乘法的gpu机器 .

相关问题