本教程將 主要面向代碼, 旨在幫助您 深入學習和卷積神經網絡。由於這個意圖,我 不會花很多時間討論激活功能,池層或密集/完全連接的層 - 將來會有 很多教程在PyImageSearch博客上將覆蓋 每個層類型/概念 在很多細節。
再次,本教程是您 第一個端到端的例子,您可以訓練一個現實的CNN(並在實際中看到它)。我們將在本系列帖子中稍后介紹激活功能,匯集層和完全連接層的細節(盡管您應該已經知道卷積運算的基本知識); 但是在此期間,只需跟隨,享受教訓,並 學習如何使用Python和Keras實現您的第一個卷積神經網絡。
MNIST數據集

圖1: MNIST數字識別數據集。
您可能已經看過MNIST數據集,無論是在PyImageSearch博客上還是在您研究的其他地方。在任何一種情況下,我將繼續查看數據集,以確保您 准確了解我們正在使用的數據。
MNIST數據集可以說是計算機視覺和機器學習文獻中研究最多,數據最多的數據集,它是您在深入學習之旅中使用的出色的“第一個數據集”。
注意:正如我們將會發現的那樣,即使在CPU上,在這個數據集上獲得> 98%的分類精度也很容易,訓練時間最短。
該數據集的目標是對手寫數字0-9進行分類。我們共獲得了7萬張圖像,通常有6萬張圖像用於培訓,10,000張用於評估; 但是,我們可以自由分割這些數據,因為我們認為合適。共同分拆包括標准60,000 / 10,000,75%/ 25%和66.6%/ 33.3%。我將在博客文章中使用2/3的數據進行培訓和1/3的數據進行測試。
每個數字表示為 28 x 28 灰度圖像(來自MNIST數據集的示例可以在上圖中看到)。這些灰度像素強度是無符號整數,像素值落在[0,255]的范圍內 。 所有數字都放置在 黑色背景上 ,具有淺色前景(即,數字本身)為 白色和 各種灰色。
值得注意的是,許多庫(如scikit-learn)都有內置的幫助方法來下載MNIST數據集,將其緩存到磁盤上,然后加載它。這些幫助方法通常將每個圖像表示為 784-d向量。
784號來自哪里?
簡單。這只是 平坦的 28 x 28 = 784的形象。
要從784-d矢量恢復我們的原始圖像,我們簡單地將陣列重塑為 28×28 圖像。
在本博客的上下文中,我們的目標是培訓LeNet,以便我們最大限度地提高我們的測試集的准確性。
LeNet架構

