def game(name=None): ids= manager.getIDs() if 'username' in session: loggedin=True username=session['username'] myGames=manager.getUserGames(username) if request.method=='POST': if request.form["submit"] == "Go": if manager.getProfilePath() != "profile/": return redirect(manager.getProfilePath()) if name is None: gamelist=manager.getCompleteGames() #print gamelist return render_template("game.html",loggedin=loggedin,username=username,ids=ids,gamelist=gamelist,myGames=myGames) print name gameFax=manager.getGameFax(name) starts=gameFax[0][2] ends=gameFax[-1][2] percent=match.match(starts,ends) percent=int(round(abs(percent)*100)) finished=manager.isComplete(name) if finished is False: return render_template("game.html",loggedin=loggedin,username=username,ids=ids,reason="This game is still in progress!",myGames=myGames) else: return render_template("game.html", loggedin=loggedin, username=username,ids=ids,gameFax=gameFax,name=name,myGames=myGames,percent=percent) else: loggedin=False username = '******' return render_template("game.html", loggedin=loggedin, username=username,ids=ids)
def game(name=None): ids = manager.getIDs() if 'username' in session: loggedin = True username = session['username'] myGames = manager.getUserGames(username) if request.method == 'POST': if request.form["submit"] == "Go": if manager.getProfilePath() != "profile/": return redirect(manager.getProfilePath()) if name is None: gamelist = manager.getCompleteGames() #print gamelist return render_template("game.html", loggedin=loggedin, username=username, ids=ids, gamelist=gamelist, myGames=myGames) print name gameFax = manager.getGameFax(name) starts = gameFax[0][2] ends = gameFax[-1][2] percent = match.match(starts, ends) percent = int(round(abs(percent) * 100)) finished = manager.isComplete(name) if finished is False: return render_template("game.html", loggedin=loggedin, username=username, ids=ids, reason="This game is still in progress!", myGames=myGames) else: return render_template("game.html", loggedin=loggedin, username=username, ids=ids, gameFax=gameFax, name=name, myGames=myGames, percent=percent) else: loggedin = False username = '******' return render_template("game.html", loggedin=loggedin, username=username, ids=ids)
def forward(self, predictions, targets): """ 損失関数の計算。 Parameters ---------- predictions : SSD netの訓練時の出力(tuple) (loc=torch.Size([num_batch, 8732, 4]), conf=torch.Size([num_batch, 8732, 21]), dbox_list=torch.Size [8732,4])。 targets : [num_batch, num_objs, 5] 5は正解のアノテーション情報[xmin, ymin, xmax, ymax, label_ind]を示す Returns ------- loss_l : テンソル locの損失の値 loss_c : テンソル confの損失の値 """ # SSDモデルの出力がタプルになっているので、個々にばらす loc_data, conf_data, dbox_list = predictions # 要素数を把握 num_batch = loc_data.size(0) # ミニバッチのサイズ num_dbox = loc_data.size(1) # DBoxの数 = 8732 num_classes = conf_data.size(2) # クラス数 = 21 # 損失の計算に使用するものを格納する変数を作成 # conf_t_label:各DBoxに一番近い正解のBBoxのラベルを格納させる # loc_t:各DBoxに一番近い正解のBBoxの位置情報を格納させる conf_t_label = torch.LongTensor(num_batch, num_dbox).to(self.device) loc_t = torch.Tensor(num_batch, num_dbox, 4).to(self.device) # loc_tとconf_t_labelに、 # DBoxと正解アノテーションtargetsをmatchさせた結果を上書きする for idx in range(num_batch): # ミニバッチでループ # 現在のミニバッチの正解アノテーションのBBoxとラベルを取得 truths = targets[idx][:, :-1].to(self.device) # BBox # ラベル [物体1のラベル, 物体2のラベル, …] labels = targets[idx][:, -1].to(self.device) # デフォルトボックスを新たな変数で用意 dbox = dbox_list.to(self.device) # 関数matchを実行し、loc_tとconf_t_labelの内容を更新する # (詳細) # loc_t:各DBoxに一番近い正解のBBoxの位置情報が上書きされる # conf_t_label:各DBoxに一番近いBBoxのラベルが上書きされる # ただし、一番近いBBoxとのjaccard overlapが0.5より小さい場合は # 正解BBoxのラベルconf_t_labelは背景クラスの0とする variance = [0.1, 0.2] # このvarianceはDBoxからBBoxに補正計算する際に使用する式の係数です match(self.jaccard_thresh, truths, dbox, variance, labels, loc_t, conf_t_label, idx) # ---------- # 位置の損失:loss_lを計算 # Smooth L1関数で損失を計算する。ただし、物体を発見したDBoxのオフセットのみを計算する # ---------- # 物体を検出したBBoxを取り出すマスクを作成 pos_mask = conf_t_label > 0 # torch.Size([num_batch, 8732]) # pos_maskをloc_dataのサイズに変形 pos_idx = pos_mask.unsqueeze(pos_mask.dim()).expand_as(loc_data) # Positive DBoxのloc_dataと、教師データloc_tを取得 loc_p = loc_data[pos_idx].view(-1, 4) loc_t = loc_t[pos_idx].view(-1, 4) # 物体を発見したPositive DBoxのオフセット情報loc_tの損失(誤差)を計算 loss_l = F.smooth_l1_loss(loc_p, loc_t, reduction='sum') # ---------- # クラス予測の損失:loss_cを計算 # 交差エントロピー誤差関数で損失を計算する。ただし、背景クラスが正解であるDBoxが圧倒的に多いので、 # Hard Negative Miningを実施し、物体発見DBoxと背景クラスDBoxの比が1:3になるようにする。 # そこで背景クラスDBoxと予想したもののうち、損失が小さいものは、クラス予測の損失から除く # ---------- batch_conf = conf_data.view(-1, num_classes) # クラス予測の損失を関数を計算(reduction='none'にして、和をとらず、次元をつぶさない) loss_c = F.cross_entropy(batch_conf, conf_t_label.view(-1), reduction='none') # ----------------- # これからNegative DBoxのうち、Hard Negative Miningで抽出するものを求めるマスクを作成します # ----------------- # 物体発見したPositive DBoxの損失を0にする # (注意)物体はlabelが1以上になっている。ラベル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 # 物体を発見したDBoxは損失0とする # Hard Negative Miningを実施する # 各DBoxの損失の大きさloss_cの順位であるidx_rankを求める _, loss_idx = loss_c.sort(1, descending=True) _, idx_rank = loss_idx.sort(1) # (注釈) # 実装コードがかなり特殊で直感的ではないです。 # 上記2行は、要は各DBoxに対して、損失の大きさが何番目なのかの情報を # 変数idx_rankとして高速に取得したいというコードです。 # # DBOXの損失値の大きい方から降順に並べ、DBoxの降順のindexをloss_idxに格納。 # 損失の大きさloss_cの順位であるidx_rankを求める。 # ここで、 # 降順になった配列indexであるloss_idxを、0から8732まで昇順に並べ直すためには、 # 何番目のloss_idxのインデックスをとってきたら良いのかを示すのが、idx_rankである。 # 例えば、 # idx_rankの要素0番目 = idx_rank[0]を求めるには、loss_idxの値が0の要素、 # つまりloss_idx[?}=0 の、?は何番かを求めることになる。ここで、? = idx_rank[0]である。 # いま、loss_idx[?]=0の0は、元のloss_cの要素の0番目という意味である。 # つまり?は、元のloss_cの要素0番目は、降順に並び替えられたloss_idxの何番目ですか # を求めていることになり、 結果、 # ? = idx_rank[0] はloss_cの要素0番目が、降順の何番目かを示すことになる。 # 背景のDBoxの数num_negを決める。HardNegative Miningにより、 # 物体発見のDBoxの数num_posの3倍(self.negpos_ratio倍)とする。 # ただし、万が一、DBoxの数を超える場合は、DBoxの数を上限とする num_neg = torch.clamp(num_pos * self.negpos_ratio, max=num_dbox) # idx_rankは各DBoxの損失の大きさが上から何番目なのかが入っている # 背景のDBoxの数num_negよりも、順位が低い(すなわち損失が大きい)DBoxを取るマスク作成 # torch.Size([num_batch, 8732]) neg_mask = idx_rank < (num_neg).expand_as(idx_rank) # ----------------- # (終了)これからNegative DBoxのうち、Hard Negative Miningで抽出するものを求めるマスクを作成します # ----------------- # マスクの形を整形し、conf_dataに合わせる # pos_idx_maskはPositive DBoxのconfを取り出すマスクです # neg_idx_maskはHard Negative Miningで抽出したNegative DBoxのconfを取り出すマスクです # pos_mask:torch.Size([num_batch, 8732])→pos_idx_mask:torch.Size([num_batch, 8732, 21]) pos_idx_mask = pos_mask.unsqueeze(2).expand_as(conf_data) neg_idx_mask = neg_mask.unsqueeze(2).expand_as(conf_data) # conf_dataからposとnegだけを取り出してconf_hnmにする。形はtorch.Size([num_pos+num_neg, 21]) conf_hnm = conf_data[(pos_idx_mask + neg_idx_mask).gt(0)].view( -1, num_classes) # (注釈)gtは greater than (>)の略称。これでmaskが1のindexを取り出す。 # pos_idx_mask+neg_idx_maskは足し算だが、indexへのmaskをまとめているだけである。 # つまり、posであろうがnegであろうが、マスクが1のものを足し算で一つのリストにし、それをgtで取得 # 同様に教師データであるconf_t_labelからposとnegだけを取り出してconf_t_label_hnmに # 形はtorch.Size([pos+neg])になる conf_t_label_hnm = conf_t_label[(pos_mask + neg_mask).gt(0)] # confidenceの損失関数を計算(要素の合計=sumを求める) loss_c = F.cross_entropy(conf_hnm, conf_t_label_hnm, reduction='sum') # 物体を発見したBBoxの数N(全ミニバッチの合計)で損失を割り算 N = num_pos.sum() loss_l /= N loss_c /= N return loss_l, loss_c
def forward(self, predictions, targets): """calculate loss function Args: - predictions: output of SSD net in training (loc=torch.Size([num_batch, 8732, 4]), conf=torch.Size([num_batch, 8732, 21]), dbox_list=torch.Size [8732,4])。 - targets: [num_batch, num_objs, 5] Returns: - loss_l: loss of loc - loss_c : loss of conf """ loc_data, conf_data, dbox_list = predictions num_batch = loc_data.size(0) # size of mini-batch num_dbox = loc_data.size(1) # number of DBox num_classes = conf_data.size(2) # number of classes # label of the correct BBox closest to each DBox conf_t_label = torch.LongTensor(num_batch, num_dbox).to(self.device) # position of the correct BBox closest to each DBox loc_t = torch.Tensor(num_batch, num_dbox, 4).to(self.device) for idx in range(num_batch): truths = targets[idx][:, :-1].to(self.device) labels = targets[idx][:, -1].to(self.device) dbox = dbox_list.to(self.device) variance = [0.1, 0.2] match(self.jaccard_thresh, truths, dbox, variance, labels, loc_t, conf_t_label, idx) # loss of position pos_mask = conf_t_label > 0 # torch.Size([num_batch, 8732]) # transform pos_mask into size of loc_data pos_idx = pos_mask.unsqueeze(pos_mask.dim()).expand_as(loc_data) loc_p = loc_data[pos_idx].view(-1, 4) loc_t = loc_t[pos_idx].view(-1, 4) loss_l = F.smooth_l1_loss(loc_p, loc_t, reduction='sum') # loss of class prediction batch_conf = conf_data.view(-1, num_classes) loss_c = F.cross_entropy(batch_conf, conf_t_label.view(-1), reduction='none') num_pos = pos_mask.long().sum(1, keepdim=True) loss_c = loss_c.view(num_batch, -1) loss_c[pos_mask] = 0 _, loss_idx = loss_c.sort(1, descending=True) _, idx_rank = loss_idx.sort(1) 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') N = num_pos.sum().float() loss_l /= N loss_c /= N return loss_l, loss_c
def test_match(self): self.maxDiff = None cases: List[MatchTestCase] = [ { "name": "empty inputs", "scofo_output": [], "ref": [], "want": { "miss_rate": 0.0, "misalign_rate": 0.0, "piece_completion": 0.0, "std_of_error": 0.0, "mean_absolute_error": 0.0, "std_of_latency": 0.0, "mean_latency": 0.0, "std_of_offset": 0.0, "mean_absolute_offset": 0.0, "miss_num": 0, "misalign_num": 0, "total_num": 0, "precision_rate": 1.0, }, }, { "name": "empty scofo_output", "scofo_output": [], "ref": [ { "tru_time": 1, "note_start": 2, "midi_note_num": 42, }, { "tru_time": 3, "note_start": 4, "midi_note_num": 42, }, ], "want": { "miss_rate": 1.0, "misalign_rate": 0.0, "piece_completion": 0.0, "std_of_error": 0.0, "mean_absolute_error": 0.0, "std_of_latency": 0.0, "mean_latency": 0.0, "std_of_offset": 0.0, "mean_absolute_offset": 0.0, "miss_num": 2, "misalign_num": 0, "total_num": 2, "precision_rate": 0.0, }, }, { "name": "empty ref", "scofo_output": [ { "est_time": 12, "det_time": 13, "note_start": 14, "midi_note_num": 42, }, { "est_time": 13, "det_time": 14, "note_start": 15, "midi_note_num": 43, }, ], "ref": [], "want": { "miss_rate": 0.0, "misalign_rate": 0.0, "piece_completion": 0.0, "std_of_error": 0.0, "mean_absolute_error": 0.0, "std_of_latency": 0.0, "mean_latency": 0.0, "std_of_offset": 0.0, "mean_absolute_offset": 0.0, "miss_num": 0, "misalign_num": 0, "total_num": 0, "precision_rate": 1.0, }, }, { "name": "missed all", "scofo_output": [ { "est_time": 12, "det_time": 13, "note_start": 14, "midi_note_num": 42, }, { "est_time": 13, "det_time": 14, "note_start": 15, "midi_note_num": 43, }, ], "ref": [ { "tru_time": 1, "note_start": 2, "midi_note_num": 42, }, { "tru_time": 3, "note_start": 4, "midi_note_num": 42, }, ], "want": { "miss_rate": 1.0, "misalign_rate": 0.0, "piece_completion": 0.0, "std_of_error": 0.0, "mean_absolute_error": 0.0, "std_of_latency": 0.0, "mean_latency": 0.0, "std_of_offset": 0.0, "mean_absolute_offset": 0.0, "miss_num": 2, "misalign_num": 0, "total_num": 2, "precision_rate": 0.0, }, }, { "name": "misaligned all", "scofo_output": [ { "est_time": 1000, "det_time": 1000, "note_start": 2, "midi_note_num": 42, }, { "est_time": 1000, "det_time": 1000, "note_start": 4, "midi_note_num": 42, }, ], "ref": [ { "tru_time": 1, "note_start": 2, "midi_note_num": 42, }, { "tru_time": 3, "note_start": 4, "midi_note_num": 42, }, ], "want": { "miss_rate": 0.0, "misalign_rate": 1.0, "piece_completion": 0.0, "std_of_error": 0.0, "mean_absolute_error": 0.0, "std_of_latency": 0.0, "mean_latency": 0.0, "std_of_offset": 0.0, "mean_absolute_offset": 0.0, "miss_num": 0, "misalign_num": 2, "total_num": 2, "precision_rate": 0.0, }, }, { "name": "aligned all", "scofo_output": [ { "est_time": 1.1, "det_time": 1.2, "note_start": 2, "midi_note_num": 42, }, { "est_time": 3.1, "det_time": 3.2, "note_start": 4, "midi_note_num": 42, }, ], "ref": [ { "tru_time": 1, "note_start": 2, "midi_note_num": 42, }, { "tru_time": 3, "note_start": 4, "midi_note_num": 42, }, ], "want": { "miss_rate": 0.0, "misalign_rate": 0.0, "piece_completion": 1.0, "std_of_error": 0.0, "mean_absolute_error": 0.1, "std_of_latency": 0.0, "mean_latency": 0.1, "std_of_offset": 0.0, "mean_absolute_offset": 0.2, "miss_num": 0, "misalign_num": 0, "total_num": 2, "precision_rate": 1.0, }, }, { "name": "aligned some", "scofo_output": [ { "est_time": 1.1, "det_time": 1.2, "note_start": 2, "midi_note_num": 42, }, { "est_time": 3.1, "det_time": 3.2, "note_start": 4, "midi_note_num": 42, }, { "est_time": 1000, "det_time": 1000, "note_start": 6, "midi_note_num": 42, }, ], "ref": [ { "tru_time": 1, "note_start": 2, "midi_note_num": 42, }, { "tru_time": 3, "note_start": 4, "midi_note_num": 42, }, { "tru_time": 5, "note_start": 6, "midi_note_num": 42, }, ], "want": { "miss_rate": 0.0, "misalign_rate": 0.3333333333333333, "piece_completion": 0.6666666666666666, "std_of_error": 0.0, "mean_absolute_error": 0.1, "std_of_latency": 0.0, "mean_latency": 0.1, "std_of_offset": 0.0, "mean_absolute_offset": 0.2, "miss_num": 0, "misalign_num": 1, "total_num": 3, "precision_rate": 0.6666666666666666, }, }, { "name": "missed some", "scofo_output": [ { "est_time": 1.1, "det_time": 1.2, "note_start": 2, "midi_note_num": 42, }, { "est_time": 3.1, "det_time": 3.2, "note_start": 4, "midi_note_num": 42, }, ], "ref": [ { "tru_time": 1, "note_start": 2, "midi_note_num": 42, }, { "tru_time": 3, "note_start": 4, "midi_note_num": 42, }, { "tru_time": 5, "note_start": 6, "midi_note_num": 42, }, ], "want": { "miss_rate": 0.3333333333333333, "misalign_rate": 0.0, "piece_completion": 0.6666666666666666, "std_of_error": 0.0, "mean_absolute_error": 0.1, "std_of_latency": 0.0, "mean_latency": 0.1, "std_of_offset": 0.0, "mean_absolute_offset": 0.2, "miss_num": 1, "misalign_num": 0, "total_num": 3, "precision_rate": 0.6666666666666666, }, }, { "name": "aligned ahead of time", "scofo_output": [ { "est_time": 0.9, "det_time": 0.95, "note_start": 2, "midi_note_num": 42, }, { "est_time": 2.9, "det_time": 2.95, "note_start": 4, "midi_note_num": 42, }, ], "ref": [ { "tru_time": 1, "note_start": 2, "midi_note_num": 42, }, { "tru_time": 3, "note_start": 4, "midi_note_num": 42, }, ], "want": { "miss_rate": 0.0, "misalign_rate": 0.0, "piece_completion": 1.0, "std_of_error": 0.0, "mean_absolute_error": 0.1, "std_of_latency": 0.0, "mean_latency": 0.05, "std_of_offset": 0.0, "mean_absolute_offset": 0.05, "miss_num": 0, "misalign_num": 0, "total_num": 2, "precision_rate": 1.0, }, }, { "name": "test errors", "scofo_output": [ { "est_time": 1.5, "det_time": 1.7, "note_start": 2, "midi_note_num": 42, }, { "est_time": 3.1, "det_time": 3.2, "note_start": 4, "midi_note_num": 42, }, ], "ref": [ { "tru_time": 1, "note_start": 2, "midi_note_num": 42, }, { "tru_time": 3, "note_start": 4, "midi_note_num": 42, }, ], "want": { "miss_rate": 0.0, "misalign_rate": 0.0, "piece_completion": 1.0, "std_of_error": 0.2, "mean_absolute_error": 0.3, "std_of_latency": 0.05, "mean_latency": 0.15, "std_of_offset": 0.25, "mean_absolute_offset": 0.45, "miss_num": 0, "misalign_num": 0, "total_num": 2, "precision_rate": 1.0, }, }, { "name": "aligned all (within 1ms bound in note_start)", "scofo_output": [ { "est_time": 1.1, "det_time": 1.2, "note_start": 1, "midi_note_num": 42, }, { "est_time": 3.1, "det_time": 3.2, "note_start": 5, "midi_note_num": 42, }, ], "ref": [ { "tru_time": 1, "note_start": 2, "midi_note_num": 42, }, { "tru_time": 3, "note_start": 4, "midi_note_num": 42, }, ], "want": { "miss_rate": 0.0, "misalign_rate": 0.0, "piece_completion": 1.0, "std_of_error": 0.0, "mean_absolute_error": 0.1, "std_of_latency": 0.0, "mean_latency": 0.1, "std_of_offset": 0.0, "mean_absolute_offset": 0.2, "miss_num": 0, "misalign_num": 0, "total_num": 2, "precision_rate": 1.0, }, }, ] for tc in cases: got = match(tc["scofo_output"], tc["ref"]) want = tc["want"] for key, val in got.items(): self.assertAlmostEqual(want[key], val, 5, f'{tc["name"]}: {key}') # type: ignore
def bach10(): import re import json from typing import Tuple, List from midi import process_midi from align import ASMAligner, alignment_repr from utils.sharedtypes import Alignment, NoteInfo, FollowerOutputLine from utils.eprint import eprint from utils.processfile import process_ref_file from utils.match import match BACH10_PATH = os.path.join(DATA_PATH, "Bach10_v1.1") OUTPUT_PATH = os.path.join(REPRO_RESULTS_PATH, "bach10") os.makedirs(OUTPUT_PATH, exist_ok=True) BACH10_PIECE_PATHS = [ f.path for f in os.scandir(BACH10_PATH) if f.is_dir() and bool(re.search(r"^[0-9]{2}-\w+$", os.path.basename(f.path))) ] BACH10_PIECE_BASENAMES = [os.path.basename(x) for x in BACH10_PIECE_PATHS] class Bach10Piece: def __init__(self, name: str, postalignthres: float): self.name = name self.dirpath = os.path.join(BACH10_PATH, name) self.refalignpath = os.path.join(self.dirpath, f"{self.name}.txt") self.rscorepath = os.path.join(self.dirpath, f"{self.name}.mid") self.pscore: List[NoteInfo] = self._refalign_to_pscore() self.rscore: List[NoteInfo] = process_midi(self.rscorepath) self.postalignthres = postalignthres def align(self) -> Alignment: """ align and return (alignment) """ eprint(f"Aligning {self.name}") aligner = ASMAligner(self.pscore, self.rscore, self.postalignthres) alignment = aligner.get_alignment() return alignment def _refalign_to_pscore(self) -> List[NoteInfo]: f = open(self.refalignpath) t = f.read().strip() f.close() def process_line(line: str) -> NoteInfo: ls = line.split() if len(ls) < 4: raise ValueError(f"Too few entries on line: {line}") # (performance time (ms), MIDI note num) return { "note_start": float(ls[0]), "midi_note_num": int(ls[2]) } return list(map(process_line, t.splitlines())) def align_piece(name: str, postalignthres: float) -> Alignment: p = Bach10Piece(name, postalignthres) return p.align() def alignment_to_follower_output( alignment: Alignment) -> List[FollowerOutputLine]: return [{ "est_time": n["p"]["note_start"], "det_time": n["p"]["note_start"], "note_start": n["s"]["note_start"], "midi_note_num": n["p"]["midi_note_num"], } for n in alignment if n["p"] is not None and n["s"] is not None and n["p"]["midi_note_num"] == n["s"]["midi_note_num"]] postalignthres: float = -1 # not needed alignments: List[Tuple[str, Alignment]] = [(name, align_piece(name, postalignthres)) for name in BACH10_PIECE_BASENAMES] for name, alignment in alignments: out_path = os.path.join(OUTPUT_PATH, name) os.makedirs(out_path, exist_ok=True) stat_file_path = os.path.join(out_path, f"{name}.align.stat") align_file_path = os.path.join(out_path, f"{name}.align.txt") sf = open(stat_file_path, "w") af = open(align_file_path, "w") stdout, stderr = alignment_repr(alignment) sf.write(stderr) af.write(stdout) sf.close() af.close() follower_output = alignment_to_follower_output(alignment) ref_contents = process_ref_file( os.path.join(BACH10_PATH, name, f"{name}.txt")) res = match(follower_output, ref_contents) res_str = json.dumps(res, indent=4) res_file_path = os.path.join(out_path, f"{name}.scofo.json") rf = open(res_file_path, "w") rf.write(res_str) rf.close() print(f"OUTPUT: {OUTPUT_PATH}")
def forward(self, predictions, targets): """ 損失関数の計算。 Parameters ---------- predictions : SSD netの訓練時の出力(tuple) (loc=torch.Size([num_batch, 8732, 4]), conf=torch.Size([num_batch, 8732, 21]), dbox_list=torch.Size [8732,4])。 targets : [num_batch, num_objs, 5] 5は正解のアノテーション情報[xmin, ymin, xmax, ymax, label_ind]を示す Returns ------- loss_l : テンソル locの損失の値 loss_c : テンソル confの損失の値 """ # SSDモデルの出力がタプルになっているので、個々にばらす loc_data, conf_data, dbox_list = predictions # 要素数を把握 num_batch = loc_data.size(0) # ミニバッチのサイズ num_dbox = loc_data.size(1) # DBoxの数 = 8732 num_classes = conf_data.size(2) # クラス数 = 21 # 損失の計算に使用するものを格納する変数を作成 # conf_t_label:各DBoxに一番近い正解のBBoxのラベルを格納させる # loc_t:各DBoxに一番近い正解のBBoxの位置情報を格納させる conf_t_label = torch.LongTensor(num_batch, num_dbox).to(self.device) loc_t = torch.Tensor(num_batch, num_dbox, 4).to(self.device) # loc_tとconf_t_labelに、 # DBoxと正解アノテーションtargetsをmatchさせた結果を上書きする for idx in range(num_batch): # ミニバッチでループ # 現在のミニバッチの正解アノテーションのBBoxとラベルを取得 truths = targets[idx][:, :-1].to(self.device) # BBox # ラベル [物体1のラベル, 物体2のラベル, …] labels = targets[idx][:, -1].to(self.device) # デフォルトボックスを新たな変数で用意 dbox = dbox_list.to(self.device) # 関数matchを実行し、loc_tとconf_t_labelの内容を更新する # (詳細) # loc_t:各DBoxに一番近い正解のBBoxの位置情報が上書きされる # conf_t_label:各DBoxに一番近いBBoxのラベルが上書きされる # ただし、一番近いBBoxとのjaccard overlapが0.5より小さい場合は # 正解BBoxのラベルconf_t_labelは背景クラスの0とする variance = [0.1, 0.2] # このvarianceはDBoxからBBoxに補正計算する際に使用する式の係数です match(self.jaccard_thresh, truths, dbox, variance, labels, loc_t, conf_t_label, idx) # ---------- # 位置の損失:loss_lを計算 # Smooth L1関数で損失を計算する。ただし、物体を発見したDBoxのオフセットのみを計算する # ---------- # 物体を検出したBBoxを取り出すマスクを作成 pos_mask = conf_t_label > 0 # torch.Size([num_batch, 8732]) # pos_maskをloc_dataのサイズに変形 pos_idx = pos_mask.unsqueeze(pos_mask.dim()).expand_as(loc_data) # Positive DBoxのloc_dataと、教師データloc_tを取得 loc_p = loc_data[pos_idx].view(-1, 4) loc_t = loc_t[pos_idx].view(-1, 4) # 物体を発見したPositive DBoxのオフセット情報loc_tの損失(誤差)を計算 loss_l = F.smooth_l1_loss(loc_p, loc_t, reduction='sum') # ---------- # クラス予測の損失:loss_cを計算 # 交差エントロピー誤差関数で損失を計算する。ただし、背景クラスが正解であるDBoxが圧倒的に多いので、 # Hard Negative Miningを実施し、物体発見DBoxと背景クラスDBoxの比が1:3になるようにする。 # そこで背景クラスDBoxと予想したもののうち、損失が小さいものは、クラス予測の損失から除く # ---------- batch_conf = conf_data.view(-1, num_classes) # クラス予測の損失を関数を計算(reduction='none'にして、和をとらず、次元をつぶさない) if not self.floss: loss_c = F.cross_entropy( batch_conf, conf_t_label.view(-1), reduction='none') 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 # 物体を発見したDBoxは損失0とする # Hard Negative Miningを実施する # 各DBoxの損失の大きさloss_cの順位であるidx_rankを求める _, loss_idx = loss_c.sort(1, descending=True) _, idx_rank = loss_idx.sort(1) num_neg = torch.clamp(num_pos*self.negpos_ratio, max=num_dbox) # idx_rankは各DBoxの損失の大きさが上から何番目なのかが入っている # 背景のDBoxの数num_negよりも、順位が低い(すなわち損失が大きい)DBoxを取るマスク作成 # torch.Size([num_batch, 8732]) 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_dataからposとnegだけを取り出してconf_hnmにする。形はtorch.Size([num_pos+num_neg, 21]) 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)] # confidenceの損失関数を計算(要素の合計=sumを求める) if not self.floss: loss_c = F.cross_entropy(conf_hnm, conf_t_label_hnm, reduction='sum') else: loss_c = self.focal(conf_hnm, conf_t_label_hnm) # 物体を発見したBBoxの数N(全ミニバッチの合計)で損失を割り算 N = num_pos.sum() loss_l /= N loss_c /= N else: loss_c = self.focal(batch_conf, conf_t_label.view(-1)) return loss_l, loss_c