以下链接是个人关于fast-reid(BoT行人重识别) 所有见解,如有错误欢迎大家指出,我会第一时间纠正。有兴趣的朋友可以加微信:a944284742相互讨论技术。若是帮助到了你什么,一定要记得点赞!因为这是对我最大的鼓励。
行人重识别02-00:fast-reid(BoT)-目录-史上最新无死角讲解
在对模型的评估的时候,我们执行的指令为:
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函数
对于 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 这个函数也是十分重要的,其主要是对一个数据集进行评估。本人注释如下:
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 这个类对象,那么他是什么呢?我们在下个篇博客对其进行讲解。