Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

【第2章】2-7_SSD_training.ipynb内のでのエラー #195

Open
jackie687456 opened this issue Jan 17, 2022 · 7 comments
Open

【第2章】2-7_SSD_training.ipynb内のでのエラー #195

jackie687456 opened this issue Jan 17, 2022 · 7 comments

Comments

@jackie687456
Copy link

小川様

私は大学の研究の一環で、SSDを用いて数式検出を行おうとしています。その際に、小川様のテキストを参考にさせていただいており、誠にありがとうございます。

研究においては独自でアノテーションされたデータを用意し、テキストを参考にさせていただいてSSDを実装していたのですが、2-7_SSD_training.ipynb内の最後のブロックでtrain_model()を実行する際に下記のようなエラーが出てしまいます。

---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
~\AppData\Local\Temp/ipykernel_18292/1042517758.py in <module>
      1 # 学習・検証を実行する
      2 num_epochs= 11
----> 3 train_model(net, dataloaders_dict, criterion, optimizer, num_epochs=num_epochs)

~\AppData\Local\Temp/ipykernel_18292/3990735102.py in train_model(net, dataloaders_dict, criterion, optimizer, num_epochs)
     46 
     47             # データローダーからminibatchずつ取り出すループ
---> 48             for images, targets in dataloaders_dict[phase]:
     49                 print("1")
     50                 # GPUが使えるならGPUにデータを送る

~\AppData\Local\Programs\Python\Python39\lib\site-packages\torch\utils\data\dataloader.py in __next__(self)
    519             if self._sampler_iter is None:
    520                 self._reset()
--> 521             data = self._next_data()
    522             self._num_yielded += 1
    523             if self._dataset_kind == _DatasetKind.Iterable and \

~\AppData\Local\Programs\Python\Python39\lib\site-packages\torch\utils\data\dataloader.py in _next_data(self)
    559     def _next_data(self):
    560         index = self._next_index()  # may raise StopIteration
--> 561         data = self._dataset_fetcher.fetch(index)  # may raise StopIteration
    562         if self._pin_memory:
    563             data = _utils.pin_memory.pin_memory(data)

~\AppData\Local\Programs\Python\Python39\lib\site-packages\torch\utils\data\_utils\fetch.py in fetch(self, possibly_batched_index)
     47     def fetch(self, possibly_batched_index):
     48         if self.auto_collation:
---> 49             data = [self.dataset[idx] for idx in possibly_batched_index]
     50         else:
     51             data = self.dataset[possibly_batched_index]

~\AppData\Local\Programs\Python\Python39\lib\site-packages\torch\utils\data\_utils\fetch.py in <listcomp>(.0)
     47     def fetch(self, possibly_batched_index):
     48         if self.auto_collation:
---> 49             data = [self.dataset[idx] for idx in possibly_batched_index]
     50         else:
     51             data = self.dataset[possibly_batched_index]

