圖像處理經典網絡

【注】本文采用 PyTorch 框架,基於 Fashion-MNIST 數據集。

1. LeNet

LeNet分爲卷積層塊和全連接層塊兩個部分。

1.1 卷積層塊

  • 卷積層塊⾥的基本單位是卷積層後接最⼤池化層:卷積層⽤來識別圖像⾥的空間模式,如線條和物體局部,之後的最⼤池化層則⽤來降低卷積層對位置的敏感性。

  • 在卷積層塊中:每個卷積層都使⽤ 5 \times 5 的窗⼝,並在輸出上使⽤ sigmoid 激活函數。第⼀個卷積層輸出通道數爲 6 ,第⼆個卷積層輸出通道數則增加到 16 。這是因爲第⼆個卷積層⽐第⼀個卷積層的輸⼊的⾼和寬要⼩,所以增加輸出通道使兩個卷積層的參數尺⼨類似。

  • 卷積層塊的兩個最⼤池化層的窗⼝形狀均爲 2 \times 2,且步幅爲 2 。由於池化窗⼝與步幅形狀相同,池化窗⼝在輸⼊上每次滑動所覆蓋的區域互不重疊。

  • 卷積層塊的輸出形狀爲(批量⼤⼩, 通道, ⾼, 寬)。

1.2 全連接層塊

  • 當卷積層塊的輸出傳⼊全連接層塊時,全連接層塊會將⼩批量中每個樣本變平(flatten)。也就是說,全連接層的輸⼊形狀將變成⼆維,其中第⼀維是⼩批量中的樣本,第⼆維是每個樣本變平後的向量表示,且向量⻓度爲通道、⾼和寬的乘積

  • 全連接層塊含 3 個全連接層。它們的輸出個數分別是 120、84 和 10 ,其中 10 爲輸出的類別個數。

1.3 PyTorch 實現

import torch 
import torch.nn as nn 
import torch.optim as optim 
import torchvision
import torchvision.transforms as transforms

# 定義 LeNet 網絡
class LeNet(nn.Module):
    def __init__(self):
        super().__init__()
        self.conv = nn.Sequential(
            nn.Conv2d(1, 6, 5),
            nn.Sigmoid(),
            nn.MaxPool2d(2, 2),
            nn.Conv2d(6, 16, 5),
            nn.Sigmoid(),
            nn.MaxPool2d(2, 2)
        )
        self.fc = nn.Sequential(
            nn.Linear(16*4*4, 120),
            nn.Sigmoid(),
            nn.Linear(120, 84),
            nn.Sigmoid(),
            nn.Linear(84, 10)
        )

    def forward(self, img):
        feature = self.conv(img)
        output = self.fc(feature.view(img.shape[0], -1))
        return output

2. AlexNet

AlexNet 與 LeNet 的設計理念⾮常相似,但也有顯著的區別。


2.1 網絡結構

  • AlexNet 包含 8 層變換,其中有 5 層卷積和 2 層全連接隱藏層,以及 1 個全連接輸出層。

  • AlexNet 將 sigmoid 激活函數改成了更加簡單的 ReLU 激活函數。

  • AlexNet 通過 Dropout 來控制全連接層的模型複雜度。

2.2 PyTorch 實現

import torch 
import torch.nn as nn
import torch.optim as optim 
import torchvision 
import torchvision.transforms as transforms 

# 定義 AlexNet
class AlexNet(nn.Module):
    def __init__(self):
        super(AlexNet, self).__init__()
        # 卷積層塊
        self.conv = nn.Sequential(
            nn.Conv2d(1, 96, 11, 4),
            nn.ReLU(),
            nn.MaxPool2d(3, 2),
            nn.Conv2d(96, 256, 5, 1, 2),
            nn.ReLU(),
            nn.MaxPool2d(3, 2),
            nn.Conv2d(256, 384, 3, 1, 1),
            nn.ReLU(),
            nn.Conv2d(384, 384, 3, 1, 1),
            nn.ReLU(),
            nn.Conv2d(384, 256, 3, 1, 1),
            nn.ReLU(),
            nn.MaxPool2d(3, 2)
        )
        # 全連接層塊
        self.fc = nn.Sequential(
            nn.Linear(256*5*5, 1024),
            nn.ReLU(),
            nn.Dropout(0.5),
            nn.Linear(1024, 1024),
            nn.ReLU(),
            nn.Dropout(0.5),
            nn.Linear(1024, 10)
        )
    
    def forward(self, img):
        feature = self.conv(img)
        output = self.fc(feature.view(img.shape[0], -1))
        return output 

