半小時學會卷積神經網絡


覺得這篇關於卷積神經網絡講的比較全面清晰易懂。

原文鏈接:https://zhuanlan.zhihu.com/p/30504700?utm_medium=social&utm_source=wechat_timeline&from=timeline&isappinstalled=0

(文中有些內容可能不是很清晰,會加上一些自己的理解,用紅色字體標出,后續不斷更新。)

 

以全連接層為基礎的深度神經網絡是深度神經網絡的基石,要說應用最廣、影響最大的深度神經網絡,那非卷積神經網絡莫屬。在本文中全面闡述卷積神經網絡。卷積神經網絡雖然發布的時間較早,但直到2006年Hilton解決深度神經網絡的訓練問題后才煥發生機。卷積神經網絡現在幾乎是圖像識別研究的標准配置。

簡單回顧卷積神經網絡的發展歷程。日本科學家福島邦彥(Kunihiko Fukushima)在1986年提出Neocognitron(神經認知機),直接啟發了后來的卷積神經網絡。Yann LeCun於1998年提出的卷積神經LeNet,首次提出了多層級聯的卷積結構,可對手寫數字進行有效識別。2012年, Alex依靠卷積神經網絡AlexNet奪得ILSVRC 2012比賽的冠軍,吹響了卷積神經網絡研究的號角。AlexNet成功應用了ReLu、Dropout、最大池化、LRN(Local Response Normalization,局部響應歸一化)、GPU加速等新技術,啟發了后續更多的技術創新,加速了卷積神經網絡和深度學習的研究。從此,深度學習研究進入蓬勃發展的新階段。2014年Google提出的GoogleNet,運用Inception Module這個可以反復堆疊高效的卷積網絡結構,獲得了當年的ImageNet ILSVRC比賽的冠軍,同年的亞軍VGGNet全程使用3×3的卷積,成功訓練了深度達19層的網絡。2015年,微軟提出了ResNet,包含殘差學習模塊,成功訓練了152層的網絡,一舉拿下當年ILSVRC比賽的冠軍。

卷積神經網絡技術的發展風起雲涌,盡管卷積神經網絡(convolutional neural network, CNN)最初是為解決計算機視覺等問題設計的,現在其應用范圍不僅僅局限於圖像和視頻領域,也可用於音頻信號等。本文主要通過卷積神經網絡在計算機視覺上應用來講解卷積神經網絡的基本原理以及如何使用PyTorch實現卷積神經網絡。

首先介紹人類視覺和計算機視覺的基本原理,計算機視覺中特征提取和選擇。然后介紹卷積神經網絡的主體思想和整體結構,並將詳細講解卷積層和池化層的網絡結構,PyTorch對這些網絡結構的支持,如何設置每一層神經網絡的配置,以及更加復雜的卷積神經網絡結構,如AlexNet, VGGNet , ResNet等。最后在MNIST數據集上通過PyTorch實現卷積神經網絡。

1 計算機視覺

1.1 人類視覺和計算機視覺

視覺是人類觀察和認識世界非常重要的手段。據統計,人類從外部世界獲取的信息約80%從視覺獲取,這既說明視覺信息量巨大,又體現了視覺功能的重要性­。同時,人類視覺是如此的功能強大,在很短的時間里,迅速地辨識視線里的物體,在人的視覺系統中,人的眼睛捕捉物體得到光信息。這些光信息經過處理,運送到大腦的視覺皮層,分析得到以下信息:有關物體的空間、色彩、形狀和紋理等。有了這些信息,大腦作出對該物體的辨識。對於人類而言,通過視覺來識別數字、識別圖片中的物體或者找出圖片中人臉的輪廓是非常簡單的任務。然而對於計算機而言,讓計算機識別圖片中的內容就不是一件容易的事情。計算機視覺希望借助計算機程序來處理、分析和理解圖片中的內容,使得計算機可以從圖片中自動識別各種不同模式的目標和對象。

在深度學習出現之前,圖像識別的一般過程,前端是特征提取,后端是模式識別算法。后端的模式識別算法包括:K近鄰算法(K-Nearest Neighbors)、支持向量機(SVM),神經網絡等。對於不同的識別場景和越來越復雜的識別目標,尋找合適的前端特征顯得尤為重要。

1.2 特征提取

對於特征提取,抽象於人的視覺原理,提取有關輪廓、色彩、紋理、空間等相關的特征。以色彩為例,它是一種現在仍然在廣泛使用的特征,稱之為顏色直方圖特征,這是一種簡單、直觀,對實際圖片顏色進行數字化表達的方式。顏色的值用RGB三原色進行表示,顏色直方圖的橫軸表示顏色的RGB值,表示該物品所有顏色的集合,縱軸表示整個圖像具有某個顏色值像素的數量,這樣,計算機就可以對圖像進行顏色表征。

                    圖1 顏色直方圖

以紋理特征為例,桔子會有凸凹不平的紋理,而蘋果的紋理則非常光滑。這種局部的紋理刻畫,如何通過特征抽象表示出來?Gabor 特征可以用來描述圖像紋理信息的特征,Gabor 濾波器的頻率和方向與人類的視覺系統類似,特別適合於紋理表示與判別。SIFT(Scale Invariant Feature Transform)特征全稱尺度不變特征變換,是一種檢測局部特征的算法,該算法通過把圖中特征點用特征向量進行描述,該特征向量具有對圖像縮放、平移、旋轉不變的特性,對於光照、仿射和投影變換也有一定的不變性。

              圖2 Garbor特征示意圖

形狀特征也是圖像特征的重要一類,HOG(Histogram of Oriented Gradients)特征就是其中一種。HOG特征是一種描述圖像局部梯度方向和梯度強度分布的特征。其核心內容是:在邊緣具體位置未知的情況下,邊緣方向的分布也可以很好地表示目標的外形輪廓。

            圖3 HOG特征檢測示意圖

上述特征提取算法提取的特征還是有局限的,盡管在顏色為黑白的數據集MNIST上的最好結果錯誤率為0.54%,但是在大型和復雜的數據ImageNet ILSVRC比賽的最好結果的錯誤率也在26%以上,而且難以突破。同時,提取的特征只在特定的場合有效,場景變化后,需要重新提取特征和調整模型參數。卷積神經網絡能夠自動提取特征,不必人為地提取特征,這樣提取的特征能夠達到更好的效果。同時,它不需要將特征提取和分類訓練兩個過程分開,在訓練的過程自動地提取特征,循環迭代,自動選取最優的特征。

1.3 數據集

卷積神經網絡的成功,計算機視覺領域的幾大數據集可謂功不可沒。在計算機視覺中有以下幾大基礎數據集。

1) MNIST

MNIST數據集是用作手寫體識別的數據集。MNIST數據集包含60000張訓練圖片,10000張測試圖片。其中每一張圖片都是0~9中的一個數字。圖片尺寸為28×28。由於數據集中數據相對比較簡單,人工標注錯誤率僅為0.2%。

    圖4 MNIST數據集樣例

2) Cifar數據集

Cifar數據集是一個圖像分類數據集。Cifar數據集分為了Cifar-10和Cifar-100兩個數據集。Cifar數據集中的圖片為32×32的彩色圖片,這些圖片是由Alex Krizhenevsky教授、Vinod Nair博士和Geoffrey Hilton教授整理的。Cifar-10數據集收集了來自10個不同種類的60000張圖片,這些種類有:飛機、汽車、鳥、貓、鹿、狗、青蛙、馬、船和卡車。在Cifar-10數據集上,人工標注的正確率為94%。

            圖5 Cifar數據集樣例

3) ImageNet數據集

