天天看點

【Tensorflow】Tensorflow中的卷積函數(conv2d、slim.conv2d、depthwise_conv2d、conv2d_transpose)

【fishing-pan:https://blog.csdn.net/u013921430 轉載請注明出處】

前言

  卷積是卷積神經網絡中最主要、最重要的運算。想必大家最早接觸卷積的概念就是在初高中的數學當中,它是一個這樣的公式;

( g ∗ h ) ( x ) = ∫ − ∞ ∞ g ( τ ) ⋅ h ( x − τ ) d τ (g\ast h)(x)=\int_{-\infty }^{\infty }g(\tau )\cdot h(x-\tau )d\tau (g∗h)(x)=∫−∞∞​g(τ)⋅h(x−τ)dτ

  上面的公式是一維空間中連續函數的卷積,而在圖像進行中,卷積操作是二維平面上的離散卷積;卷積的過程可以表示為下方的式子,式子中的 k e r n e l ( i , j ) kernel(i,j) kernel(i,j) 表示卷積核的坐标為 ( i , j ) (i,j) (i,j) 處的權重, H H H 與 W W W 分别表示卷積核的高與寬。

o u t p u t ( x , y ) = ∑ i = 0 i = H − 1 ∑ j = 0 j = W − 1 i n p u t ( x + i , y + j ) ⋅ k e r n e l ( i , j ) output(x,y)=\sum_{i=0}^{i=H-1}\sum_{j=0}^{j=W-1}input(x+i,y+j)\cdot kernel(i,j) output(x,y)=i=0∑i=H−1​j=0∑j=W−1​input(x+i,y+j)⋅kernel(i,j)

  詳細的計算過程可以模拟為下方的gif圖中的形式。(圖檔來源連結)

【Tensorflow】Tensorflow中的卷積函數(conv2d、slim.conv2d、depthwise_conv2d、conv2d_transpose)

Tensorflow中的卷積函數到底做了些什麼

  上面的簡單介紹主要是為了讓大家了解卷積的操作過程。下面将進入正題,介紹一下tf中的卷積函數。本文将着重介紹tf.nn.conv2d函數,餘下的函數主要分析他們之間的差別。

tf.nn.conv2d

  這應該是tensorflow建構網絡模型時最常用的卷積函數了,他的定義如下;

conv2d(input, filter, strides, padding, use_cudnn_on_gpu=True, 
       data_format="NHWC", dilations=[1, 1, 1, 1], name=None):
           

  參數中帶有預設值的一般不需要管,我們這裡就不詳細介紹了,大家想了解的可以自行檢視函數定義。接下來詳細介紹一下四個主要的參數:input, filter, strides, padding

   input:輸入的tensor,被卷積的圖像,conv2d要求input必須是四維的。四個次元分别為[batch, in_height, in_width, in_channels],即batch size,輸入圖像的高和寬以及單張圖像的通道數。

   filter:卷積核,也要求是四維,[filter_height, filter_width, in_channels, out_channels]四個次元分别表示卷積核的高、寬,輸入圖像的通道數和卷積輸出通道數。其中in_channels大小需要與 input 的in_channels一緻。

  strides:步長,即卷積核在與圖像做卷積的過程中每次移動的距離,一般定義為[1,stride_h,stride_w,1],stride_h與stride_w分别表示在高的方向和寬的方向的移動的步長,第一個1表示在batch上移動的步長,最後一個1表示在通道次元移動的步長,而目前tensorflow規定:strides[0] = strides[3] = 1,即不允許跳過bacth和通道,前面的動态圖中的stride_h與stride_w均為1。

  padding:邊緣處理方式,值為“SAME” 和 “VALID”,熟悉圖像卷積操作的朋友應該都熟悉這兩種模式;由于卷積核是有尺寸的,當卷積核移動到邊緣時,卷積核中的部分元素沒有對應的像素值與之比對。此時選擇“SAME”模式,則在對應的位置補零,繼續完成卷積運算,在strides為[1,1,1,1]的情況下,卷積操作前後圖像尺寸不變即為“SAME”。若選擇 “VALID”模式,則在邊緣處不進行卷積運算,若運算後圖像的尺寸會變小。

  首先分别定義輸入圖像與卷積核。為了友善分析,将輸入圖像的batch size設為1,圖像尺寸為10x10,通道數為3。卷積核尺寸為[5,5,3,4]。進行卷積操作,然後輸出卷積結果的尺寸。

input_img=tf.Variable(tf.constant(10,dtype=tf.float32,shape=[1,10,10,3])) #定義輸入圖像
W1=tf.Variable(tf.truncated_normal([5,5,3,4],stddev=0.1)) #定義卷積核
conv1=tf.nn.conv2d(input_img,W1,strides=[1,2,2,1],padding='SAME') #卷積操作
init=tf.global_variables_initializer()

with tf.Session() as sess:
    sess.run(init)
    print('conv1 shape is:',sess.run(tf.shape(conv1)))
           

  可想而知,conv1的尺寸是[1 5 5 4],因為在定義卷積核的時候就明确說明卷積核的第四個次元為輸出通道數。那麼問題來了,conv1中的每個元素的值是如何确定的呢?問得好。

  從表面上來看,我們隻定義了一個卷積核W1,“他”将圖像從3通道變成了4通道,就像下面這個過程。

【Tensorflow】Tensorflow中的卷積函數(conv2d、slim.conv2d、depthwise_conv2d、conv2d_transpose)

  但是實際上并不是這樣, W 1 W1 W1不是一個卷積核,而是表示一個包含c個卷積核的卷積層。conv2d函數卷積的過程可以表示成下面的公式;其中 i i i、 j j j表示像素點的位置, d i di di、 d j dj dj表示卷積核中元素的相對位置, q q q 表示輸入通道序号, c c c 表示輸出通道數。

c o n v 1 [ b , i , j , c ] = ∑ d i , d j , q i n p u t [ b , s t r i d e s [ 1 ] ∗ i + d i , s t r i d e s [ 2 ] ∗ j + d j , q ] ∗ W 1 [ d i , d j , q , c ] conv1[b, i, j, c]=\sum_{di,dj,q}^{ }input[b, strides[1] * i + di, strides[2] * j + dj, q] *W_{1}[di, dj, q, c] conv1[b,i,j,c]=di,dj,q∑​input[b,strides[1]∗i+di,strides[2]∗j+dj,q]∗W1​[di,dj,q,c]

  從公式中可以看出,卷積核的前三個次元形成一個三維的卷積核,與輸入圖像做卷積,最終得到一個單通道的輸出(這個過程可以簡單了解為,把三個通道的特征壓縮到了一個通道上);而卷積核的第四個次元 c c c 對應着輸出的每一個通道,其實 c c c 表示的是有多少個這樣的卷積核。其過程可以簡化成下圖;

【Tensorflow】Tensorflow中的卷積函數(conv2d、slim.conv2d、depthwise_conv2d、conv2d_transpose)

  經過上述描述,想必大家也都明白了為何卷積核的第三個次元要是輸入圖像的通道數。

tf.contrib.slim.conv2d

  另一個常見的卷積函數是 tf.contrib.slim.conv2d,他的卷積過程與tf.nn.conv2d一緻,其函數名和參數定義如下。

convolution(inputs,num_outputs,kernel_size,stride=1,padding='SAME',data_format=None,
            rate=1,activation_fn=nn.relu,normalizer_fn=None,normalizer_params=None,weights_initializer=initializers.xavier_initializer(),
            weights_regularizer=None,biases_initializer=init_ops.zeros_initializer(),biases_regularizer=None,
            reuse=None,variables_collections=None,outputs_collections=None,trainable=True,cope=None):
           

  其中的參數很多,就不一一介紹了,主要的參數依然是inputs,num_outputs,kernel_size,stride,padding。使用slim.conv2d函數進行卷積操作,不需要單獨定義卷積層,激活函數,甚至是偏置。

input_img=tf.Variable(tf.constant(10,dtype=tf.float32,shape=[1,10,10,3]))#定義輸入圖像

W1=tf.Variable(tf.truncated_normal([5,5,3,4],stddev=0.1))#定義卷積核
conv1=tf.nn.conv2d(input_img,W1,strides=[1,2,2,1],padding='SAME')
relu1=tf.nn.relu(conv1)

conv4=slim.conv2d(input_imgg,4,[5,5],strides=2,padding='SAME')
           

  在上方的代碼中第6行與2至4行所執行的功能是一緻的。而代碼量卻小了很多。是以在建構網絡層數較多的網絡時,使用slim.conv2d以及slim庫中的其他功能可以使模型的建構,訓練,測試都變得更加簡單。

tf.nn.depthwise_conv2d

  tf.nn.depthwise_conv2d的意思是深度二維卷積,他的定義如下。

depthwise_conv2d(input,filter,strides, padding,
                 rate=None,name=None, data_format=None):
           

  與tf.nn.conv2d一樣,他的主要參數也是input, filter, strides, padding,他與tf.nn.conv2d的差別在于filter的不同。在depthwise_conv2d中filter的四個次元分别為[filter_height, filter_width, in_channels, channel_multiplier],其第四個次元不再是輸出通道數,而是通道倍數,最終輸出的總的通道數為 i n c h a n n e l s ∗ c h a n n e l m u l t i p l i e r in channels*channel multiplier inchannels∗channelmultiplier,因為此時分通道進行卷積與輸出,不再卷積結果合并成單通道,明白了這個道理,函數的卷積過程就好了解了。可以用以下的公式表示;

c o n v 1 [ b , i , j , c ∗ m u l t i + q ] = ∑ d i , d j i n p u t [ b , s t r i d e s [ 1 ] ∗ i + d i , s t r i d e s [ 2 ] ∗ j + d j , c ] ∗ W 1 [ d i , d j , c , q ] conv1[b, i, j, c*multi + q]=\sum_{di,dj}^{ }input[b, strides[1] * i + di, strides[2] * j + dj, c] *W_{1}[di, dj, c, q] conv1[b,i,j,c∗multi+q]=di,dj∑​input[b,strides[1]∗i+di,strides[2]∗j+dj,c]∗W1​[di,dj,c,q]

  而卷積過程可以表示為下圖所示的形式。

【Tensorflow】Tensorflow中的卷積函數(conv2d、slim.conv2d、depthwise_conv2d、conv2d_transpose)

  這裡可以驗證一下上述過程。使用同樣的輸入與卷積核,分别用不同的卷積方式做卷積,并且對depthwise_conv2d卷積的輸出的第一個、第五個、第九個通道對應位置的元素求和,與conv2d卷積第一通道的結果對比,看看有什麼差異。

import tensorflow as tf

input_img=tf.Variable(tf.constant(10,dtype=tf.float32,shape=[1,10,10,3]))#定義輸入圖像
W1=tf.Variable(tf.truncated_normal([5,5,3,4],stddev=0.1))#定義卷積核
conv1=tf.nn.conv2d(input_img,W1,strides=[1,2,2,1],padding='SAME')
conv2=tf.nn.depthwise_conv2d(input_img,W1,strides=[1,2,2,1],padding='SAME')

conv2_1=conv2[:,:,:,0]
conv2_2=conv2[:,:,:,4]
conv2_3=conv2[:,:,:,8]

conv2_sum123=tf.add(tf.add(conv2_1,conv2_2),conv2_3)
init=tf.global_variables_initializer()

with tf.Session() as sess:
    sess.run(init)
    print('conv1 shape is:',sess.run(tf.shape(conv1)))
    print('conv2 shape is:',sess.run(tf.shape(conv2)))
#    print('conv2d sum:',sess.run(tf.reduce_sum(conv1,axis=3)))
#    print('depthwise_conv2d sum:',sess.run(tf.reduce_sum(conv2,axis=3)))
    print(sess.run(conv2_sum123))
    print(sess.run(conv1[:,:,:,0]))
           

  最終輸出如下:

【Tensorflow】Tensorflow中的卷積函數(conv2d、slim.conv2d、depthwise_conv2d、conv2d_transpose)

  從求和結果可以看出,兩者基本一緻。也證明了depthwise_conv2d的計算輸出是未進行通道求和之前的conv2d函數的計算結果。

tf.nn.conv2d_transpose

  tf.nn.conv2d_transpose是反卷積函數,進行的是卷積的反向操作,一般應用于上采樣過程,其定義如下;

conv2d_transpose(value,filter,  output_shape,strides,
    padding="SAME",data_format="NHWC",name=None):
           

   value:指需要做反卷積的輸入圖像,與之前的一樣,要求是一個四維的tensor。

   filter:卷積核,要求是一個四維tensor,與之前的卷積核不同,其四個次元分别是[filter_height, filter_width, out_channels, in_channels],注意輸入通道數與輸出通道數的位置改變了

  output_shape:反卷積操作輸出tensor的shape,之前的卷積中是沒有這個參數的。

  strides:反卷積時在圖像每一維上的步長

  此處output_shape并不能取任意的尺寸,應該根據輸入圖像尺寸、卷積核尺寸以及正向卷積的過程,反推出尺寸大小。例如,下面的代碼中output_shape有兩個錯誤,第一個是輸出圖像的長寬不比對,第二個就是輸出通道數為5與卷積核的輸出通道數3不比對。

import tensorflow as tf

input_img=tf.Variable(tf.constant(10,dtype=tf.float32,shape=[1,10,10,3]))#定義輸入圖像
W1=tf.Variable(tf.truncated_normal([5,5,3,4],stddev=0.1))#定義卷積核
conv1=tf.nn.conv2d(input_img,W1,strides=[1,2,2,1],padding='SAME')

conv3=tf.nn.conv2d_transpose(conv1,W1,[1,15,15,5],strides=[1,2,2,1],padding='SAME')
init=tf.global_variables_initializer()

with tf.Session() as sess:
    sess.run(init)
    print('conv1 shape is:',sess.run(tf.shape(conv1)))
    print('conv3 shape is:',sess.run(tf.shape(conv3)))

    print(sess.run(conv3[:,:,:,0]))
           

  反卷積的過程就不展開了,感興趣的可以自行查找資料學習。

參考

  最後,向大家推薦一個對卷積較長的描述的網站(連結)。

  已完。。

繼續閱讀