• Tensorflow--二维离散卷积


    Tensorflow–二维离散卷积

    一.二维离散卷积的计算原理

    二维离散卷积的计算原理同一维离散卷积的计算原理类似,也有三种卷积类型:full卷积,same卷积核valid卷积。通过3行3列的二维张量x和2行2列的二维张量K
    未命名文件.png

    1.full卷积

    full卷积的计算过程如下:K沿着x按照先行后列的顺序移动,每移动到一个固定位置,对应位置的值相乘,然后求和

    注意:同一维卷积类似,对二维卷积的定义一般分为两步,首先将卷积核翻转180,然后计算对应位置相乘的和,如常用的Numpy,MATLAB中实现的卷积函数都是先将输入的卷积核翻转180,Tensorflow中实现二维卷积的函数为:

    tf.nn.conv2d(input,filter,strides,padding,use_cudnn_on_gpu=True,data_format="NHWC",dilations=[1,1,1,1],name=None)
    

    该函数内部没有对卷积核翻转

    2.same卷积

    x和K进行same卷积,首先为K指定一个锚点,然后将锚点先行后列地移动到输入张量x的每一个位置处,对应位置相乘然后求和。卷积核K的高等于FH,宽等于FW,其锚点的位置一般用以下规则定义

    .如果FH为奇数,FW为奇数,锚点的位置是((FH-1)/2,(FW-1)/2)

    .如果FH为奇数,FW为偶数,锚点的位置是((FH-1)/2,(FW-2)/2)

    .如果FH为偶数,FW为奇数,锚点的位置是((FH-2)/2,(FW-1)/2)

    .如果FH为偶数,FW为偶数,锚点的位置是((FH-2)/2,(FW-2)/2)

    这里的位置索引是从0开始的

    以上面的示例为例,K的高为2,宽为2,所以锚点的位置在K的(0,0)处

    import tensorflow as tf
    
    X=tf.constant(
        [
            [
                [[2],[3],[8]],
                [[6],[1],[5]],
                [[7],[2],[-1]]
            ]
        ]
        ,tf.float32
    )
    
    K=tf.constant(
        [
            [
                [[4]],[[1]]],
            [
                [[2]],[[3]]
            ]
        ]
        ,tf.float32
    )
    
    # same卷积
    conv=tf.nn.conv2d(X,K,(1,1,1,1),'SAME')
    
    session=tf.Session()
    
    print(session.run(conv))
    
    [[[[26.]
       [37.]
       [42.]]
    
      [[45.]
       [10.]
       [18.]]
    
      [[30.]
       [ 7.]
       [-4.]]]]
    

    3.valid卷积

    如果卷积核K靠近x的边界,那么K就会有部分延伸到x外,导致访问到未定义的值;如果忽略边界,只考虑x能完全覆盖K的值情况(即K在x内部移动),则该过程称为valid卷积

    import tensorflow as tf
    
    X=tf.constant(
        [
            [
                [[2],[3],[8]],
                [[6],[1],[5]],
                [[7],[2],[-1]]
            ]
        ]
        ,tf.float32
    )
    
    K=tf.constant(
        [
            [
                [[4]],[[1]]],
            [
                [[2]],[[3]]
            ]
        ]
        ,tf.float32
    )
    
    # same卷积
    conv=tf.nn.conv2d(X,K,(1,1,1,1),'VALID')
    
    session=tf.Session()
    
    print(session.run(conv))
    
    [[[[26.]
       [37.]]
    
      [[45.]
       [10.]]]]
    

    4.full,same,valid卷积的关系

    假设有H行W列的二维张量x与FH行FW列的二维张量K卷积,两者full卷积的结果记为,same卷积的结果记为,valid的结果记为

    full卷积与valid卷积的关系
    Cvalid=C[FH-1:H-1,FW-1:W-1]

    full卷积与same卷积的关系
    假设same卷积的卷积核的锚点的位置在第Fr行,第Fc列处
    C=C[FH-Fr-1:H+FH-Fr-2,FW-Fc-1:W+FW-Fc-2]

    same卷积与valid卷积的关系
    C=Csame[Fr:H-FH+Fr,Fc:W-FW+FC]
    未命名文件 (1).png

    5.卷积结果的输出尺寸

    我们讨论的卷积操作,在卷积过程中卷积核的移动步长均是1,所以H行W列的x与FH行FW列的卷积核K的same卷积结果的尺寸为H行W列,valid卷积结果的尺寸为H-FH+1行W-FW+1列

    same卷积结果的尺寸

    valid卷积结果的尺寸

    二.离散卷积的性质

    1.可分离的卷积核

    如果一个卷积核由至少两个尺寸比它小的卷积核full卷积二成,既满足

    Kennel=kernel1☆kernel2☆…kerneln

    其中kerneli的尺寸均比Kernel小,1≤i≤n,则陈卷积核Kernel是可分离的

    2.full和same卷积的性质

    以下代码实现了I与卷积核Kernel的same卷积,因为Kernel是可分离的,利用same卷积的性质,可以计算两者的same卷积

    import tensorflow as tf
    
    # 输入张量5x5
    I=tf.constant(
        [
            [
                [[2],[9],[11],[4],[8]],
                [[6],[12],[20],[16],[5]],
                [[1],[32],[13],[14],[10]],
                [[11],[20],[27],[40],[17]],
                [[9],[8],[11],[4],[1]]
            ]
        ]
        ,tf.float32
    )
    
    # 卷积核3x3
    Kernel=tf.constant(
        [
            [
                [[4]],[[8]],[[12]]
            ],
            [
                [[5]],[[10]],[[15]]  
            ],
            [
                [[6]],[[12]],[[18]]
            ]
        ]
        ,tf.float32
    )
    
    session=tf.Session()
    
    # 输入张量与卷积核直接卷积
    result=tf.nn.conv2d(I,Kernel,[1,1,1,1],'SAME')
    print("直接卷积结果是:")
    print(session.run(result))
    
    # 卷积核分离为3x1的垂直卷积核和1x3的水平卷积核
    kernel1=tf.constant(
        [
            [[[4]]],
            [[[5]]],
            [[[6]]]
        ]
        ,tf.float32
    )
    
    kernel2=tf.constant(
        [
            [[[3]],[[2]],[[1]]]
        ]
        ,tf.float32
    )
    
    # 将kernel2翻转180
    rotate180_kernel2=tf.reverse(kernel2,axis=[1])
    
    # 输入张量与分离的卷积核的卷积
    result1=tf.nn.conv2d(I,kernel1,[1,1,1,1],'SAME')
    result2=tf.nn.conv2d(result1,rotate180_kernel2,[1,1,1,1],'SAME')
    print("利用卷积核的分离性的卷积结果:")
    print(session.run(result2))
    
    直接卷积结果是:
    [[[[ 443.]
       [ 805.]
       [ 815.]
       [ 617.]
       [ 256.]]
    
      [[ 952.]
       [1286.]
       [1272.]
       [ 933.]
       [ 414.]]
    
      [[1174.]
       [1672.]
       [2064.]
       [1571.]
       [ 718.]]
    
      [[1054.]
       [1424.]
       [1622.]
       [1206.]
       [ 542.]]
    
      [[ 538.]
       [ 818.]
       [ 986.]
       [ 742.]
       [ 326.]]]]
    利用卷积核的分离性的卷积结果:
    [[[[ 443.]
       [ 805.]
       [ 815.]
       [ 617.]
       [ 256.]]
    
      [[ 952.]
       [1286.]
       [1272.]
       [ 933.]
       [ 414.]]
    
      [[1174.]
       [1672.]
       [2064.]
       [1571.]
       [ 718.]]
    
      [[1054.]
       [1424.]
       [1622.]
       [1206.]
       [ 542.]]
    
      [[ 538.]
       [ 818.]
       [ 986.]
       [ 742.]
       [ 326.]]]]
    

    3.快速计算卷积

    假设输入张量I的尺寸是H_W,卷积核Kernel的尺寸为FH_W_FH*FW

    如果卷积核Kernel是可分离的,分离为FH_1的垂直卷积核kernel1和1_W_(FH+FW)

    以上面示例为例,两者same卷积的计算次数为5_5_3=225,利用卷积核的分离性及卷积的结合率,same卷积的计算次数为(5_5)*(3+3)=150。显然,利用卷积核的分离性,计算次数比直接卷积减少了很多,张量或者卷积核的尺寸越大,忧伤越明显

    三.二维卷积定理

    二维卷积定理是一维卷积定理的推广,它揭示了二维傅里叶变换和二维卷积的某种关系

    1.二维离散傅里叶变换

    假设有M行N列的复数数列f,其中f(x,y)代表f第x行第y列对应的值,那么对任意的x∈[0,M-1],y∈[0,N-1],是否存在M行N列的复数数列F,使得以下等式成立:

    Tensorflow通过函数fft2d和ifft2d实现二维离散的傅里叶变换及逆变换

    import tensorflow as tf
    
    f=tf.constant(
        [
            [10,2,8],
            [5,12,3]
        ]
        ,tf.complex64
    )
    
    session=tf.Session()
    
    F=tf.fft2d(f)
    
    print("f的二维离散傅里叶变换:")
    print(session.run(F))
    
    # 计算F的傅里叶逆变换(显然与输入的f是相等的)
    F_ifft2d=tf.ifft2d(F)
    
    print("F的傅里叶逆变换:")
    print(session.run(F_ifft2d))
    
    f的二维离散傅里叶变换:
    [[4.0000000e+01-2.3841858e-07j 2.4999998e+00-2.5980763e+00j
      2.5000002e+00+2.5980752e+00j]
     [4.7683716e-07-2.3841858e-07j 7.5000000e+00+1.2990381e+01j
      7.5000005e+00-1.2990381e+01j]]
    F的傅里叶逆变换:
    [[10.       -4.7683716e-07j  1.9999998+1.5894572e-07j
       7.9999995+3.1789145e-07j]
     [ 5.       -1.5894572e-07j 12.       +6.3578290e-07j
       3.       -1.5894572e-07j]]
    

    2.二维与一维傅里叶变换的关系

    二维离散傅里叶变换也可以分解为先计算每一列的傅里叶变换,再计算每一行的傅里叶变换

    Tensorflow并没有提供分别计算二维数列的行或列的傅里叶变换,Numpy中函数fft可以实现该功能,具体代码如下:

    import numpy as np
    
    f=np.array(
        [
            [10,2,8],
            [5,12,3]
        ]
        ,np.complex64
    )
    
    # 第1步:对每一列进行傅里叶变换
    f_0_fft=np.fft.fft(f,axis=0)
    print(f_0_fft)
    
    # 第2步:将上面结果,分别对每一行进行傅里叶变换
    f_0_1_fft=np.fft.fft(f_0_fft,axis=1)
    print(f_0_1_fft)
    
    [[ 15.+0.j  14.+0.j  11.+0.j]
     [  5.+0.j -10.+0.j   5.+0.j]]
    [[40.  +0.j          2.5 -2.59807621j  2.5 +2.59807621j]
     [ 0.  +0.j          7.5+12.99038106j  7.5-12.99038106j]]
    

    以下代码是先计算每一行的一维傅里叶变换,再计算每一列的一维离散傅里叶变换,代码如下:

    # 第1步:对每一行进行傅里叶变换
    f_1_fft=np.fft.fft(f,axis=1)
    print(f_1_fft)
    
    # 第2步:将上面得到的结果,分别对每一列进行傅里叶变换
    f_1_0_fft=np.fft.fft(f_1_fft,axis=0)
    print(f_1_0_fft)
    

    3.卷积定理

    假设有高为H,宽为W的二维输入张量I,高为FH,宽为FW的卷积核k,那么I与k的full卷积结果的尺寸是高为H+FH-1,宽为W+FW-1

    在I的右侧和下层补零,且将I的尺寸扩充到与full卷积的尺寸相同,即

    其中0≤h≤H+FH-1,0≤w<W+FW-1

    将卷积核k逆时针翻转180得到k_rotate180,然后对其右侧和下侧进行补零,且将k_rotate180的尺寸可从到和full卷积相同的尺寸

    其中0≤h≤H+FH-1,0≤w<W+FW-1

    假设fft2_Ip和fft2_krp分别是I_padded和k_rotate180_padded的傅里叶变换,那么I☆k的傅里叶变换等于fft2_Ip*fft2_krp,即

    其中*代表对应位置的元素相乘,即对应位置的两个复数相乘,该性质称为卷积定理

    4.利用卷积定理快速计算卷积

    我们以上例中的x和K为例,利用卷积定理计算两者的卷积,具体实现代码如下:

    import tensorflow as tf
    
    # 输入张量I
    I=tf.constant(
        [
            [2,3,8],
            [6,1,5],
            [7,2,-1]
        ]
        ,tf.complex64
    )
    
    # 卷积核
    k=tf.constant(
        [
            [4,1],
            [2,3]
        ]
        ,tf.complex64
    )
    
    # 对输入张量的下侧和右侧补0
    I_padded=tf.pad(I,[[0,1],[0,1]])
    
    # 翻转卷积核180
    k_rotate180=tf.reverse(k,[0,1])
    
    # 对翻转后的卷积核下侧和右侧补0
    k_rotate180_padded=tf.pad(k_rotate180,[[0,2],[0,2]])
    
    # 二维离散傅里叶变换
    I_padded_fft2=tf.fft2d(I_padded)
    k_rotate180_padded_fft2=tf.fft2d(k_rotate180_padded)
    
    # 两个二维傅里叶变换对应位置相乘
    xk_fft2=tf.multiply(I_padded_fft2,k_rotate180_padded_fft2)
    
    # 对以上相乘的结果进行傅里叶逆变换
    xk=tf.ifft2d(xk_fft2)
    
    session=tf.Session()
    
    # 利用卷积定理计算的full卷积的结果
    print(session.run(xk))
    
    [[ 6.+0.j 13.+0.j 30.+0.j 16.+0.j]
     [20.+0.j 26.+0.j 37.+0.j 42.+0.j]
     [27.+0.j 45.+0.j 10.+0.j 18.+0.j]
     [ 7.+0.j 30.+0.j  7.+0.j -4.+0.j]]
    

    四.多深度的离散卷积

    1.基本的多深度卷积

    我们以3行3列2深度的三维张量x和2行2列2深度的三维卷积核k的valid卷积为例

    import tensorflow as tf
    
    # 3行3列2深度
    x=tf.constant(
        [
            [
                [[2,5],[3,3],[8,2]],
                [[6,1],[1,2],[5,4]],
                [[7,9],[2,3],[-1,3]]
            ]
        ]
        ,tf.float32
    )
    
    # 2行2列2深度的卷积核
    k=tf.constant(
        [
            [[[3],[1]],[[-2],[2]]],
            [[[-1],[-3]],[[4],[5]]]
        ]
        ,tf.float32
    )
    
    # 每一深度分别计算乘积,然后求和
    x_conv2d_k=tf.nn.conv2d(x,k,[1,1,1,1],'VALID')
    
    session=tf.Session()
    
    print(session.run(x_conv2d_k))
    
    [[[[16.]
       [33.]]
    
      [[10.]
       [ 3.]]]]
    

    即1个3行3列2深度的三维张量与1个2行2列2深度的卷积核的valid卷积结果是1个2行2列1深度的三维张量

    2.1个张量与多个卷积核的卷积

    示例理解1个3行3列2深度的张量与3个2行2列2深度的卷积核卷积

    import tensorflow as tf
    
    # 1个3行3列2深度
    x=tf.constant(
        [
            [
                [[2,5],[3,3],[8,2]],
                [[6,1],[1,2],[5,4]],
                [[7,9],[2,3],[-1,3]]
            ]
        ]
        ,tf.float32
    )
    
    # 3个2行2列2深度的卷积核
    kernels=tf.constant(
        [
            [[[3,1,-3],[1,-1,7]],[[-2,2,-5],[2,7,3]]],
            [[[-1,3,1],[-3,-8,6]],[[4,6,8],[5,9,-5]]]
        ]
        ,tf.float32
    )
    
    # valid卷积
    validResult=tf.nn.conv2d(x,kernels,[1,1,1,1],'VALID')
    
    session=tf.Session()
    
    print(session.run(validResult))
    
    [[[[16. 58. 33.]
       [33. 83. 11.]]
    
      [[10.  9. 52.]
       [ 3. 40. -5.]]]]
    

    即1个3行3列2深度的输入张量,与3个2行2列2深度的卷积核的valid卷积结果是1个2行2列3深度的三维张量

    3.多个张量分别与多个卷积核的卷积

    以2个3行3列2深度的三维张量,分别与3个2行2列2深度的卷积核进行基本的多深度卷积

    import tensorflow as tf
    
    # 2个3行3列2深度
    x=tf.constant(
        [
            [
                [[2,5],[3,3],[8,2]],
                [[6,1],[1,2],[5,4]],
                [[7,9],[2,3],[-1,3]]
            ],
            [
                [[1,3],[2,1],[3,2]],
                [[1,1],[2,2],[1,4]],
                [[3,4],[4,2],[-1,1]]
            ]
        ]
        ,tf.float32
    )
    
    # 3个2行2列2深度的卷积核
    kernels=tf.constant(
        [
            [[[3,1,-3],[1,-1,7]],[[-2,2,-5],[2,7,3]]],
            [[[-1,3,1],[-3,-8,6]],[[4,6,8],[5,9,-5]]]
        ]
        ,tf.float32
    )
    
    # valid卷积
    validResult=tf.nn.conv2d(x,kernels,[1,1,1,1],'VALID')
    
    session=tf.Session()
    
    print(session.run(validResult))
    
    [[[[16. 58. 33.]
       [33. 83. 11.]]
    
      [[10.  9. 52.]
       [ 3. 40. -5.]]]
    
    
     [[[18. 34. 24.]
       [21. 53. -6.]]
    
      [[15. 37. 49.]
       [ 5. 29. 18.]]]]
    

    即2个3行3列2深度的输入张量分别与3个2行2列2深度的卷积核的valid卷积结果是2个2行2列3深度的三维张量(即四维张量)

    总结:利用函数tf.nn.conv2d可以计算M个深度为D三维张量分别与N个深度为D的卷积核的卷积,其返回结果为M个深度为N的三维张量(即四维张量)

    函数tf.nn.conv2d实现的是分别在深度上卷积,然后沿深度上求和的卷积计算方式。接下来介绍另一个函数depthwise_conv2d,该函数实现的只是在深度上卷积

    4.在每一深度上分别卷积

    函数depthwise_conv2d与函数conv2d的不同之处在于conv2d在每一深度上卷积,然后求和,depthwise_conv2d没有求和这一步,具体代码如下

    x_depthwise_conv2d_k=tf.nn.depthwise_conv2d(x,k,[1,1,1,1],‘VALID’)

    import tensorflow as tf
    
    # 3行3列2深度
    x=tf.constant(
        [
            [
                [[2,5],[3,3],[8,2]],
                [[6,1],[1,2],[5,4]],
                [[7,9],[2,3],[-1,3]]
            ]
        ]
        ,tf.float32
    )
    
    # 2行2列2深度的卷积核
    k=tf.constant(
        [
            [[[3],[1]],[[-2],[2]]],
            [[[-1],[-3]],[[4],[5]]]
        ]
        ,tf.float32
    )
    
    # 每一深度分别计算乘积,然后求和
    x_depthwise_conv2d_k=tf.nn.depthwise_conv2d(x,k,[1,1,1,1],'VALID')
    
    session=tf.Session()
    
    print(session.run(x_depthwise_conv2d_k))
    
    [[[[ -2.  18.]
       [ 12.  21.]]
    
      [[ 17.  -7.]
       [-13.  16.]]]]
    

    5.单个张量与多个卷积核在深度上分别卷积

    以1个3行3列2深度的三维张量与3个2行2列2深度的三维卷积核卷积,因为输入张量与每个卷积核的卷积结果的深度为2,一共与3个卷积核卷积,即有3个卷积结果,将它们在深度方向上连接,所以最终结果的深度为2*3=6

    import tensorflow as tf
    
    # 1个3行3列2深度
    x=tf.constant(
        [
            [
                [[2,5],[3,3],[8,2]],
                [[6,1],[1,2],[5,4]],
                [[7,9],[2,3],[-1,3]]
            ]
        ]
        ,tf.float32
    )
    
    # 3个2行2列2深度的卷积核
    kernels=tf.constant(
        [
            [[[3,1,-3],[1,-1,7]],[[-2,2,-5],[2,7,3]]],
            [[[-1,3,1],[-3,-8,6]],[[4,6,8],[5,9,-5]]]
        ]
        ,tf.float32
    )
    
    # valid卷积
    x_depthwise_conv2d_k=tf.nn.depthwise_conv2d(x,kernels,[1,1,1,1],'VALID')
    
    session=tf.Session()
    
    print(session.run(x_depthwise_conv2d_k))
    
    [[[[ -2.  32.  -7.  18.  26.  40.]
       [ 12.  52.  -8.  21.  31.  19.]]
    
      [[ 17.  41.   0.  -7. -32.  52.]
       [-13.  11. -34.  16.  29.  29.]]]]
    

    总结:1个深度为D的三维张量与N个深度为D的卷积核的depthwise_conv2d卷积,其结果为1个深度为NxD的三维张量

    6.分离卷积

    我们介绍Tensorflow实现的另一个关于卷积的函数:

    separable_conv2d(input,depthwise_filter,pointwise_filter,strides,padding,rate=None,name=None,data_format=None)
    

    函数separable_conv2d实现的功能是函数depthwise_conv2d和conv2d的组合,代码如下:

    import tensorflow as tf
    
    # 1个3行3列2深度
    x=tf.constant(
        [
            [
                [[2,5],[3,3],[8,2]],
                [[6,1],[1,2],[5,4]],
                [[7,9],[2,3],[-1,3]]
            ]
        ]
        ,tf.float32
    )
    
    # 1个2行2列2深度的卷积核depthwiseFilter
    depthwise_filter=tf.constant(
        [
            [[[3],[1]],[[-2],[2]]],
            [[[-1],[-3]],[[4],[5]]]
        ]
        ,tf.float32
    )
    
    # 1行1列2深度的卷积核pointwiseFilter
    pointwise_filter=tf.constant(
        [
            [[[-1],[1]]]
        ]
        ,tf.float32
    )
    
    # 分离卷积
    result=tf.nn.separable_conv2d(x,depthwise_filter,pointwise_filter,[1,1,1,1],'VALID')
    
    session=tf.Session()
    
    print(session.run(result))
    
    [[[[ 20.]
       [  9.]]
    
      [[-24.]
       [ 29.]]]]
    

    假设有1个3行3列2深度的三维张量,先与3个2行2列2深度的卷积核进行depthwise_conv2d卷积,其结果的深度为6,然后与2个1行1列6深度的卷积核conv2d卷积,最后结果的深度为2,具体代码如下:

    import tensorflow as tf
    
    # 1个3行3列2深度
    x=tf.constant(
        [
            [
                [[2,5],[3,3],[8,2]],
                [[6,1],[1,2],[5,4]],
                [[7,9],[2,3],[-1,3]]
            ]
        ]
        ,tf.float32
    )
    
    # 3个2行2列2深度的卷积核depthwiseFilter
    depthwise_filter=tf.constant(
        [
            [[[3,1,-3],[1,-1,7]],[[-2,2,-5],[2,7,3]]],
            [[[-1,3,1],[-3,-8,6]],[[4,6,8],[5,9,-5]]]
        ]
        ,tf.float32
    )
    
    # 2个1行1列6深度的卷积核pointwiseFilter
    pointwise_filter=tf.constant(
        [
            [[[0,0],[1,0],[0,1],[0,0],[0,0],[0,0]]]
        ],tf.float32
    )
    
    # 分离卷积
    result=tf.nn.separable_conv2d(x,depthwise_filter,pointwise_filter,[1,1,1,1],'VALID')
    
    session=tf.Session()
    
    print(session.run(result))
    
    [[[[ 32.  -7.]
       [ 52.  -8.]]
    
      [[ 41.   0.]
       [ 11. -34.]]]]
    
  • 相关阅读:
    cocos2d 设置按钮不可用
    cocos2d-js屏幕任何位置点击开始的实现
    cocos2d-js取不到cocostudio里面控件问题
    XMLHttpRequest
    [cocos2d-js]长按按钮事件
    [cocos2d-js]chipmunk例子(二)
    [cocos2d-js]chipmunk例子(一)
    BOX2D测试
    Cocos2d-JS v3.0 alpha 导入 cocostudio的ui配置
    Cocos2d-JS v3.0 alpha不支持cocos2d-x的Physics integration
  • 原文地址:https://www.cnblogs.com/LQ6H/p/10339963.html
Copyright © 2020-2023  润新知