-
Notifications
You must be signed in to change notification settings - Fork 335
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
Comments
ご質問ありがとうございます。 ご自身で可能性として挙げられている通り、私も、
と思います。 utils\data_augumentation.pyの
---> 18 max_xy = np.minimum(box_a[:, 2:], box_b[2:]) でエラーが生まれており、
のエラーメッセージとなっています。 まずは、本書の通りに動かした場合のbox_aのshapeと、現在のご自身のプログラムのbox_aのshapeを確認し、 もし、違うshapeになっていれば、その後、コードを辿って、どのテンソルからshapeが変わっているのか、 愚直にひとつずつprintしてshapeを確認していくのをおすすめします(ちょっと、面倒で大変ですが確実です)。 どうぞよろしくお願い致します。 |
すぐにご返信を頂いていたにもかかわらず、ご報告が遅くなってしまって申し訳ありません。 質問させていただいた内容についてなのですが、いろいろ試行錯誤してみたものの正しく解決することができず、以下のようにDataTransformクラスの一部をコメントアウトすることで場当たり的な対処をすることになってしまいました。下記の修正によって一応学習はできたのですが、自身が取り組んでいるタスクである数式検出においては望む結果は得られませんでした。うまくいかない原因として以下のようなことを想定しています。
そこで、さらにモデルを修正して改善を図ろうと考えているのですが、以下のようなことはコードを一部修正することで可能でしょうか。
コードを書く力が不足しており、これらを自力で実装するのに手間取っております。コードのどのあたりを修正すれば実現可能であるのか、アドバイスを頂ければ幸いです。このような場で質問させていただくような内容ではないことは重々承知なのですが、もしよろしければよろしくお願いいたします。 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) |
ご返信ありがとうございます。 上記のコメントアウトで動作させられたとのこと、重要な情報をありがとうございます。
こちらは、torchvisionの前処理のなかでも、RandomCropを使用することで可能です https://pytorch.org/vision/main/generated/torchvision.transforms.RandomCrop.html その他前処理は以下が参考になります
こちらはSSDのコードを変更すれば可能ではありますが、少し大変です。 該当してくる部分がこことことで、こうしてくださいと回答するのは、 (2-7_SSD_training.ipynbの、
部分を適切に変更すればいけるのでは?と考えています。ただ絶対にこれでいけるかは自信がなく。 ぜひコードを読みながら、挑戦いただければ幸いです。 |
いつもお忙しい中大変丁寧にご回答いただきまして本当にありがとうございます。現状試せていることについてご報告させていただきます。
ちょうどご指摘いただいている個所と、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]], #####変更#####
}
こちらはまだ試せていないので、いただいたアドバイスを参考に取り組んでみたいと思います。 現状の問題学習した重みを利用していくつかの画像で検出を行ったところ、20epoch目までの重みを用いるとどの画像に対しても同じバウンディングボックスが示されてしまい、30epoch目以降の重みを用いるとバウンディングボックスが一切検出されなくなってしまいます。このような現象が起こる一般的な原因で、もしなにか思い当たることがございましたらご指摘いただければ幸いです。これはそもそも学習の段階で何か間違っている可能性があるのでしょうか。それともタスクの難しさやデータセットの悪さによるものなのでしょうか。 |
いろいろと独自に試していただき、そしてその知見を共有いただけて、非常に感謝いたします。
アドバイスとしては、こうした挙動の確認も重要ですが、epochごとのlossの推移も気になるところです。 同様に、SSDを独自に試された方も、lossがnanに飛ぶ状況になり、その後解決されていました。 それらの経緯は、こちらのIssueの の、#190 (comment) 参考になれば、幸いです。 |
引き続きのアドバイスをありがとうございます。いろいろと改良を行っていった結果、
という部分については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も確認し、以下のような対策を取ってみたのですが解決しませんでした。
クロスエントロピー誤差でNaNが出るのはよくあることなのかなと、いろいろ検索する中で感じたのですが、SmoothL1LossでNaNが出てしまう事例で参考になるものは見つけられず...。もし思い当たる原因がありましたらご助言いただけると幸いです。 何度もお時間を頂き感謝の念に堪えません。どうぞよろしくお願いいたします。 |
御進捗の共有を誠にありがとうございます。 おっしゃる通り、SmmothL1Lossでは計算で0除算などNaNが生まれる要因がないため、Lossに入れている、 また、バッチサイズを小さくするのは良くありません。 上記のコメントより、以上の2点が気になりました。 |
小川様
私は大学の研究の一環で、SSDを用いて数式検出を行おうとしています。その際に、小川様のテキストを参考にさせていただいており、誠にありがとうございます。
研究においては独自でアノテーションされたデータを用意し、テキストを参考にさせていただいてSSDを実装していたのですが、2-7_SSD_training.ipynb内の最後のブロックでtrain_model()を実行する際に下記のようなエラーが出てしまいます。
まず、テキスト通りのコードで、テキスト通りのデータを用いて学習を行った場合には問題なく実行できます。
私の用いるデータは、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?の形が間違っているせいで出てしまっているエラーなのかと愚考します。しかし、これ以上の原因究明方法が分からず、手詰まりになってしまいました。
お忙しい中大変恐縮ですが、ご協力いただけますと幸いです。
The text was updated successfully, but these errors were encountered: