【Detectron2を掘り下げる】学習中にval datasetに対する評価指標を計算・ロギングする

Detectron2は物体検出やセグメンテーションをする上で色々と便利な機能が簡単に実装できるライブラリです。
ただ、使ってみるとモデルを構築する上で提供されていないものがチラホラあるので、掘り下げて実装してみました。

前回は検証用（val）データセットに対してのlossを計算、ログを取る機能を実装しました。
今回はvalデータセットに対してAverage Precisionなどのその他の評価指標を計算、ログを取る機能を実装しました。また、データセットのフォーマットはCOCO形式のものを使用しています。

前回の記事
[【Detectron2を掘り下げる】val lossを計算・ロギングする](https://qiita.com/insilicomab/items/42fe790939c97bc0a471)

# 環境

- Python 3.10.13
- CUDA 11.7
- pytorch==1.13.1
- detectron2==0.6

# 実装
### 1. configを設定する

```python
from detectron2.config import get_cfg
 
cfg = get_cfg()
cfg.DATASETS.TEST = ("your_validation_dataset",)
cfg.TEST.EVAL_PERIOD = 100
```

この例では100回のイテレーション後に1回、検証用データセットに対して評価が実施されます。
`TEST.EVAL_PERIOD`を設定すると、検証用データセット全体を使用してevaluatorが呼び出され、結果がストレージに書き込まれます。

### 2. Evaluatorを実装する

以下の公式githubのURLの`build_evaluator`を丸パクリ実装します。

https://github.com/facebookresearch/detectron2/blob/main/tools/train_net.py

`trainer`に`build_evaluator`を定義してください。

```python
class Trainer(DefaultTrainer):
    ...

    @classmethod
    def build_evaluator(cls, cfg, dataset_name, output_folder=None):
        """
        Create evaluator(s) for a given dataset.
        This uses the special metadata "evaluator_type" associated with each builtin dataset.
        For your own dataset, you can simply create an evaluator manually in your
        script and do not have to worry about the hacky if-else logic here.
        """
        if output_folder is None:
            output_folder = os.path.join(cfg.OUTPUT_DIR, "inference")
        evaluator_list = []
        evaluator_type = MetadataCatalog.get(dataset_name).evaluator_type
        if evaluator_type in ["sem_seg", "coco_panoptic_seg"]:
            evaluator_list.append(
                SemSegEvaluator(
                    dataset_name,
                    distributed=True,
                    output_dir=output_folder,
                )
            )
        if evaluator_type in ["coco", "coco_panoptic_seg"]:
            evaluator_list.append(COCOEvaluator(dataset_name, output_dir=output_folder))
        if evaluator_type == "coco_panoptic_seg":
            evaluator_list.append(COCOPanopticEvaluator(dataset_name, output_folder))
        if evaluator_type == "cityscapes_instance":
            return CityscapesInstanceEvaluator(dataset_name)
        if evaluator_type == "cityscapes_sem_seg":
            return CityscapesSemSegEvaluator(dataset_name)
        elif evaluator_type == "pascal_voc":
            return PascalVOCDetectionEvaluator(dataset_name)
        elif evaluator_type == "lvis":
            return LVISEvaluator(dataset_name, output_dir=output_folder)
        if len(evaluator_list) == 0:
            raise NotImplementedError(
                "no Evaluator for the dataset {} with the type {}".format(
                    dataset_name, evaluator_type
                )
            )
        elif len(evaluator_list) == 1:
            return evaluator_list[0]
        return DatasetEvaluators(evaluator_list)
```

# 学習する

学習が進み、`cfg.TEST.EVAL_PERIOD`で設定したイテレーションごとにvalデータセットに対して以下のように評価指標が計算されます。

```sh
Loading and preparing results...
DONE (t=0.00s)
creating index...
index created!
 Average Precision  (AP) @[ IoU=0.50:0.95 | area=   all | maxDets=100 ] = 0.847
 Average Precision  (AP) @[ IoU=0.50      | area=   all | maxDets=100 ] = 1.000
 Average Precision  (AP) @[ IoU=0.75      | area=   all | maxDets=100 ] = 1.000
 Average Precision  (AP) @[ IoU=0.50:0.95 | area= small | maxDets=100 ] = -1.000
 Average Precision  (AP) @[ IoU=0.50:0.95 | area=medium | maxDets=100 ] = -1.000
 Average Precision  (AP) @[ IoU=0.50:0.95 | area= large | maxDets=100 ] = 0.851
 Average Recall     (AR) @[ IoU=0.50:0.95 | area=   all | maxDets=  1 ] = 0.880
 Average Recall     (AR) @[ IoU=0.50:0.95 | area=   all | maxDets= 10 ] = 0.880
 Average Recall     (AR) @[ IoU=0.50:0.95 | area=   all | maxDets=100 ] = 0.880
 Average Recall     (AR) @[ IoU=0.50:0.95 | area= small | maxDets=100 ] = -1.000
 Average Recall     (AR) @[ IoU=0.50:0.95 | area=medium | maxDets=100 ] = -1.000
 Average Recall     (AR) @[ IoU=0.50:0.95 | area= large | maxDets=100 ] = 0.880
[11/14 19:05:02 d2.evaluation.coco_evaluation]: Evaluation results for bbox: 
|   AP   |  AP50   |  AP75   |  APs  |  APm  |  APl   |
|:------:|:-------:|:-------:|:-----:|:-----:|:------:|
| 84.719 | 100.000 | 100.000 |  nan  |  nan  | 85.059 |
[11/14 19:05:02 d2.evaluation.coco_evaluation]: Some metrics cannot be computed and is shown as NaN.
Loading and preparing results...
DONE (t=0.00s)
creating index...
index created!
 Average Precision  (AP) @[ IoU=0.50:0.95 | area=   all | maxDets=100 ] = 0.908
 Average Precision  (AP) @[ IoU=0.50      | area=   all | maxDets=100 ] = 1.000
 Average Precision  (AP) @[ IoU=0.75      | area=   all | maxDets=100 ] = 1.000
 Average Precision  (AP) @[ IoU=0.50:0.95 | area= small | maxDets=100 ] = -1.000
 Average Precision  (AP) @[ IoU=0.50:0.95 | area=medium | maxDets=100 ] = -1.000
 Average Precision  (AP) @[ IoU=0.50:0.95 | area= large | maxDets=100 ] = 0.908
 Average Recall     (AR) @[ IoU=0.50:0.95 | area=   all | maxDets=  1 ] = 0.920
 Average Recall     (AR) @[ IoU=0.50:0.95 | area=   all | maxDets= 10 ] = 0.920
 Average Recall     (AR) @[ IoU=0.50:0.95 | area=   all | maxDets=100 ] = 0.920
 Average Recall     (AR) @[ IoU=0.50:0.95 | area= small | maxDets=100 ] = -1.000
 Average Recall     (AR) @[ IoU=0.50:0.95 | area=medium | maxDets=100 ] = -1.000
 Average Recall     (AR) @[ IoU=0.50:0.95 | area= large | maxDets=100 ] = 0.920
[11/14 19:05:02 d2.evaluation.coco_evaluation]: Evaluation results for segm: 
|   AP   |  AP50   |  AP75   |  APs  |  APm  |  APl   |
|:------:|:-------:|:-------:|:-----:|:-----:|:------:|
| 90.759 | 100.000 | 100.000 |  nan  |  nan  | 90.759 |
[11/14 19:05:02 d2.evaluation.coco_evaluation]: Some metrics cannot be computed and is shown as NaN.
[11/14 19:05:02 d2.engine.defaults]: Evaluation results for balloon_val in csv format:
[11/14 19:05:02 d2.evaluation.testing]: copypaste: Task: bbox
[11/14 19:05:02 d2.evaluation.testing]: copypaste: AP,AP50,AP75,APs,APm,APl
[11/14 19:05:02 d2.evaluation.testing]: copypaste: 84.7195,100.0000,100.0000,nan,nan,85.0589
[11/14 19:05:02 d2.evaluation.testing]: copypaste: Task: segm
[11/14 19:05:02 d2.evaluation.testing]: copypaste: AP,AP50,AP75,APs,APm,APl
[11/14 19:05:02 d2.evaluation.testing]: copypaste: 90.7591,100.0000,100.0000,nan,nan,90.7591
```

以下はMLflowでログを取った結果です。
[【Detectron2を掘り下げる】MLflowで実験管理](https://qiita.com/insilicomab/items/a63b7d0035c9c10517d3)

![newplot.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/471633/f6d0d2ef-a8f3-2e90-b0bc-a1c74d5e56c5.png)

こんな感じでサクッと実装することができます。