在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
中被调用)。# 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。
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
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打印出来,就可以看到完整的网络结构:
验证过程的实现在coco_utils.py文件中,调用了coco_eval()方法,在该方法里,主要是使用了cocoeval的api,
cocoEval.evaluate()
:Running per image evaluation…cocoEval.accumulate()
:accumulate per image resultscocoEval.summarize()
:display summary metrics of results这三个方法的实现,可以**其Github网址**中找到。这里就不在说了。