在线观看www成人影院-在线观看www日本免费网站-在线观看www视频-在线观看操-欧美18在线-欧美1级

0
  • 聊天消息
  • 系統消息
  • 評論與回復
登錄后你可以
  • 下載海量資料
  • 學習在線課程
  • 觀看技術視頻
  • 寫文章/發帖/加入社區
會員中心
創作中心

完善資料讓更多小伙伴認識你,還能領取20積分哦,立即完善>

3天內不再提示

PyTorch教程-8.6. 殘差網絡 (ResNet) 和 ResNeXt

jf_pJlTbmA9 ? 來源:PyTorch ? 作者:PyTorch ? 2023-06-05 15:44 ? 次閱讀

隨著我們設計越來越深的網絡,了解添加層如何增加網絡的復雜性和表現力變得勢在必行。更重要的是設計網絡的能力,其中添加層使網絡嚴格更具表現力而不僅僅是不同。為了取得一些進展,我們需要一點數學知識。

import torch
from torch import nn
from torch.nn import functional as F
from d2l import torch as d2l

from mxnet import init, np, npx
from mxnet.gluon import nn
from d2l import mxnet as d2l

npx.set_np()

import jax
from flax import linen as nn
from jax import numpy as jnp
from d2l import jax as d2l

import tensorflow as tf
from d2l import tensorflow as d2l

8.6.1. 函數類

考慮F,特定網絡架構(連同學習率和其他超參數設置)可以達到的功能類別。也就是說,對于所有 f∈F存在一些參數集(例如,權重和偏差),可以通過在合適的數據集上進行訓練來獲得。讓我們假設f?是我們真正想要找到的“真實”功能。如果它在F,我們的狀態很好,但通常我們不會那么幸運。相反,我們將嘗試找到一些fF?這是我們最好的選擇 F. 例如,給定一個具有特征的數據集 X和標簽y,我們可以嘗試通過解決以下優化問題來找到它:

(8.6.1)fF?=defargminf?L(X,y,f)subject tof∈F.

我們知道正則化 (Morozov,1984 年,Tikhonov 和 Arsenin,1977 年)可以控制復雜度F并實現一致性,因此更大的訓練數據通常會帶來更好的效果fF?. 唯一合理的假設是,如果我們設計一個不同的、更強大的架構F′我們應該取得更好的結果。換句話說,我們期望fF′? 比“更好”fF?. 然而,如果 F?F′甚至不能保證這會發生。實際上,fF′?可能會更糟。如圖 8.6.1所示,對于非嵌套函數類,較大的函數類并不總是向“真實”函數靠攏f?. 例如,在圖 8.6.1的左側,雖然F3更接近f?比F1,F6 遠離并且不能保證進一步增加復雜性可以減少距離f?. 對于嵌套函數類,其中 F1?…?F6在圖 8.6.1右側,我們可以從非嵌套函數類中避免上述問題。

poYBAGR9NdSABwXfAAGkEZU56U8741.svg

圖 8.6.1對于非嵌套函數類,更大(用面積表示)的函數類并不能保證更接近“真實”函數(f?). 這不會發生在嵌套函數類中。

因此,只有當較大的函數類包含較小的函數類時,我們才能保證增加它們會嚴格增加網絡的表達能力。對于深度神經網絡,如果我們可以將新添加的層訓練成恒等函數 f(x)=x,新模型將與原始模型一樣有效。由于新模型可能會得到更好的解決方案來擬合訓練數據集,因此添加的層可能更容易減少訓練錯誤。

這是He等人提出的問題。( 2016 )在處理非常深的計算機視覺模型時考慮。他們提出的殘差網絡( ResNet )的核心思想是,每個附加層都應該更容易地包含身份函數作為其元素之一。這些考慮相當深刻,但它們導致了一個非常簡單的解決方案,即殘差塊。憑借它,ResNet 在 2015 年贏得了 ImageNet 大規模視覺識別挑戰賽。該設計對如何構建深度神經網絡產生了深遠的影響。例如,殘差塊已添加到循環網絡中 (Kim等人,2017 年,普拉卡什等。, 2016 年)。同樣,Transformers (Vaswani等人,2017 年)使用它們有效地堆疊多層網絡。它也被用于圖神經網絡 (Kipf 和 Welling,2016 年),并且作為一個基本概念,它已被廣泛用于計算機視覺 (Redmon 和 Farhadi,2018 年,Ren等人,2015 年)。請注意,殘差網絡早于高速公路網絡 (Srivastava等人,2015 年)這有一些共同的動機,盡管沒有圍繞身份函數進行優雅的參數化。

8.6.2. 殘差塊

讓我們關注神經網絡的局部部分,如圖 8.6.2所示。表示輸入x. 我們假設我們想要通過學習獲得的期望底層映射是f(x), 用作頂部激活函數的輸入。左邊虛線框內的部分必須直接學習映射f(x). 右邊虛線框內的部分需要學習殘差映射 g(x)=f(x)?x,這就是殘差塊的名稱來源。如果身份映射 f(x)=x是所需的底層映射,殘差映射相當于g(x)=0從而更容易學習:我們只需要將虛線框內的上層權重層(例如全連接層和卷積層)的權重和偏差推到零即可。右圖說明了 ResNet的殘差塊,其中實線承載層輸入x加法運算符稱為殘差連接(或快捷連接)。使用殘差塊,輸入可以通過跨層的殘差連接更快地向前傳播。事實上,殘差塊可以被認為是多分支 Inception 塊的一個特例:它有兩個分支,其中一個是恒等映射。

