跟我一起學PyTorch-06:卷積神經網絡CNN

以全連接層為基礎的深度神經網絡DNN是整個深度學習的基石。要說應用最廣、影響最大的深度神經網絡,那就是卷積神經網絡(Convolutional Neural Network,CNN)。卷積神經網絡雖然發布的時間較早,但直到2006年Hilton解決深度神經網絡的訓練問題后才煥發生機。卷積神經網絡現在幾乎是圖像識別研究的標配。

簡單回顧卷積神經網絡的發展歷程。日本科學家福島邦彥在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全程使用3x3的卷積,成功訓練了深度達19層的網絡。2015年,微軟提出了ResNet,包含殘差學習模塊,成功訓練了152層的網絡,一舉拿下當年的ILSVRC比賽的冠軍。

卷積神經網絡技術的發展風起云涌,盡管卷積神經網絡最初是為了解決計算機視覺等問題設計的,現在其應用范圍不僅僅局限于圖像和視頻領域,也可用于時間序列信號,比如音頻信號等。本章主要通過卷積神經網絡在計算機視覺上的應用來介紹卷積神經網絡的原理以及如何使用PyTorch實現卷積神經網絡。

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

1.計算機視覺

1.人類視覺和計算機視覺

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

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

2.特征提取

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

image.png

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

image.png

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

image.png

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

3.數據集

對于卷積神經網絡的成功,計算機視覺領域的幾大數據集可謂功不可沒。在計算機視覺中有以下幾大基礎數據集。
(1)MNIST
MNIST數據集是用作手寫體識別的數據集。MNIST數據集包括60000張訓練圖片和10000張測試圖片。如圖所示。其中,每一張圖片都是0~9中的一個數字。圖片尺寸為28x28。由于數據集中數據相對比較簡單,人工標注錯誤率僅為0.2%。

image.png

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

image.png

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

image.png

2.卷積神經網絡

計算機視覺作為人工智能的重要領域,在2006年后取得了很多突破性的進展。本章介紹的卷積神經網絡就是這些突破性進展背后的技術基礎。在前面章節中介紹的神經網絡每兩層的所有節點都是兩兩相連的,所以稱這種網絡結構為全連接層網絡結構。可將只包含全連接層的神經網絡稱為全連接神經網絡。

image.png

卷積神經網絡利用卷積結構減少需要學習的參數量,從而提高反向傳播算法的訓練效率。如下圖所示,在卷積神經網絡中,第一個卷積層會直接接受圖像像素級的輸入,每一個卷積操作只處理一小塊圖像,進行卷積操作后傳遞給后面的網絡,每一層卷積都會提取數據中最有效的特征。這種方法可以提取到圖像中最基礎的特征,比如不同方向的拐角或者邊,而后進行組合和抽象成更高階的特征,因此卷積神經網絡對圖像縮放、平移和旋轉具有不變性。

image.png

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

image.png

對于一個200x200的輸入圖像而言,如果下一個隱含層的神經元數目為10000個,采用全連接則有200x200
x10000=400000000個權值參數,如此巨大的參數幾乎難以訓練;而采用局部連接,隱含層的每個神經元僅與圖像中4x4的局部圖像相連接,那么此時的權值參數個數為4x4x10000=160000,大大減少了參數的個數。

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

image.png

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

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

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

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

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

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

(4)Softmax層。Softmax層主要用于分類問題。

1.卷積層

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

image.png

在卷積層中,卷積核所處理的節點矩陣的長、寬都是人工指定的,這個節點矩陣的尺寸稱為卷積核的尺寸。卷積核處理的深度和當前層神經網絡節點矩陣的深度是一致的,即便節點矩陣是三維的,卷積核的尺寸只需要指定兩個維度。一般而言,卷積核的尺寸是3x3和5x5。如上圖中,左邊表示輸入的數據,輸入的數據尺寸為3x32x32,中間表示卷積核,右邊每一個小圓點表示一個神經元,圖中有5個神經元。假設卷積核尺寸為5x5,卷積層中每個神經元會有輸入數據中3x5x5區域的權重,一共75個權重。這里再次強調下卷積核的深度必須為3,和輸入數據保持一致。

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

(1)卷積核的數量就是卷積層的輸出深度,如上圖所示的5個神經元,該參數是用戶指定的,和使用的卷積核數量一致。
(2)卷積核計算運算時必須指定滑動步長。比如步長為1,說明卷積核每次移動一個像素點;步長為2,卷積核會滑動2個像素點。滑動的操作使得輸出的數據變得更少。
(3)邊界填充如果為0,可以保證輸入和輸出在空間上尺寸一致;如果邊界填充大于0,可以確保在進行卷積操作時不損失邊界信息。

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

以一維空間來說明卷積操作,如下圖所示。其中,輸入數據大小為5,卷積核的大小為3,填充為1,滑動步長分別為1和2的卷積操作結果如下:

image.png

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

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

# 方形卷積核和等長的步長
m = nn.Conv2d(16,33,3,stride=2)
# 非方形卷積核,非等長的步長和邊界填充
m = nn.Conv2d(16,33,(3,5),stride=(2,1),padding=(4,2))
# 非方形卷積核,非等長的步長,邊界填充和空間間隔
m = nn.Conv2d(16,33,(3,5),stride=(2,1),padding=(4,2),dilation=(3,1))
input = 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.池化層

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

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

image.png

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

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

image.png

在PyTorch中,池化層包括nn.MaxPool2d和nn.AvgPool2d等。下面以nn.MaxPool2d為例進行說明:

nn.MaxPool2d(kernel_size,stride=None,padding=0,dilation=1,return_indices=False,ceil_mode=False)

# 方形窗口尺寸為3,等長滑動步長為2
m = nn.MaxPool2d(3,stride=2)
# 非方形窗口,非等長滑動步長
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_mode表示使用方格代替層結構。

3.經典卷積神經網絡

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

(1)LeNet

LeNet具體指的是LeNet-5。LeNet-5模型是Yann LeCun教授于1998年提出的,它是第一個成功應用于數字識別的卷積神經網絡。在MNIST數據集上,LeNet-5模型可以達到約99.2%的正確率。LeNet-5模型總共有7層,包括2個卷積層,2個池化層,2個全連接層和1個輸出層。下圖是LeNet-5的模型架構。