ImageNet數據集是一個大型圖像數據集,由斯坦福大學的李飛飛教授帶頭整理而成。在ImageNet中,近1500萬張圖片關聯到WordNet中20000個名次同義詞集上。ImageNet每年舉行計算機視覺相關的競賽—Image Large Scale Visual Recognition Challenge(ILSVRC), ImageNet的數據集涵蓋計算機視覺的各個研究方向,其用做圖像分類的數據集是ILSVRC2012圖像分類數據集。ILSVRC2012數據集的數據和Cifar-10數據集一致,識別圖像中主要物體,其包含了來自1000個種類的120萬張圖片,每張圖片只屬於一個種類,大小從幾千字節到幾百萬字節不等。卷積神經網絡也正是在此數據集上一戰成名。

                              圖6 ImageNet數據集樣例

2 卷積神經網絡

計算機視覺作為人工智能的重要領域,在2006年后取得了很多突破性的進展。本文介紹的卷積神經網絡就是這些突破性進展背后的技術基礎。在前面章節中介紹的神經網絡每兩層的所有節點都是兩兩相連的,所以稱這種網絡結構為全連接層網絡結構。可將只包含全連接層的神經網絡稱之為全連接神經網絡。卷積神經網絡利用卷積結構減少需要學習的參數量,從而提高反向傳播算法的訓練效率。在卷積神經網絡中,第一個卷積層會直接接受圖像像素級的輸入,每一個卷積操作只處理一小塊圖像,進行卷積操作后傳遞到后面的網絡,每一層卷積都會提取數據中最有效的特征。這種方法可以提取到圖像中最基礎的特征,比如不同方向的拐角或者邊,而后進行組合和抽象成更高階的特征,因此卷積神經網絡對圖像縮放、平移和旋轉具有不變性。

                      圖7全連接神經網絡和卷積神經網絡結構示意圖

在圖像處理中,圖像是一個或多個的二維矩陣,如之前文中提到的MNIST手寫體圖片是一個28×28的二維矩陣。傳統的神經網絡都是采用全連接的方式,即輸入層到隱藏層的神經元都是全部連接的,這樣導致參數量巨大,使得網絡訓練耗時甚至難以訓練,並容易過擬合,而卷積神經網絡則通過局部連接、權值共享等方法避免這一困難。

對於一個200×200的輸入圖像而言,如果下一個隱藏層的神經元數目為10^4個,采用全連接則有200×200×10^4 = 4×10^8個權值參數,如此數目巨大的參數幾乎難以訓練;而采用局部連接,隱藏層的每個神經元僅與圖像中4×4的局部圖像相連接,那么此時的權值參數數量為4×4×10^4 = 1.6×10^5,將直接減少3個數量級。

                  圖8全連接神經網絡和局部連接網絡

 

盡管減少了幾個數量級,但參數數量依然較多。能否再進一步減少參數?方法就是權值共享。一個卷積層可以有多個不同的卷積核,而每一個卷積核都對應一個濾波后映射出的新圖像,同一個新圖像中每一個像素都來自完全相同的卷積核,就就是卷積核的權值共享。具體做法是,在局部連接中隱藏層的每一個神經元連接的是一個4×4的局部圖像,因此有4×4個權值參數,將這4×4個權值參數共享給剩下的神經元,也就是說隱藏層中4×10^4個神經元的權值參數相同,那么此時不管隱藏層神經元的數目是多少,需要訓練的參數就是這 4×4個權值參數(也就是卷積核的大小),如圖9所示。

(我覺得這里說的權值共享就是每一層神經層layer的所有神經元的權重都是一樣的,而不管隱藏層神經元的數目。當然也有同一層不同神經元節點采用權值是不同的,我倒覺得這種是大多數的。)

                  圖9 局部連接神經網絡和卷積

 

這大概就是卷積神經網絡的神奇之處,盡管只有這么少的參數,依舊有出色的性能。但是,這樣僅提取了圖像的一種特征,如果要多提取出一些特征,可以增加多個卷積核,不同的卷積核能夠得到圖像的不同映射下的特征,稱之為特征映射。如果有100個卷積核,最終的權值參數也僅為100×100=10^4個而已。另外,偏置參數也是共享的,同一種濾波器共享一個。

