【動手學PaddlePaddle2.0系列】PP-YOLO詳解(1)-- backbone

PP-YOLO詳解(1)-- backbonepython

你們好,本次教程將帶領你們開啓PP-YOLO學習。經過前面一系列學習,相信你們已經掌握了圖像分類任務的基本概念以及相關實踐,下面將帶你們實戰目標檢測任務中經典的YOLO系列算法:PP-YOLO。git

本次將對使用的backbone網絡:ResNet50-vd-dcn,進行講解。github

下載安裝命令

## CPU版本安裝命令
pip install -f https://paddlepaddle.org.cn/pip/oschina/cpu paddlepaddle

## GPU版本安裝命令
pip install -f https://paddlepaddle.org.cn/pip/oschina/gpu paddlepaddle-gpu

1 ResNet_D

Resnet_vd最先是在Bag of Tricks for Image Classification with Convolutional Neural Networks,這篇文章中提出了不少很是實用的訓練技巧,我會在後面專門出一期教程對這些技巧進行詳細講解。算法

對於ResNet網絡來講,最核心的部分即跳躍鏈接結構,其分爲兩種BasicBlock、BottleneckBlock,網絡

ide

在這裏,咱們只關心BottleneckBlock這個結構,在論文中,對該結構作了以下改進:性能

須要注意的是每一個結構中輸入和輸出的鏈接方式,在resnet_c中,改進的地方爲模型開始的7 * 7卷積層,使用了連續的3 * 3 卷積層進行了替代。在論文中主要針對BottleneckBlock進行了改進。原始的BottleneckBlock以下所示:學習

1.1 代碼講解

代碼來源於paddle2.0rc官方API文檔源碼,傳送門,經過閱讀源碼,發現這裏直接實現了resnet_vb結構,即下采樣放在了第二個卷積(3 * 3)上,沒有放在第一個卷積(1 * 1)上。而且paddleclas中,對resnet的定義也是使用了resnet_vb結構。url

1.1.1 ResNet_B詳解

class BottleneckBlock(nn.Layer):
    def __init__(self,
                 num_channels,
                 num_filters,
                 stride,
                 shortcut=True,
                 name=None,
                 data_format="NCHW"):
        super(BottleneckBlock, self).__init__()

        self.conv0 = ConvBNLayer(
            num_channels=num_channels,
            num_filters=num_filters,
            filter_size=1,
            act="relu",
            name=name + "_branch2a",
            data_format=data_format)
        self.conv1 = ConvBNLayer(
            num_channels=num_filters,
            num_filters=num_filters,
            filter_size=3,
            stride=stride,
            act="relu",
            name=name + "_branch2b",
            data_format=data_format)
        self.conv2 = ConvBNLayer(
            num_channels=num_filters,
            num_filters=num_filters * 4,
            filter_size=1,
            act=None,
            name=name + "_branch2c",
            data_format=data_format)

        if not shortcut:
            self.short = ConvBNLayer(
                num_channels=num_channels,
                num_filters=num_filters * 4,
                filter_size=1,
                stride=stride,
                name=name + "_branch1",
                data_format=data_format)

        self.shortcut = shortcut

        self._num_channels_out = num_filters * 4

    def forward(self, inputs):
        y = self.conv0(inputs)
        conv1 = self.conv1(y)
        conv2 = self.conv2(conv1)

        if self.shortcut:
            short = inputs
        else:
            short = self.short(inputs)

        y = paddle.add(x=short, y=conv2)
        y = F.relu(y)
        return y

在這裏,再也不對resnet中原始的BottleneckBlock進行贅述,直接使用飛槳官方給出的代碼。對於想要嘗試原始的BottleneckBlock的同窗,只需將第二個卷積中的stride換到第一個卷積中便可。spa

1.1.2 ResNet_C詳解

self.conv1_1 = ConvBNLayer(
            num_channels=3,
            num_filters=32,
            filter_size=3,
            stride=2,
            act='relu',
            name="conv1_1")
        self.conv1_2 = ConvBNLayer(
            num_channels=32,
            num_filters=32,
            filter_size=3,
            stride=1,
            act='relu',
            name="conv1_2")
        self.conv1_3 = ConvBNLayer(
            num_channels=32,
            num_filters=64,
            filter_size=3,
            stride=1,
            act='relu',
            name="conv1_3")

 

self.conv = ConvBNLayer(
            num_channels=self.input_image_channel,
            num_filters=64,
            filter_size=7,
            stride=2,
            act="relu",
            name="conv1",
            data_format=self.data_format)

1.1.3 ResNet_D詳解

class ConvBNLayer(nn.Layer):
    def __init__(self,
                 num_channels,
                 num_filters,
                 filter_size,
                 stride=1,
                 groups=1,
                 is_vd_mode=False,
                 act=None,
                 lr_mult=1.0,
                 name=None):
        super(ConvBNLayer, self).__init__()
        self.is_vd_mode = is_vd_mode
        self._pool2d_avg = AvgPool2D(
            kernel_size=2, stride=2, padding=0, ceil_mode=True)
        self._conv = Conv2D(
            in_channels=num_channels,
            out_channels=num_filters,
            kernel_size=filter_size,
            stride=stride,
            padding=(filter_size - 1) // 2,
            groups=groups,
            weight_attr=ParamAttr(
                name=name + "_weights", learning_rate=lr_mult),
            bias_attr=False)
        if name == "conv1":
            bn_name = "bn_" + name
        else:
            bn_name = "bn" + name[3:]
        self._batch_norm = BatchNorm(
            num_filters,
            act=act,
            param_attr=ParamAttr(name=bn_name + '_scale'),
            bias_attr=ParamAttr(bn_name + '_offset'),
            moving_mean_name=bn_name + '_mean',
            moving_variance_name=bn_name + '_variance')

    def forward(self, inputs):
        if self.is_vd_mode:
            inputs = self._pool2d_avg(inputs)
        y = self._conv(inputs)
        y = self._batch_norm(y)
        return y

1.2 ResNet_D總結

在原始的ResNet中,如圖所示,Path A的第一個1 x 1卷積層使用了strde=2的步長,會致使3/4的信息丟失。所以ResNet-B將第1、二個卷積層的步長交換。實驗代表ResNet-B 有0.5%的性能提高。

由於卷積的代價會隨着卷積核的長和寬增大而接近平方增長,所以ResNet-C使用3個連續的3 x 3 卷積替換Input stem的7 x 7 卷積。實驗代表ResNet-C 有0.2%的性能提高。

ResNet-D在ResNet-B的基礎上進一步調整,在Path B的1 x 1卷積前面,加入2 x 2 stride 2的pooling層,將下采樣提早,避免了3/4的信息丟失。實驗代表ResNet-D 有0.3%的性能提高。

上述中均來自於論文中的論述。實際上這裏還存在着一個問題,爲何在Path A中將下采樣提早就會避免信息丟失。(目前我也對這部分存在疑問,找到答案後會對此進行闡述)

對於ResNet-C使用3個連續的3 x 3 卷積替換Input stem的7 x 7 卷積,我本身的理解有兩方面的好處,

(1)感覺野相同,(2)減小參數量。

ResNet-D同ResNet-B中Path A中相同處理的辦法相同,一樣將下采樣提早,避免信息丟失。

下載安裝命令

## CPU版本安裝命令
pip install -f https://paddlepaddle.org.cn/pip/oschina/cpu paddlepaddle

## GPU版本安裝命令
pip install -f https://paddlepaddle.org.cn/pip/oschina/gpu paddlepaddle-gpu

一點小小的宣傳&下期預告

下一個項目中將爲你們介紹在Paddle2.0中DCN 是如何實現的,而且對代碼詳解。歡迎你們關注哦。

我目前在上海,感興趣的領域包括模型壓縮、小目標檢測、嵌入式,歡迎交流關注。來AI Studio互粉吧等你哦

本文同步分享在 博客「小鴨學院」(CSDN)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。

相關文章
相關標籤/搜索