mmdetection源码笔记(五):测试之test.py的解读

引言

在test阶段有以下几个方法:

  • single_gpu_test():顾名思义,就是单GPU测试,该方法在main()中调用,当不分布式测试的时候,则运行次测试方法,该方法的实现中,其实是调用了检测器测试过程的forward()前向计算过程,以cascade_rcnn为例,在cascade_rcnn的父类中的forward()方法中,通过判断test_mode当前处于训练还是测试阶段,来调用在cascade_rcnn类中重写的forwad_train()simple_test()方法,来完成训练和测试的前向传播过程。
  • multi_gpu_test():同上。
  • collect_results()
  • parse_args():读取命令行参数。同训练文件train.py中的parse_args()作用一样。这边就不讲了
  • main():该py文件的主体过程,在main中,读取配置文件,读取模型文件,读取数据集,加载检查点模型,然后开始进行测试,并将测试过程的前向传播的结果,进行eval,这个eval过程,主要是调用了cocoEval的API来完成的(cocoEval.evaluate()cocoEval.accumulate()cocoEval.summarize(),这些在coco_utils.py中被调用)。

单gpu测试和多gpu测试代码:

# coding=gbk
import argparse
import os
import os.path as osp
import shutil
import tempfile
import mmcv
import torch
import torch.distributed as dist
from mmcv.runner import load_checkpoint, get_dist_info
from mmcv.parallel import MMDataParallel, MMDistributedDataParallel
from mmdet.apis import init_dist
from mmdet.core import results2json, coco_eval, wrap_fp16_model
from mmdet.datasets import build_dataloader, build_dataset
from mmdet.models import build_detector

def single_gpu_test(model, data_loader, show=False):# 单 gpu 测试
   model.eval()                    
   #model.eval() 将通知你所有的图层你处于评估模式,这样,batchnorm或dropout图层将在eval模式而不是训练模式下工作。
   #torch.no_grad() 影响autograd引擎并停用它。它将减少内存使用并加快计算速度,但将无法进行反向提升(在eval脚本中不需要)。
   results = []
   dataset = data_loader.dataset                   # Dataset对象
   prog_bar = mmcv.ProgressBar(len(dataset))       # 进度条
   for i, data in enumerate(data_loader):          # data是个字典
       with torch.no_grad():                       # with torch.no_grad() 使用 no_grad 上下文管理器,处理autograd引擎并阻止它计算渐变
           result = model(return_loss=False, rescale=not show, **data)  # result = model()??? 传入data,调用的应该是测试函数
       results.append(result)

       if show:
           model.module.show_result(data, result, dataset.img_norm_cfg) # show_result() 在 module类定义的方法里

       batch_size = data['img'][0].size(0)         # data是个字典:有字段img,img_meta。
       #'img':[tensor([[[[....]]]])]
       #'img_meta': [DataContainer([[{'ori_shape': (600, 800, 3), 'img_shape': (800, 1067, 3), 'pad_shape': (800, 1088, 3), 'scale_factor': 1.3333333333333333, 'flip': False}]])]
       #print(data)              
       #print(data['img'])                         # 是个tensor
       #print(data['img'][0])                      # 是个 tensor
       #print(batch_size)                          # batch_size = 1
       #exit("输出数据查看结束!")
       for _ in range(batch_size):                 # batch_size = 1
           prog_bar.update()                       # 更新进度条
   return results
def multi_gpu_test(model, data_loader, tmpdir=None):# 多 gpu 测试
    model.eval()                                    # 为了早点响应.train(),应利用.eval() 将网络明确地设置为评估模式。
    results = []
    dataset = data_loader.dataset
    rank, world_size = get_dist_info()              # get_dist_info() 获得分布式训练的信息  rank  and  world_size 0 and 1 
    # world_size(int, optional): 参与工作的进程数
    # rank(int, optional): 当前进程的rank排名
    if rank == 0:
        prog_bar = mmcv.ProgressBar(len(dataset))   # 进度条
    for i, data in enumerate(data_loader):          # data_loader :
        with torch.no_grad():
            result = model(return_loss=False, rescale=True, **data)
        results.append(result)

        if rank == 0:
            batch_size = data['img'][0].size(0)
            for _ in range(batch_size * world_size):
                prog_bar.update()
    # collect results from all ranks
    results = collect_results(results, len(dataset), tmpdir)
    return results

它们的返回值是一个大list。

collect_results()

def collect_results(result_part, size, tmpdir=None): 
   rank, world_size = get_dist_info()               # get_dist_info() 获得分布式训练的信息  
   # create a tmp dir if it is not specified
   if tmpdir is None:
       MAX_LEN = 512
       # 32 is whitespace
       dir_tensor = torch.full((MAX_LEN, ),         # torch.full(size, fill_value),把fill_value这个数字变成size形状的张量
                               32,
                               dtype=torch.uint8,
                               device='cuda')
       if rank == 0:
           tmpdir = tempfile.mkdtemp()             # 创建一个唯一的临时目录
           tmpdir = torch.tensor(
               bytearray(tmpdir.encode()), dtype=torch.uint8, device='cuda')
           dir_tensor[:len(tmpdir)] = tmpdir
       dist.broadcast(dir_tensor, 0)               # torch.distributed.broadcast() 后端的广播功能
       tmpdir = dir_tensor.cpu().numpy().tobytes().decode().rstrip()
   else:
       mmcv.mkdir_or_exist(tmpdir)
   # dump the part result to the dir 将部分结果转储到目录
   mmcv.dump(result_part, osp.join(tmpdir, 'part_{}.pkl'.format(rank)))
   dist.barrier()                                  # torch.distributed.barrier() 后端的屏障功能
   # collect all parts
   if rank != 0:
       return None
   else:
       # load results of all parts from tmp dir 从tmp目录加载所有部件的结果
       part_list = []
       for i in range(world_size):
           part_file = osp.join(tmpdir, 'part_{}.pkl'.format(i))
           part_list.append(mmcv.load(part_file))
       # sort the results
       ordered_results = []
       for res in zip(*part_list):
           ordered_results.extend(list(res))
       # the dataloader may pad some samples
       ordered_results = ordered_results[:size]
       # remove tmp dir
       shutil.rmtree(tmpdir)
       return ordered_results

main()

def main():
    args = parse_args() # 获得命令行参数
    if args.out is not None and not args.out.endswith(('.pkl', '.pickle')):
        raise ValueError('The output file must be a pkl file.')
    cfg = mmcv.Config.fromfile(args.config) # 读取配置文件  
    # set cudnn_benchmark
    if cfg.get('cudnn_benchmark', False):
        torch.backends.cudnn.benchmark = True# 在图片输入尺度固定时开启,可以加速
    cfg.model.pretrained = None
    cfg.data.test.test_mode = True           # test_mode 测试模式,用来区分当前处于测试还是训练,该字段在custom.py中,用来过滤图片的(不让不符合的图片参与训练)
    # init distributed env first, since logger depends on the dist info.
    if args.launcher == 'none':
        distributed = False
    else:
        distributed = True
        init_dist(args.launcher, **cfg.dist_params)
    # build the dataloader
    # TODO: support multiple images per gpu (only minor changes are needed)
    dataset = build_dataset(cfg.data.test)  # type = CocoDataset    dataset -> Dataset对象)
    data_loader = build_dataloader(         # data_loader 
        dataset,
        imgs_per_gpu=1,
        workers_per_gpu=cfg.data.workers_per_gpu, # workers_per_gpu = 2
        dist=distributed,
        shuffle=False)
    # build the model and load checkpoint
    model = build_detector(cfg.model, train_cfg=None, test_cfg=cfg.test_cfg)  # build detection()
    fp16_cfg = cfg.get('fp16', None)
    if fp16_cfg is not None:
        wrap_fp16_model(model)
    checkpoint = load_checkpoint(model, args.checkpoint, map_location='cpu')  # load_checkpoint() 
    # old versions did not save class info in checkpoints, this walkaround is
    # for backward compatibility 
    if 'CLASSES' in checkpoint['meta']:
        model.CLASSES = checkpoint['meta']['CLASSES']# 检查点有CLASSES,model的CLASSES = checkpoint['meta']['CLASSES']
    else:
        model.CLASSES = dataset.CLASSES              # 没有的话,就为数据集的CLASSES属性,是一个元组数据类型

    # 非分布式
    if not distributed:      
        # model 为检测器的model,如:cascade_rcnn     
        model = MMDataParallel(model, device_ids=[0])# 这里将模型复制到gpu ,(默认是cuda('0'),即转到第一个GPU) 
        outputs = single_gpu_test(model, data_loader, args.show)
    else:
        model = MMDistributedDataParallel(model.cuda())   
        outputs = multi_gpu_test(model, data_loader, args.tmpdir)
        # 了解一下 outputs 是什么? list ->   results ? results 具体的信息是啥?  那就print一下就行了。
    rank, _ = get_dist_info()# 获取当前进程的rank排名,排名为 0
    if args.out and rank == 0:
        print('\nwriting results to {}'.format(args.out))
        mmcv.dump(outputs, args.out)                 # mmcv.dump(),应该也是类似于python.dump()??? 将结果保存到指定的文件.pkl
        eval_types = args.eval                       # --eval bbox (proposal_fast、proposal、segm、keypoints、bbox)
        if eval_types:
            print('Starting evaluate {}'.format(' and '.join(eval_types)))
            if eval_types == ['proposal_fast']:      # proposal_fast ?
                result_file = args.out               # .pkl训练文件
                coco_eval(result_file, eval_types, dataset.coco)
            else:
                if not isinstance(outputs[0], dict):                         # outputs[] = 测试前向传播后的结果   list
                    result_files = results2json(dataset, outputs, args.out)  # result2json() 转换成json格式 (将outputs数据保存到json格式的result_files中)
                    coco_eval(result_files, eval_types, dataset.coco)        # coco_eval(), eval_types :bbox, args.out = ./result/result_100.pkl
                    # result_files = {'bbox': './result/result_100.pkl.bbox.json', 'proposal': './result/result_100.pkl.bbox.json'}
                else:
                    for name in outputs[0]:
                        print('\nEvaluating {}'.format(name))
                        outputs_ = [out[name] for out in outputs]
                        result_file = args.out + '.{}'.format(name)
                        result_files = results2json(dataset, outputs_,
                                                    result_file)
                        coco_eval(result_files, eval_types, dataset.coco)    # coco_eval() 在coco_utils.py文件中

将model打印出来,就可以看到完整的网络结构:
mmdetection源码笔记(五):测试之test.py的解读_第1张图片
验证过程的实现在coco_utils.py文件中,调用了coco_eval()方法,在该方法里,主要是使用了cocoeval的api,

  • cocoEval.evaluate():Running per image evaluation…
  • cocoEval.accumulate():accumulate per image results
  • cocoEval.summarize():display summary metrics of results

这三个方法的实现,可以**其Github网址**中找到。这里就不在说了。

你可能感兴趣的:(mmdetection源码笔记)