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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章