3. VGG

3.1 網絡結構

  • VGG塊的組成規律是:連續使⽤數個相同的填充爲1、窗⼝形狀爲 3 \times 3 的卷積層後接上⼀個步幅爲2、窗⼝形狀爲 2 \times 2 的最⼤池化層。

  • 對於給定的感受野(與輸出有關的輸⼊圖⽚的局部⼤⼩),採⽤堆積的⼩卷積核優於採⽤⼤的卷積核,因爲可以增加⽹絡深度來保證學習更復雜的模式,⽽且代價還⽐較⼩(參數更少)。在 VGG 中,使⽤了 3 個 3 \times 3 卷積核來代替 7 \times 7 卷積核,使⽤了 2 個 3 \times 3 卷積核來代替 5 \times 5 卷積核。

3.2 PyTorch 實現

以下實現了一個簡單的 VGG-11 網絡。

import torch 
import torch.nn as nn
import torch.optim as optim 
import torchvision 
import torchvision.transforms as transforms 

# 定義網絡模型
class VGG(nn.Module):
    def __init__(self):
        super(VGG, self).__init__()
        # 卷積層
        self.vgg_block = nn.Sequential(
            # vgg_block_1
            nn.Conv2d(1, 32, 3, 1, 1),
            nn.ReLU(),
            nn.MaxPool2d(2, 2),
            # vgg_block_2
            nn.Conv2d(32, 64, 3, 1, 1),
            nn.ReLU(),
            nn.MaxPool2d(2, 2),
            # vgg_block_3
            nn.Conv2d(64, 128, 3, 1, 1),
            nn.Conv2d(128, 128, 3, 1, 1),
            nn.ReLU(),
            nn.MaxPool2d(2, 2),
            # vgg_block_4
            nn.Conv2d(128, 256, 3, 1, 1),
            nn.Conv2d(256, 256, 3, 1, 1),
            nn.ReLU(),
            nn.MaxPool2d(2, 2),
            # vgg_block_5
            nn.Conv2d(256, 256, 3, 1, 1),
            nn.Conv2d(256, 256, 3, 1, 1),
            nn.ReLU(),
            nn.MaxPool2d(2, 2) 
        )
        # 全連接層
        self.fc_block = nn.Sequential(
            nn.Flatten(),
            nn.Linear(256*7*7, 1024),
            nn.ReLU(),
            nn.Dropout(0.5),
            nn.Linear(1024, 1024),
            nn.ReLU(),
            nn.Dropout(0.5),
            nn.Linear(1024, 10)         
        )
    
    def forward(self, X):
        vgg = self.vgg_block(X)
        fc = self.fc_block(vgg)
        return fc

4. NiN

4.1 網絡結構

  • NiN 使⽤ 1 \times 1 的卷積層來替代全連接層。

  • NiN 塊是 NiN 中的基礎塊。它由⼀個卷積層加兩個充當全連接層的 1 \times 1 卷積層串聯⽽成。其中第⼀個卷積層的超參數可以⾃⾏設置,⽽第⼆和第三個卷積層的超參數⼀般是固定的。

  • NiN 去掉了 AlexNet 最後的 3 個全連接層,取⽽代之地,NiN 使⽤了輸出通道數等於標籤類別數的 NiN 塊,然後使⽤全局平均池化層對每個通道中所有元素求平均並直接⽤於分類。

4.2 PyTorch 實現

import torch 
import torch.nn as nn
import torch.optim as optim 
import torchvision 
import torchvision.transforms as transforms 

