ImageNet冠軍帶你入門計算機視覺:卷積神經網路
本文由 【AI前線】原創,原文鏈接:http://t.cn/RYs9U26
AI 前線導語: 「在第一篇文章《ImageNet 冠軍帶你入門計算機視覺:監督學習與神經網路的簡單實現》中,我們介紹了神經網路的基本概念以及 Tensorflow 的基本用法。 本文為系列的第二篇文章,將會介紹卷積神經網路。重點介紹經典的卷積神經網路,全卷積網路的基本概念和基本單元,以及卷積神經網路與神經網路的異同。最後通過實現一個在實際中有廣泛應用的人臉關鍵點檢測演算法,介紹如何用 TensorFlow 構建卷積神經網路」。
神經網路的發展歷史
卷積神經網路(Convolutional Neural Network, CNN)的起源可以追溯到上世紀 60 年代。生物學研究表明,視覺信息從視網膜傳遞到大腦中是通過多個層次的感受野 (Receptive Field) 激發完成的,並提出了 Neocognitron 等早期模型。1998 年,深度學習三巨頭之一的 Lecun 等,正式提出了 CNN,並設計了如下圖所示的 LeNet-5 模型。該模型在手寫字元識別等領域取得了不錯的結果。
由於計算資源等原因,CNN 在很長時間內處於被遺忘的狀態。二十多年後的 ImageNet 比賽中,基於 CNN 的 AlexNet 在比賽中大放異彩,並引領了 CNN 的復興,此後 CNN 的研究進入了高速發展期。目前卷積神經網路的發展有兩個主要方向:
- 如何提高模型的性能。這個方向的一大重點是如何訓練更寬、更深的網路。沿著這一思路湧現出了包括 GoogleNet,VGG,ResNet,ResNext 在內的很多經典模型。
- 如何提高模型的速度。提高速度對 CNN 在移動端的部署至關重要。通過去掉 max pooling,改用 stride 卷積,使用 group 卷積,定點化等方法,人臉檢測、前後背景分割等 CNN 應用已經在手機上大規模部署。
目前,CNN 是計算機視覺領域最重要的演算法,在很多問題上都取得了良好的效果。因為篇幅關係,本文將主要介紹卷積神經網路的基礎知識。
神經網路 vs 卷積神經網路
上篇文章中我們介紹了神經網路。神經網路在大數據處理,語言識別等領域都有著廣泛的應用。但在處理圖像問題時會許多問題:
參數爆炸
以 200x200x3 的圖像為例,如果輸入層之後的 hidden layer 有 100 個神經元,那麼參數量會達到 200x200x3x100=1200 萬。顯然有如此多參數的模型是難以訓練且容易過擬合的。
平移不變性
對很多圖像問題,我們希望模型滿足一定的平移不變性。 例如對圖像分類問題,我們希望物體出現在圖片的任何位置上,模型都能正確識別出物體。
局部相關性
在大數據等問題中,輸入維度之間不存在顯式的拓撲關係,因此適合使用神經網路(全連接層)進行建模。但對於計算機視覺的問題,輸入圖片的相鄰像素之間存在天然的拓撲關係。例如,判斷圖片中某個位置是否有物體時,我們只需要考慮這個位置周邊的像素就可以了,而不需要像傳統神經網路那樣將圖片中所有像素的信息作為輸入。
為了克服神經網路的上述問題,在視覺領域,我們需要一種更合理的網路結構。卷積神經網路,在設計時通過局部連接和參數共享的方式,克服了神經網路的上述問題,因而在圖像領域取得了驚人的效果。接下來我們將詳細介紹卷積神經網路的原理。
卷積神經網路
網路結構
卷積神經網路和傳統神經網路的整體結構大致相同。如下圖所示,含有 2 層全連接層的傳統神經網路和含有 2 層卷積層的卷積神經網路都是由基本單元堆疊而成,前一層的輸出作為後一層的輸入。最終層的輸出,作為模型的預測值。二者的主要差別在於基本單元不同,卷積神經網路使用卷積層代替了神經網路中的全連接層。
和全連接層一樣,卷積層中也含有可以學習的參數 weight 和 bias。模型的參數,可以按上一篇文章介紹的方法,在監督學習的框架下定義損失函數,通過反向傳播進行優化。
卷積 (Convolution)
卷積層是整個卷積神經網路的基礎。2D 卷積操作,可以看作是一個類似模板匹配的過程。如下圖所示,將尺寸為 h × w × d 的模板,通過滑動窗口的方式和輸入進行匹配。滑動過程中,輸入中對應位置的值和模板的權重的內積加一個偏移量 b,作為對應輸出位置的值。w,h 是模板的大小,統稱為 kernel size,在 CNN 中,w 和 h 一般會取相同的值。d 是模板的 channel 數量,和輸入的 channel 數相同,例如對 RGB 圖像,channel 數為 3。
模板在卷積神經網路中常被稱為卷積核( K )或者過濾器(filter)。在標準卷積中,輸出位置 (x,y) 對應的輸出值可以表示成:
在 CNN 中,除了描述單個 filter 的 h ,w ,d 這 3 個參數之外,還有 3 個重要的參數 depth, stride 和 padding:
- depth 指的是輸出 channel 的數量, 對應於卷積層中 filter 的數量
- stride 指的是 filter 每次滑動的步長
- padding 指的是在輸入四周補 0 的寬度。使用 padding 主要是為了控制輸出的尺寸。如果不添加 padding,使用 kernel size 大於 1 的 filter 會使輸尺寸出比輸入小。在實際中經常會增加 padding,使得輸入和輸出的尺寸一致。
如下圖所示,對 1D 的情況,假設輸入尺寸為 W ,filter 的尺寸為 F,stride 為 S,padding 為 P ,那麼輸出的尺寸為 (W - F + 2P)/S + 1為。 通過設定 P=(F-1)/2,當 S=1 時,輸入和輸出的尺寸會保持一致。2D 卷積的計算和 1D 卷積類似。
對比傳統神經網路中的全連接層,卷積層實際上可以看成是全連接層的一種特例。首先是局部連接性,通過利用輸入自帶的空間拓撲結構,卷積神經網路只需考慮在空間上和輸出節點距離在 filter 範圍內的輸入節點,其他邊的權重都為 0。此外,對於不同的輸出節點,我們強制 filter 的參數完全一致。但通過這種 局部連接 和 參數共享,卷積層可以更好的利用圖像中內在的拓撲關係及平移不變形,大大減少了參數,從而得到一個更好的局部最優解,使其在圖像問題上有更好的性能。
在 tensorflow 中實現卷積層非常簡單,可以直接調用tf.nn.conv2d
:
池化 (Pooling)
在 CNN 網路中,除了大量的卷積層,我們也會根據需要,插入適量的池化層。池化層可以用來減少輸入的尺寸,從而減少後續網路的參數與計算量。常見的池化操作(如 max pooling,average pooling),通常也可以提供一定的平移不變性。
我們以 max pooling 舉例,max pooling 對 kernel size 範圍內的所有值取 max,結果作為對應位置的輸出。pooling 通常是對每個 channel 單獨操作,因此輸出的 channel 數和輸入相同。池化層和卷積層類似,pooling 操作也可以理解為採用滑動窗口的方式,因此也有和卷積對應的步長 stride 和 padding 等概念。 下圖所示就是一個 kernel size 和 stride 都為 2 的 max pooling 操作:
實際當中,池化層的參數有兩種比較常見的配置,一種是 kernel size 和 stride 都為 2 的,這種設置池化過程中無重疊區域。另一種是 kernel size 為 3,stride 為 2 的有重疊 pooling。在 tensorflow 中實現池化層也非常簡單:
卷積神經網路的經典網路結構
介紹了卷積神經網路的基本組成模塊之後,我們接下來介紹一下卷積神經網路的經典網路結構。從 1998 的 LeNet-5 開始,到 Imagenet 2012 的 AlexNet 模型,再到後來的 VGG 等一系列經典模型,基本都遵從了這個經典結構。
為了清晰,我們省略了卷積和全連接層之後的非線性激活函數。如上圖所示,經典的卷積神經網路,可以分為三個部分:
- 一系列級聯的 conv+pooling 層(有時會省略掉 pooling 層)。在級聯的過程中,輸入的尺寸逐漸變小,同時輸出的 channel 逐漸變多,完成對信息從低級到高級的抽象。
- 一系列級聯的全連接層。在卷積層到全連接層的交界處,卷積層的輸出轉化成一維的輸入送入全連接層。之後根據任務的複雜程度,級聯一系列全連接層。
- 最後的輸出層,根據任務的需要,決定輸出的形式。 如多分類問題,最後會接一個 softmax 層。
經典卷積神經網路,可以看作是一個輸出尺寸固定的非線性函數。它可以將尺寸為 H times W times 3 的輸入圖片轉化為最終的維度為 d 的定長向量。經典卷積神經網路在圖像分類、回歸等問題上取得了巨大的成功。之後的實戰部分,我們會給出一個回歸問題的例子。
全卷積網路
(Fully Convolution Network)
經典的卷積神經網路中由於有全連接層的存在,只能接受固定尺寸的圖片作為輸入,併產生固定尺寸的輸出。雖然可以通過使用 adaptive pooling 的方式, 接受變長的輸入,但這種處理仍然只能產生固定尺寸的輸出。為了克服經典卷積神經網路的這種缺點,在物體分割等輸出尺寸可變的應用場景下,我們不再使用全連接層。這種主要計算單元全部由卷積層組成的網路,被稱為全卷積網路(FCN)。
如上圖所示,由於卷積操作對輸入尺寸無限制,且輸出尺寸由輸入決定,因此全卷積網路可以很好的處理如分割等尺寸不固定的問題。全卷積網路,可以看成是一種輸出尺寸隨輸入尺寸線性變化的非線性函數。它可以將尺寸為 H × W × 3 的輸入圖片轉化為最終維度為 H/S ×H/S × d 的輸出。 可以轉化為這種形式的監督學習問題,基本都可以在全卷積網路的框架下求解。
反卷積(Deconvolution)
在全卷積網路中,標準的卷積 + 池化操作,會使輸出的尺寸變小。對於很多問題,我們需要輸出的尺寸和輸入圖片保持一致,因此我們需要一種可以擴大輸入尺寸的操作。最常用的操作就是反卷積。
反卷積可以理解成卷積的逆向操作。這裡我們主要介紹 stride>1 且為整數的反卷積。這種反卷積可以理解為一種廣義的差值操作。以下圖為例,輸入是 3x3 的綠色方格,反卷積的 stride 為 2,kernel size 為 3,padding 為 1。在滑動過程中,對每個輸入方格,其輸出為對應的 3x3 陰影區域,輸出值為輸入值和 kernel 對應位置值的乘積。最終的輸出為滑動過程中每個輸出位置對應值的累加和。這可以看成是一種以 3x3 kernel 值為權重的差值操作。最外邊的一圈白色區域無法進行完整的差值操作,因此可以通過設定 padding 為 1, 將周圍的一圈白色區域去掉,最終的輸出尺寸為 5x5。
根據上面的描述,stride>1 且為整數的反卷積,如果固定反卷積 kernel 的取值為雙線性差值 kernel,反卷積可以等價於雙線性差值。而通過學習得到反卷積 kernel,相比固定參數的 kernel,可以更好的適應不同的問題,因此反卷積可以看成是傳統差值的一種推廣。和卷積類似,tensorflow 中已經實現了反卷積模塊tf.layers.conv2d_transpose
。
卷積神經網路在視覺識別中的應用
CNN 在視覺識別(Visual Recognition)中有著非常廣泛的應用。我們接下來以視覺識別中的三大經典問題:分類 / 回歸、檢測和分割為例,介紹如何用 CNN 解決實際問題。
分類 / 回歸 (classification/regression)
圖像分類是指判別圖像屬於哪一 / 哪些預先指定的類別,圖像回歸是指根據圖像內容判斷圖片屬性的取值。分類和回歸在實際中都有著廣泛的應用。從物體分類,人臉識別,再到 12306 的驗證碼識別等,都可以抽象成標準的分類問題。類似的,人臉的關鍵點位置預測,人臉的屬性預測(如年齡,顏值)等,也都可以抽象為標準的回歸問題。目前視覺領域的應用,如果能抽象成輸出為定長的分類或者回歸問題,在有大量訓練數據的情況下,通常都可以採用之前介紹的經典卷積神經網路框架解決。
檢測 (detection)
檢測問題通常是指判斷出圖片中是否有物體,以及該物體的位置。檢測有 one-stage 和 two-stage 的方法。 因為篇幅關係,我們重點介紹在 FCN 框架下的 one-stage 方法。
按之前的介紹,FCN 可以看作是將 H × W × 3 的輸入圖片,轉化為 H/S × W/S × d 輸出的非線性函數。在 FCN 的框架下解決檢測問題,我們可以預測每一個輸出位置是否有物體,以及物體左上角、右下角相對於當前輸入位置的偏移。 這樣對每個輸出位置,需要 5 維的向量來表示是否有物體,即 d=5。 定義了網路的輸出之後,我們人工構造出對應的 ground truth,之後在監督學習的框架下,通過定義損失函數(l2 loss) 並進行反向傳播,進行參數的學習。
分割 (segmentation)
分割問題是指給出圖片中每個像素點的類別。 基於 FCN 的分割方法和上面介紹的 one-stage 的檢測方法非常類似。 對一個多分類的分割問題,對輸出的每一個位置,我們可以判斷其所屬的類別。 在 FCN 的框架下,對於 N 分類問題,輸出為 H/S × W/S × N。之後通過反向傳播的方式進行訓練。 分割和檢測問題有一個區別是我們有時需要得到和輸入圖片同樣大小的輸出(H × W × N),但卷積神經網路為了加速,通常會添加 pooling 層,減小中間卷積層的尺寸。 如下圖所示,為了保證輸出的尺寸滿足要求,我們可以在網路的最後添加反卷積層進行補償,從而獲得更大尺寸的輸出。
實戰: 人臉關鍵點檢測
人臉關鍵點檢測是現在視覺領域比較成熟的一項應用,是活體檢測,人類美化,人臉識別等高級應用的基礎。本文最後通過一個人臉關鍵點檢測的例子,展示如何用 Tensorflow 實現圖像回歸類的應用。實驗數據集採用 Kaggle 比賽中的 Faical Kerypoints Detection 數據集(https://www.kaggle.com/c/facial-keypoints-detection)。該數據集包含了 7094 張訓練圖片和 1783 張測試圖片。數據集中的每一張人臉都有 15 個關鍵點的標註,圖片的尺寸為 96x96。
L2 距離回歸
Kaggle 比賽的目標是預測人臉上 15 個關鍵點的坐標,總共 30 個 float 值,屬於標準的回歸問題。我們選擇採用最常見的 l2 距離,作為優化的目標。和第一篇文章中神經網路模型的代碼結構一樣,我們將代碼分成了 3 個主要模塊,分別是 Dataset 模塊,Net 模塊和 Solver 模塊。
模型結構
inference
我們在 inference 函數中定義網路的主體結構。因為模型會重複用到全連接層和卷積層,因此我們將他們封裝成函數linear_relu
和conv_relu
,從而方便復用代碼。 網路結構上我們採用了比較簡單的 3 層卷積,2 層全連接的結構。卷積層的輸出通過tf.reshape
轉化成了全連接層可以接受的格式。因為是回歸問題,我們直接將最後一層全連接層的結果作為輸出。loss
為了簡單,對於標準的回歸問題,我們使用 mse 作為損失函數tf.reduce_mean(tf.square(predictions - labels), name=mse)
測試時,我們依舊使用 tensorflow 提供了 tf.metrics 模塊,自動完成對每個 batch 的評價,並將所有的評價匯總。在這個例子里,我們是解決回歸問題,因此可以使用tf.metrics.mean_squared_error
計算均方誤差。
Dataset 部分,我們使用了 tensorflow 推薦的 tfrecord 格式。通過TFRecordDataset
函數讀取 tfrecord 文件,並通過parse_example
將 tfrecod 轉換成模型的輸入格式。tfrecord 作為一種定長格式,可以大大加快數據的讀取速遞。特別在使用 GPU 時,可以防止數據 io 成為性能的瓶頸。
Solver
通過模塊化的設計,我們可以完全復用第一篇文章中的 Solver 代碼,而不需要任何修改,進而提高代碼的復用效率。
實驗結果
封裝好借口之後,我們可以通過上面簡單的代碼,完成模型的訓練。下圖是 tensorboad 中可視化的網路結構、loss 的統計以及模型在測試圖片上的效果
可以看到,一個 3 層卷積 +2 層全連接的經典卷積神經網路,就可以很好的解決人臉關鍵點檢測的問題。在實際中,我們可以使用更複雜的網路和一些其他 trick 來進一步提高模型性能。
作者簡介
董健,360 高級數據科學家,前 Amazon 研究科學家。 目前主要關注深度學習、強化學習、計算機視覺等方面的科學和技術創新,擁有豐富的大數據、計算機視覺經驗。曾經多次領隊參加 Pascal VOC、ImageNet 等世界著名人工智慧競賽並獲得冠軍。
博士期間在頂級國際學術會議和雜誌上發表過多篇學術論文。從 2015 年底加入 360 至今,董健作為主要技術人員參與並領導了多個計算機視覺和大數據項目。
完整代碼下載:
https://github.com/Dong--Jian/Vision-Tutorial
關注我們的微信號"AI前線",後台回復「AI」可獲得《AI前線》系列PDF電子書
推薦閱讀: