pytorch入門——邊學邊練06 Residual_Network
訪問本站觀看效果更佳
寫在前面
今天我們探討一下大名鼎鼎的ResNet。ResNet在2015年被提出,在ImageNet比賽classification任務上獲得第一名,因為它“簡單與實用”並存,之後很多方法都建立在ResNet50或者ResNet101的基礎上完成的,檢測,分割,識別等領域都紛紛使用ResNet,Alphazero也使用了ResNet,所以可見ResNet確實很好用。 原始碼地址Deep Residual Network。我們根據論文的內容來做一下,您會發現非常容易就能實現Resnet。
Resnet關鍵部分理解
關於Resnet的原理部分,本文不做詳細介紹,我們主要精力放在實現上。上圖就是Resnet的主要結構,甚至我們對比著上圖就能把Resnet實現出來了。這裡我們撇開為什麼有效
-
我們在CIFAR-10資料集上進行了更多的研究[20],該資料集有10個類別,50k個訓練影象和10k個測試影象。我們在訓練集上訓練並在測試集上評估的實驗。我們關注的是極深網路的行為,而不是推動最先進的結果,所以我們故意使用如下簡單的體系結構。
-
普通/殘差體系結構遵循圖3(中/右)的形式。網路輸入是32×32影象,它的每個畫素減去平均值。第一層是3×3卷積。然後,我們分別在尺寸為{32,16,8}的特徵圖上使用3×3卷積的6n個堆疊層,每個特徵圖尺寸為2n層。過濾器的數量分別為{16,32,64}。下采樣通過以2的步長卷積來執行。網路以全域性平均池化,10路全連線層和softmax結束。總共有6n + 2個堆疊的權重層。下表總結了架構:
-
當使用捷徑連線時,它們連線到3×3的層對(總共3n個捷徑連線)。在這個資料集中,我們在所有情況下都使用恆等捷徑(即選項A),因此我們的殘差模型具有與相應的普通模型完全相同的深度,寬度和引數數量。
我們使用0.0001的權重衰減和0.9的動量,並採用[12]和BN中的權重初始化,但沒有使用dropout。這些模型在兩個GPU上以128個小批量進行訓練。我們以0.1的學習速率開始,在32k和48k迭代時將其除以10,並於64k迭代後終止訓練。網路是在45k / 5k的訓練/ 驗證集上訓練的。我們使用[24]中的簡單資料增強策略進行訓練:每邊填充4個畫素,從填充影象或其水平翻轉中隨機取樣32×32裁剪。對於測試,我們只評估原本的32×32的影象。 -
我們比較了n={3,5,7,9},分別對應20,32,44,56層的網路。圖6(左)顯示了普通網路的表現。深度普通網路經歷了深度的增加,並且隨著深度的增加表現出更高的訓練誤差。這種現象與ImageNet(圖4左側)和MNIST(見[41])類似,表明這樣的優化難度是一個根本的問題。
** 好了,如果嫌麻煩可以直接跳過上面的話,跟著我一起來看看。首先我們要明確一點,上文提到的Resnet是由3個Resnet Block
構成的。這裡就有兩個問題。**
ResNet Block
內部結構是什麼?
block
如何構建網路?
我們先看第一個問題——ResNet Block
內部結構。其實下圖已經告訴我們怎麼去做了。主要的想法就是在輸出的位置加上一個x
再送入relu
,剩下的部分就是一個CNN
。前面的實現裡我們都自己寫過了。
我們再看第二個問題——block
如何構建網路。我們再重新讀一讀這句話,這句話就是答案:
第一層是3×3卷積。然後,我們分別在尺寸為{32,16,8}的特徵圖上使用3×3卷積的6n個堆疊層,每個特徵圖尺寸為2n層。過濾器的數量分別為{16,32,64}。下采樣通過以2的步長卷積來執行。網路以全域性平均池化,10路全連線層和softmax結束。總共有6n + 2個堆疊的權重層。
逐句逐句的分析。
第一層是3×3卷積。
首先放一個conv3x3
。
然後,我們分別在尺寸為{32,16,8}的特徵圖上使用3×3卷積的6n個堆疊層,每個特徵圖尺寸為2n層。
我們操作的物件是不同尺寸的特徵圖。什麼是特徵圖?輸入經過conv3x3
是特徵圖嗎?還不是哦~得再加一個啟用層relu
。我們拿到了out=relu(conv3×3)
。接著要幹什麼呢?使用3×3卷積,但引數怎麼定呢?所謂的尺寸就是指圖片的大小,{32,16,8}就是告訴我們stride=2
。
再接著看6n個堆疊層,每個特徵圖尺寸為2n層
注意之前的圖,說白了就是把Block
疊放兩層。
過濾器的數量分別為{16,32,64}
這也就是說卷積層的out_channels
分別為{16,32,64}。
下采樣通過以2的步長卷積來執行
就是告訴我們做一個卷積,步長為2,但是要注意下采樣放的位置。
網路以全域性平均池化
告訴我們加一個池化層。
10路全連線層和softmax結束
這個操作不用多說了吧?
具體實現
下面我們來敲敲程式碼,完成上述網路結構的實現。
首先我們來完成最簡單的一部分,做一個3×3的卷積,因為後面反覆要用到:
# 3x3 convolution
def conv3x3(in_channels, out_channels, stride=1):
return nn.Conv2d(in_channels, out_channels, kernel_size=3,
stride=stride, padding=1, bias=False)
再做一個ResNet Block
。這個也是非常簡單,CNN
加上x
。再看一眼圖。先放一個conv3x3
,然後加入relu
再放一個conv3x3
。注意中間加上batchnorm
收斂更快。
# Residual block
class ResidualBlock(nn.Module):
def __init__(self, in_channels, out_channels, stride=1, downsample=None):
super(ResidualBlock, self).__init__()
self.conv1 = conv3x3(in_channels, out_channels, stride)
self.bn1 = nn.BatchNorm2d(out_channels)
self.relu = nn.ReLU(inplace=True)
self.conv2 = conv3x3(out_channels, out_channels)
self.bn2 = nn.BatchNorm2d(out_channels)
self.downsample = downsample
def forward(self, x):
residual = x
out = self.conv1(x)
out = self.bn1(out)
out = self.relu(out)
out = self.conv2(out)
out = self.bn2(out)
if self.downsample:
residual = self.downsample(x)
out += residual
out = self.relu(out)
return out
out += residual
簡簡單單一句話就搞定了。一個部分解決了,再看另一個部分。比對上文的分析,我們可以看到這裡從概念上講沒有太多新的東西,主要是搞明白論文裡的各種名詞對應的是何種操作。
# ResNet
class ResNet(nn.Module):
def __init__(self, block, layers, num_classes=10):
super(ResNet, self).__init__()
self.in_channels = 16
self.conv = conv3x3(3, 16)
self.bn = nn.BatchNorm2d(16)
self.relu = nn.ReLU(inplace=True)
self.layer1 = self.make_layer(block, 16, layers[0])
self.layer2 = self.make_layer(block, 32, layers[0], 2)
self.layer3 = self.make_layer(block, 64, layers[1], 2)
self.avg_pool = nn.AvgPool2d(8)
self.fc = nn.Linear(64, num_classes)
def make_layer(self, block, out_channels, blocks, stride=1):
downsample = None
if (stride != 1) or (self.in_channels != out_channels):
downsample = nn.Sequential(
conv3x3(self.in_channels, out_channels, stride=stride),
nn.BatchNorm2d(out_channels))
layers = []
layers.append(block(self.in_channels, out_channels, stride, downsample))
self.in_channels = out_channels
for i in range(1, blocks):
layers.append(block(out_channels, out_channels))
return nn.Sequential(*layers)
def forward(self, x):
out = self.conv(x)
out = self.bn(out)
out = self.relu(out)
out = self.layer1(out)
out = self.layer2(out)
out = self.layer3(out)
out = self.avg_pool(out)
out = out.view(out.size(0), -1)
out = self.fc(out)
return out
小結
今天我們研究了一下經典的resnet
結構,熟悉了pytorch
的操作。應當說,內容還是比較簡單的,我們後面再來點複雜的東西吧!