1. 程式人生 > 其它 >卷積圖解_深入解讀反捲積網路(附實現程式碼)

卷積圖解_深入解讀反捲積網路(附實現程式碼)

技術標籤:卷積圖解

一、反捲積的基本概念

反捲積網路在許多領域諸如卷積網路視覺化(參考卷積神經網路視覺化的探索)、語義分割(參考Michael:一文弄懂文字檢測演算法Corner(附原始碼))有應用。

vdumoulin/conv_arithmetic裡有反捲積的動圖。

參考如何理解深度學習中的deconvolution networks?,反捲積更準確來說是轉置卷積,比如在TensorFlow中的呼叫就是tf/nn/conv2d_transpose,該函式這麼形容反捲積:

This operation is sometimes called "deconvolution" afterDeconvolutional Networks, but is really the transpose (gradient) of conv2d
rather than an actual deconvolution.

用公式可以這麼表達:

卷積運算可表示為:

,如果是數學關係上的逆卷積則應該表示為: ,但是反捲積真實的關係應該為:

至於反捲積層的核引數初始化及訓練中的變化情況,以及與卷積層的核引數有沒有關係,我認為 @愚蠢隊長 的話還是有點意思的(這裡推一篇我的文章神經網路是怎麼“卷積”的):):

先要理解卷積,理解卷積之後,自然就理解反捲積了。

下面就使用Numpy復現反捲積的工作原理。


二、使用Numpy復現反捲積

首先我們去TensorFlow原始碼裡找答案吧,API提示原始碼位於nn_ops.py#L2080-L2147,經過一番查詢發現函式指向nn_ops.py#L2017:

filter = deprecation.deprecated_argument_lookup(
      "filters", filters, "filter", filter)
padding, explicit_paddings = _convert_padding(padding)
return gen_nn_ops.conv2d_backprop_input(
      input_sizes, filter, out_backprop, strides, padding, use_cudnn_on_gpu,
      explicit_paddings, data_format, dilations, name)

注意這裡只改變了pading,filter是沒有改變的,關鍵的是,最終指向了卷積的反向推導部分,又應了本文開頭的那句話:

先要理解卷積,理解卷積之後,自然就理解反捲積了。

這裡我們還是先推導卷積的公式吧,深入解讀卷積網路的工作原理從im2col演算法推導了卷積的執行原理,但這個實際上在本文是執行不了的,畢竟卷積運算可表示為:

,構造的卷積矩陣 ,而不是輸入矩陣

因此,本文參考抽絲剝繭,帶你理解轉置卷積(反捲積),首先構造一個演算法實現卷積運算

,這裡考慮工作量,只考慮batch_size、輸入channel、輸出channel均為1 的情況,且padding為"VALID",相關的程式碼位於test_conv_transpose.py。

首先需要對輸入展開成列:

input_np_flattern = np.reshape(input_np, newshape=[input_size*input_size, 1])

之後需要構造卷積矩陣:

filter_np_matrix = np.zeros((output_size, output_size, input_size, input_size))
for h in range(output_size):
	for w in range(output_size):
		start_h = h*stride
		start_w = w*stride
		end_h = start_h + filter_size
		end_w = start_w + filter_size
		filter_np_matrix[h, w, start_h:end_h, start_w:end_w] = filter_np

原理還是很簡單的,根據步長等確立卷積區域,當然,最後要reshape成合適的維度:

filter_np_matrix = np.reshape(filter_np_matrix, newshape=[output_size*output_size, input_size*input_size])

最後矩陣相乘就得到輸出矩陣了,reshape成合適的規格即可:

output_np = np.dot(filter_np_matrix, input_np_flattern)
output_np = np.reshape(output_np, newshape=[output_size, output_size])

實驗部分與深入解讀卷積網路的工作原理是一樣的,輸入是

,卷積核為 ,stride為2,padding為"VALID"。

使用Numpy的輸出為:

output_np = [[312. 384.],[672. 744.]]
output_np.shape = (2, 2)

使用TensorFlow的輸出為:

output_tf = [[[[312.],[384.]],[[672.],[744.]]]]

驗證環節與深入解讀卷積網路的工作原理相同,這裡就不驗證了。

下面開始講反捲積部分。

首先與卷積部分相同,需要對輸入展開成列:

output_np_flattern = np.reshape(output_np, newshape=[output_size*output_size, 1])

之後執行

操作即可:
output_np_transpose = np.dot(filter_np_matrix.T, output_np_flattern)
output_np_transpose = np.reshape(output_np_transpose, newshape=[input_size, input_size])

得到的輸出是:

output_np_transpose = [[   0.  312.  624.  384.  768.]
 [ 936. 1248. 2712. 1536. 1920.]
 [1872. 2856. 6144. 3432. 4560.]
 [2016. 2688. 5592. 2976. 3720.]
 [4032. 4704. 9840. 5208. 5952.]]
output_np_transpose.shape = (5, 5)

至於TensorFlow的程式碼,首先需要構造output_shape,這裡參考conv_arithmetic.html#transposed-convolution-arithmetic,可得到輸出尺寸為:

這個公式實際上是卷積輸出尺寸的逆公式,之所以不唯一原因在於向下取整這個操作,這裡使得輸出尺寸由卷積操作的輸入決定:

output_shape = [batch_size, input_size, input_size, input_channel]

然後進行反捲積操作:

output_tf_transpose = tf.nn.conv2d_transpose(output_tf, filter_tf, output_shape, [1, stride, stride, 1], padding)

得到的輸出是:

output_tf_transpose = [[[[   0.],[ 312.],[ 624.],[ 384.],[ 768.]],
[[ 936.],[1248.],[2712.],[1536.],[1920.]]
[[1872.],[2856.],[6144.],[3432.],[4560.]]
[[2016.],[2688.],[5592.],[2976.],[3720.]]
[[4032.],[4704.],[9840.],[5208.],[5952.]]]]

可見兩者是完全相同的。

抽絲剝繭,帶你理解轉置卷積(反捲積)還提供了一個形象的圖解去解釋反捲積的工作原理,並在TensorFlow下驗證了這個想法,也是值得一讀的。

孫小也:反捲積(Transposed Convolution)詳細推導裡面關於反捲積的內容也相當不錯。

【已完結】