YutaroOgawa/pytorch_advanced

【第2章】損失関数クラス MultiBoxLoss intersect関数でのエラー

bo0221 opened this issue · 23 comments

小川様へ

今,第2章で手持ちのデータで物体検出を試みておりまして,本書を見ながら流れに沿って進め,最後の訓練のところまで来ました.
なのですが,実行してみても以下のように損失関数のところでエラーが出てしまいます.損失関数のところについては,本書のコードから自分の手で変更はしておりません.また,私にとってこのあたりのコードが難解なため,どこが問題で,どこを直せばよいのかわからず,手が止まっています.
こういった相談をするのは初めてで,情報が不十分なところがあると思いますが,よろしくお願いいたします。


ValueError Traceback (most recent call last)
in
1 num_epochs = 50
----> 2 train_model(net, dataloaders_dict, criterion, optimizer, num_epochs=num_epochs)

in train_model(net, dataloaders_dict, criterion, optimizer, num_epochs)
62
63 # 損失の計算
---> 64 loss_l, loss_c = criterion(outputs, targets)
65 loss = loss_l + loss_c
66

~\anaconda3\lib\site-packages\torch\nn\modules\module.py in _call_impl(self, *input, **kwargs)
1049 if not (self._backward_hooks or self._forward_hooks or self._forward_pre_hooks or _global_backward_hooks
1050 or _global_forward_hooks or _global_forward_pre_hooks):
-> 1051 return forward_call(*input, **kwargs)
1052 # Do not call functions when jit is used
1053 full_backward_hooks, non_full_backward_hooks = [], []

in forward(self, predictions, targets)
878 variance = [0.1, 0.2]
879 # このvarianceはDBoxからBBoxに補正計算する際に使用する式の係数です
--> 880 match(self.jaccard_thresh, truths, dbox,
881 variance, labels, loc_t, conf_t_label, idx)
882

in match(threshold, truths, priors, variances, labels, loc_t, conf_t, idx)
83 """
84 # jaccard index
---> 85 overlaps = jaccard(
86 truths,
87 point_form(priors)

in jaccard(box_a, box_b)
56 jaccard overlap: (tensor) Shape: [box_a.size(0), box_b.size(0)]
57 """
---> 58 inter = intersect(box_a, box_b)
59 area_a = ((box_a[:, 2]-box_a[:, 0]) *
60 (box_a[:, 3]-box_a[:, 1])).unsqueeze(1).expand_as(inter) # [A,B]

in intersect(box_a, box_b)
15
16 def intersect(box_a, box_b):
---> 17 max_xy = np.minimum(box_a[:, 2:], box_b[2:])
18 min_xy = np.maximum(box_a[:, :2], box_b[:2])
19 inter = np.clip((max_xy - min_xy), a_min=0, a_max=np.inf)

ValueError: operands could not be broadcast together with shapes (25,2) (8730,4)

@bo0221 さま

本書をご活用いただき、ありがとうございます。
また、独自の取り組みにも挑戦いただき、とても嬉しいです。

今回手持ちのデータをご使用とのことですが、このあたりが気になります。
今回、最後のエラーメッセージがshapeに関するものですが、画像のサイズが違うのかなと?

デバッグ方法ですが、

[1] まず本書の通りに、本書で使用しているデータで動作するかを確認する
※これがダメだった場合は、PyTorchなどのバージョンの問題の可能性あり

[2] 本書で使用しているデータで動作し、独自データで動作しない場合は、独自データの問題の可能性あり
※ぱっとは、画像サイズや読み込み方(PILとOpenCVなど)が気になります

お手数ですが、ひとつずつ原因候補を潰してみてください。
過程で困った点などございましたら、引き続き、気軽にご質問いただければ、嬉しいです

どうぞよろしくお願い致します。

小川様へ
ご指摘ありがとうございました.
上記でご指摘していただいた点についてすべて確認したんですが,間違いは見つけられませんでした.
ですが,今まですべての過程をipynbファイルに書いていたところを関数やクラスをimportする方式に変えたところ,なぜか上記のエラーを解決することができました.

