【動手學深度學習】#7 現代卷積神經網絡

主要參考學習資料:

《動手學深度學習》阿斯頓·張 等 著

【動手學深度學習 PyTorch版】嗶哩嗶哩@跟李牧學AI

概述

  • 硬件性能和大數據的發展為深度卷積神經網絡AlexNet)的實現提供了條件。
  • VGG首次將塊的思想用于搭建網絡。
  • NiN將多層感知機應用在每個像素上以取代最終的全連接層。
  • GoogLeNet將不同卷積核大小的卷積層并行連接。
  • 批量規范化可加速深層網絡的收斂。
  • ResNet用函數參數化思想緩解了深層網絡的退化問題。
  • DenseNet是ResNet在函數展開上的邏輯擴展。

目錄

  • 7.1 深度卷積神經網絡(AlexNet)
    • 7.1.1 學習表征
      • 1. 數據
      • 2. 硬件
    • 7.1.2 AlexNet
    • 7.1.3 讀取數據集
    • 7.1.4 訓練AlexNet
  • 7.2 使用塊的網絡(VGG)
    • 7.2.1 VGG塊
    • 7.2.2 VGG網絡
    • 7.2.3 訓練模型
  • 7.3 網絡中的網絡(NiN)
    • 7.3.1 NiN塊
    • 7.3.2 NiN模型
    • 7.3.3 訓練模型
  • 7.4 含并行連接的網絡(GoogLeNet)
    • 7.4.1 Inception塊
    • 7.4.2 GoogLeNet模型
    • 7.4.3 訓練模型
  • 7.5 批量規范化
    • 7.5.1 訓練深層網絡
    • 7.5.2 批量規范化層
      • 1.全連接層
      • 2.卷積層
      • 3.預測
    • 7.5.3 從零實現
    • 7.5.4 使用批量規范化層的LeNet
    • 7.5.5 簡明實現
  • 7.6 殘差網絡(ResNet)
    • 7.6.1 函數類
    • 7.6.2 殘差塊
    • 7.6.3 ResNet模型
    • 7.6.4 訓練模型
  • 7.7 稠密連接網絡(DenseNet)
    • 7.7.1 從ResNet到DenseNet
    • 7.7.2 稠密塊體
    • 7.7.3 過渡層
    • 7.7.4 DenseNet模型
    • 7.7.5 訓練模型

7.1 深度卷積神經網絡(AlexNet)

提出LeNet后的一段時間里,出于以下幾點原因,神經網絡未能超越機器學習主導圖像分類領域:

  • 硬件不足以開發出有大量參數的深層多通道多層卷積神經網絡。
  • 數據集相對較小。
  • 訓練神經網絡的一些關鍵技巧仍然確實。

和神經網絡訓練端到端的系統不同,經典機器學習的過程如下:

  • ①獲取一個有趣的數據集。
  • ②根據光學、幾何學、其他知識以及偶然的發現,手動對特征數據集進行預處理。
  • ③通過標準的特征提取算法或其他手動調整的流水線來輸入數據。
  • ④將提取的特征送入最喜歡的分類器中以訓練分類器。

7.1.1 學習表征

另一個影響圖像分類領域發展的重要因素是圖像特征的提取方法。

在2012年前,圖像特征是機械地計算出來的,設計一套新的特征函數、改進結果并撰寫論文是盛極一時的潮流。

另一批研究人員認為特征本身應該被學習,并在合理的復雜性前提下,應該由多個共同學習的神經網絡層組成,基于此提出的AlexNet在2012年ImageNet挑戰賽中取得了轟動一時的成績。

在AlexNet的底層,模型學習到了一些類似傳統濾波器的特征提取器,而較高層建立在這些底層表示的基礎上以表示更大的特征,更高的層可以檢測整個物體,最終的隱藏神經元可以學習圖像的綜合表示。這一突破可歸因于以下兩個關鍵因素:

1. 數據

包含許多特征的深度模型需要大量有標簽數據才能顯著優于基于凸優化的傳統方法,然而限于早期計算機有限的存儲資源和研究預算,大部分研究只基于小的公開數據集。隨著大數據的發展,直至2009年,擁有涵蓋一千個類別的一百萬多個樣本的ImageNet數據集發布,以此為基礎發起的挑戰賽推動了計算機視覺和機器學習研究的發展。

2. 硬件

深度學習對計算資源要求很高,訓練可能需要數百輪,每次迭代需要通過代價高昂的許多線性代數層傳遞數據,因此早期優化凸目標的簡單算法是研究人員的首選。GPU訓練神經網絡改變了這一格局。

圖形處理器(GPU)早期用來加速圖形處理以服務于電腦游戲。GPU可優化高吞吐量的 4 × 4 4\times4 4×4矩陣和向量乘法,這些數學運算與卷積層的計算驚人地相似。

GPU相對于CPU的優勢如下:

  • CPU的核時鐘頻率高,性能高,但制造成本也高,需要大量芯片面積、復雜支持結構,且單任務性能相對較差,總體性價比不高。
  • GPU由成百上千個小處理單元組成,通常被分成更大的組。單個核時鐘頻率低,性能較弱,但龐大的核數量使GPU比CPU快幾個數量級。其原因在于功耗隨時鐘頻率呈平方級增長,且GPU簡單的內核更節能,最后GPU滿足了深度學習需要的高內存帶寬。

7.1.2 AlexNet

AlexNet與LeNet架構對比

卷積層(更多的層數和輸出通道):

全連接層(更多的輸出):

除此之外,AlexNet還采用了早期沒有的ReLU激活函數和暫退法。

import torch  
from torch import nn  
from d2l import torch as d2l  
from matplotlib import pyplot as pltnet = nn.Sequential(  nn.Conv2d(1, 96, kernel_size=11, stride=4, padding=1), nn.ReLU(),  nn.MaxPool2d(kernel_size=3, stride=2),  nn.Conv2d(96, 256, kernel_size=5, padding=2), nn.ReLU(),  nn.MaxPool2d(kernel_size=3, stride=2),  nn.Conv2d(256, 384, kernel_size=3, padding=1), nn.ReLU(),  nn.Conv2d(384, 384, kernel_size=3, padding=1), nn.ReLU(),  nn.Conv2d(384, 256, kernel_size=3, padding=1), nn.ReLU(),  nn.MaxPool2d(kernel_size=3, stride=2),  nn.Flatten(),  nn.Linear(6400, 4096), nn.ReLU(),  nn.Dropout(p=0.5),  nn.Linear(4096, 4096), nn.ReLU(),  nn.Dropout(p=0.5),  nn.Linear(4096, 10)  
)

7.1.3 讀取數據集

訓練ImageNet模型可能需要數小時乃至數天,因此我們仍使用Fashion-MNIST數據集,但為了使用AlexNet架構需要將其分辨率提高到 224 × 224 224\times224 224×224像素。

batch_size = 128  
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size, resize=224)

7.1.4 訓練AlexNet

lr, num_epochs = 0.01, 10  
d2l.train_ch6(net, train_iter, test_iter, num_epochs, lr, d2l.try_gpu())  
plt.show()

訓練結果:

loss 0.326, train acc 0.880, test acc 0.880
407.4 examples/sec on cuda:0

7.2 使用塊的網絡(VGG)

使用塊的想法首先出現在VGG網絡中。

7.2.1 VGG塊

一個VGG塊由一系列卷積層組成,再加上用于空間降采樣的最大匯聚層。

import torch  
from torch import nn  
from d2l import torch as d2l  
from matplotlib import pyplot as plt#參數分別為卷積層數量、輸入通道數量、輸出通道數量
def vgg_block(num_convs, in_channel, out_channel):  layers = []  for _ in range(num_convs):  layers.append(nn.Conv2d(in_channel, out_channel,   kernel_size=3, padding=1))  layers.append(nn.ReLU())  in_channel = out_channel  layers.append(nn.MaxPool2d(kernel_size=2, stride=2))  return nn.Sequential(*layers)

7.2.2 VGG網絡

VGG網絡由數個VGG塊構成的卷積層和全連接層組成,每個VGG塊由超參數conv_arch指定卷積層個數和每個卷積層的輸出通道數。

conv_arch = ((1, 64), (1, 128), (2, 256), (2, 512), (2, 512))  def vgg(conv_arch):  conv_blks = []  in_channels = 1  for (num_convs, out_channels) in conv_arch:  conv_blks.append(vgg_block(num_convs, in_channels, out_channels))  in_channels = out_channels  return nn.Sequential(  *conv_blks, nn.Flatten(),  nn.Linear(out_channels * 7 * 7, 4096), nn.ReLU(), nn.Dropout(0.5),  nn.Linear(4096, 4096), nn.ReLU(), nn.Dropout(0.5),  nn.Linear(4096, 10))  net = vgg(conv_arch)

7.2.3 訓練模型

由于VGG-11比AlexNet計算量更大,我們構建一個通道數較少的網絡,足夠用于訓練Fashion-MNIST數據集。

#將每層的輸出通道數除以4
ratio = 4  
small_conv_arch = [(pair[0], pair[1] // ratio) for pair in conv_arch]  
net = vgg(small_conv_arch)  lr, num_epochs, batch_size = 0.05, 10, 128  
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size, resize=224)  
d2l.train_ch6(net, train_iter, test_iter, num_epochs, lr, d2l.try_gpu())  
plt.show()

訓練結果:

loss 0.179, train acc 0.933, test acc 0.917
333.2 examples/sec on cuda:0

7.3 網絡中的網絡(NiN)

LeNet、AlexNet和VGG最終都使用全連接層對特征進行表征處理,然而全連接層可能會完全放棄表征的空間結構。NiN的解決方案是在每個像素的通道上分別使用多層感知機( 1 × 1 1\times1 1×1卷積層)。

7.3.1 NiN塊

NiN塊以一個普通卷積層開始,后面是兩個 1 × 1 1\times1 1×1卷積層,它們充當帶有ReLU激活函數的逐像素全連接層。

import torch  
from torch import nn  
from d2l import torch as d2l  def nin_block(in_channels, out_channels, kernel_size, strides, padding):  return nn.Sequential(  nn.Conv2d(in_channels, out_channels, kernel_size, strides, 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())

7.3.2 NiN模型

NiN使用卷積核大小分別為 11 × 11 11\times11 11×11 5 × 5 5\times5 5×5 3 × 3 3\times3 3×3的卷積層,輸出通道數與AlexNet相同。每個NiN塊后有一個最大匯聚層,匯聚窗口形狀為 3 × 3 3\times3 3×3,步幅為 2 2 2。NiN完全取消了全連接層,而是使用一個輸出通道數等于標簽類別數的NiN塊和一個全局平均匯聚層來生成對數幾率。

net = nn.Sequential(  nin_block(1, 96, kernel_size=11, strides=4, padding=0),  nn.MaxPool2d(kernel_size=3, stride=2),  nin_block(96, 256, kernel_size=5, strides=1, padding=2),  nn.MaxPool2d(kernel_size=3, stride=2),  nin_block(256, 384, kernel_size=3, strides=1, padding=1),  nn.MaxPool2d(kernel_size=3, stride=2),  nin_block(384, 10, kernel_size=3, strides=1, padding=1),  nn.AdaptiveAvgPool2d(1),  nn.Flatten())

7.3.3 訓練模型

lr, num_epochs, batch_size = 0.1, 10, 128  
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size, resize=224)  
d2l.train_ch6(net, train_iter, test_iter, num_epochs, lr, d2l.try_gpu())

訓練結果:

loss 0.310, train acc 0.885, test acc 0.885
385.2 examples/sec on cuda:0

7.4 含并行連接的網絡(GoogLeNet)

GoogLeNet在2014年的ImageNet挑戰賽大放異彩,它吸收并改進了NiN中串聯網絡的思想,還提出了使用不同大小的卷積核組合是有利的以解決多大的卷積核最合適的問題。

7.4.1 Inception塊

Inception塊 4 4 4條并行路徑組成。前 3 3 3條路徑使用卷積核大小為 1 × 1 1\times1 1×1 3 × 3 3\times3 3×3 5 × 5 5\times5 5×5的卷積層,從不同的空間大小提取信息。中間的 2 2 2條路徑在輸入上執行 1 × 1 1\times1 1×1卷積以減少通道數從而降低模型復雜度。第 4 4 4條路徑使用 3 × 3 3\times3 3×3最大匯聚層,然后使用 1 × 1 1\times1 1×1卷積層改變通道數。所有路徑都使用合適的填充使輸入和輸出的高度和寬度一致,最后每條路徑的輸出在通道維度上合并構成Inception塊的輸出。Inception塊的超參數是每層輸出通道數。

import torch  
from torch import nn  
from torch.nn import functional as F  
from d2l import torch as d2l  
from matplotlib import pyplot as pltclass Inception(nn.Module):  def __init__(self, in_channels, c1, c2, c3, c4, **kwargs):  super(Inception, self).__init__(**kwargs)  #編號代表第幾條路徑的第幾層self.p1_1 = nn.Conv2d(in_channels, c1, kernel_size=1)  self.p2_1 = nn.Conv2d(in_channels, c2[0], kernel_size=1)  self.p2_2 = nn.Conv2d(c2[0], c2[1], kernel_size=3, padding=1)  self.p3_1 = nn.Conv2d(in_channels, c3[0], kernel_size=1)  self.p3_2 = nn.Conv2d(c3[0], c3[1], kernel_size=5, padding=2)  self.p4_1 = nn.MaxPool2d(kernel_size=3, stride=1, padding=1)  self.p4_2 = nn.Conv2d(in_channels, c4[0], kernel_size=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)

7.4.2 GoogLeNet模型

GoogLeNet使用 9 9 9個Inception塊和全局平均匯聚層的堆疊來生成其估計值,Inception塊之間的最大匯聚層可降低維度。

模塊一

  • 64 64 64通道 7 × 7 7\times7 7×7卷積層
  • 3 × 3 3\times3 3×3最大匯聚層
b1 = nn.Sequential(nn.Conv2d(1, 64, kernel_size=7, stride=2, padding=3),  nn.ReLU(),  nn.MaxPool2d(kernel_size=3, stride=2, padding=1))  

模塊二

  • 64 64 64通道 1 × 1 1\times1 1×1卷積層
  • 192 192 192通道 3 × 3 3\times3 3×3卷積層
  • 3 × 3 3\times3 3×3最大匯聚層
b2 = nn.Sequential(nn.Conv2d(64, 64, kernel_size=1),  nn.ReLU(),  nn.Conv2d(64, 192, kernel_size=3, padding=1),  nn.ReLU(),  nn.MaxPool2d(kernel_size=3, stride=2, padding=1))  

模塊三

  • 256 ( 64 + 128 + 32 + 32 ) 256(64+128+32+32) 256(64+128+32+32)通道Inception塊
  • 480 ( 128 + 192 + 96 + 64 ) 480(128+192+96+64) 480(128+192+96+64)通道Inception塊
  • 3 × 3 3\times3 3×3最大匯聚層
b3 = nn.Sequential(Inception(192, 64, (96, 128), (16, 32), 32),  Inception(256, 128, (128, 192), (32, 96), 64),  nn.MaxPool2d(kernel_size=3, stride=2, padding=1))  

模塊四

  • 512 ( 192 + 208 + 48 + 64 ) 512(192+208+48+64) 512(192+208+48+64)通道Inception塊
  • 512 ( 160 + 224 + 64 + 64 ) 512(160+224+64+64) 512(160+224+64+64)通道Inception塊
  • 512 ( 128 + 256 + 64 + 64 ) 512(128+256+64+64) 512(128+256+64+64)通道Inception塊
  • 528 ( 112 + 288 + 64 + 64 ) 528(112+288+64+64) 528(112+288+64+64)通道Inception塊
  • 832 ( 256 + 320 + 128 + 128 ) 832(256+320+128+128) 832(256+320+128+128)通道Inception塊
  • 3 × 3 3\times3 3×3最大匯聚層
b4 = nn.Sequential(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(kernel_size=3, stride=2, padding=1))

模塊五

  • 832 ( 256 + 320 + 128 + 128 ) 832(256+320+128+128) 832(256+320+128+128)通道Inception塊
  • 1024 ( 384 + 384 + 128 + 128 ) 1024(384+384+128+128) 1024(384+384+128+128)通道Inception塊
  • 全局平均匯聚層
  • 展平層
b5 = nn.Sequential(Inception(832, 256, (160, 320), (32, 128), 128),  Inception(832, 384, (192, 384), (48, 128), 128),  nn.AdaptiveAvgPool2d(1),  nn.Flatten())  #再連接一個輸出個數為標簽類別數的全連接層
net = nn.Sequential(b1, b2, b3, b4, b5, nn.Linear(1024, 10))

7.4.3 訓練模型

GoogLeNet模型計算復雜,而且不如VGG便于修改通道數,為縮短訓練過程我們將輸入的高度和寬度從 224 224 224降為 96 96 96

lr, num_epochs, batch_size = 0.1, 10, 128  
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size, resize=96)  
d2l.train_ch6(net, train_iter, test_iter, num_epochs, lr, d2l.try_gpu())

訓練結果:

loss 0.248, train acc 0.904, test acc 0.864
601.7 examples/sec on cuda:0

7.5 批量規范化

訓練深層神經網絡十分困難,特別是想要在較短的時間內使它們收斂。批量規范化可持續加速深層網絡的收斂。

7.5.1 訓練深層網絡

神經網絡需要批量規范化層的原因如下:

  • 數據預處理的方式通常會對最終結果產生巨大影響。
  • 中間層中的變量可能具有更廣的變化范圍,批量規范化的發明者非正式地假設變量分布中的這種偏移可能會阻礙網絡的收斂。
  • 更深層的網絡更復雜,也更容易過擬合,意味著正則化變得更加重要。

另一種說法是,由于深層網絡得到的梯度比淺層大,參數更新也會更快,因而當輸入數據的分布發生變化時,深層網絡的參數會受到更大的影響而極不穩定。

批量規范化的原理是在每次訓練迭代中首先規范化輸入,即減去其均值并除以其標準差,使其均值為 0 0 0,標準差為 1 1 1,再應用比例系數和比例偏移。只有使用足夠大的小批量,批量規范化才是有效且穩定的。對于來自小批量 B B B的輸入 x \boldsymbol x x

B N ( x ) = γ ⊙ x ? μ ^ B σ ^ B + β \mathrm{BN}(\boldsymbol x)=\gamma\odot\displaystyle\frac{\boldsymbol x-\hat{\boldsymbol\mu}_B}{\hat{\boldsymbol\sigma}_B}+\beta BN(x)=γσ^B?x?μ^?B??+β

由于標準化處理是一個主觀選擇,因此引入模型學習的與 x \boldsymbol x x形狀相同的拉伸參數 γ \gamma γ偏移參數 β \beta β。小批量 B B B的樣本均值和標準差如下計算:

μ ^ B = 1 ∣ B ∣ ∑ x ∈ B x \hat{\boldsymbol\mu}_B=\displaystyle\frac1{|B|}\sum_{\boldsymbol x\in B}\boldsymbol x μ^?B?=B1?xB?x

σ ^ B 2 = 1 ∣ B ∣ ∑ x ∈ B ( x ? μ ^ B ) 2 + ? \hat{\boldsymbol\sigma}^2_B=\displaystyle\frac1{|B|}\sum_{\boldsymbol x\in B}(\boldsymbol x-\hat{\boldsymbol\mu}_B)^2+\epsilon σ^B2?=B1?xB?(x?μ^?B?)2+?

小常量 ? > 0 \epsilon>0 ?>0確保分母不為零。

均值和方差的噪聲估計抵消了縮放效應。事實證明,這些優化中的各種噪聲源通常會實現更快的訓練和較少的過擬合,但尚未在理論上明確證明。

7.5.2 批量規范化層

1.全連接層

通常我們將批量規范化層置于全連接層中的仿射變換與激活函數之間:

h = ? ( B N ( W x + b ) ) \boldsymbol h=\phi(\mathrm{BN}(\boldsymbol{Wx}+b)) h=?(BN(Wx+b))

2.卷積層

卷積層可以在卷積層之后和非線性激活函數之前應用批量規范化。在多輸出通道情況下,每個通道有自己的拉伸參數和偏移參數以對各自的輸出執行批量規范化。假設小批量包含 m m m個樣本,且對于每個通道卷積的輸出高度為 p p p、寬度為 q q q,則我們在每個輸出通道上的 m ? p ? q m\cdot p\cdot q m?p?q個元素進行批量規范化。

3.預測

批量規范化在訓練和預測時的行為通常不同,我們不再需要樣本噪聲,也可能需要對單個樣本進行預測。一種常用的方法是使用訓練集移動平均所得的樣本均值和方差來得到確定的輸出。移動平均是在不知道全局數據的情況下對均值和方差進行動態估計的方法,其將過去樣本的移動平均估計值與新樣本的統計值加權相加。

7.5.3 從零實現

