yolov5 モデルとコードの解釈

バックボーン: 新しい CSPDarknet-53
ネック: SPPF、新しい CSP-PAN
ヘッド: yolov3 ヘッド
下の図は v5l です。
ここに画像の説明を挿入します

改善点:

1.フォーカスモジュール

Focus モジュールは、2x2 の隣接ピクセルを 1 つのパッチに分割し、各パッチ内の同じ位置 (同じ色) のピクセルをまとめて 4 つの特徴マップを取得し、3x3 サイズの畳み込み層を接続します。これは、6x6 畳み込み層を直接使用するのと同じです。

ここに画像の説明を挿入します

コーディング

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().__init__()
        self.conv = Conv(c1 * 4, c2, k, s, p, g, act)
        # self.contract = Contract(gain=2)

    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))
        # return self.conv(self.contract(x))

::2 : 1行目の先頭から最終行の末尾までのすべての値をステップサイズ2で示します(図の1行目と3行目のすべての値) ::2 :
最初の列の先頭から最後の列の末尾までを示し、ステップサイズはすべて 2 の値です (図の 1 列目と 3 列目の値はすべて)

ここに画像の説明を挿入します

2.SPPF

SPP を SPPF に変更します。k
=5 の 2 つの Maxpool は、k=9 の 1 つの Maxpool に相当します。k=
5 の 3 つの Maxpool は、k=13 の 1 つの Maxpool に相当します。
作成者は、k=5 の複数の Maxpool をシリアル化し、次の実行を実行します。さらなる融合により、ターゲットのマルチスケール問題をある程度解決できます。
ここに画像の説明を挿入します

ここに画像の説明を挿入します
コーディング:

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)   #这里对应第一个CBL
        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))
        # 将原来的x,一次池化后的y1,两次池化后的y2,3次池化的self.m(y2)先进行拼接,然后再CBL

3. データの強化

3.1 モザイク

4 つの画像を 1 つの画像に結合します (変更なし)
ここに画像の説明を挿入します
コーディング:

def load_mosaic(self, index):
    # YOLOv5 4-mosaic loader. Loads 1 image + 3 random images into a 4-image mosaic
    labels4, segments4 = [], []
    s = self.img_size       #图片大小



    '''
Mosaic流程:
1.初始化背景图,大小为(img*2,img*22.随机选取一个中心点,范围为(-x,2*s+x)
3.随机选取三张图,基于中心点分别将四张图放在左上,右上,左下,右下,部分会由于小于4张图片的宽高因此会进行裁剪
4.重新计算打标边框的偏移量计算上

    '''



    # 随机选取拼接四张图的中心点[-320,960]
    yc, xc = (int(random.uniform(-x, 2 * s + x)) for x in self.mosaic_border)  # mosaic center x, y  self.mosaic_border=[-320,320]
    indices = [index] + random.choices(self.indices, k=3)  # 3 additional image indices  随机选三张图
    random.shuffle(indices)
    for i, index in enumerate(indices):
        # Load image
        img, _, (h, w), img_label = load_image_label(self, index)

        # place img in img4
        if i == 0:  # top left
            img4 = np.full((s * 2, s * 2, img.shape[2]), 114, dtype=np.uint8)  # base image with 4 tiles   初始化背景图------填充一个背景图s*2
            # 先计算出第一张图贴到左上角的起点xy,终点就是xc,yc
            x1a, y1a, x2a, y2a = max(xc - w, 0), max(yc - h, 0), xc, yc  # xmin, ymin, xmax, ymax (large image)
            # 计算裁剪的要贴的图,避免越界,这里就会裁掉多余的一部分
            x1b, y1b, x2b, y2b = w - (x2a - x1a), h - (y2a - y1a), w, h  # xmin, ymin, xmax, ymax (small image)
        elif i == 1:  # top right
            x1a, y1a, x2a, y2a = xc, max(yc - h, 0), min(xc + w, s * 2), yc
            x1b, y1b, x2b, y2b = 0, h - (y2a - y1a), min(w, x2a - x1a), h
        elif i == 2:  # bottom left
            x1a, y1a, x2a, y2a = max(xc - w, 0), yc, xc, min(s * 2, yc + h)
            x1b, y1b, x2b, y2b = w - (x2a - x1a), 0, w, min(y2a - y1a, h)
        elif i == 3:  # bottom right
            x1a, y1a, x2a, y2a = xc, yc, min(xc + w, s * 2), min(s * 2, yc + h)
            x1b, y1b, x2b, y2b = 0, 0, min(w, x2a - x1a), min(y2a - y1a, h)

        img4[y1a:y2a, x1a:x2a] = img[y1b:y2b, x1b:x2b]  # img4[ymin:ymax, xmin:xmax]
        padw = x1a - x1b
        padh = y1a - y1b

        # Labels  裁剪过后,对应标签也要往下移动
        labels, segments = img_label.copy(), self.segments[index].copy() # labels (array): (num_gt_perimg, [cls_id, poly])
        if labels.size:
            # labels[:, 1:] = xywhn2xyxy(labels[:, 1:], w, h, padw, padh)  # normalized xywh to pixel xyxy format
            labels[:, [1, 3, 5, 7]] = img_label[:, [1, 3, 5, 7]] + padw
            labels[:, [2, 4, 6, 8]] = img_label[:, [2, 4, 6, 8]] + padh
            segments = [xyn2xy(x, w, h, padw, padh) for x in segments]
        labels4.append(labels)
        segments4.extend(segments)