# 定義網絡模型
class NiN(nn.Module):
    def __init__(self):
        super(NiN, self).__init__()
        # 定義 NiN 塊
        def nin_block(in_channels, out_channels, kernel_size, stride, padding):
            blk = nn.Sequential(
                nn.Conv2d(in_channels, out_channels, kernel_size, stride, padding),
                nn.ReLU(),
                nn.Conv2d(out_channels, out_channels, kernel_size=1),
                nn.ReLU(),
                nn.Conv2d(out_channels, out_channels, kernel_size=1),
                nn.ReLU(),
            )
            return blk 

        self.nin = nn.Sequential(
            nin_block(1, 96, 11, 4, 0),
            nn.MaxPool2d(3, 2),
            nin_block(96, 256, 5, 1, 2),
            nn.MaxPool2d(3, 2),
            nin_block(256, 384, 3, 1, 1),
            nn.MaxPool2d(3, 2),
            nn.Dropout(0.5),
            nin_block(384, 10, 3, 1, 1),
            nn.AvgPool2d(5),
            nn.Flatten(1, -1),
        )
    
    def forward(self, X):
        output = self.nin(X)
        return output 

5. GoogLeNet

5.1 網絡結構

GoogLeNet 引入了並行連結的網絡結構,其基礎卷積塊稱爲 Inception 塊,其結構如下:


  • Inception 塊⾥有 4 條並⾏的線路。
  • 前 3 條線路使⽤窗⼝⼤⼩分別是 1 \time 13 \times 35 \times 5 的卷積層來抽取不同空間尺⼨下的信息,其中中間 2 個線路會對輸⼊先做 1 \times 1 卷積來減少輸⼊通道數,以降低模型複雜度。
  • 第 4 條線路則使⽤ 3 \times 3 最⼤池化層,後接 1 \times 1 卷積層來改變通道數。
  • 4 條線路都使⽤了合適的填充來使輸⼊與輸出的⾼和寬⼀致。最後我們將每條線路的輸出在通道維上連結,並輸⼊接下來的層中去。

GoogLeNet 跟 VGG ⼀樣,在主體卷積部分中使⽤ 5 個模塊(block),每個模塊之間使⽤步幅爲2的 3 \times 3 最⼤池化層來減⼩輸出⾼寬。

5.2 PyTorch 實現

import torch 
import torch.nn as nn
import torch.optim as optim 
import torch.nn.functional as F
import torchvision 
import torchvision.transforms as transforms 

# 定義 GlobalAvgPool2d 層
class GlobalAvgPool2d(nn.Module):
    def __init__(self):
        super(GlobalAvgPool2d, self).__init__()
    
    def forward(self, X):
        return F.avg_pool2d(X, kernel_size=X.size()[2:])

# 定義 Inception 塊
class Inception(nn.Module):
    def __init__(self, in_c, c1, c2, c3, c4):
        super(Inception, self).__init__()
        # 線路 1 :單 1 x 1 卷積層
        self.p1_1 = nn.Conv2d(in_c, c1, 1)
        # 線路 2 :1 x 1 卷積層後接 3 x 3 卷積層
        self.p2_1 = nn.Conv2d(in_c, c2[0], 1)
        self.p2_2 = nn.Conv2d(c2[0], c2[1], 3, 1, 1)
        # 線路 3 :1 x 1 卷積層後接 5 x 5 卷積層
        self.p3_1 = nn.Conv2d(in_c, c3[0], 1)
        self.p3_2 = nn.Conv2d(c3[0], c3[1], 5, 1, 2)
        # 線路 4 :3 x 3 最大池化後接 1 x 1 卷積層
        self.p4_1 = nn.MaxPool2d(3, 1, 1)
        self.p4_2 = nn.Conv2d(in_c, c4, 1)

    def forward(self, X):
        p1 = F.relu(self.p1_1(X))
        p2 = F.relu(self.p2_2(F.relu(self.p2_1(X))))
        p3 = F.relu(self.p3_2(F.relu(self.p3_1(X))))
        p4 = F.relu(self.p4_2(self.p4_1(X)))
        return torch.cat((p1, p2, p3, p4), dim=1)

