Pytorch實現CNN經典網絡框架(LeNet、AlexNet、VGGNet、GoogLeNet、ResNet)

卷積神經網絡可謂是現在深度學習領域中大紅大紫的網絡框架,尤其在計算機視覺領域更是一枝獨秀。CNN從90年代的LeNet開始,21世紀初沉寂了10年,直到12年AlexNet開始又再煥發第二春,從ZF Net到VGG,GoogLeNet再到ResNet和最近的DenseNet,網絡越來越深,架構越來越複雜,解決反向傳播時梯度消失的方法也越來越巧妙。下面介紹幾種網絡的結構框架和代碼實現。

一、LeNet

1、介紹
LeNet是卷積神經網絡的祖師爺LeCun在1998年提出,用於解決手寫數字識別的視覺任務。 一共有7層,其中2層卷積和2層化層交替出現,最後輸出3層全連接層得到整體的結果。沒有添加激活層。
隨後CNN的最基本的架構就定下來了:卷積層、池化層、全連接層。如今各大深度學習框架中所使用的LeNet都是簡化改進過的LeNet-5(-5表示具有5個層),和原始的LeNet有些許不同,比如把激活函數改爲了現在很常用的ReLu。
LeNet-5跟現有的conv->pool->ReLU的套路不同,它使用的方式是conv1->pool->conv2->pool2再接全連接層,但是不變的是,卷積層後緊接池化層的模式。
2、網絡結構
在這裏插入圖片描述
3、代碼實現

import torch.nn as nn

class Lenet(nn.Module):
    def __init__(self):
        super(Lenet,self).__init__()
        layer1 = nn.Sequential()
        layer1.add_module('conv1',nn.Conv2d(1,6,3,padding=1))
        layer1.add_module('pool1',nn.MaxPool2d(2,2))
        self.layer1 = layer1
        
        layer2 = nn.Sequential()
        layer2.add_module('conv2',nn.Conv2d(6,16,5))
        layer2.add_module('pool2',nn.MaxPool2d(2,2))
        self.layer2 = layer2
        
        layer3 = nn.Sequential()
        layer3.add_module('fc1',nn.Linear(400,120))
        layer3.add_module('fc2',nn.Linear(120,84))
        layer3.add_module('fc3',nn.Linear(84,10))
        
        self.layer3 = layer3
        
    def forward(self,x):
        x = self.layer1(x)
        x = self.layer2(x)
        x = x.view(x.size(0),-1)
        x = self.layer3(x)
        
        return x

二、AlexNet

1、介紹
2012年AlexNet框架在ImageNet競賽上面大放異彩的,它以領先第二名10%的準確度奪得冠軍。掀起了卷積神經網絡在圖像領域的熱潮。AlexNet相比於LeNet層數更深,第一次引入了激活層ReLU,並且在全連接層加入了Dropout防止過擬合。
2、網絡結構
在這裏插入圖片描述
3、代碼實現