c:\Users\kwmrs\Documents\studies\SSD_IBEM\utils\ssd_model.py in __getitem__(self, index)
    213         前処理をした画像のテンソル形式のデータとアノテーションを取得
    214         '''
--> 215         im, gt, h, w = self.pull_item(index)
    216         return im, gt
    217 

c:\Users\kwmrs\Documents\studies\SSD_IBEM\utils\ssd_model.py in pull_item(self, index)
    229         print("anno_list =")
    230         print(anno_list)
--> 231 
    232         # 3. 前処理を実施
    233         img, boxes, labels = self.transform(

c:\Users\kwmrs\Documents\studies\SSD_IBEM\utils\ssd_model.py in __call__(self, img, phase, boxes, labels)
    177             前処理のモードを指定。
    178         """
--> 179         return self.data_transform[phase](img, boxes, labels)
    180 
    181 

c:\Users\kwmrs\Documents\studies\SSD_IBEM\utils\data_augumentation.py in __call__(self, img, boxes, labels)
     58     def __call__(self, img, boxes=None, labels=None):
     59         for t in self.transforms:
---> 60             img, boxes, labels = t(img, boxes, labels)
     61         return img, boxes, labels
     62 

c:\Users\kwmrs\Documents\studies\SSD_IBEM\utils\data_augumentation.py in __call__(self, image, boxes, labels)
    272 
    273                 # calculate IoU (jaccard overlap) b/t the cropped and gt boxes
--> 274                 overlap = jaccard_numpy(boxes, rect)
    275 
    276                 # is min and max overlap constraint satisfied? if not try again

c:\Users\kwmrs\Documents\studies\SSD_IBEM\utils\data_augumentation.py in jaccard_numpy(box_a, box_b)
     33         jaccard overlap: Shape: [box_a.shape[0], box_a.shape[1]]
     34     """
---> 35     inter = intersect(box_a, box_b)
     36     area_a = ((box_a[:, 2]-box_a[:, 0]) *
     37               (box_a[:, 3]-box_a[:, 1]))  # [A,B]

c:\Users\kwmrs\Documents\studies\SSD_IBEM\utils\data_augumentation.py in intersect(box_a, box_b)
     16 
     17 def intersect(box_a, box_b):
---> 18     max_xy = np.minimum(box_a[:, 2:], box_b[2:])
     19     min_xy = np.maximum(box_a[:, :2], box_b[:2])
     20     inter = np.clip((max_xy - min_xy), a_min=0, a_max=np.inf)

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

まず、テキスト通りのコードで、テキスト通りのデータを用いて学習を行った場合には問題なく実行できます。

私の用いるデータは、2クラス(埋め込み数式とディスプレイ数式)を検出しようとしています。アノテーションの保存形式が独自のtxtファイルとなっており、それに合わせてssd_model.py内のmake_datapath_list()やAnno_xml2list()を大きく変更いたしました。また、検出するクラス数が2クラスであることから、(元のクラス数+背景クラスの1)である21を記述されている部分を3に変更するようにしました。これらの変更はまず、2-2-3_Dataset_DataLoader.ipynb、2-4-5_SSD_model_forward.ipynb、2-6_loss_function.ipynbでそれぞれ実行して試しており、その際にはエラーは出ずに実行できました。

issue #190 の方の質問内容と似通っているところがあって参考にもさせていただき、どこかしらでlistやarray?の形が間違っているせいで出てしまっているエラーなのかと愚考します。しかし、これ以上の原因究明方法が分からず、手詰まりになってしまいました。

お忙しい中大変恐縮ですが、ご協力いただけますと幸いです。

@YutaroOgawa
Copy link
Owner

YutaroOgawa commented Jan 17, 2022

@jackie687456 さま

ご質問ありがとうございます。
独自に取り組んでいただき、著者としてもとても嬉しいです。

ご自身で可能性として挙げられている通り、私も、

どこかしらでlistやarray?の形が間違っているせいで出てしまっているエラーなのか

と思います。

utils\data_augumentation.pyの

 17 def intersect(box_a, box_b):

---> 18 max_xy = np.minimum(box_a[:, 2:], box_b[2:])

でエラーが生まれており、

operands could not be broadcast together with shapes (4,3) (2,)

のエラーメッセージとなっています。

まずは、本書の通りに動かした場合のbox_aのshapeと、現在のご自身のプログラムのbox_aのshapeを確認し、
同じになっているか、それぞれの値をprintして調べるのが良いと思います。

もし、違うshapeになっていれば、その後、コードを辿って、どのテンソルからshapeが変わっているのか、

愚直にひとつずつprintしてshapeを確認していくのをおすすめします(ちょっと、面倒で大変ですが確実です)。

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

@jackie687456
Copy link
Author

@YutaroOgawa

すぐにご返信を頂いていたにもかかわらず、ご報告が遅くなってしまって申し訳ありません。

質問させていただいた内容についてなのですが、いろいろ試行錯誤してみたものの正しく解決することができず、以下のようにDataTransformクラスの一部をコメントアウトすることで場当たり的な対処をすることになってしまいました。下記の修正によって一応学習はできたのですが、自身が取り組んでいるタスクである数式検出においては望む結果は得られませんでした。うまくいかない原因として以下のようなことを想定しています。

  • 文書画像全体をそのまま300×300にリサイズしてしまっていることで、文字や数式としての細部がつぶれてしまっている
  • SSDではバウンディングボックスの出力として、数種類の大きさとアスペクト比を持ったデフォルトボックスを用意(8732個)し、その各デフォルトボックスに対して大きさを調整したものと各クラスの信頼度を得る。しかし、実装したコードのデフォルトボックスのアスペクト比は最も横長なものでも1:3である一方で、数式にはより横長なものも存在する

そこで、さらにモデルを修正して改善を図ろうと考えているのですが、以下のようなことはコードを一部修正することで可能でしょうか。

  • 入力画像を圧縮するのではなく、切り取ることによって300×300の入力とする。
  • デフォルトボックスのアスペクト比を増やす、もしくは変更する。

コードを書く力が不足しており、これらを自力で実装するのに手間取っております。コードのどのあたりを修正すれば実現可能であるのか、アドバイスを頂ければ幸いです。このような場で質問させていただくような内容ではないことは重々承知なのですが、もしよろしければよろしくお願いいたします。

class DataTransform():
    """
    画像とアノテーションの前処理クラス。訓練と推論で異なる動作をする。
    画像のサイズを300x300にする。
    学習時はデータオーギュメンテーションする。


    Attributes
    ----------
    input_size : int
        リサイズ先の画像の大きさ。
    color_mean : (B, G, R)
        各色チャネルの平均値。
    """

    def __init__(self, input_size, color_mean):
        self.data_transform = {
            'train': Compose([
                ConvertFromInts(),  # intをfloat32に変換
                ToAbsoluteCoords(),  # アノテーションデータの規格化を戻す
                #PhotometricDistort(),  # 画像の色調などをランダムに変化
                #Expand(color_mean),  # 画像のキャンバスを広げる
                RandomSampleCrop(),  # 画像内の部分をランダムに抜き出す
                #RandomMirror(),  # 画像を反転させる
                ToPercentCoords(),  # アノテーションデータを0-1に規格化
                Resize(input_size),  # 画像サイズをinput_size×input_sizeに変形
                SubtractMeans(color_mean)  # BGRの色の平均値を引き算
            ]),
            'val': Compose([
                ConvertFromInts(),  # intをfloatに変換
                Resize(input_size),  # 画像サイズをinput_size×input_sizeに変形
                SubtractMeans(color_mean)  # BGRの色の平均値を引き算
            ])
        }

    def __call__(self, img, phase, boxes, labels):
        """
        Parameters
        ----------
        phase : 'train' or 'val'
            前処理のモードを指定。
        """
        return self.data_transform[phase](img, boxes, labels)

@YutaroOgawa
Copy link
Owner

@jackie687456 さま

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

上記のコメントアウトで動作させられたとのこと、重要な情報をありがとうございます。

入力画像を圧縮するのではなく、切り取ることによって300×300の入力とする。

こちらは、torchvisionの前処理のなかでも、RandomCropを使用することで可能です

https://pytorch.org/vision/main/generated/torchvision.transforms.RandomCrop.html

その他前処理は以下が参考になります
https://pytorch.org/vision/stable/transforms.html


デフォルトボックスのアスペクト比を増やす、もしくは変更する。

こちらはSSDのコードを変更すれば可能ではありますが、少し大変です。
アスペクト比を決めている部分を変更するか、新たなアスペクト比を追加するかです。

該当してくる部分がこことことで、こうしてくださいと回答するのは、
Issueでは対応できるレベルを超えてしまい大変なので、大変申し訳ございません。

(2-7_SSD_training.ipynbの、

# SSD300の設定
ssd_cfg = {
    'num_classes': 21,  # 背景クラスを含めた合計クラス数
    '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]],
}

部分を適切に変更すればいけるのでは?と考えています。ただ絶対にこれでいけるかは自信がなく。

ぜひコードを読みながら、挑戦いただければ幸いです。

@jackie687456
Copy link
Author

@YutaroOgawa

いつもお忙しい中大変丁寧にご回答いただきまして本当にありがとうございます。現状試せていることについてご報告させていただきます。

デフォルトボックスのアスペクト比を増やす、もしくは変更する。

ちょうどご指摘いただいている個所と、ssd_model.pyのDBoxクラスをわずかに修正することで実現が可能でした。私の場合はsource1~3を従来の6種類+アスペクト比{5, 7, 10}のものを加えた9種類に変更してみました。まだ望む結果を得る段階には至っていないのですが、比較的横長のものが増えた気がします。変更後のコードを以下に記載させていただきます。

ssd_model.py

class DBox(object):
    def __init__(self, cfg):
        super(DBox, self).__init__()

        # 初期設定
        self.image_size = cfg['input_size']  # 画像サイズの300
        # [38, 19, …] 各sourceの特徴量マップのサイズ
        self.feature_maps = cfg['feature_maps']
        self.num_priors = len(cfg["feature_maps"])  # sourceの個数=6
        self.steps = cfg['steps']  # [8, 16, …] DBoxのピクセルサイズ

        self.min_sizes = cfg['min_sizes']
        # [30, 60, …] 小さい正方形のDBoxのピクセルサイズ(正確には面積)

        self.max_sizes = cfg['max_sizes']
        # [60, 111, …] 大きい正方形のDBoxのピクセルサイズ(正確には面積)

        self.aspect_ratios = cfg['aspect_ratios']  # 長方形のDBoxのアスペクト比

    def make_dbox_list(self):
        '''DBoxを作成する'''
        mean = []
        # 'feature_maps': [38, 19, 10, 5, 3, 1]
        for k, f in enumerate(self.feature_maps):
            for i, j in product(range(f), repeat=2):  # fまでの数で2ペアの組み合わせを作る f_P_2 個
                # 特徴量の画像サイズ
                # 300 / 'steps': [8, 16, 32, 64, 100, 300],
                f_k = self.image_size / self.steps[k]

                # DBoxの中心座標 x,y ただし、0~1で規格化している
                cx = (j + 0.5) / f_k
                cy = (i + 0.5) / f_k

                # アスペクト比1の小さいDBox [cx,cy, width, height]
                # 'min_sizes': [30, 60, 111, 162, 213, 264]
                s_k = self.min_sizes[k]/self.image_size
                mean += [cx, cy, s_k, s_k]

                # アスペクト比1の大きいDBox [cx,cy, width, height]
                # 'max_sizes': [60, 111, 162, 213, 264, 315],
                s_k_prime = sqrt(s_k * (self.max_sizes[k]/self.image_size))
                mean += [cx, cy, s_k_prime, s_k_prime]

                # その他のアスペクト比のdefBox [cx,cy, width, height]
                for ar in self.aspect_ratios[k]:
                    mean += [cx, cy, s_k*sqrt(ar), s_k/sqrt(ar)]
                    mean += [cx, cy, s_k/sqrt(ar), s_k*sqrt(ar)]

                ##############
                #####追加#####
                ##############
                # 横長のdefBox [cx,cy, width, height]
                if k<3:
                    aspect_ratio = [5, 7, 10]
                    for ar in aspect_ratio:
                        mean += [cx, cy, s_k*sqrt(ar), s_k/sqrt(ar)]

        # DBoxをテンソルに変換 torch.Size([8732, 4])
        output = torch.Tensor(mean).view(-1, 4)
        
        # DBoxが画像の外にはみ出るのを防ぐため、大きさを最小0、最大1にする
        output.clamp_(max=1, min=0)
        return output

2-7_SSD_training.ipynb

# SSD300の設定
ssd_cfg = {
    'num_classes': 3,  # 背景クラスを含めた合計クラス数
    'input_size': 300,  # 画像の入力サイズ
    'bbox_aspect_num': [9, 9, 9, 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, 3], [2, 3], [2, 3], [2, 3], [2], [2]],                 #####変更#####
}

入力画像を圧縮するのではなく、切り取ることによって300×300の入力とする。

こちらはまだ試せていないので、いただいたアドバイスを参考に取り組んでみたいと思います。

現状の問題

学習した重みを利用していくつかの画像で検出を行ったところ、20epoch目までの重みを用いるとどの画像に対しても同じバウンディングボックスが示されてしまい、30epoch目以降の重みを用いるとバウンディングボックスが一切検出されなくなってしまいます。このような現象が起こる一般的な原因で、もしなにか思い当たることがございましたらご指摘いただければ幸いです。これはそもそも学習の段階で何か間違っている可能性があるのでしょうか。それともタスクの難しさやデータセットの悪さによるものなのでしょうか。

@YutaroOgawa
Copy link
Owner

@jackie687456 さま

いろいろと独自に試していただき、そしてその知見を共有いただけて、非常に感謝いたします。

学習した重みを利用していくつかの画像で検出を行ったところ、20epoch目までの重みを用いるとどの画像に対しても同じバウンディングボックスが示されてしまい、30epoch目以降の重みを用いるとバウンディングボックスが一切検出されなくなってしまいます。このような現象が起こる一般的な原因で、もしなにか思い当たることがございましたらご指摘いただければ幸いです。

アドバイスとしては、こうした挙動の確認も重要ですが、epochごとのlossの推移も気になるところです。

同様に、SSDを独自に試された方も、lossがnanに飛ぶ状況になり、その後解決されていました。

それらの経緯は、こちらのIssueの

#190

の、#190 (comment)
あたりからになります

参考になれば、幸いです。

@jackie687456
Copy link
Author

jackie687456 commented Feb 2, 2022

@YutaroOgawa

引き続きのアドバイスをありがとうございます。いろいろと改良を行っていった結果、

入力画像を圧縮するのではなく、切り取ることによって300×300の入力とする。

という部分についてはdata_augumentation.pyで定義されたRandamSmapleCropクラスを改造したSampleCropクラスを作成することにより実現できました。切り取るサイズが300×300だと小さすぎたので、600×600を切り取って300×300にリサイズするようにしました(リサイズはまた別のクラスでの処理)。少なくとも一つは正解boxが含まれるように、一定回数ランダムで切り取る試行を行い、条件を満たさなければ一つ目のboxが強制的に含まれるようにしました。念のため、以下にその内容を記載させていただきます。

class SampleCrop in data_augumentation.py

class SampleCrop(object):
    """Crop
    Arguments:
        img (Image): the image being input during training
        boxes (Tensor): the original bounding boxes in pt form
        labels (Tensor): the class labels for each bbox
        mode (float tuple): the min and max jaccard overlaps
    Return:
        (img, boxes, classes)
            img (Image): the cropped image
            boxes (Tensor): the adjusted bounding boxes in pt form
            labels (Tensor): the class labels for each bbox
    """
    def __call__(self, image, boxes=None, labels=None):
        height, width, _ = image.shape
        w = 600
        h = 600

        while True:
            min_iou = 0.8
            max_iou = float('inf')

            # max trails (50)
            for _ in range(50):
                current_image = image

                left = random.uniform(width - w)
                top = random.uniform(height - h)

                # convert to integer rect x1,y1,x2,y2
                rect = np.array([int(left), int(top), int(left+w), int(top+h)])

                # calculate IoU (jaccard overlap) b/t the cropped and gt boxes
                overlap = jaccard_numpy(boxes, rect)

                # is min and max overlap constraint satisfied? if not try again
                if overlap.min() < min_iou and max_iou < overlap.max():
                    continue

                # cut the crop from the image
                current_image = current_image[rect[1]:rect[3], rect[0]:rect[2],
                                              :]

                # keep overlap with gt box IF center in sampled patch
                centers = (boxes[:, :2] + boxes[:, 2:]) / 2.0

                #全部trueの時だけmask=1になる
                # mask in all gt boxes that above and to the left of centers
                m1 = (rect[0] < centers[:, 0]) * (rect[1] < centers[:, 1])
                # mask in all gt boxes that under and to the right of centers
                m2 = (rect[2] > centers[:, 0]) * (rect[3] > centers[:, 1])
                # mask in that both m1 and m2 are true
                mask = m1 * m2

                # boxが大きい場合は入れる
                box_sizes = boxes[:, 2:] - boxes[:, :2]
                for i in range(len(mask)):
                    huge_box_judge = (box_sizes[i, 0] > w*2) or (box_sizes[i, 1] > h*2)
                    overlap_judge = overlap[i] > 0.8
                    if overlap_judge and huge_box_judge:
                        mask[i] = True

                # have any valid boxes? try again if not
                if not mask.any():
                    continue

                #中心が枠内のboxだけを取得
                # take only matching gt boxes
                current_boxes = boxes[mask, :].copy()
                # take only matching gt labels
                current_labels = labels[mask]

                # should we use the box left and top corner or the crop's
                # 左上
                current_boxes[:, :2] = np.maximum(current_boxes[:, :2], rect[:2])
                # adjust to crop (by substracting crop's left,top)
                current_boxes[:, :2] -= rect[:2]
                # 右下
                current_boxes[:, 2:] = np.minimum(current_boxes[:, 2:], rect[2:])
                # adjust to crop (by substracting crop's left,top)
                current_boxes[:, 2:] -= rect[:2]

                return current_image, current_boxes, current_labels

            # 50回を超えたらGTのboxを基準にとる
            current_image = image
            box_tl_x = boxes[0, 0]
            box_tl_y = boxes[0, 1]

            if box_tl_x <= width-w and box_tl_y <= height-w:
                left = box_tl_x
                top = box_tl_y
            elif box_tl_x > width-w:
                left = box_tl_x
                top = height-w
            elif box_tl_y > height-w:
                left = width-w
                top = box_tl_y
            else:
                left = width-w
                top = height-w

            # convert to integer rect x1,y1,x2,y2
            rect = np.array([int(left), int(top), int(left+w), int(top+h)])

            # calculate IoU (jaccard overlap) b/t the cropped and gt boxes
            overlap = jaccard_numpy(boxes, rect)

            # cut the crop from the image
            current_image = current_image[rect[1]:rect[3], rect[0]:rect[2],
                                              :]

            # keep overlap with gt box IF center in sampled patch
            centers = (boxes[:, :2] + boxes[:, 2:]) / 2.0

            #全部trueの時だけmask=Trueになる
            # mask in all gt boxes that above and to the left of centers
            m1 = (rect[0] < centers[:, 0]) * (rect[1] < centers[:, 1])
            # mask in all gt boxes that under and to the right of centers
            m2 = (rect[2] > centers[:, 0]) * (rect[3] > centers[:, 1])
            # mask in that both m1 and m2 are true
            mask = m1 * m2

            # boxが大きい場合は入れる
            box_sizes = boxes[:, 2:] - boxes[:, :2]
            for i in range(len(mask)):
                huge_box_judge = (box_sizes[i, 0] > w*2) or (box_sizes[i, 1] > h*2)
                overlap_judge = overlap[i] > 0.8
                if overlap_judge and huge_box_judge:
                    mask[i] = True
            # 基準にした最初のboxは入れる
            mask[0] = True

            #中心が枠内のboxだけを取得
            # take only matching gt boxes
            current_boxes = boxes[mask, :].copy()
            # take only matching gt labels
            current_labels = labels[mask]

            # should we use the box left and top corner or the crop's
            current_boxes[:, :2] = np.maximum(current_boxes[:, :2], rect[:2])
            # adjust to crop (by substracting crop's left,top)
            current_boxes[:, :2] -= rect[:2]

            current_boxes[:, 2:] = np.minimum(current_boxes[:, 2:],
                                                  rect[2:])
            # adjust to crop (by substracting crop's left,top)
            current_boxes[:, 2:] -= rect[:2]

            return current_image, current_boxes, current_labels

これで無事学習が進む...と思ったのですがそううまくはいかず、今度は学習の際、数epochするとlossがNaNになってしまう現象に陥りました。

アドバイスいただいた通り、epochごとのlossをloss_lとloss_cに分けて出力して確認することで原因の特定を図ったのですが、どうやら毎度loss_l、すなわちSmoothL1Lossの方で値がNaNになっているようでした。紹介した頂いたIsuueも確認し、以下のような対策を取ってみたのですが解決しませんでした。

  • 学習率を下げる
    1e-5あたりにしたが変わらず
  • バッチサイズを小さくする
    データの数が少ない(訓練用で1300程度)ので、バッチサイズを4にしたが変わらず
  • データに誤りがないか確かめる
    データの形として「アノテーションの単位が0から1の範囲で正規化した座標で、かつ、原点は左上」であることを確認。
    データを取り出す作業のみを50エポック分行い、すべての画像とboxに対してNaN値が含まれないか確認したが問題なし。

クロスエントロピー誤差でNaNが出るのはよくあることなのかなと、いろいろ検索する中で感じたのですが、SmoothL1LossでNaNが出てしまう事例で参考になるものは見つけられず...。もし思い当たる原因がありましたらご助言いただけると幸いです。

何度もお時間を頂き感謝の念に堪えません。どうぞよろしくお願いいたします。

@YutaroOgawa
Copy link
Owner

@jackie687456 さま

御進捗の共有を誠にありがとうございます。

おっしゃる通り、SmmothL1Lossでは計算で0除算などNaNが生まれる要因がないため、Lossに入れている、
正解のloc_tか、SSDで推定されたloc_p自体がNaNになっている可能性があります。
こちら確認してみてください。

また、バッチサイズを小さくするのは良くありません。
データ数の少なさはデータ拡張で補い、バッチサイズは変更しないままにします。
ミニバッチ内の平均的に学習することで、良いネットワークへと訓練されていくので、ミニバッチのサイズが小さいと、うまく学習できなくなります

上記のコメントより、以上の2点が気になりました。
どうぞよろしくお願いいたします。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants