殘差網絡結構及其實現

一、殘差網絡基本結構

(1) 傳統的深度學習遇到的困難
  1. 梯度消失問題:隨着網絡深度的增加,網絡會出現梯度消失/梯度爆炸的情況,阻礙收斂的過程
  2. 退化問題:精確率出現飽和的情況是層數的增加引起的,並不是過擬合,表明更深的網絡並未出現更高的訓練錯誤率。
  3. 深度學習提高網絡精度常用初始化權值、局部感受野、權值共享等技術,但是層數較深時,依然面對反向傳播時梯度消失這類困難。
(2) 深度殘差網絡思路
  1. 引入skip connect 打破了網絡的對稱性,減輕了神經網絡的退化
  2. 認爲可以混合利用不同層級的特徵進一步抽取深層特徵。
  3. 類似的工作在文章《Highway》和《Training Very Deep Networks》
  4. 深度殘差的目標:更深層次的網絡應該也是利於優化的,層數越多準確率越高,訓練方法與傳統的深度網絡相比不會有很大變化
(3)殘差結構(左圖)和殘差瓶頸結構(右圖)

殘差瓶頸結構:1 * 1 卷積 ——> 3 * 3卷積 ——> 1 * 1 卷積
在這裏插入圖片描述
首端和末端的1x1卷積用來削減和恢復維度,相比於原本結構,只有中間3x3成爲瓶頸部分。
開始的1 * 1 卷積主要目的是爲了減少參數的數量,從而減少計算量,且在降維之後可以更加有效、直觀地進行數據的訓練和特徵提取。這裏用32 個1*1 的卷積核進行降維,準確的說是32組1 * 1 * 64大小的卷積核,那麼與原始圖片卷積後生成32 * 56 * 56大小的特徵圖,降低了圖片的厚度(通道),保持圖片的寬高不變,所以參數量也降低了。最後再用1 * 1 卷積恢復維度。
Alt注意:

  1. H(x) = F(x) + x , F(x) 就表示殘差,x表示輸入。F(x)和x維度要一致。
  2. 殘差在數理統計中是指實際觀察值與估計值(擬合值)之間的差。“殘差”蘊含了有關模型基本假設的重要信息。如果迴歸模型正確的話, 我們可以將殘差看作誤差的觀測值。它應符合模型的假設條件,且具有誤差的一些性質。利用殘差所提供的信息,來考察模型假設的合理性及數據的可靠性稱爲殘差分析。
(4)三種網絡結構進行對比

Alt
說明:

在殘差網絡虛線處feature map的維度增加了,作者提出了兩種選擇:

  1. 直接通過zero padding 來增加維度(channel)。
  2. 乘以W矩陣投影到新的空間。是用1x1卷積實現的,直接改變1x1卷積的filters數目。這種會增加參數。

二、殘差網絡實現

(1) 簡單殘差結構的實現

# 這個例子可以用來了解殘差塊的基本原理
import torch
import torch.nn as nn
import numpy as np


class ResNet(nn.Module):
    def __init__(self):
        super(ResNet, self).__init__()
        self.conv = nn.Conv2d(3, 6, 3, 1, padding=1)

    def forward(self, x):
        # 這裏的維度要一致
        return x.repeat(1, 2, 1, 1) + self.conv(x)

if __name__ == '__main__':
    net = ResNet()
    x = torch.randn(1, 3, 6, 6)
    print(net(x).shape)

(2) 完整殘差結構(殘差塊)實現

"""
3 * 3 卷積 ————> 3 * 3 卷積
"""
import torch.nn as nn
import torch.nn.functional as F


class BasicBlock(nn.Module):
    expansion = 1
   
    def __init__(self, in_feature, out_feature, stride=1):
        super(BasicBlock, self).__init__()
        self.conv1 = nn.Conv2d(in_feature, out_feature, kernel_size=3, stride=stride, padding=1)
        self.bn1 = nn.BatchNorm2d(out_feature)
        self.conv2 = nn.Conv2d(out_feature, out_feature, kernel_size=3, stride=1, padding=1)
        self.bn2 = nn.BatchNorm2d(out_feature)
        self.shortcut = nn.Sequential()
        """
        經過處理後的x要與x的維度相同(尺寸和深度)
        如果不相同,需要添加捲積+BN來變換爲同一維度
        """
        if stride != 1 or in_feature != self.expansion * in_feature:
            self.shortcut = nn.Sequential(
                nn.Conv2d(in_feature, self.expansion*in_feature, kernel_size=1, stride=stride),
                nn.BatchNorm2d(self.expansion * in_feature)
            )
        
    def forward(self, x):
        out = F.relu(self.bn1(self.conv1(x)))
        out = self.bn2(self.conv2(out))
        out += self.shortcut(x)
        out = F.relu(out)
        return out

(3) 殘差瓶頸結構實現

"""
1*1 卷積 ————> 3 * 3 卷積 ————>  1 * 1 卷積
"""
import torch.nn as nn
import torch.nn.functional as F


class Bottleneck(nn.Module):
    # 前面1x1和3x3卷積的filter個數相等,最後1x1卷積是其expansion倍
    expansion = 4
    def __init__(self, in_feature, out_feature, stride=1):
        super(Bottleneck, self).__init__()
        self.conv1 = nn.Conv2d(in_feature, out_feature, kernel_size=1, stride=stride)
        self.bn1 = nn.BatchNorm2d(out_feature)
        self.conv2 = nn.Conv2d(out_feature, out_feature, kernel_size=3, padding=1, stride=stride)
        self.bn2 = nn.BatchNorm2d(out_feature)
        self.conv3 = nn.Conv2d(out_feature, self.expansion*out_feature, kernel_size=1, padding=1, stride=stride)
        self.bn3 = nn.BatchNorm2d(self.expansion*out_feature)
        self.shortcut = nn.Sequential()
        if stride != 1 or in_feature != self.expansion*out_feature:
            self.shortcut = nn.Sequential(
                nn.Conv2d(in_feature, self.expansion*out_feature, kernel_size=1, stride=stride),
                nn.BatchNorm2d(self.expansion*out_feature)
            )
    def forward(self, x):
        out = F.relu(self.bn1(self.conv1(x)))
        out = F.relu(self.bn2(self.conv2(out)))
        out = self.bn3(self.conv3(out))
        out += self.shortcut(x)
        out = F.relu(out)
        return out
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章