輪廓檢測論文解讀 | Richer Convolutional Features| CVPR | 2017

  • 2020 年 12 月 17 日
  • AI
<<醫療影像/語義分割>>

輪廓檢測論文解讀 | 整體嵌套邊緣檢測HED | CVPR | 2015

孿生網路入門(下) Siamese Net分類服裝MNIST數據集(pytorch)

孿生網路入門(上) Siamese Net及其損失函數

影像分割必備知識點 | Unet++ 超詳解+註解

影像分割必備知識點 | Unet詳解 理論+ 程式碼

影像分割必備知識點 | Dice損失 理論+程式碼

3D卷積入門 | 多論文筆記 | R2D C3D P3D MCx R(2+1)D

醫學AI論文解讀 | 超聲心動圖在臨床中的自動化檢測 | Circulation | 2018 | 中英雙語

有什麼問題可以加作者微信討論,cyx645016617 上千人的粉絲群已經成立,氛圍超好。為大家提供一個遇到問題有可能得到答案的平台。

0 概述

  • 論文名稱:「Richer Convolutional Features for Edge Detection」
  • 論文鏈接://openaccess.thecvf.com/content_cvpr_2017/papers/Liu_Richer_Convolutional_Features_CVPR_2017_paper.pdf
  • 縮寫:RCF

這一篇文論在我看來,是CVPR 2015年 HED網路(holistically-nested edge detection)的一個改進,RCF的論文中也基本上和HED網路處處對比

在上一篇文章中,我們依稀記得HED模型有這樣一個圖:

其中有HED的五個side output的特徵圖,下圖是RCF論文中的圖:

我們從這兩個圖的區別中來認識RCF相比HED的改進,大家可以看一看圖。

揭曉答案:

  • HED是豹子的圖片,但是RCF是兩隻小鳥的圖片(手動狗頭)
  • HED中的是side output的輸出的特徵圖,而RCF中是conv3_1,conv3_2,這意味著RCF似乎把每一個卷積之後的輸出的特徵圖都作為了一個side output

沒錯,HED選取了5個side output,每一個side output都是池化層之前的卷積層輸出的特徵圖;而RCF則對每一次卷積的輸出特徵圖都作為side output,換句話說 最終的side output中,同一尺寸的輸出可能不止一個

如果還沒有理解,請看下面章節,模型結構。

1 模型結構

RCF的backbone是VGG模型:

從圖中可以看到:

  • 主幹網路上分成state1到5,stage1有兩個卷積層,stage2有兩個卷積層,總共有13個卷積層,每一次卷積輸出的影像,再額外接入一個1×1的卷積,來降低通道數,所以可以看到,圖中有大量的21通道的卷積層。
  • 同一個stage的21通道的特徵圖經過通道拼接,變成42通道或者是63通道的特徵圖,然後再經過一個1×1的卷積層,來把通道數降低成1,再進過sigmoid層,輸出的結果就是一個RCF模型中的side output了

2 損失函數

這裡的損失函數其實和HED來說類似:

首先整體來看,損失函數依然使用二值交叉熵

其中 表示 negative的像素值,表示positive的像素值。一般來說輪廓檢測任務中,positive的樣本應該是較少的,因此的值較小,因此損失函數中第一行,y=0也就是計算非輪廓部分的損失的時候,就會增加一個較小的權重,來避免類別不均衡的問題。

損失函數中有兩個常數,一個是,這個就是權重常數,默認為1.1;另外一個是。論文中的描述為:

Edge datasets in this community are usually labeled by several annotators using their knowledge about the presences of objects and object parts. Though humans vary in cognition, these human-labeled edges for the same image share high consistency. For each image, we average all the ground truth to generate an edge probability map, which ranges from 0 to 1. Here, 0 means no annotator labeled at this pixel, and 1 means all annotators have labeled at this pixel. We consider the pixels with edge probability higher than η as positive samples and the pixels with edge probability equal to 0 as negative samples. Otherwise, if a pixel is marked by fewer than η of the annotators, this pixel may be semantically controversial to be an edge point. Thus, whether regarding it as positive or negative samples may confuse networks. So we ignore pixels in this category.

大意就是:一般對數據集進行標註,是有多個人來完成的。不同的人雖然有不同的意識,但是他們對於同一個圖片的輪廓標註往往是具有一致性。RCF網路最後的輸出,是由5個side output融合產生的,因此你這個RCF的輸出也應該把大於的考慮為positive,然後小於的考慮為negative。其實這一點我自己在復現的時候並沒有考慮,我看網上的github和官方的程式碼中,都沒有考慮這個,都是直接交叉熵。。。我這就也就多此一舉的講解一下論文中的這個的含義

3 pytorch部分程式碼

對於這個RCF論文來說,關鍵就是一個模型的構建,另外一個就是損失函數的構建,這裡放出這兩部分的程式碼,來幫助大家更好的理解上面的內容。

3.1 模型部分

下面的程式碼在上取樣部分的寫法比較老舊,因為這個網上找來的pytorch版本估計比較老,當時還沒有Conv2DTrans這樣的函數封裝,但是不妨礙大家通過程式碼來學習RCF。

class RCF(nn.Module):
    def __init__(self):
        super(RCF, self).__init__()
        #lr 1 2 decay 1 0
        self.conv1_1 = nn.Conv2d(3643, padding=1)
        self.conv1_2 = nn.Conv2d(64643, padding=1)

        self.conv2_1 = nn.Conv2d(641283, padding=1)
        self.conv2_2 = nn.Conv2d(1281283, padding=1)

        self.conv3_1 = nn.Conv2d(1282563, padding=1)
        self.conv3_2 = nn.Conv2d(2562563, padding=1)
        self.conv3_3 = nn.Conv2d(2562563, padding=1)

        self.conv4_1 = nn.Conv2d(2565123, padding=1)
        self.conv4_2 = nn.Conv2d(5125123, padding=1)
        self.conv4_3 = nn.Conv2d(5125123, padding=1)

        self.conv5_1 = nn.Conv2d(512512, kernel_size=3,
                        stride=1, padding=2, dilation=2)
        self.conv5_2 = nn.Conv2d(512512, kernel_size=3,
                        stride=1, padding=2, dilation=2)
        self.conv5_3 = nn.Conv2d(512512, kernel_size=3,
                        stride=1, padding=2, dilation=2)
        self.relu = nn.ReLU()
        self.maxpool = nn.MaxPool2d(2, stride=2, ceil_mode=True)
        self.maxpool4 = nn.MaxPool2d(2, stride=1, ceil_mode=True)


        #lr 0.1 0.2 decay 1 0
        self.conv1_1_down = nn.Conv2d(64211, padding=0)
        self.conv1_2_down = nn.Conv2d(64211, padding=0)

        self.conv2_1_down = nn.Conv2d(128211, padding=0)
        self.conv2_2_down = nn.Conv2d(128211, padding=0)

        self.conv3_1_down = nn.Conv2d(256211, padding=0)
        self.conv3_2_down = nn.Conv2d(256211, padding=0)
        self.conv3_3_down = nn.Conv2d(256211, padding=0)

        self.conv4_1_down = nn.Conv2d(512211, padding=0)
        self.conv4_2_down = nn.Conv2d(512211, padding=0)
        self.conv4_3_down = nn.Conv2d(512211, padding=0)
        
        self.conv5_1_down = nn.Conv2d(512211, padding=0)
        self.conv5_2_down = nn.Conv2d(512211, padding=0)
        self.conv5_3_down = nn.Conv2d(512211, padding=0)

        #lr 0.01 0.02 decay 1 0
        self.score_dsn1 = nn.Conv2d(2111)
        self.score_dsn2 = nn.Conv2d(2111)
        self.score_dsn3 = nn.Conv2d(2111)
        self.score_dsn4 = nn.Conv2d(2111)
        self.score_dsn5 = nn.Conv2d(2111)
        #lr 0.001 0.002 decay 1 0
        self.score_final = nn.Conv2d(511)

    def forward(self, x):
        # VGG
        img_H, img_W = x.shape[2], x.shape[3]
        conv1_1 = self.relu(self.conv1_1(x))
        conv1_2 = self.relu(self.conv1_2(conv1_1))
        pool1   = self.maxpool(conv1_2)

        conv2_1 = self.relu(self.conv2_1(pool1))
        conv2_2 = self.relu(self.conv2_2(conv2_1))
        pool2   = self.maxpool(conv2_2)

        conv3_1 = self.relu(self.conv3_1(pool2))
        conv3_2 = self.relu(self.conv3_2(conv3_1))
        conv3_3 = self.relu(self.conv3_3(conv3_2))
        pool3   = self.maxpool(conv3_3)

        conv4_1 = self.relu(self.conv4_1(pool3))
        conv4_2 = self.relu(self.conv4_2(conv4_1))
        conv4_3 = self.relu(self.conv4_3(conv4_2))
        pool4   = self.maxpool4(conv4_3)

        conv5_1 = self.relu(self.conv5_1(pool4))
        conv5_2 = self.relu(self.conv5_2(conv5_1))
        conv5_3 = self.relu(self.conv5_3(conv5_2))

        conv1_1_down = self.conv1_1_down(conv1_1)
        conv1_2_down = self.conv1_2_down(conv1_2)
        conv2_1_down = self.conv2_1_down(conv2_1)
        conv2_2_down = self.conv2_2_down(conv2_2)
        conv3_1_down = self.conv3_1_down(conv3_1)
        conv3_2_down = self.conv3_2_down(conv3_2)
        conv3_3_down = self.conv3_3_down(conv3_3)
        conv4_1_down = self.conv4_1_down(conv4_1)
        conv4_2_down = self.conv4_2_down(conv4_2)
        conv4_3_down = self.conv4_3_down(conv4_3)
        conv5_1_down = self.conv5_1_down(conv5_1)
        conv5_2_down = self.conv5_2_down(conv5_2)
        conv5_3_down = self.conv5_3_down(conv5_3)

        so1_out = self.score_dsn1(conv1_1_down + conv1_2_down)
        so2_out = self.score_dsn2(conv2_1_down + conv2_2_down)
        so3_out = self.score_dsn3(conv3_1_down + conv3_2_down + conv3_3_down)
        so4_out = self.score_dsn4(conv4_1_down + conv4_2_down + conv4_3_down)
        so5_out = self.score_dsn5(conv5_1_down + conv5_2_down + conv5_3_down)
        ## transpose and crop way 
        weight_deconv2 =  make_bilinear_weights(41).cuda()
        weight_deconv3 =  make_bilinear_weights(81).cuda()
        weight_deconv4 =  make_bilinear_weights(161).cuda()
        weight_deconv5 =  make_bilinear_weights(321).cuda()

        upsample2 = torch.nn.functional.conv_transpose2d(so2_out, weight_deconv2, stride=2)
        upsample3 = torch.nn.functional.conv_transpose2d(so3_out, weight_deconv3, stride=4)
        upsample4 = torch.nn.functional.conv_transpose2d(so4_out, weight_deconv4, stride=8)
        upsample5 = torch.nn.functional.conv_transpose2d(so5_out, weight_deconv5, stride=8)
        ### center crop
        so1 = crop(so1_out, img_H, img_W)
        so2 = crop(upsample2, img_H, img_W)
        so3 = crop(upsample3, img_H, img_W)
        so4 = crop(upsample4, img_H, img_W)
        so5 = crop(upsample5, img_H, img_W)

        fusecat = torch.cat((so1, so2, so3, so4, so5), dim=1)
        fuse = self.score_final(fusecat)
        results = [so1, so2, so3, so4, so5, fuse]
        results = [torch.sigmoid(r) for r in results]
        return results

