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