背景###
今までの紹介は、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#####
図のように、空白の枠をAnchor Boxとして、オレンジ色な枠を目標物体(Target Box)として、目標物体はAnchor Boxの右上にある場合、
IOU = ((cx2 - cx3) * (cy2 - cy3)) / ((cx2 - cx1) * (cy2 - cy1) + (cx4 - cx3) * (cy4 - cy3))
ケース2#####
図のように、空白の枠をAnchor Boxとして、オレンジ色な枠を目標物体(Target Box)として、目標物体はAnchor Boxの左上にある場合、
IOU = ((cx2 - cx3) * (cy2 - cy3)) / ((cx2 - cx1) * (cy2 - cy1) + (cx4 - cx3) * (cy4 - cy3))
ケース3#####
図のように、空白の枠を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を例として、説明します
図のの中、一番上の表は4個のAnchor Boxの座標を表しています。
真ん中の表は目標物体(Target Box)の座標を表しています。
一番したの表は各Anchor Boxから各Target Boxに対して、計算されたIOUの値を表しています。
Anchor Box1とTarget Box1と重ねるところがないため、IOUは0になります
Anchor Box1とTarget Box2と結構重ねってあるため、IOUは0.5542になります
図のように、各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に対して、目標物体を種類を特定します####
目標物体は予め用意したデータなので、座標データだけではなく、目標の種類を表している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を詳しく紹介(二)モデルの流れでは、モデルから各Anchor Boxにたいして、信頼度を生成しています。
詳細
図のように各Batchで、99765このAnchor Boxに対して、80種類の分類信頼度を表しています。信頼度はsigmod活性化関数によって、0-1までの値を生成しています。
ここではAnchor Boxを4個、10種類の分類を例としてあげます。
図のように、Retinaモデルから各Anchor Boxにたいして十種類の分類に対して信頼度を生成しています。
これは例えばの話です。
計算された信頼度の教師データ####
図のように、Assigned Target Boxから計算されたただしい教師データは以上になります。
Negative Anchor Boxは全てな分類に対して、属していませんですので、十種類の分類に対して信頼度を0にします
Anchor Box1の正しい分類は3ですので、class_3の信頼度を1にします、その他の分類の信頼度を0にします
Anchor Box2の正しい分類は5ですので、class_5の信頼度を1にします、その他の分類の信頼度を0にします
損失関数Focal Loss####
図のように、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をかけます。
ここのαは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####
図のように、t=1(計算された信頼度の教師データ=1)の時のfocal Loss
例2####
図のように、t=0(計算された信頼度の教師データ=0)の時のfocal Loss
例3####
図のように、すべての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の教師データ####
図のように、水色な枠は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)
になります
図のように、計算されたRegression情報は以上になります
二重線されたAnchor Box3とAnchor Box4はTarget BoxとのIOUは小さいため、Regression損失計算に参加しません。
これで、nchor BoxのRegression情報の教師データを計算できました。
つぎはモデルから出力されたRegression情報とこちらで計算された教師Regression情報と比較して、損失値を計算します。
モデルから出力されたのRegression情報####
「RetinaNetを詳しく紹介(二)モデルの流れ」からモデルは各Anchor Boxに対して、4個のRegression情報を生成したことを紹介しました。
図のように、最後のレイヤ(-1,4)の処理によって、各Anchor Boxに対して、4個のRegression情報を生成しています。
例えば、モデルからAnchor Box1からAnchor Box4まで生成されたregression情報は以上になります。
ここの△x,△y,△width,△heightはモデルの出力です
では、次は、計算された教師データ△x,△y,△width,△heightと比較して、損失値を計算します
Regression Loss####
流れ#####
図のように、流れは以下になります。
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#####
図のように流れ1,2が行って、Regression Diffは以上になります
図のように、例としてあげたRegression情報を計算したら、Regression Diff以上になります
Regression Loss#####
図のように、例としてあげたRegression情報を計算したら、最後のRegression Lossは以上になります。
最後にmean関数をかけて平均値を求めます。
Final Loss###
図のように、信頼度情報の損失値と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)

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情報の損失値にします。
次回は推論について説明します。
間違ってあるところがあれば、教えていただきたいともいます。