[yolov3のMMDetectionソースコードの解釈]バックボーン-Darknet53。

序文

このブログでは、バックボーンであるDarknet53に関するMMDetectionの実装ソースコードについて説明します。実際、私は以前に多くのバージョンのyolov3コードを学習しましたが、それらはすべてさまざまな構成ファイルのソースコードに基づいており、完全なネットワーク構築はありません。コードなので、実験中に変更したいと思います。yolov3のネットワークを使用すると、開始方法や構成ファイルの変更方法がなく、どこから始めればよいかわからないことがわかります。つい最近MMDetectionを学んだので、この最も単純で最も古典的な1ステージのターゲット検出ネットワークから始めて、MMDetectionの謎を解明する予定です(小さな構成を少し書く...)。

[Darknet53ネットワーク構造(どちらも盗まれた写真です。私は怠惰すぎて自分で描くことができません)]
画像の説明を追加してください

画像の説明を追加してください
さて、上記はDarkent53の基本的な理解があります、バックボーンの構築を始めましょう。

1.構成ファイル

バックボーン関連の構成ファイルは次のとおりです。

モデル名_バックボーン名_データセット名

yolov3_d53_mstrain-608_273e_pest.py

model = dict(
    type='YOLOV3',
    backbone=dict(
        type='Darknet',  # backbone类型
        depth=53,        # 网络层数
        out_indices=(3, 4, 5),   # 输出的stage的序号
        init_cfg=dict(type='Pretrained', checkpoint='open-mmlab://darknet53')),  # 预训练模型:open-mmlab://darknet53

二、ダークネット

ここでは、構成ファイルから始めて、構成を読み取り、登録してクラスDarknet(BaseModule)を見つける方法を計画していません。ここでの学習センターは、darknet53の構築に配置されます。構成ファイルの解釈とレジストリについてはMMCVなどの操作については、他のソースコード解釈ブログ[MMDetectionターゲット検出フレームワークの学習]を参照してください。

2.1、ダークネットクラスの紹介とグローバルパラメータ設定

@BACKBONES.register_module()
class Darknet(BaseModule):
    """Darknet backbone.
    Args:
        depth (int): Depth of Darknet. Currently only support 53.
        out_indices (Sequence[int]): Output from which stages.
        frozen_stages (int): Stages to be frozen (stop grad and set eval mode).
            -1 means not freezing any parameters. Default: -1.
        conv_cfg (dict): Config dict for convolution layer. Default: None.
        norm_cfg (dict): Dictionary to construct and config norm layer.
            Default: dict(type='BN', requires_grad=True)
        act_cfg (dict): Config dict for activation layer.
            Default: dict(type='LeakyReLU', negative_slope=0.1).
        norm_eval (bool): Whether to set norm layers to eval mode, namely,
            freeze running stats (mean and var). Note: Effect on Batch Norm
            and its variants only.
        pretrained (str, optional): model pretrained path. Default: None
        init_cfg (dict or list[dict], optional): Initialization config dict.
            Default: None

    Example:
        >>> from mmdet.models import Darknet
        >>> import torch
        >>> self = Darknet(depth=53)
        >>> self.eval()
        >>> inputs = torch.rand(1, 3, 416, 416)
        >>> level_outputs = self.forward(inputs)
        >>> for level_out in level_outputs:
        ...     print(tuple(level_out.shape))
        ...
        (1, 256, 52, 52)
        (1, 512, 26, 26)
        (1, 1024, 13, 13)
    """

    # Dict(depth: (layers, channels))
    arch_settings = {
    
    
        # 深度  5个stage的重复个数    5个stage的输入channel和输出channel
        53: ((1, 2, 8, 8, 4), ((32, 64), (64, 128), (128, 256), (256, 512),
                               (512, 1024)))
    }

2.2、__init__初期化

    def __init__(self,
                 depth=53,  # backbone深度
                 out_indices=(3, 4, 5),  # backbone输出的stage的序号(输出向Neck)
                 frozen_stages=-1,  # 哪些层需要冻结权重训练
                 conv_cfg=None,  # 卷积层配置
                 norm_cfg=dict(type='BN', requires_grad=True),  # norm层配置
                 act_cfg=dict(type='LeakyReLU', negative_slope=0.1),  # 激活函数配置
                 norm_eval=True,  # 是否将norm layer设置为eval mode 相应的需要冻结mean and var参数
                 pretrained=None,  # 预训练配置
                 init_cfg=None):  # 初始化配置
        super(Darknet, self).__init__(init_cfg)
        if depth not in self.arch_settings:
            raise KeyError(f'invalid depth {
      
      depth} for darknet')

        self.depth = depth  # backbone深度
        self.out_indices = out_indices  # backbone输出的stage的序号(输出向Neck)
        self.frozen_stages = frozen_stages  # 哪些层需要冻结权重训练
        # 5个stage的重复个数 5个stage的输入channel和输出channel
        self.layers, self.channels = self.arch_settings[depth]

        # 卷积的配置文件 一般conv_cfg=None  norm_cfg=BN  act_cfg=LeakyReLU
        cfg = dict(conv_cfg=conv_cfg, norm_cfg=norm_cfg, act_cfg=act_cfg)

        # backbone的第一层
        self.conv1 = ConvModule(3, 32, 3, padding=1, **cfg)

        # self.cr_blocks:存放所有的卷积名即通过name可以直接用self.name找到
        self.cr_blocks = ['conv1']

        # 依次遍历搭建其他的5个stage  [1,2,8,8,4]
        for i, n_layers in enumerate(self.layers):
            layer_name = f'conv_res_block{
      
      i + 1}'  # 每一个stage的name
            in_c, out_c = self.channels[i]  # 每一个stage的输入输出channel
            self.add_module(  # 调用make_conv_res_block函数,搭建当前stage
                layer_name,
                self.make_conv_res_block(in_c, out_c, n_layers, **cfg))
            self.cr_blocks.append(layer_name)  # 更新self.cr_blocks

        # 是否将norm layer设置为eval mode 相应的需要冻结mean and var参数
        # Note: Effect on Batch Norm and its variants only.
        self.norm_eval = norm_eval

        assert not (init_cfg and pretrained), \
            'init_cfg and pretrained cannot be setting at the same time'
        # pretrained=str self.init_cfg 导入预训练初始化配置
        if isinstance(pretrained, str):
            warnings.warn('DeprecationWarning: pretrained is deprecated, '
                          'please use "init_cfg" instead')
            self.init_cfg = dict(type='Pretrained', checkpoint=pretrained)
        # pretrained=None and init_cfg=None  设置为Kaiming初始化配置
        elif pretrained is None:
            if init_cfg is None:
                self.init_cfg = [
                    dict(type='Kaiming', layer='Conv2d'),
                    dict(
                        type='Constant',
                        val=1,
                        layer=['_BatchNorm', 'GroupNorm'])
                ]
        else:
            raise TypeError('pretrained must be a str or None')

初期化後、self(Darknet)に次の変数があることがわかります。

ここに画像の説明を挿入

2.3。前向き推論

    def forward(self, x):
        outs = []  # 存放backbone的3个输出feature map
        for i, layer_name in enumerate(self.cr_blocks):  # 遍历每一个操作名称
            # getattr:返回对象self的一个name=layer_name的属性(就是__init__搭建的一个层结构)
            cr_block = getattr(self, layer_name)
            x = cr_block(x)  # 前向推理
            # 如果i in self.out_indices 就将当前层的输出feature map保存到outs
            # 值得注意的是i=0就是backbone的第一个卷积层  i=1~5就是后面需要在make_conv_res_block中搭建的5个模块
            # 其中第3、4、5三个模块输出到Neck中
            if i in self.out_indices:
                outs.append(x)

        # 返回outs 3个输出feature map会在YOLOV3的forward中传入YOLOv3Neck
        return tuple(outs)

2.4、ステージ1-5をビルドします

この時点で、記事の冒頭にある2つの図と組み合わせると、一般的なバックボーン全体の構造に精通しているはずです。残りは質問です。このstage1-stage5はどのように構築されますか。これには、make_conv_res_blockとResBlockの2つの関数が含まれます。

make_conv_res_block関数:

    @staticmethod
    def make_conv_res_block(in_channels,  # stage的输入channel
                            out_channels,  # stage的输出channel  out_channels=2*in_channels
                            res_repeat,  # 这个stage的ResBlock重复个数
                            conv_cfg=None,  # 卷积配置 一般是None
                            norm_cfg=dict(type='BN', requires_grad=True),  # norm layer配置 一般是BN
                            act_cfg=dict(type='LeakyReLU',  # 激活函数配置 一般是LeakyReLU
                                         negative_slope=0.1)):
        """In Darknet backbone, ConvLayer is usually followed by ResBlock. This
        function will make that. The Conv layers always have 3x3 filters with
        stride=2. The number of the filters in Conv layer is the same as the
        out channels of the ResBlock.

        Args:
            in_channels (int): The number of input channels.
            out_channels (int): The number of output channels.
            res_repeat (int): The number of ResBlocks.
            conv_cfg (dict): Config dict for convolution layer. Default: None.
            norm_cfg (dict): Dictionary to construct and config norm layer.
                Default: dict(type='BN', requires_grad=True)
            act_cfg (dict): Config dict for activation layer.
                Default: dict(type='LeakyReLU', negative_slope=0.1).
        """
        # 注意:
        # 1、一个stage = Conv(k=3,s=2,p=1) + ResBlock x n
        # 2、整个stage在第一个Conv就将feature map的channel上升为了当前stage输入channel的2倍(即当前stage的输出channel)
        #    wh下采样为输入feature map的一般半 且之后的所有ResBlock部分的feature map wh不变

        # {'conv_cfg': None, 'norm_cfg': {'type': 'BN', 'requires_grad': True}, 'act_cfg': {'type': 'LeakyReLU', 'negative_slope': 0.1}}
        cfg = dict(conv_cfg=conv_cfg, norm_cfg=norm_cfg, act_cfg=act_cfg)

        model = nn.Sequential()
        model.add_module(  # 搭建当前stage的第一个3x3Conv 下采样卷积
            'conv',
            ConvModule(  # mmcv内置的卷积搭建模块 类似Pytorch中的Conv2d
                in_channels, out_channels, 3, stride=2, padding=1, **cfg))

        # 依次搭建res_repeat个ResBlock
        # 注意这res_repeat个ResBlock中所有的卷积输出channel都是out_channels
        for idx in range(res_repeat):
            model.add_module('res{}'.format(idx),
                             ResBlock(out_channels, **cfg))
        return model

ResBlock関数:

class ResBlock(BaseModule):
    """The basic residual block used in Darknet. Each ResBlock consists of two
    ConvModules and the input is added to the final output. Each ConvModule is
    composed of Conv, BN, and LeakyReLU. In YoloV3 paper, the first convLayer
    has half of the number of the filters as much as the second convLayer. The
    first convLayer has filter size of 1x1 and the second one has the filter
    size of 3x3.

    Args:
        in_channels (int): The input channels. Must be even.
        conv_cfg (dict): Config dict for convolution layer. Default: None.
        norm_cfg (dict): Dictionary to construct and config norm layer.
            Default: dict(type='BN', requires_grad=True)
        act_cfg (dict): Config dict for activation layer.
            Default: dict(type='LeakyReLU', negative_slope=0.1).
        init_cfg (dict or list[dict], optional): Initialization config dict.
            Default: None
    """

    def __init__(self,
                 in_channels,  # ResBlock的输入channel=输出channel
                 conv_cfg=None,  # conv配置 一般为None
                 norm_cfg=dict(type='BN', requires_grad=True),  # norm layer配置 一般为BN
                 act_cfg=dict(type='LeakyReLU', negative_slope=0.1),  # 激活函数配置 一般为LeakyReLU
                 init_cfg=None):  # 初始化配置 一般为None
        super(ResBlock, self).__init__(init_cfg)
        # 注意:ResBlock = 1x1Conv+BN+LeakyReLU + 3x3Conv+BN+LeakyReLU
        #      第一个卷积将channel下降为输入channel一半  第二个卷积将channel恢复到输入channel大小
        #      所以整个ResBlock的输入channel和输出channel相等  且整个ResBlock所有的特征的wh都相等

        assert in_channels % 2 == 0  # ensure the in_channels is even
        half_in_channels = in_channels // 2  # 第一个卷积的输出channel 要下降为输入的一半

        # shortcut
        cfg = dict(conv_cfg=conv_cfg, norm_cfg=norm_cfg, act_cfg=act_cfg)

        self.conv1 = ConvModule(in_channels, half_in_channels, 1, **cfg)  # 1x1conv
        self.conv2 = ConvModule(  # 3x3conv
            half_in_channels, in_channels, 3, padding=1, **cfg)

    def forward(self, x):
        residual = x
        out = self.conv1(x)
        out = self.conv2(out)
        out = out + residual

        return out

[バックボーンからの出力]

ここに画像の説明を挿入
形状を含むテンソル形式の3つのフィーチャマップを返します:[bs、256、64、64]、[bs、512、32、32]、[bs、1024、16、16]、これはネック(FPN)レイヤーに渡されます機能融合用。

要約する

yoloシリーズのコードを長い間学んでいるので、yolov3、yolov4、yolov5の設定ファイルバージョンをたくさん見てきたので、この部分は全体として比較的シンプルです。最初は、ソースコードによると、コメント、そしてデバッグは理解できます。

しかし実際には、BaseModuleやConvModuleなど、私が選択的に無視するMMCVに関するコードがいくつかあります。1つは時間が足りないのでメインコードしか見ることができない、もう1つは怠け者(笑)です。MMCVのソースコードでこの落とし穴を埋め合わせられるといいのですが。未来!

おすすめ

転載: blog.csdn.net/qq_38253797/article/details/121857710