版權聲明:本文為博主原創文章,未經博主允許不得轉載。 https://blog.csdn.net/sinat_29957455/article/details/85558870 </div>
<link rel="stylesheet" href="https://csdnimg.cn/release/phoenix/template/css/ck_htmledit_views-3019150162.css">
<div id="content_views" class="markdown_views prism-atom-one-dark">
<!-- flowchart 箭頭圖標 勿刪 -->
<svg xmlns="http://www.w3.org/2000/svg" style="display: none;">
<path stroke-linecap="round" d="M5,0 0,2.5 5,5z" id="raphael-marker-block" style="-webkit-tap-highlight-color: rgba(0, 0, 0, 0);"></path>
</svg>
<h4><a id="_0"></a>前言</h4>
卷積
和反卷積
在CNN中經常被用到,想要徹底搞懂並不是那么容易。本文主要分三個部分來講解卷積和反卷積,分別包括概念
、工作過程
、代碼示例
,其中代碼實踐部分主結合TensorFlow框架來進行實踐。給大家介紹一個卷積過程的可視化工具,這個項目是github上面的一個開源項目。
卷積和反卷積
卷積(Convolutional)
:卷積在圖像處理領域被廣泛的應用,像濾波
、邊緣檢測
、圖片銳化
等,都是通過不同的卷積核來實現的。在卷積神經網絡中通過卷積操作可以提取圖片中的特征,低層的卷積層可以提取到圖片的一些邊緣
、線條
、角
等特征,高層的卷積能夠從低層的卷積層中學到更復雜的特征,從而實現到圖片的分類和識別。
反卷積
:反卷積也被稱為轉置卷積
,反卷積其實就是卷積的逆過程。大家可能對於反卷積的認識有一個誤區,以為通過反卷積就可以獲取到經過卷積之前的圖片,實際上通過反卷積操作並不能還原出卷積之前的圖片,只能還原出卷積之前圖片的尺寸
。那么到底反卷積有什么作用呢?通過反卷積可以用來可視化卷積的過程,反卷積在GAN等領域中有着大量的應用。
工作過程
卷積
上圖展示了一個卷積的過程,其中藍色的圖片(4*4)表示的是進行卷積的圖片,陰影的圖片(3*3)表示的是卷積核,綠色的圖片(2*2)表示是進行卷積計算之后的圖片
。在卷積操作中有幾個比較重要的參數,輸入圖片的尺寸、步長、卷積核的大小、輸出圖片的尺寸、填充大小
。
下面用一個圖來詳細介紹這些參數:
輸入圖片的尺寸
:上圖中的藍色圖片(55),表示的是需要進行卷積操作的圖片,在后面的公式中有
來表示輸入圖片的尺寸。
卷積核的大小
:上圖中的會移動陰影圖片表示的是卷積核(44),通過不同參數不同大小的卷積核可以提取到圖片的不同特征,后面用
表示卷積核的尺寸。
步長
:是指卷積核移動的長度,通過上圖可以發現卷積核水平方向移動的步長和垂直方向移動的步長是一樣的都是1,后面用
表示步長。
填充大小
:是指在輸入圖片周圍填充的圈數,通常都是用0來進行填充的,上圖中藍色圖片周圍兩圈虛線的矩形表示的是填充的值,所以填充掉是2,后面用
來表示填充大小。
輸出圖片的尺寸
:經過卷積操作之后獲取到的圖片的大小,上圖的綠色圖片(6*6),后面用o來表示。
如果已知
,可以求得
,計算公式如下:
反卷積
上圖展示一個反卷積的工作過程,乍看一下好像反卷積和卷積的工作過程差不多,主要的區別在於反卷積輸出圖片的尺寸會大於輸入圖片的尺寸,通過增加padding來實現這一操作
,上圖展示的是一個strides(步長)為1的反卷積。下面看一個strides不為1的反卷積
上圖中的反卷積的stride為2,通過間隔插入padding來實現的。同樣,可以根據反卷積的
參數來計算反卷積的輸出
,也就是卷積的輸入
。公式如下:
,其實就是根據上式推導出來的。
代碼示例
為了便於大家理解卷積和反卷積工作過程,將會使用圖示的方式來展示卷積和反卷積的工作過程,並利用tensorflow的卷積和反卷積函數來進行驗證。
卷積
使用tensorflow來實現卷積的時候,主要利用tf.nn.conv2d
函數來實現的,先介紹一下函數的參數
功能說明
:通過4維的input和filter來計算2維卷積
input
:4維的tensor,需要進行卷積的矩陣filter
:4維的tensor,卷積核的參數,需要和input具有相同的數據類型,[filter_height,filter_width,in_channels,out_channels],其中filter_height表示卷積核的高,filter_width表示卷積核的寬,in_channels表示需要進行卷積圖片的通道數,out_channels卷積之后輸出的通道數strides
:int類型的列表,設置卷積核滑動的步長padding
:填充類型有"SAME"和"VALID"兩種模式,當步長為1時,padding為"SAME"可以保持輸出與輸入的尺寸具有相同的大小。use_cudnn_on_gpu
:使用cudnn來加速卷積,默認是Truedata_format
:輸入數據的格式,有"NHWC"和"NCHW"兩種模式,默認使用的是"NHWC",表示[batch,height,width,channels],"NCHW"數據格式[batch,channels,height,width]dilations
:一維的list,默認是[1,1,1,1],用來設置卷積核的擴展name
:操作的名稱
TensorFlow提供的卷積函數padding只有"SAME"
和"VALID"
兩種模式,所以輸出矩陣的尺寸大小與之前的公式有所不同,下面介紹這兩種模式下輸出矩陣尺寸的計算公式:
padding為SAME時
:
,其中
表示輸入矩陣的大小,s表示卷積核的步長,ceil
函數表示向上取整。下圖展示是一個padding為SAME的卷積,卷積開始的時候保證卷積核的中心位於輸入矩陣角的頂點位置
。
padding為VALID時
:
,
表示卷積核的尺寸。下圖展示的是一個padding為VALID的卷積過程,卷積核始終都是位於輸入矩陣內進行移動
。
x1 = tf.constant(1.0, shape=[1,4,4,3])
x2 = tf.constant(1.0,shape=[1,6,6,3])
kernel = tf.constant(1.0,shape=[3,3,3,1])
y1_1 <span class="token operator">=</span> tf<span class="token punctuation">.</span>nn<span class="token punctuation">.</span>conv2d<span class="token punctuation">(</span>x1<span class="token punctuation">,</span>kernel<span class="token punctuation">,</span>strides<span class="token operator">=</span><span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">,</span><span class="token number">2</span><span class="token punctuation">,</span><span class="token number">2</span><span class="token punctuation">,</span><span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">,</span>padding<span class="token operator">=</span><span class="token string">"SAME"</span><span class="token punctuation">)</span>
y1_2 <span class="token operator">=</span> tf<span class="token punctuation">.</span>nn<span class="token punctuation">.</span>conv2d<span class="token punctuation">(</span>x1<span class="token punctuation">,</span>kernel<span class="token punctuation">,</span>strides<span class="token operator">=</span><span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">,</span><span class="token number">2</span><span class="token punctuation">,</span><span class="token number">2</span><span class="token punctuation">,</span><span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">,</span>padding<span class="token operator">=</span><span class="token string">"VALID"</span><span class="token punctuation">)</span>
y2_1 <span class="token operator">=</span> tf<span class="token punctuation">.</span>nn<span class="token punctuation">.</span>conv2d<span class="token punctuation">(</span>x2<span class="token punctuation">,</span>kernel<span class="token punctuation">,</span>strides<span class="token operator">=</span><span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">,</span><span class="token number">2</span><span class="token punctuation">,</span><span class="token number">2</span><span class="token punctuation">,</span><span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">,</span>padding<span class="token operator">=</span><span class="token string">"SAME"</span><span class="token punctuation">)</span>
y2_2 <span class="token operator">=</span> tf<span class="token punctuation">.</span>nn<span class="token punctuation">.</span>conv2d<span class="token punctuation">(</span>x2<span class="token punctuation">,</span>kernel<span class="token punctuation">,</span>strides<span class="token operator">=</span><span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">,</span><span class="token number">2</span><span class="token punctuation">,</span><span class="token number">2</span><span class="token punctuation">,</span><span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">,</span>padding<span class="token operator">=</span><span class="token string">"VALID"</span><span class="token punctuation">)</span>
sess <span class="token operator">=</span> tf<span class="token punctuation">.</span>InteractiveSession<span class="token punctuation">(</span><span class="token punctuation">)</span>
tf<span class="token punctuation">.</span>global_variables_initializer<span class="token punctuation">(</span><span class="token punctuation">)</span>
x1<span class="token punctuation">,</span>y1_1<span class="token punctuation">,</span>y1_2<span class="token punctuation">,</span>x2<span class="token punctuation">,</span>y2_1<span class="token punctuation">,</span>y2_2 <span class="token operator">=</span> sess<span class="token punctuation">.</span>run<span class="token punctuation">(</span><span class="token punctuation">[</span>x1<span class="token punctuation">,</span>y1_1<span class="token punctuation">,</span>y1_2<span class="token punctuation">,</span>x2<span class="token punctuation">,</span>y2_1<span class="token punctuation">,</span>y2_2<span class="token punctuation">]</span><span class="token punctuation">)</span>
<span class="token keyword">print</span><span class="token punctuation">(</span>x1<span class="token punctuation">.</span>shape<span class="token punctuation">)</span> <span class="token comment">#(1, 4, 4, 3)</span>
<span class="token keyword">print</span><span class="token punctuation">(</span>y1_1<span class="token punctuation">.</span>shape<span class="token punctuation">)</span> <span class="token comment">#(1, 2, 2, 1)</span>
<span class="token keyword">print</span><span class="token punctuation">(</span>y1_2<span class="token punctuation">.</span>shape<span class="token punctuation">)</span> <span class="token comment">#(1, 1, 1, 1)</span>
<span class="token keyword">print</span><span class="token punctuation">(</span>x2<span class="token punctuation">.</span>shape<span class="token punctuation">)</span> <span class="token comment">#(1, 6, 6, 3)</span>
<span class="token keyword">print</span><span class="token punctuation">(</span>y2_1<span class="token punctuation">.</span>shape<span class="token punctuation">)</span> <span class="token comment">#(1, 3, 3, 1)</span>
<span class="token keyword">print</span><span class="token punctuation">(</span>y2_2<span class="token punctuation">.</span>shape<span class="token punctuation">)</span> <span class="token comment">#(1, 2, 2, 1)</span>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
下面看一個卷積的計算例子
x1 = tf.constant([i*0.1 for i in range(16)],shape=
[1,4,4,1],dtype=tf.float32)
kernel = tf.ones(shape=[3,3,1,1],dtype=tf.float32)
conv1 = tf.nn.conv2d(x1,kernel,strides=
[1,1,1,1],padding="VALID")
sess = tf.InteractiveSession()
tf.global_variables_initializer()
conv1 = sess.run(conv1)
print(conv1)
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
將卷積核與輸入矩陣對應的位置進行乘加計算即可,對於多維輸入矩陣和多維卷積核的卷積計算,將卷積后的結果進行堆疊,作為最終卷積的輸出結果。
反卷積
tensorflow提供了tf.nn.conv2d_transpose
函數來計算反卷積
功能說明
:計算反卷積(轉置卷積)
value
:4維的tensor,float類型,需要進行反卷積的矩陣filter
:卷積核,參數格式[height,width,output_channels,in_channels],這里需要注意output_channels和in_channels的順序output_shape
:一維的Tensor,設置反卷積輸出矩陣的shapestrides
:反卷積的步長padding
:"SAME"和"VALID"兩種模式data_format
:和之前卷積參數一樣name
:操作的名稱
if __name__ == "__main__":
x1 = tf.constant([4.5,5.4,8.1,9.0],shape=
[1,2,2,1],dtype=tf.float32)
dev_con1 = tf.ones(shape=[3,3,1,1],dtype=tf.float32)
y1 = tf.nn.conv2d_transpose(x1,dev_con1,output_shape=
[1,4,4,1],strides=[1,1,1,1],padding="VALID")
sess = tf.InteractiveSession()
tf.global_variables_initializer()
y1,x1 = sess.run([y1,x1])
print(y1)
print(x1)
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
需要注意的是,通過反卷積並不能還原卷積之前的矩陣,只能從大小上進行還原,反卷積的本質還是卷積,只是在進行卷積之前,會進行一個自動的padding補0,從而使得輸出的矩陣與指定輸出矩陣的shape相同。
框架本身,會根據你設定的反卷積值來計算輸入矩陣的尺寸,如果shape不符合,則會報錯。
錯誤提示:InvalidArgumentError (see above for traceback): Conv2DSlowBackpropInput
,這時候需要檢查反卷積的參數與輸入矩陣之間的shape是否符合。計算規則可以根據padding為SAME
還是VALID
來計算輸入和輸出矩陣的shape是否相符合。如上例中,根據反卷積的參數來計算輸入矩陣的shape:因為padding是VALID
模式,所以我們套用
,而輸入矩陣x1的shape剛好是2*2,所以符合。
上面介紹的反卷積的stride
是1,接下來看一個stride不為1的例子
x1 = tf.constant([4.5,5.4,8.1,9.0],shape=
[1,2,2,1],dtype=tf.float32)
dev_con1 = tf.ones(shape=[3,3,1,1],dtype=tf.float32)
y1 = tf.nn.conv2d_transpose(x1,dev_con1,output_shape=
[1,6,6,1],strides=[1,2,2,1],padding="VALID")
sess = tf.InteractiveSession()
tf.global_variables_initializer()
y1,x1 = sess.run([y1,x1])
<span class="token keyword">print</span><span class="token punctuation">(</span>x1<span class="token punctuation">)</span>
<span class="token keyword">print</span><span class="token punctuation">(</span>y1<span class="token punctuation">)</span>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
需要注意的是,在進行反卷積的時候設置的stride並不是指反卷積在進行卷積時候卷積核的移動步長,而是被卷積矩陣填充的padding,仔細觀察紅色框內可以發現之前輸入矩陣之間有一行和一列0的填充
。
</div>
<link href="https://csdnimg.cn/release/phoenix/mdeditor/markdown_views-e44c3c0e64.css" rel="stylesheet">
</div>