《Scikit-Learn與TensorFlow機器學習實用指南》 第13章 卷積神經網絡


(第一部分 機器學習基礎) 第01章 機器學習概覽 第02章 一個完整的機器學習項目(上) 第02章 一個完整的機器學習項目(下) 第03章 分類 第04章 訓練模型 第05章 支持向量機 第06章 決策樹 第07章 集成學習和隨機森林 第08章 降維 (第二部分 神經網絡和深度學習) 第9章 啓動和運行TensorFlow 第10章 人工神經網絡 第11章 訓練深度神經網絡(上) 第11章 訓練深度神經網絡(下) 第12章 設備和服務器上的分佈式 TensorFlow 第13章 卷積神經網絡


儘管 IBM 的深藍超級計算機在1996年擊敗了國際象棋世界冠軍 Garry Kasparvo,直到近幾年計算機都不能可靠地完成一些看起來較爲複雜的任務,比如判別照片中是否有狗以及識別語音。爲什麼這些任務對於人類而言如此簡單?答案在於感知主要發生在我們意識領域之外,在我們大腦中的專門視覺,聽覺和其他感官模塊內。當感官信息達到我們的意識時,它已經被裝飾了高級特徵;例如,當你看着一隻可愛的小狗的照片時,你不能選擇不看這隻小狗,或不注意它的可愛。你也不能解釋你如何認出這是一隻可愛的小狗,這對你來說很明顯。因此,我們不能相信我們的主觀經驗:感知並不是微不足道的,理解它我們必須看看感官模塊是如何工作的。

​卷積神經網絡(CNN)是從大腦視覺皮層的研究中出現的,自 20 世紀 80 年代以來它們一直用於圖像識別。在過去的幾年裏,由於計算能力的增加,可用訓練數據的數量以及第 11 章介紹的訓練深度網絡的技巧,CNN 致力於在某些複雜的視覺任務中做出超出人類的表現。他們使圖像搜索服務,自動駕駛汽車,視頻自動分類系統等變得強大。此外,CNN 並不侷限於視覺感知:它們在其他任務中也很成功,如語音識別或自然語言處理(NLP); 然而,我們現在將專注於視覺應用。

​在本章中,我們將介紹 CNN 的起源,構建它們模塊的外觀以及如何使用 TensorFlow 實現它們。然後我們將介紹一些最好的 CNN 架構。

視覺皮層的結構

​David H.Hubel 和 Torsten Wiesel 在 1958 年和 1959 年對貓進行了一系列實驗(以及幾年後在猴子上的實驗),對視覺皮層的結構提供了重要的見解(1981 年作者因此獲得了諾貝爾生理和醫學獎)。具體來說,他們發現視皮層中的許多神經元有一個小的局部感受野,這意味着它們只對位於視野中有限的一部分區域的視覺刺激起作用(見圖 13-1,五個神經元的局部感受野由虛線圓圈表示)。不同神經元的感受野可能重疊,並且它們一起平鋪了整個視野。此外,作者表明,一些神經元只對水平線方向的圖像作出反應,而另一些神經元只對不同方向的線作出反應(兩個神經元可能具有相同的感受野,但對不同方向的線作出反應)。他們還注意到一些神經元具有較大的感受野,並且它們對較複雜的模式作出反應,這些模式是較低層模式的組合。這些觀察結果讓我們想到:更高級別的神經元是基於相鄰低級神經元的輸出(在圖 13-1 中,請注意,每個神經元只與來自前一層的少數神經元相連)。這個強大的結構能夠檢測視野中任何區域的各種複雜圖案。

圖13-1 視覺皮層的局部感受野

​這些對視覺皮層的研究啓發了 1980 年推出的新認知機(neocognitron),後者逐漸演變爲我們現在稱之爲卷積神經網絡。一個重要的里程碑是 Yann LeCun,LéonBottou,Yoshua Bengio 和 Patrick Haffner 於 1998 年發表的一篇論文,該論文引入了著名的 LeNet-5 架構,廣泛用於識別手寫支票號碼。這個架構有一些你已經知道的構建塊,比如完全連接層和 Sigmoid 激活函數,但是它還引入了兩個新的構建塊:卷積層和池化層。現在我們來看看他們。

卷積層

​CNN 最重要的組成部分是卷積層:第一卷積層中的神經元不是連接到輸入圖像中的每一個像素(就像它們在前面的章節中那樣),而是僅僅連接到它們的局部感受野中的像素(參見圖 13-2)。 進而,第二卷積層中的每個神經元只與位於第一層中的小矩形內的神經元連接。 這種架構允許網絡專注於第一隱藏層中的低級特徵,然後將其組裝成下一隱藏層中的高級特徵,等等。 這種層次結構在現實世界的圖像中是很常見的,這也是 CNN 在圖像識別方面效果很好的原因之一。

