物体検出SSD詳しく紹介(四)信頼度訓練

###背景###
前章はSSDモデルから出力するOffset情報の訓練を紹介しました。IOUという計算式をつかって、各段階デフォルトボックスの教師データを特定し、そして、損失関数を計算する流れです。
SSDモデルは各デフォルトボックスに対して、Offset情報と信頼度情報を提供していいます。Offset情報はデフォルトボックスを教師データと同じようなバインディングボックスに変形するための材料です。
信頼度情報はこのデフォルトボックスはすべての21種類のカテゴリーに属する可能性を表しています。
###目的###
1. 各デフォルトボックスの教師信頼度データを特定する
つまり、デフォルトボックスはどのカテゴリーに属しています
2. SSDモデルから出力する信頼度情報と教師信頼度情報の損失を計算する

###訓練###
####デフォルトボックスの教師データを探し出す####
まず、IOUの計算に戻ります
各デフォルトボックスからすべての教師データGround TruthsとIOU計算して、自分と一番IOUが高い教師データGround Truthを教師データとして取り扱います。
#####教師データが一つの場合#####
![SSD物体検出2.026.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/191439/7f872361-6bc4-d7fa-74ee-6ef4e7d91163.jpeg)
図のように各デフォルトボックスは唯一なGround TruthとIOUけいさんして、IOU>αのデフォルトボックスを残らせます。ポジティブボックスとして扱います。
***残らせられたポジティブボックスにっとては、一番IOUが高い教師データGround Truthは唯一な馬、他にはないから、教師データの信頼度を馬にします***

![SSD物体検出2.039.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/191439/a0e85e7a-f0d9-3ee7-43d4-5def9a544269.jpeg)
図のように、他のデフォルトボックスにとっては、一番IOUが高い教師データGround Truthは唯一な馬かもしれませんが、IOU<αのため、教師データの信頼度を背景(background)にします***

#####教師データが複数ある場合#####
######ケース1######
![SSD物体検出2.027.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/191439/dd45f8de-7518-93c9-d601-ad6cf49526eb.jpeg)
ずのように、教師データは馬だけではなく、猫、犬もあります
![SSD物体検出2.028.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/191439/474ab54d-b015-81db-36bb-05605bc2fc03.jpeg)
ずのように、一つのデフォルトボックスを例にします
このデフォルトボックスからすべて教師データ(馬、猫、犬)とのIOUを計算します
それぞれは
IOU馬->0.14
IOU猫->0.39
IOU犬->0.52
![SSD物体検出2.029.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/191439/6b8df6b4-d225-3153-6073-ab5e347d62f0.jpeg)
ずのように、一番IOUが高いのは犬ですので、このデフォルトボックスの教師データを犬にします。

######ケース2######
![SSD物体検出2.030.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/191439/b5faa704-56f3-377d-de97-12e16d118b2c.jpeg)
図のように、もう一つのデフォルトボックスを例にします。今回のデフォルトボックスはすべての教師データGround Truthどっちでも重ねることがないです。
![SSD物体検出2.031.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/191439/1effdc66-b0c0-067c-3755-b26737d4ee6f.jpeg)
図のように、このデフォルトボックスにとっては、教師データを犬、猫、馬どっちにされても、可能性があります。
![SSD物体検出2.032.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/191439/9960854a-e524-c186-d336-16df9b33d31b.jpeg)
図のように、
オレンジのデフォルトボックスは教師データが猫に属するデフォルトボックス
緑のデフォルトボックスは教師データが犬に属するデフォルトボックス
水色のデフォルトボックスは教師データが馬に属するデフォルトボックス
どちでも重ねがないデフォルトボックスはランダムから馬、猫、犬から教師データを選びます
![SSD物体検出2.033.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/191439/a97e11ea-9e01-99ce-2bb0-281e49244f04.jpeg)

ずのように、各デフォルトボックスはIOUと属する教師データを特定したあと、IOU<αのデフォルトボックスの教師データを背景(background)にします

####信頼度の損失計算####
#####損失関数#####
![SSD物体検出2.037.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/191439/c03b099e-f8c9-02fb-47a5-3d05e189b9ed.jpeg)
ずのように、損失計算の方法は、softmax+cross_entropy_error

#####ポジティブボックスの損失#####
![SSD物体検出2.034.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/191439/3a18b5f1-cd5d-dd0b-ed58-f3653ddf5d23.jpeg)
図のように、デフォルトボックスは教師データ馬を特定して、信頼度の教師データは図のようになります

![SSD物体検出2.035.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/191439/260c2561-8b55-3510-6f03-a1a9bf02f486.jpeg)
図のように、SSDモデルはこのデフォルトボックスにたいして、提供する信頼度のデータは図のようになります

![SSD物体検出2.036.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/191439/383ad10d-1966-f247-ac96-386c3d5846b6.jpeg)
そして、SSDモデル提供する信頼度データと信頼度の教師データを比較して、損失計算を準備します

![SSD物体検出2.038.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/191439/bf4101ec-6fb5-924f-8543-50dc9c4bf38e.jpeg)
図のように、実際に値を代入した結果loss=2.7707

#####ネガテイブボックスの損失#####
![SSD物体検出2.040.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/191439/30131975-d42d-59f3-6631-0fa51f2f371e.jpeg)

![SSD物体検出2.041.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/191439/164ad11e-78a7-a25a-be34-c007a6f33d57.jpeg)
図のように、教師データには背景(background)は1.0になります

![SSD物体検出2.042.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/191439/eac03322-c0d2-665d-741d-fc240b5269be.jpeg)
図のように、実際に値を代入するイメージです

####Hard Negative Mining####
SSDはHard Negative Miningが行います。
ようするには、信頼度損失を計算する際に、各デフォルトボックスの信頼度の損失値を計算して、合計します。
でも、ポジティブボックスは少なく、ネガテイブボックスは圧倒的に多い現状です。
そのバランスを維持するためには、以下の流れをします
1.一回すべてのデフォルトボックスの信頼度損失を計算します
2.ポジティブデフォルトボックスの信頼度損失を0にします
3.すべてのデフォルトボックスの信頼度を降順にします
4.ポジティブデフォルトボックスの数をpnにします、indexをIndexPに保存します
5.降順になったデフォルトボックスの信頼度を上からpn*3をとって、indexをIndexNに保存します
6.すべてのデフォルトボックスに、indexはindexPとIndexNのデフォルトボックスだけとって、***もう一度***信頼度損失計算します.
7.そうしたら、ポジティブボックスとネガティブボックスの比例は1:3になります
8.信頼度損失を２回計算したわけです。１回目はすべてのデフォルトボックスに対して、２回目は特定されたデフォルトボックスに対します。
![SSD物体検出2.043.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/191439/c5cf8db8-a25f-85f8-ff22-8b9d2963a474.jpeg)
図のように、まずはポジティブボックスとネガティブボックスにわけます。
![SSD物体検出2.044.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/191439/98fd5cef-417e-2db0-366e-001f370ee194.jpeg)
ずのように、実際にネガティブボックスはポジティブボックスより、圧倒的に多い

![SSD物体検出2.045.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/191439/7cb846fd-4969-071b-e57a-5b6770072451.jpeg)
ずのように、１回目の損失計算:すべてのデフォルトボックスの信頼度損失を計算します

![SSD物体検出2.046.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/191439/afd5e032-3763-a555-120e-9eae82b4a0fb.jpeg)
図のように、ポジティブボックスの損失を全部0にします

![SSD物体検出2.047.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/191439/922cafdb-91af-7c23-d839-5e88f654e382.jpeg)
ずのように、ポジティブボックスの数は6個です。そして、ネガティブボックスの中から損失の降順で上から18このネガティブボックスをとります

![SSD物体検出2.049.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/191439/bb63e20b-9fe0-9cd2-9479-7f9704f8d397.jpeg)
ずのように、前のステップ残ったのデフォルトボックスに対して、もう一度損失を計算します(ここの必要があるかと疑っています、１回目計算できた損失値をそのまま使えると思いますけど)

###ソースコード###
各デフォルトボックスの信頼度教師データを特定します

```
conf = labels[best_truth_idx] + 1
conf[best_truth_overlap < threshold] = 0 -->ポジティブボックスの信頼度を0にする
```

```
best_truth_idx = tensor([0, 0, 0,  ..., 2, 2, 2], device='cuda:0')->各デフォルトボックスの信頼度教師データの番号
labels = tensor([14., 14., 17.], device='cuda:0')->教師データのカテゴリー番号(教師データGroundTruthは3個)
conf = tensor([15., 15., 15.,  ..., 18., 18., 18.], device='cuda:0')->各デフォルトボックスの信頼度教師データのカテゴリー番号
```

１回目の信頼度計算

```
loss_c = F.cross_entropy(batch_conf, conf_t_label.view(-1), reduction='none')
```

```
各デフォルトボックスのIOU
batch_conf = tensor([[ 1.2041, -0.0737,  1.2133,  ..., -1.1512,  0.5440,  1.4540],
        [ 0.3814, -0.8049,  0.7727,  ..., -0.5996, -0.5597, -0.3455],
        [-0.4916,  0.5868, -0.1321,  ...,  0.0529,  0.7314,  1.1589],
        ...,
        [-0.0170,  0.0476,  0.3374,  ..., -0.2304, -0.1694,  0.0337],
        [ 0.3133,  0.1897, -0.1472,  ...,  0.1327,  0.2246, -0.0078],
        [ 0.0589,  0.2930,  0.2262,  ...,  0.2082, -0.0045, -0.2921]],
       device='cuda:0', grad_fn=<ViewBackward>)

各デフォルトボックスの教師データカテゴリー番号
conf_t_label = tensor([[0, 0, 0,  ..., 8, 0, 8],
        [0, 0, 0,  ..., 0, 0, 0],
        [0, 0, 0,  ..., 0, 0, 0],
        ...,
        [0, 0, 0,  ..., 3, 0, 3],
        [0, 0, 0,  ..., 0, 8, 8],
        [0, 0, 0,  ..., 0, 0, 0]], device='cuda:0')
```

ポジティブボックスの信頼度を0にします

```
num_pos = pos_mask.long().sum(1, keepdim=True)
loss_c = loss_c.view(num_batch, -1)  # torch.Size([num_batch, 8732])
loss_c[pos_mask] = 0
```

ここは、デフォルトボックスの信頼度はすべての信頼度の中に何番名なのかをidx_rankに納めました

```
_, loss_idx = loss_c.sort(1, descending=True)
_, idx_rank = loss_idx.sort(1)
```

デフォルトボックスの順番を変わらず、中身の内容が自分の信頼度のランキングです

```
idx_rank = tensor([[7832, 6827, 3786,  ..., 8730, 7021, 8731],
        [1026, 2675, 7253,  ..., 5144, 6300, 5666],
        [1565, 3243, 7233,  ..., 5716, 6670, 5968],
        ...,
        [5329, 3652, 1536,  ..., 8730, 6505, 8731],
        [1120, 2669, 7308,  ..., 5538, 8730, 8731],
        [ 938, 2729, 7092,  ..., 5411, 6054, 5508]], device='cuda:0')
```


上のことを理解するために、簡易な例文を作りました。

```
a = torch.tensor(
    [
        [2.4522, 2.8841, 3.6130, 0.0000, 2.8224, 0.0000],
        [4.5089, 3.8272, 2.5402, 3.1420, 2.7718, 3.0715],
        [4.5089, 3.8272, 2.5402, 3.1506, 2.8058, 3.0757],
        [3.2006, 3.5532, 4.1683, 0.0000, 2.8597, 0.0000],
        [4.5089, 3.8272, 2.5402, 3.1491, 0.0000, 0.0000],
    ],
)

x, y = a.sort(1, descending=True)
print(x)
print(y)
m,n = y.sort(1)
print(n)
```

```
tensor([[3.6130, 2.8841, 2.8224, 2.4522, 0.0000, 0.0000],
        [4.5089, 3.8272, 3.1420, 3.0715, 2.7718, 2.5402],
        [4.5089, 3.8272, 3.1506, 3.0757, 2.8058, 2.5402],
        [4.1683, 3.5532, 3.2006, 2.8597, 0.0000, 0.0000],
        [4.5089, 3.8272, 3.1491, 2.5402, 0.0000, 0.0000]])
tensor([[2, 1, 4, 0, 3, 5],
        [0, 1, 3, 5, 4, 2],
        [0, 1, 3, 5, 4, 2],
        [2, 1, 0, 4, 3, 5],
        [0, 1, 3, 2, 4, 5]])
tensor([[3, 1, 0, 4, 2, 5],
        [0, 1, 5, 2, 4, 3],
        [0, 1, 5, 2, 4, 3],
        [2, 1, 0, 4, 3, 5],
        [0, 1, 3, 2, 4, 5]])
```

したのことはHard Negative Miningの最後に、ポジティブボックスと洗い出したネガティブボックスのIndexを取得して、もう一度信頼度を計算します。

```
num_neg = torch.clamp(num_pos*self.negpos_ratio, max=num_dbox)
neg_mask = idx_rank < (num_neg).expand_as(idx_rank)
pos_idx_mask = pos_mask.unsqueeze(2).expand_as(conf_data)
neg_idx_mask = neg_mask.unsqueeze(2).expand_as(conf_data)
conf_hnm = conf_data[(pos_idx_mask+neg_idx_mask).gt(0)].view(-1, num_classes)
conf_t_label_hnm = conf_t_label[(pos_mask+neg_mask).gt(0)]
loss_c = F.cross_entropy(conf_hnm, conf_t_label_hnm, reduction='sum')
```
###まとめ###
信頼度の訓練方法を紹介しました
1.各デフォルトボックスが信頼度教師データを特定します
2.各デフォルトボックスごとに、SSDモデルの信頼度出力と教師データと損失を計算します
3.ポジティブボックスとネガティブボックスのバランスをとるために、Hard Negative Miningが行います

次の章は推論を紹介したいと思います。