3.2 コピーペースト

いくつかのターゲットを画像にランダムに貼り付けます。前提として、データにはセグメント データ、つまり各ターゲットのインスタンス セグメンテーション情報が必要です。
ここに画像の説明を挿入します

3.3 ランダムアフィン(回転、スケール、平行移動、剪断)

アフィン変換をランダムに実行しますが、設定ファイルのハイパーパラメータによると、Scale and Translation、つまりスケーリングと変換のみが使用されていることがわかります。

3.4 ミックスアップ

2枚の写真を一定の透明度で融合させるものですが、役に立つかどうかは不明です、結局のところ、紙もアブレーション実験もありません。コード内のより大きなモデルのみが MixUp を使用し、毎回使用される確率は 10% のみです。
ここに画像の説明を挿入します

3.5 アルバムメンテーション

主に、フィルタリング、ヒストグラムのイコライゼーション、画質の変更などを行うためです。 コードでは、albumenments パッケージがインストールされている場合にのみ有効になることがわかりますが、プロジェクトのrequirements.txt ファイルでは、albumenments パッケージがコメントアウトされています。 . したがって、デフォルトでは有効になっていません。

3.6 HSV(色相、彩度、明度)を拡張する

色相、彩度、明度をランダムに調整します

3.7 ランダム左右反転、ランダム左右反転

4. トレーニング戦略

マルチスケール トレーニング (0.5 ~ 1.5x)、マルチスケール トレーニング、入力画像のサイズが 640 × 6400 に設定されていると仮定すると、トレーニング中に使用されるサイズは 0.5 × 640 〜 1.5 × 640 の間でランダムに選択されます、注意してくださいこれらはすべて 32 の整数倍です (ネットワークは最大 32 回ダウンサンプリングするため)。
AutoAnchor (カスタム データのトレーニング用) では、独自のデータ セットをトレーニングするときに、独自のデータ セット内のターゲットに基づいて再クラスター化し、アンカー テンプレートを生成できます。
ウォームアップおよびコサイン LR スケジューラ。トレーニング前にウォームアップ ウォームアップを実行し、コサイン学習率削減戦略を使用します。
EMA (指数移動平均) は、更新プロセスをよりスムーズにするためにトレーニング パラメーターに勢いを加えるものとして理解できます。
混合精度、混合精度トレーニングでは、GPU ハードウェアがサポートしている場合、メモリ使用量を削減し、トレーニングを高速化できます。
ハイパーパラメータの進化、ハイパーパラメータの最適化、錬金術の経験のない人は触れないでください。デフォルトのままにしてください。

5.ボーダー予測

ここに画像の説明を挿入します
コーディング

y = x[i].sigmoid()
y[..., 0:2] = (y[..., 0:2] * 2. - 0.5 + self.grid[i]) * self.stride[i]  # xy
y[..., 2:4] = (y[..., 2:4] * 2) ** 2 * self.anchor_grid[i]  # wh

ここに画像の説明を挿入します

IOUコーディング

# 计算两个框的iou(DIOU,GIOU,CIOU)
def bbox_iou(box1, box2, x1y1x2y2=True, GIoU=False, DIoU=False, CIoU=False, eps=1e-7):
    # Returns the IoU of box1 to box2. box1 is 4, box2 is nx4
    box2 = box2.T

    # Get the coordinates of bounding boxes
    if x1y1x2y2:  # x1, y1, x2, y2 = box1
        b1_x1, b1_y1, b1_x2, b1_y2 = box1[0], box1[1], box1[2], box1[3]
        b2_x1, b2_y1, b2_x2, b2_y2 = box2[0], box2[1], box2[2], box2[3]
    else:  # transform from xywh to xyxy
        b1_x1, b1_x2 = box1[0] - box1[2] / 2, box1[0] + box1[2] / 2
        b1_y1, b1_y2 = box1[1] - box1[3] / 2, box1[1] + box1[3] / 2
        b2_x1, b2_x2 = box2[0] - box2[2] / 2, box2[0] + box2[2] / 2
        b2_y1, b2_y2 = box2[1] - box2[3] / 2, box2[1] + box2[3] / 2

    # Intersection area  计算交集
    inter = (torch.min(b1_x2, b2_x2) - torch.max(b1_x1, b2_x1)).clamp(0) * \
            (torch.min(b1_y2, b2_y2) - torch.max(b1_y1, b2_y1)).clamp(0)

    # Union Area
    w1, h1 = b1_x2 - b1_x1, b1_y2 - b1_y1 + eps
    w2, h2 = b2_x2 - b2_x1, b2_y2 - b2_y1 + eps
    union = w1 * h1 + w2 * h2 - inter + eps

    iou = inter / union
    if CIoU or DIoU or GIoU:
        # c是包含两个包围框的最小外接矩形
        cw = torch.max(b1_x2, b2_x2) - torch.min(b1_x1, b2_x1)  # convex (smallest enclosing box) width
        ch = torch.max(b1_y2, b2_y2) - torch.min(b1_y1, b2_y1)  # convex height
        if CIoU or DIoU:  # Distance or Complete IoU https://arxiv.org/abs/1911.08287v1
            # c2是包围框的对角线的平方
            c2 = cw ** 2 + ch ** 2 + eps  # convex diagonal squared
            # 中心点的距离
            rho2 = ((b2_x1 + b2_x2 - b1_x1 - b1_x2) ** 2 +
                    (b2_y1 + b2_y2 - b1_y1 - b1_y2) ** 2) / 4  # center distance squared
            if CIoU:  # https://github.com/Zzh-tju/DIoU-SSD-pytorch/blob/master/utils/box/box_utils.py#L47
                v = (4 / math.pi ** 2) * torch.pow(torch.atan(w2 / h2) - torch.atan(w1 / h1), 2)
                with torch.no_grad():
                    alpha = v / (v - iou + (1 + eps))
                return iou - (rho2 / c2 + v * alpha)  # CIoU
            else:
                return iou - rho2 / c2  # DIoU
        else:  # GIoU https://arxiv.org/pdf/1902.09630.pdf
            c_area = cw * ch + eps  # convex area
            return iou - (c_area - union) / c_area  # GIoU
    else:
        return iou  # IoU

5.損失計算

主に次の 3 つの部分に分かれています。

  • クラス損失、分類損失、BCE 損失を使用します。陽性サンプルの分類損失のみが計算されることに注意してください。
  • オブジェクトネス損失、obj 損失、依然として BCE 損失を使用します。ここでの obj は、ネットワークによって予測されたターゲット バウンディング ボックスと GT ボックスの CIoU を指すことに注意してください。ここで計算されるのは、すべてのサンプルのオブジェクト損失です。(これは v3v4 とは異なります。v3v4 はターゲットが存在するかどうかを判断するだけですが、v5 はバウンディング ボックスと GT を計算する必要があります)
  • 位置損失、位置損失、CIoU 損失を使用します。陽性サンプルの位置損失のみが計算されることに注意してください。
    ここに画像の説明を挿入します

予測フィーチャ レイヤーは異なる重みを採用し、小さなターゲットの重みは相対的に大きくなります。
ここに画像の説明を挿入します

簡単に言うと、境界回帰とは、
ターゲット値が真の値に限りなく近づくように、変換係数とスケーリング係数を見つけることです。この無限近接条件を満たす係数が回帰係数です。
無限に近いとは、2 つが可能な限り似ていることを意味します。定量化するには、損失関数を構築し、この関数で 2 つの間の類似性を表します。類似するほど、2 つの間の差は小さくなります。継続的に減少させることで、損失関数の値を使用すると、適切な移動倍率とズーム倍率を取得できます。損失関数の値を減らすプロセスが最適化です。ニューラルネットワークの従来のルーチン。Compute_loss は、損失関数を構築するプロセスであり、フレーム回帰損失関数は、損失関数 = フレーム回帰係数 * アンカー - 正のサンプルの真の値
で構成されます。フレーム回帰ニューラル ネットワーク トレーニングの目的は、このセットを見つけることです。正のサンプルに対応するアンカーが無限に近づくような回帰係数。正のサンプルの真の値。プログラムによって出力される最終的な信頼レベルは、存在信頼レベル * 分類信頼レベルです。

コーディング:

過剰適合を防ぐために、 smooth_BCE は
ここに画像の説明を挿入します
ラベル [1,0]=>[0.95,0.05] を平滑化します。

def smooth_BCE(eps=0.1):  # https://github.com/ultralytics/yolov3/issues/238#issuecomment-598028441
    # return positive, negative label smoothing BCE targets
    return 1.0 - 0.5 * eps, 0.5 * eps
self.cp, self.cn = smooth_BCE(eps=h.get('label_smoothing', 0.0))

言葉の喪失

デフォルトではオンになっていないため、正と負の両方のサンプルの最終結果が 1 に近くなるようにするため、正しく予測されたサンプルの重みは非常に小さくなり、誤って予測されたサンプルの重みは非常に大きくなり、これにより問題が解決されます。カテゴリの不均衡の問題 1. 1 段階
問題の解決 物体検出における正のサンプルと負のサンプル間の不均衡の問題
2. 損失関数が難しいサンプルにより多くの注意を払うように、単純なサンプルの重みを減らす

class FocalLoss(nn.Module):
    # Wraps focal loss around existing loss_fcn(), i.e. criteria = FocalLoss(nn.BCEWithLogitsLoss(), gamma=1.5)
    def __init__(self, loss_fcn, gamma=1.5, alpha=0.25):
        super().__init__()
        self.loss_fcn = loss_fcn  # must be nn.BCEWithLogitsLoss()
        self.gamma = gamma
        self.alpha = alpha
        self.reduction = loss_fcn.reduction
        self.loss_fcn.reduction = 'none'  # required to apply FL to each element

    def forward(self, pred, true):
        loss = self.loss_fcn(pred, true)
        # p_t = torch.exp(-loss)
        # loss *= self.alpha * (1.000001 - p_t) ** self.gamma  # non-zero power for gradient stability

        # TF implementation https://github.com/tensorflow/addons/blob/v0.7.1/tensorflow_addons/losses/focal_loss.py
        pred_prob = torch.sigmoid(pred)  # prob from logits
        # 只留下正样本概率true * pred_prob,让负样本也接近1
        p_t = true * pred_prob + (1 - true) * (1 - pred_prob)
        alpha_factor = true * self.alpha + (1 - true) * (1 - self.alpha)
        # 调整正负样本权重
        modulating_factor = (1.0 - p_t) ** self.gamma
        loss *= alpha_factor * modulating_factor

        if self.reduction == 'mean':
            return loss.mean()
        elif self.reduction == 'sum':
            return loss.sum()
        else:  # 'none'
            return loss

bulid_targets

def build_targets(self, p, targets):


p が各予測ヘッドによって出力された結果 p[0].shape であると仮定します
。 torch.Size([16, 3, 80, 80, 85]) # 16 は btachsize、3 はアンカーの数、80/40/20 : 3 つの検出ヘッドの特徴マップ サイズ、85: ココ データ セットの 80 カテゴリ + 4 (x, y, w, h) + 1 (前景かどうか) p[1].shape: torch.Size([16
, 3, 40, 40, 85])
p[2].shape: torch.Size([16, 3, 20, 20, 85])


対象: GT ボックス情報、次元は (n, 6)、n はバッチ イメージ全体の GT ボックスの数、以下では例として GT ボックスの数を 190 とします。
6 の各次元は (バッチ内の画像のインデックス、ターゲット カテゴリ、x、y、w、h) です。


build_targets の主な作業:主に gt ボックス 1 を処理します。長さと幅の 3 種類のアンカーがあり、各アンカーにはそれに対応する gt ボックスがあるため、gt ボックス 3 つのコピーをコピーします。つまり、スクリーニングの前に

gt box には 3 種類のアンカーがあり、それぞれに対応しています。
2. アンカーの w および h に対する gt ボックスの w および h の比率が、設定されたハイパーパラメータアンカー_t より大きい gt ボックスをフィルターで除外します。
3. 残りの GT ボックスについては、各 GT ボックスは予測に少なくとも 3 つの正方形を使用します。1 つは GT ボックスの中心点が位置する正方形で、他の 2 つは中心点に最も近い 2 つの正方形です。以下の図で:
ここに画像の説明を挿入します
gt の場合、ボックスの中心座標は (51.7, 44.8)、次に gt ボックスの中心点が位置する正方形 (51, 44)、および最も近い 2 つの正方形 (51, 45) と(52, 44) を中心点に移動して、この GT ボックスを予測します

#compute_loss() のターゲットをビルド、入力ターゲット(image,class,x,y,w,h)

na, nt = self.na, targets.shape[0]     # na:3, nt:190  

#対応するカテゴリー、ボーダー、対応するアンカーインデックス、対応するアンカー

tcls, tbox, indices, anch = [], [], [], []  
ai = torch.arange(na, device=targets.device).float().view(na, 1).repeat(1, nt)          #anchor的索引,shape为(3, 190), 3个anchor因此要复制三份
targets = torch.cat((targets.repeat(na, 1, 1), ai[:, :, None]), 2)

#元のターゲットにアンカー インデックスを追加して、どのアンカーに対応するかを示します。これを 3 回繰り返し、ターゲットは 3 つのアンカーに対応します。次の図は、処理されたターゲットの内容を示しています。その形状は (3, 190, 7) で、赤色のボックスは元のターゲットです。青いボックスは各 GT ボックスにインデックスを追加します。
ここに画像の説明を挿入します

