行人重识别02-09:fast-reid(BoT)-pytorch编程规范(fast-reid为例)6-模型测试评估-1

以下链接是个人关于fast-reid(BoT行人重识别) 所有见解,如有错误欢迎大家指出,我会第一时间纠正。有兴趣的朋友可以加微信:17575010159 相互讨论技术。若是帮助到了你什么,一定要记得点赞!因为这是对我最大的鼓励。 文末附带 \color{blue}{文末附带} 文末附带 公众号 − \color{blue}{公众号 -} 公众号 海量资源。 \color{blue}{ 海量资源}。 海量资源

行人重识别02-00:fast-reid(BoT)-目录-史上最新无死角讲解

极度推荐的商业级项目: \color{red}{极度推荐的商业级项目:} 极度推荐的商业级项目:这是本人落地的行为分析项目,主要包含(1.行人检测,2.行人追踪,3.行为识别三大模块):行为分析(商用级别)00-目录-史上最新无死角讲解

前言

在对模型的评估的时候,我们执行的指令为:

python tools/train_net.py --config-file ./configs/Market1501/bagtricks_R50.yml --eval-only \
MODEL.WEIGHTS checkpoints/market_bot_R50.pth  MODEL.DEVICE "cuda:0"

那么我们该小节,就来看看,其对数据的评估是怎么一个过程。

总体逻辑

在对模型进行评估的时候,我们首先要准备好评估的数据。然后再为该数据集创建一个数据迭代器。在 Market-1501-v15.09.15 数据集中,我们可以看到如下两个文件夹,分别为 bounding_box_test,query。评估的任务,就是从bounding_box_test中找到所有属于 query 中图像ID的图片。如,取出 query 中的一张图片 q,需要在 bounding_box_test 中找到所有和图片 q 身份相同的情况。bounding_box_test 与 query 数据集的介绍如下:

1.bounding_box_test: 用于测试集的 750 人,包含 19,732 张图像,前缀为 0000 表示在提取这 750 人的过程中DPM检测错的图(可能与query是同一个人),-1 表示检测出来其他人的图(不在这 750 人中)
2.query:为 750 人在每个摄像头中随机选择一张图像作为query,因此一个人的query最多有 6 个,共有 3,368 张图像

我们先看 fastreid\engine\defaults.py,找到类 class DefaultTrainer(SimpleTrainer) 中的函数 def test(self),该函数就是对数据进行评估的总体逻辑了,注释如下:

    @classmethod
    def test(cls, cfg, model, evaluators=None):
        """
        对模型进行评估
        Args:
            cfg (CfgNode):
            model (nn.Module):
            evaluators (list[DatasetEvaluator] or None): if None, will call
                :meth:`build_evaluator`. Otherwise, must have the same length as
                `cfg.DATASETS.TEST`.
        Returns:
            dict: a dict of result metrics
        """
        # 用于log日志的保存
        logger = logging.getLogger(__name__)
        # 检测是evaluators是否为正确的评估器
        if isinstance(evaluators, DatasetEvaluator):
            evaluators = [evaluators]

        # 如果evaluators不为none,则对evaluators的长度进行检测
        if evaluators is not None:
            assert len(cfg.DATASETS.TEST) == len(evaluators), "{} != {}".format(
                len(cfg.DATASETS.TEST), len(evaluators)
            )
        # 创建一个字典,用于结果保存
        results = OrderedDict()

        # 对多个数据集进行评估
        for idx, dataset_name in enumerate(cfg.DATASETS.TESTS):
            # 进行log打印,并且创建评估数据迭代器
            logger.info("Prepare testing set")
            # 构建测试数据迭代器,num_query 表示数据集中 query 文件夹图片的数目
            data_loader, num_query = cls.build_test_loader(cfg, dataset_name)

            # When evaluators are passed in as arguments,
            # implicitly assume that evaluators can be created before data_loader.
            # 如果已经存在 evaluator,则获得数据集对应的 evaluator(评估器)
            if evaluators is not None:
                evaluator = evaluators[idx]
            # 如果不存在 evaluator,则尝试去构建
            else:
                try:
                    evaluator = cls.build_evaluator(cfg, num_query)
                except NotImplementedError:
                    logger.warn(
                        "No evaluator found. Use `DefaultTrainer.test(evaluators=)`, "
                        "or implement its `build_evaluator` method."
                    )
                    results[dataset_name] = {}
                    continue
            # 对单个评估数据集进行推断,并且获得推断结果
            results_i = inference_on_dataset(model, data_loader, evaluator)
            # 保存数据集对应的推断结果
            results[dataset_name] = results_i

        # 如果为主进程,则返回一个评估之后的字典
        if comm.is_main_process():
            assert isinstance(
                results, dict
            ), "Evaluator must return a dict on the main process. Got {} instead.".format(
                results
            )
            # 使用csv的格式打印评估结果
            print_csv_format(results)
        
        if len(results) == 1: results = list(results.values())[0]

        return results

其上呢,存在两个比较重要的函数:

	# data_loader 包含了测试数据需要的信息
	data_loader, num_query = cls.build_test_loader(cfg, dataset_name)
    # 对单个评估数据集进行推断,并且获得推断结果
    results_i = inference_on_dataset(model, data_loader, evaluator)

首先我们来分析一下测试(评估)数据集的构建过程,其上的build_test_loader函数

data_loader

对于 build_test_loader,其会调用到 build_reid_test_loader 函数,本人注释如下:

def build_reid_test_loader(cfg, dataset_name):
    # 把cfg配置拷贝一份
    cfg = cfg.clone()
    # 解冻cfg配置文件
    cfg.defrost()
    # 获得数据集,该为fastreid.data.datasets.market1501.Market1501
    dataset = DATASET_REGISTRY.get(dataset_name)(root=_root)
    # 如果是主进程,则打印训练数据的信息
    if comm.is_main_process():
        dataset.show_test()
    # 把 gt_query 和 bounding_box_test 图像信息整合到一起(这里是个重点,大家注意一下)
    test_items = dataset.query + dataset.gallery
    # 创建数据转换方式,如通道变换,剪切,水平反转等等
    test_transforms = build_transforms(cfg, is_train=False)
    # CommDataset中实现了__getitem__函数
    test_set = CommDataset(test_items, test_transforms, relabel=False)
    # 获得每个GPU的batch_size
    mini_batch_size = cfg.TEST.IMS_PER_BATCH // comm.get_world_size()
    # 指定线程数目,构建batch数据采样器等等
    data_sampler = samplers.InferenceSampler(len(test_set))
    batch_sampler = torch.utils.data.BatchSampler(data_sampler, mini_batch_size, False)
    test_loader = DataLoader(
        test_set,
        batch_sampler=batch_sampler,
        num_workers=0,  # save some memory
        collate_fn=fast_batch_collator)
    return test_loader, len(dataset.query)

inference_on_dataset

前面提到 inference_on_dataset 这个函数也是十分重要的,其主要是对一个数据集进行评估。本人注释如下:

def inference_on_dataset(model, data_loader, evaluator):
    """
    在data_loader上运行模型,并使用 evaluator 计算指标,model会被设置为评估模式
    Run model on the data_loader and evaluate the metrics with evaluator.
    The model will be used in eval mode.
    Args:
        model (nn.Module): a module which accepts an object from
            `data_loader` and returns some outputs. It will be temporarily set to `eval` mode.
            If you wish to evaluate a model in `training` mode instead, you can
            wrap the given model and override its behavior of `.eval()` and `.train()`.
        data_loader: an iterable object with a length.
            The elements it generates will be the inputs to the model.
        evaluator (DatasetEvaluator): the evaluator to run. Use
            :class:`DatasetEvaluators([])` if you only want to benchmark, but
            don't want to do any evaluation.
    Returns:
        The return value of `evaluator.evaluate()`
    """
    logger = logging.getLogger(__name__)
    logger.info("Start inference on {} images".format(len(data_loader.dataset)))

    # 获得需要测试数据集总的迭代次数
    total = len(data_loader)  # inference data loader must have a fixed length
    # 对评估器进行置位操作,清除图片特征,身份id,图片对应的摄像头id
    evaluator.reset()
    num_warmup = min(5, total - 1)
    # 记录开始之前的时间点
    start_time = time.perf_counter()
    # 初始化提出特征消耗的时间
    total_compute_time = 0
    # 设置为评估模式
    with inference_context(model), torch.no_grad():
        # 循环提取 data_loader(query)所有图片的特征,保存到evaluator之中
        for idx, inputs in enumerate(data_loader):
            if idx == num_warmup:
                start_time = time.perf_counter()
                total_compute_time = 0

            start_compute_time = time.perf_counter()
            # 进行一个batch特征的提提取
            outputs = model(inputs)
            if torch.cuda.is_available():
                torch.cuda.synchronize()
            # 计算提取特征消耗的时间
            total_compute_time += time.perf_counter() - start_compute_time
            # 把提出的特征,以及标签添加到evaluator.pids,evaluator.camids,evaluator.features之中
            evaluator.process(inputs, outputs)

            # 计算每个提取batch特征消耗的时间
            idx += 1
            iters_after_start = idx + 1 - num_warmup * int(idx >= num_warmup)
            seconds_per_batch = total_compute_time / iters_after_start
            # 对一些相关信息进行打印
            if idx >= num_warmup * 2 or seconds_per_batch > 30:
                total_seconds_per_img = (time.perf_counter() - start_time) / iters_after_start
                eta = datetime.timedelta(seconds=int(total_seconds_per_img * (total - idx - 1)))
                log_every_n_seconds(
                    logging.INFO,
                    "Inference done {}/{}. {:.4f} s / batch. ETA={}".format(
                        idx + 1, total, seconds_per_batch, str(eta)
                    ),
                    n=30,
                )

    # Measure the time only for this worker (before the synchronization barrier),计算总共消耗的时间,进行打印
    total_time = time.perf_counter() - start_time
    total_time_str = str(datetime.timedelta(seconds=total_time))
    # NOTE this format is parsed by grep
    logger.info(
        "Total inference time: {} ({:.6f} s / batch per device)".format(
            total_time_str, total_time / (total - num_warmup)
        )
    )
    total_compute_time_str = str(datetime.timedelta(seconds=int(total_compute_time)))
    logger.info(
        "Total inference pure compute time: {} ({:.6f} s / batch per device)".format(
            total_compute_time_str, total_compute_time / (total - num_warmup)
        )
    )
    # 对结果进行评估
    results = evaluator.evaluate()
    # An evaluator may return None when not in main process.
    # Replace it by an empty dict instead to make it easier for downstream code to handle
    if results is None:
        results = {}
    return results

其上由两个地方需要注意,分别是:

	# 把提出的特征,以及标签添加到evaluator.pids,evaluator.camids,evaluator.features之中
	evaluator.process(inputs, outputs)
    # 对结果进行评估
    results = evaluator.evaluate()	

可以看到,其都涉及到了 evaluator 这个类对象,那么他是什么呢?我们在下个篇博客对其进行讲解。

在这里插入图片描述

  • 6
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

江南才尽,年少无知!

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值