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.]]]]
posted @ 2019-01-30 10:12  LQ6H  阅读(2110)  评论(0编辑  收藏  举报