
作者|360 董健
編輯|Vincent,Emily
AI 前線導語:在第一篇文章《ImageNet 冠軍帶你入門計算機視覺:監督學習與神經網絡的簡單實作》中,我們介紹了神經網絡的基本概念以及 Tensorflow 的基本用法。 本文為系列的第二篇文章,将會介紹卷積神經網絡。重點介紹經典的卷積神經網絡,全卷積網絡的基本概念和基本單元,以及卷積神經網絡與神經網絡的異同。最後通過實作一個在實際中有廣泛應用的人臉關鍵點檢測算法,介紹如何用 TensorFlow 建構卷積神經網絡。
更多幹貨内容請關注微信号“AI 前線”,ID:ai-front
神經網絡的發展曆史
卷積神經網絡(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 中實作卷積層非常簡單,可以直接調用:
池化 (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 中已經實作了反卷積子產品。
卷積神經網絡在視覺識别中的應用
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 函數中定義網絡的主體結構。因為模型會重複用到全連接配接層和卷積層,是以我們将他們封裝成函數和,進而友善複用代碼。 網絡結構上我們采用了比較簡單的 3 層卷積,2 層全連接配接的結構。卷積層的輸出通過轉化成了全連接配接層可以接受的格式。因為是回歸問題,我們直接将最後一層全連接配接層的結果作為輸出。
為了簡單,對于标準的回歸問題,我們使用 mse 作為損失函數
測試時,我們依舊使用 tensorflow 提供了 tf.metrics 子產品,自動完成對每個 batch 的評價,并将所有的評價彙總。在這個例子裡,我們是解決回歸問題,是以可以使用計算均方誤差。
Dataset 部分,我們使用了 tensorflow 推薦的 tfrecord 格式。通過函數讀取 tfrecord 檔案,并通過将 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