目次
主に、yolov5s.yaml の構成ファイルから、Focus、C3、SPP、Conv、および Bottleneck モジュールを 1 つずつ分析します。
これは最も初期のバージョン v5 の説明であり、現在 2022 年の最新バージョンは V6.2 です。Focus の Backbone 部分が 6*6 Conv に置き換えられている、SPP の Neck 部分が SPPF に置き換えられているなど、細部にいくつかの違いがあります。詳しく知りたい場合は、github に移動して学習することをお勧めします。ソースコード。
yolov5s.yaml
# Parameters
nc: 5 # number of classes
depth_multiple: 0.33 # model depth multiple
width_multiple: 0.50 # layer channel multiple
anchors:
- [24,24,29,84,59,42] # P3/8
- [45,146,75,87,157,49] # P4/16
- [310,167,139,341,127,151] # P5/32
# YOLOv5 backbone
backbone:
# [from, number, module, args]
[[-1, 1, Focus, [64, 3]], # 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, 9, 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, 1, SPP, [1024, [5, 9, 13]]],
[-1, 3, C3, [1024, False]], # 9
]
# YOLOv5 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)
]
yolov5s.yaml 基本パラメータの意味
yolov5 の各バージョンのパラメーターの意味は同じです。違いは、ネットワーク チャネルの数と繰り返されるモジュールの数にあるため、この構成ファイルの他のバージョンを理解している限り、意味は似ています。
いくつかの基本的なパラメータ:
nc: データ セット内のオブジェクトのカテゴリの数
depth_multiple: ネットワークの深さを制御する係数
width_multiple: ネットワークの幅を制御する係数
アンカー: 異なるスケールのフィーチャ マップに割り当てられたアンカー。 3 つのリストは、それらが 3 つのスケールに割り当てられていることを示します。これら 3 つの A スケールは **[[17, 20, 23], 1, Detect, [nc, anchors]] で指定され、これらは 17、20、および 23 層です。ネットワークのそれぞれ。注釈 P3/8 は、入力が 23 = 8 倍ダウンサンプリングされることを意味し、ネットワークの 17 層目の特徴マップが入力の 1/8 であることもわかります。さまざまなデータ セットによると、K-means クラスタリング アルゴリズムを使用して、データ セットに最適なアンカー ボックスを生成できます。
背骨:
バックボーン ネットワークの定義はリストであり、各行はレイヤーを表します。各行が 4 つの要素を持つリストであることがわかります。[from、number、module、args] は、これら 4 つの要素の意味を説明しています。
from: このレイヤーの入力がどこから来るかを示します。-1 は入力が前の層から取得されることを意味し、-2 は上位 2 つの層を意味し、3 は 3 番目の層 (0 から数えて) を意味し、[-1, 4] は入力が上位層と 4 番目の層から取得されることを意味します。 、 等々。. . ネットワーク層の数はコメントに記載されています. 0 から始まる各行は層を表します. 例えば, 0-P1/2 は 0 番目の層を表し, 特徴マップのサイズは入力の 1/21 です.
number: この層のモジュールのスタック回数を示します. C3 や BottleneckCSP などのモジュールの場合, サブモジュールのスタック回数を示します. 詳細はソースコードを参照してください. もちろん、最終的な数値に depth_multiple 係数を掛ける必要があります。
module : この層のモジュールが何であるかを示します。Conv はコンボリューション + BN + 活性化モジュールです。すべてのモジュールは、model/common.py で定義されています。
args: モジュールに入力されるパラメーターを示します。たとえば、Conv: [128, 3, 2] は、出力チャネル 128、畳み込みカーネル サイズ 3、strid=2 を意味します。もちろん、出力チャネルの最終的な数に width_multiple を掛ける必要があります。他のモジュールでは、最初のパラメータ値が一般的に参照されます。 to 出力チャネルの数。詳細については、model/common.py の定義を参照してください。
頭
ルールは BackBone と同じです。最後の
レイヤーについて説明します。検出モジュール、[nc、アンカー] は、検出モジュールを初期化するためのパラメーターです。Detect モジュールは model/yolo.py で宣言されています。これは、モデルから目的のレイヤーを入力として取得し、それを対応する検出ヘッドに変換し、その出力を使用して損失を計算することと同じです。
集中
1. フォーカスモジュールの機能
v5 のフォーカス モジュールは、画像がバックボーンに入る前に画像をスライスします. 特定の操作は、画像内の 1 つおきのピクセルの値を取得することです. これは、隣接するダウンサンプリングに似ています. この方法では、4 つの画像が取得されます.相補的で、長さはほぼ同じですが、情報が失われることはありません.このように、WとHの情報はチャネル空間に集中し、入力チャネルは4倍に拡張されます,つまり、スプライスされた写真元の RGB 3 チャネル モード 12 チャネルと比較され、最終的に得られた新しい画像が畳み込み操作にかけられ、最終的に情報損失のないダブル ダウンサンプリングされた特徴マップが得られます。
yolov5s を例にとると、元の 640 × 640 × 3 の画像が Focus 構造体に入力され、スライス操作を使用して最初に 320 × 320 × 12 の特徴マップになり、次に畳み込み操作の後、最終的に320 × 320 × 32 フィーチャー マップ フィーチャー マップ。スライス操作は次のとおりです。
プロジェクトの common.py には特定のコード実装があります。
class Focus(nn.Module):
# Focus wh information into c-space
def __init__(self, c1, c2, k=1, s=1, p=None, g=1, act=True): # ch_in, ch_out, kernel, stride, padding, groups
super(Focus, self).__init__()
self.conv = Conv(c1 * 4, c2, k, s, p, g, act) # 这里输入通道变成了4倍
def forward(self, x): # x(b,c,w,h) -> y(b,4c,w/2,h/2)
return self.conv(torch.cat([x[..., ::2, ::2], x[..., 1::2, ::2], x[..., ::2, 1::2], x[..., 1::2, 1::2]], 1))
テンソルのスライス操作を理解していない友人は、私が説明したスライス操作のリンクを参照できます: Pytorch tensor operation Python slicing operation
フォーカスのパラメータ量
パラメーターの数 ( params ): モデルのサイズに関連し、単位は通常 M であり、パラメーターは通常 float32 で表されるため、モデルのサイズはパラメーターの数の 4 倍になります。
計算量 (FLOPs) : 浮動小数点演算を使用して、アルゴリズムの速度に関連するアルゴリズム/モデルの複雑さを測定できます. 通常、大きなモデルの単位は G であり、小さなモデルの単位は通常 G です. M; 通常は乗算と加算演算のみが考慮され、Conv や FC などのパラメーター レイヤーの計算量のみが考慮され、BN や PReLU などは無視されます。バイアス バイアス加算やシャウトカット残差 Plus などの加算演算では、現在の技術ではバイアスのない BN と CNN があります。
パラメータ計算式:
Kh × Kw × Cin × Cout
FLOPs 計算式:
Kh × Kw × Cin × Cout × H × W = ie (現在のレイヤー フィルター × 出力特徴マップ) = params × H × W
ご存知の通り、画像がFocusモジュールを通過した後、最も直感的にダウンサンプリングの役割を果たすことになりますが、一般的に使用されている畳み込みダウンサンプリングとは少し異なります.Focusの計算量と通常のダウンサンプリングの計算量畳み込みを計算できます 比較を行います:
yolov5s のネットワーク構造では、Focus モジュールの畳み込みカーネルが 3 × 3 で、出力チャネルが 32 であることがわかります。
比較を行います。
**通常のダウンサンプリング:** 640 × 640 × 3 の画像を、ステップ サイズ 2、出力チャネル 32 の 3 × 3 畳み込みに入力します。ダウンサンプリング後、320 × 320 × 32 の特徴マップが取得され、次の計算が行われます。通常の畳み込みダウンサンプリング理論の量は次のとおりです。
FLOPs (conv) = 3 × 3 × 3 × 32 × 320 × 320 = 88473600 (バイアスを考慮しない)
params パラメータの量 (conv) = 3 × 3 × 3 × 32 +32 +32 = 928 (最後の 2 つの 32 はバイアスです)および BN 層パラメータ)
**フォーカス: ** 640 × 640 × 3 の画像をフォーカス構造に入力し、スライス操作を使用して、最初に 320 × 320 × 12 の特徴マップになり、次に 3 × 3 の畳み込み操作を行い、出力チャネルは 32 ですとなり、最終的に 320 × 320 × 32 の特徴マップになると、Focus 理論の計算量は次のようになります。
FLOPs (Focus) = 3 × 3 × 12 × 32 × 320 × 320 = 353894400 (バイアスを考慮しない場合)
params パラメータ amount (Focus) = 3 × 3 × 12 × 32 +32 +32 =3520 (上図 次の 2 つの 32 を考慮した出力パラメータの量は、バイアス層と BN 層のパラメータです。通常、これら 2 つの比率は比較的小さく、無視できます)。
Focus の計算量とパラメーターは、通常の畳み込みの 4 倍と、通常の畳み込みよりも多くなりますが、ダウンサンプリング中に情報の損失がないことが明確にわかります。
Yolov3とYolov5の改善比較
考えてみれば、Focus のパラメータ量が大きくなると、なぜ yolov5 の推論速度が V3 よりも速く正確になるのでしょうか?
2 つの構成ファイルを比較すると、V5 は V3 の 3 つのレイヤーを直接 Focus のレイヤーに置き換え、Focus はダウンサンプリングのような画像の特徴情報を失わないことがわかります。したがって、v5 はネットワーク パフォーマンスを迅速かつ正確に改善することができ、Focus は多くの貢献をしました.Focus はダウンサンプリングの機能だけでなく、画像情報を失うことなくダウンサンプリングすることもできます.
Focus モジュールの機能は、ダウンサンプリングに似た画像のスライスです. まず、画像は 320×320×12 の特徴マップに変換され、次に 3×3 畳み込み操作の後、出力チャネルは 32 になります。 32のfeature mapは一般的なコンボリューションの4倍の計算量なので、ダウンサンプリングしても情報が失われることはありません。
フォーカスに関する追加
以前のバージョンと比較すると、YOLOv5 は v6.0 バージョンの後に小さな変更があり、ネットワークの最初のレイヤー (元は Focus モジュール) が 6x6 畳み込みレイヤーに置き換えられています。この 2 つは理論的には同等ですが、一部の既存の GPU デバイス (および対応する最適化アルゴリズム) では、Focus モジュールを使用するよりも 6x6 畳み込みレイヤーを使用する方が効率的です。詳細については、この問題#4825を参照してください. 下の図は、2x2 の隣接する各ピクセルをパッチに分割し、同じ位置 (同じcolor) 各パッチで ) ピクセルをまとめて 4 つの特徴マップを取得し、3x3 の畳み込みレイヤーを接続します。これは、6x6 畳み込み層を直接使用することと同じです。
ネットワーク構造図
具体的なネットワーク構造図については、チューターが描いたものを参照できます。B局にも詳しい解説動画やブログがあります。家庭教師ブログはv5作者のGlenn Jocherさんに承認されており、ファンが誇らしげすぎます(泣)。インタラクティブシーン#6998
C3モジュール
機能:
1 yolov5 の新しいバージョンでは、著者は BottleneckCSP (ボトルネック層) モジュールを C3 モジュールに変更しました。その構造と機能は基本的に CSP アーキテクチャと同じですが、修正ユニットの選択が異なります。 3 つの標準畳み込みレイヤーと複数のボトルネック モジュールが含まれています (数は、構成ファイル .yaml の n と depth_multiple パラメーターの積によって決まります)
2 C3 と BottleneckCSP モジュールの違いは、残差出力後の Conv モジュールが削除され、concat 後の標準畳み込みモジュールのアクティベーション関数も LeakyRelu から SiLU に変更されていることです (同上)。
3 このモジュールは残差特徴を学習するためのメインモジュールです. その構造は2つのブランチに分かれています. 1つは上記の指定された複数のボトルネックスタックと3つの標準的な畳み込み層を使用し、もう1つは基本的な畳み込みモジュールを通過するだけです. 最後に, 2 つのブランチでの連結操作。
コード:
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(C3, self).__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))
変換モジュール
入力特徴マップに対して畳み込み、BN、および活性化関数演算を実行します.新しいバージョンの YOLOv5 では、作成者は活性化関数として Silu を使用します.
コード:
class Conv(nn.Module):
# Standard convolution
# ch_in, ch_out, kernel, stride, padding, groups
def __init__(self, c1, c2, k=1, s=1, p=None, g=1, act=True):
# k为卷积核大小,s为步长
# g即group,当g=1时,相当于普通卷积,当g>1时,进行分组卷积。
# 分组卷积相对与普通卷积减少了参数量,提高训练效率
super(Conv, self).__init__()
self.conv = nn.Conv2d(c1, c2, k, s, autopad(k, p), groups=g, bias=False)
self.bn = nn.BatchNorm2d(c2)
self.act = nn.Hardswish() 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 fuseforward(self, x):
return self.act(self.conv(x))
ボトルネック モジュール
効果:
1 最初にチャネル数を減らしてから拡張します (デフォルトでは半分に減らします). 具体的な方法は、最初に 1×1 畳み込みを実行してチャネルを半分に減らし、次に 3×3 畳み込みでチャネル数を 2 倍にし、機能を取得しても (合計 2 つの標準的な畳み込みモジュールを使用)、入力チャネルと出力チャネルの数は変わりません。
2 ショートカット パラメータは、残りの接続を実行するかどうかを制御します (ResNet を使用)。
3 yolov5 のバックボーンのボトルネックはデフォルトでショートカットを True にし、ヘッドのボトルネックはショートカットを使用しません。
4 ResNet に対応し、特徴の融合に concat の代わりに add を使用して、融合後の特徴の数が変わらないようにします。
達成:
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(Bottleneck, self).__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))
SPP モジュール
SPPの構造
効果:
1 SPP は Spatial Pyramid Pooling の略で、最初に標準の畳み込みモジュールを介して入力チャネルを半分にし、次にそれぞれ 5、9、および 13 のカーネル サイズで maxpooling を実行します (異なるカーネル サイズの場合、パディングは適応的です)。
2 最大 3 回のプーリング操作の結果と、プーリング操作なしのデータを連結すると、最終的な合併後のチャネル数は元の 2 倍になります。
コード:
class SPP(nn.Module):
# Spatial pyramid pooling layer used in YOLOv3-SPP
def __init__(self, c1, c2, k=(5, 9, 13)):
super(SPP, self).__init__()
c_ = c1 // 2 # hidden channels
self.cv1 = Conv(c1, c_, 1, 1)
self.cv2 = Conv(c_ * (len(k) + 1), c2, 1, 1)
self.m = nn.ModuleList([nn.MaxPool2d(kernel_size=x, stride=1, padding=x // 2) for x in k])
def forward(self, x):
x = self.cv1(x)
return self.cv2(torch.cat([x] + [m(x) for m in self.m], 1))
SPPF
V5 のバージョン 6.0 以降、ネック部分は SPP から SPPF (Glenn Jocher 自身が設計) に置き換えられました. 2 つの機能は同じですが、後者の方が効率的です. SPP の構造を下図に示します.入力は、サイズの異なる複数の MaxPools を並列に通過し、さらに融合され、ターゲットのマルチスケール問題をある程度解決できます。
SPPF構造
SPPとSPPFの比較実験
SPP と SPPF の計算結果と速度を比較するために、簡単な小さな実験を行います. コードは次のとおりです (ここでは、SPPF の最初と最後の 1x1 畳み込み層は削除され、MaxPool を含む部分のみが比較されます)。 .
メンターコードから
import time
import torch
import torch.nn as nn
class SPP(nn.Module):
def __init__(self):
super().__init__()
self.maxpool1 = nn.MaxPool2d(5, 1, padding=2)
self.maxpool2 = nn.MaxPool2d(9, 1, padding=4)
self.maxpool3 = nn.MaxPool2d(13, 1, padding=6)
def forward(self, x):
o1 = self.maxpool1(x)
o2 = self.maxpool2(x)
o3 = self.maxpool3(x)
return torch.cat([x, o1, o2, o3], dim=1)
class SPPF(nn.Module):
def __init__(self):
super().__init__()
self.maxpool = nn.MaxPool2d(5, 1, padding=2)
def forward(self, x):
o1 = self.maxpool(x)
o2 = self.maxpool(o1)
o3 = self.maxpool(o2)
return torch.cat([x, o1, o2, o3], dim=1)
def main():
input_tensor = torch.rand(8, 32, 16, 16)
spp = SPP()
sppf = SPPF()
output1 = spp(input_tensor)
output2 = sppf(input_tensor)
print(torch.equal(output1, output2))
t_start = time.time()
for _ in range(100):
spp(input_tensor)
print(f"spp time: {
time.time() - t_start}")
t_start = time.time()
for _ in range(100):
sppf(input_tensor)
print(f"sppf time: {
time.time() - t_start}")
if __name__ == '__main__':
main()
出力
True
spp time: 0.5373051166534424
sppf time: 0.20780706405639648
比較すると、両者の計算結果はまったく同じであることがわかりますが、SPPF の計算速度は SPP の 2 倍以上速く、幸福度は 2 倍です。
Neck部分のもう1つの違いはNew CSP-PAN . YOLOv4ではNeckのPAN構造にCSP構造が導入されていなかったが、YOLOv5では作者がPAN構造にCSPを追加した。詳細については、上記のネットワーク構造図を参照してください。各 C3 モジュールには CSP 構造が含まれています。Head セクションでは、YOLOv3、v4、および v5 はすべて同じです。
引用
yolov5モデル構成 yamlファイル詳細
yolov5のFocusモジュールの理解
[YOLOV5] YOLOv5モジュール解析
YOLOv5ネットワーク詳細説明