ですが,また損失関数のところで別のエラーが出てしまいました.以下の「IndexError: Target 2 is out of bounds.」ですが,調べたところネットワークの出力を変える必要があるのかなと思ったのですが,もしそうだとしたら,どこを変える必要があるのでしょうか.
ちなみに,考えているクラス数は背景を含めて2クラスで,dataloaderのクラスラベルは1だけの状態です.お忙しいところ恐縮ですがよろしくお願いいたします。

ーーーーーーーーーーーーーーーーーーーーーーーーーーーー
IndexError Traceback (most recent call last)
in
1 # 学習・検証を実行する
2 num_epochs= 50
----> 3 train_model(net, dataloaders_dict, criterion, optimizer, num_epochs=num_epochs)

in train_model(net, dataloaders_dict, criterion, optimizer, num_epochs)
62
63 # 損失の計算
---> 64 loss_l, loss_c = criterion(outputs, targets)
65 loss = loss_l + loss_c
66

~\anaconda3\lib\site-packages\torch\nn\modules\module.py in _call_impl(self, *input, **kwargs)
1049 if not (self._backward_hooks or self._forward_hooks or self._forward_pre_hooks or _global_backward_hooks
1050 or _global_forward_hooks or _global_forward_pre_hooks):
-> 1051 return forward_call(*input, **kwargs)
1052 # Do not call functions when jit is used
1053 full_backward_hooks, non_full_backward_hooks = [], []

D:\Pytorch\第2章\utils\ssd_model.py in forward(self, predictions, targets)
956
957 # クラス予測の損失を関数を計算(reduction='none'にして、和をとらず、次元をつぶさない)
--> 958 loss_c = F.cross_entropy(
959 batch_conf, conf_t_label.view(-1), reduction='none')
960

~\anaconda3\lib\site-packages\torch\nn\functional.py in cross_entropy(input, target, weight, size_average, ignore_index, reduce, reduction)
2822 if size_average is not None or reduce is not None:
2823 reduction = _Reduction.legacy_get_string(size_average, reduce)
-> 2824 return torch._C._nn.cross_entropy_loss(input, target, weight, _Reduction.get_enum(reduction), ignore_index)
2825
2826

IndexError: Target 2 is out of bounds.

@bo0221 さま

1つ問題が解決し前進したとのことで、ありがとうございます。

着眼点として「クラス数が違うから今動かないよね」は正しいと思います。

本書の実装コードは物体数を可変にするコードにしていないので、修正が面倒です。
(分かりやすさを優先しているため、大変申し訳ございません)

現在のクラスラベル数21や、それと同等のコード部分を、全部2に直していけば良いと思うのですが、
具体的に「ここと、ここと、ここです。」と答えるのはすぐにできず、申し訳ございません

面倒ですが、コードを追いながら、どこを直すべきか修正してみてください。

回答になっておらず、申し訳ございませんが、どうぞよろしくお願い致します。

お忙しい中,ご返信ありがとうございます.
クラス数は,コードを見ていく中で,下記のnum_classesだけを変えればよいのかなと思っています.

新たに感じたのは,今回多クラス分類ではなく二値分類なのでBinaryEntropyLossをラベルのLossのみに適用してみましたが,inputサイズとtargetのサイズが合わないといわれてダメでした.

Target 2 is out of bounds.という損失関数でのエラーは,ネットを見ると例えば2クラスの分類で本当は[0,1]と名前を付けなければならないところを,[1,2]のようにしているときに出ると記載がありました.
ですが,dataloaderのところではtargetのラベルは1だけにしていますし,0はnegativeBoxに認識されたらそれが教師データのtargetになると認識しています.
以上のようにターゲットを[0,1]だけにしているにも関わらず,なぜこのようなエラーがでてしまうのかで手が止まっています.何か考えられる原因はありますでしょうか.
s]sd_cfg = {
'num_classes': 2, # 背景クラスを含めた合計クラス数
'input_size': 300, # 画像の入力サイズ
'bbox_aspect_num': [4, 6, 6, 6, 4, 4], # 出力するDBoxのアスペクト比の種類
'feature_maps': [38, 19, 10, 5, 3, 1], # 各sourceの画像サイズ
'steps': [8, 16, 32, 64, 100, 300], # DBOXの大きさを決める
'min_sizes': [30, 60, 111, 162, 213, 264], # DBOXの大きさを決める
'max_sizes': [60, 111, 162, 213, 264, 315], # DBOXの大きさを決める
'aspect_ratios': [[2], [2, 3], [2, 3], [2, 3], [2], [2]],
}

@bo0221 さま

私も上記のnum_classesで変わると思うのですが、アノテーション時に使用するデータのクラスに1を与えていないでしょうか?

本書ではデータを用意した段階では、データのクラスは0から始まっていた?(で途中の処理だけ背景を0にして、クラス番号をずらしていた?)

それとも、1から始まっていた?

申し訳ございません。ぱっと、どっちだったか思い出せないのですが(自著なのに)、
本書のMS COCO?(でしたっけ)の場合、クラスラベルが0はじまりか、1はじまりか、と対応しているかが、気になりました。

小川様へ,

ご返答ありがとうございました.確認したところ,小川様のおっしゃる通りで,本書のコードによるとdataloaderの時点では背景はラベル0ではありませんでした.つまり,その時点では0から物体ラベルが始まっていました.
確認不足で,申し訳ありませんでした.

後から見返してみて,背景の0ラベルはおそらくjaccard係数が0.5以下だった場合にBBoxに教師データとして初めてつけられるという風に書いてあったので,そのときにつけられたのだと思ってpyファイルをみていたんですが,以下のそれっぽいコードを見つけました.
utils.match.pyから抜粋
ーーーーーーーーーーーーーーーーーーーーーーーーーーー
conf = labels[best_truth_idx] + 1 # Shape: [num_priors]
conf[best_truth_overlap < threshold] = 0 # label as background
ーーーーーーーーーーーーーーーーーーーーーーーーーー
ずっとutils.SSD_model.pyに気を取られて気が付きませんでした.ご迷惑をおかけいたしました.

また,もしわからないことが出てきたときにはご相談させてください.

@bo0221 さま

とんでもないです。本書を活用し、新たな挑戦をしていただけていること、本当に著者冥利につきます。
また、他の読者の方もきっと同じような内容で困ることがあるので、こうして時間をかけて質問していただけて、
本当に嬉しく思います。

私の、大雑把な回答・アドバイス?にも関わらず、どしどし前に進められていて凄いです。
ぜひ、今後ともいろいろご質問くださいませ!

すいません,さっそく質問で申し訳ないです.
2時間ほど悩んでいることで,エラーにはならないのですが,訓練時にLossがNanになってしまいます.
調べてみると,CrossEntropyLossのところで,logの中身が0になることが原因で,少しの誤差を加えることで解決するとあったのですが,どうやってCrossEntropyLossの計算途中で誤差を加えるのかがわからないのですが,解決策はありますか

以下のように損失が不安定になっていて,途中からnanが出てきます.
Train Loss: tensor(68630.6016, grad_fn=)
Train Loss: tensor(55620.4414, grad_fn=)
Train Loss: tensor(55231.3164, grad_fn=)
Train Loss: tensor(92996.2344, grad_fn=)
Train Loss: tensor(87211.0938, grad_fn=)
Train Loss: tensor(84169.8594, grad_fn=)
Train Loss: tensor(82305.4531, grad_fn=)
Train Loss: tensor(82030.2969, grad_fn=)
Train Loss: tensor(nan, grad_fn=)

@bo0221 さま

クロスエントロピーはlogを計算するため、マイナスを入れると計算が破綻します。

そのため、例えば、

F.binary_cross_entropy(x_hat,x,reduction='sum')

は、

F.binary_cross_entropy(x_hat+1e-8,x,reduction='sum')

のように、1e-8のような少しの正の値を足します。

ただ、損失がすごい勢いで増加しているので、
(またクラス分類の問題なので、負の値が入ることもないと思うので、)
こうした微修正の問題ではなく、

[1] 学習率が高すぎる?
[2] 本書のように、ベース部分に訓練済みモデルを使用していない?

あたりが気になるところです。

ご返信ありがとうございます.

度々すみません,もう一度本書のコードを実行して何が違うのか確かめてみたんですが,locの情報を規格化するのを忘れておりました.
なので,損失が馬鹿でかくなっていたんだと思います.

もう少し,確認する癖をつけようと思います.お忙しい中,質問に答えていただいてありがとうございました.

何度もすいません.cross_entropyのところで1e-8を入れて今度こそうまくいったと思ったのですが,なぜか途中からlossがnanになってしまいます.ほかの小さい値を入れてみたり,学習率を変えてみたりしているのですが変わりません.
これは,何が問題なんでしょうか.