import torch  
from torch import nn  
from d2l import torch as d2l  
from matplotlib import pyplot as pltdef batch_norm(X, gamma, beta, moving_mean, moving_var, eps, momentum): #判斷是預測模式還是訓練模式 if not torch.is_grad_enabled():  #預測模式傳入移動平均所得均值和方差X_hat = (X - moving_mean) / torch.sqrt(moving_var + eps)  else:  #限定X來自全連接層或卷積層assert len(X.shape) in (2, 4)  if len(X.shape) == 2:  #全連接層計算特征維上的均值和方差mean = X.mean(dim=0)  var = X.var(dim=0)  else:  #卷積層計算通道上(axis=1)的均值和方差#保持X的形狀以便廣播mean = X.mean(dim=(0, 2, 3), keepdim=True)  var = X.var(dim=(0, 2, 3), keepdim=True)  #訓練模式用當前均值和方差做標準化X_hat = (X - mean) / torch.sqrt(var + eps)  #更新移動平均的均值和方差moving_mean = momentum * moving_mean + (1 - momentum) * mean  moving_var = momentum * moving_var + (1 - momentum) * var  #縮放和移位Y = gamma * X_hat + beta  return Y, moving_mean.detach(), moving_var.detach()#批量規范化層
class BatchNorm(nn.Module):  #num_features:全連接層的輸出數量或卷積層的輸出通道數#num_dims:2表示全連接層,4表示卷積層def __init__(self, num_features, num_dims):  super().__init__()  if num_dims == 2:  shape = (1, num_features)  else:  shape = (1, num_features, 1, 1)  #初始化參與梯度的拉伸參數、偏移參數self.gamma = nn.Parameter(torch.ones(shape))  self.beta = nn.Parameter(torch.zeros(shape)) #初始化非模型參數移動均值和移動方差 self.moving_mean = torch.zeros(shape)  self.moving_var = torch.ones(shape)  def forward(self, X):  #同一設備計算if self.moving_mean.device != X.device:  self.moving_mean = self.moving_mean.to(X.device)  self.moving_var = self.moving_var.to(X.device)  Y, self.moving_mean, self.moving_var = batch_norm(X, self.gamma, self.beta, self.moving_mean, self.moving_var, eps=1e-5, momentum=0.9)  return Y

7.5.4 使用批量規范化層的LeNet

#將批量規范化層應用在卷積層和全連接層之后、激活函數之前
net = nn.Sequential(  nn.Conv2d(1, 6, kernel_size=5), BatchNorm(6, num_dims=4), nn.Sigmoid(),  nn.AvgPool2d(kernel_size=2, stride=2),  nn.Conv2d(6, 16, kernel_size=5), BatchNorm(16, num_dims=4), nn.Sigmoid(),  nn.AvgPool2d(kernel_size=2, stride=2), nn.Flatten(),  nn.Linear(16 * 4 * 4, 120), BatchNorm(120, num_dims=2), nn.Sigmoid(),  nn.Linear(120, 84), BatchNorm(84, num_dims=2), nn.Sigmoid(),  nn.Linear(84, 10))  #訓練與6.6相同,但學習率大得多 
lr, num_epochs, batch_size = 1.0, 10, 256  
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size=batch_size)  
d2l.train_ch6(net, train_iter, test_iter, num_epochs, lr, d2l.try_gpu())  
plt.show()

訓練效果:

loss 0.267, train acc 0.902, test acc 0.842
21184.6 examples/sec on cuda:0

7.5.5 簡明實現

pytorch框架定義了批量規范化層,其中BatchNorm1d應用于全連接層,BatchNorm2d應用于卷積層。

net = nn.Sequential(  nn.Conv2d(1, 6, kernel_size=5), nn.BatchNorm2d(6, num_dims=4), nn.Sigmoid(),  nn.AvgPool2d(kernel_size=2, stride=2),  nn.Conv2d(6, 16, kernel_size=5), nn.BatchNorm2d(16, num_dims=4), nn.Sigmoid(),  nn.AvgPool2d(kernel_size=2, stride=2), nn.Flatten(),  nn.Linear(16 * 4 * 4, 120), nn.BatchNorm1d(120, num_dims=2), nn.Sigmoid(),  nn.Linear(120, 84), nn.BatchNorm1d(84, num_dims=2), nn.Sigmoid(),  nn.Linear(84, 10))  

7.6 殘差網絡(ResNet)

殘差網絡基于網絡退化問題而提出,當時的研究者發現,模型的深度越大,其訓練效果未必會提升,反而可能下降。

7.6.1 函數類

假設有一類特定的神經網絡架構 F F F(函數類)可以學習到的函數為 f ∈ F f\in F fF,而我們真正需要的函數是 f ? f^* f?。通常 f ? ? F f^*\notin F f?/F,因此我們只能在 F F F中尋找最接近 f ? f^* f?的函數 f F ? f^*_F fF??,而我們尋找的方法即是對給定的 X \boldsymbol X X特征和 y \boldsymbol y y標簽的數據集解決如下優化問題:

f F ? : = a r g m i n f L ( X , y , f ) , f ∈ F f^*_F:=\underset f{\mathrm{argmin}}L(\boldsymbol X,\boldsymbol y,f),f\in F fF??:=fargmin?L(X,y,f),fF

要得到更接近 f ? f^* f?的函數只有設計更強大的架構 F ′ F' F,使 f F ′ ? f^*_{F'} fF?? f F ? f^*_F fF??更接近 f ? f^* f?。然而如果 F ? F ′ F\nsubseteq F' F?F非嵌套函數類),更復雜的函數類并不意味著向 f ? f^* f?靠攏,但嵌套函數類則可以保證這一點:

由此,如果新添加的層可以被訓練為恒等函數 f ( x ) = x f(\boldsymbol x)=\boldsymbol x f(x)=x,那么它既可以保證和原模型一樣的效果,也可能得出更優解。

7.6.2 殘差塊

殘差塊在正常塊的基礎上引出一條路徑讓輸入與輸出直接相加,這樣即是新的塊沒有從訓練中得到任何東西,新模型也能保證和原模型有一樣的效果(權重和偏置參數設置成 0 0 0)。此外,輸入在殘差塊中還可以通過跨層數據更快地向前傳播。殘差塊的引入只會讓原模型朝著接近目標函數的方向發展。

ResNet殘差塊的細節設計基于VGG塊,額外路徑中可選的 1 × 1 1\times1 1×1卷積層負責處理輸入以匹配原有塊輸出通道融合的情況:

import torch  
from torch import nn  
from torch.nn import functional as F  
from d2l import torch as d2l  
from matplotlib import pyplot as pltclass Residual(nn.Module):  def __init__(self, input_channels, num_channels, use_1x1conv=False, strides=1):  super().__init__()  self.conv1 = nn.Conv2d(input_channels, num_channels, kernel_size=3, stride=strides, padding=1)  self.conv2 = nn.Conv2d(num_channels, num_channels, kernel_size=3, padding=1)  if use_1x1conv:  self.conv3 = nn.Conv2d(input_channels, num_channels, kernel_size=1, stride=strides)  else:  self.conv3 = None  self.bn1 = nn.BatchNorm2d(num_channels)  self.bn2 = nn.BatchNorm2d(num_channels)  def forward(self, X):  Y = F.relu(self.bn1(self.conv1(X)))  Y = self.bn2(self.conv2(Y))  if self.conv3:  X = self.conv3(X)  Y += X  return F.relu(Y)

7.6.3 ResNet模型

ResNet前兩層與GoogLeNet一樣,但在卷積層后增加了批量規范化層。后面ResNet使用 4 4 4個由殘差塊組成的模塊,每個模塊使用若干輸出通道數相同的殘差塊,其中后 3 3 3個模塊包含 1 × 1 1\times1 1×1卷積處理。最后與GoogLeNet一樣使用全局平均匯聚層和全連接層輸出:

b1 = nn.Sequential(nn.Conv2d(1, 64, kernel_size=7, stride=2, padding=3),   nn.BatchNorm2d(64), nn.ReLU(),  nn.MaxPool2d(kernel_size=3, stride=2, padding=1))  #殘差模塊
def resnet_block(input_channels, num_channels, num_residuals, firstblock=False):  blk = []  for i in range(num_residuals):  #除了第一個模塊,每個模塊的第一個塊做1×1卷積if i == 0 and not firstblock:  blk.append(Residual(input_channels, num_channels, use_1x1conv=True, strides=2))  else:  blk.append(Residual(num_channels, num_channels))  return blk  b2 = nn.Sequential(*resnet_block(64, 64, 2, firstblock=True))  
b3 = nn.Sequential(*resnet_block(64, 128, 2))  
b4 = nn.Sequential(*resnet_block(128, 256, 2))  
b5 = nn.Sequential(*resnet_block(256, 512, 2))  net = nn.Sequential(b1, b2, b3, b4, b5,  nn.AdaptiveAvgPool2d(1),  nn.Flatten(), nn.Linear(512, 10))

7.6.4 訓練模型

lr, num_epochs, batch_size = 0.05, 10, 256  
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size=batch_size, resize=96)  
d2l.train_ch6(net, train_iter, test_iter, num_epochs, lr, d2l.try_gpu())  
plt.show()

訓練結果:

loss 0.010, train acc 0.998, test acc 0.906
828.8 examples/sec on cuda:0

7.7 稠密連接網絡(DenseNet)

ResNet極大地改變了如何參數化深度網絡中函數的觀點,稠密連接網絡在某種程度上時ResNet的邏輯擴展。

7.7.1 從ResNet到DenseNet

泰勒展開將函數分解成越來越高階的項:

f ( x ) = f ( 0 ) + f ′ ( 0 ) x + f ′ ′ ( 0 ) 2 ! x 2 + f ′ ′ ′ 3 ! x 3 + ? f(x)=f(0)+f'(0)x+\displaystyle\frac{f^{''}(0)}{2!}x^2+\frac{f^{'''}}{3!}x^3+\cdots f(x)=f(0)+f(0)x+2!f′′(0)?x2+3!f′′′?x3+?

而ResNet將函數展開為:

f ( x ) = x + g ( x ) f(\boldsymbol x)=\boldsymbol x+g(\boldsymbol x) f(x)=x+g(x)

ResNet將 f f f分解為一個簡單的線性項和一個復雜的非線性項,而DenseNet提出了一種將 f f f拓展成超過兩部分的方案。關鍵區別在于DenseNet輸出是連接(用 [ , ] [,] [,]表示)而不是ResNet的簡單相加:

x → [ x , f 1 ( x ) , f 2 ( [ x , f 1 ( x ) ] ) , f 3 ( [ x , f 1 ( x ) , f 2 ( [ x , f 1 ( x ) ] ) ] ) , ? ] \boldsymbol x\rightarrow[\boldsymbol x,f_1(\boldsymbol x),f_2([\boldsymbol x,f_1(\boldsymbol x)]),f_3([\boldsymbol x,f_1(\boldsymbol x),f_2([\boldsymbol x,f_1(\boldsymbol x)])]),\cdots] x[x,f1?(x),f2?([x,f1?(x)]),f3?([x,f1?(x),f2?([x,f1?(x)])]),?]