圖13-2 有方形局部感受野的CNN層

注意: 到目前爲止,我們所看到的所有多層神經網絡都有由一長串神經元組成的層,在輸入到神經網絡之前我們必須將輸入圖像壓縮成 1D。 現在,每個圖層都以 2D 表示,這使得神經元與其相應的輸入進行匹配變得更加容易。

​位於給定層的第i行第j列的神經元連接到位於前一層中的神經元的輸出的第i行到第 i + fh – 1 行,第j列到第 j + fw – 1 列。fhfw是局部感受野的高度和寬度(見圖 13-3)。 爲了使圖層具有與前一圖層相同的高度和寬度,通常在輸入周圍添加零,如圖所示。 這被稱爲零填充.

圖13-3 圖層和零填充層的連接

​如圖 13-4 所示,通過將局部感受野隔開,還可以將較大的輸入層連接到更小的層。 兩個連續的感受野之間的距離被稱爲步幅。 在圖中,一個5×7的輸入層(加零填充)連接到一個3×4層,使用3×3的卷積核和一個步幅爲 2(在這個例子中,步幅在兩個方向是相同的,但是它並不一定總是如此)。 位於上層第i行第j列的神經元與位於前一層中的神經元的輸出連接的第 i × sh 至 i × sh + fh – 1 行,第 j × sw + fw – 1 列, ShSw是垂直和水平的步幅。

圖13-4 用步幅降維

過濾器

​神經元的權重可以表示爲局部感受野大小的小圖像。 例如,圖 13-5 顯示了兩個可能的權重集,稱爲過濾器(或卷積核)。第一個表示爲中間有一條垂直的白線的黑色正方形(除了中間一列外,這是一個充滿 0 的7×7矩陣,除了中央垂直線是 1)。 使用這些權重的神經元會忽略除了中央垂直線以外感受野的一切(因爲除位於中央垂直線以外,所有的輸入都將乘 0)。第二個卷積核是一個黑色的正方形,中間有一條水平的白線。 再一次,使用這些權重的神經元將忽略除了中心水平線之外的局部感受野中的一切。

圖13-5 用兩個過濾器得到兩張特徵映射

​現在,如果一個圖層中的所有神經元都使用相同的垂直線卷積核(以及相同的偏置項),並且將網絡輸入到圖 13-5(底部圖像)中所示的輸入圖像,則該圖層將輸出左上圖像。 請注意,垂直的白線得到增強,其餘的變得模糊。 類似地,如果所有的神經元都使用水平線卷積核,右上角的圖像就是你所得到的。 注意到水平的白線得到增強,其餘的則被模糊了。因此,使用相同卷積和的一個充滿神經元的圖層將爲您提供一個特徵映射,該特徵映射突出顯示圖像中與卷積和最相似的區域。 在訓練過程中,CNN 爲其任務找到最有用的卷積和,並學習將它們組合成更復雜的模式(例如,交叉是圖像中垂直卷積和和水平卷積和都激活的區域)。

疊加的多個特徵映射

​到目前爲止,爲了簡單起見,我們已經將每個卷積層表示爲一個薄的二維層,但是實際上它是由幾個相同大小的特徵映射組成的,所以使用3D圖表示其會更加準確(見圖 13-6)。 在一個特徵映射中,所有神經元共享相同的參數(權重和偏置,權值共享),但是不同的特徵映射可能具有不同的參數。 神經元的感受野與前面描述的相同,但是它延伸到所有先前的層的特徵映射。 簡而言之,卷積層同時對其輸入應用多個卷積核,使其能夠檢測輸入中的任何位置的多個特徵。

圖13-6 有多個特徵映射和三通道圖片的卷積層

注意: ​事實上,特徵映射中的所有神經元共享相同的參數會顯着減少模型中的參數數量,但最重要的是,一旦 CNN 學會識別一個位置的模式,就可以在任何其他位置識別它。 相比之下,一旦一個常規 DNN 學會識別一個位置的模式,它只能在該特定位置識別它。

​而且,輸入圖像也由多個子圖層組成:每個顏色通道一個。 通常有三種:紅色,綠色和藍色(RGB)。 灰度圖像只有一個通道,但是一些圖像可能更多 - 例如捕捉額外光頻(如紅外線)的衛星圖像。

​具體地,位於給定卷積層L中的特徵映射k的i行,j列中的神經元連接到前一層(L-1)位於 i × sw 到 i × sw + fw – 1 行,j × sh 到 j × sh + fh – 1 列的神經元的輸出。請注意,位於同一行第i列和第j列但位於不同特徵映射中的所有神經元都連接到上一層中完全相同神經元的輸出。