loss_l: tensor(5.0545, grad_fn=) loss_c tensor(12.4402, grad_fn=)

loss_l: tensor(5.6490, grad_fn=) loss_c tensor(11.7182, grad_fn=)

loss_l: tensor(5.0131, grad_fn=) loss_c tensor(11.2012, grad_fn=)

loss_l: tensor(6.9989, grad_fn=) loss_c tensor(10.9804, grad_fn=)

loss_l: tensor(4.8557, grad_fn=) loss_c tensor(10.0007, grad_fn=)

loss_l: tensor(6.6621, grad_fn=) loss_c tensor(10.2895, grad_fn=)

loss_l: tensor(5.7157, grad_fn=) loss_c tensor(7.4265, grad_fn=)

loss_l: tensor(8.5623, grad_fn=) loss_c tensor(8.2928, grad_fn=)

loss_l: tensor(nan, grad_fn=) loss_c tensor(1.8583, grad_fn=)

loss_l: tensor(nan, grad_fn=) loss_c tensor(nan, grad_fn=)

@bo0221 さま

少しずつ前進していて良いですね。

クロスエントロピーは途中で計算がnanにふっとぶことがあります。

まず対策としては、学習率を下げてみるのが良いと思います。

そしてその前に、lossが1.8とか10くらいのときで訓練を止めて、モデルを実際に動かしてみてどういう挙動になっているのかを確認すると良いと思います。

lossが10のときと、lossが1.8のときとを比べて、望んだ方向に訓練が進んでいるのかなど、実際の挙動もぜひ確認してみてください。

一般的なアドバイスでしかないですが、参考になれば幸いです

ご返答ありがとうございます.

nanを示すまでのモデルを保存して,それについて推論を試みようとしました.

ですが,ほかのIssueにも挙げられている通り,推論の際にはPytorchのバージョンを1.5未満にする必要があるようでした.Anacondaの環境で行っているのですが,新しい環境を作るまえに,誤って自分の環境を消してしまいました.
新しくPytorchの環境を作ろうとして,本書で紹介されているURL[https://pytorch.org/get-started/locally/]から飛んでインストールしたのですが,「import torch」はできても「torch.nn.init,torch.optim as optim,torch.version」などがNo module namedとエラーになってしまいます.ほかのサイトのPytorchインストール手順でもやっても同じ状況です.

調べ方が悪いだけなのかネットで検索しても原因がヒットしないのですが,なにか解決法はありますでしょうか.

@bo0221 さま

https://pytorch.org/get-started/locally/
からでは、PyTorchのv1.5以下は用意されていないと思うので、

https://pytorch.org/get-started/previous-versions/#via-pip
を参考に例えば

conda install pytorch-cpu==1.0.0 torchvision-cpu==0.2.1 cpuonly -c pytorch

などでインストールしてみてください。

またAnaconda自体を一度アンインストールして、完全にPC上から消してやり直すのも良いです。

参考)
https://insilico-notebook.com/conda-uninstall/

このあたりで、まずAnacondaを完全に消して、インストールし直し、
その後、PyTorchの古いバージョンを入れるというのはいかがでしょうか?

何から何まで本当にありがとうございます.
無事環境をつくることができました.

nanを示すまでのモデルを保存して,それについて画像を表示してみました.
小麦をターゲットとしています.

これは,イタレーションが8までのものです.それ以降は大体どの学習率でもnanになってしまいます.
学習率は1e-3 から 1e-8まで試しました.

あまり,変化が見られなくどう判断してよいものかよくわかりません.

対象が小さいか,数が多すぎて学習がおかしくなるのでしょうか.学習率の問題ではないのかなとも思ったのですが.

test

@bo0221 さま

環境を再構築できたとのことで、良かったです。
素早いリカバリーで凄いです。

面白い取り組みですね!

SSDは対象が
・対象が被り過ぎている
・対象が画像に対して小さすぎる