class AlexNet(nn.Module):
    def __init__(self,num_classes):
        super(AlexNet,self).__init__()
        #特徵抽取
        self.features = nn.Sequential(
            nn.Conv2d(2,64,kernel_size=11,stride=4,padding=2),
            nn.ReLU(inplace=True),
            nn.MaxPool2d(kernel_size=3,stride=2),
            
            nn.Conv2d(64,192,kernel_size=3,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.Con2d(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 = view(x.size(0),256*6*6)
        x = self.classifier(x)
        return x 

三、VGGNet

1、介紹
VGGNe是ImageNet 2014年的亞軍,它使用了更小的濾波器,同時使用的更深的網絡結構。VGG只是對網絡層進行不斷的堆疊,並沒有進行太多的創新,但增加深度確實可以一定程度改善模型效果。
AlexNet只有8層網絡,而VGGNet有16-19層網絡。AlexNet使用1111的大濾波器,而VGGNet使用33的卷積濾波器和2*2的大池化層。AlexNet和VGGNet對比圖:
在這裏插入圖片描述
2、網絡結構 VGG-16
在這裏插入圖片描述
3、代碼實現

class VGG(nn.Module):
    def __init__(self,num_classes):
        super(VGG,self).__init__()
        self.features = nn.Sequential(
            nn.Conv2d(3,64,kernel_size=3,padding=1),
            nn.ReLU(True),
            
            nn.Conv2d(64,64,kernel_size=3,padding=1),
            nn.ReLU(True),
            nn.MaxPool2d(kernel_size=2,stride=2),
            
            nn.Conv2d(64,128,kernel_size=3,padding=1),
            nn.ReLU(True),

            nn.Conv2d(128,128,kernel_size=3,padding=1),
            nn.ReLU(True),
            nn.MaxPool2d(kernel_size=2,stride=2),
            
            nn.Conv2d(128,256,kernel_size=3,padding=1),
            nn.ReLU(True),
            
            nn.Conv2d(256,256,kernel_size=3,padding=1),
            nn.ReLU(True),

            nn.Conv2d(256,256,kernel_size=3,padding=1),
            nn.ReLU(True),
            nn.MaxPool2d(kernel_size=2,stride=2),
            
            nn.Conv2d(256,512,kernel_size=3,padding=1),
            nn.ReLU(True),
            
            nn.Conv2d(512,512,kernel_size=3,padding=1),
            nn.ReLU(True),

            nn.Conv2d(512,512,kernel_size=3,padding=1),
            nn.ReLU(True),
            nn.MaxPool2d(kernel_size=2,stride=2),
            
            nn.Conv2d(512,512,kernel_size=3,padding=1),
            nn.ReLU(True),
            
            nn.Conv2d(512,512,kernel_size=3,padding=1),
            nn.ReLU(True),

            nn.Conv2d(512,512,kernel_size=3,padding=1),
            nn.ReLU(True),
            nn.MaxPool2d(kernel_size=2,stride=2))
        
        self.classifier = nn.Sequential(
            nn.Linear(512*7*7,4096),
            nn.ReLU(True),
            nn.Dropout(),
            
            nn.Linear(4096,4096),
            nn.ReLu(True),
            nn.Dropout(),
            
            nn.Linear(4096,num_classes))
        
        self._initialize_weights()
        
    def forward(self,x):
        x = self.features(x)
        x = x.view(x.size(0),-1)
        x = self.classifier(x)

四、GoogLeNet

1、介紹
GoogLeNet 也叫InceptionNet,是在 2014 年被提出的。GoogLeNet 採取了比 VGGNet 更深的網絡結構, 一共有 22 層,但是它的參數卻比 A1exNet少了12倍。同時有很高的計算效率 ,因爲它採用了一種很有效的Inception模塊,而且它也沒有全連接層,是 2014 年比賽的冠軍。
2、網絡結構

  • 簡單版Inception模塊:
    該結構將CNN中常用的卷積(1x1,3x3,5x5)、池化操作(3x3)堆疊在一起(卷積、池化後的尺寸相同,將通道相加),一方面增加了網絡的寬度,另一方面也增加了網絡對尺度的適應性。
    網絡卷積層中的網絡能夠提取輸入的每一個細節信息,同時5x5的濾波器也能夠覆蓋大部分接受層的的輸入。還可以進行一個池化操作,以減少空間大小,降低過度擬合。在這些層之上,在每一個卷積層後都要做一個ReLU操作,以增加網絡的非線性特徵。然而這個Inception原始版本,所有的卷積核都在上一層的所有輸出上來做,而那個5x5的卷積核所需的計算量就太大了,造成了特徵圖的厚度很大
    在這裏插入圖片描述
  • 優化版Inception模型:爲了避免上述情況,在3x3前、5x5前、max pooling後分別加上了1x1的卷積核,以起到了降低特徵圖厚度的作用,這也就形成了Inception v1的網絡結構,如下圖所示:
    在這裏插入圖片描述
    3、代碼實現
    整個GoogLeNet都是由這些Inception模塊組成的。首先定義一個最基礎的卷積模塊、然後根據這個模塊定義了1x1 ,3x3和5x5的模塊和一個池化層,最後使用 torch.cat()將它們按深度拼接起來,得到輸出結果。
class BasicConv2d(nn.Module):
    def __init__(self,in_channels,out_channels,**kwargs):
        super(BasicConv2d,self).__init__()
        self.conv = nn.Conv2d(in_channels,out_channels,bias=False,**kwargs)
        self.bn = nn.BatchNorm2d(out_channels,eps=0.001)
        
    def forward(self,x):
        x = self.conv(x)
        x = self.bn(x)
        return F.relu(x,inplace=True)
    
class Inception(nn.Module):
    def __init__(self,in_channels,pool_features):
        super(Inception,self).__init__()
        self.branch1x1 = BasicConv2d(in_channels,64,kernel_size=1)
        
        self.branch5x5_1 = BasicConv2d(in_channels,48,kernel_size=1)
        self.branch5x5_2 = BasicConv2d(48,64,kernel_size=5,padding=2)
        
        self.branch3x3dbl_1 = BasicConv2d(in_channels,64,kernel_size=1)
        self.branch3x3dbl_2 = BasicConv2d(64,96,kernel_size=3,padding=1)
        self.branch3x3dbl_3 = BasicConv2d(96,96,kernel_size=3,padding=1)
        
        self.branch_pool = BasicConv2d(in_channels,pool_features,kernel_size=1)
        
    def forward(self,x):
        branch1x1 = self.branch1x1(x)
        
        branch5x5 = self.branch5x5_1(x)
        branch5x5 = self.branch5x5_2(branch5x5)
        
        branch3x3dbl = self.branch3x3dbl_1(x)
        branch3x3dbl = self.branch3x3dbl_2(branch3x3dbl)
        branch3x3dbl = self.branch3x3dbl_3(branch3x3dbl)
        
        branch_pool = F.avg_pool2d(x,kernel_size=3,stride=1,padding=1)
        branch_pool = self.branch_pool(branch_pool)
        
        outputs = [branch1x1,branch5x5,branch3x3dbl,branch_pool]

        return torch.cat(outputs,1)                                     

五、ResNet

1、介紹
ResNet是2015年ImageNet競賽的冠軍。由微軟研究院提出,不再是簡單的堆積層數,通過殘差模塊能夠成功地訓練高達152層深的神經網絡。ResNet 最初的設計靈感來自這個問題:

  • 隨着網絡深度增加,網絡的準確度應該同步增加,當然要注意過擬合問題。但是網絡深度增加的一個問題在於這些增加的層是參數更新的信號,因爲梯度是從後向前傳播的,增加網絡深度後,比較靠前的層梯度會很小。這意味着這些層基本上學習停滯了,這就是梯度消失問題。
  • 深度網絡的第二個問題在於訓練,當網絡更深時意味着參數空間更大,優化問題變得更難,因此簡單地去增加網絡深度反而出現更高的訓練誤差。在不斷加深度神經網絡的時候,會出現一個Degradation,即準確率會先上開然後達到飽和,再持續增加深度則會導致模型準確率下降。比如下圖,一個56層的網絡的性能卻不如20層的性能好,這不是因爲過擬合(訓練集訓練誤差依然很高),這就是退化問題。殘差網絡ResNet設計一種殘差模塊讓我們可以訓練更深的網絡。
    在這裏插入圖片描述

這裏詳細分析一下殘差單元來理解ResNet的精髓。
從下圖可以看出,數據經過了兩條路線,一條是常規路線,另一條則是捷(shortcut),直接實現單位映射的直接連接的路線,這有點類似與電路中的“短路”。通過實驗,這種帶有shortcut的結構確實可以很好地應對退化問題。我們把網絡中的一個模塊的輸入和輸出關係看作是y=H(x),那麼直接通過梯度方法求H(x)就會遇到上面提到的退化問題,如果使用了這種帶shortcut的結構,那麼可變參數部分的優化目標就不再是H(x),若用F(x)來代表需要優化的部分的話,則H(x)=F(x)+x,也就是F(x)=H(x)-x。因爲在單位映射的假設中y=x就相當於觀測值,所以F(x)就對應着殘差,因而叫殘差網絡。爲啥要這樣做,因爲作者認爲學習殘差F(X)比直接學習H(X)簡單!設想下,現在根據我們只需要去學習輸入和輸出的差值就可以了,絕對量變爲相對量(H(x)-x 就是輸出相對於輸入變化了多少),優化起來簡單很多。

考慮到x的維度與F(X)維度可能不匹配情況,需進行維度匹配。這裏論文中採用兩種方法解決這一問題(其實是三種,但通過實驗發現第三種方法會使performance急劇下降,故不採用):

  • zero_padding:對恆等層進行0填充的方式將維度補充完整。這種方法不會增加額外的參數
  • projection:在恆等層採用1x1的卷積核來增加維度。這種方法會增加額外的參數
    在這裏插入圖片描述

2、網絡結構
下圖展示了兩種形態的殘差模塊,左圖是常規殘差模塊,有兩個3×3卷積核卷積核組成,但是隨着網絡進一步加深,這種殘差結構在實踐中並不是十分有效。針對這問題,右圖的“瓶頸殘差模塊”(bottleneck residual block)可以有更好的效果,它依次由1×1、3×3、1×1這三個卷積層堆積而成,這裏的1×1的卷積能夠起降維或升維的作用,從而令3×3的卷積可以在相對較低維度的輸入上進行,以達到提高計算效率的目的。
在這裏插入圖片描述
在這裏插入圖片描述

3、殘差模塊代碼實現

def conv3x3(in_plans,out_planes,stride=1):
    return nn.Conv2d(in_plans,out_planes,kernel_size=3,stride=stride
                    ,padding=1,bias=False)

class BasicBlock(nn.Module):
    def __init__(self,inplanes,planes,stride=1,downsample=None):
        super(BasicBlock,self).__init__()
        self.conv1 = conv3x3(inplanes,planes,stride)
        self.bn1 = nn.BatchNorm2d(planes)
        self.relu = nn.ReLU(inplace=True)
        self.conv2 = conv3x3(planes,planes)
        self.bn2 = nn.BatchNorm2d(planes)
        self.downsample = downsample
        self.stride = stride
        
    def forward(self,x):
        residual = x
        
        x = self.conv1(x)
        x = self.bn1(x)
        x = self.relu(x)
        
        x = self.conv2(x)
        out = self.bn2(x)
        
        if self.downsample is not None:
            residual = self.downsample(out)
            
        out += residual
        out = self.relu(out)
        
        return out
        

PyTorch將上面介紹過這些網絡,都在torchvision.model 裏面,同時大部分網絡都有預訓練好的參數。可以根據具體任務進行遷移學習和微調。

參考文獻:
1、CNN網絡架構演進:從LeNet到DenseNet:https://www.cnblogs.com/skyfsm/p/8451834.html
2、《深度學習之Pytorch》

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章