私はディープ ラーニングを始めたばかりの新人です。学んだことを記録するメモのようなものになれば幸いです。また、同じく始めようとしている人たちを助けることができればと思っています。また、大手の方々にもできることを願っています。間違いの修正にご協力ください~ 侵害は削除されます。
1. SPP適用の背景
畳み込みニューラル ネットワークでは固定入力設計がよく見られますが、入力を固定サイズにできない場合はどうなるのでしょうか?
一般的には次のような方法があります。
(1) 入力に対してリサイズ操作を実行し、すべてが設計したレイヤーの入力仕様になるようにします。しかし、これはあまりにも暴力的かつ直接的であるため、多くの情報が失われたり、存在すべきではない多くの情報 (画像の変形など) が追加されたりして、最終結果に影響を与える可能性があります。
(2) ネットワーク内の全結合層を置き換え、最後の畳み込み層にグローバル平均プーリングを使用します。グローバル平均プーリングはチャネル数にのみ関係し、特徴マップのサイズとは関係ありません。
(3) 最後はもちろん、これから説明する SPP 構造です~
ノート:
しかし、yolov5では、SPP/SPPFの役割は、ローカル特徴量とグローバル特徴量のfeatherMapレベルの融合を実現することです。(リマインドしてくれた @qq_44622851 ブロガーに感謝します)
2. SPPの構造解析
SPP 構造は空間ピラミッド プーリングとも呼ばれ、任意のサイズの特徴マップを固定サイズの特徴ベクトルに変換できます。
次に、SPP がそれをどのように処理するかを詳しく説明します~
入力レイヤー: まず、任意のサイズの画像ができました。そのサイズは w * h です。
出力層: 21 ニューロン -- つまり、後で 21 個の特徴を抽出する必要があります。
分析は以下の図に示されています。それぞれ、1 * 1 ブロック、2 * 2 ブロック、および 4 * 4 サブピクチャーの各ボックスの最大値を取得します (つまり、青いフレーム内の最大値を取得します)。ステップは、最後に抽出された固有値 (つまり、取り出された最大値) の合計が 1 * 1 + 2 * 2 + 4 * 4 = 21 になるように、最大プーリングを実行することです。次に、取得された特徴が結合されます。
YOLOv5 の SPP の構造図を次の図に示します。
このうち、前後に CBL が 1 つ追加されており、中央のカーネル サイズはそれぞれ 1 * 1、5 * 5、9 * 9、13 * 13 です。
3. SPPF構造の解析
(x、y1 については、以下のコードを参照してください)
CBL(conv+BN+Leaky relu) が CBS(conv+BN+SiLU) に変更されましたが、今まで名前が変わっていることに気づきませんでした。
4. YOLOv5におけるSPP/SPPF構造ソースコード解析(コメント解析含む)
コードのコメントは、上図の SPP 構造に対応します。
class SPP(nn.Module):
def __init__(self, c1, c2, k=(5, 9, 13)):#这里5,9,13,就是初始化的kernel size
super().__init__()
c_ = c1 // 2 # hidden channels
self.cv1 = Conv(c1, c_, 1, 1)#这里对应第一个CBL
self.cv2 = Conv(c_ * (len(k) + 1), c2, 1, 1)#这里对应SPP操作里的最后一个CBL
self.m = nn.ModuleList([nn.MaxPool2d(kernel_size=x, stride=1, padding=x // 2) for x in k])
#这里对应SPP核心操作,对5 * 5分块,9 * 9分块和13 * 13子图分别取最大池化
def forward(self, x):
x = self.cv1(x)
with warnings.catch_warnings():
warnings.simplefilter('ignore') # suppress torch 1.9.0 max_pool2d() warning忽略警告
return self.cv2(torch.cat([x] + [m(x) for m in self.m], 1))
#torch.cat对应concat
SPPF構造
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)#先通过CBL进行通道数的减半
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))
#将原来的x,一次池化后的y1,两次池化后的y2,3次池化的self.m(y2)先进行拼接,然后再CBL
皆さんの批判と修正を歓迎します、ありがとう~