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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM