2
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

RetinaNetを詳しく紹介(四)訓練 Focal Loss,Regression 損失

Last updated at Posted at 2020-06-14

背景###

今までの紹介は、Retinaモデルから信頼度情報とRegression情報をもらえます。また画像のサイズに基づいて、サイズと座標が固定されたAnchorBoxを計算してもらいました。
物体検出の目的は、与えられた画像にある目標物体を枠付けとラベル付けることをします。

計算されたAnchorBoxにRegression情報をかけて、目標物体の枠に変換します。
信頼度情報は各AnchorBoxがどの種類を指しているかを示しています。

AnchorBoxを洗い出す###

前回の初回では、サイズが(640,832)の入力画像から99765個のanchorBoxを生成しています
詳細

でも、例えば、実際の目標物体は2個しかない場合は、99765は明らかに多過ぎです。99765個のAnchorBoxの中、目標物体と全然重ねていない、もしくは少しだけ重なってあるものは大半数です。
すべてのAnchorBoxを目標物体に変換する必要がなく、目標物体とある程度重なってあるAnchor Boxを洗い出して、目標物体に変換させます。

各AnchorBoxに対して、各Target Boxに対するIOUを計算します。####

IOU = Inner / Union
つまり、重ねってある共通エリアの面積が両方の総面積を示す割合を表しています。

ケース1#####

RetinaNet物体検出3.001.jpeg

図のように、空白の枠をAnchor Boxとして、オレンジ色な枠を目標物体(Target Box)として、目標物体はAnchor Boxの右上にある場合、
IOU = ((cx2 - cx3) * (cy2 - cy3)) / ((cx2 - cx1) * (cy2 - cy1) + (cx4 - cx3) * (cy4 - cy3))

ケース2#####

RetinaNet物体検出3.002.jpeg
図のように、空白の枠をAnchor Boxとして、オレンジ色な枠を目標物体(Target Box)として、目標物体はAnchor Boxの左上にある場合、
IOU = ((cx2 - cx3) * (cy2 - cy3)) / ((cx2 - cx1) * (cy2 - cy1) + (cx4 - cx3) * (cy4 - cy3))

ケース3#####

RetinaNet物体検出3.003.jpeg
図のように、空白の枠をAnchor Boxとして、オレンジ色な枠を目標物体(Target Box)として、目標物体とAnchor Boxと重ねることがない場合は、innerは0です。IOUも0になります

つまり、以下のルールにたいして、
(cx1,cy1) Anchor Box左下の座標
(cx2,cy2) Anchor Box右上の座標
(cx3,cy3) Target Box左下の座標
(cx4,cy4) Target Box右上の座標

IOU = [min((min(cx2,cx4) - max(cx1,cx3)),0) * min((min(cy2,cy4) - max(cy1,cy3)),0)] / (cx2 - cx1) * (cy2 - cy1) + (cx4 - cx3) * (cy4 - cy3)

各AnchorBoxに対して、一番IOUが高いTarget Boxを洗い出します####

ここからは4個Anchor Boxと2個Target Boxを例として、説明します

RetinaNet物体検出3.004.jpeg
図のの中、一番上の表は4個のAnchor Boxの座標を表しています。
真ん中の表は目標物体(Target Box)の座標を表しています。
一番したの表は各Anchor Boxから各Target Boxに対して、計算されたIOUの値を表しています。

Anchor Box1とTarget Box1と重ねるところがないため、IOUは0になります
Anchor Box1とTarget Box2と結構重ねってあるため、IOUは0.5542になります

RetinaNet物体検出3.005.jpeg
図のように、各AnchorBoxからTarget Box1とのIOUとTarget Box2とのIOUの中から、IOUが高いTarget Boxを選びます。
選ばれたTarget BoxはそれぞれのAnchor BoxのAssigned Boxになります

そして、
各Anchor BoxのAssigned BoxとのIOUがある値より高いなら、それらのAnchor BoxをPositive Anchor Boxesをします
各Anchor BoxのAssigned BoxとのIOUがある値より低い、それらのAnchor BoxをNegative Anchor Boxesをします

ここである値を0.4にします

図のようにAnchor Box1とTarget Box2とのIOUは0.5542 > 0.4のため、Anchor Box1をPositve Boxにします
Anchor Box12Target Box1とのIOUは0.5542 > 0.7222のため、Anchor Box2をPositve Boxにします
Anchor Box3とAnchor Box4はTarget Boxとの最大のIOUも0.4より小さいため、Negative Boxにします。

RetinaNet物体検出はPositve AnchorBoxを使って、目標物体に変形します
Negative AnchorBoxは目標物体との離れが大きいため、変形処理をしません

各AnchorBoxに対して、目標物体を種類を特定します####

RetinaNet物体検出3.006.jpeg
目標物体は予め用意したデータなので、座標データだけではなく、目標の種類を表しているIndexもあります。
構造はこのようになります
x1,y1,x2,y2,classIndex

ここまでは、IOUの計算とAssigned target Boxの計算によって、各Anchor BoxのTarget Boxを特定いてます。Target Boxの際ののコラムは種類のindexなので、各Anchor Boxの種類のIndexも分かります。
図のように、Anchor Box1のTarget BoxはTarget Box2になります。Anchor Box1の種類のIndexは3になります。

信頼度訓練(Focal Loss)###

RetinaNet物体検出3.007.jpeg

モデルから出力されたの信頼度情報####

RetinaNetを詳しく紹介(二)モデルの流れでは、モデルから各Anchor Boxにたいして、信頼度を生成しています。
詳細
図のように各Batchで、99765このAnchor Boxに対して、80種類の分類信頼度を表しています。信頼度はsigmod活性化関数によって、0-1までの値を生成しています。

ここではAnchor Boxを4個、10種類の分類を例としてあげます。
RetinaNet物体検出3.008.jpeg
図のように、Retinaモデルから各Anchor Boxにたいして十種類の分類に対して信頼度を生成しています。
これは例えばの話です。

計算された信頼度の教師データ####

RetinaNet物体検出3.009.jpeg
図のように、Assigned Target Boxから計算されたただしい教師データは以上になります。
Negative Anchor Boxは全てな分類に対して、属していませんですので、十種類の分類に対して信頼度を0にします
Anchor Box1の正しい分類は3ですので、class_3の信頼度を1にします、その他の分類の信頼度を0にします
Anchor Box2の正しい分類は5ですので、class_5の信頼度を1にします、その他の分類の信頼度を0にします

損失関数Focal Loss####

RetinaNet物体検出3.010.jpeg

図のように、Focal Lossは一般のCross_Entropy_Lossと似ていますけど、少し改造しました。

詳細
一般の2分類問題は、モデルからの信頼度出力をsigmoidにかけてます。
そして、cross_entroy_error = −(𝑦log(𝑝)+(1−𝑦)log(1−𝑝))

一般の他分類分類問題は、モデルからの信頼度出力をsoftmaxにかけてます。
そして、cross_entroy_error =  −∑𝑐=1𝑀𝑦𝑜,𝑐log(𝑝𝑜,𝑐)

RetinaNetは他分類ですが、信頼度生成する際に、sigmoidをかけてます。そして、損失関数のベースは2分類問題のCross Entropy Errorを採用しています。なぜかというと、weightをかけます。

RetinaNet物体検出3.011.jpeg
ここのαはPositive Sampleのバランスを調整するためなパラメータです。
ここのγは簡易なSampleと難しいsampleのバランスを調整するためなパラメータです。

Focal Loss α####

一般的には、t=1のsampleはt=0のsampleより、明らかに少ないです。
α=0.75の場合は
t=1の場合は0.75をかけます
t=0の場合は0.25をかけます
そうしたら、t=0のsampleのweightを小さくすることになります

Focal Loss γ####

γは一般的に2にします
例えばt=1の時に、予測値は0.55の時に
予測値は正解値と結構離れてあるため、計算された損失を重視したいです。そして、pow((1-0.55),2)をかけます、結局0.2025を掛けます

それにたいして、予測値は0.95の時に
予測値は正解値と近いため、計算された損失に力をたくさん入れたくないです。そして、pow((1-0.95),2)をかけます、結局0.0025を掛けます

例1####

RetinaNet物体検出3.012.jpeg
図のように、t=1(計算された信頼度の教師データ=1)の時のfocal Loss

例2####

RetinaNet物体検出3.013.jpeg
図のように、t=0(計算された信頼度の教師データ=0)の時のfocal Loss

例3####

RetinaNet物体検出3.014.jpeg
図のように、すべてのAnchor Boxのすべてな分類に対するfocal loss。
最後にすべてなfocal lossを足し算して、Positive Anchor Boxの数に割り算します。

これで信頼度情報の損失計算が終わります。次はAnchor Boxを変形するためなRegressionの損失情報を計算します。