3.2 損失函數部分

def cross_entropy_loss_RCF(prediction, label):
    label = label.long()
    mask = label.float()
    num_positive = torch.sum((mask==1).float()).float()
    num_negative = torch.sum((mask==0).float()).float()

    mask[mask == 1] = 1.0 * num_negative / (num_positive + num_negative)
    mask[mask == 0] = 1.1 * num_positive / (num_positive + num_negative)
    mask[mask == 2] = 0
    cost = torch.nn.functional.binary_cross_entropy(
            prediction.float(),label.float(), weight=mask, reduce=False)
    return torch.sum(cost)

參考文章:

  1. //blog.csdn.net/a8039974/article/details/85696282
  2. //gitee.com/HEART1/RCF-pytorch/blob/master/functions.py
  3. //openaccess.thecvf.com/content_cvpr_2017/papers/Liu_Richer_Convolutional_Features_CVPR_2017_paper.pdf
<<小白學PyTorch(暫時完結)>>

擴展之Tensorflow2.0 | 21 Keras的API詳解(下)池化、Normalization層

擴展之Tensorflow2.0 | 21 Keras的API詳解(上)卷積、激活、初始化、正則

擴展之Tensorflow2.0 | 20 TF2的eager模式與求導

擴展之Tensorflow2.0 | 19 TF2模型的存儲與載入

擴展之Tensorflow2.0 | 18 TF2構建自定義模型

擴展之Tensorflow2.0 | 17 TFrec文件的創建與讀取

擴展之Tensorflow2.0 | 16 TF2讀取圖片的方法

擴展之Tensorflow2.0 | 15 TF2實現一個簡單的服裝分類任務

小白學PyTorch | 14 tensorboardX可視化教程

小白學PyTorch | 13 EfficientNet詳解及PyTorch實現

小白學PyTorch | 12 SENet詳解及PyTorch實現

小白學PyTorch | 11 MobileNet詳解及PyTorch實現

小白學PyTorch | 10 pytorch常見運算詳解

小白學PyTorch | 9 tensor數據結構與存儲結構

小白學PyTorch | 8 實戰之MNIST小試牛刀

小白學PyTorch | 7 最新版本torchvision.transforms常用API翻譯與講解

小白學PyTorch | 6 模型的構建訪問遍歷存儲(附程式碼)

小白學PyTorch | 5 torchvision預訓練模型與數據集全覽

小白學PyTorch | 4 構建模型三要素與權重初始化

小白學PyTorch | 3 淺談Dataset和Dataloader

小白學PyTorch | 2 淺談訓練集驗證集和測試集

小白學PyTorch | 1 搭建一個超簡單的網路

小白學PyTorch | 動態圖與靜態圖的淺顯理解

<<小白學影像(暫時完結)>>

輪廓檢測論文解讀 | 整體嵌套邊緣檢測HED | CVPR | 2015

孿生網路入門(下) Siamese Net分類服裝MNIST數據集(pytorch)

孿生網路入門(上) Siamese Net及其損失函數

影像分割必備知識點 | Unet++ 超詳解+註解

影像分割必備知識點 | Unet詳解 理論+ 程式碼

影像分割必備知識點 | Dice損失 理論+程式碼

3D卷積入門 | 多論文筆記 | R2D C3D P3D MCx R(2+1)D

小白學論文 | EfficientNet強在哪裡

小白學論文 | 神經網路初始化Xavier

小白學論文 | 端側神經網路GhostNet(2019)

小白學目標檢測 | RCNN, SPPNet, Fast, Faster

小白學影像 | BatchNormalization詳解與比較

小白學影像 | Group Normalization詳解+PyTorch程式碼

小白學影像 | 八篇經典CNN論文串講

影像增強 | CLAHE 限制對比度自適應直方圖均衡化

小白學卷積 | 深入淺出卷積網路的平移不變性

小白學卷積 | (反)卷積輸出尺寸計算

損失函數 | 焦點損失函數 FocalLoss 與 GHM

<<小白學機器學習(暫時完結)>>

小白學ML | 隨機森林 全解 (全網最全)

小白學SVM | SVM優化推導 + 拉格朗日 + hingeLoss

小白學LGB | LightGBM = GOSS + histogram + EFB

小白學LGB | LightGBM的調參與並行

小白學XGB | XGBoost推導與牛頓法

評價指標 | 詳解F1-score與多分類F1

小白學ML | Adaboost及手推演算法案例

小白學ML | GBDT梯度提升樹

小白學優化 | 最小二乘法與嶺回歸&Lasso回歸

小白學排序 | 十大經典排序演算法(動圖)

雜談 | 正態分布為什麼如此常見

Adam優化器為什麼被人吐槽?

機器學習不得不知道的提升技巧:SWA與pseudo-label

<<小白面經(暫時完結)>>

秋招總結 | 一個非Top學校的跨專業的演算法應屆研究生的幾十場面試

【小白面經】快手 AI演算法崗 附答案解析

【小白面經】 拼多多 AI演算法崗 附帶解析

【小白面經】八種應對樣本不均衡的策略

【小白面經】之防止過擬合的所有方法

【小白面經】梯度消失爆炸及其解決方法

【小白面經】 判別模型&生成模型

<<小白健身(暫時完結)>>

【小白健身】腹肌搓衣板化

【小白健身】8個動作練爆胸大肌

【小白健身 】背闊大作戰(下)

【小白健身】背闊大作戰(上)

【小白健身】徒手健身40個動作(gif)

【小白健身】彈力帶輕度健身gif動圖