1. 程式人生 > >pytorch入門——邊學邊練06 Residual_Network

pytorch入門——邊學邊練06 Residual_Network

訪問本站觀看效果更佳

寫在前面

今天我們探討一下大名鼎鼎的ResNet。ResNet在2015年被提出,在ImageNet比賽classification任務上獲得第一名,因為它“簡單與實用”並存,之後很多方法都建立在ResNet50或者ResNet101的基礎上完成的,檢測,分割,識別等領域都紛紛使用ResNet,Alphazero也使用了ResNet,所以可見ResNet確實很好用。 原始碼地址Deep Residual Network。我們根據論文的內容來做一下,您會發現非常容易就能實現Resnet。

Resnet關鍵部分理解

圖1
關於Resnet的原理部分,本文不做詳細介紹,我們主要精力放在實現上。上圖就是Resnet的主要結構,甚至我們對比著上圖就能把Resnet實現出來了。這裡我們撇開為什麼有效

的問題,來看看如何做,此處我們主要看論文的4.2節。以下是論文翻譯:

  • 我們在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個堆疊的權重層。下表總結了架構:screenshot from 2018-08-27 15-30-15

  • 當使用捷徑連線時,它們連線到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。前面的實現裡我們都自己寫過了。圖1
    我們再看第二個問題——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收斂更快。
圖1

# 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的操作。應當說,內容還是比較簡單的,我們後面再來點複雜的東西吧!