image.png

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.conv2d(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%的準確率而奪得冠軍,成功地展示了深度學習的威力。AlexNet的網絡結構如下圖所示。

image.png

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

image.png

具體的參數配置可以查看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公司的研究人員一起研發的卷積神經網絡。通過堆疊3x3的小型卷積核和2x2的最大池化層,VGGNet成功構筑了深達19層的卷積神經網絡。VGGNet取得了2014年ImageNet比賽的第二名,由于拓展性強,遷移到其他圖片數據上的泛化性比較好,因此可用作遷移學習。下圖顯示的是VGGNet各個級別的的網絡結構圖。雖然從A到E每一級網絡逐層變深,但是網絡的參數量并沒有增長很多,因為參數量主要都消耗在最后3個全連接層。前面的卷積層參數很深,參數量并不是很多,但是在訓練時計算量大,比較耗時。D和E模型就是VGGNet-16和VGGNet-19。

image.png

下面給出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(sfg[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 = []
        ln_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%的準確率。該神經網絡由2個卷積層和3個全連接層構成。通過這個例子我們可以掌握設計卷積神經網絡的特征以及參數的配置。

1.配置庫和配置參數
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,datasets

# 配置參數
torch.manual_seed(1)    # 設置隨機種子,確保結果可重復
batch_size = 128        # 批處理大小
learning_rate = 1e-2    # 學習率
num_epoches = 10        # 訓練次數
2.加載MNIST數據集
# 下載訓練集MNIST(手寫數字數據)
train_dataset = datasets.MNIST(root='./data',                    # 數據集保存路徑
                               train=True,                       # 訓練數據集
                               transform=transforms.ToTensor(),  # 轉為Tensor
                               download=True)                    # 下載數據

test_dataset = datasets.MNIST(root='./data',
                               train=False,                      # 測試數據集
                               transform=transforms.ToTensor())

# 訓練數據的加載方式:每次從train_dataset中隨機(shuffle=True)選擇batch_size個樣本作為一個批次返回,因此所選擇的數據可能會重復
train_loader = DataLoader(train_dataset,batch_size=batch_size,shuffle=True)
# 測試數據的加載方式:每次從test_dataset中選擇batch_size個不同的樣本作為一個批次返回,要覆蓋到所有測試樣本
test_loader = DataLoader(test_dataset,batch_size=batch_size,shuffle=False)
3.創建CNN模型
class Cnn(nn.Module):
    def __init__(self,in_dim,n_class): # 28*28*1
        super(Cnn,self).__init__()
        # 定義卷積層
        self.conv = nn.Sequential(
            nn.Conv2d(in_dim,6,3,stride=1,padding=1),  # 28*28
            nn.ReLU(True),
            nn.MaxPool2d(2,2),  # 14*14
            nn.Conv2d(6,16,5,stride=1,padding=0),  # 10*10*16
            nn.ReLU(True),
            nn.MaxPool2d(2,2))   # 5*5*16
        # 定義全連接層
        self.fc = nn.Sequential(
            nn.Linear(400,120),  # 400 = 5*5*16
            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 = 5*5*16
        out = self.fc(out)
        return out
    
model = Cnn(1,10)   # 圖片大小是28*28,10是數據的種類

print(model)

輸出如下:

Cnn(
  (conv): Sequential(
    (0): Conv2d(1, 6, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (1): ReLU(inplace)
    (2): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
    (3): Conv2d(6, 16, kernel_size=(5, 5), stride=(1, 1))
    (4): ReLU(inplace)
    (5): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
  )
  (fc): Sequential(
    (0): Linear(in_features=400, out_features=120, bias=True)
    (1): Linear(in_features=120, out_features=84, bias=True)
    (2): Linear(in_features=84, out_features=10, bias=True)
  )
)
4.訓練模型
# 定義損失:交叉熵損失
criterion = nn.CrossEntropyLoss() 
# 定義優化器:隨機梯度下降SGD
optimizer = optim.SGD(model.parameters(),lr=learning_rate)

# 共訓練num_epoches輪
for epoch in range(num_epoches): 
    running_loss = 0.0  # 當前損失 
    running_acc = 0.0   # 當前準確度
    # 訓練集:60000,批處理:128
    for i,data in enumerate(train_loader,1): 
        img,label = data 
        img = Variable(img) 
        label = Variable(label) 
        out = model(img)                              # 前向傳播
        loss = criterion(out,label)                   # 這個損失是當前批次的平均損失
        running_loss += loss.item() * label.size(0)   # 累計損失大小,乘積表示當前批次的總損失
        _,pred=torch.max(out,1)                       # 多分類問題的類別取概率最大的類別
        num_correct = (pred == label).sum()           # 當前批次預測正確的個數
        running_acc += num_correct.item()             # 累計預測正確的個數
        optimizer.zero_grad()                         # 梯度清零
        loss.backward()                               # 誤差反向傳播
        optimizer.step()                              # 梯度更新
    # 每訓練一輪,打印一次信息
    print('Train {} epoch, Loss: {:.6f}, Acc: {:.6f}'.format(epoch+1,running_loss/(len(train_dataset)),running_acc/(len(train_dataset))))

輸出如下:

Train 1 epoch, Loss: 1.366457, Acc: 0.626950
Train 2 epoch, Loss: 0.411538, Acc: 0.877867
Train 3 epoch, Loss: 0.294756, Acc: 0.911117
Train 4 epoch, Loss: 0.232250, Acc: 0.930517
Train 5 epoch, Loss: 0.188249, Acc: 0.943533
Train 6 epoch, Loss: 0.159317, Acc: 0.952500
Train 7 epoch, Loss: 0.139562, Acc: 0.957750
Train 8 epoch, Loss: 0.125770, Acc: 0.962000
Train 9 epoch, Loss: 0.115610, Acc: 0.964783
Train 10 epoch, Loss: 0.108309, Acc: 0.966983
5.評估模型
# 由于訓練和測試的BatchNorm,Drop等配置不同,所以需要說明是模型評估
model.eval()
eval_loss = 0
eval_acc = 0
for data in test_loader:
    img,label = data
    img = Variable(img)
    # 測試不需要 label = Variable(label)
    out = model(img)                             # 前向傳播
    loss = criterion(out,label)                  # 當前批次的平均損失
    eval_loss += loss.item() * label.size(0)     # 累計損失
    _,pred = torch.max(out,1)                    # 多分類問題的類別取概率最大的類別
    num_correct = (pred==label).sum()            # 當前批次預測正確的個數
    eval_acc += num_correct.item()               # 累計預測正確的個數

# 打印測試集上的評估結果 
print('Test Loss: {:.6f}, Acc: {:.6f}'.format(eval_loss/(len(test_dataset)),eval_acc * 1.0 / (len(test_dataset))))

輸出如下:

Test Loss: 0.091677, Acc: 0.972800
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容