mmengine/docs/zh_cn/tutorials/evaluator.md
Yuan Liu 2d3e91248c
[Feature]: Add sampler seed hook (#64)
* [Feature]: Add sampler seed hook

* [Fix]: Add call with to UT
2022-03-01 15:38:01 +08:00

7.3 KiB
Raw Blame History

评测器Evaluator

在模型验证和模型测试中,通常需要对模型精度做定量评测。在 MMEngine 中实现了评测器来完成这一功能。评测器可以根据模型的输入数据和预测结果计算特定的评测指标Metric。评测器与数据集之间相互解耦这使得用户可以任意组合所需的测试数据和评测器。如 COCOEvaluator 可用于计算 COCO 数据集的 APAR 等评测指标,也可用于其他的目标检测数据集上。

模型精度评测

使用评测器计算模型精度的过程如下图所示。

测试数据通常会被划分为若干批次batch。通过一个循环依次将每个批次的数据送入模型得到对应的预测结果并将预测结果连同模型的输入数据一起通过评测器的 process() 方法送入评测器。当循环结束后,再调用评测器的 evaluate() 方法,即可计算得到对应的评测指标。

在实际使用中,这些操作均由任务执行器完成。用户只需要在配置文件中选择要使用的评测器并配置相应参数即可。

在配置文件中配置评测器

在配置文件中配置评测器时需要指定评测器的类别、参数以及调用方式等。其中调用方式通常针对模型验证阶段包括调用评测器的间隔时间单位epoch 或 iteration、间隔时间、主要评测指标即筛选最佳 checkpoint 所依据的指标)等。

例如,用户希望在模型验证时使用 COCO 评测器,每 10 epoch 评测一次,并以 AP 作为主要评测指标,对应的配置文件部分如下:

validation_cfg=dict(
    evaluator=dict(type='COCO'),  # 使用 COCO 评测器,无参数
    main_metric='AP',  # 主要评测指标为 AP
    interval=10,  # 每 10 epoch 评测一次
    by_epoch=True,
)

使用多个评测器

评测器支持组合使用。用户可以通过配置多个评测器,在模型验证或模型测试阶段同时计算多个评测指标。使用多个评测器时,只需要在配置文件里将所有评测器的配置写在一个列表里即可:

validation_cfg=dict(
    evaluator=[
        dict(type='Accuracy', top_k=1),  # 使用分类正确率评测器
        dict(type='F1Score')  # 使用 F1_score 评测器
    ],
    main_metric='accuracy'
    interval=10,
    by_epoch=True,
)

在使用多个评测器时,可能会出现评测指标同名的情况。比如,在下面的例子中使用了 2 个参数不同的分类正确率评测器,它们对应的评测指标都是 accuracy。此时为了避免歧义需要给评测器设置 prefix 参数。评测器的 prefix 会自动添加在评测指标名称的开头,从而使同名的评测指标可以区分。

validation_cfg=dict(
    evaluator=[
        dict(type='Accuracy', top_k=1, prefix='top1'),
        dict(type='Accuracy', top_k=5, prefix='top5')
    ],
    main_metric='top1_accuracy',  # 前缀 'top1' 被自动添加进指标名称中,用以区分同名指标
    interval=10,
    by_epoch=True,
)

增加自定义评测器

在 OpenMMLab 的各个算法库中,已经实现了对应方向的常用评测器。如 MMDetection 中提供了 COCO 评测器MMClassification 中提供了 Accuracy、F1Score 等评测器等。

用户也可以根据自身需求,增加自定义的评测器。在实现自定义评测器时,用户需要继承 MMEngine 中提供的评测器基类 BaseEvaluator,并实现对应的抽象方法。

评测器基类

评测器基类 BaseEvaluator 是一个抽象类,具有以下 2 个抽象方法:

  • process(): 处理每个批次的测试数据和模型预测结果。处理结果应存放在 self.results 列表中,用于在处理完所有测试数据后计算评测指标。
  • compute_metrics(): 计算评测指标,并将所评测指标存放在一个字典中返回。

其中,compute_metrics() 会在 evaluate() 方法中被调用;后者在计算评测指标前,会在分布式测试时收集和汇总不同 rank 的中间处理结果。而 process()evaluate() 都会由任务执行器调用。因此,用户只需要在继承 BaseEvaluator 后实现 process()compute_metrics() 方法即可。

需要注意的是,self.results 中存放的具体类型取决于自定义评测器类的实现。例如,当测试样本或模型输出数据量较大(如语义分割、图像生成等任务),不宜全部存放在内存中时,可以在 self.results 中存放每个批次计算得到的指标,并在 compute_metrics() 中汇总;或将每个批次的中间结果存储到临时文件中,并在 self.results 中存放临时文件路径,最后由 compute_metrics() 从文件中读取数据并计算指标。

自定义评测器类

我们以实现分类正确率Classification Accuracy评测器为例说明实现自定义评测器的方法。

首先,自定义评测器类应继承自 BaseEvaluator,并应加入注册器 EVALUATORS (关于注册器的说明请参考相关文档)。

process() 方法有 2 个输入参数,分别是测试数据样本data_samples和模型预测结果 predictions。我们从中分别取出样本类别标签和分类预测结果,并存放在 self.results 中。

compute_metrics()方法有 1 个输入参数 results,里面存放了所有批次测试数据经过 process() 方法处理后得到的结果。从中取出样本类别标签和分类预测结果,即可计算得到分类正确率 acc。最终,将计算得到的评测指标以字典的形式返回。

具体的实现如下:

from mmengine.evaluator import BaseEvaluator
from mmengine.registry import EVALUATORS

import numpy as np

@EVALUATORS.register_module()
class Accuracy(BaseEvaluator):

    def process(self, data_samples: Dict, predictions: Dict):
        """Process one batch of data and predictions. The processed
        Results should be stored in `self.results`, which will be used
        to computed the metrics when all batches have been processed.

        Args:
            data_samples (dict): The data samples from the dataset.
            predictions (dict): The output of the model.
        """

        # 取出分类预测结果和类别标签
        result = dict(
            'pred': predictions.pred_label,
            'gt': data_samples.gt_label
        )

        # 将当前 batch 的结果存进 self.results
        self.results.append(result)

    def compute_metrics(self, results: List):
        """Compute the metrics from processed results.

        Args:
            results (dict): The processed results of each batch.

        Returns:
            Dict: The computed metrics. The keys are the names of the metrics,
            and the values are corresponding results.
        """

        # 汇总所有样本的分类预测结果和类别标签
        preds = np.concatenate([res['pred'] for res in results])
        gts = np.concatenate([res['gt'] for res in results])

        # 计算分类正确率
        acc = (preds == gts).sum() / preds.size

        # 返回评测指标结果
        return {'accuracy': acc}