Tensorflow–二维离散卷积
一.二维离散卷积的计算原理
二维离散卷积的计算原理同一维离散卷积的计算原理类似,也有三种卷积类型:full卷积,same卷积核valid卷积。通过3行3列的二维张量x和2行2列的二维张量K
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]
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.]]]]