總結一下,卷積神經網絡的要點就是卷積層中的局部連接、權值共享、和池化層中降采樣。局部連接、權值共享和降采樣降低了參數量,使得訓練復雜度大大降低,並減輕了過擬合的風險。同時還賦予了卷積神經網絡對平移、形變、尺度的某種程度的不變性,提高了模型的泛化能力。

一般的卷積神經網絡由以下幾個層組成:卷積層,池化層,全連接層,Softmax層。這四者構成了常見的卷積神經網絡。

1.卷積層。 卷積層是一個卷積神經網絡最重要的部分,也是卷積神經網絡得名的緣由。卷積層中每一個節點的輸入是上一層神經網絡的一小塊,卷積層試圖將神經網絡中的每一個小塊進行更加深入地分析從而得到抽象程度更高的特征。

2.池化層。池化層的神經網絡不會改變三維矩陣的深度,但是它將縮小矩陣的大小。池化層操作將分辨率較高的圖片轉化為分辨率較低的圖片。

3.全連接層。 經過多輪的卷積層和池化層處理后,卷積神經網絡一般會接1到2層全連接層來給出最后的分類結果。

4.Softmax層。Softmax層主要用於分類問題。

在卷積神經網絡中用到全連接層和Softmax層在前面章節已有詳細的介紹,這里不作贅述。在下面的2.1節和2.2節中將詳細介紹卷積神經網絡中特殊的兩個網絡結構—卷積層和池化層及具體參數的計算。

2.1 卷積層

圖10顯示了卷積層神經網絡結構中最重要的部分,這個稱之為過濾器(filter)或者卷積核(kernel),在PyTorch文檔將這個結構稱為卷積核(kernel),因此在本書中將統稱這個結構為卷積核。如圖10所示,卷積核將當前層神經網絡上的一個子節點矩陣轉化為下一層神經網絡上的一個節點矩陣。

        圖10 卷積核結構示意圖

在卷積層中,卷積核所處理的節點矩陣的長、寬都是人工指定的,這個節點矩陣的尺寸稱之為卷積核的尺寸。卷積核處理的深度和當前層的神經網絡節點矩陣的深度是一致的,即便節點矩陣是三維的,卷積核的尺寸只需指定兩個維度。一般地,卷積核的尺寸是3×3和5×5。在圖10中,左邊表示輸入的數據,輸入數據的尺寸為3×32×32(注意:在PyTorch中,數據輸入形式表示3×32×32),中間表示卷積核,右邊每一個小圓點表示一共神經元,圖中有5個神經元。假設卷積核尺寸為5×5,卷積層中每個神經元會有輸入數據中3×5×5區域的權重,一共75個權重。這里再次強調下卷積核的深度必須為3,和輸入數據保持一致。

在卷積層,還需說明神經元的數量,它們的排列方式、滑動步長、以及邊界填充。

首先,神經元的數量,就是卷積層的輸出深度,形如圖10的5個神經元,該參數是用戶指定的,它和使用的濾波器數量一致。

其次,卷積核進行運算時,必須指定滑動步長。比如步長為1,說明卷積核每次移動1個像素點。當步長為2,卷積核會滑動2個像素點。滑動的操作使得輸出的數據變得更少。

最后,介紹邊界填充。邊界填充如果為0,可以保證輸入和輸出在空間上尺寸一致;如果邊界填充大於0,可以確保在進行卷積操作時,不損失邊界信息。

那么,輸出的尺寸最終如何計算?在PyTorch中,可以用一個公式來計算,就是floor((W-F+2P)/ S + 1)。其中,floor 表示下取整操作,W表示輸入數據的大小,F表示卷積層中卷積核的尺寸,S表示步長,P表示邊界填充0的數量。比如輸入是5×5,卷積核是3×3,步長是1,填充的數量是0,那么根據公式,就能得到(3+2×0)/ 1 + 1 = 3,輸出的空間大小為3×3;如果步長為2,那么(3+2×0)/ 2 + 1 = 2,輸出的空間大小為2×2。