稠密網絡由稠密塊和過渡層組成,前者定義如何連接輸入和輸出,后者控制通道數使其不會太復雜。

7.7.2 稠密塊體

DenseNet使用了ResNet改良版的“批量規范化層+激活層+卷積層”架構,一個稠密塊由多個這樣的卷積塊組成,每個卷積塊使用相同數量的輸出通道,但最終每個卷積塊的輸入和輸出會在通道維度上連接,即每次都會使輸出通道數增加一個卷積塊的通道數。卷積塊的通道數控制了輸出通道數相對于輸入通道數的增長率。

import torch  
from torch import nn  
from d2l import torch as d2l  
import matplotlib.pyplot as plt  #卷積塊
def conv_block(input_channels, num_channels):  return nn.Sequential(  nn.BatchNorm2d(input_channels), nn.ReLU(),  nn.Conv2d(input_channels, num_channels, kernel_size=3, padding=1))  #稠密塊
class DenseBlock(nn.Module):  def __init__(self, num_convs, input_channels, num_channels):  super().__init__()  layer = []  for i in range(num_convs):  layer.append(conv_block(num_channels * i + input_channels, num_channels))  self.net = nn.Sequential(*layer)  def forward(self, X):  for blk in self.net:  Y = blk(X)  X = torch.cat((X, Y), 1)  return X

7.7.3 過渡層

為了應對卷積塊帶來的通道增長,過渡層通過 1 × 1 1\times1 1×1卷積層減小通道數,并使用平均匯聚層減半高度和寬度,從而降低模型復雜度。

def transition_block(input_channels, num_channels):  return nn.Sequential(  nn.BatchNorm2d(input_channels), nn.ReLU(),  nn.Conv2d(input_channels, num_channels, kernel_size=1),  nn.AvgPool2d(kernel_size=2, stride=2))

7.7.4 DenseNet模型

b1 = nn.Sequential(  nn.Conv2d(1, 64, kernel_size=7, stride=2, padding=3),  nn.BatchNorm2d(64), nn.ReLU(),  nn.MaxPool2d(kernel_size=3, stride=2, padding=1))  #當前輸出通道數和增長率(卷積塊通道數)
num_channels, growth_rate = 64, 32  
#每個稠密塊的卷積塊個數
num_convs_in_dense_blocks = [4, 4, 4, 4]  
blks = []  
for i, num_convs in enumerate(num_convs_in_dense_blocks):  blks.append(DenseBlock(num_convs, num_channels, growth_rate))  #更新輸出通道數num_channels += num_convs * growth_rate  if i != len(num_convs_in_dense_blocks) - 1:  #過渡層將輸出通道數減半blks.append(transition_block(num_channels, num_channels // 2))  num_channels //= 2  net = nn.Sequential(  b1, *blks,  nn.BatchNorm2d(num_channels), nn.ReLU(),  nn.AdaptiveAvgPool2d(1),  nn.Flatten(),  nn.Linear(num_channels, 10))

7.7.5 訓練模型

lr, num_epochs, batch_size = 0.05, 10, 256  
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size=batch_size, resize=96)  
d2l.train_ch6(net, train_iter, test_iter, num_epochs, lr, d2l.try_gpu())  
plt.show()

訓練結果:

loss 0.140, train acc 0.950, test acc 0.909
820.2 examples/sec on cuda:0

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/74336.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/74336.shtml
英文地址,請注明出處:http://en.pswp.cn/web/74336.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Jenkins + CICD流程一鍵自動部署Vue前端項目(保姆級)

git倉庫地址:參考以下代碼完成,或者采用自己的代碼。 南澤/cicd-test 拉取項目代碼到本地 使用云服務器或虛擬機采用docker部署jenkins 安裝docker過程省略 采用docker部署jenkins,注意這里的命令,一定要映射docker路徑,否則無…

使用 libevent 處理 TCP 粘包問題(基于 Content-Length 或雙 \r\n)

在基于 libevent 的 TCP 服務器開發中,處理消息邊界是常見需求。以下是兩種主流分包方案的完整實現: 一、基于 Content-Length 的分包方案 1.1 數據結構設計 typedef struct {struct bufferevent *bev;int content_length; // 當前消息的預期長度int received_bytes; //…

酶動力學預測工具CataPro安裝教程

簡介:預測酶動力學參數是酶發現和酶工程中的一項重要任務。在此,研究人員基于蛋白質語言模型、小分子語言模型和分子指紋,提出了一種名為 CataPro 的新酶動力學參數預測算法。該研究從 BRENDA 和 SABIO-RK 數據庫中收集了最新的轉化率&#x…

項目實戰:基于Spring WebFlux與LangChain4j實現大語言模型流式輸出

一、背景 在大語言模型(LLM)應用場景中,GPT-4等模型的響應生成往往需要數秒至數十秒的等待時間。傳統同步請求會導致用戶面對空白頁面等待,體驗較差。本文通過Spring WebFlux響應式編程與SSE服務器推送技術,實現類似打…

Go語言入門經典:數組與切片詳解

Go語言入門經典:數組與切片詳解 數組和切片是Go語言中兩種重要的數據結構。數組是一種固定長度的集合,而切片則是一種靈活的動態集合。本章將詳細講解數組和切片的定義、初始化、訪問元素、動態操作等內容,幫助讀者全面掌握這兩種數據結構。…

uniapp中如何用iconfont來管理圖標集成到我們開發的項目中

我們在開發不管小程序還是APP的過程中都會用到圖標這個東西,那么iconfont提供了對應的功能,怎么才能方便的集成到我們的小程序或者APP項目中,目標是方便調用并且方便管理。 首先注冊ICONFONT賬號 www.iconfont.cn中去注冊即可選擇我們需要的圖標如下 我們搜索我們需要的圖…

從實用的角度聊聊Linux下文本編輯器VIM

本文從實用的角度聊聊Vim的常用命令。何為實用?我舉個不實用的例子大家就明白了,用vim寫代碼。;) “vim是從 vi 發展出來的一個文本編輯器。代碼補全、編譯及錯誤跳轉等方便編程的功能特別豐富,在程序員中被廣泛使用,和Emacs并列成…

優化程序命名:提升專業感與用戶體驗

在軟件開發的廣闊天地中,程序命名這一環節常常被開發者們忽視。不少程序沿用著簡單直白、缺乏雕琢的名字,如同素面朝天的璞玉,雖不影響其核心功能的發揮,但卻在無形之中錯失了許多提升用戶印象與拓展應用場景的機會。今天&#xf…

LeetCode BFS解決最短路問題

廣度優先搜索(BFS, Breadth-First Search)是一種用于圖和樹結構的遍歷算法,特別適合解決無權圖的最短路徑問題。 算法思想: BFS從起始節點開始,按照"廣度優先"的原則,逐層向外擴展搜索: 先訪問起始節點的…

[物聯網iot]對比WIFI、MQTT、TCP、UDP通信協議

第一步:先理解最基礎的關系(類比快遞) 假設你要給朋友寄快遞: Wi-Fi:相當于“公路和卡車”,負責把包裹從你家運到快遞站。 TCP/UDP:相當于“快遞公司的運輸規則”。 TCP:順豐快遞&…

基于python的電影數據分析及可視化系統

一、項目背景 隨著電影行業的快速發展,電影數據日益豐富,如何有效地分析和可視化這些數據成為行業內的一個重要課題。本系統旨在利用Python編程語言,結合數據分析與可視化技術,為電影行業從業者、研究者及愛好者提供一個便捷的電…

Java8 到 Java21 系列之 Lambda 表達式:函數式編程的開端(Java 8)

Java8 到 Java21 系列之 Lambda 表達式:函數式編程的開端(Java 8) 系列目錄 Java8 到 Java21 系列之 Lambda 表達式:函數式編程的開端(Java 8)Java 8 到 Java 21 系列之 Stream API:數據處理的…

②EtherCAT/Ethernet/IP/Profinet/ModbusTCP協議互轉工業串口網關

型號 協議轉換通信網關 EtherCAT 轉 Modbus TCP 配置說明 網線連接電腦到模塊上的 WEB 網頁設置網口,電腦所連網口的網段設置成 192.168.1.X(X 是除 8 外的任一數值)后,打開瀏覽器,地址欄輸入 192.168.1.8 &#xff…

機器視覺--python基礎語法

Python基礎語法 1. Python標識符 在 Python 里,標識符由字母、數字、下劃線組成。 在 Python 中,所有標識符可以包括英文、數字以及下劃線(_),但不能以數字開頭。 Python 中的標識符是區分大小寫的。 以下劃線開頭的標識符是有特殊意義的…

算法日常記錄

1. 鏈表 1.1 刪除鏈表的倒數第 N 個結點 問題描述:給你一個鏈表,刪除鏈表的倒數第 n 個結點,并且返回鏈表的頭結點。 輸入:head [1,2,3,4,5], n 2 輸出:[1,2,3,5] 思路:先讓fast跑n步,然后…

14使用按鈕實現helloworld(1)

目錄 還可以通過按鈕的方式來創建 hello world 涉及Qt 中的信號槽機制本質就是給按鈕的點擊操作,關聯上一個處理函數當用戶點擊的時候 就會執行這個處理函數 connect(誰發的信號, 信號類型, 誰來處理這個信息, 怎么處理的&…

【Golang】泛型與類型約束

文章目錄 一、環境二、沒有泛型的Go三、泛型的優點四、理解泛型(一)泛型函數(Generic function)1)定義2)調用 (二)類型約束(Type constraint)1)接…

k8s常用總結

1. Kubernetes 架構概覽 主節點(Master): 負責集群管理,包括 API Server、Controller Manager、Scheduler 和 etcd 存儲。 工作節點(Node): 運行 Pod 和容器,包含 kubelet、kube-pr…

Android 單例模式全解析:從基礎實現到最佳實踐

單例模式(Singleton Pattern)是軟件開發中常用的設計模式,其核心是確保一個類在全局范圍內只有一個實例,并提供全局訪問點。在 Android 開發中,單例模式常用于管理全局資源(如網絡管理器、數據庫助手、配置…

ffmpeg濾鏡使用

ffmpeg實現畫中畫效果 FFmpeg中,可以通過overlay將多個視頻流、多個多媒體采集設備、多個視頻文件合并到一個界面中,生成畫中畫的效果 FFmpeg 濾鏡 overlay 基本參數 x和y x坐標和Y坐標 eof action 遇到 eof表示時的處理方式,默認為重復。…