Pytorch入門實戰二:LeNet、AleNet、VGG、GoogLeNet、ResNet模型詳解
LeNet
1998年, LeCun提出了第一個真正的卷積神經網路,也是整個神經網路的開山之作,稱為 LeNet,現在主要指的是 LeNet5或 LeNet-5,如圖1.1所示。它的主要特徵是將卷積層和下采樣層相結合作為網路的基本機構,如果不計輸入層,該模型共 7層,包括 2個卷積層, 2個下采樣層, 3個全連線層。
圖1.1
注:由於在接入全連線層時,要將池化層的輸出轉換成全連線層需要的維度,因此,必須清晰的知道全連線層前feature map的大小。卷積層與池化層輸出的影象大小,其計算如圖 1.2所示。
圖1.2
本次利用 pytorch實現整個 LeNet模型,圖中的 Subsampling層即可看作如今的池化層,最後一層(輸出層)也當作全連線層進行處理。
1 import torch as torch 2 import torch.nn as nn 3 class LeNet(nn.Module): 4def __init__(self): 5super(LeNet,self).__init__() 6layer1 = nn.Sequential() 7layer1.add_module('conv1',nn.Conv2d(1,6,5)) 8layer1.add_module('pool1',nn.MaxPool2d(2,2)) 9self.layer1 = layer1 10 11layer2 = nn.Sequential() 12layer2.add_module('conv2',nn.Conv2d(6,16,5)) 13layer2.add_module('pool2',nn.MaxPool2d(2,2)) 14self.layer2 = layer2 15 16layer3 = nn.Sequential() 17layer3.add_module('fc1',nn.Linear(16*5*5,120)) 18layer3.add_module('fc2',nn.Linear(120,84)) 19layer3.add_module('fc3',nn.Linear(84,10)) 20self.layer3 = layer3 21 22def forward(self, x): 23x = self.layer1(x) 24x = self.layer2(x) 25x = x.view(x.size(0),-1)#轉換(降低)資料維度,進入全連線層 26x = self.layer3(x) 27return x 28 #代入資料檢驗 29 y = torch.randn(1,1,32,32) 30 model = LeNet() 31 model(y)
AlexNet
在 2010年,斯坦福大學的李飛飛正式組織並啟動了大規模視覺影象識別競賽( ImageNet Large Scale Visual Recognition Challenge, ILSVRC)。在 2012年, Alex Krizhevsky、 Ilya Sutskever提出了一種非常重要的卷積神經網路模型,它就是 AlexNet,如圖1.3所示,在 ImageNet競賽上大放異彩,領先第二名 10%的準確率奪得了冠軍,吸引了學術界與工業界的廣泛關注。
AlexNet神經網路相比 LeNet:
- 1、 使用 ReLU啟用函式。在 AlexNet之前,神經網路一般都使用 sigmoid或 tanh作為啟用函式,這類函式在自變數非常大或者非常小時,函式輸出基本不變,稱之為飽和函式。為了提高訓練速度, AlexNet使用了修正線性函式 ReLU,它是一種非飽和函式,與 sigmoid 和 tanh 函式相比, ReLU分片的線性結構實現了非線性結構的表達能力,梯度消失現象相對較弱,有助於訓練更深層的網路。
- 2、 使用 GPU訓練。與 CPU不同的是, GPU轉為執行復雜的數學和幾何計算而設計, AlexNet使用了 2個 GPU來提升速度,分別放置一半卷積核。
- 3、 區域性響應歸一化。 AlexNet使用區域性響應歸一化技巧,將 ImageNet上的 top-1與 top-5錯誤率分別減少了 1.4%和 1.2%。
- 4、 重疊池化層。與不重疊池化層相比,重疊池化層有助於緩解過擬合,使得 AlexNet的 top-1和 top-5錯誤率分別降低了 0.4%和 0.3%。
- 5、 減少過擬合。 AlexNet使用了資料擴增與丟失輸出兩種技巧。資料擴增: a、影象的平移、翻轉, b、基於 PCA的 RGB強度調整。丟失輸出技巧( DropOut層), AlexNet以 0.5的概率將兩個全連線層神經元的輸出設定為 0,有效阻止了過擬合現象的發生。
圖1.3
利用pytorch實現 AlexNet網路,由於當時, GPU的計算能力不強,因此 Alex採用了 2個 GPU並行來計算,如今的 GPU計算能力,完全可以替代。
1 import torch.nn as nn 2 import torch 3 4 class AlexNet(nn.Module): 5def __init__(self,num_classes): 6super(AlexNet,self).__init__() 7self.features = nn.Sequential( 8nn.Conv2d(3,64,11,4,padding=2), 9# inplace=True,是對於Conv2d這樣的上層網路傳遞下來的tensor直接進行修改,好處就是可以節省運算記憶體,不用多儲存變數 10nn.ReLU(inplace=True), 11nn.MaxPool2d(kernel_size=3,stride=2), 12 13nn.Conv2d(64,192,kernel_size=5,padding=2), 14nn.ReLU(inplace=True), 15nn.MaxPool2d(kernel_size=3,stride=2), 16 17nn.Conv2d(192,384,kernel_size=3,padding=1), 18nn.ReLU(inplace=True), 19nn.Conv2d(384,256,kernel_size=3,padding=1), 20nn.ReLU(inplace=True), 21 22nn.Conv2d(256,256,kernel_size=3,padding=1), 23nn.ReLU(inplace=True), 24nn.MaxPool2d(kernel_size=3,stride=1) 25) 26self.classifier = nn.Sequential( 27nn.Dropout(), 28nn.Linear(256*6*6,4096), 29nn.ReLU(inplace=True), 30nn.Dropout(), 31nn.Linear(4096,4096), 32nn.ReLU(inplace=True), 33nn.Linear(4096,num_classes) 34) 35def forward(self, x): 36x = self.features(x) 37x = x.view(x.size(0),-1) 38x = self.classifier(x) 39return x
VGGNet
在 2014年,參加 ILSVRC競賽的“ VGG”隊在 ImageNet上獲得了比賽的亞軍。 VGG的核心思想是利用較小的卷積核來增加網路的深度。常用的有 VGG16、 VGG19兩種型別。 VGG16擁有 13個卷積層(核大小均為 3*3), 5個最大池化層, 3個全連線層。 VGG19擁有 16個卷積層(核大小均為 3*3), 5個最大池化層, 3個全連線層,如圖1.4所示。
圖1.4
加深結構都使用 ReLU啟用函式, VGG19比 VGG16的區別在於多了 3個卷積層,利用 pytorch實現整 VG16模型, VGG19同理。
1 import torch as torch 2 import torch.nn as nn 3 4 class VGG16(nn.Module): 5def __init__(self,num_classes): 6super(VGG16,self).__init__() 7self.features = nn.Sequential( 8nn.Conv2d(3,64,kernel_size=3,padding=1), 9nn.ReLU(inplace=True), 10nn.Conv2d(64,64,kernel_size=3,padding=1), 11nn.ReLU(inplace=True), 12 13nn.Conv2d(64,128,kernel_size=3,padding=1), 14nn.ReLU(inplace=True), 15nn.Conv2d(128, 128, kernel_size=3, padding=1), 16nn.ReLU(inplace=True), 17 18nn.Conv2d(128, 256, kernel_size=3, padding=1), 19nn.ReLU(inplace=True), 20nn.Conv2d(256, 256, kernel_size=3, padding=1), 21nn.ReLU(inplace=True), 22nn.Conv2d(256, 256, kernel_size=3, padding=1), 23nn.ReLU(inplace=True), 24 25nn.Conv2d(256, 512, kernel_size=3, padding=1), 26nn.ReLU(inplace=True), 27nn.Conv2d(512, 512, kernel_size=3, padding=1), 28nn.ReLU(inplace=True), 29nn.Conv2d(512, 512, kernel_size=3, padding=1), 30nn.ReLU(inplace=True), 31 32nn.Conv2d(512, 512, kernel_size=3, padding=1), 33nn.ReLU(inplace=True), 34nn.Conv2d(512, 512, kernel_size=3, padding=1), 35nn.ReLU(inplace=True), 36nn.Conv2d(512, 512, kernel_size=3, padding=1), 37nn.ReLU(inplace=True) 38) 39 40self.classifier = nn.Sequential( 41nn.Linear(512*7*7,4096), 42nn.ReLU(inplace=True), 43nn.Dropout(), 44 45nn.Linear(4096,4096), 46nn.ReLU(True), 47nn.Dropout(), 48 49nn.Linear(4096,num_classes) 50) 51def forward(self, x): 52x = self.features(x), 53x = x.view(x.size(0),-1) 54x = self.classifier(x) 55return x
GoogLeNet
GoogLeNet專注於加深網路結構,與此同時引入了新的基本結構—— Inception模組,從而來增加網路的寬度。 GoogLeNet一共 22層,它沒有全連線層,在 2014年的比賽中獲得了冠軍。
每個原始 Inception模組由 previous layer、並行處理層及 filter concatenation層組成,如圖 1.5。並行處理層包含 4個分支,即 1*1卷積分支, 3*3卷積分支, 5*5卷積分支和 3*3最大池化分支。一個關於原始 Inception模組的最大問題是, 5*5卷積分支即使採用中等規模的卷積核個數,在計算代價上也可能是無法承受的。這個問題在混合池化層之後會更為突出,很快的出現計算量的暴漲。
圖 1.5
為了克服原始 Inception模組上的困難, GoogLeNet推出了一個新款,即採用 1*1的卷積層來降低輸入層的維度,使網路引數減少,因此減少網路的複雜性,如圖 1.6。因此得到降維 Inception模組,稱為 inception V1。
圖1.6
從 GoogLeNet中明顯看出,共包含 9個 Inception V1模組,如圖1.7所示。所有層均採用了 ReLU啟用函式。
圖1.7
自從 2014年過後, Inception模組不斷的改進,現在已發展到 V4。 GoogLeNet V2中的 Inception參考 VGGNet用兩個 3*3核的卷積層代替了具有 5*5核的卷積層,與此同時減少了一個輔助分類器,並引入了 Batch Normalization( BN),它是一個非常有用的正則化方法。 V3相對於 V2的學習效率提升了很多倍,並且訓練時間大大縮短了。在 ImageNet上的 top-5錯誤率為 4.8%。 Inception V3通過改進 V2得到,其核心思想是將一個較大的 n*n的二維卷積拆成兩個較小的一維卷積 n*1和 1*n。 Inception V3有三種不同的結構( Base的大小分別為 35*35、 17*17、 8*8),如圖1.8所示,其中分支可能巢狀。 GoogLeNet也只用了一個輔助分類器,在 ImageNet上 top-5的錯誤率為 3.5%。 Inception V4是一種與
圖1.8
接下來利用 pytorch實現 GoogLeNet中的 Inception V2模組,其實整個 GoogLeNet都是由 Inception模組構成的。
1 import torch.nn as nn 2 import torch as torch 3 import torch.nn.functional as F 4 import torchvision.models.inception 5 class BasicConv2d(nn.Module): 6def __init__(self,in_channels,out_channels,**kwargs): 7super(BasicConv2d,self).__init__() 8self.conv = nn.Conv2d(in_channels,out_channels,bias=False,**kwargs) 9self.bn = nn.BatchNorm2d(out_channels,eps=0.001) 10def forward(self, x): 11x = self.conv(x) 12x = self.bn(x) 13return F.relu(x,inplace=True) 14 15 class Inception(nn.Module): 16def __init__(self,in_channels,pool_features): 17super(Inception,self).__init__() 18self.branch1X1 = BasicConv2d(in_channels,64,kernel_size = 1) 19 20self.branch5X5_1 = BasicConv2d(in_channels,48,kernel_size = 1) 21self.branch5X5_2 = BasicConv2d(48,64,kernel_size=5,padding = 2) 22 23self.branch3X3_1 = BasicConv2d(in_channels,64,kernel_size = 1) 24self.branch3X3_2 = BasicConv2d(64,96,kernel_size = 3,padding = 1) 25# self.branch3X3_2 = BasicConv2d(96, 96, kernel_size=1,padding = 1) 26 27self.branch_pool = BasicConv2d(in_channels,pool_features,kernel_size = 1) 28def forward(self, x): 29branch1X1 = self.branch1X1(x) 30 31branch5X5 = self.branch5X5_1(x) 32branch5X5 = self.branch5X5_2(branch5X5) 33 34branch3X3 = self.branch3X3_1(x) 35branch3X3 = self.branch3X3_2(branch3X3) 36 37branch_pool = F.avg_pool2d(x,kernel_size = 3,stride = 1,padding = 1) 38branch_pool = self.branch_pool(branch_pool) 39 40outputs = [branch1X1,branch3X3,branch5X5,branch_pool] 41return torch.cat(outputs,1)
ResNet
隨著神經網路的深度不斷的加深,梯度消失、梯度爆炸的問題會越來越嚴重,這也導致了神經網路的學習與訓練變得越來越困難。有些網路在開始收斂時,可能出現退化問題,導致準確率很快達到飽和,出現層次越深、錯誤率反而越高的現象。讓人驚訝的是,這不是過擬合的問題,僅僅是因為加深了網路。這便有了 ResNet的設計, ResNet在 2015年的 ImageNet競賽獲得了冠軍,由微軟研究院提出,通過殘差模組能夠成功的訓練高達 152層深的網路,如圖 1.10所示。
ReNet與普通殘差網路不同之處在於,引入了跨層連線( shorcut connection),來構造出了殘差模組。
在一個殘差模組中,一般跨層連線只有跨 2~3層,如圖 1.9所示,但是不排除跨更多的層,跨一層的實驗效果不理想。 在去掉跨連線層,用其輸出用 H(x),當加入跨連線層時, F(x) 與 H(x)存在關係: F(x): =H(x)-X,稱為殘差模組。既可以用全連線層構造殘差模組,也可以用卷積層構造殘差模組。基於殘差模組的網路結構非常的深,其深度可達 1000層以上。
圖 1.9
圖 1.10
用於 ImageNet 的 5 種深層殘差網路結構,如圖 1.11 所示。
圖1.11
從何凱明的論文中也讀到 plain-18、 plain-34(即未加 shotcut層)錯誤率比 ResNet-18、 ResNet-34(加了 shotcut層)大了很多,如圖 1.12所示。
圖 1.12
下面利用 pytorch實現 ReNet的殘差學習單元,此處參考了torchvision的model。
1 import torch.nn as nn 2 def conv3x3(in_planes, out_planes, stride=1): 3"""3x3 convolution with padding""" 4return nn.Conv2d(in_planes, out_planes, kernel_size=3, stride=stride, 5padding=1, bias=False) 6 class BasicBlock(nn.Module): 7expansion = 1 8def __init__(self, inplanes, planes, stride=1, downsample=None): 9super(BasicBlock, self).__init__() 10self.conv1 = conv3x3(inplanes, planes, stride) 11self.bn1 = nn.BatchNorm2d(planes) 12self.relu = nn.ReLU(inplace=True) 13self.conv2 = conv3x3(planes, planes) 14self.bn2 = nn.BatchNorm2d(planes) 15self.downsample = downsample 16self.stride = stride 17 18def forward(self, x): 19residual = x 20out = self.conv1(x) 21out = self.bn1(out) 22out = self.relu(out) 23out = self.conv2(out) 24out = self.bn2(out) 25if self.downsample is not None: 26residual = self.downsample(x) 27out += residual 28out = self.relu(out) 29return out
當然,不管是LeNet,還是VGGNet,亦或是ResNet,這些經典的網路結構,pytorch的torchvision的model中都已經實現,並且還有預訓練好的模型,可直接對模型進行微調便可使用。