在圖11中,以一維空間來說明卷積操作,右上角表示神經網絡的權重,其中輸入數據的大小為5,卷積核的大小為3;左邊表示滑動步長為1,且填充也為1;右邊表示滑動步長為2,填充為1。

(這里其實是下面是輸入數據,經過卷積核處理后得到上面的輸出,與上面解釋稍有不同。)

                        圖11一維空間上的卷積操作

在PyTorch中, 類nn.Conv2d()是卷積核模塊。卷積核及其調用例子如下:

nn.Conv2d(in_channels, out_channels, kernel_size, stride=1, padding=0,dilation=1,groups=1, bias=True)

一般地,其調用如下:

# With square kernels and equal stride m = nn.Conv2d(16, 33, 3, stride=2) # non-square kernels and unequal stride and with padding m = nn.Conv2d(16, 33, (3, 5), stride=(2, 1), padding=(4, 2)) # non-square kernels and unequal stride and with padding and dilation m = nn.Conv2d(16, 33, (3, 5), stride=(2, 1), padding=(4, 2), dilation=(3, 1)) nput = autograd.Variable(torch.randn(20, 16, 50, 100)) output = m(input) 

nn.Conv2d中參數含義:in_channels表示輸入數據體的深度;out_channels表示輸出數據體的深度;kernel_size 表示卷積核的大小;stride表示滑動的步長;padding表示邊界0填充的個數;dilation表示輸入數據體的空間間隔;groups 表示輸入數據體和輸出數據體在深度上的關聯;bias 表示偏置。

2.2 池化層

通常會在卷積層后面插入池化層,其作用是逐漸降低網絡的空間尺寸,達到減少網絡中參數的數量,減少計算資源的使用的目的,同時也能有效地控制過擬合。

池化層一般有兩次方式:Max Pooling 和 Mean Pooling。以下以Max Pooling來說明池化層的具體內容。池化層操作不改變模型的深度,對輸入數據體在深度上的切片作為輸入,不斷地滑動窗口,取這些窗口的最大值作為輸出結果,減少它的空間尺寸。池化層的效果如圖12所示。

               圖12池化層的處理效果

圖13說明池化層的具體計算,以窗口大小是2,滑動步長是2為例:每次都從2x2的窗口中選擇最大的數值,同時每次滑動2個步長進入新的窗口。

                                  圖13池化層計算

池化層為什么有效?圖片特征具有局部不變性,也就是說,即便通過下采樣也不會丟失圖片擁有的特征。由於這種特性,可以將圖片縮小再進行卷積處理,這樣能夠大大地降低卷積計算的時間。最常用的池化層尺寸是2x2,滑動步長為2,對圖像進行下采樣,將其中75%的信息丟棄,選擇其中最大的保留下來,這樣也達到去除一些噪聲信息的目的。

在PyTorch中,池化層是包括在類nn.MaxPool2d和nn.AvgPoo2d。下面介紹一下nn.MaxPool2d及其調用例子。其調用如下

nn.MaxPool2d(kernel_size, stride=None, padding=0, dilation=1,

return_indices=False,ceil_mode=False)

一般地,用法可如下

# pool of square window of size=3, stride=2 m = nn.MaxPool2d(3, stride=2) # pool of non-square window m = nn.MaxPool2d((3, 2), stride=(2, 1)) input = autograd.Variable(torch.randn(20, 16, 50, 32)) output = m(input) 

nn.MaxPool2d中各個參數的含義。其中,kernel_size, stride,padding, dilation在nn.Conv2d中已經解釋過。return_indices表示是否返回最大值所處的下標;ceil_model表示使用方格代替層結構。

2.3 經典卷積神經網絡

下面講述三種經典的卷積神經網絡:LeNet 、AlexNet 、VGGNet。這三種卷積神經網絡的結構不算特別復雜,有興趣的也可以去了解下GoogleNet和ResNet。

1. LeNet

LeNet具體指的是LeNet-5。LeNet-5模型是Yann LeCun教授於1998年在論文Gradient-based
learning applied to document recognition中提出的,它是第一個成功應用於數字識別問題的卷積神經網絡。在MNIST數據集上,LeNet-5模型可以達到大約99.2%的正確率。LeNet-5模型總共有7層,包括有2個卷積層,2個池化層,2個全連接層和一個輸出層,圖14展示了LeNet-5模型的架構。

                圖14 LeNet-5模型結構圖

論文提出的LeNet-5模型中,卷積層和池化層的實現與PyTorch的實現有細微的區別,這里不過多的討論具體細節。

class LeNet(nn.Module): def __init__(self): super(LeNet, self).__init__() self.conv1 = nn.Conv2d(3, 6, 5) self.conv2 = nn.Conv2d(6, 16, 5) self.fc1 = nn.Linear(16*5*5, 120) self.fc2 = nn.Linear(120, 84) self.fc3 = nn.Linear(84, 10) def forward(self, x): out = F.relu(self.conv1(x)) out = F.max_pool2d(out, 2) out = F.relu(self.conv2(out)) out = F.max_pool2d(out, 2) out = out.view(out.size(0), -1) out = F.relu(self.fc1(out)) out = F.relu(self.fc2(out)) out = self.fc3(out) return out 

2.AlexNet

2012年, Hilton的學生Alex Krizhevsky提出了卷積神經網絡模型AlexNet。AlexNet在卷積神經網絡上成功地應用了Relu,Dropout和LRN等技巧。在ImageNet競賽上,AlexNet以領先第二名10%的准確率而奪得冠軍。成功地展示了深度學習的威力。它的網絡結構如下:

                                        圖15 AlexNet模型結構圖

 

圖15看起來有點復雜,這是由於當時GPU計算能力不強,AlexNet使用了兩個GPU並行計算,現在可以用一個GPU替換。以單個GPU的AlexNet模型為例,包括有:5個卷積層,3個池化層,3個全連接層。其中卷積層和全連接層包括有relu層,在全連接層中還有dropout層。具體參數的配置可以參看圖16。

  圖16 AlexNet 網絡結構精簡版

具體參數的配置可以參看具體的PyTorch源碼。

下面給出PyTorch的程序實現AlexNet模型的卷積神經網絡。

class AlexNet(nn.Module): def __init__(self, num_classes): super(AlexNet, self).__init__() self.features = nn.Sequential( nn.Conv2d(3, 96, kernel_size=11, stride=4, padding=2), nn.ReLU(inplace=True), nn.MaxPool2d(kernel_size=3, stride=2), nn.Conv2d(64, 256, kernel_size=5, padding=2), nn.ReLU(inplace=True), nn.MaxPool2d(kernel_size=3, stride=2), nn.Conv2d(192, 384, kernel_size=3, padding=1), nn.ReLU(inplace=True), nn.Conv2d(384, 256, kernel_size=3, padding=1), nn.ReLU(inplace=True), nn.Conv2d(256, 256, kernel_size=3, padding=1), nn.ReLU(inplace=True), nn.MaxPool2d(kernel_size=3, stride=2), ) self.classifier = nn.Sequential( nn.Dropout(), nn.Linear(256 * 6 * 6, 4096), nn.ReLU(inplace=True), nn.Dropout(), nn.Linear(4096, 4096), nn.ReLU(inplace=True), nn.Linear(4096, num_classes), ) def forward(self, x): x = self.features(x) x = x.view(x.size(0), 256 * 6 * 6) x = self.classifier(x) return x 

3. VGGNet

VGGNet是牛津大學計算機視覺組和Google DeepMind公司的研究人員一起研發的一種卷積神經網絡。通過堆疊3×3 的小型卷積核和2×2的最大池化層,VGGNet成功地構築了最深達19層的卷積神經網絡。VGGNet取得了2014年Image NET 比賽的第二名,由於VGGNet的拓展性強,遷移到其他圖片數據上的泛化性比較好,可用作遷移學習。表1顯示了VGGNet各級別的網絡結構圖。雖然從A到E每一級網絡逐漸變深,但是網絡的參數量並沒有增長很多,因為參數量主要都消耗在最后3個全連接層。前面的卷積層參數很深,參數量並不是很多,但是在訓練時計算量大,比較耗時。D和E模型就是VGGNet-16和VGGNet-19。

      表1 VGGNet模型各級別網絡結構圖