​公式 13-1 用一個大數學公式對前面的內容作了解釋:它展示瞭如何計算卷積層中給定神經元的輸出。它是計算所有輸入的加權總和,並且加上偏置項。

公式13-1 計算卷積層中一個神經元的輸出

  • zi, j, k 是卷積層(L層)特徵映射k中位於第i行第j列的神經元的輸出。
  • 如前所述,sh 和 sw 是垂直和水平的步幅,fh 和 fw 是感受野的高度和寬度,fn′ 是前一層(第l - 1層)的特徵映射的數量。
  • xi′, j′, k′ 是位於層L-1i'行,j'列,特徵映射k'(或者如果前一層是輸入層的通道k')的神經元的輸出。
  • bk 是特徵映射k的偏置項(在L層中)。您可以將其視爲調整特徵映射k的整體亮度的旋鈕。
  • wu, v, k′ ,k 是層L的特徵映射k中的任何神經元與位於行u,列v(相對於神經元的感受野)的輸入之間的連接權重,以及特徵映射k'。

TensorFlow 實現

​在 Tensorflow 中,每個輸入圖像的通常被表示爲三維張量 shape [height, width, channels]。 一個小批次被表示爲四維張量 shape [mini-batch size, height, width, channels]。 卷積層的權重被表示爲四維張量 [fh, fw, fn, fn′]。 卷積層的偏差項簡單地表示爲一維形狀的張量 [fn]。我們來看一個簡單的例子。 下面的代碼使用 Scikit-Learnload_sample_images()(加載兩個彩色圖像,一箇中國廟宇,另一個是一朵花)加載兩個樣本圖像。 然後創建兩個7×7的卷積核(一箇中間是垂直的白線,另一個是水平的白線),並將他們應用到兩張圖形中,使用 TensorFlow 的conv2d()函數構建的卷積圖層(使用零填充且步幅爲 2)。 最後,繪製其中一個結果特徵映射(類似於圖 13-5 中的右上圖)。

from sklearn.datasets import load_sample_image
import matplotlib.pyplot as plt
import numpy as np
import tensorflow as tf

if __name__ == '__main__':

    # Load sample images
    china = load_sample_image("china.jpg")
    flower = load_sample_image("flower.jpg")
    dataset = np.array([china, flower], dtype=np.float32)
    batch_size, height, width, channels = dataset.shape

    # Create 2 filters
    filters = np.zeros(shape=(7, 7, channels, 2), dtype=np.float32)
    filters[:, 3, :, 0] = 1  # vertical line
    filters[3, :, :, 1] = 1  # horizontal line

    # Create a graph with input X plus a convolutional layer applying the 2 filters
    X = tf.placeholder(tf.float32, shape=(None, height, width, channels))
    convolution = tf.nn.conv2d(X, filters, strides=[1,2,2,1], padding="SAME")

    with tf.Session() as sess:
        output = sess.run(convolution, feed_dict={X: dataset})

    plt.imshow(output[0, :, :, 1], cmap="gray") # plot 1st image's 2nd feature map
    plt.show()

大部分代碼是不言而喻的,但conv2d()這一行值得解釋一下:

  • X是輸入小批次(4D 張量,如前所述)
  • 卷積核是應用的一組卷積核(也是一個 4D 張量,如前所述)。
  • 步幅是一個四元素的一維數組,其中兩個中間的值是垂直和水平的步幅(shsw)。 第一個和最後一個元素現在必須等於 1。他們可能有一天會被用來指定批量步長(跳過一些實例)和頻道步幅(跳過上一層的特徵映射或通道)。
  • padding必須是"VALID""SAME"
    • 如果設置爲"VALID",卷積層不使用零填充,並且可能會忽略輸入圖像底部和右側的某些行和列,具體取決於步幅,如圖 13-7 所示(爲簡單起見, 這裏只顯示水平尺寸,當然,垂直尺寸也適用相同的邏輯)
    • 如果設置爲"SAME",則卷積層在必要時使用零填充。 在這種情況下,輸出神經元的數量等於輸入神經元的數量除以該步幅,向上舍入(在這個例子中,ceil(13/5)= 3)。 然後在輸入周圍儘可能均勻地添加零。

圖13-7 填充選項 —— 輸入寬度13,過濾寬度6,步幅5

不幸的是,卷積圖層有很多超參數:你必須選擇卷積核的數量,高度和寬度,步幅和填充類型。 與往常一樣,您可以使用交叉驗證來查找正確的超參數值,但這非常耗時。 稍後我們將討論常見的 CNN 體系結構,以便讓您瞭解超參數值在實踐中的最佳工作方式。