pYYBAGR9NdeAOMD1AAI9tco820o210.svg

圖 8.6.2在一個規則塊中(左),虛線框內的部分必須直接學習映射f(x). 在殘差塊中(右),虛線框內的部分需要學習殘差映射 g(x)=f(x)?x, 進行身份映射f(x)=x更容易學習。

ResNet 完全遵循 VGG 的3×3卷積層設計。殘差塊有兩個3×3具有相同輸出通道數的卷積層。每個卷積層后跟一個批量歸一化層和一個 ReLU 激活函數。然后,我們跳過這兩個卷積操作,將輸入直接添加到最終的 ReLU 激活函數之前。這種設計要求兩個卷積層的輸出必須與輸入具有相同的形狀,這樣才能將它們相加。如果我們想改變頻道的數量,我們需要引入一個額外的 1×1卷積層將輸入轉換為加法運算所需的形狀。讓我們看看下面的代碼。

class Residual(nn.Module): #@save
  """The Residual block of ResNet models."""
  def __init__(self, num_channels, use_1x1conv=False, strides=1):
    super().__init__()
    self.conv1 = nn.LazyConv2d(num_channels, kernel_size=3, padding=1,
                  stride=strides)
    self.conv2 = nn.LazyConv2d(num_channels, kernel_size=3, padding=1)
    if use_1x1conv:
      self.conv3 = nn.LazyConv2d(num_channels, kernel_size=1,
                    stride=strides)
    else:
      self.conv3 = None
    self.bn1 = nn.LazyBatchNorm2d()
    self.bn2 = nn.LazyBatchNorm2d()

  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)

class Residual(nn.Block): #@save
  """The Residual block of ResNet models."""
  def __init__(self, num_channels, use_1x1conv=False, strides=1, **kwargs):
    super().__init__(**kwargs)
    self.conv1 = nn.Conv2D(num_channels, kernel_size=3, padding=1,
                strides=strides)
    self.conv2 = nn.Conv2D(num_channels, kernel_size=3, padding=1)
    if use_1x1conv:
      self.conv3 = nn.Conv2D(num_channels, kernel_size=1,
                  strides=strides)
    else:
      self.conv3 = None
    self.bn1 = nn.BatchNorm()
    self.bn2 = nn.BatchNorm()

  def forward(self, X):
    Y = npx.relu(self.bn1(self.conv1(X)))
    Y = self.bn2(self.conv2(Y))
    if self.conv3:
      X = self.conv3(X)
    return npx.relu(Y + X)

class Residual(nn.Module): #@save
  """The Residual block of ResNet models."""
  num_channels: int
  use_1x1conv: bool = False
  strides: tuple = (1, 1)
  training: bool = True

  def setup(self):
    self.conv1 = nn.Conv(self.num_channels, kernel_size=(3, 3),
               padding='same', strides=self.strides)
    self.conv2 = nn.Conv(self.num_channels, kernel_size=(3, 3),
               padding='same')
    if self.use_1x1conv:
      self.conv3 = nn.Conv(self.num_channels, kernel_size=(1, 1),
                 strides=self.strides)
    else:
      self.conv3 = None
    self.bn1 = nn.BatchNorm(not self.training)
    self.bn2 = nn.BatchNorm(not self.training)

  def __call__(self, X):
    Y = nn.relu(self.bn1(self.conv1(X)))
    Y = self.bn2(self.conv2(Y))
    if self.conv3:
      X = self.conv3(X)
    Y += X
    return nn.relu(Y)

class Residual(tf.keras.Model): #@save
  """The Residual block of ResNet models."""
  def __init__(self, num_channels, use_1x1conv=False, strides=1):
    super().__init__()
    self.conv1 = tf.keras.layers.Conv2D(num_channels, padding='same',
                      kernel_size=3, strides=strides)
    self.conv2 = tf.keras.layers.Conv2D(num_channels, kernel_size=3,
                      padding='same')
    self.conv3 = None
    if use_1x1conv:
      self.conv3 = tf.keras.layers.Conv2D(num_channels, kernel_size=1,
                        strides=strides)
    self.bn1 = tf.keras.layers.BatchNormalization()
    self.bn2 = tf.keras.layers.BatchNormalization()

  def call(self, X):
    Y = tf.keras.activations.relu(self.bn1(self.conv1(X)))
    Y = self.bn2(self.conv2(Y))
    if self.conv3 is not None:
      X = self.conv3(X)
    Y += X
    return tf.keras.activations.relu(Y)

此代碼生成兩種類型的網絡:一種是我們在應用 ReLU 非線性之前將輸入添加到輸出 use_1x1conv=False,另一種是我們通過1×1添加前的卷積。 圖 8.6.3說明了這一點。

pYYBAGR9NdmAMEv-AAICnd0x3iQ460.svg

圖 8.6.3 ResNet 塊有無1×1卷積,將輸入轉換為加法運算所需的形狀。

現在讓我們看一下輸入和輸出形狀相同的情況,其中1×1不需要卷積。

blk = Residual(3)
X = torch.randn(4, 3, 6, 6)
blk(X).shape

torch.Size([4, 3, 6, 6])