下面給出PyTorch的程序實現VGGNet模型的卷積神經網絡。

cfg = { 'VGG11': [64, 'M', 128, 'M', 256, 256, 'M', 512, 512, 'M', 512, 512, 'M'], 'VGG13': [64, 64, 'M', 128, 128, 'M', 256, 256, 'M', 512, 512, 'M', 512, 512, 'M'], 'VGG16': [64, 64, 'M', 128, 128, 'M', 256, 256, 256, 'M', 512, 512, 512, 'M', 512, 512, 512, 'M'], 'VGG19': [64, 64, 'M', 128, 128, 'M', 256, 256, 256, 256, 'M', 512, 512, 512, 512, 'M', 512, 512, 512, 512, 'M'], } class VGG(nn.Module): def __init__(self, vgg_name): super(VGG, self).__init__() self.features = self._make_layers(cfg[vgg_name]) self.classifier = nn.Linear(512, 10) def forward(self, x): out = self.features(x) out = out.view(out.size(0), -1) out = self.classifier(out) return out def _make_layers(self, cfg): layers = [] in_channels = 3 for x in cfg: if x == 'M': layers += [nn.MaxPool2d(kernel_size=2, stride=2)] else: layers += [nn.Conv2d(in_channels, x, kernel_size=3, padding=1), nn.BatchNorm2d(x), nn.ReLU(inplace=True)] in_channels = x layers += [nn.AvgPool2d(kernel_size=1, stride=1)] return nn.Sequential(*layers) 

 

3 MNIST數據集上卷積神經網絡的實現

本節講解如何使用PyTorch實現一個簡單的卷積神經網絡,使用的數據集是MNIST,預期可以達到97.05%左右的准確率。該神經網絡由2個卷積層和3個全連接層構建,讀者通過這個例子可以掌握設計卷積神經網絡的特征以及參數的配置。

1. 配置庫和配置參數

#coding=utf-8 #配置庫 import torch from torch import nn, optim import torch.nn.functional as F from torch.autograd import Variable from torch.utils.data import DataLoader from torchvision import transforms from torchvision import datasets # 配置參數 torch.manual_seed(1) #設置隨機數種子,確保結果可重復 batch_size = 128 #批處理大小 learning_rate = 1e-2 #學習率 num_epoches = 10 #訓練次數 

2. 加載MINSIT數據

# 下載訓練集 MNIST 手寫數字訓練集 train_dataset = datasets.MNIST( root='./data', #數據保持的位置 train=True, # 訓練集 transform=transforms.ToTensor(),# 一個取值范圍是[0,255]的PIL.Image # 轉化為取值范圍是[0,1.0]的torch.FloadTensor download=True) #下載數據 test_dataset = datasets.MNIST( root='./data', train=False, # 測試集 transform=transforms.ToTensor()) 

3. 數據的批處理

#數據的批處理,尺寸大小為batch_size, #在訓練集中,shuffle 必須設置為True, 表示次序是隨機的 train_loader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True) test_loader = DataLoader(test_dataset, batch_size=batch_size, shuffle=False) 

4. 創建CNN模型

和以前一樣, 我們用一個類來建立 CNN 模型. 這個 CNN 整體流程是 卷積(Conv2d) -> 激勵函數(ReLU) -> 池化, 向下采樣 (MaxPooling) -> 再來一遍 -> 展開多維的卷積成的特征圖 -> 接入全連接層 (Linear) -> 輸出。

