yolov5-master ソースコードの詳細なメモ - yolo モジュール

この記事では、yolov5 ニューラル ネットワークの構造を簡単に説明するだけです。具体的な理論的な知識については、ご自身でニューラル ネットワークを学習してください。(コンピューターの読書体験の方が優れています)

目次

yolov5s.yaml:

yolo.py:

       __nameの場合:

       モデル:

              init: ネットワーク構造を構築する

              モデルの定義: モデルを定義します。

              建てる:

              forward: 入力画像を予測します

       解析モデル:

共通.py:


yolo ファイルを解析する前に、yolov5 のネットワーク構造がどのようなものかを理解する必要があります。

yolov5s.yaml:

(このファイルは実際には、モデルの構築をガイドする構成ファイルの単なる説明です。参考までに、このファイルを参照して独自のモデル ファイルを構成できます)

まず、図に示す yolov5 ネットワーク構造を理解します。

まず、中央の Backone、Head とは何かを理解しましょう。

# YOLOv5 v6.0 backbone
backbone:
  # [from, number, module, args]
  [[-1, 1, Conv, [64, 6, 2, 2]],  # 0-P1/2
   [-1, 1, Conv, [128, 3, 2]],  # 1-P2/4
   [-1, 3, C3, [128]],
   [-1, 1, Conv, [256, 3, 2]],  # 3-P3/8
   [-1, 6, C3, [256]],
   [-1, 1, Conv, [512, 3, 2]],  # 5-P4/16
   [-1, 9, C3, [512]],
   [-1, 1, Conv, [1024, 3, 2]],  # 7-P5/32
   [-1, 3, C3, [1024]],
   [-1, 1, SPPF, [1024, 5]],  # 9
  ]

# YOLOv5 v6.0 head
head:
  [[-1, 1, Conv, [512, 1, 1]],
   [-1, 1, nn.Upsample, [None, 2, 'nearest']],
   [[-1, 6], 1, Concat, [1]],  # cat backbone P4
   [-1, 3, C3, [512, False]],  # 13

   [-1, 1, Conv, [256, 1, 1]],
   [-1, 1, nn.Upsample, [None, 2, 'nearest']],
   [[-1, 4], 1, Concat, [1]],  # cat backbone P3
   [-1, 3, C3, [256, False]],  # 17 (P3/8-small)

   [-1, 1, Conv, [256, 3, 2]],
   [[-1, 14], 1, Concat, [1]],  # cat head P4
   [-1, 3, C3, [512, False]],  # 20 (P4/16-medium)

   [-1, 1, Conv, [512, 3, 2]],
   [[-1, 10], 1, Concat, [1]],  # cat head P5
   [-1, 3, C3, [1024, False]],  # 23 (P5/32-large)

   [[17, 20, 23], 1, Detect, [nc, anchors]],  # Detect(P3, P4, P5)
  ]

       バックボーン:数字の意味だけを表す

              #0-P1/2 1階

                     from: -1 は入力が前の層から渡されることを意味し、[-1, 6] は入力が 11 番目と 6 番目の層から渡されることを意味します

                     数値: モジュール構造の数を示します。数値>1 の場合、数値=数値*深さ_多重になります。

                     module: モジュール構造 (Conv、C3 など)、畳み込み層構造、( common.py )で定義

                     args: 受信パラメータ。各パラメータの意味を判断するには、共通の各ネットワーク層モデル カテゴリに問い合わせる必要があります。

                     P1:1階

                     /2: ステップ サイズは 2 で、ピクチャの長さと幅は 2 で割られます (ピクチャの解像度で長さと幅が 32 の倍数である必要があるのはこのためです)。

                 #1-P2/4 2階:

                層ごとに

        head: ここもバックボーンと同様、さまざまなネットワーク層で構成されています

               nn.Upsample : アップサンプリング層

               Concat : 各層の送信機能を合成するネットワーク層

               検出:                   推論検出層

ここには合計 24 のニューラル ネットワーク層があることがわかりましたが、これらの 24 層はどのように互いに重ね合わされるのでしょうか? 単純に層ごとに重ね合わされているのでしょうか?

答えは明らかにそうではありません。実際のネットワーク構造は次のとおりです: (インターネット上の多くの場所にあるネットワーク構造解析のバックボーンは上から下に重ねて表示されます。ここでは B ステーションから上に向かって 480920279 を下から上に学習しました。ネットワーク構造)

この図の意味は、RGB 3 チャネルの画像をニューラル ネットワークに渡し、Backbone10 層のネットワークを通過した後、ヘッドに入り、アップサンプリング、包括的な特徴処理などを行い、最後に 3 つの C3 ネットワーク層を経ます。 3 つの C3 レイヤーは、上から下まで、高レベル フィーチャ レイヤー、中レベル フィーチャ レイヤー、および低レベル フィーチャ レイヤーと呼ばれるものです。

これらのフィーチャ レイヤーの違いは、低レベルは小さなターゲットを検出し、中レベルは中サイズのターゲットを検出し、高レベルは大きなターゲットを検出し、これらを組み合わせてターゲットを予測することです。

