引言
在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打印出來,就可以看到完整的網絡結構:
驗證過程的實現在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網址**中找到。這裏就不在說了。