Chainer に IoU評価関数 を追加する(Evaluatorを自作)

最終更新日

Comments: 0

更新履歴

  • 2018/11/17 集計時に誤差が発生するバグを修正しました

概要

Signate では機械学習についてコンペが開催されており、
私が参加しているコンペには、評価関数「IOU」を使用してランクが決定する事が多いので学習中にも以下のように iou の結果が必要になったため実装してみました。

epoch       iou           main/loss   main/accuracy  ...
1           0             0.042866    0.993138       ...
2           0.000329707   0.0347965   0.993241       ...
3           0.00700626    0.0307309   0.993857       ...

上記を実装する上で、Chainerのデータ構造・機械学習(深層学習)の理解を深める事および、学習済みモデルの唯一の評価方法として有用と考えております。
この実装が皆様のお役に立てれば幸いです。

ちなみにお恥ずかしい話ではありますが、Signate は全然勝てておりません(涙)

IoUとは

https://signate.jp/competitions/110#evaluation

Evaluatorとは

DNNの訓練中にモデルの訓練が意図通りに進んでいるかを評価するための機構となります。
また、モデルとは関係無い値などもログ(レポート)として出力できるため、そのような用途にも使用する事ができます。

  • 例)
    • 独自の評価方法を実装する
    • 現在時間を出力する
    • クラス毎の結果を出力する(他クラス分類)

ソースコード

Evaluatorを読み解く

ソースコード全体は Qiita にて公開しておりますのでこちらをご参照ください。

呼び出し側

trainer.extend(IouEvaluator(test_iter, model, device=gpu_id))

呼び出される側

  • 呼び出し側から受け取った値、test_iter、model を以下のようにして受け取ります。
    def evaluate(self):
        iterator = self._iterators['main']
        model = self._targets['main']
        eval_func = self.eval_func or model
  • 独自評価を行ったら最終的にはこの summary に格納します
        summary = reporter_module.DictSummary()
  • it はミニバッチの単位にデータを分割して渡してくれます。(バッチサイズ=64の場合、batch変数にはリスト型で(img, list)[64] でデータが格納されます)
        for batch in it:
            observation = {}

            (中略)

        summary.add(observation)
  • batch変数を扱いやすいように 加工した値を in_arrays変数に格納します。(in_arrays変数には、タプル型で(img[64], label[64])のような形でデータが格納されます)
                in_arrays = self.converter(batch, self.device)
  • in_arrays変数の方を判別して、eval_func/self.iou(今回追加した関数)を実行します。
  • 各バッチ単位での集計結果(and_count/or_count)を加算する
            with reporter_module.report_scope(observation):
                in_arrays = self.converter(batch, self.device)
                with function.no_backprop_mode():
                    if isinstance(in_arrays, tuple):
                        eval_func(*in_arrays)
                        ac, oc = self.iou(in_arrays)
                    elif isinstance(in_arrays, dict):
                        eval_func(**in_arrays)
                        ac, oc = self.iou(in_arrays)
                    else:
                        eval_func(in_arrays)
                        ac, oc = self.iou(in_arrays)
                    and_count = and_count + ac
                    or_count = or_count + oc
  • 学習中のモデル(model)を取得します。
  • テストデータの正解ラベル(labels)を取得します。
  • 学習結果の推定ラベル(y)を取得します。
    def iou(self, in_arrays):
        model = self._targets['main']

        _, labels = in_arrays
        if self.device >= 0:
            labels = chainer.cuda.to_cpu(labels)

        y = model.y.data
        if self.device >= 0:
            y = chainer.cuda.to_cpu(y)
        # print(y)
        y = y.argmax(axis=1)

        # print('labels', labels)
        # print('predct', y)
        and_count = (labels & y).sum()
        or_count = (labels | y).sum()
        return and_count, or_count
  • 上記で作成した iou の結果を格納する。(データ数/200でミニバッチ/64の場合、4ループ分のデータがここに格納される)
        iou_observation = {}
        if(or_count == 0):
            iou_observation['iou'] = 0.
        else:
            iou_observation['iou'] = float(and_count) / or_count
        summary.add(observation)
  • 上記で作成された4ループ分のデータをここで計算するし結果を返す。(4ループ分の平均を計算)
        return summary.compute_mean()

ソースコード

ソースコード全体は Qiita にて公開しておりますのでこちらをご参照ください。

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

コメントする