# Parameters
nc: 6  # number of classes
depth_multiple: 0.33  # model depth multiple
width_multiple: 0.50  # layer channel multiple
anchors:
  - [10,13, 16,30, 33,23]  # P3/8
  - [30,61, 62,45, 59,119]  # P4/16
  - [116,90, 156,198, 373,326]  # P5/32

        nc:(クラス数)対象カテゴリ番号

        アンカー: 3 つのレイヤーが異なる機能レベルに対応します

               [10,13, 16,30, 33,23] 下層のアンカー 3 個、10×13、16×30、33×23

               [30,61,62,45,59,119]中間層のアンカー3本

        Depth_multiple: モデルの深さの倍数、モデル作成時の数値* Depth_multiple

        width_multiple: チャネル倍数、各レイヤのチャネルパラメータ * width_multiple=送信チャネル数、深さと幅パラメータの乗算は整数ではないため切り捨てられます

        Depth_multiple、width_multiple はモデルの複雑さを決定します。値が大きいほど、精度が複雑になり、時間がかかります。

さまざまなモデル ファイルの精度: n<s<m<l<x (精度が高いほど時間がかかります) (これらのファイルの違いは深さの倍数とチャネルの倍数のみです)

したがって、このマニュアルを参照して、モデル構成ファイルを自分で作成することができます。

ニューラル ネットワークの構造を理解したら、yolo.pyに移動して、ニューラル ネットワークがどのように実装されているかを確認しましょう。

yolo.py:

       __nameの場合:

              モデルの作成:              yolov5 モデルを作成します

(ここにはいくつかの書き方があるかもしれません):

最初:

# Create model
im = torch.rand(opt.batch_size,3,640,640).to(device)
model = Model(opt.cfg).to(device)

2 番目のタイプ:

    # Create model
    model = Model(opt.cfg).to(device)
    model.train()

    # Profile
    if opt.profile:
        img = torch.rand(8 if torch.cuda.is_available() else 1, 3, 640, 640).to(device)
        y = model(img, profile=True)

                     im(g): 画像をランダムに定義します

                     y=model: モデルを定義します ( -->Model )

              (オプション):

       モデル:

              init: ネットワーク構造を構築する

    def __init__(self, cfg='yolov5s.yaml', ch=3, nc=None, anchors=None):  # model, input channels, number of classes
        super().__init__()
        if isinstance(cfg, dict):
            self.yaml = cfg  # model dict
        else:  # is *.yaml
            import yaml  # for torch hub
            self.yaml_file = Path(cfg).name
            with open(cfg, encoding='ascii', errors='ignore') as f:
                self.yaml = yaml.safe_load(f)  # model dict

                     cfg: 設定ファイル (yolov5s.yaml)

                     ch: 入力映像チャンネル番号

                     super().init: 設定ファイルをロードします。

                            入力が文字列かどうかを判断する

                            self.yaml_file はファイル名を取得します

                     with: ファイルのロードを開始すると、主要な要素が辞書の形式で保存されます。

              モデルの定義: モデルを定義します。

# Define model
        ch = self.yaml['ch'] = self.yaml.get('ch', ch)  # input channels
        if nc and nc != self.yaml['nc']:            # 判断该值和yaml中的值是否一样
            LOGGER.info(f"Overriding model.yaml nc={self.yaml['nc']} with nc={nc}")
            self.yaml['nc'] = nc  # override yaml value
        if anchors:
            LOGGER.info(f'Overriding model.yaml anchors with anchors={anchors}')
            self.yaml['anchors'] = round(anchors)  # override yaml value
        self.model, self.save = parse_model(deepcopy(self.yaml), ch=[ch])  # model, savelist
        self.names = [str(i) for i in range(self.yaml['nc'])]  # default names
        self.inplace = self.yaml.get('inplace', True)

                     ch: チャンネル数を定義します

                     nc、アンカー: 修正クラスの数

                     .model: モデルを構築します ( ——> parse_model )

                     .names: カテゴリ名

                     .inplace: キーワードを読み込みます

              建てる:

# Build strides, anchors
        m = self.model[-1]  # Detect()
        if isinstance(m, Detect):
            s = 256  # 2x min stride
            m.inplace = self.inplace
            m.stride = torch.tensor([s / x.shape[-2] for x in self.forward(torch.zeros(1, ch, s, s))])  # forward:[8, 16, 32]
            m.anchors /= m.stride.view(-1, 1, 1)
            check_anchor_order(m)
            self.stride = m.stride
            self._initialize_biases()  # only run once

                     モデルの上位層が検出層であるかどうかを判断する

                            m.stride: s*s ピクチャを予測用の低-中-高の特徴レベルに配置し、元のサイズを予測レイヤーのサイズで割ってステップ サイズを取得します。

                            m.anchors /=: アンカーをステップ サイズで割った値

                            check_anchor: 受信アンカーの順序を確認します。

              forward: 入力画像を予測します

       解析モデル:

LOGGER.info(f"\n{'':>3}{'from':>18}{'n':>3}{'params':>10}  {'module':<40}{'arguments':<30}")
    anchors, nc, gd, gw = d['anchors'], d['nc'], d['depth_multiple'], d['width_multiple']
    na = (len(anchors[0]) // 2) if isinstance(anchors, list) else anchors  # number of anchors
    no = na * (nc + 5)  # number of outputs = anchors * (classes + 5)

    layers, save, c2 = [], [], ch[-1]  # layers, savelist, ch out

              .info: 情報を印刷します

              yaml パラメータを取得します。

              na: アンカーの数

              no: 出力チャネル、nc(80)、5 (長方形ボックス内の 4 つの点 + 信頼度)、値は 255 です。

              レイヤー (作成されたネットワークの各レイヤーのストレージ)、保存 (保存されるフィーチャ レイヤーの統計)

    for i, (f, n, m, args) in enumerate(d['backbone'] + d['head']):  # from, number, module, args
        # 获取模型,这里主要是作者防止格式错误而不采取直接赋值
        m = eval(m) if isinstance(m, str) else m  # eval strings
        for j, a in enumerate(args):
            try:
                # 同理防止格式错误而不直接赋值
                args[j] = eval(a) if isinstance(a, str) else a  # eval strings, [64, 6, 2, 2]
            except NameError:
                pass

              モデルと引数を取得します。作成者が書式設定エラーを防止し、直接代入を使用していないため、これがここに書かれている可能性があります。

次のステップでは、現在のネットワーク層が畳み込み層であるか、アップサンプリング層、検出層などであるかを判断し、それに応じて異なる処理を実行します。

# n>1就乘以深度倍数
        n = n_ = max(round(n * gd), 1) if n > 1 else n  # depth gain
        if m in [Conv, GhostConv, Bottleneck, GhostBottleneck, SPP, SPPF, DWConv, MixConv2d, Focus, CrossConv,
                 BottleneckCSP, C3, C3TR, C3SPP, C3Ghost]:
            c1, c2 = ch[f], args[0]
            if c2 != no:  # if not output
                c2 = make_divisible(c2 * gw, 8)

            args = [c1, c2, *args[1:]]   # args[3, 32, 6, 2, 2]
            if m in [BottleneckCSP, C3, C3TR, C3Ghost]:
                args.insert(2, n)  # number of repeats
                n = 1
        elif m is nn.BatchNorm2d:
            args = [ch[f]]
        elif m is Concat:
            c2 = sum(ch[x] for x in f)
        elif m is Detect:
            args.append([ch[x] for x in f])
            if isinstance(args[1], int):  # number of anchors
                args[1] = [list(range(args[1] * 2))] * len(f)
        elif m is Contract:
            c2 = ch[f] * args[0] ** 2
        elif m is Expand:
            c2 = ch[f] // args[0] ** 2
        else:
            c2 = ch[f]

              m の場合: 構造体のタイプを決定します

                     畳み込み層: チャネル数が 255 であるかどうかを判断し、それ以外の場合はチャネルの倍数を乗算し、それが 8 の倍数であるかどうかを判断します (8 の倍数は GPU 計算に適しています)。

                        C3層:        

m_ = nn.Sequential(*(m(*args) for _ in range(n))) if n > 1 else m(*args)  # module
        t = str(m)[8:-2].replace('__main__.', '')  # module type
        np = sum(x.numel() for x in m_.parameters())  # number params
        m_.i, m_.f, m_.type, m_.np = i, f, t, np  # attach index, 'from' index, type, number params
        LOGGER.info(f'{i:>3}{str(f):>18}{n_:>3}{np:10.0f}  {t:<40}{str(args):<30}')  # print
        save.extend(x % i for x in ([f] if isinstance(f, int) else f) if x != -1)  # append to savelist
        layers.append(m_)

              save.extend: 必要なフィーチャ レイヤー [4、6、10、14、17、20、23] を保存します。

              ch.append(c2): 各レイヤーのチャンネル数を保存し、上位レイヤーの出力チャンネルをそのレイヤーの入力チャンネルとして使用します

共通.py:

簡単に理解するために Conv を例に挙げます。

class Conv(nn.Module):
    # Standard convolution
    def __init__(self, c1, c2, k=1, s=1, p=None, g=1, act=True):  # ch_in, ch_out, kernel, stride, padding, groups
        super().__init__()
        self.conv = nn.Conv2d(c1, c2, k, s, autopad(k, p), groups=g, bias=False)
        self.bn = nn.BatchNorm2d(c2)
        self.act = nn.SiLU() if act is True else (act if isinstance(act, nn.Module) else nn.Identity())

    def forward(self, x):
        return self.act(self.bn(self.conv(x)))

    def forward_fuse(self, x):
        return self.act(self.conv(x))

    コンバージョン:

              初期化:

                     c1: このレイヤーのチャンネル数を入力します

                     c2: この層のチャンネル数を出力します。

                     k=1: コンボリューションカーネルのサイズ

                     s=1: 畳み込み層のスライディングのステップ サイズ

       C3:

私の才能も知識も乏しいので、読者の皆様が記事の内容に誤りを発見された場合は、ご一報いただければ幸いです。

             

おすすめ

転載: blog.csdn.net/qq_68271367/article/details/127418353