Tensorflow快餐教程(9)-卷積

lusing發表於2018-05-04

卷積

卷積就是滑動中提取特徵的過程

在數學中,卷積convolution是一種函式的定義。它是通過兩個函式f和g生成第三個函式的一種數學運算元,表徵函式f與g經過翻轉和平移的重疊部分的面積。其定義為:
$h(x)=f(x)*g(x) =int_{-infty}^{infty}f(t)g(x-t)dt$
也可以用星號表示:$h(x)=(f*g)(x)$
卷積的第一個引數(上例中的f),通常叫做輸入。第二個引數(函式g)叫做核函式kernel function。輸出有時候叫特徵對映feature map.
也可以定義離散形式的卷積:
$h(x)=(f*g)(x) = sum_{t=-infty}^{infty}f(t)g(x-t)$

g(x-t)是變化的,而f(t)是固定不動的。我們可以將卷積理解成是g(x-t)滑動過程中對f(t)進行取樣。

我們一般可以用$f(x)=wx+b$來作為核函式提取特徵

我們來舉個小例子說明一下卷積對於特徵提取的過程。

假設有一個5*5的矩陣做為輸入:

array([[1., 1., 1., 1., 1.],
       [1., 1., 1., 1., 1.],
       [1., 1., 1., 1., 1.],
       [1., 1., 1., 1., 1.],
       [1., 1., 1., 1., 1.]], dtype=float32)

y=wx+b我們取w為3*3的全1矩陣,b=0。
這樣我們計算左邊第一個3*3的小塊,得$1*1+1*1+1*1+1*1+1*1+1*1+1*1+1*1+1*1=9$。
以此類推,最後我們得到一個
[[9,9,9],
[9,9,9],
[9,9,9]]
的矩陣。
相當於我們把一個55的黑白矩陣壓縮成了33的灰度矩陣。

全是1的話大家看不太清楚,我們選一個對角矩陣再來計算一下:

array([[1., 0., 0., 0., 0.],
       [0., 1., 0., 0., 0.],
       [0., 0., 1., 0., 0.],
       [0., 0., 0., 1., 0.],
       [0., 0., 0., 0., 1.]], dtype=float32)

計算卷積之後的結果為:

[[3,2,1],
[2,3,2],
[1,2,3]]

影像縮小了之後,仍然是主對角線最黑。基本特徵還是被提取出來了。

填充Padding

從前面對角線的例子可以看出,由於圖片中間的點被計算的次數多,而邊緣上的點計算的次數少,所以明明右上角和左下角都是0,提取完特徵之後被中間的1給影響了。
我們可以選擇給這個55的矩陣外邊加上一圈padding,將其變成77的矩陣。

我們再重新計算一下卷積,獲取下面一個5*5的新矩陣:

[[2,2,1,0,0],
 [2,3,2,1,0],
 [1,2,3,2,1],
 [0,1,2,3,2],
 [0,0,1,2,2]]

這樣邊緣的0就被識別出來了。Padding的最主要作用就是讓邊界變得更清晰。

步幅Stride

上面我們求卷積的時候,每次向右移到一步,這個移動距離就是Stride。
比如我們想把圖片壓縮得更狠一點,取得更高的壓縮率,我們就可以加大步幅。

卷積用Tensorflow實現

上面知識儲備已足,我們開始用Tensorflow來計算卷積吧。

首先是輸入,按照Tensorflow的要求,我們得把5*5的,resize成[1,5,5,1]格式的,如下:

>>> c
array([[[[1.],
         [0.],
         [0.],
         [0.],
         [0.]],

        [[0.],
         [1.],
         [0.],
         [0.],
         [0.]],

        [[0.],
         [0.],
         [1.],
         [0.],
         [0.]],

        [[0.],
         [0.],
         [0.],
         [1.],
         [0.]],

        [[0.],
         [0.],
         [0.],
         [0.],
         [1.]]]], dtype=float32)

然後我們還需要準備卷積核,先生成一個3*3全1矩陣:

>>> a2 = sess.run(tf.ones([3,3]))
>>> a2
array([[1., 1., 1.],
       [1., 1., 1.],
       [1., 1., 1.]], dtype=float32)

然後將其reshape成[3,3,1,1]格式的:

>>> a3 = sess.run(tf.reshape(a2,[3,3,1,1]))
>>> a3
array([[[[1.]],

        [[1.]],

        [[1.]]],


       [[[1.]],

        [[1.]],

        [[1.]]],


       [[[1.]],

        [[1.]],

        [[1.]]]], dtype=float32)

步幅我們設成[1,1,1,1],其實就是x軸1,y軸1,前面和後面的1先不用管它。padding設成`SAME`:

>>> a1 = tf.nn.conv2d(c,a3,strides=[1,1,1,1],padding=`SAME`)
>>> sess.run(a1)
array([[[[2.],
         [2.],
         [1.],
         [0.],
         [0.]],

        [[2.],
         [3.],
         [2.],
         [1.],
         [0.]],

        [[1.],
         [2.],
         [3.],
         [2.],
         [1.]],

        [[0.],
         [1.],
         [2.],
         [3.],
         [2.]],

        [[0.],
         [0.],
         [1.],
         [2.],
         [2.]]]], dtype=float32)

結果看起來不爽的話,我們再重新將其reshape成[5,5]的:

>>> a5 = tf.reshape(a4,[5,5])                               
>>> sess.run(a5)
array([[2., 2., 1., 0., 0.],
       [2., 3., 2., 1., 0.],
       [1., 2., 3., 2., 1.],
       [0., 1., 2., 3., 2.],
       [0., 0., 1., 2., 2.]], dtype=float32)

嗯,是不是跟我們手動計算的一樣呢?恭喜你,已經學會卷積啦!

池化層

池化層跟卷積也很像,但是計算要簡單得多。池化主要有兩種,一種是取最大值,一種是取平均值。而卷積是要做矩陣內積運算的。

我們以2*2最大池化為例,處理一下上節加了padding的卷積

[[2,2,1,0,0],
 [2,3,2,1,0],
 [1,2,3,2,1],
 [0,1,2,3,2],
 [0,0,1,2,2]]

最大池化就是取最大值,比如[[2,2],[2,3]]就取3。結果如下:

[[3,3,2,1],
  [3,3,3,2],
  [2,3,3,3],
  [1,2,3,3,]]

可以看到,池化雖然進一步丟失了資訊,但是基本規律還是不變的。

池化被認為可以提高泛化性,對於微小的變動不敏感。比如對於少量的平移,旋轉或者縮放保持同樣的識別。但是,池化層不是必須的。

在Tensorflow中,可以用tf.nn.max_pool來實現最大池功能。

首先我們要把圖片resize成(-1,高,寬,1)這樣格式的:

>>> b = tf.reshape(a,[-1,5,5,1])
>>> c = sess.run(b)
array([[[[1.],
         [0.],
         [0.],
         [0.],
         [0.]],

        [[0.],
         [1.],
         [0.],
         [0.],
         [0.]],

        [[0.],
         [0.],
         [1.],
         [0.],
         [0.]],

        [[0.],
         [0.],
         [0.],
         [1.],
         [0.]],

        [[0.],
         [0.],
         [0.],
         [0.],
         [1.]]]], dtype=float32)

然後我們用步幅為2。2*2視窗計算max pooling。
命令如下:

d =tf.nn.max_pool(c,ksize=[1,2,2,1], strides=[1,2,2,1], padding=`SAME`)

四元組中我們不必管第一個和最後一個,中間兩個是高和寬。ksize和步幅皆如此。
執行結果是一個3*3的對角陣:

>>> sess.run(d)
array([[[[1.],
         [0.],
         [0.]],

        [[0.],
         [1.],
         [0.]],

        [[0.],
         [0.],
         [1.]]]], dtype=float32)

卷積網路的結構

卷積神經網路也叫卷積網路,是一種善於處理網格資料的網路。典型應用是處理一維網格資料語音和二維網格資料影像。只要是在網路結構中使用了哪怕一層的卷積層,就叫做卷積神經網路。

與之前介紹的神經網路都是全連線網路,即每一層的每個節點都是上一層的所有節點相連線。
而卷積網路一般是五種連線結構的組合:

  1. 輸入層:一般認為就是原圖片
  2. 卷積層:與全連線網路不同,卷積層中每一個節點的輸入只是上一層神經網路的一小塊,常用的塊大小為33或者55。一般來說,通過卷積層處理過的節點的矩陣的深度會變深
  3. 池化層:Pooling層不會改變矩陣的深度,但是會使矩陣變小。可以理解為池化層是把高解析度的圖片轉化成低解析度的圖片
  4. 全連線層:最後的分類工作一般還是由一至兩個全連線層來實現的。
  5. Softmask層:可以得到當前樣例屬於不同分類的概率情況

卷積網路簡史

上節深度學習簡史中我們提到過,第一個卷積網路模型於1989年由Yann LeCun提出。卷積網路的主要概念如為什麼要卷積,為什麼要降取樣,什麼是徑向基函式RBF(Radial Basis Function)等。
10年後的1998年,Yann LeCun設計了LeNet網路。在論文《Gradient-Based Learning Applied to Document Recognition》中提出了LeNet-5的模型,結構如下:
LeNet-5
論文原文在:http://yann.lecun.com/exdb/publis/pdf/lecun-98.pdf

但是,正如我們前面介紹的,當時還是SVM輝煌的時代。
轉折點要到十幾年後的2012年,Hinton的學生Alex Krizhevsky發明的AlexNet之時。AlexNet在2012年的影像分類競賽中將Top-5錯誤率從上一年的25.8%降到15.3%.

AlexNet成功之後,大家的研究熱情空前高漲。主要方向有網路加深和功能增強。

  1. 網路加深:深度學習的優勢就是突破了加深層數的關鍵點。那麼就可以構建比AlexNet層數更多的網路。代表作是2014年ImageNet比賽的亞軍牛津大學的VGGNet,它的層數可以達到16~19層。從而將Top-5錯誤率從AlexNet的15.3%降到了7.32%.
  2. 功能增強:代表作是GoogLeNet。GoogLeNet參考了NIN(Network In Network)的思想,將原來的線性卷積層改成了多層感知機。同時,將全連線層改進為全域性平均池化。功能增強之後,GoogLeNet力壓VGGNet,獲得2014年ImageNet的冠軍,將Top-5錯誤率降到6.67%. 雖然功能有增強,但是在層數上GoogLeNet也毫沒客氣地增加到22層。後來在GoogLeNet的基礎上又推出Inception V3, V4等網路。
  3. 既加深也增強:代表作是2015年的ImageNet分類冠軍,由微軟亞洲研究院發明的ResNet殘差網路。ResNet在2015年的成績把錯誤率降到3.57%.

話說ImageNet真是個群英薈萃的地方。2012年第一名是Alex。2013的第一名是Matthew Zeiler,之前講過他提出的AdaGrad。
值得一提的是,在最近幾年的ImageNet中,華人屢創佳績。比如2016年的冠軍被公安部第三研究所搜神團隊獲得,成績是錯誤率2.99%。


相關文章