# 定義卷積神經網絡模型 class Cnn(nn.Module): def __init__(self, in_dim, n_class): # 1x28x28 super(Cnn, self).__init__() self.conv = nn.Sequential( nn.Conv2d(in_dim, 6, 3, stride=1, padding=1), #28 x 28 nn.ReLU(True), nn.MaxPool2d(2, 2), # 14 x 14 nn.Conv2d(6, 16, 5, stride=1, padding=0), # 16 x 10 x 10 nn.ReLU(True), nn.MaxPool2d(2, 2)) # 16x5x5 self.fc = nn.Sequential( nn.Linear(400, 120), # 400 = 16 x 5 x 5 nn.Linear(120, 84), nn.Linear(84, n_class)) def forward(self, x): out = self.conv(x) out = out.view(out.size(0), 400) # 400 = 16 x 5 x 5 out = self.fc(out) return out model = Cnn(1, 10) # 圖片大小是28x28, 10是數據的種類 

打印模型,呈現網絡結構

print(model) 

5. 訓練

下面我們開始訓練, 將 img, label 都用 Variable 包起來, 然后放入 model 中計算 out, 最后再計算less和正確率.

# 定義loss和optimizer criterion = nn.CrossEntropyLoss() optimizer = optim.SGD(model.parameters(), lr=learning_rate) # 開始訓練 for epoch in range(num_epoches): running_loss = 0.0 running_acc = 0.0 for i, data in enumerate(train_loader, 1): #批處理 img, label = data img = Variable(img) label = Variable(label) # 前向傳播 out = model(img) loss = criterion(out, label) # loss running_loss += loss.data[0] * label.size(0) # total loss , 由於loss 是batch 取均值的,需要把batch size 乘回去 _, pred = torch.max(out, 1) # 預測結果 num_correct = (pred == label).sum() #正確結果的num #accuracy = (pred == label).float().mean() #正確率 running_acc += num_correct.data[0] # 正確結果的總數 # 后向傳播 optimizer.zero_grad() #梯度清零,以免影響其他batch loss.backward() # 后向傳播,計算梯度 optimizer.step() #利用梯度更新 W ,b參數 #打印一個循環后,訓練集合上的loss 和正確率 print('Train {} epoch, Loss: {:.6f}, Acc: {:.6f}'.format( epoch + 1, running_loss / (len(train_dataset)), running_acc / (len( train_dataset)))) 

6. 在測試集測試識別率

#模型測試, model.eval() #由於訓練和測試 BatchNorm, Dropout配置不同,需要說明是否模型測試 eval_loss = 0 eval_acc = 0 for data in test_loader: #test set 批處理 img, label = data img = Variable(img, volatile=True) # volatile 確定你是否不調用.backward(), 測試中不需要 label = Variable(label, volatile=True) out = model(img) # 前向算法 loss = criterion(out, label) # 計算 loss eval_loss += loss.data[0] * label.size(0) # total loss _, pred = torch.max(out, 1) # 預測結果 num_correct = (pred == label).sum() # 正確結果 eval_acc += num_correct.data[0] #正確結果總數 print('Test Loss: {:.6f}, Acc: {:.6f}'.format(eval_loss / (len( test_dataset)), eval_acc * 1.0 / (len(test_dataset)))) 

 

最后的訓練和測試上loss和識別率分別是: Train 1 epoch, Loss: 2.226007, Acc: 0.320517 Train 2 epoch, Loss: 0.736581, Acc: 0.803717 Train 3 epoch, Loss: 0.329458, Acc: 0.901117 Train 4 epoch, Loss: 0.252310, Acc: 0.923550 Train 5 epoch, Loss: 0.201800, Acc: 0.939000 Train 6 epoch, Loss: 0.167249, Acc: 0.949550 Train 7 epoch, Loss: 0.145517, Acc: 0.955617 Train 8 epoch, Loss: 0.128391, Acc: 0.960817 Train 9 epoch, Loss: 0.117047, Acc: 0.964567 Train 10 epoch, Loss: 0.108246, Acc: 0.966550 Test Loss: 0.094209, Acc: 0.970500 

本文有內容和代碼來自網絡,侵刪。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM