def call(self, inputs): features = inputs[0] rois = inputs[1] n_roi_boxes = K.shape(rois)[1] # roisには[0,0,0,0]のRoIも含むが、バッチ毎の要素数を合わせるため、そのまま処理する。 # crop_and_resizeの準備 # roisを0軸目を除き(バッチを示す次元を除き)、フラットにする。 roi_unstack = K.concatenate(tf.unstack(rois), axis=0) # roi_unstackの各roiに対応するバッチを指すindex batch_pos = K.flatten( K.repeat(K.reshape(K.arange(self.batch_size), [-1, 1]), n_roi_boxes)) # RoiAlignの代わりにcrop_and_resizeを利用。 # crop_and_resize内部でbilinear interporlationしてようなので、アルゴリズム的には同じっぽい crop_boxes = tf.image.crop_and_resize(features, roi_unstack, batch_pos, self.out_shape) # (N * n_rois, out_size, out_size, channels) # から # (N, n_rois, out_size, out_size, channels) # へ変換 crop_boxes = K.reshape(crop_boxes, [self.batch_size, n_roi_boxes] + self.out_shape + [-1]) log.tfprint(crop_boxes, "crop_boxes: ") return crop_boxes
def head_labels_loss(gt, pred): """ヘッドのラベル分類の損失関数 gt: 正解 [N, R] 2軸目はラベルを示すID pred: 予測値(softmax済み) [N, R, labels]. """ gt = log.tfprint(gt, "head_labels_loss_val:gt", summarize=1024) pred = log.tfprint(pred, "head_labels_loss_val:pred", summarize=1024) loss = labels_loss(gt, pred) loss = log.tfprint(loss, "head_labels_loss") return loss
def head_mask_loss(gt_masks, gt_labels, pred_masks): """マスクの損失関数 gt_masks: 正解データ。 マスクデータをbboxの領域のみ切り抜いてconfig.mask_out_shapeにリサイズしたデータ。 [N, R, h, w] バイナリマスク gt_labels: 正解データのラベルID [N, R] pred_masks: 予測値 バイナリマスク [N, R, n_labels h, w] ※h, w は config.mask_out_shape になる。 """ # Positiveなラベルが付与されているRoIのみ評価対象とする pos_idx = tf.where(gt_labels > 0) i = K.cast(pos_idx[:, 0], tf.int32) j = K.cast(pos_idx[:, 1], tf.int32) k = K.cast(tf.gather_nd(gt_labels, pos_idx), tf.int32) # i = log.tfprint(i, "i:head_mask_loss") # j = log.tfprint(j, "j:head_mask_loss") # k = log.tfprint(k, "k:head_mask_loss") pos_pred_idx = K.stack((i, j, k), axis=1) # pos_pred_idx = log.tfprint(pos_pred_idx, "pos_pred_idx:head_mask_loss") pred_masks = tf.gather_nd(pred_masks, pos_pred_idx) gt_masks = tf.gather_nd(gt_masks, pos_idx) loss = K.switch(tf.size(gt_masks) > 0, K.binary_crossentropy(gt_masks, pred_masks), tf.constant(0.0)) loss = K.mean(loss) loss = log.tfprint(loss, "head_mask_loss") return loss
def rpn_objects_loss(gt, pred): """RPNのオブジェクト/非オブジェクト分類の損失関数 gt: 正解 [N, anchors] 2軸目の値は以下の通り。 positive=1, negative=0, neutral(exclude from eval)=-1 pred: 予測値(softmax済み) [batch, anchors, 2]. 3軸目はオブジェクトor非オブジェクトを示す数値。 """ # 評価対象外の−1に該当する要素を除く indices = tf.where(gt > -1) # print("indicies", indices) # print("pred", pred) gt = tf.gather_nd(gt, indices) pred = tf.gather_nd(pred, indices) # 交差エントロピー誤差 # バッチ毎の計算ではなく、全体の平均値でOK。 # 論文に以下の記載がある。 # In our current implementation (as in the released code), # the cls term in Eqn.(1) is normalized by the mini-batch size # (i.e., Ncls = 256) and the reg term is normalized by the number of # anchor locations (i.e., Nreg ∼ 2, 400). loss = labels_loss(gt, pred) loss = log.tfprint(loss, "rpn_objects_loss") return loss
def head_offsets_loss(gt_offsets, gt_labels, pred_offsets): """ヘッドのオフセット回帰の損失関数 positive(gt_fg > 0)データのみ評価対象とする gt_offsets: 正解オフセット [N, R, 4] gt_labels: 正解データのラベルID [N, R] pred_offsets: ラベル毎の予測値 [N, R, n_labels, 4]. """ # 正解データのラベルIDに対応するオフセットのみを損失評価対象とする。 # 論文には以下のようにあるので、正解ラベルのBBoxのみで良さそう。 # The second task loss, Lloc, is defined over a tuple of true bounding-box # regression targets for class u, v = (vx, vy, vw, vh), and a predicted # tuple tu = (tux , tuy , tuw, tuh ), again for class u. pos_idx = tf.where(gt_labels > 0) i = K.cast(pos_idx[:, 0], tf.int32) j = K.cast(pos_idx[:, 1], tf.int32) k = K.cast(tf.gather_nd(gt_labels, pos_idx), tf.int32) pos_pred_idx = K.stack((i, j, k), axis=1) pred_offsets = tf.gather_nd(pred_offsets, pos_pred_idx) gt_offsets = tf.gather_nd(gt_offsets, pos_idx) loss = offsets_loss(gt_offsets, pred_offsets) loss = log.tfprint(loss, "head_offsets_loss") return loss
def sparse_categorical_crossentropy(gt_ids, pred_one_hot_post_softmax): """ K.sparse_categorical_crossentropyだと結果がNaNになる。。。 0割り算が発生しているかも。 https://qiita.com/4Ui_iUrz1/items/35a8089ab0ebc98061c1 対策として、微少値を用いてlog(0)にならないよう調整した本関数を作成。 """ gt_ids = log.tfprint(gt_ids, "cross:gt_ids:") pred_one_hot_post_softmax = log.tfprint(pred_one_hot_post_softmax, "cross:pred_one_hot_post_softmax:") gt_one_hot = K.one_hot(gt_ids, K.shape(pred_one_hot_post_softmax)[-1]) gt_one_hot = log.tfprint(gt_one_hot, "cross:gt_one_hot:") epsilon = K.epsilon() # 1e-07 loss = -K.sum( gt_one_hot * K.log( tf.clip_by_value(pred_one_hot_post_softmax, epsilon, 1 - epsilon)), axis=-1) loss = log.tfprint(loss, "cross:loss:") return loss
def rpn_offsets_loss(gt_offsets, gt_fg, pred_offsets): """RPNのオフセット回帰の損失関数 positive(gt_fg > 0)データのみ評価対象とする gt_offsets: 正解オフセット [N, R, 4] 3軸目は領域提案とアンカーのオフセット(中心、幅、高さ)。 (tx, ty, th, tw) gt_fg: 正解データの前景/背景 [N, R] pred_offsets: 予測値 [N, R, 4]. """ pos_idx = tf.where(gt_fg > 0) gt_offsets = tf.gather_nd(gt_offsets, pos_idx) pred_offsets = tf.gather_nd(pred_offsets, pos_idx) # FasterRCNNの論文上は、RPNのオフセット回帰には係数10を乗ずることでオブジェクト分類損失とのバランスを取ることになっている。 # が、rpnの損失の全損失に占める割合が高すぎるようなら係数調整 p = 1. loss = p * offsets_loss(gt_offsets, pred_offsets) loss = log.tfprint(loss, "rpn_offsets_loss") return loss
def _subsampling(self, normalized_rois, gt_bboxes, gt_labels, pos_iou_thresh=0.5, exclusive_iou_tresh=0.1, pos_ratio=0.25): """正解データとのIoUを基にRoIをサンプリングする。 IoUがpos_iou_thresh以上であるRoIをオブジェクトとみなす。 オブジェクトはサンプルの25%以内とする。(n_samples_per_batch * pos_ratio 以内) pos_iou_thresh未満、exclusive_iou_thresh以上は非オブジェクトとみなす。 exclusive_iou_thresh未満は偶然の一致であり意味なし(難解)なので無視。 ※論文ではheuristic for hard example mining.と記載されている点。 バッチ毎のサンプル数はn_samples_per_batch以内とする。 (n_samples_per_batch未満の場合は、n_samples_per_batchになるよう0パディングする。) 上記のサンプリングに対応する正解データのラベル、また、BBoxとのオフセットも得る。 Args: normalized_rois (tensor) : RegionProposalLayerで得られたRoI。 (N, n_rois, 4) 3軸目は領域の左上と右下の座標が0〜1に正規化された値。 入力画像サイズの高さ、幅で除算することで正規化された値。 (y1, x1, y2, x2) gt_bboxes (ndarray) : 正解BBox。 (N, config.n_max_gt_objects_per_image, 4) 座標は正規化されていない。 gt_labels (ndarray) : 正解ラベル。 (N, config.n_max_gt_objects_per_image) ==0:背景データ >=1:オブジェクト Returns: sample_rois (tensor): サンプリングしたRoI。 (N, n_samples_per_batch, 4) 3軸目の座標は0〜1に正規化された値。 sample_gt_offset (tensor): サンプリングしたRoIに対応するBBoxとのオフセット。 (N, n_samples_per_batch, 4) 3軸目の座標は0〜1に正規化された値をself.config.bbox_refinement_stdで割ることで標準化した値。 sample_gt_labels (tensor): サンプリングしたRoIに対応するBBoxのラベル。 (N, n_samples_per_batch) """ pos_roi_per_batch = round(self.n_samples_per_batch * pos_ratio) # gt_bboxesをnormalized_roisに合わせて正規化する。 # これでIoUが評価出来るようになる。 input_h = self.config.image_shape[0] input_w = self.config.image_shape[1] normalized_gt_bboxes = bbox.normalize_bbox(gt_bboxes, input_h, input_w) # 入力をバッチ毎に分割 normalized_rois = tf.split(normalized_rois, self.config.batch_size) normalized_gt_bboxes = tf.split(normalized_gt_bboxes, self.config.batch_size) gt_labels = tf.split(gt_labels, self.config.batch_size) sample_rois = [] sample_gt_offsets = [] sample_gt_labels = [] for roi, gt_bbox, gt_label in zip(normalized_rois, normalized_gt_bboxes, gt_labels): # 0次元目(バッチサイズ)は不要なので削除 roi = log.tfprint(roi, "roi: ") gt_bbox = log.tfprint(gt_bbox, "gt_bbox: ") gt_label = log.tfprint(gt_label, "gt_label: ") roi = K.squeeze(roi, 0) gt_bbox = K.squeeze(gt_bbox, 0) gt_label = K.squeeze(gt_label, 0) roi = log.tfprint(roi, "roi_squeezed: ") gt_bbox = log.tfprint(gt_bbox, "gt_bbox_squeezed: ") gt_label = log.tfprint(gt_label, "gt_label_squeezed: ") # ゼロパディング行を除外 # K.gather(zero, K.squeeze(tf.where(K.any(zero, axis=1)), -1) ) idx_roi_row = K.flatten(tf.where(K.any(roi, axis=1))) idx_gt_bbox = K.flatten(tf.where(K.any(gt_bbox, axis=1))) roi = K.gather(roi, idx_roi_row) # gt_bboxとgt_labelは行数と行の並びが同じなので同じidxを利用できる gt_bbox = K.gather(gt_bbox, idx_gt_bbox) gt_label = K.gather(gt_label, idx_gt_bbox) gt_bbox = log.tfprint(gt_bbox, "gt_bbox_gathered: ") gt_label = log.tfprint(gt_label, "gt_label_gathered: ") # IoUを求める。 # (n_rois, ) ious = bbox.get_iou_K(roi, gt_bbox) ious = log.tfprint(ious, "ious: ") # 各RoI毎にIoU最大のBBoxの位置を得る idx_max_gt = K.argmax(ious, axis=1) idx_max_gt = log.tfprint(idx_max_gt, "idx_max_gt: ") max_iou = K.max(ious, axis=1) # max_iouの行数はroiと同じになる max_iou = log.tfprint(max_iou, "max_iou: ") idx_pos = K.flatten(tf.where(max_iou >= pos_iou_thresh)) # positiveサンプル数をpos_roi_per_batch以内に制限 limit_pos = K.minimum(pos_roi_per_batch, K.shape(idx_pos)[0]) idx_pos = K.switch( K.shape(idx_pos)[0] > 0, tf.random_shuffle(idx_pos)[:limit_pos], idx_pos) limit_pos = log.tfprint(limit_pos, "limit_pos: ") idx_pos = log.tfprint(idx_pos, "idx_pos: ") # negativeサンプル数を # n_samples_per_batch - pos_roi_per_batch # に制限 idx_neg = K.flatten( tf.where((max_iou < pos_iou_thresh) & (max_iou >= exclusive_iou_tresh))) # negativeサンプル数は pos_roi_per_batch - limit_pos(つまり残り) 以内に制限 limit_neg = self.n_samples_per_batch - limit_pos limit_neg = K.minimum(limit_neg, K.shape(idx_neg)[0]) idx_neg = K.switch( K.shape(idx_neg)[0] > 0, tf.random_shuffle(idx_neg)[:limit_neg], idx_neg) limit_neg = log.tfprint(limit_neg, "limit_neg: ") idx_neg = log.tfprint(idx_neg, "idx_neg: ") # 返却するサンプルを抽出 # GTのoffsets, labelsは各roisに対応させる。つまり、同じ位置に格納する。 idx_keep = K.concatenate((idx_pos, idx_neg)) idx_keep = log.tfprint(idx_keep, "idx_keep: ") # 各RoIの最大IoUを示すIndexについても、上記返却するサンプルのみを残す。 idx_gt_keep = K.gather(idx_max_gt, idx_keep) # IoUが閾値以上のPositiveとみなされるサンプルのみを残すためのIndex。 idx_gt_keep_pos = K.gather(idx_max_gt, idx_pos) idx_gt_keep = log.tfprint(idx_gt_keep, "idx_gt_keep: ") sample_roi = K.gather(roi, idx_keep) sample_gt_offset = bbox.get_offset_K( sample_roi, K.gather(gt_bbox, idx_gt_keep)) # negativeな要素には0を設定 sample_gt_label = K.concatenate(( K.cast(K.gather(gt_label, idx_gt_keep_pos), dtype='int32'), K.zeros( [limit_neg], # K.zerosは0階テンソルを受け付けないので配列化。。。 dtype='int32'))) # 行数がn_samples_per_batch未満の場合は0パディング remain = tf.maximum( self.n_samples_per_batch - tf.shape(sample_roi)[0], 0) sample_roi = tf.pad(sample_roi, [(0, remain), (0, 0)], name='subsample_sample_roi') sample_gt_offset = tf.pad(sample_gt_offset, [(0, remain), (0, 0)], name='subsample_sample_gt_offset') sample_gt_offset /= self.config.bbox_refinement_std sample_gt_label = tf.pad(sample_gt_label, [(0, remain)], name='subsample_sample_gt_label') sample_roi = log.tfprint(sample_roi, "sample_roi: ") sample_gt_offset = log.tfprint(sample_gt_offset, "sample_gt_offset: ") sample_gt_label = log.tfprint(sample_gt_label, "sample_gt_label: ") sample_rois.append(sample_roi) sample_gt_offsets.append(sample_gt_offset) sample_gt_labels.append(sample_gt_label) return [ K.stack(sample_rois), K.stack(sample_gt_offsets), K.stack(sample_gt_labels) ]