g = 0.5**  # bias
off = torch.tensor([[0, 0],
                [1, 0], [0, 1], [-1, 0], [0, -1],**  # j,k,l,m
                # [1, 1], [1, -1], [-1, 1], [-1, -1],  # jk,jm,lk,lm
                **], device=targets.device).float() * g**  # offsets

#traverse feature map self.nl はアンカー層です

for i in range(self.nl): // 针对每一个检测头
anchors = self.anchors[i]

i=0 の場合、アンカーの値は次のようになります。
アンカー
tensor([[1.25000, 1.62500],
[2.00000, 3.75000],
[4.12500, 2.87500]], device='cuda:0')

gain[2:6] = torch.tensor(p[i].shape)[[3, 2, 3, 2]]

#gain の値は [ 1., 1., 80., 80., 80., 80., 1.] で、80 の位置が gt box 情報の xywh に対応します。

t = targets * gain

#targets の xywh は 0 ~ 1 の間に正規化されます。ゲインを乗算した後、ターゲットの xywh は検出ヘッドの特徴マップのサイズにマッピングされます。

if nt:
	 r = t[:, :, 4:6] / anchors[:, None]

#ここでのrの形状は[3, 190, 2]で、2はそれぞれgtボックスのwとhとアンカーのwとhの比を表します。

    j = torch.max(r, 1. / r).max(2)[0] < self.hyp['anchor_t']

#比率は0.25~4のままにしておきます

gxy = t[:, 2:4]  #取出过滤后的gt box的中心点浮点型的坐标
gxi = gain[[2, 3]] - gxy   #将以图像左上角为原点的坐标变换为以图像右下角为原点的坐标

#3 つのアンカーの合計 GT ボックスは 3 * 190 = 570 で、フィルタリング後には 271 が残ると仮定します。

j, k = ((gxy % 1. < g) & (gxy > 1.)).T

# 画像の左上を原点座標として、中心点の小数部をとり、小数部が0.5未満であればtrue、0.5以上であればfalseとします。真の位置はそれぞれ、正方形の左側近くの gt ボックスと正方形の上部近くの gt ボックスを表し、j と k の形状は (271) になります。

l, m = ((gxi % 1. < g) & (gxi > 1.)).T

# 画像の右下を原点座標として中心点の小数部を取り、小数部が0.5未満であればtrue、0.5より大きい場合はfalseとなります。l と m の形状は両方とも (271) です。真の位置は、正方形の右側近くの gt ボックスと正方形の底部近くの gt ボックスを表します。すると、l と m の形状は (271) になります。 j と l の値は正反対であり
、k と m の値も正反対です。

j = torch.stack((torch.ones_like(j), j, k, l, m))

# j、k、l、m をテンソルに結合し、すべて true である次元も追加します。結合後の j の形状は (5, 271) になります。

t = t.repeat((5, 1, 1))[j]

t の前の形状は (271, 7) です。ここでは、t を 5 回コピーし、j を使用してフィルターします。最初の t は、
すべての true 値を持つ次元が前のステップで追加されたため、すべての gt ボックスを保持します。 .
2 番目の t は、最初の t は正方形の左側近くの gt ボックスを保持し、
3 番目の t は正方形の上部近くの gt ボックスを保持し、
4 番目の t は正方形の右側近くの gt ボックスを保持します。
5 番目の t は正方形の底部近くの gt ボックスを保持します。gt ボックス、
フィルタリング後の t の形状は (808, 7) であり、これはすべての gt ボックスが保持されることを意味します。

 offsets = (torch.zeros_like(gxy)[None] + off[:, None])[j]

オフセットの形状は (808, 2) で、808 個の保持された gt ボックスの x と y に対応するオフセットを表します。最初の t はすべての gt ボックス
オフセットを [0, 0] として保持します。つまり、オフセットは作成されません。
2 番目の t で予約された gt ボックスを、オフセット [0.5, 0] で正方形の左側に近づけます。つまり、左に 0.5 オフセットします (次のコードは gxy-offsets を使用しているため、正の 0.5 です) 左へのオフセットを意味します)、左の正方形へのオフセットは、左の正方形を使用して、
3 番目の t によって予約された正方形の上部付近の gt ボックスを予測することを意味します。オフセットは [0, 0.5] です。上向きのオフセットが 0.5 の場合、オフセットは上側になります。グリッド、つまり、上の正方形を使用して、正方形の
右側に近い 4 番目の t 予約済み GT ボックスを予測することを意味し、オフセットは [-0.5, 0] です。つまり、右に 0.5 オフセットし、次に右の正方形にオフセットします。これは、右側の正方形を使用して、
5 番目の t によって予約された正方形の底部近くの GT ボックスを予測することを示します。オフセットは [0, 0.5]、つまり、下向きのオフセットが 0.5 の場合、オフセットは下の正方形になります。つまり、下の正方形を使用します。GT
ボックスの中心点の x 座標を予測するためのグリッドは、正方形の左側に近いか、または正方形の右側であり、y 座標は正方形の上部に近いか、正方形の底部に近いため、gt ボックスは上記の 5 つの t の範囲内にあります。内部には、true の t が 3 つあります。
つまり、GT ボックスには予測する 3 つの正方形があり、1 つは中心点が位置する正方形で、他の 2 つは最も近い 2 つの正方形です。Yolov3 は、中心点が位置する正方形のみを予測に使用します。これが yolov3 との違いです。

else:
t = targets[0]
offsets = 0
b, c = t[:, :2].long().T  # image, class
gxy = t[:, 2:4]  # grid xy
gwh = t[:, 4:6]  # grid wh
gij = (gxy - offsets).long()** 

中心点を最も近い隣接する正方形にオフセットし、切り捨てます。gij の形状は (808, 2) です。

gi, gj = gij.T  # grid xy indices

#追加

a = t[:, 6].long()  # anchor indices
indices.append((b, a, gj.clamp_(0, gain[3] - 1), gi.clamp_(0, gain[2] - 1)))  # image, anchor, grid indices
tbox.append(torch.cat((gxy - gij, gwh), 1)) # box
anch.append(anchors[a]) # anchors
tcls.append(c)  # class
return tcls, tbox, indices, anch

6. グリッド感度

v3では、予測座標が最終的にセル内に収まるようにシグモイド関数を導入していますが、境界や右下隅に落ちた場合、ネットワークの予測値が負の無限大か正の無限大になる必要があります。これは非常に困難であり、極端な価値を持つネットワークは一般に手の届かないところにあります。
ここに画像の説明を挿入します

この問題を解決するために、作者はオフセットを元の (0, 1) から (− 0.5, 1.5) にスケーリングして、ネットワークによって予測されるオフセットが簡単に 0 または 1 に到達できるようにしました。 YOLOv5 では、
ここに画像の説明を挿入します
予測グリッドの左上隅に対するアンカーのオフセットに加え、予測対象のwとhの計算式も調整されており、独自の計算式では幅と高さを制限していないと著者は説明しました予測ターゲットにより、勾配爆発やトレーニングが不安定になる可能性があり、その他の問題が発生する前: 次のよう
ここに画像の説明を挿入します
に調整されました。
ここに画像の説明を挿入します

下図は y=e^x の修正前と修正後の変化曲線を示しています. 調整倍率は (0, 4) に限定されています. ここでの 4 は以下のポジティブサンプルマッチングに使用されます.
ここに画像の説明を挿入します

7. 陽性サンプルの一致方法

v4 と v5 の GT およびアンカー マッチング方法は異なります。YOLOv4
では、各 GT ボックスは対応するアンカー テンプレート テンプレートを使用して直接計算されます。IoU が設定されたしきい値より大きい限り、マッチングは成功します。
しかし、YOLOv5 では、作成者は最初に各 GT ボックスと対応するアンカー テンプレート テンプレートの高さと幅の比率を計算し、
ここに画像の説明を挿入します
次にこれらの比率とその逆数の間の最大値をカウントします。これは、GT の幅と幅を計算するものとして理解できます。それぞれボックス テンプレートとアンカー テンプレート 高さ方向の最大の差 (等しい場合、比率は 1 で差が最小になります)
ここに画像の説明を挿入します
r_max < 4 (4 は調整された倍率) の場合、一致は成功します。x1
はAnchor の元のサイズ
。x4 は、Anchor サイズに 4 を乗算したサイズです。
最初のフレーム画像は、Anchor x 4 のサイズを超えています。
ここに画像の説明を挿入します
ポジティブ サンプル サンプリングの詳細:

残りのステップは、GT を対応する予測フィーチャ レイヤーに投影し、GT の中心点に従って対応するセルを配置することです。ピクチャ内に対応するセルが 3 つあることに注意してください。ネットワーク予測中心点のオフセット範囲は (− 0.5, 1.5) に調整されているため、グリッド セルの左上隅が (− 0.5, 1.5) の範囲内にある限り、当然のことながら、 GT の中心点、それに対応するアンカーは GT の位置に戻ることができます。これにより、陽性サンプルの数が大幅に増加します。
ここに画像の説明を挿入します
ここに画像の説明を挿入します

チップ:

1.EMA

問題の背景:
深層学習のトレーニングでは大域的な最適解を見つけることができないことが多く、ほとんどの場合、局所的な最適解の間を行ったり来たりするため、取得される重みは局所的な最適解の中で最悪となる可能性が高く、解決策は、これらの局所的な最適解を取得し、平均化操作を実行し、ネットワークに予測のためにこの重みをロードさせることです。この考えにより、次の重み平均化方法が導出されます。
定義: EMA は、およそ過去の1/(1-β)回の v 値の平均
ここに画像の説明を挿入します
として見ることができます。過去の n 回の通常の平均は次のようになります。 EMA から類推すると、 β=n-1/nの場合、2 つの式は形式的に等しいことがわかります。2 つの平均は厳密に等しいわけではないことに注意してください。これは理解を助けるためのものです。

ここに画像の説明を挿入します

実際、EMA を計算するとき、過去の **1/(1-β)** 瞬間前の値は、平均して1/eの加重比まで減少します。ここで v_t を展開すると、次のようになります
ここに画像の説明を挿入します
。深層学習では、θt は時間 t におけるモデルの重み重み、vt は時間 t におけるシャドウの重みです。勾配降下プロセス中、このシャドウ ウェイトは常に維持されますが、このシャドウ ウェイトはトレーニングには参加しません。基本的な仮定は、モデルの重みが最後の n ステップの実際の最適点でジッターするため、モデルをより堅牢にするために最後の n ステップの平均を取るということです。
深層学習の公式は次のとおりです。
ここに画像の説明を挿入します

手順:

  1. EMAの初期化
  2. EMA.register()
  3. トレーニング プロセス中に、パラメータ EMA.update() を更新し、シャドウ ウェイトの更新を同期します。
  4. EMA.apply_shadow()

平均する方法は?
ここでは、減衰 = 0.999 であると仮定します。より直観的に理解すると、最後の 1000 回のトレーニング中に、モデルはすでにトレーニングされており、ジッター段階にあります。スライド平均は、最後の 1000 回のジッターを平均することに相当します。これにより、重量がより堅牢になります。

利点:
1. 占有メモリが少なく、過去 10 または 100 個の履歴 v 値を保存せずに平均値を推定できます。(もちろん、スライド平均は、すべての履歴値を保存して平均を計算するほど正確ではありませんが、後者の方がより多くのメモリを使用し、計算コストが高くなります) 2. 追加のトレーニング時間は必要なく、手動でパラメータを調整する必要もありません
。テスト段階でのみテストする必要があるため、さらに数セットのテストを実行して最良の結果を選択するだけです。

下の図は、温度データ(青点)、フィッティング曲線(赤線)、EMA曲線(緑線)を示していますが、緑線は明らかなヒステリシスを持っており、影のようにトレンドをたどっていることがわかります。シャドウ変数。
ここに画像の説明を挿入します

2. パラメータ

pytorch のモデルに保存する必要があるパラメーターは次のとおりです。

パラメーター: バックプロパゲーションはオプティマイザーによって更新される必要があり、トレーニングできます。
バッファ: バックプロパゲーションはオプティマイザによって更新する必要がなく、トレーニングすることもできません。

これら 2 つのパラメーターはそれぞれ OrderDict 変数に保存され、最終的にはmodel.state_module() によって返されて保存されます。

例:
簡単な後処理に使用できる変数 (yolov5 のアンカーなど) を保存したい場合、その変数をネットワークに登録する必要があります。使用できる API は次のとおりです。

  • self.register_buffer() : トレーニングできません。
  • self.register_parameter()、nn.parameter.Parameter()、nn.Parameter(): トレーニングできます。
    ここに画像の説明を挿入します
    メンバー変数:
    _buffers: self.register_buffer() によって定義され、requires_grad のデフォルトは False であり、トレーニングできません。
    _parasmeter: self.register_parameter()、nn.parameter.Parameter()、および nn.Parameter() によって定義された変数はすべてこの属性の下に保存され、定義されたパラメーターの require_grad はデフォルトで True です。
    _module: nn.Sequential()、nn.conv() などで定義されたネットワーク構造内の構造体がこの属性に格納されます。

    メンバー関数:
    self.state_dict(): OrderedDict 型。パラメータとバッファ self.name_parameters() を含む、ニューラル ネットワークの推論パラメータを保存します
    。これはイテレータです。self._module および self._parameters + tensor 内のすべてのトレーニング可能なパラメーターの名前。BN の bn.weight と bn.bias を含みます。
    self.parameters(): self.name_parameters() と同じですが、名前が含まれません
    self.name_buffers(): イテレータです。ネットワーク内のすべてのトレーニング不可能なパラメーターの名前と、登録されたバッファー + テンソル内のパラメーター。BN の bn.running_mean、bn.running_var、bn.num_batches_tracked を含みます。
    self.buffers(): self.name_buffers() と同じですが、名前が含まれません。
    net.named_modules(): これはイテレータです。
    self._module + レイヤーnet.modules()で定義されたネットワーク構造の名前

3.借用書

近年のバウンディングボックス損失回帰の開発プロセスは、スムーズ L1 損失 -> IoU 損失 (2016 年) -> GIoU 損失 (2019 年) -> DIoU 損失 (2020 年) -> CIoU 損失 (2020 年) です。

a.IOU_Loss IOU: GT と予測IOU_Loss = 1 - IOU の
交差比ただし、次の2 つの問題があります: 1. IOU=0 の場合、2 つのボックス間の距離を反映できません。微分可能であり、IOU_Loss は 2 つのボックスが交差しない場合に最適化できません。2. 2 つの IOU が同じで、2 つの予測ボックスのサイズも同じ場合、IOU_Loss では 2 つの交差状況を区別できないため、改善のためにGIOU_Lossを導入します。

ここに画像の説明を挿入します


ここに画像の説明を挿入します

ここに画像の説明を挿入します

b. GIOU_Loss
GIOU_Loss = 1 - GIOU = 1- (IOU - 差集合/外接長方形)
ここで、A は予測ボックス、B は実際のボックス、C は A と B の最小境界ボックス、および A 間の関係、 B、C は具体的には以下のようになります。
ここに画像の説明を挿入します

ここに画像の説明を挿入します
IOU=0 の場合、GIOU は問題を解決しますが、IOU の同じ問題は解決されません 予測ボックスがターゲット ボックスの内側にあり、予測ボックスのサイズが同じ場合、予測ボックスとターゲット ボックスの差集合は同じなので、この 3 つの状態の GIOU 値も同じですが、このとき GIOU は IOU に縮退し、相対的な位置関係が区別できなくなります。
ここに画像の説明を挿入します
そこで、改善のためにDIOU_Lossが導入されました。

c.
DIOU_Loss の適切なターゲット フレーム回帰関数では、重なり合う領域、中心点の距離、アスペクト比という 3 つの重要な幾何学的要素を考慮する必要があります
GIOU_Loss = 1 - GIOU = 1- (IOU - 中心点の距離/対角距離)

ここに画像の説明を挿入します
DIOU_Loss は、重なり合う領域と中心を考慮しますポイント距離では、ターゲット フレームが予測フレームをラップしている場合、2 つのフレーム間の距離が直接測定されるため、DIOU_Loss はより速く収束しますが、
ここに画像の説明を挿入します
アスペクト比は考慮されていません。
ここに画像の説明を挿入します
たとえば、上記の 3 つのケースでは、ターゲット フレームが予測フレームをラップしており、DIOU_Loss が機能します。
ただし、予測フレームの中心点の位置は同じなので、DIOU_Lossの計算式によれば、3つの値は同じになります。
そこで、改善のためにCIOU_Lossが導入されました。

d. CIOU_Loss
CIOU_Loss と DIOU_Loss の式は同じですが、これに基づいて、予測ボックスとターゲット ボックスのアスペクト比を考慮したインパクト ファクターが追加されます。
ここに画像の説明を挿入します
このうち、v はアスペクト比の一貫性を測定するパラメータであり、
ここに画像の説明を挿入します
CIOU_Loss がターゲット フレームの回帰関数において重なり領域、中心点距離、アスペクト比の 3 つの重要な幾何学的要素を考慮するように定義することもできます。

概要
IOU_Loss:主に検出フレームとターゲットフレームの重複領域を考慮します。

GIOU_Loss: IOU に基づいて、境界ボックスが重ならない場合の問題を解決します。

DIOU_Loss: IOU と GIOU に基づいて、バウンディング ボックスの中心点の距離情報を考慮します。

CIOU_Loss: DIOU に基づいて、バウンディング ボックスのアスペクト比のスケール情報を考慮します。

Yolov4 は CIOU_Loss 回帰手法を採用しており、これにより予測ボックス回帰がより高速かつ正確になります。

関連している:

1. https://blog.csdn.net/qq_37541097/article/details/123594351
2.指数移動平均(EMA)の原理とPyTorchの実装
3. 【錬金術スキル】指数移動平均(EMA)【ある程度テストデータに対する最終モデルのパフォーマンスの改善(精度、FID、汎化能力など)]
4. [注意事項] 分類モデルACC、BatchSize&LARS、Bag of Tricksの改善:tirckは異なるデータには適さない可能性がありますシナリオ
5. YOLOV5 が使用するトリック (1)
6.未読:ディープ ニューラル ネットワーク モデル トレーニングのトリック (原理とコードの概要)
7. IOU、GIOU、DIOU、CIOU 損失関数の詳細な説明
8. コーディング分析は非常に詳細です。
2021SC@SDUSC 山東大学ソフトウェア学院ソフトウェア工学応用と実践 – YOLOV5 コード分析 (11) loss.py
YoloV5 コードの詳細な解釈

おすすめ

転載: blog.csdn.net/qq_42740834/article/details/125488211