blk = Residual(3)
blk.initialize()
X = np.random.randn(4, 3, 6, 6)
blk(X).shape

(4, 3, 6, 6)

blk = Residual(3)
X = jax.random.normal(d2l.get_key(), (4, 6, 6, 3))
blk.init_with_output(d2l.get_key(), X)[0].shape

(4, 6, 6, 3)

blk = Residual(3)
X = tf.random.normal((4, 6, 6, 3))
Y = blk(X)
Y.shape

TensorShape([4, 6, 6, 3])

我們還可以選擇在增加輸出通道數量的同時將輸出高度和寬度減半。在這種情況下,我們使用 1×1卷積通過use_1x1conv=True. 這在每個 ResNet 塊的開頭派上用場,可以通過strides=2.

blk = Residual(6, use_1x1conv=True, strides=2)
blk(X).shape

torch.Size([4, 6, 3, 3])

blk = Residual(6, use_1x1conv=True, strides=2)
blk.initialize()
blk(X).shape

(4, 6, 3, 3)

blk = Residual(6, use_1x1conv=True, strides=(2, 2))
blk.init_with_output(d2l.get_key(), X)[0].shape

(4, 3, 3, 6)

blk = Residual(6, use_1x1conv=True, strides=2)
blk(X).shape

TensorShape([4, 3, 3, 6])

8.6.3. ResNet模型

ResNet 的前兩層與我們之前描述的 GoogLeNet 相同:7×7具有 64 個輸出通道且步幅為 2 的卷積層之后是3×3 步幅為2的最大池化層。不同之處在于ResNet中每個卷積層之后添加的批量歸一化層。

class ResNet(d2l.Classifier):
  def b1(self):
    return nn.Sequential(
      nn.LazyConv2d(64, kernel_size=7, stride=2, padding=3),
      nn.LazyBatchNorm2d(), nn.ReLU(),
      nn.MaxPool2d(kernel_size=3, stride=2, padding=1))

class ResNet(d2l.Classifier):
  def b1(self):
    net = nn.Sequential()
    net.add(nn.Conv2D(64, kernel_size=7, strides=2, padding=3),
        nn.BatchNorm(), nn.Activation('relu'),
        nn.MaxPool2D(pool_size=3, strides=2, padding=1))
    return net

class ResNet(d2l.Classifier):
  arch: tuple
  lr: float = 0.1
  num_classes: int = 10
  training: bool = True

  def setup(self):
    self.net = self.create_net()

  def b1(self):
    return nn.Sequential([
      nn.Conv(64, kernel_size=(7, 7), strides=(2, 2), padding='same'),
      nn.BatchNorm(not self.training), nn.relu,
      lambda x: nn.max_pool(x, window_shape=(3, 3), strides=(2, 2),
                 padding='same')])

class ResNet(d2l.Classifier):
  def b1(self):
    return tf.keras.models.Sequential([
      tf.keras.layers.Conv2D(64, kernel_size=7, strides=2,
                  padding='same'),
      tf.keras.layers.BatchNormalization(),
      tf.keras.layers.Activation('relu'),
      tf.keras.layers.MaxPool2D(pool_size=3, strides=2,
                   padding='same')])

GoogLeNet 使用由 Inception 塊組成的四個模塊。然而,ResNet 使用了四個由殘差塊組成的模塊,每個模塊使用了幾個具有相同輸出通道數的殘差塊。第一個模塊中的通道數與輸入通道數相同。由于已經使用了步幅為 2 的最大池化層,因此沒有必要減少高度和寬度。在每個后續模塊的第一個殘差塊中,通道數與前一個模塊相比增加了一倍,高度和寬度減半。

@d2l.add_to_class(ResNet)
def block(self, num_residuals, num_channels, first_block=False):
  blk = []
  for i in range(num_residuals):
    if i == 0 and not first_block:
      blk.append(Residual(num_channels, use_1x1conv=True, strides=2))
    else:
      blk.append(Residual(num_channels))
  return nn.Sequential(*blk)

@d2l.add_to_class(ResNet)
def block(self, num_residuals, num_channels, first_block=False):
  blk = nn.Sequential()
  for i in range(num_residuals):
    if i == 0 and not first_block:
      blk.add(Residual(num_channels, use_1x1conv=True, strides=2))
    else:
      blk.add(Residual(num_channels))
  return blk

@d2l.add_to_class(ResNet)
def block(self, num_residuals, num_channels, first_block=False):
  blk = []
  for i in range(num_residuals):
    if i == 0 and not first_block:
      blk.append(Residual(num_channels, use_1x1conv=True,
                strides=(2, 2), training=self.training))
    else:
      blk.append(Residual(num_channels, training=self.training))
  return nn.Sequential(blk)

@d2l.add_to_class(ResNet)
def block(self, num_residuals, num_channels, first_block=False):
  blk = tf.keras.models.Sequential()
  for i in range(num_residuals):
    if i == 0 and not first_block:
      blk.add(Residual(num_channels, use_1x1conv=True, strides=2))
    else:
      blk.add(Residual(num_channels))
  return blk

然后,我們將所有模塊添加到 ResNet。這里,每個模塊使用兩個殘差塊。最后,就像 GoogLeNet 一樣,我們添加了一個全局平均池化層,然后是全連接層輸出。

@d2l.add_to_class(ResNet)
def __init__(self, arch, lr=0.1, num_classes=10):
  super(ResNet, self).__init__()
  self.save_hyperparameters()
  self.net = nn.Sequential(self.b1())
  for i, b in enumerate(arch):
    self.net.add_module(f'b{i+2}', self.block(*b, first_block=(i==0)))
  self.net.add_module('last', nn.Sequential(
    nn.AdaptiveAvgPool2d((1, 1)), nn.Flatten(),
    nn.LazyLinear(num_classes)))
  self.net.apply(d2l.init_cnn)

@d2l.add_to_class(ResNet)
def __init__(self, arch, lr=0.1, num_classes=10):
  super(ResNet, self).__init__()
  self.save_hyperparameters()
  self.net = nn.Sequential()
  self.net.add(self.b1())
  for i, b in enumerate(arch):
    self.net.add(self.block(*b, first_block=(i==0)))
  self.net.add(nn.GlobalAvgPool2D(), nn.Dense(num_classes))
  self.net.initialize(init.Xavier())

@d2l.add_to_class(ResNet)
def create_net(self):
  net = nn.Sequential([self.b1()])
  for i, b in enumerate(self.arch):
    net.layers.extend([self.block(*b, first_block=(i==0))])
  net.layers.extend([nn.Sequential([
    # Flax does not provide a GlobalAvg2D layer
    lambda x: nn.avg_pool(x, window_shape=x.shape[1:3],
               strides=x.shape[1:3], padding='valid'),
    lambda x: x.reshape((x.shape[0], -1)),
    nn.Dense(self.num_classes)])])
  return net

@d2l.add_to_class(ResNet)
def __init__(self, arch, lr=0.1, num_classes=10):
  super(ResNet, self).__init__()
  self.save_hyperparameters()
  self.net = tf.keras.models.Sequential(self.b1())
  for i, b in enumerate(arch):
    self.net.add(self.block(*b, first_block=(i==0)))
  self.net.add(tf.keras.models.Sequential([
    tf.keras.layers.GlobalAvgPool2D(),
    tf.keras.layers.Dense(units=num_classes)]))

每個模塊有 4 個卷積層(不包括 1×1卷積層)。與第一個一起 7×7卷積層和最后的全連接層,一共18層。因此,該模型通常被稱為 ResNet-18。通過在模塊中配置不同數量的通道和殘差塊,我們可以創建不同的 ResNet 模型,例如更深的 152 層 ResNet-152。雖然 ResNet 的主要架構與 GoogLeNet 相似,但 ResNet 的結構更簡單,也更容易修改。所有這些因素導致了 ResNet 的快速和廣泛使用。圖 8.6.4描繪了完整的 ResNet-18。

poYBAGR9NdyAUEOHAAFd5cJ1snI428.svg

圖 8.6.4 ResNet-18 架構。

在訓練 ResNet 之前,讓我們觀察輸入形狀在 ResNet 的不同模塊之間是如何變化的。與之前的所有架構一樣,分辨率會降低,而通道數量會增加,直到全局平均池化層聚合所有特征為止。

class ResNet18(ResNet):
  def __init__(self, lr=0.1, num_classes=10):
    super().__init__(((2, 64), (2, 128), (2, 256), (2, 512)),
            lr, num_classes)

ResNet18().layer_summary((1, 1, 96, 96))

Sequential output shape:   torch.Size([1, 64, 24, 24])
Sequential output shape:   torch.Size([1, 64, 24, 24])
Sequential output shape:   torch.Size([1, 128, 12, 12])
Sequential output shape:   torch.Size([1, 256, 6, 6])
Sequential output shape:   torch.Size([1, 512, 3, 3])
Sequential output shape:   torch.Size([1, 10])

class ResNet18(ResNet):
  def __init__(self, lr=0.1, num_classes=10):
    super().__init__(((2, 64), (2, 128), (2, 256), (2, 512)),
            lr, num_classes)

ResNet18().layer_summary((1, 1, 96, 96))

Sequential output shape:   (1, 64, 24, 24)
Sequential output shape:   (1, 64, 24, 24)
Sequential output shape:   (1, 128, 12, 12)
Sequential output shape:   (1, 256, 6, 6)
Sequential output shape:   (1, 512, 3, 3)
GlobalAvgPool2D output shape:    (1, 512, 1, 1)
Dense output shape: (1, 10)

class ResNet18(ResNet):
  arch: tuple = ((2, 64), (2, 128), (2, 256), (2, 512))
  lr: float = 0.1
  num_classes: int = 10

ResNet18(training=False).layer_summary((1, 96, 96, 1))

Sequential output shape:   (1, 24, 24, 64)
Sequential output shape:   (1, 24, 24, 64)
Sequential output shape:   (1, 12, 12, 128)
Sequential output shape:   (1, 6, 6, 256)
Sequential output shape:   (1, 3, 3, 512)
Sequential output shape:   (1, 10)

class ResNet18(ResNet):
  def __init__(self, lr=0.1, num_classes=10):
    super().__init__(((2, 64), (2, 128), (2, 256), (2, 512)),
            lr, num_classes)

ResNet18().layer_summary((1, 96, 96, 1))

Sequential output shape:   (1, 24, 24, 64)
Sequential output shape:   (1, 24, 24, 64)
Sequential output shape:   (1, 12, 12, 128)
Sequential output shape:   (1, 6, 6, 256)
Sequential output shape:   (1, 3, 3, 512)
Sequential output shape:   (1, 10)

8.6.4. 訓練

我們像以前一樣在 Fashion-MNIST 數據集上訓練 ResNet。ResNet 是一個非常強大和靈活的架構。捕獲訓練和驗證損失的圖說明了兩個圖之間的顯著差距,訓練損失明顯較低。對于這種靈活性的網絡,更多的訓練數據將在縮小差距和提高準確性方面提供顯著的好處。

model = ResNet18(lr=0.01)
trainer = d2l.Trainer(max_epochs=10, num_gpus=1)
data = d2l.FashionMNIST(batch_size=128, resize=(96, 96))
model.apply_init([next(iter(data.get_dataloader(True)))[0]], d2l.init_cnn)
trainer.fit(model, data)

pYYBAGR9Nd6AY_5sAAGKAbS5afA220.svg

model = ResNet18(lr=0.01)
trainer = d2l.Trainer(max_epochs=10, num_gpus=1)
data = d2l.FashionMNIST(batch_size=128, resize=(96, 96))
trainer.fit(model, data)

poYBAGR9NeGAZKshAAGIgZn8cJU302.svg

model = ResNet18(lr=0.01)
trainer = d2l.Trainer(max_epochs=10, num_gpus=1)
data = d2l.FashionMNIST(batch_size=128, resize=(96, 96))
trainer.fit(model, data)

pYYBAGR9NeOAGdpfAAGIaQJ1PQQ165.svg

trainer = d2l.Trainer(max_epochs=10)
data = d2l.FashionMNIST(batch_size=128, resize=(96, 96))
with d2l.try_gpu():
  model = ResNet18(lr=0.01)
  trainer.fit(model, data)

poYBAGR9NeWAPB1XAAFovnrEzQc063.svg

8.6.5. ResNeXt

在 ResNet 的設計中遇到的挑戰之一是給定塊內的非線性和維度之間的權衡。也就是說,我們可以通過增加層數或增加卷積寬度來增加更多的非線性。另一種策略是增加可以在塊之間傳輸信息的通道數量。不幸的是,后者會帶來二次懲罰,因為攝取的計算成本ci 渠道和發射co渠道正比于 O(ci?co)(參見我們在 第 7.4 節中的討論)。

我們可以從圖 8.4.1的 Inception 塊中得到一些啟發 ,它有信息在不同的組中流經塊。將多個獨立組的思想應用于圖 8.6.3的 ResNet 塊導致了 ResNeXt 的設計(Xie et al. , 2017)。與 Inception 中變換的大雜燴不同,ResNeXt 在所有分支中采用 相同的變換,從而最大限度地減少了對每個分支進行手動調整的需要。

poYBAGR9NeiASf3WAARy5TTS_W0346.svg

圖 8.6.5 ResNeXt 塊。分組卷積的使用g 團體是g比密集卷積快 1 倍。當中間通道的數量增加時,它是一個瓶頸殘差塊 b小于c.

從中分解一個卷積ci到co頻道進入其中一個g大小組ci/g產生g 尺寸輸出co/g非常恰當地稱為分組卷積。計算成本(按比例)從 O(ci?co)到 O(g?(ci/g)?(co/g))=O(ci?co/g),即它是g倍更快。更好的是,生成輸出所需的參數數量也從 ci×co矩陣到g更小的矩陣 (ci/g)×(co/g), 又是一個g次減少。在下文中,我們假設兩者ci和co被整除g.

這種設計中唯一的挑戰是,兩者之間沒有信息交換g團體。圖 8.6.5的 ResNeXt 塊 以兩種方式修正了這一點:分組卷積與3×3內核夾在兩個中間1×1卷積。第二個在改回頻道數量方面起著雙重作用。好處是我們只支付O(c?b)費用1×1內核,可以湊合O(b2/g)費用 3×3內核。類似于第 8.6.2 節中的殘差塊實現,殘差連接被替換(因此泛化)為1×1卷積。

圖 8.6.5中的右圖提供了生成的網絡塊的更簡潔的摘要。它還將在第 8.8 節中的通用現代 CNN 設計中發揮重要作用 。請注意,分組卷積的想法可以追溯到 AlexNet 的實現 (Krizhevsky等人,2012 年)。當在內存有限的兩個 GPU 上分配網絡時,該實現將每個 GPU 視為自己的通道,沒有任何不良影響。

該類的以下實現ResNeXtBlock作為參數groups(g), 與bot_channels(b) 中間(瓶頸)渠道。最后,當我們需要減少表示的高度和寬度時,我們添加一個步幅2通過設置。use_1x1conv=True, strides=2

class ResNeXtBlock(nn.Module): #@save
  """The ResNeXt block."""
  def __init__(self, num_channels, groups, bot_mul, use_1x1conv=False,
         strides=1):
    super().__init__()
    bot_channels = int(round(num_channels * bot_mul))
    self.conv1 = nn.LazyConv2d(bot_channels, kernel_size=1, stride=1)
    self.conv2 = nn.LazyConv2d(bot_channels, kernel_size=3,
                  stride=strides, padding=1,
                  groups=bot_channels//groups)
    self.conv3 = nn.LazyConv2d(num_channels, kernel_size=1, stride=1)
    self.bn1 = nn.LazyBatchNorm2d()
    self.bn2 = nn.LazyBatchNorm2d()
    self.bn3 = nn.LazyBatchNorm2d()
    if use_1x1conv:
      self.conv4 = nn.LazyConv2d(num_channels, kernel_size=1,
                    stride=strides)
      self.bn4 = nn.LazyBatchNorm2d()
    else:
      self.conv4 = None

  def forward(self, X):
    Y = F.relu(self.bn1(self.conv1(X)))
    Y = F.relu(self.bn2(self.conv2(Y)))
    Y = self.bn3(self.conv3(Y))
    if self.conv4:
      X = self.bn4(self.conv4(X))
    return F.relu(Y + X)

class ResNeXtBlock(nn.Block): #@save
  """The ResNeXt block."""
  def __init__(self, num_channels, groups, bot_mul,
         use_1x1conv=False, strides=1, **kwargs):
    super().__init__(**kwargs)
    bot_channels = int(round(num_channels * bot_mul))
    self.conv1 = nn.Conv2D(bot_channels, kernel_size=1, padding=0,
                strides=1)
    self.conv2 = nn.Conv2D(bot_channels, kernel_size=3, padding=1,
                strides=strides, groups=bot_channels//groups)
    self.conv3 = nn.Conv2D(num_channels, kernel_size=1, padding=0,
                strides=1)
    self.bn1 = nn.BatchNorm()
    self.bn2 = nn.BatchNorm()
    self.bn3 = nn.BatchNorm()
    if use_1x1conv:
      self.conv4 = nn.Conv2D(num_channels, kernel_size=1,
                  strides=strides)
      self.bn4 = nn.BatchNorm()
    else:
      self.conv4 = None

  def forward(self, X):
    Y = npx.relu(self.bn1(self.conv1(X)))
    Y = npx.relu(self.bn2(self.conv2(Y)))
    Y = self.bn3(self.conv3(Y))
    if self.conv4:
      X = self.bn4(self.conv4(X))
    return npx.relu(Y + X)

class ResNeXtBlock(nn.Module): #@save
  """The ResNeXt block."""
  num_channels: int
  groups: int
  bot_mul: int
  use_1x1conv: bool = False
  strides: tuple = (1, 1)
  training: bool = True

  def setup(self):
    bot_channels = int(round(self.num_channels * self.bot_mul))
    self.conv1 = nn.Conv(bot_channels, kernel_size=(1, 1),
                strides=(1, 1))
    self.conv2 = nn.Conv(bot_channels, kernel_size=(3, 3),
                strides=self.strides, padding='same',
                feature_group_count=bot_channels//self.groups)
    self.conv3 = nn.Conv(self.num_channels, kernel_size=(1, 1),
                strides=(1, 1))
    self.bn1 = nn.BatchNorm(not self.training)
    self.bn2 = nn.BatchNorm(not self.training)
    self.bn3 = nn.BatchNorm(not self.training)
    if self.use_1x1conv:
      self.conv4 = nn.Conv(self.num_channels, kernel_size=(1, 1),
                    strides=self.strides)
      self.bn4 = nn.BatchNorm(not self.training)
    else:
      self.conv4 = None

  def __call__(self, X):
    Y = nn.relu(self.bn1(self.conv1(X)))
    Y = nn.relu(self.bn2(self.conv2(Y)))
    Y = self.bn3(self.conv3(Y))
    if self.conv4:
      X = self.bn4(self.conv4(X))
    return nn.relu(Y + X)

class ResNeXtBlock(tf.keras.Model): #@save
  """The ResNeXt block."""
  def __init__(self, num_channels, groups, bot_mul, use_1x1conv=False,
         strides=1):
    super().__init__()
    bot_channels = int(round(num_channels * bot_mul))
    self.conv1 = tf.keras.layers.Conv2D(bot_channels, 1, strides=1)
    self.conv2 = tf.keras.layers.Conv2D(bot_channels, 3, strides=strides,
                      padding="same",
                      groups=bot_channels//groups)
    self.conv3 = tf.keras.layers.Conv2D(num_channels, 1, strides=1)
    self.bn1 = tf.keras.layers.BatchNormalization()
    self.bn2 = tf.keras.layers.BatchNormalization()
    self.bn3 = tf.keras.layers.BatchNormalization()
    if use_1x1conv:
      self.conv4 = tf.keras.layers.Conv2D(num_channels, 1,
                        strides=strides)
      self.bn4 = tf.keras.layers.BatchNormalization()
    else:
      self.conv4 = None

  def call(self, X):
    Y = tf.keras.activations.relu(self.bn1(self.conv1(X)))
    Y = tf.keras.activations.relu(self.bn2(self.conv2(Y)))
    Y = self.bn3(self.conv3(Y))
    if self.conv4:
      X = self.bn4(self.conv4(X))
    return tf.keras.activations.relu(Y + X)

它的使用與前面討論的完全相似ResNetBlock。例如,當使用 ( ) 時,輸入和輸出的形狀相同。或者,設置 輸出高度和寬度的一半。use_1x1conv=False, strides=1use_1x1conv=True, strides=2

blk = ResNeXtBlock(32, 16, 1)
X = torch.randn(4, 32, 96, 96)
blk(X).shape

torch.Size([4, 32, 96, 96])

blk = ResNeXtBlock(32, 16, 1)
blk.initialize()
X = np.random.randn(4, 32, 96, 96)
blk(X).shape

(4, 32, 96, 96)

blk = ResNeXtBlock(32, 16, 1)
X = jnp.zeros((4, 96, 96, 32))
blk.init_with_output(d2l.get_key(), X)[0].shape

(4, 96, 96, 32)

blk = ResNeXtBlock(32, 16, 1)
X = tf.random.normal((4, 96, 96, 32))
Y = blk(X)
Y.shape

TensorShape([4, 96, 96, 32])

8.6.6. 總結與討論

嵌套函數類是可取的,因為它們允許我們在增加容量時獲得更強大的函數類,而不是細微不同的函數類。實現這一點的一種方法是允許附加層簡單地將輸入傳遞到輸出。殘余連接允許這樣做。因此,這改變了形式為簡單函數的歸納偏差 f(x)=0看起來像簡單的功能 f(x)=x.

殘差映射可以更容易地學習身份函數,例如將權重層中的參數推為零。我們可以通過殘差塊來訓練有效的深度神經網絡。輸入可以通過跨層的剩余連接更快地向前傳播。因此,我們可以訓練更深層次的網絡。例如,最初的 ResNet 論文( He et al. , 2016 )允許多達 152 層。殘差網絡的另一個好處是它允許我們添加層,初始化為恒等函數,在培訓過程。畢竟,層的默認行為是讓數據不加改變地通過。在某些情況下,這可以加速超大型網絡的訓練。

在殘差連接之前,引入了帶有門控單元的旁路路徑,以有效地訓練超過 100 層的高速公路網絡 (Srivastava等人,2015 年)。使用身份函數作為繞過路徑,ResNet 在多個計算機視覺任務上表現非常出色。殘差連接對后續深度神經網絡的設計產生了重大影響,包括卷積和順序性質。正如我們稍后將介紹的,Transformer 架構 (Vaswani等人,2017 年)采用殘差連接(連同其他設計選擇),并且在語言、視覺、語音和強化學習等不同領域普遍存在。

ResNeXt 是卷積神經網絡的設計如何隨著時間的推移而演變的一個例子:通過更節儉地計算并與激活的大小(通道數)進行權衡,它允許以更低的成本更快、更準確的網絡. 查看分組卷積的另一種方法是考慮卷積權重的塊對角矩陣。請注意,有很多這樣的“技巧”可以提高網絡的效率。例如,ShiftNet (Wu等人,2018 年)模仿了 3×3卷積,簡單地通過向通道添加移位激活,提供增加的功能復雜性,這次沒有任何計算成本。

到目前為止我們討論的設計的一個共同特征是網絡設計是相當手動的,主要依靠設計者的獨創性來找到“正確的”網絡超參數。雖然顯然可行,但就人力時間而言,它也非常昂貴,并且無法保證結果在任何意義上都是最佳的。在 第 8.8 節中,我們將討論一些以更自動化的方式獲得高質量網絡的策略。特別是,我們將回顧導致 RegNetX/Y 模型的 網絡設計空間的概念(Radosavovic等人,2020 年)。

8.6.7. 練習

圖 8.4.1中的 Inception 塊 和殘差塊的主要區別是什么?他們如何在計算、準確性和他們可以描述的函數類別方面進行比較?

參考 ResNet 論文( He et al. , 2016 )中的表 1 來實現網絡的不同變體。

對于更深的網絡,ResNet 引入了一個“瓶頸”架構來降低模型的復雜性。嘗試實施它。

在ResNet的后續版本中,作者將“卷積、批量歸一化和激活”結構改為“批量歸一化、激活和卷積”結構。自己進行此改進。參見He等人的圖 1 。( 2016 )了解詳情。

為什么我們不能無限制地增加函數的復雜性,即使函數類是嵌套的?

聲明:本文內容及配圖由入駐作者撰寫或者入駐合作網站授權轉載。文章觀點僅代表作者本人,不代表電子發燒友網立場。文章及其配圖僅供工程師學習之用,如有內容侵權或者其他違規問題,請聯系本站處理。 舉報投訴
  • pytorch
    +關注

    關注

    2

    文章

    808

    瀏覽量

    13388
收藏 人收藏

    評論

    相關推薦

    深度學習與圖神經網絡學習分享:CNN經典網絡之-ResNet

    深度學習與圖神經網絡學習分享:CNN 經典網絡之-ResNet resnet 又叫深度
    的頭像 發表于 10-12 09:54 ?1111次閱讀
    深度學習與圖神經<b class='flag-5'>網絡</b>學習分享:CNN經典<b class='flag-5'>網絡</b>之-<b class='flag-5'>ResNet</b>

    什么是深度收縮網絡

       深度收縮網絡是深度網絡的一種新的升級版本,其實是深度
    發表于 11-26 06:33

    由多模塊組成的多窗口網絡優化模型

      基于卷積神經網絡的單圖像超分辨率模型網絡結構過深,導致高頻信息丟失以及模型體積龐大等問題。提出一種由多個模塊構成的多窗口
    發表于 03-10 16:31 ?3次下載
    由多<b class='flag-5'>殘</b><b class='flag-5'>差</b>模塊組成的多窗口<b class='flag-5'>殘</b><b class='flag-5'>差</b><b class='flag-5'>網絡</b>優化模型

    一種改進的網絡結構以減少卷積層參數

    傳統的網絡在果實病害分類中存在層數較多,以及在實際應用中有參數冗余的問題,且原始損失函數對具有相似特征的病害容易造成錯誤識別。為解決果害分類中參數過多及相似樣本區分度低的問題,提出一種改進的
    發表于 03-23 14:48 ?8次下載
    一種改進的<b class='flag-5'>殘</b><b class='flag-5'>差</b><b class='flag-5'>網絡</b>結構以減少卷積層參數

    基于雙超密集網絡的多模態醫學圖像融合方法

    針對基于網絡和密集網絡的圖像融合方法存在網絡中間層的部分有用信息丟失和融合圖像細節不清晰的問題,提出了基于雙
    發表于 04-14 11:18 ?19次下載
    基于雙<b class='flag-5'>殘</b><b class='flag-5'>差</b>超密集<b class='flag-5'>網絡</b>的多模態醫學圖像融合方法

    基于連接的改進端到端文本識別網絡結構

    針對已有文本識別網絡由于深度不夠而識別準確率較低的問題,文中提岀一種改進的端到端文本識別網絡結構。首先,將文本作為序列,采用模塊將文本按列切分成特征向量輸入循環層。這種
    發表于 05-17 15:18 ?6次下載

    基于多尺度網絡的邊緣檢測技術

    面向對象的邊緣檢測技術是智能視覺處理領堿的關鍵基礎技術,然而目前基于卷積神經網絡的邊緣檢測結果存在分辨率低、噪聲較多等問題。因此,文中提出了一種基于多尺度網絡的對象級邊緣檢測算法。
    發表于 05-29 14:27 ?3次下載

    基于改進網絡的水下圖像重建修復

    自然水體成像中湍流及懸浮顆粒等環境因素會造成水下采集的圖像存在扭曲失真、分辨率低、背景模糊等問題,為了解決上述問題并進一步提高圖像重建和復原的質量,提出了一種改進的基于網絡的圖像超分辨率重建方法
    發表于 06-17 15:37 ?10次下載

    基于多尺度通道注意機制的人臉超分辨率網絡

    基于多尺度通道注意機制的人臉超分辨率網絡
    發表于 06-27 14:36 ?15次下載

    基于神經網絡的微型電機轉子焊點圖像檢測

    基于神經網絡的微型電機轉子焊點圖像檢測
    發表于 07-02 14:56 ?23次下載

    基于非對稱注意力機制網絡的圖像檢測

    基于非對稱注意力機制網絡的圖像檢測
    發表于 07-05 15:29 ?9次下載

    《圖學學報》—深度網絡的無人機多目標識別

    級聯區域建議網絡(CRPN)的搜索模式對其進行改善。此外,深層次的卷積神經網絡訓練中易產生退化現象,而引入學習的深度
    發表于 12-02 17:14 ?1153次閱讀
    《圖學學報》—深度<b class='flag-5'>殘</b><b class='flag-5'>差</b><b class='flag-5'>網絡</b>的無人機多目標識別

    《圖學學報》—深度網絡的無人機多目標識別

    一種級聯區域建議網絡(CRPN)的搜索模式對其進行改善。此外,深層次的卷積神經網絡訓練中易產生退化現象,而引入學習的深度
    發表于 12-06 17:02 ?645次閱讀

    PyTorch教程8.6網絡(ResNet)和ResNeXt

    電子發燒友網站提供《PyTorch教程8.6網絡(ResNet)和
    發表于 06-05 10:08 ?0次下載
    <b class='flag-5'>PyTorch</b>教程<b class='flag-5'>8.6</b>之<b class='flag-5'>殘</b><b class='flag-5'>差</b><b class='flag-5'>網絡</b>(<b class='flag-5'>ResNet</b>)和<b class='flag-5'>ResNeXt</b>

    網絡是深度神經網絡

    網絡(Residual Network,通常簡稱為ResNet) 是深度神經網絡的一種 ,其獨特的結構設計在解決深層
    的頭像 發表于 07-11 18:13 ?1205次閱讀
    主站蜘蛛池模板: 热re99久久国产精品 | 精品伊人久久大线蕉地址 | 久久99热久久精品99 | 欧美一级精品 | 精品国产一区二区三区国产馆 | 在线 | 一区二区三区四区 | 国产伦子系列视频6 | 91福利专区 | 扒开末成年粉嫩的小缝强文 | 国产精品欧美久久久久天天影视 | 日本a级特黄三级三级三级 日本边添边爱边摸边做边爱 | 特级黄毛片 | 国产精品久久久久国产精品三级 | 黄色毛片免费 | 性生活黄色毛片 | 国产美女视频黄a视频免费全过程 | 网www天堂资源在线 网红和老师啪啪对白清晰 网络色综合久久 | 最新bt合集 | 日本一区二区三区不卡在线视频 | 成人午夜影院在线观看 | 午夜寂寞视频在线观看 | 大又大粗又爽又黄少妇毛片 | 一区二区三区影视 | 在线免费亚洲 | 5g国产精品影院天天5g天天爽 | 91激情| 乱色伦短篇小说 | 欧美色成人综合 | 日本在线视频一区二区 | 久久99国产精品久久99 | 黄色有码视频 | 亚洲国产精品综合久久2007 | 777欧美 | 72种姿势欧美久久久久大黄蕉 | 天天干天天爱天天射 | 色噜噜狠狠色综合中文字幕 | 国产在线理论片免费播放 | 人人爱操| 五月婷婷色网 | 天堂网站www天堂资源在线 | 手机在线观看视频你懂的 |