第二次作業:卷積神經網絡 part 2

第二次作業:卷積神經網絡 part 2

問題總結

  • 輸出層激活函數是否有必要?

  • 為什麼DnCNN要輸出殘差圖片?圖像復原又該如何操作?

  • DSCMR中的J2損失函數效果並不明顯,為什麼還要引入呢?

代碼練習

MobileNet V1

Mobilenet v1是Google於2017年發佈的網絡架構,旨在充分利用移動設備和嵌入式應用的有限的資源,有效地最大化模型的準確性,以滿足有限資源下的各種應用案例。

使用了深度可分離卷積,把標準卷積分解為 depth-wise 和 point-wise 卷積,合起來被稱作Depthwise Separable Convolution(參見Google的Xception)。

將常規卷積的做法(改變大小和通道數)拆分成兩步走:depthwise層,只改變feature map的大小,不改變通道數;Pointwise 層,只改變通道數,不改變大小。

該結構和常規卷積操作類似,可用來提取特徵,但相比於常規卷積操作,其參數量和運算成本較低。所以在一些輕量級網絡中會碰到這種結構如MobileNet

class Block(nn.Module):
    '''Depthwise conv + Pointwise conv'''
    def __init__(self, in_planes, out_planes, stride=1):
        super(Block, self).__init__()
        # Depthwise 卷積,3*3 的卷積核,分為 in_planes,即各層單獨進行卷積
        self.conv1 = nn.Conv2d(in_planes, in_planes, kernel_size=3, stride=stride, padding=1, groups=in_planes, bias=False)
        self.bn1 = nn.BatchNorm2d(in_planes)
        # Pointwise 卷積,1*1 的卷積核
        self.conv2 = nn.Conv2d(in_planes, out_planes, kernel_size=1, stride=1, padding=0, bias=False)
        self.bn2 = nn.BatchNorm2d(out_planes)

    def forward(self, x):
        out = F.relu(self.bn1(self.conv1(x)))
        out = F.relu(self.bn2(self.conv2(out)))
        return out

MobileNet V1網絡:

class MobileNetV1(nn.Module):
    # (128,2) means conv planes=128, stride=2
    cfg = [(64,1), (128,2), (128,1), (256,2), (256,1), (512,2), (512,1), 
           (1024,2), (1024,1)]

    def __init__(self, num_classes=10):
        super(MobileNetV1, self).__init__()
        self.conv1 = nn.Conv2d(3, 32, kernel_size=3, stride=1, padding=1, bias=False)
        self.bn1 = nn.BatchNorm2d(32)
        self.layers = self._make_layers(in_planes=32)
        self.linear = nn.Linear(1024, num_classes)

    def _make_layers(self, in_planes):
        layers = []
        for x in self.cfg:
            out_planes = x[0]
            stride = x[1]
            layers.append(Block(in_planes, out_planes, stride))
            in_planes = out_planes
        return nn.Sequential(*layers)

    def forward(self, x):
        out = F.relu(self.bn1(self.conv1(x)))
        out = self.layers(out)
        out = F.avg_pool2d(out, 2)
        out = out.view(out.size(0), -1)
        out = self.linear(out)
        return out

經過10輪訓練後,對CIFAR10數據集的表現如下:

Accuracy of the network on the 10000 test images: 78.14 %

可以看出MoblieNet在降低參數量的同時仍保持了較好的準確率。

MobileNet V2

Mark Sandler在CVPR 2018提出了MobileNetV2

  • MobileNet V1 的主要問題:

    • 結構非常簡單,但是沒有使用RestNet里的residual learning
    • Depthwise Conv確實是大大降低了計算量,但實際中,發現不少訓練出來的kernel是空的
  • MobileNet V2 的主要改動:

    • 設計了Inverted residual block
    • 去掉輸出部分的ReLU6

class Block(nn.Module):
    '''expand + depthwise + pointwise'''
    def __init__(self, in_planes, out_planes, expansion, stride):
        super(Block, self).__init__()
        self.stride = stride
        # 通過 expansion 增大 feature map 的數量
        planes = expansion * in_planes
        self.conv1 = nn.Conv2d(in_planes, planes, kernel_size=1, stride=1, padding=0, bias=False)
        self.bn1 = nn.BatchNorm2d(planes)
        self.conv2 = nn.Conv2d(planes, planes, kernel_size=3, stride=stride, padding=1, groups=planes, bias=False)
        self.bn2 = nn.BatchNorm2d(planes)
        self.conv3 = nn.Conv2d(planes, out_planes, kernel_size=1, stride=1, padding=0, bias=False)
        self.bn3 = nn.BatchNorm2d(out_planes)

        # 步長為 1 時,如果 in 和 out 的 feature map 通道不同,用一個卷積改變通道數
        if stride == 1 and in_planes != out_planes:
            self.shortcut = nn.Sequential(
                nn.Conv2d(in_planes, out_planes, kernel_size=1, stride=1, padding=0, bias=False),
                nn.BatchNorm2d(out_planes))
        # 步長為 1 時,如果 in 和 out 的 feature map 通道相同,直接返回輸入
        if stride == 1 and in_planes == out_planes:
            self.shortcut = nn.Sequential()

    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))
        # 步長為1,加 shortcut 操作
        if self.stride == 1:
            return out + self.shortcut(x)
        # 步長為2,直接輸出
        else:
            return out

MobileNet V2網絡:

class MobileNetV2(nn.Module):
    # (expansion, out_planes, num_blocks, stride)
    cfg = [(1,  16, 1, 1),
           (6,  24, 2, 1), 
           (6,  32, 3, 2),
           (6,  64, 4, 2),
           (6,  96, 3, 1),
           (6, 160, 3, 2),
           (6, 320, 1, 1)]

    def __init__(self, num_classes=10):
        super(MobileNetV2, self).__init__()
        self.conv1 = nn.Conv2d(3, 32, kernel_size=3, stride=1, padding=1, bias=False)
        self.bn1 = nn.BatchNorm2d(32)
        self.layers = self._make_layers(in_planes=32)
        self.conv2 = nn.Conv2d(320, 1280, kernel_size=1, stride=1, padding=0, bias=False)
        self.bn2 = nn.BatchNorm2d(1280)
        self.linear = nn.Linear(1280, num_classes)

    def _make_layers(self, in_planes):
        layers = []
        for expansion, out_planes, num_blocks, stride in self.cfg:
            strides = [stride] + [1]*(num_blocks-1)
            for stride in strides:
                layers.append(Block(in_planes, out_planes, expansion, stride))
                in_planes = out_planes
        return nn.Sequential(*layers)

    def forward(self, x):
        out = F.relu(self.bn1(self.conv1(x)))
        out = self.layers(out)
        out = F.relu(self.bn2(self.conv2(out)))
        out = F.avg_pool2d(out, 4)
        out = out.view(out.size(0), -1)
        out = self.linear(out)
        return out

經過10輪訓練後,對CIFAR10數據集的表現如下:

Accuracy of the network on the 10000 test images: 80.10 %

可以看出MoblieNet V2相對V1準確率有所提升。

HybridSN 高光譜分類

構建一個混合網絡解決高光譜圖像分類問題,首先用3D卷積,然後使用2D卷積。

實驗若設置對照(3D->2D,2D->3D),更具說服力。

一維卷積、二維卷積、三維卷積的區別:卷積神經網絡(CNN)之一維卷積、二維卷積、三維卷積詳解

class HybridSN(nn.Module):

  def __init__(self):
    super(HybridSN, self).__init__()
    # 3個3D卷積
    # conv1:(1, 30, 25, 25), 8個 7x3x3 的卷積核 ==> (8, 24, 23, 23)
    self.conv1_3d = nn.Conv3d(1,8,(7,3,3))
    self.relu1 = nn.ReLU()
	# conv2:(8, 24, 23, 23), 16個 5x3x3 的卷積核 ==>(16, 20, 21, 21)
    self.conv2_3d = nn.Conv3d(8,16,(5,3,3))
    self.relu2 = nn.ReLU()
	# conv3:(16, 20, 21, 21),32個 3x3x3 的卷積核 ==>(32, 18, 19, 19)
    self.conv3_3d = nn.Conv3d(16,32,(3,3,3))
    self.relu3 = nn.ReLU()
    # 2D卷積
	# conv4:(576, 19, 19),64個 3x3 的卷積核 ==>(64, 17, 17)
    self.conv4_2d = nn.Conv2d(576,64,(3,3))
    self.relu4 = nn.ReLU()
	# 接下來依次為256,128節點的全連接層,都使用比例為0.4的 Dropout
    self.fn1 = nn.Linear(18496,256)
    self.fn2 = nn.Linear(256,128)

    self.fn_out = nn.Linear(128,class_num)

    self.drop = nn.Dropout(p = 0.4)
    
    # self.soft = nn.Softmax(dim = 1)

  def forward(self, x):
    out = self.conv1_3d(x)
    out = self.relu1(out)
    out = self.conv2_3d(out)
    out = self.relu2(out)
    out = self.conv3_3d(out)
    out = self.relu3(out)
	# 進行二維卷積,因此把前面的 32*18 reshape 一下,得到 (576, 19, 19)
    b,x,y,m,n = out.size()
    out = out.view(b,x*y,m,n)

    out = self.conv4_2d(out)
    out = self.relu4(out)
	# 接下來是一個 flatten 操作,變為 18496 維的向量
    # 進行重組,以b行,d列的形式存放(d自動計算)
    out = out.reshape(b,-1)

    out = self.fn1(out)
    out = self.drop(out)
    out = self.fn2(out)
    out = self.drop(out)

    out = self.fn_out(out)

    # out = self.soft(out)

    return out

accuracy 0.9740 9225

還可以繼續提升性能:增大訓練樣本、調節學習率。

論文閱讀心得

Beyond a Gaussian Denoiser(DnCNN)

第一次在圖像去噪領域使用深度學習方法。

原來多用bm3d。

該論文提出了卷積神經網絡結合殘差學習來進行圖像降噪,直接學習圖像噪聲,可以更好的降噪。

  • 強調了residual learning(殘差學習)和batch normalization(批量標準化)在圖像復原中相輔相成的作用,可以在較深的網絡的條件下,依然能帶來快的收斂和好的性能。
  • 文章提出DnCNN,在高斯去噪問題下,用單模型應對不同程度的高斯噪音;甚至可以用單模型應對高斯去噪、超分辨率、JPEG去鎖三個領域的問題。

摘自:【圖像去噪】DnCNN論文詳解(Beyond a Gaussian Denoiser: Residual Learning of Deep CNN for Image Denoising)

DnCNN

全卷積,將DnCNN的卷積核大小設置為3 * 3,並且去掉了所有的池化層。

內部協變量移位(internal covariate shift):深層神經網絡在做非線性變換前的激活輸入值,隨着網絡深度加深或者在訓練過程中,其分佈逐漸發生偏移或者變動,之所以訓練收斂慢,一般是整體分佈逐漸往非線性函數的取值區間的上下限兩端靠近(對於Sigmoid函數來說,意味着激活輸入值WU+B是大的負值或正值),所以這導致反向傳播時低層神經網絡的梯度消失,這是訓練深層神經網絡收斂越來越慢的本質原因。

批量標準化(batch normalization):就是通過一定的規範化手段,把每層神經網絡任意神經元這個輸入值的分佈強行拉回到均值為0方差為1標準正態分佈,即把越來越偏的分佈強制拉回比較標準的分佈,這樣使得激活輸入值落在非線性函數對輸入比較敏感的區域,所以輸入的小變化才就會導致損失函數有較大的變化,意思就是讓梯度變大,避免梯度消失問題產生,而且梯度變大意味着學習收斂速度快,能大大加快訓練速度。

  • BN的作用:

在每一層的非線性處理之前加入標準化、縮放、移位操作來減輕內部協變量的移位。可以給訓練帶來更快的速度,更好的表現,使網絡對初始化變量的影響沒有那麼大。

  • 實驗部分:
    • 對比有無residual learning與batch normalization對復原效果、收斂快慢的影響,最終證明這兩是相輔相成的,都利用上時網絡各方面性能達到最好。
    • 根據特定程度的高斯噪聲訓練DnCNN-S、根據不定程度的高斯噪聲訓練DnCNN-B、根據不同程度的噪音(包括不同程度的高斯噪聲、不同程度的低分辨率、不同程度的JPEG編碼)訓練的DnCNN-3來與最前沿的其他算法做對比實驗。結論:DnCNN-S有最好的性能,但是DnCNN-B也有優於其他算法的性能,證明了DnCNN-B具有很好的盲去高斯噪聲的能力;DnCNN-3則證明了DnCNN-3具有不俗的復原圖像的泛化能力。
    • 對比了DnCNN與其他前沿去噪算法的運行速度的實驗,結論:速度還是不錯的,CPU\GPU環境下均屬於中上水平。

SENet

國內自動駕駛創業公司 Momenta 在 ImageNet 2017 挑戰賽中奪冠,網絡架構為 SENet,論文作者為 Momenta 高級研發工程師胡傑。該網絡通過學習的方式獲取每個特徵通道的重要程度,然後依照這個重要程度去提升有用的特徵並抑制對當前任務用處不大的特徵。

引入注意力機制:強調重要部分(深色部分),忽略不重要部分(淺色部分)。

類似的還有雙重注意力網絡(DANet)等。

可以在不同維度上進行壓縮,設置對照。

SENet的全稱是Squeeze-and-Excitation Networks(壓縮和激勵網絡),主要由兩部分組成:

  • Squeeze部分。即為壓縮部分,原始feature map的維度為H×W×C,其中H是高度(Height),W是寬度(width),C是通道數(channel)。Squeeze做的事情是把H×W×C壓縮為1×1×C,相當於把H×W壓縮成一維了,實際中一般是用global average pooling實現的。H×W壓縮成一維後,相當於這一維參數獲得了之前H×W全局的視野,感受區域更廣。
  • Excitation部分。得到Squeeze的1×1×C的表示後,加入一個FC全連接層(Fully Connected),對每個通道的重要性進行預測,得到不同channel的重要性大小後再作用(激勵)到之前的feature map的對應channel上,再進行後續操作。

可以看出,SENet和ResNet很相似,但比ResNet做得更多。ResNet只是增加了一個skip connection,而SENet在相鄰兩層之間加入了處理,使得channel之間的信息交互成為可能,進一步提高了網絡的準確率。

SE模塊主要為了提升模型對channel特徵的敏感性,這個模塊是輕量級的,而且可以應用在現有的網絡結構中,只需要增加較少的計算量就可以帶來性能的提升。

摘自:最後一屆ImageNet冠軍模型:SENet

深度監督跨模態檢索(DSCMR)

該論文設計了三個損失函數,用來提升深度跨模態檢索的準確率。

J2損失函數使用數學公式推導,但效果並不明顯。