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):
4 def __init__(self):
5 super(LeNet,self).__init__()
6 layer1 = nn.Sequential()
7 layer1.add_module('conv1',nn.Conv2d(1,6,5))
8 layer1.add_module('pool1',nn.MaxPool2d(2,2))
9 self.layer1 = layer1
10
11 layer2 = nn.Sequential()
12 layer2.add_module('conv2',nn.Conv2d(6,16,5))
13 layer2.add_module('pool2',nn.MaxPool2d(2,2))
14 self.layer2 = layer2
15
16 layer3 = nn.Sequential()
17 layer3.add_module('fc1',nn.Linear(16*5*5,120))
18 layer3.add_module('fc2',nn.Linear(120,84))
19 layer3.add_module('fc3',nn.Linear(84,10))
20 self.layer3 = layer3
21
22 def forward(self, x):
23 x = self.layer1(x)
24 x = self.layer2(x)
25 x = x.view(x.size(0),-1)#轉換(降低)資料次元,進入全連接配接層
26 x = self.layer3(x)
27 return 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):
5 def __init__(self,num_classes):
6 super(AlexNet,self).__init__()
7 self.features = nn.Sequential(
8 nn.Conv2d(3,64,11,4,padding=2),
9 # inplace=True,是對于Conv2d這樣的上層網絡傳遞下來的tensor直接進行修改,好處就是可以節省運算記憶體,不用多儲存變量
10 nn.ReLU(inplace=True),
11 nn.MaxPool2d(kernel_size=3,stride=2),
12
13 nn.Conv2d(64,192,kernel_size=5,padding=2),
14 nn.ReLU(inplace=True),
15 nn.MaxPool2d(kernel_size=3,stride=2),
16
17 nn.Conv2d(192,384,kernel_size=3,padding=1),
18 nn.ReLU(inplace=True),
19 nn.Conv2d(384,256,kernel_size=3,padding=1),
20 nn.ReLU(inplace=True),
21
22 nn.Conv2d(256,256,kernel_size=3,padding=1),
23 nn.ReLU(inplace=True),
24 nn.MaxPool2d(kernel_size=3,stride=1)
25 )
26 self.classifier = nn.Sequential(
27 nn.Dropout(),
28 nn.Linear(256*6*6,4096),
29 nn.ReLU(inplace=True),
30 nn.Dropout(),
31 nn.Linear(4096,4096),
32 nn.ReLU(inplace=True),
33 nn.Linear(4096,num_classes)
34 )
35 def forward(self, x):
36 x = self.features(x)
37 x = x.view(x.size(0),-1)
38 x = self.classifier(x)
39 return 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):
5 def __init__(self,num_classes):
6 super(VGG16,self).__init__()
7 self.features = nn.Sequential(
8 nn.Conv2d(3,64,kernel_size=3,padding=1),
9 nn.ReLU(inplace=True),
10 nn.Conv2d(64,64,kernel_size=3,padding=1),
11 nn.ReLU(inplace=True),
12
13 nn.Conv2d(64,128,kernel_size=3,padding=1),
14 nn.ReLU(inplace=True),
15 nn.Conv2d(128, 128, kernel_size=3, padding=1),
16 nn.ReLU(inplace=True),
17
18 nn.Conv2d(128, 256, kernel_size=3, padding=1),
19 nn.ReLU(inplace=True),
20 nn.Conv2d(256, 256, kernel_size=3, padding=1),
21 nn.ReLU(inplace=True),
22 nn.Conv2d(256, 256, kernel_size=3, padding=1),
23 nn.ReLU(inplace=True),
24
25 nn.Conv2d(256, 512, kernel_size=3, padding=1),
26 nn.ReLU(inplace=True),
27 nn.Conv2d(512, 512, kernel_size=3, padding=1),
28 nn.ReLU(inplace=True),
29 nn.Conv2d(512, 512, kernel_size=3, padding=1),
30 nn.ReLU(inplace=True),
31
32 nn.Conv2d(512, 512, kernel_size=3, padding=1),
33 nn.ReLU(inplace=True),
34 nn.Conv2d(512, 512, kernel_size=3, padding=1),
35 nn.ReLU(inplace=True),
36 nn.Conv2d(512, 512, kernel_size=3, padding=1),
37 nn.ReLU(inplace=True)
38 )
39
40 self.classifier = nn.Sequential(
41 nn.Linear(512*7*7,4096),
42 nn.ReLU(inplace=True),
43 nn.Dropout(),
44
45 nn.Linear(4096,4096),
46 nn.ReLU(True),
47 nn.Dropout(),
48
49 nn.Linear(4096,num_classes)
50 )
51 def forward(self, x):
52 x = self.features(x),
53 x = x.view(x.size(0),-1)
54 x = self.classifier(x)
55 return 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是一種與Inception V3類似或更複雜的網絡子產品。V4在ImageNet上top-5的錯誤率為3.08%。
圖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):
6 def __init__(self,in_channels,out_channels,**kwargs):
7 super(BasicConv2d,self).__init__()
8 self.conv = nn.Conv2d(in_channels,out_channels,bias=False,**kwargs)
9 self.bn = nn.BatchNorm2d(out_channels,eps=0.001)
10 def forward(self, x):
11 x = self.conv(x)
12 x = self.bn(x)
13 return F.relu(x,inplace=True)
14
15 class Inception(nn.Module):
16 def __init__(self,in_channels,pool_features):
17 super(Inception,self).__init__()
18 self.branch1X1 = BasicConv2d(in_channels,64,kernel_size = 1)
19
20 self.branch5X5_1 = BasicConv2d(in_channels,48,kernel_size = 1)
21 self.branch5X5_2 = BasicConv2d(48,64,kernel_size=5,padding = 2)
22
23 self.branch3X3_1 = BasicConv2d(in_channels,64,kernel_size = 1)
24 self.branch3X3_2 = BasicConv2d(64,96,kernel_size = 3,padding = 1)
25 # self.branch3X3_2 = BasicConv2d(96, 96, kernel_size=1,padding = 1)
26
27 self.branch_pool = BasicConv2d(in_channels,pool_features,kernel_size = 1)
28 def forward(self, x):
29 branch1X1 = self.branch1X1(x)
30
31 branch5X5 = self.branch5X5_1(x)
32 branch5X5 = self.branch5X5_2(branch5X5)
33
34 branch3X3 = self.branch3X3_1(x)
35 branch3X3 = self.branch3X3_2(branch3X3)
36
37 branch_pool = F.avg_pool2d(x,kernel_size = 3,stride = 1,padding = 1)
38 branch_pool = self.branch_pool(branch_pool)
39
40 outputs = [branch1X1,branch3X3,branch5X5,branch_pool]
41 return 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"""
4 return nn.Conv2d(in_planes, out_planes, kernel_size=3, stride=stride,
5 padding=1, bias=False)
6 class BasicBlock(nn.Module):
7 expansion = 1
8 def __init__(self, inplanes, planes, stride=1, downsample=None):
9 super(BasicBlock, self).__init__()
10 self.conv1 = conv3x3(inplanes, planes, stride)
11 self.bn1 = nn.BatchNorm2d(planes)
12 self.relu = nn.ReLU(inplace=True)
13 self.conv2 = conv3x3(planes, planes)
14 self.bn2 = nn.BatchNorm2d(planes)
15 self.downsample = downsample
16 self.stride = stride
17
18 def forward(self, x):
19 residual = x
20 out = self.conv1(x)
21 out = self.bn1(out)
22 out = self.relu(out)
23 out = self.conv2(out)
24 out = self.bn2(out)
25 if self.downsample is not None:
26 residual = self.downsample(x)
27 out += residual
28 out = self.relu(out)
29 return out
當然,不管是LeNet,還是VGGNet,亦或是ResNet,這些經典的網絡結構,pytorch的torchvision的model中都已經實作,并且還有預訓練好的模型,可直接對模型進行微調便可使用。