圖2: LeNet架構由兩組卷積,激活和合並層組成,后面是完全連接的層,激活,另一個完全連接,最后是一個softmax分類器(圖像源)。
LeNet架構是卷積神經網絡的一個很好的“第一架構”(特別是在MNIST數據集上進行了培訓時,手寫數字識別的圖像數據集)。
LeNet很小,易於理解 - 但足夠大,可以提供有趣的結果。此外,LeNet + MNIST的組合能夠在CPU上運行,使初學者能夠輕松地在深度學習和卷積神經網絡中邁出第一步。
在許多方面,LeNet + MNIST是“Hello,World”等同於Deep Learning的圖像分類。
LeNet架構由以下層組成:
而不是解釋每層的卷積過濾器數量,過濾器本身的大小以及現在完全連接節點的數量,我將保存這個討論,直到我們的 “使用Python和Keras實現LeNet” 部分博客文章,其中的源代碼將作為輔助解除。
同時,我們來看看我們的項目結構 - 一個結構,我們將 在以后的PyImageSearch博客文章中多次重用。
注意:原來的LeNet架構使用 TANH 激活功能而不是 RELU 。我們 在這里使用RELU的原因是因為它有更好的分類精度,因為一些很好的,理想的屬性(我將在未來的博客文章中討論)。如果您在LeNet中進行任何其他討論,您可能會看到他們使用 TANH, 而不是再想一想。
我們的CNN項目結構
在我們潛入任何代碼之前,我們先來看看我們的項目結構:
為了保持代碼的組織,我們將定義一個名為pyimagesearch的包 。在 pyimagesearch 模塊中,我們將創建一個 cnn 子模塊 - 這是我們將存儲卷積神經網絡實現的地方,以及與CNN相關的任何幫助實用程序。
看看cnn里面 ,你會看到 網絡 子模塊:這是 網絡實現本身將被存儲的地方。顧名思義,這一點。py 文件將定義一個名為LeNet的類 ,這是我們在Python + Keras中實際的LeNet實現。
該 lenet_mnist 。py 腳本將是我們的驅動程序,用於實例化LeNet網絡架構,訓練模型(或加載模型,如果我們的網絡是預先訓練的),然后評估MNIST數據集上的網絡性能。
最后, 輸出 目錄將在我們的LeNet 模型訓練完成后存儲 ,從而允許我們在后續調用lenet_mnist 時對數字進行分類 。py,而不必重新訓練網絡。
過去一年,我個人一直在使用這個項目結構(或項目結構非常相似)。 我發現它很有條理,易於擴展 - 隨着我們用更多的網絡架構和幫助功能添加到這個庫中,這將在未來的博客文章中變得更加明顯。
用Python和Keras實現LeNet
首先,我會假設你已經有Keras,scikit學習,和OpenCV的系統上安裝(和可選,啟用GPU支持)。
否則,打開 禮物。py 文件並插入以下代碼:
第2-7行處理從keras 庫導入所需的函數/類 。
所述 LeNet 類被定義在 第9行,其次是 構建 於方法 11號線。每當我定義一個新的網絡架構,我 總是將它放在自己的類中(主要用於命名空間和組織目的),然后創建一個 靜態 構建 函數。
該 構建 方法,顧名思義,需要提供的任何參數,其在 最低限度 包括:
- 輸入圖像的 寬度。
- 輸入圖像的 高度。
- 輸入圖像的 深度(即通道數)。
- 和數量 班在我們的數據集(即類標簽的唯一的號碼)。
我通常還包括一個 可用於加載預訓練模型的 權值路徑。給定這些參數, 構建 函數負責構建網絡架構。
談到構建LeNet架構時, 第13行將實例化一個 Sequential 類,我們將用它來構建網絡。
現在模型已初始化,我們可以開始添加圖層:
在 第15-19行,我們創建了第一組 CONV = > RELU = > POOL 圖層集。
我們的 CONV 層將學習20個卷積濾波器,每個濾波器的大小為 5 x 5。該值的輸入尺寸與輸入圖像的寬度,高度和深度相同(在本例中為MNIST數據集),所以我們將有 28 x 28個輸入,單個通道用於深度(灰度)。
然后,我們將在x和y方向上應用ReLU激活功能,然后 在x和 y方向上移動2 x 2的 最大值池 (假設一個2 x 2的滑動窗口,通過激活體積“滑動”,進行最大運算,同時在水平和垂直方向上采取2像素的步驟)。
注:本教程主要是基於代碼的意思是你 第一次接觸到實現卷積神經網絡-我會去到 很多更 詳細的關於卷積層,激活功能,和最大集中在未來的博客帖子層。在此期間,只需試着跟隨代碼。
我們現在准備應用我們的第二組 CONV = > RELU = > POOL 層:
這一次,我們將學習 50個卷積濾波器,而不是像上一個圖層集中的 20個卷積濾波器。
通常,在網絡的更深層次上,觀察到的CONV 濾波器數量的 增加。
接下來,我們來到LeNet架構的完全連接的層(通常稱為“密集”層):
在 第27行,我們取上一個MaxPooling2D 層的輸出, 並將其 平坦化為一個向量,使我們可以應用密集/完全連接的層。如果您有神經網絡的任何先前經驗,那么您將知道一個密集/完全連接的層是網絡中的“標准”類型的層,其中上一層中的每個節點都連接到下一層的每個節點(因此,術語“完全連接”)。
我們的完全連接的層將包含500個單位(28行),我們通過另一個非線性ReLU激活。
第32行是 非常重要的,雖然它很容易忽視 - 這一行定義了另一個 Dense 類,但接受一個變量(即,不是硬編碼)的大小。這個大小是由 類 變量表示的類標簽的 數量 。在MNIST數據集的情況下,我們有10個類(我們正在嘗試學習識別的十位數中的每個一個)。
最后,我們應用一個softmax分類器(多項式邏輯回歸),它將返回 概率列表,一個用於10個類標簽中的每一個(第33行)。具有最大概率的類標簽將被選為網絡的最終分類。
我們的最后一個代碼塊處理加載一個預先存在的 weightsPath (如果這樣一個文件存在)並將構造的模型返回給調用函數:
創建LeNet驅動程序腳本
現在我們已經使用Python + Keras實現了LeNet卷積神經網絡架構,現在是定義lenet_mnist的時候了 。py 驅動腳本將處理:
- 加載MNIST數據集。
- 將MNIST分成培訓和 測試分組 。
- 加載和編譯LeNet架構。
- 培訓網絡
- 可選地將序列化的網絡權重保存到磁盤,以便可以重用(而不必重新訓練網絡)。
- 顯示 網絡輸出的可視示例,以證明我們的實現確實正常工作。
打開你的 lenet_mnist 。py 文件並插入以下代碼:
第2-9行處理導入我們需要的Python包。注意我們如何 從cnn 和 pyimagesearch的網絡 子模塊 導入我們的 LeNet類 。
注意:如果您跟隨此博客文章並打算執行代碼, 請使用此帖子底部的“下載”部分。為了保持這個短短的簡潔,我已經省略了 __init__ 。py 更新可能會拋棄新的Python開發人員。
從那里, 第12-19行解析三個可選的命令行參數,每個參數詳細如下:
- - 保存- 模型 :指示器變量,用於指定在培訓LeNet后是否 將模型保存到磁盤。
- - 負載- 模型 :另一個指示器變量,此時間指定我們是否應該 加載從磁盤預先訓練的模型。
- - 權重 :在這種情況下 - 保存- 模型 提供的 - 權- 路徑 應該指向我們要 保存序列化的模型。而在這情況下 - 負載- 模型 提供的 - 權重 應該指向預先存在的權重文件我們的系統上的生活。
我們現在可以加載MNIST數據集並將其分為我們的培訓和測試分裂:
第25行從磁盤加載MNIST數據集。如果這是您首次 使用“MNIST Original” 字符串調用fetch_mldata函數 ,則需要下載MNIST數據集。MNIST數據集是一個55MB的文件,所以根據你的互聯網連接,這個下載可能需要幾秒到幾分鍾的時間。
在下載MNIST數據集之后,我們將 數據 從一組 784-d特征向量(即原始像素強度)重構為 28×28灰度圖像,我們可以通過網絡(30行)。
我們的 數據 矩陣現在具有形狀 (70000 ,28 ,28 ) ; 然而, 存在一個問題 --Keras假定我們將為每個圖像提供 至少 1個通道,因此我們需要在數據 陣列中添加一個額外的維度(第31行)。這一行執行后,新形狀 數據 矩陣將是: (70000 ,1 ,28 ,28 ) - ,現在適合於通過我們的LeNet架構。
最后, 第32-33行執行訓練和測試拆分,使用2/3的數據進行訓練,剩下的1/3用於測試。我們也可以將圖像從 [0,255]縮小至 [ 0,1.0 ],這是一種常見的縮放技術。
下一步是處理我們的標簽,以便它們可以與分類交叉熵損失函數一起使用:
39和40行處理我們的培訓和測試標簽(即,MNIST數據集中每個圖像的“地面真相”標簽)。
由於我們使用分類交叉熵損失函數,我們需要應用 將整數的標簽從整數轉換為 向量的to_categorical函數 ,其中每個向量范圍從 [ 0 ,類] 。該函數為每個類標簽生成一個向量 ,其中正確標簽的索引設置為 1,所有其他條目設置為 0。
在MNIST數據集的情況下,我們有10個lass標簽,因此每個標簽現在表示為 10-d向量。例如,考慮培訓標簽 “3”。應用 to_categorical 函數后,我們的向量現在看起來像:
注意,除了現在設置為1的第三個索引之外,向量中的所有條目都為零 。
我們現在准備建立我們的 LeNet 架構,可選擇從磁盤加載任何預先訓練的權重,然后訓練我們的網絡:
我們將使用隨機梯度下降(SGD)訓練我們的網絡 ,學習率為 0.01 。分類交叉熵將被用作我們的損失函數,這是在使用具有兩個以上類標簽的數據集時相當標准的選擇。然后我們的模型被編譯並加載到第45-48行的內存中 。
在這種情況下 - 負載- 模型 不提供,我們要培養我們的網絡(52號線)。
培訓我們的網絡是通過打電話來完成的 。 實例化模型的擬合方法 (第54和55行)。我們將允許我們的網絡訓練 20個紀元(表明我們的網絡將“看到”每個訓練示例共20次,以學習每個數字類的區分過濾器)。
然后我們對測試數據進行評估(59-61行),並將結果顯示給我們的終端。
接下來,我們檢查一下我們是否應該將網絡權重序列化為文件,以便我們運行 lenet_mnist。py 腳本 后續時間,無需從頭開始重新訓練網絡:
我們的最后一個代碼塊可以從我們的測試集中隨機選擇幾位數字,然后通過我們訓練有素的LeNet網絡進行分類:
對於每個隨機選擇的數字,我們使用LeNet模型(第71行)對圖像進行分類。
我們網絡的實際 預測是通過找到具有最大概率的類標簽的索引獲得的 。記住,我們的網絡將通過softmax函數返回一組概率,每個類別標簽一個,因此網絡的實際“預測”是具有最大概率的類標簽。
76-80行處理將28 x 28圖像調整 到 96 x 96 像素,以便我們可以更好地可視化,然后繪制 圖像 上的 預測 。
最后, 第82-86行將結果顯示在我們的屏幕上。
用Python和Keras訓練LeNet
要在MNIST數據集上訓練LeNet,請確保已使用 本教程底部找到的“下載”表單下載源代碼 。這個 。zip 文件包含本教程中詳細介紹的所有代碼 - 此外,此代碼的組織方式與上面詳細描述的 相同的項目結構, 確保在系統上正常運行(如果您的環境配置正確)。
下載后 。郵政編碼 存檔,您可以通過執行以下命令在MNIST上訓練LeNet:
我的機器輸出結果如下:

圖3:在我的Titan X上的MNIST數據集上訓練LeNet每個時期需要大約3秒鍾。經過20個時代,LeNet在培訓數據上達到98.90%的分類精度,測試數據的精度達到98.49%。
在我的Titan X GPU上,每個紀元需要大約3秒鍾,允許 整個訓練過程在大約60秒內完成。
只有20個時代,LeNet 在MNIST數據集上達到 98.49%的分類精度 - 在所有計算時間只有60秒的時間里,差不多!
注意:如果執行 lenet_mnist 。py 腳本在我們的CPU而不是GPU,期望每個時代的時間跳到70-90秒。您仍然可以在您的CPU上訓練LeNet,只需要一段時間。
用Python和Keras評估LeNet
下面我列出了LeNet + MNIST實現的幾個示例評估圖像:

圖4:應用LeNet對MNIST數據集中的數字進行分類。
在上述圖像中,我們可以正確地將數字分類為 “6”。
在這個圖像中,LeNet正確地將數字識別為 “2”:

圖5:在Python和Keras中實現LeNet。
下面的圖像是CNN濾波器學習的卷積濾波器的魯棒性,區分性質的一個很好的例子:這個 “6”是相當扭曲的,在數字的圓形區域之間留下了很少的差距,但LeNet仍然能夠正確分類數字:

圖6:使用LeNet和卷積神經網絡正確分類特別難讀的數字。
這是另一個圖像,這次分類嚴重偏斜的 “1”:

圖7:使用卷積神經網絡對偶數位進行正確分類。
最后,最后一個例子演示了分類“2”的LeNet模型 :

圖8:使用LeNet和Deep Learning對數字進行分類的最終示例。
Running the serialized LeNet model
After our lenet_mnist.py script finishes executing the first time (provided you supplied both--save-model and --weights ), you should now have a lenet_weights.hdf5 file in youroutput directory.
Instead of re-training our network on subsequent runs of lenet_mnist.py , we can instead load these weights and use them to classify digits.
To load our pre-trained LeNet model, just execute the following command:
I’ve included a GIF animation of LeNet used to correctly classify handwritten digits below:

圖9: LeNet的示例動畫正確分類數字。
概要
在今天的博文中,我演示了如何使用Python編程語言和Keras庫實現LeNet架構,用於深入學習。
LeNet架構是一個偉大的 “你好,世界”網絡,讓你的腳深入學習和卷積神經網絡。網絡本身很簡單,內存占用空間小,當應用於MNIST數據集時,可以在CPU或GPU上運行,使其成為實驗和學習的理想選擇,尤其是在深入學習新手時。
本教程主要以 代碼為重點,因此,我需要跳過 重要的卷積神經網絡概念的詳細評論,例如激活層,池層和密集/完全連接的層(否則這個帖子可能 很容易被5x為長)。
在將來的博客文章中,我會詳細介紹 每個這些圖層類型 - 同時,您只需熟悉代碼並嘗試自己執行。如果您感覺 真的很大膽,請嘗試調整每個卷積層的過濾器數量和過濾器尺寸,看看會發生什么!
無論如何,我希望你喜歡這篇博客文章 - 我一定會在將來做更深入的學習和圖像分類工作。