前回の記事「YOLOV5 のアンカー設定」では、アンカーの生成原理と検出プロセスについて説明し、YOLOv5 のネットワーク構造について大まかに理解しました。次に、YOLOv5 のバックボーンに焦点を当て、基礎となるソース コードを深く掘り下げて、v5 のバックボーン設計を体験します。
1 バックボーンの概要とパラメータ
# Parameters
nc: 80 # number of classes
depth_multiple: 0.33 # model depth multiple
width_multiple: 0.50 # layer channel multiple
# 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
]
yolov5s のバックボーン部分は上記の通りで、そのネットワーク構造は yaml ファイルを使用して設定され、入力で構成されるネットワーク モジュールは ./models/yolo.py 解析ファイルを介して追加されます。v3 や v4 で使用されている config によって設定されるネットワークとは異なり、yaml ファイル内のネットワーク コンポーネントを重ね合わせる必要はなく、構成ファイルに番号を設定するだけで済みます。
1.1 パラメータ
# Parameters
nc: 80 # number of classes
depth_multiple: 0.33 # model depth multiple
width_multiple: 0.50 # layer channel multiple
NC:8
データセット内のカテゴリの数を表しますたとえば、MNIST には 0 から 9 までの合計 10 個のクラスが含まれています。
深さの倍数: 0.33
モデルの深さを制御するために使用され、数値≠1の場合にのみ有効になります。たとえば、最初の C3 レイヤー (後で導入される c3) のパラメーターは に設定され[-1, 3, C3, [128]]
、数値 = 3 になります。これは、v5s には 1 つの C3 (3*0.33) が存在することを意味します。同様に、v5l の C3 の数は次のようになります。 3 (v5l の Depth_multiple パラメータは 1)。
幅の倍数: 0.50
モデルの幅を制御するために使用されます。主に引数の ch_out に使用されます。。例えば、最初の Conv 層では ch_out=64 とすると、v5s の実演算処理では、コンボリューション処理のコンボリューションカーネルが 64x0.5 に設定されるため、32 チャネルの特徴マップが出力されます。
1.2 バックボーン
# 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
]
- から:-n は、最初の n 層から取得された入力を表します-1 など、前の層から入力を取得することを意味します
- 番号:ネットワークモジュールの数を示します
[-1, 3, C3, [128]]
3 つの C3 モジュールが含まれていることを示すなど - モデル:ネットワーク モジュールの名前を示します。詳細は ./models/common.py で確認できます。Conv、C3、SPPF などの共通定義済みモジュールです。
- 引数:さまざまなモジュールに渡されるパラメータ、つまり [ch_out、kernel、stride、padding、groups] を示します。, 入力が上位層の出力であるため、ここでは ch_in さえ省略されています (ch_in の初期値は 3)。修正が面倒なので、ここでの入力の取得は
def parse_model(md, ch)
./models/yolo.pyの関数から解析しています。
1.3 経験値
[-1, 1, Conv, [64, 6, 2, 2]], # 0-P1/2
入力: 3x640x640
[ch_out, kernel, stride, padding]=[64, 6, 2, 2]
したがって、新しいチャネルの数は 64x0.5=32 です。
機能マップの式に従って計算されます: Feature_new=(Feature_old-kernel+2xpadding) )/stride +1 が利用可能:
新しい機能マップのサイズは次のとおりです: Feature_new=(640-6+2x2)/2+1=320
[-1, 1, Conv, [128, 3, 2]], # 1-P2/4
入力: 32x320x320
[ch_out, kernel, stride]=[128, 3, 2]
同様に、新しいチャネル番号は 64、新しい特徴マップ サイズは 160 です。
2 バックボーンの構成
バックボーン v6.0 では、フォーカス モジュールが削除されています (モデルのエクスポートと展開を容易にするため)。バックボーンは、次の図に示すように、主に CBL、BottleneckCSP/C3、および SPP/SPPF で構成されています。
3.1 CBS
CBS モジュールは実際には特別なものではなく、Conv+BatchNorm+SiLU です。ここでは Conv のパラメータに焦点を当てます。ここで pytorch の畳み込み演算を確認します。まず CBL ソース コードに移動します。
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)
#其中nn.Identity()是网络中的占位符,并没有实际操作,在增减网络过程中,可以使得整个网络层数据不变,便于迁移权重数据;nn.SiLU()一种激活函数(S形加权线性单元)。
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))
ソース コードからわかります。Conv() には 7 つのパラメーターが含まれており、これらは 2 次元畳み込み Conv2d() の重要なパラメーターでもあります。ch_in、ch_out、kernel、stride については何も言うことはありません。最後の 3 つのパラメーターについて説明しましょう。
パディング
私が現在見ている主流の畳み込み演算から判断すると、ほとんどの研究者はカーネルを通じて特徴マップのサイズを変更しません。たとえば、googlenet の 3x3 カーネルは padding=1 を設定するため、カーネル≠ 1 を修正する必要がある場合、入力は機能マップが設定されます。p 値が指定されている場合は、p 値に従って埋められ、p 値がデフォルトの場合は、autopad 関数によって埋められます。
def autopad(k, p=None): # kernel, padding
# Pad to 'same'
if p is None:
p = k // 2 if isinstance(k, int) else [x // 2 for x in k] # auto-pad
#如果k是整数,p为k与2整除后向下取整;如果k是列表等,p对应的是列表中每个元素整除2。
return p
ここで著者は、さまざまな畳み込み演算にさまざまなサイズの畳み込みカーネルを使用する場合にはパディングも変更する必要があると考えています。そのため、値を p に代入するときに、最初に k が int であるかどうかを確認し、k がリストである場合は次にチェックします。リスト内のそれぞれの要素は分割可能です。
グループ
以下の図に示すように、グループ畳み込みを表します。
groups – 入力チャンネルから出力へのブロックされた接続の数
- groups=1 では、すべての入力がすべての出力に畳み込まれます。
- groups=2 の場合、この操作は 2 つの conv 層を並べて使用することと同等になり、それぞれが入力チャネルの半分を認識し、出力チャネルの半分を生成し、その後両方が連結されます。
- groups= in_channels では、各入力チャンネルは、サイズ ⌊(out_channels)/(in_channels)⌋ の独自のフィルター セットと畳み込まれます。
活動
機能マップをアクティブにするかどうかを決定します。SiLU は、アクティブ化に Sigmoid を使用することを意味します。
もう一つ:拡張
Conv2d のもう 1 つの重要なパラメータは拡張の拡張です. 一般的な説明は, カーネル ポイント (畳み込みカーネル ポイント) 間隔のパラメータを制御することです. 畳み込みカーネル スペースを変更することで, 特徴マップと特徴情報が保存されます. セマンティック セグメンテーションではタスク 拡張畳み込みはより効果的です。
3.2 CSP/C3
CSP はバックボーンの C3 です。バックボーンの C3 にはショートカットがありますが、C3 はネックのショートカットを使用しないため、バックボーンの C3 層は CSP1_x で表され、ネックの C3 は で表されます。 CSP2_x。
3.2.1 CSP の構造
次にバックボーンのC3層のモジュール構成を整理してみます。まずソースコードをアップロードします。
class C3(nn.Module):
# CSP Bottleneck with 3 convolutions
def __init__(self, c1, c2, n=1, shortcut=True, g=1, e=0.5): # ch_in, ch_out, number, shortcut, groups, expansion
super().__init__()
c_ = int(c2 * e) # hidden channels
self.cv1 = Conv(c1, c_, 1, 1)
self.cv2 = Conv(c1, c_, 1, 1)
self.cv3 = Conv(2 * c_, c2, 1) # act=FReLU(c2)
self.m = nn.Sequential(*[Bottleneck(c_, c_, shortcut, g, e=1.0) for _ in range(n)])
# self.m = nn.Sequential(*[CrossConv(c_, c_, 3, 1, g, 1.0, shortcut) for _ in range(n)])
def forward(self, x):
return self.cv3(torch.cat((self.m(self.cv1(x)), self.cv2(x)), dim=1))
それはソースコードから見ることができます:入力特徴マップの 1 つのブランチは最初に .cv1 を通過し、次に .m を通過してサブ特徴マップ 1 を取得します。もう一方のブランチは .cv2 を通過してサブ特徴マップ 2 を取得します。最後に、下図に示すように、サブ特徴マップ 1 とサブ特徴マップ 2 を結合して .cv3 に入力し、C3 レイヤーの出力を取得します。ここでの CV 操作はわかりやすい、つまり前の Conv2d+BN+SiLU であり、キーは .m 操作です。
.m 操作は、nn.Sequential を使用して、複数のボトルネック (図では Resx にちなんで名付けられています) をネットワークに接続します。for ループの n は、ネットワーク構成ファイルの引数内の番号です。つまり、number× Depth_multiple のボトルネックが接続されます。シリーズをネットワークへ。では、ボトルネックとは何でしょうか?
3.2.2 ボトルネック
ボトルネックを理解したい場合は、Resnet から始める必要があります。Resnet が登場する前は、ネットワークが深ければ深いほど、より多くの情報を取得でき、モデルの汎化効果が高まると一般に信じられていました。ただし、その後の多くの研究では、ネットワークの深さが特定のレベルに達すると、モデルの精度が大幅に低下することが示されています。これは過学習によって引き起こされるのではなく、バックプロパゲーション中の勾配の爆発と消滅によって引き起こされます。言い換えれば、ネットワークが深くなるほど、より多くの機能を学習するのではなく、モデルを最適化することが難しくなります。
ディープ ネットワーク モデルがより良いトレーニング結果を達成できるようにするために、残差ネットワークで提案されている残差マッピングが以前の基本マッピングを置き換えます。入力 x の場合、必要な出力は H(x) であり、ネットワークは恒等マッピングを使用して x を初期結果として受け取り、元のマッピング関係は F(x)+x になります。多層畳み込みを使用して H(x) を近似する代わりに、H(x)-x を近似する、つまり残差 F(x) を近似する方が適切です。したがって、ResNet は学習目標を目標値 H(x) と x の差に変更することに相当し、その後の学習目標は残差結果を 0 に近づけることになります。
残りのモジュールの利点は何ですか?
1.勾配拡散の側面。ResNetにショートカット構造を追加した後、バックプロパゲーション中に、2つのブロックごとに勾配が渡されるだけでなく、導出前の勾配も追加されます。これは、各ブロック内で前方に渡される勾配を人為的に増加させることに相当し、可能性が減ります。勾配分散の。
2.機能の冗長性。順方向畳み込みでは、各層の畳み込みでは実際には画像の情報の一部しか抽出されないため、層が深くなるほど元の画像情報の損失は大きくなりますが、元の画像のごく一部に過ぎません。抽出を行います。これにより明らかにアンダーフィッティングのようなものが生じます。ショートカット構造を追加することは、前のレイヤー画像のすべての情報を各ブロックに追加することと同等であり、より多くの元の情報をある程度保持します。
resnet では、ショートカット付きの残差モジュールを使用して、数百、さらには数千の層を持つネットワークを構築できます。一方、浅い残差モジュールは Basicblock (18, 34) という名前が付けられ、深いネットワークで使用される残差モジュールは Bottleneck と呼ばれます。 (50+)。
Bottleneck と Basicblock の最大の違いは、コンボリューション カーネルの構成です。Basicblock は 2 つの 3x3 畳み込み層で構成され、Bottleneck は 3x3 畳み込み層を挟む 2 つの 1x1 畳み込み層で構成されます。このうち、1x1 畳み込み層は次元を削減してから次元を復元するため、3x3 畳み込みの方がパラメーターが少なく、計算プロセスが高速になります。
最初の 1x1 畳み込みは 256 次元のチャネルを 64 次元に削減し、最後に 1x1 畳み込みによって復元します。全体として使用されるパラメータの数: 1x1x256x64 + 3x3x64x64 + 1x1x64x256 = 69632 (ボトルネックを使用しない場合) 2 つの 3x3x256 Convolution、パラメータ数: 3x3x256x256x2 = 1179648、16.94 倍の差です。
ボトルネックによりパラメータの量が削減され、計算が最適化され、元の精度が維持されます。
ここまで述べましたが、これは CSP のボトルネックに関するこれまでの状況を要約するためのものであり、実際にはより明確になっている CSP のボトルネックを振り返ってみましょう。
class Bottleneck(nn.Module):
# Standard bottleneck
def __init__(self, c1, c2, shortcut=True, g=1, e=0.5): # ch_in, ch_out, shortcut, groups, expansion
super().__init__()
c_ = int(c2 * e) # hidden channels
self.cv1 = Conv(c1, c_, 1, 1)
self.cv2 = Conv(c_, c2, 3, 1, g=g)
self.add = shortcut and c1 == c2
def forward(self, x):
return x + self.cv2(self.cv1(x)) if self.add else self.cv2(self.cv1(x))
CSP のボトルネックは resnet モジュールのボトルネックと似ていることがわかります。最初は 1x1 畳み込み層 (CBS)、次に 3x3 畳み込み層、そして最後にショートカットを通じて初期入力に追加されます。ただし、ここでの resnet との違いは、CSP が入力次元を半分にした後、次元を増やすために 1x1 畳み込みカーネルを使用せず、元の入力 x の次元を削減し、テンソルを結合するための連結方法を採用したことです。元の入力と同じ次元の出力。実際、ここで 1 つの点を区別するだけで十分です。resnetのショートカットは add によって実現され、これは機能マップの対応する位置の追加であり、チャネル数は変わりませんが、CSP のショートカットは concat によって実現されます。 , それはチャンネル数の増加です。どちらも情報融合の主な方法ですが、テンソルに対する具体的な操作は異なります。
次に、バックボーンではショートカット=True、ネックではショートカット=Falseなど、タスクの要件に応じてショートカットを設定できます。
ショートカット=Trueの場合、Resxは図のようになります。
ショートカット=False の場合、Resx は図のようになります:
これは実際に YOLOv5 が賞賛されている点です。コードはより体系的で、コードの冗長性が低くなります。ボトルネックと通常の畳み込みを組み合わせるために指定する必要があるパラメーターは 1 つだけであり、同時に、全体的な感覚も向上します。
3.3 SSPF
class SPPF(nn.Module):
# Spatial Pyramid Pooling - Fast (SPPF) layer for YOLOv5 by Glenn Jocher
def __init__(self, c1, c2, k=5): # equivalent to SPP(k=(5, 9, 13))
super().__init__()
c_ = c1 // 2 # hidden channels
self.cv1 = Conv(c1, c_, 1, 1)
self.cv2 = Conv(c_ * 4, c2, 1, 1)
self.m = nn.MaxPool2d(kernel_size=k, stride=1, padding=k // 2)
def forward(self, x):
x = self.cv1(x)
with warnings.catch_warnings():
warnings.simplefilter('ignore') # suppress torch 1.9.0 max_pool2d() warning
y1 = self.m(x)
y2 = self.m(y1)
return self.cv2(torch.cat([x, y1, y2, self.m(y2)], 1))
SSPF モジュールは、CBS の後に x、1 つのプーリングの後に y1、2 つのプーリングの後に y2、3 つのプーリングの後に self.m(y2) をスプライスし、CBS で特徴を抽出します。注意して観察すると、SSPF が特徴マップを複数回プールしているにもかかわらず、特徴マップのサイズは変化しておらず、チャネル数も変化していないことがわかります。そのため、後続の 4 つの出力は、チャンネルの寸法。このモジュールの主な機能は高レベルの特徴を抽出して融合することであり、融合の過程で、作成者は最大プーリングを何度も使用して、できるだけ多くの高レベルの意味論的特徴を抽出します。
YOLOv5s のバックボーンの概要
最後に、上記の説明と組み合わせると、v5 のバックボーンを理解するのは難しくないはずです。