ResNet極大地改變了如何參數化深層網絡中函數的觀點。?稠密連接網絡(DenseNet)在某種程度上是ResNet的邏輯擴展。讓我們先從數學上了解一下。
7.7.1.?從ResNet到DenseNet
?
7.7.2.?稠密塊體?
?DenseNet使用了ResNet改良版的“批量規范化、激活和卷積”架構(參見?7.6節中的練習)。 我們首先實現一下這個架構:
import torch
from torch import nn
from d2l import torch as d2l#BN層-激活層-卷積層
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(DenseBlock, self).__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), dim=1)return X
在下面的例子中,我們定義一個有2個輸出通道數為10的DenseBlock
。 使用通道數為3的輸入時,我們會得到通道數為3+2*10=23的輸出。 卷積塊的通道數控制了輸出通道數相對于輸入通道數的增長,因此也被稱為增長率(growth rate)。
驗證一下:
blk = DenseBlock(2, 3, 10)
X = torch.randn(4, 3, 8, 8)
Y = blk(X)
Y.shape輸出:torch.Size([4, 23, 8, 8])
7.7.3.?過渡層
?由于每個稠密塊都會帶來通道數的增加,使用過多則會過于復雜化模型。 而過渡層可以用來控制模型復雜度。 它通過1*1卷積層來減小通道數,并使用步幅為2的平均匯聚層減半高和寬,從而進一步降低模型復雜度。(這里用的是平均而不是最大池化因為這里本身過渡層就是為了過渡保留背景信息而不是篩選,題主個人認為,但是下面人的討論好像acc沒有什么變化,這下還是神經網絡的不可解釋性了)
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))
對上一個例子中稠密塊的輸出使用通道數為10的過渡層。 此時輸出的通道數減為10,高和寬均減半。
blk = transition_block(23, 10)
blk(Y).shape輸出:
torch.Size([4, 10, 4, 4])
7.7.4.?DenseNet模型
我們來構造DenseNet模型。DenseNet首先使用同ResNet一樣的單卷積層和最大匯聚層:
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))
接下來,類似于ResNet使用的4個殘差塊,DenseNet使用的是4個稠密塊。 與ResNet類似,我們可以設置每個稠密塊使用多少個卷積層。 這里我們設成4,從而與?7.6節的ResNet-18保持一致。 稠密塊里的卷積層通道數(即增長率)設為32,所以每個稠密塊將增加128個通道。
在每個模塊之間,ResNet通過步幅為2的殘差塊減小高和寬,DenseNet則使用過渡層來減半高和寬,并減半通道數。
# num_channels為當前的通道數
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 = num_channels // 2
與ResNet類似,最后接上全局匯聚層和全連接層來輸出結果。
net = nn.Sequential(b1, *blks,nn.BatchNorm2d(num_channels), nn.ReLU(),nn.AdaptiveAvgPool2d((1, 1)),nn.Flatten(),nn.Linear(num_channels, 10))
7.7.5.?訓練模型
由于這里使用了比較深的網絡,本節里我們將輸入高和寬從224降到96來簡化計算。
lr, num_epochs, batch_size = 0.1, 10, 256
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())輸出:
5626.3 examples/sec on cuda:0
loss 0.140, train acc 0.948, test acc 0.885
7.7.6.?小結
-
在跨層連接上,不同于ResNet中將輸入與輸出相加,稠密連接網絡(DenseNet)在通道維上連結輸入與輸出。
-
DenseNet的主要構建模塊是稠密塊和過渡層。
-
在構建DenseNet時,我們需要通過添加過渡層來控制網絡的維數,從而再次減少通道的數量。