內存需求

​CNN 的另一個問題是卷積層需要大量的 RAM,特別是在訓練期間,因爲反向傳播需要在正向傳遞期間計算的所有中間值。

​例如,考慮具有5×5卷積核的卷積層,輸出 200 個尺寸爲150×100的特徵映射,步長爲 1,使用SAME填充。 如果輸入是150×100RGB 圖像(三個通道),則參數的數量是(5×5×3 + 1)×200 = 15,200+1對應於偏置項),這跟全連接層比較是相當小的。(具有150×100神經元的全連接層,每個連接到所有150×100×3輸入,將具有150 ^ 2×100 ^ 2×3 = 675,000,000個參數!)然而,200 個特徵映射中的每一個包含150×100個神經元,並且這些神經元中的每一個都需要計算其5×5×3 = 75個輸入的權重和:總共 2.25 億次浮點乘法。不像全連接層那麼糟糕,但仍然是計算密集型的。 而且,如果使用 32 位浮點數來表示特徵映射,則卷積層的輸出將佔用 RAM 的200×150×100×32 = 9600萬位(大約 11.4MB)。 這只是一個例子! 如果訓練批次包含 100 個實例,則該層將佔用超過 1 GB 的 RAM!

​在推理過程中(即對新實例進行預測時),一旦下一層計算完畢,一層所佔用的 RAM 就可以被釋放,因此只需要兩個連續層所需的 RAM 數量。 但是在訓練期間,在正向傳遞期間計算的所有內容都需要被保留用於反向傳遞,所以所需的 RAM 量(至少)是所有層所需的 RAM 總量。

提示: ​如果由於內存不足錯誤導致訓練崩潰,則可以嘗試減少小批量大小。 或者,您可以嘗試使用步幅降低維度,或者刪除幾個圖層。 或者你可以嘗試使用 16 位浮點數而不是 32 位浮點數。 或者你可以在多個設備上分發 CNN。

池化層

​一旦你理解了卷積層是如何工作的,池化層很容易掌握。 他們的目標是對輸入圖像進行二次抽樣(即收縮)以減少計算負擔,內存使用量和參數數量(從而限制過度擬合的風險)。 減少輸入圖像的大小也使得神經網絡容忍一點點的圖像變換(位置不變)。

​就像在卷積圖層中一樣,池化層中的每個神經元都連接到前一層中有限數量的神經元的輸出,位於一個小的矩形感受野內。 您必須像以前一樣定義其大小,跨度和填充類型。 但是,彙集的神經元沒有權重; 它所做的只是使用聚合函數(如最大值或平均值)來聚合輸入。 圖 13-8 顯示了最大池層,這是最常見的池化類型。 在這個例子中,我們使用一個2×2的核,步幅爲 2,沒有填充。 請注意,只有每個核中的最大輸入值纔會進入下一層。 其他輸入被丟棄。

圖13-8 最大池化層(2x2的池化內核,步幅是2,沒有填充)

​這顯然是一個非常具有破壞性的層:即使只有一個2×2的核和 2 的步幅,輸出在兩個方向上都會減小兩倍(所以它的面積將減少四倍),一下減少了 75% 的輸入值

​池化層通常獨立於每個輸入通道工作,因此輸出深度與輸入深度相同。 接下來可以看到,在這種情況下,圖像的空間維度(高度和寬度)保持不變,但是通道數目可以減少。

​在 TensorFlow 中實現一個最大池層是非常容易的。 以下代碼使用2×2核創建最大池化層,步幅爲2,沒有填充,然後將其應用於數據集中的所有圖像:

import numpy as np
from sklearn.datasets import load_sample_image
import tensorflow as tf
import matplotlib.pyplot as plt

china = load_sample_image("china.jpg")
flower = load_sample_image("flower.jpg")

dataset = np.array([china, flower], dtype=np.float32)
batch_size, height, width, channels = dataset.shape

# Create 2 filters
filters = np.zeros(shape=(7, 7, channels, 2), dtype=np.float32)
filters[:, 3, :, 0] = 1  # vertical line
filters[3, :, :, 1] = 1  # horizontal line

X = tf.placeholder(tf.float32, shape=(None, height, width, channels))
max_pool = tf.nn.max_pool(X, ksize=[1,2,2,1], strides=[1,2,2,1],padding="VALID")

with tf.Session() as sess:
    output = sess.run(max_pool, feed_dict={X: dataset})

plt.imshow(output[0].astype(np.uint8))  # plot the output for the 1st image
plt.show()

ksize參數包含沿輸入張量的所有四維的核形狀:[min-batch, height, width, channels]。 TensorFlow 目前不支持在多個實例上合併,因此ksize的第一個元素必須等於 1。此外,它不支持在空間維度(高度和寬度)和深度維度上合併,因此ksize[1]ksize[2]都必須等於 1,否則ksize[3]必須等於 1。

​要創建一個平均池化層,只需使用avg_pool()函數而不是max_pool()

​現在你知道所有的構建模塊來創建一個卷積神經網絡。 我們來看看如何組裝它們。

CNN 架構

​典型的 CNN 體系結構有一些卷積層(每一個通常跟着一個 ReLU 層),然後是一個池化層,然後是另外幾個卷積層(+ ReLU),然後是另一個池化層,等等。 隨着網絡的進展,圖像變得越來越小,但是由於卷積層的緣故,圖像通常也會越來越深(即更多的特徵映射)(見圖 13-9)。 在堆棧的頂部,添加由幾個全連接層(+ ReLU)組成的常規前饋神經網絡,並且最終層輸出預測(例如,輸出估計類別概率的 softmax 層)。

圖13-9 典型的CNN架構

提示: ​一個常見的錯誤是使用太大的卷積核。 通常可以通過將兩個3×3內核堆疊在一起來獲得與9×9內核相同的效果,計算量更少。

​多年來,這種基礎架構的變體已經被開發出來,導致了該領域的驚人進步。 這種進步的一個很好的衡量標準是比賽中的錯誤率,比如 ILSVRC ImageNet(http://image-net.org) 的挑戰。 在這個比賽中,圖像分類的五大誤差率在五年內從 26% 下降到僅僅 3% 左右。 前五位錯誤率是系統前5位預測未包含正確答案的測試圖像的數量。 圖像很大(256 像素),有 1000 個類,其中一些非常微妙(嘗試區分 120 個狗的品種)。 查看獲獎作品的演變是瞭解 CNN 如何工作的好方法。

​我們先來看看經典的 LeNet-5 架構(1998 年),然後是 ILSVRC 挑戰賽的三名獲勝者 AlexNet(2012),GoogLeNet(2014)和 ResNet(2015)。

在其他視覺任務中,如物體檢測和定位以及圖像分割,也取得了驚人的進展。 在物體檢測和定位中,神經網絡通常輸出圖像中各種物體周圍的一系列邊界框。 例如,參見Maxine Oquab 等人的 2015 年論文,該論文爲每個客體類別輸出熱圖,或者 Russell Stewart 等人的 2015 年論文,該論文結合使用 CNN 來檢測人臉,並使用遞歸神經網絡來輸出 圍繞它們的一系列邊界框。 在圖像分割中,網絡輸出圖像(通常與輸入大小相同),其中每個像素指示相應輸入像素所屬的對象的類別。 例如,查看 Evan Shelhamer 等人的 2016 年論文。

LeNet-5

​LeNet-5 架構也許是最廣爲人知的 CNN 架構。 如前所述,它是由 Yann LeCun 於 1998 年創建的,廣泛用於手寫數字識別(MNIST)。 它由表 13-1 所示的層組成。

表13-1 LeNet-5架構

有一些額外的細節要注意:

  • MNIST 圖像是28×28像素,但是它們被零填充到32×32像素,並且在被輸入到網絡之前被歸一化。 網絡的其餘部分不使用任何填充,這就是爲什麼隨着圖像在網絡中的進展,大小不斷縮小。
  • 平均池化層比平常稍微複雜一些:每個神經元計算輸入的平均值,然後將結果乘以一個可學習的係數(每個特徵映射一個),並添加一個可學習的偏差項(每個特徵映射一個),然後最後應用激活函數。
  • C3 圖中的大多數神經元僅在三個或四個 S2 圖(而不是全部六個 S2 圖)中連接到神經元。有關詳細信息,請參閱原始論文中的表 1。
  • 輸出層有點特殊:每個神經元不是計算輸入和權向量的點積,而是輸出其輸入向量和其權向量之間的歐幾里德距離的平方。 每個輸出測量圖像屬於特定數字類別的多少。 交叉熵損失函數現在是首選,因爲它更多地懲罰不好的預測,產生更大的梯度,從而更快地收斂。

Yann LeCun 的網站(http://yann.lecun.com)(“LENET”部分)展示了 LeNet-5 分類數字的很好的演示。

AlexNet

​AlexNet CNN 架構贏得了 2012 年的 ImageNet ILSVRC 挑戰賽:它達到了 17% 的 top-5 的錯誤率,而第二名錯誤率有 26%! 它由 Alex Krizhevsky(因此而得名),Ilya Sutskever 和 Geoffrey Hinton 開發。 它與 LeNet-5 非常相似,只是更大更深,它是第一個將卷積層直接堆疊在一起,而不是在每個卷積層頂部堆疊一個池化層。 表 13-2 介紹了這種架構。

表13-2 AlexNet架構

​爲了減少過擬合,作者使用了前面章節中討論的兩種正則化技術:首先他們在訓練期間將丟失率(dropout 率爲 50%)應用於層 F8 和 F9 的輸出。其次,他們通過隨機對訓練圖像進行各種偏移,水平翻轉和改變照明條件來進行數據增強。

​AlexNet 還在層 C1 和 C3 的 ReLU 步驟之後立即使用競爭標準化步驟,稱爲局部響應標準化(local response normalization)。 這種標準化形式使得在相同的位置的神經元被最強烈的激活但是在相鄰的特徵映射中抑制神經元(在生物神經元中觀察到了這種競爭激活)。 這鼓勵不同的特徵映射特殊化,迫使它們分開,並讓他們探索更廣泛的特徵,最終提升泛化能力。 公式 13-2 顯示瞭如何應用 LRN。

公式13-2 局部響應標準化

  • bi 是位於特徵映射i的神經元的標準化輸出,在某行u和列v(注意,在這個等式中我們只考慮位於這個行和列的神經元,所以uv沒有顯示)。
  • ai 是在 ReLU 步驟之後,但在歸一化之前的那個神經元的激活。
  • kαβr是超參數。 k稱爲偏置,r稱爲深度半徑。
  • fn 是特徵映射的數量。

​例如,如果r = 2且神經元具有強激活,則將抑制位於其上下的特徵映射中的神經元的激活。

​在 AlexNet 中,超參數設置如下:r = 2α= 0.00002β= 0.75k = 1。這個步驟可以使用 TensorFlow 的local_response_normalization()操作來實現。

​AlexNet 的一個名爲 ZF Net 的變體由 Matthew Zeiler 和 Rob Fergus 開發,贏得了 2013 年 ILSVRC 的挑戰。 它本質上是 AlexNet,只做了一些超參數調整(特徵映射的數量,內核大小,步幅等)。

GoogLeNet

​GoogLeNet 架構是由 Christian Szegedy 等人開發的。 來自 Google Research,通過低於 7% 的 top-5 錯誤率,贏得了 ILSVRC 2014 的挑戰賽。 這個偉大的表現很大程度上因爲它比以前的 CNN 網絡更深(見圖 13-11)。 這是通過稱爲初始模塊(inception modules)的子網絡實現的,這使得 GoogLeNet 比以前的架構更有效地使用參數:實際上,GoogLeNet 的參數比 AlexNet 少了 10 倍(約 600 萬而不是 6000 萬)。

圖13-10 初始化模塊

​初始模塊的架構如圖 13-10 所示。 符號3×3 + 2(S)表示該層使用3×3內核,步幅 2 和SAME填充。 輸入信號首先被複制並饋送到四個不同的層。 所有卷積層都使用 ReLU 激活功能。 請注意,第二組卷積層使用不同的內核大小(1×13×35×5),允許它們以不同的比例捕獲圖案。 還要注意,每一層都使用了跨度爲1和SAME填充的(即使是最大的池化層),所以它們的輸出全都具有與其輸入相同的高度和寬度。這使得將所有輸出在最後的深度連接層(depth concat layer)上沿着深度方向堆疊成爲可能(即,堆疊來自所有四個頂部卷積層的特徵映射)。 這個連接層可以在 TensorFlow 中使用concat()操作實現,其中axis = 3(軸 3 是深度)。

​您可能想知道爲什麼初始模塊具有1×1內核的卷積層。 當然這些圖層不能捕獲任何功能,因爲他們一次只能看一個像素? 實際上,這些層次有兩個目的:

​首先,它們被配置爲輸出比輸入少得多的特徵映射,所以它們作爲瓶頸層,意味着它們降低了維度。 在3×35×5卷積之前,這是特別有用的,因爲這些在計算上是非常耗費內存的層。

​其次,每一個卷積層對([1 × 1, 3 × 3][1 × 1, 5 × 5]表現地像一個強大的卷積層,可以捕捉到更多的複雜的模式。事實上,這一對卷積層不是在圖像上掃過一個簡單的線性分類器(就像單個卷積層一樣),而是在圖像上掃描一個雙層神經網絡。

​簡而言之,您可以將整個初始模塊視爲類固醇卷積層,能夠輸出捕捉各種尺度複雜模式的特徵映射。

警告: ​每個卷積層的卷積核的數量是一個超參數。 不幸的是,這意味着你有六個超參數來調整你添加的每個初始層。

圖13-11 GoogLeNet CNN 架構

​現在讓我們來看看 GoogLeNet CNN 的架構(見圖 13-11)。 它非常深,我們不得不將它分成三列,但是 GoogLeNet 實際上是一列,包括九個初始模塊(帶有旋轉頂端的框),每個模塊實際上包含三層。每個卷積層和池化層輸出的特徵映射的數量顯示在內核大小前。 初始模塊中的六個數字表示模塊中每個卷積層輸出的特徵映射的數量(與圖 13-10 中的順序相同)。 請注意,所有的卷積層都使用 ReLU 激活函數。

讓我們來過一遍這個網絡:

  • 前兩層將圖像的高度和寬度除以 4(使其面積除以 16),以減少計算負擔。
  • 然後,局部響應標準化層確保前面的層學習各種各樣的功能(如前所述)
  • 接下來是兩個卷積層,其中第一個像瓶頸層一樣。 正如前面所解釋的,你可以把這一對看作是一個單一的更智能的卷積層。
  • 再次,局部響應標準化層確保了先前的層捕捉各種各樣的模式。
  • 接下來,最大池化層將圖像高度和寬度減少 2,再次加快計算速度。
  • 然後是九個初始模塊的堆疊,與幾個最大池層交織,以降低維度並加速網絡。
  • 接下來,平均池化層使用具有VALID填充的特徵映射的大小的內核,輸出1×1特徵映射:這種令人驚訝的策略被稱爲全局平均池化。 它有效地強制以前的圖層產生特徵映射,這些特徵映射實際上是每個目標類的置信圖(因爲其他類型的功能將被平均步驟破壞)。 這樣在 CNN 的頂部就不必有有幾個全連接層(如 AlexNet),大大減少了網絡中的參數數量,並減少了了過度擬合的風險。
  • 最後一層是不言自明的:正則化 drop out,然後是具有 softmax 激活函數的完全連接層來輸出估計類的概率。

​這個圖略有簡化:原來的 GoogLeNet 架構還包括兩個插在第三和第六個初始模塊之上的輔助分類器。 它們都由一個平均池層,一個卷積層,兩個全連接層和一個 softmax 激活層組成。 在訓練期間,他們的損失(縮小了 70%)加在了整體損失上。 目標是解決消失梯度問題,正則化網絡。 但是,結果顯示其效果相對小。

ResNet

​最後是,2015 年 ILSVRC 挑戰賽的贏家 Kaiming He 等人開發的 Residual Network(或 ResNet),該網絡的 top-5 誤率低到驚人的 3.6%,它使用了一個非常深的 CNN,由 152 層組成。 能夠訓練如此深的網絡的關鍵是使用跳過連接(skip connection,也稱爲快捷連接):一個層的輸入信號也被添加到位於下一層的輸出。 讓我們看看爲什麼這是有用的。

​當訓練一個神經網絡時,目標是使其模擬一個目標函數h(x)。 如果將輸入x添加到網絡的輸出中(即添加跳過連接),那麼網絡將被迫模擬f(x)= h(x) - x而不是h(x)。 這被稱爲殘留學習(見圖 13-12)。

圖13-12 殘留學習

​當你初始化一個普通的神經網絡時,它的權重接近於零,所以網絡只輸出接近零的值。 如果添加跳過連接,則生成的網絡只輸出其輸入的副本; 換句話說,它最初對身份函數進行建模。 如果目標函數與身份函數非常接近(常常是這種情況),這將大大加快訓練速度。

​而且,如果添加了許多跳轉連接,即使幾個層還沒有開始學習,網絡也可以開始進行(見圖 13-13)。 由於跳過連接,信號可以很容易地通過整個網絡。 深度剩餘網絡可以看作是一堆剩餘單位,其中每個剩餘單位是一個有跳過連接的小型神經網絡。

圖13-13 常規深度神經網絡(左)和深度殘留網絡(右)

​現在讓我們看看 ResNet 的架構(見圖 13-14)。 這實際上是令人驚訝的簡單。 它的開始和結束與GoogLeNet完全一樣(除了沒有 dropout 層),而在兩者之間只是一堆很簡單的殘餘單位。 每個殘差單元由兩個卷積層組成,使用3×3的內核和保存空間維度(步幅 1,SAME填充),批量歸一化(BN)和 ReLU 激活。

圖13-14 ResNet架構

​需要注意的是特徵映射的數量每隔幾個殘差單位會加倍,同時它們的高度和寬度減半(使用步幅 2 卷積層)。 發生這種情況時,輸入不能直接添加到剩餘單元的輸出中,因爲它們不具有相同的形狀(例如,此問題影響圖 13-14 中的虛線箭頭表示的跳過連接)。 爲了解決這個問題,輸入通過一個1×1卷積層,步長2和正確數量的輸出特徵映射(見圖 13-15)。

圖13-15 當特徵映射的大小和深度改變時跳過連接

​ResNet-34 是具有 34 個層(僅計算卷積層和完全連接層)的 ResNet,包含 3 個剩餘單元輸出 64 個特徵映射,4 個剩餘單元輸出 128 個特徵映射,6 個剩餘單元輸出 256 個特徵映射,3 個剩餘單元輸出 512 個特徵映射。

​ResNet-152 更深,使用稍微不同的剩餘單位。 他們使用三個卷積層,而不是兩個 256 個特徵映射的3×3的卷積層,它們使用三個卷積層:第一個卷積層只有 64 個特徵映射(少 4 倍),這是一個瓶頸層(已經討論過) ,然後是具有 64 個特徵映射的3×3層,最後是具有 256 個特徵映射(4×64)的另一個1×1卷積層,以恢復原始深度。ResNet-152 包含三個這樣的剩餘單位,輸出 256 個特徵映射,然後是 8 個剩餘單位,輸出 512 個特徵映射,高達 36 個剩餘單位,輸出 1024 個特徵映射,最後是 3 個剩餘單位,輸出 2048 個特徵映射。

​正如你所看到的,這個領域正在迅速發展,每年都會有各種各樣的架構出現。 一個明顯的趨勢是 CNN 越來越深入。 他們也越來越輕量,需要越來越少的參數。 目前,ResNet 架構既是最強大的,也是最簡單的,所以它現在應該是你應該使用的,但是每年都要繼續關注 ILSVRC 的挑戰。 2016 年獲獎者是來自中國的 Trimps-Soushen 團隊,他們的出錯率驚人的縮減到 2.99%。 爲了達到這個目標,他們訓練了以前模型的組合,並將它們合併爲一個整體。 根據任務的不同,降低的錯誤率可能會或可能不值得額外的複雜性。

​還有其他一些架構可供您參考,特別是 VGGNet(2014 年 ILSVRC 挑戰賽的亞軍)和 Inception-v4(將 GooLeNet 和 ResNet 的思想融合在一起,實現了接近 3% 的 top-5 誤差 ImageNet 分類率)。

​實施我們剛剛討論的各種CNN架構真的沒什麼特別的。 我們之前看到如何構建所有的獨立構建模塊,所以現在您只需要組裝它們來創建所需的構架。 我們將在即將開始的練習中構建 ResNet-34,您將在 Jupyter 筆記本中找到完整的工作代碼。

TensorFlow 卷積操作

TensorFlow 還提供了一些其他類型的卷積層:

  • conv1d()爲 1D 輸入創建一個卷積層。 例如,在自然語言處理中這是有用的,其中句子可以表示爲一維單詞陣列,並且接受場覆蓋一些鄰近單詞。
  • conv3d()創建一個 3D 輸入的卷積層,如 3D PET 掃描。
  • atrous_conv2d()創建了一個 atrous 卷積層(“àtrous”是法語“with holes”)。 這相當於使用具有通過插入行和列(即,孔)而擴大的卷積核的普通卷積層。 例如,等於[[1,2,3]]1×3卷積核可以以4的擴張率擴張,導致擴張的卷積核[[1,0,0,0,2,0,0,0,3]]。 這使得卷積層在沒有計算價格的情況下具有更大的局部感受野,並且不使用額外的參數。
  • conv2d_transpose()創建了一個轉置卷積層,有時稱爲去卷積層,它對圖像進行上採樣(這個名稱是非常具有誤導性的,因爲這個層並不執行去卷積,這是一個定義良好的數學運算(卷積的逆)) 。這是通過在輸入之間插入零來實現的,所以你可以把它看作是一個使用分數步長的普通卷積層。例如,在圖像分割中,上採樣是有用的:在典型的CNN中,特徵映射越來越小當通過網絡時,所以如果你想輸出一個與輸入大小相同的圖像,你需要一個上採樣層。
  • depthwise_conv2d()創建一個深度卷積層,將每個卷積核獨立應用於每個單獨的輸入通道。 因此,如果有fn卷積核和fn'輸入通道,那麼這將輸出fn×fn'特徵映射。
  • separable_conv2d()創建一個可分離的卷積層,首先像深度卷積層一樣工作,然後將1×1卷積層應用於結果特徵映射。 這使得可以將卷積核應用於任意的輸入通道組。

練習

(這章的負責人沒有翻譯練習題,之後我補上) ***​

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章