# 定義網絡模型
class GoogLeNet(nn.Module):
    def __init__(self):
        super(GoogLeNet, self).__init__()
        self.googlenet = nn.Sequential(
            # 第一模塊
            nn.Conv2d(1, 64, 7, 2, 3),
            nn.ReLU(),
            nn.MaxPool2d(3, 2, 1),
            # 第二模塊
            nn.Conv2d(64, 64, 1),
            nn.Conv2d(64, 192, 3, 1),
            nn.MaxPool2d(3, 2, 1), 
            Inception(192, 64, (96, 128), (16, 32), 32),
            Inception(256, 128, (128, 192), (32, 96), 64),
            nn.MaxPool2d(3, 2, 1),
            # 第三模塊
            Inception(480, 192, (96, 208), (16, 48), 64),
            Inception(512, 160, (112, 224), (24, 64), 64),
            Inception(512, 128, (128, 256), (24, 64), 64),
            Inception(512, 112, (144, 288), (32, 64), 64),
            Inception(528, 256, (160, 320), (32, 128), 128),
            nn.MaxPool2d(3, 2, 1),
            # 第四模塊
            Inception(832, 256, (160, 320), (32, 128), 128),
            Inception(832, 384, (192, 384), (48, 128), 128),
            GlobalAvgPool2d(),
            # 輸出層
            nn.Flatten(1, -1),
            nn.Linear(1024, 10),
        )
    
    def forward(self, X):
        output = self.googlenet(X)
        return output 

6. ResNet

6.1 網絡結構

ResNet 引入了恆等映射的跨層連結,其基礎塊稱爲殘差塊(Residual),如下所示:


  • ResNet 沿⽤了 VGG 全 3 \times 3 卷積層的設計。殘差塊⾥⾸先有 2 個有相同輸出通道數的 3 \times 3 卷積層。每個卷積層後接⼀個批量歸⼀化層和 ReLU 激活函數。
  • 然後我們將輸⼊跳過這兩個卷積運算後直接加在最後的 ReLU 激活函數前。這樣的設計要求兩個卷積層的輸出與輸⼊形狀⼀樣,從⽽可以相加。
  • 如果想改變通道數,就需要引⼊⼀個額外的 1 \times 1 卷積層來將輸⼊變換成需要的形狀後再做相加運算。

GoogLeNet 在後⾯接了 4 個由 Inception 塊組成的模塊。ResNet 則使⽤ 4 個由殘差塊組成的模塊,每個模塊使⽤若⼲個同樣輸出通道數的殘差塊。

6.2 PyTorch 實現

import torch 
import torch.nn as nn
import torch.optim as optim 
import torch.nn.functional as F
import torchvision 
import torchvision.transforms as transforms 

# 定義 GlobalAvgPool2d 層
class GlobalAvgPool2d(nn.Module):
    def __init__(self):
        super(GlobalAvgPool2d, self).__init__()
    
    def forward(self, X):
        return F.avg_pool2d(X, kernel_size=X.size()[2:])

# 定義 Residual 塊
class Residual(nn.Module):
    def __init__(self, in_channels, out_channels, stride=1, XconvX=False):
        super(Residual, self).__init__()
        self.residual = nn.Sequential(
            nn.Conv2d(in_channels, out_channels, 3, stride, 1),
            nn.BatchNorm2d(out_channels),
            nn.ReLU(),
            nn.Conv2d(out_channels, out_channels, 3, 1, 1),
            nn.BatchNorm2d(out_channels),
        )
        if XconvX:
            self.XconvX = nn.Conv2d(in_channels, out_channels, 1, stride) 
        else: 
            self.XconvX = None

    def forward(self, X):
        Y = self.residual(X)
        if self.XconvX:
            X = self.XconvX(X)
        return F.relu(Y + X)

# 定義網絡模型
class ResNet(nn.Module):
    def __init__(self):
        super(ResNet, self).__init__()
        self.resnet = nn.Sequential(
            # 輸入層
            nn.Conv2d(1, 64, 7, 2, 3),
            nn.BatchNorm2d(64),
            nn.ReLU(),
            nn.MaxPool2d(3, 2, 1),
            # 第一模塊
            Residual(64, 64, 1),
            Residual(64, 64, 1),
            # 第二模塊
            Residual(64, 128, 2, True),
            Residual(128, 128, 1), 
            # 第三模塊
            Residual(128, 256, 2, True),
            Residual(256, 256, 1),
            # 第四模塊
            Residual(256, 512, 2, True),
            Residual(512, 512, 1),
            # 輸出層
            GlobalAvgPool2d(),
            nn.Flatten(1, -1),
            nn.Linear(512, 10),
        )
    
    def forward(self, X):
        output = self.resnet(X)
        return output 

7. DenseNet

7.1 網絡結構

DenseNet 的主要局部結構如下:


DenseNet 網絡結構如下:


DenseNet 的基礎塊稱爲稠密塊(DenseBlock)和過渡層(TransitionLayer):

  • 前者定義了輸⼊和輸出是如何連結的,後者則⽤來控制通道數,使之不過⼤。
  • 稠密塊由多個「批量歸一化、激活和卷積」結構構成,其中卷積塊的通道數控制了輸出通道數相對於輸⼊通道數的增⻓,因此也被稱爲增⻓率(growth rate)。
  • 過渡層通過 1 \times 1 卷積層來減⼩通道數,並使⽤步幅爲 2 的平均池化層減半⾼和寬,從⽽進⼀步降低模型複雜度。

DenseNet 與 ResNet 的主要區別在於:

  • 首先,DenseNet ⾥模塊的輸出不是像 ResNet 那樣殘差映射 B 和恆等映射 A 相加後輸出,⽽是在通道維上將二者連接後輸出。
  • 其次,在一個 DenseBlock 中,DenseNet 中恆等映射 A 直接和殘差映射 B 後的所有層連接在了一起,故稱爲「稠密連接」。

7.2 PyTorch 實現

import torch 
import torch.nn as nn
import torch.optim as optim 
import torch.nn.functional as F
import torchvision 
import torchvision.transforms as transforms 

# 定義 GlobalAvgPool2d 層
class GlobalAvgPool2d(nn.Module):
    def __init__(self):
        super(GlobalAvgPool2d, self).__init__()
    
    def forward(self, X):
        return F.avg_pool2d(X, kernel_size=X.size()[2:])

# 定義批量歸一化、激活和卷積結構
def conv_block(in_channels, out_channels):
    blk = nn.Sequential(
        nn.BatchNorm2d(in_channels),
        nn.ReLU(),
        nn.Conv2d(in_channels, out_channels, 3, 1, 1)
    )
    return blk 

# 定義 DenseBlock 塊
class DenseBlock(nn.Module):
    def __init__(self, in_channels, out_channels, num_convs):
        super(DenseBlock, self).__init__()
        dense_block = []
        for i in range(num_convs):
            in_c = in_channels + i*out_channels 
            dense_block.append(conv_block(in_c, out_channels))
        self.dense_block = nn.ModuleList(dense_block)
        self.out_channels = in_channels + num_convs*out_channels

    def forward(self, X):
        for blk in self.dense_block:
            Y = blk(X)
            X = torch.cat((X, Y), dim=1)
        return X 

# 定義 TransitionBlock 塊
class TransitionBlock(nn.Module):
    def __init__(self, in_channels, out_channels):
        super(TransitionBlock, self).__init__()
        self.transition_block = nn.Sequential(
            nn.BatchNorm2d(in_channels),
            nn.ReLU(),
            nn.Conv2d(in_channels, out_channels, 1, 1, 0),
            nn.AvgPool2d(2, 2),
        )

    def forward(self, X):
        output = self.transition_block(X)
        return output

# 定義網絡模型
class DenseNet(nn.Module):
    def __init__(self):
        super(DenseNet, self).__init__()
        self.dense_net = nn.Sequential()
        self.dense_net.add_module(
            'InputLayer',
            nn.Sequential(
                # 輸入層
                nn.Conv2d(1, 64, 7, 2, 3),
                nn.BatchNorm2d(64),
                nn.ReLU(),
                nn.MaxPool2d(3, 2, 1),
            )
        )
        # 中間 4 個稠密塊 + 3 個過渡層
        num_channels, growth_rate = 64, 32 
        num_convs_in_dense_block = [4, 4, 4, 4]
        for i, num_convs in enumerate(num_convs_in_dense_block):
            # 稠密塊
            dense_block = DenseBlock(num_channels, growth_rate, num_convs)
            self.dense_net.add_module('DenseBlock %d' % i, dense_block)
            num_channels = dense_block.out_channels 
            if i != len(num_convs_in_dense_block) - 1:
                # 過渡層
                self.dense_net.add_module('TransitionBlock %d' % i, TransitionBlock(num_channels, num_channels // 2))
                num_channels = num_channels // 2 
        self.dense_net.add_module(
            'OutputLayer',
            nn.Sequential(
                # 輸出層
                nn.BatchNorm2d(num_channels),
                nn.ReLU(),
                GlobalAvgPool2d(),
                nn.Flatten(1, -1),
                nn.Linear(num_channels, 10),
            )
        )

    def forward(self, X):
        output = self.dense_net(X)
        return output 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章