Regression Loss###

予め用意されたAnchor Boxのサイズと座標が同じです。ただ、目標物体GTにより、変形できそうなAnchor Boxを洗い出す。そうして、RetinaNetモデルからの出力には、こちらのAnchor Boxを変形するためのRegression情報があります。そちらのRegression情報を正確にAnchor Boxを変形するために、RetinaNetを訓練させます。

計算されたRegressionの教師データ####

RetinaNet物体検出3.015.jpeg
図のように、水色な枠はAnchor Box、オレンジ色な枠は目標物体。もちろんこの目標物体はAnchor BoxのIOU最大な目標物体。IOUも0.5を超えています。
水色な枠をオレンジ色な枠に変形させるには、以下の方程式が行われます。
Anchor Boxの中心座標(cx,cy)
Anchor Boxの横幅 width
Anchor Boxの縦幅 height

Target Boxの中心座標(tx,ty)
Target Boxの横幅 dWidth
Target Boxの縦幅 dHeight

tx = cx + width * △x
ty = cy + height * △y
dWidth = (e△width) * width
dHeight = (e
△height) * height

ここにある(△x,△y,△width,△height)はAnchro Boxを変形するためなRegression情報です
cx,cy,width,height,tx,ty,dWight,dHeightのすでに分かってあります。
だから、このAnchor BoxのRegression情報の教師データは
△x = (tx - cx) / width
△y = (ty - cy) / height
△width = ln(dWidth / width)
△height = ln(dHeight/height)
になります

RetinaNet物体検出3.016.jpeg
図のように、計算されたRegression情報は以上になります
二重線されたAnchor Box3とAnchor Box4はTarget BoxとのIOUは小さいため、Regression損失計算に参加しません。
これで、nchor BoxのRegression情報の教師データを計算できました。
つぎはモデルから出力されたRegression情報とこちらで計算された教師Regression情報と比較して、損失値を計算します。

モデルから出力されたのRegression情報####

RetinaNet物体検出3.017.jpeg
「RetinaNetを詳しく紹介(二)モデルの流れ」からモデルは各Anchor Boxに対して、4個のRegression情報を生成したことを紹介しました。
図のように、最後のレイヤ(-1,4)の処理によって、各Anchor Boxに対して、4個のRegression情報を生成しています。

RetinaNet物体検出3.018.jpeg
例えば、モデルからAnchor Box1からAnchor Box4まで生成されたregression情報は以上になります。
ここの△x,△y,△width,△heightはモデルの出力です
では、次は、計算された教師データ△x,△y,△width,△heightと比較して、損失値を計算します

Regression Loss####

流れ#####

RetinaNet物体検出3.019.jpeg
図のように、流れは以下になります。
1.計算された教師データをまず(0.1,0.1,0.2,0.2)に割り算します。
2.そして、モデルの出力と引き算します。
3.そして、計算された値が0.5/9.0の上下によって、さらにわけて、計算します。
最後に(Positive Anchor Box Num,4)のRegression Lossの平均値を計算して(1,1)の結果を求めます。

Regression Diff#####

RetinaNet物体検出3.020.jpeg
図のように流れ1,2が行って、Regression Diffは以上になります

RetinaNet物体検出3.021.jpeg
図のように、例としてあげたRegression情報を計算したら、Regression Diff以上になります

Regression Loss#####

RetinaNet物体検出3.022.jpeg
図のように、例としてあげたRegression情報を計算したら、最後のRegression Lossは以上になります。
最後にmean関数をかけて平均値を求めます。

Final Loss###

RetinaNet物体検出3.023.jpeg
図のように、信頼度情報の損失値とRegression情報の損失値を足し算します。

ソースコード###

IOUの計算####

def zhengze(annotations):
    minX = torch.min(annotations[:, 0], annotations[:, 2])
    maxX = torch.max(annotations[:, 0], annotations[:, 2])

    minY = torch.min(annotations[:, 1], annotations[:, 3])
    maxY = torch.max(annotations[:, 1], annotations[:, 3])

    result = torch.zeros(annotations.shape)

    result[:, 0] = minX
    result[:, 1] = minY
    result[:, 2] = maxX
    result[:, 3] = maxY

    size = annotations.shape
    if size[1] > 4:
        result[:, 4:] = annotations[:, 4:]

    return result


def run_calculate_iou(anchorBox, targets):
    fig = plt.figure()
    ax = fig.add_subplot(111)

    for box in anchorBox.numpy():
        x1 = box[0]
        y1 = box[1]
        x2 = box[2]
        y2 = box[3]
        cx = 0
        cy = 0
        width = x2 - x1
        height = y2 - y1
        plt.plot(cx, cy, "o")
        rect = plt.Rectangle([x1, y1], width, height, fill=None)
        ax.add_patch(rect)

    for box in targets.numpy():
        x1 = box[0]
        y1 = box[1]
        x2 = box[2]
        y2 = box[3]
        cx = 0
        cy = 0
        width = x2 - x1
        height = y2 - y1
        plt.plot(cx, cy, "o", "red")
        rect = plt.Rectangle([x1, y1], width, height, fill=True)
        ax.add_patch(rect)
    #
    plt.show()

    return calculateIOU(anchorBox, targets)


def calculateIOU(anchorBox, targets):
    cx1 = anchorBox[:, 0]
    cy1 = anchorBox[:, 1]
    cx2 = anchorBox[:, 2]
    cy2 = anchorBox[:, 3]

    cx3 = targets[:, 0]
    cy3 = targets[:, 1]
    cx4 = targets[:, 2]
    cy4 = targets[:, 3]

    cx1 = torch.unsqueeze(cx1, dim=1)
    cy1 = torch.unsqueeze(cy1, dim=1)
    cx2 = torch.unsqueeze(cx2, dim=1)
    cy2 = torch.unsqueeze(cy2, dim=1)

    iouW = torch.clamp(torch.min(cx2, cx4) - torch.max(cx1, cx3), min=0)
    iouH = torch.clamp(torch.min(cy2, cy4) - torch.max(cy1, cy3), min=0)

    innerArea = iouW * iouH

    anchorBoxesArea = (anchorBox[:, 0] - anchorBox[:, 2]) * (anchorBox[:, 1] - anchorBox[:, 3])

    targetArea = (cx3 - cx4) * (cy3 - cy4)

    anchorBoxesArea = torch.unsqueeze(anchorBoxesArea, dim=1)

    unionArea = anchorBoxesArea + targetArea - innerArea

    unionArea = torch.clamp(unionArea, min=1e-8)

    iou = innerArea / unionArea

    print(iou)

    iouMaxValue, iouMaxIndexes = torch.max(iou, dim=1)

    negativeIndexes = torch.le(iouMaxValue, 0.4)

    classificationNum = 10
    anchorBoxsNum = anchorBox.shape[0]

    result = torch.ones((anchorBoxsNum, classificationNum))

    result[negativeIndexes] = 0

    positiveIndexes = torch.gt(iouMaxValue, 0.5)

    result[positiveIndexes] = 0

    anchorBoxTargets = targets[:][iouMaxIndexes]
    # anchorBoxTargets = targets[iouMaxIndexes, :]
    anchorBoxTargetsClassIndexes = anchorBoxTargets[positiveIndexes, 4].long() - 1
    result[positiveIndexes, anchorBoxTargetsClassIndexes] = 1
    return positiveIndexes, anchorBoxTargets, result


# cx1,cy1,cx2,cy2
anchorBoxes = torch.Tensor([
    [1, 5, 17, 25],
    [22, 3, 38, 15],
    [4, 16, 22, 37],
    [23, 23, 38, 35],
])

# cx1,cy1,cx2,cy2
targetBoxes = torch.Tensor([
    [20, 15, 35, 3, 5],
    [5, 3, 16, 30, 3],
])

classification = torch.Tensor([
    [0.053, 0.2463, 0.753, 0.2352, 0.3413, 0.1234, 0.3253, 0.235, 0.235, 0.0023],
    [0.235, 0.3435, 0.023, 0.0023, 0.7897, 0.23453, 0.5235, 0.5623, 0.3423, 0.3462],
    [0.0023, 0.3452, 0.3423, 0.0023, 0.9235, 0.234, 0.0023, 0.1242, 0.0023, 0.1235],
    [0.0023, 0.124, 0.0235, 0.1252, 0.03252, 0.0023, 0.2353, 0.2352, 0.135, 0.2352]
])

regression = torch.Tensor([
    [-0.0064, -0.0055, 0.095, 0.095],
    [0.0030, -0.0043, -0.0095, 0.0018],
    [0.0009, -0.0083, -0.0095, -0.0095],
    [-0.0036, 0.0023, -0.0056, -0.0068],
])


def calculateLoss(anchorBoxes, targetBoxes, classification, regression):
    positiveIndexes, anchorBoxsTargets, teacher = run_calculate_iou(zhengze(anchorBoxes), zhengze(targetBoxes))
    focalLoss = fun_focal_loss(classification, teacher)

    regressionLoss = calculateRegressionLoss(anchorBoxes, positiveIndexes.bool(), anchorBoxsTargets, regression)
    print(focalLoss)
    print(regressionLoss)

    loss = focalLoss + regressionLoss

    print(loss)


calculateLoss(anchorBoxes, targetBoxes, classification, regression)
スクリーンショット 2020-06-15 7.28.32.png 図のように、例のとしてあげたAnchor BoxとTarget Boxを表しています。

IOUの出力

tensor([[0.0000, 0.5542],
        [0.7222, 0.0000],
        [0.0000, 0.2956],
        [0.0000, 0.0000]])

信頼度損失Focal Loss計算####

import torch
def fun_focal_loss(classificition, teacher):
    alpha = 0.25
    gama = 2.00

    alphaValue = torch.ones(classificition.shape) * alpha

    alphaWeight = torch.where(torch.eq(teacher, 1), alphaValue, 1 - alphaValue)

    gamaWeight = torch.where(torch.eq(teacher, 1), 1 - classificition, classificition)
    gamaWeight = torch.pow(gamaWeight, gama)

    loss = -1 * teacher * torch.log(classificition) + (-1 * (1 - teacher) * torch.log(1 - classificition))

    loss = loss * alphaWeight * gamaWeight

    loss = loss.sum() / classificition.shape[0]
    return loss

regression Loss計算####

import torch

def calculateRegressionLoss(anchorBoxes, positiveIndexes, targetBoxes, regression):
    print(anchorBoxes)

    anchorBoxes = anchorBoxes[positiveIndexes, :]
    targetBoxes = targetBoxes[positiveIndexes, :]
    regression = regression[positiveIndexes, :]

    anchorBoxWidth = anchorBoxes[:, 2] - anchorBoxes[:, 0]
    anchorBoxHeight = anchorBoxes[:, 3] - anchorBoxes[:, 1]

    anchorBoxCenterX = anchorBoxes[:, 0] + 0.5 * anchorBoxWidth
    anchorBoxCenterY = anchorBoxes[:, 1] + 0.5 * anchorBoxHeight

    gtWidth = targetBoxes[:, 2] - targetBoxes[:, 0]
    gtHeight = targetBoxes[:, 3] - targetBoxes[:, 1]

    gtCenterX = targetBoxes[:, 0] + 0.5 * gtWidth
    gtCenterY = targetBoxes[:, 1] + 0.5 * gtHeight

    detaX = (gtCenterX - anchorBoxCenterX) / anchorBoxWidth
    detaY = (gtCenterY - anchorBoxCenterY) / anchorBoxHeight

    detaWidth = torch.log(gtWidth / anchorBoxWidth)
    detaHeight = torch.log(gtHeight / anchorBoxHeight)

    stacks = torch.stack((detaX, detaY, detaWidth, detaHeight))
    stacks = stacks.t()

    stacks = stacks / torch.Tensor([0.1, 0.1, 0.2, 0.2])
    print(stacks)

    diff = abs(regression - stacks)

    loss = torch.where(torch.le(diff, 1.0 / 9.0), 0.5 * 9.0 * torch.pow(diff, 2), diff - 0.5 / 9.0)
    loss = loss.mean()
    return loss

ここにソースコードを載せてあります。
ここ

まとめ###

RetinaNetの訓練は以下に分けてます
1.信頼度情報訓練
1-1. IOU計算から、各Anchor Boxの最大TargetBoxを探し出します
1-2. 各Anchor Boxの最大TargetBoxとのIOUを使って、Positive BoxとNegative Boxを洗い出します
1-3. 各Anchor Boxが属する分類教師データを洗い出します
1-4. モデルからの出力と1-3の結果とfocal lossして、結果を信頼度情報の損失値にします

2.Regression情報訓練
2-1. 1-2から洗い出したPositive BoxとそれらのTarget BoxとのRegression情報を計算します
2-2. 1-2から洗い出したPositive Boxに対する、モデルからの出力を特定します
2-3. 2-1と2-2を比較して、Regression Lossを計算します。結果をRegression情報の損失値にします。

次回は推論について説明します。

間違ってあるところがあれば、教えていただきたいともいます。

2
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?