とうまく機能しづらい、デメリットはあります。
(一方で、1画像内の数が多すぎるは、上記2つに該当していなければ問題ありません。
要は各バウンディングボックスが捉えられれば良いので。

上記のepoch発展の様子を見ていると、「うまく、教師データが作れていない?」
という疑問が湧きました。

例えば、教師データのアノテーションの単位が(確か)0から1の範囲で正規化した座標で、かつ、
原点は左上ですが、そのあたりは問題ないでしょうか?

一度、正解例で上記のような画像を描画してみるのが良いかもしれません。
また、描画する際の各ボックスの信頼度の下限をもっと、大きな値にして、描画されるボックスの量を減らす方が良いと思います(とくに1クラスなので、多クラスよりも描画するボックスの信頼度は下げた方が良い)

独自の取り組みは大変ですが、上記参考になれば、幸いです。どうぞよろしくお願い致します。

ご返信ありがとうございます。

小川様のいう通りで,データの中に消したと思っていたNanが入り込んでいたので,これが原因だと思われます.申し訳ございません。

google colabのGPUの都合上 10epochのモデルしか保存できなかったのですが,ちゃんと学習させることができました.本当にありがとうございました。

ですが,そのモデルを自分の環境で読み込もうとする(torch.loadの箇所)とエラーが発生します.(自分のjupyterlabで学習させてときにはエラーはおきませんでした.)
調べてみると,これはPytorchのバージョンのエラーかもしれないという記事を見つけましたが,あまりよくわかりませんでした.
一応googlecolabではPytorchのバージョンを合わせて学習したのですが,何か原因があるのでしょうか.


UnpicklingError Traceback (most recent call last)
in
6
7 # SSDの学習済みの重みを設定
----> 8 net_weights = torch.load('./weights/ssd300_10.pth')
9
10 #net_weights = torch.load('./weights/ssd300_mAP_77.43_v2.pth',

~\anaconda3\envs\pytorchtest2\lib\site-packages\torch\serialization.py in load(f, map_location, pickle_module)
365 f = open(f, 'rb')
366 try:
--> 367 return _load(f, map_location, pickle_module)
368 finally:
369 if new_fd:

~\anaconda3\envs\pytorchtest2\lib\site-packages\torch\serialization.py in _load(f, map_location, pickle_module)
526 f.seek(0)
527
--> 528 magic_number = pickle_module.load(f)
529 if magic_number != MAGIC_NUMBER:
530 raise RuntimeError("Invalid magic number; corrupt file?")

UnpicklingError: A load persistent id instruction was encountered,
but no persistent_load function was specified.

@bo0221 さま

前に進めることができており、私も嬉しいです。
本書を活用し、自分なりの問題に取り組んでいただけること、大変光栄です。

モデルの読み込み部分ですが、これは、何が原因なんでしょうか。。。

https://pytorch.org/tutorials/beginner/saving_loading_models.html

にて、以下に記載があるように、PyTorchの1.6以上で通常のoptionで保存したファイルは、v1.5以下では読めなくなっています。

The 1.6 release of PyTorch switched torch.save to use a new zipfile-based file format. torch.load still retains the ability to load files in the old format. If for any reason you want torch.save to use the old format, pass the kwarg _use_new_zipfile_serialization=False.

ローカルとGoogle Colabでバージョンを合わしているとのことですが、Google ColabのデフォルトのPyTorchのバージョンは1.6以上だと思います。

お手元の環境は、推論時にエラーがでないように、v1.5以下にしている?
などが、気になりました。

いかがでしょうか?

@bo0221 さま

いえいえ、まったく問題ございません。
原因の仮説がひとつ見つかって良かったです!

成果がでたので,ご報告させていただきます.

epoch_210_test

私のミスがほとんどの原因だったのですが,真摯に対応していただき本当にありがとうございました.
また,何かありましたらよろしくお願いいたします。

@bo0221 さま

成果のご報告をいただき、誠にありがとうございます。
書籍を執筆する人間として、これ以上なく嬉しいです。

面白いですね!複雑な形ですが、しっかりと検出できている!



私のミスがほとんどの原因だったのですが,真摯に対応していただき本当にありがとうございました

いえいえ、応用しようとすると誰もが一度は踏みそうなミスばかりだったので、本Issueは他の多くの読者の方にもとても参考になると思います。

さすがにIssueにて、手取り足取り詳細にアシストまではできないので、アドバイスしかできないのですが、
そこから成果まで持っていけたのは @bo0221 さまがきちんとした基礎がありその地の力だと思います。

こちらこそ、誠にありがとうございました!