Commit 8acd5d54 authored by zhangwenwei's avatar zhangwenwei
Browse files

Tune performance with new schedule

parent f93167c3
...@@ -11,7 +11,6 @@ from mmcv.utils import print_log ...@@ -11,7 +11,6 @@ from mmcv.utils import print_log
from mmdet.datasets import DATASETS from mmdet.datasets import DATASETS
from ..core.bbox import Box3DMode, CameraInstance3DBoxes from ..core.bbox import Box3DMode, CameraInstance3DBoxes
from .custom_3d import Custom3DDataset from .custom_3d import Custom3DDataset
from .utils import remove_dontcare
@DATASETS.register_module() @DATASETS.register_module()
...@@ -83,7 +82,7 @@ class KittiDataset(Custom3DDataset): ...@@ -83,7 +82,7 @@ class KittiDataset(Custom3DDataset):
annos = info['annos'] annos = info['annos']
# we need other objects to avoid collision when sample # we need other objects to avoid collision when sample
annos = remove_dontcare(annos) annos = self.remove_dontcare(annos)
loc = annos['location'] loc = annos['location']
dims = annos['dimensions'] dims = annos['dimensions']
rots = annos['rotation_y'] rots = annos['rotation_y']
...@@ -128,6 +127,16 @@ class KittiDataset(Custom3DDataset): ...@@ -128,6 +127,16 @@ class KittiDataset(Custom3DDataset):
inds = np.array(inds, dtype=np.int64) inds = np.array(inds, dtype=np.int64)
return inds return inds
def remove_dontcare(self, ann_info):
img_filtered_annotations = {}
relevant_annotation_indices = [
i for i, x in enumerate(ann_info['name']) if x != 'DontCare'
]
for key in ann_info.keys():
img_filtered_annotations[key] = (
ann_info[key][relevant_annotation_indices])
return img_filtered_annotations
def format_results(self, def format_results(self,
outputs, outputs,
pklfile_prefix=None, pklfile_prefix=None,
......
from .build_loader import build_dataloader
from .sampler import DistributedGroupSampler, GroupSampler
__all__ = ['GroupSampler', 'DistributedGroupSampler', 'build_dataloader']
import platform
import random
from functools import partial
import numpy as np
from mmcv.parallel import collate
from mmcv.runner import get_dist_info
from torch.utils.data import DataLoader
from .sampler import DistributedGroupSampler, DistributedSampler, GroupSampler
if platform.system() != 'Windows':
# https://github.com/pytorch/pytorch/issues/973
import resource
rlimit = resource.getrlimit(resource.RLIMIT_NOFILE)
resource.setrlimit(resource.RLIMIT_NOFILE, (4096, rlimit[1]))
def build_dataloader(dataset,
samples_per_gpu,
workers_per_gpu,
num_gpus=1,
dist=True,
seed=None,
**kwargs):
shuffle = kwargs.get('shuffle', True)
if dist:
rank, world_size = get_dist_info()
if shuffle:
sampler = DistributedGroupSampler(dataset, samples_per_gpu,
world_size, rank)
else:
sampler = DistributedSampler(
dataset, world_size, rank, shuffle=False)
batch_size = samples_per_gpu
num_workers = workers_per_gpu
else:
sampler = GroupSampler(dataset, samples_per_gpu) if shuffle else None
batch_size = num_gpus * samples_per_gpu
num_workers = num_gpus * workers_per_gpu
data_loader = DataLoader(
dataset,
batch_size=batch_size,
sampler=sampler,
num_workers=num_workers,
collate_fn=partial(collate, samples_per_gpu=samples_per_gpu),
pin_memory=False,
worker_init_fn=worker_init_fn if seed is not None else None,
**kwargs)
return data_loader
def worker_init_fn(seed):
np.random.seed(seed)
random.seed(seed)
from __future__ import division
import math
import numpy as np
import torch
from mmcv.runner import get_dist_info
from torch.utils.data import DistributedSampler as _DistributedSampler
from torch.utils.data import Sampler
class DistributedSampler(_DistributedSampler):
def __init__(self, dataset, num_replicas=None, rank=None, shuffle=True):
super().__init__(dataset, num_replicas=num_replicas, rank=rank)
self.shuffle = shuffle
def __iter__(self):
# deterministically shuffle based on epoch
if self.shuffle:
g = torch.Generator()
g.manual_seed(self.epoch)
indices = torch.randperm(len(self.dataset), generator=g).tolist()
else:
indices = torch.arange(len(self.dataset)).tolist()
# add extra samples to make it evenly divisible
indices += indices[:(self.total_size - len(indices))]
assert len(indices) == self.total_size
# subsample
indices = indices[self.rank:self.total_size:self.num_replicas]
assert len(indices) == self.num_samples
return iter(indices)
class GroupSampler(Sampler):
def __init__(self, dataset, samples_per_gpu=1):
assert hasattr(dataset, 'flag')
self.dataset = dataset
self.samples_per_gpu = samples_per_gpu
self.flag = dataset.flag.astype(np.int64)
self.group_sizes = np.bincount(self.flag)
self.num_samples = 0
for i, size in enumerate(self.group_sizes):
self.num_samples += int(np.ceil(
size / self.samples_per_gpu)) * self.samples_per_gpu
def __iter__(self):
indices = []
for i, size in enumerate(self.group_sizes):
if size == 0:
continue
indice = np.where(self.flag == i)[0]
assert len(indice) == size
np.random.shuffle(indice)
num_extra = int(np.ceil(size / self.samples_per_gpu)
) * self.samples_per_gpu - len(indice)
indice = np.concatenate(
[indice, np.random.choice(indice, num_extra)])
indices.append(indice)
indices = np.concatenate(indices)
indices = [
indices[i * self.samples_per_gpu:(i + 1) * self.samples_per_gpu]
for i in np.random.permutation(
range(len(indices) // self.samples_per_gpu))
]
indices = np.concatenate(indices)
indices = indices.astype(np.int64).tolist()
assert len(indices) == self.num_samples
return iter(indices)
def __len__(self):
return self.num_samples
class DistributedGroupSampler(Sampler):
"""Sampler that restricts data loading to a subset of the dataset.
It is especially useful in conjunction with
:class:`torch.nn.parallel.DistributedDataParallel`. In such case, each
process can pass a DistributedSampler instance as a DataLoader sampler,
and load a subset of the original dataset that is exclusive to it.
.. note::
Dataset is assumed to be of constant size.
Arguments:
dataset: Dataset used for sampling.
num_replicas (optional): Number of processes participating in
distributed training.
rank (optional): Rank of the current process within num_replicas.
"""
def __init__(self,
dataset,
samples_per_gpu=1,
num_replicas=None,
rank=None):
_rank, _num_replicas = get_dist_info()
if num_replicas is None:
num_replicas = _num_replicas
if rank is None:
rank = _rank
self.dataset = dataset
self.samples_per_gpu = samples_per_gpu
self.num_replicas = num_replicas
self.rank = rank
self.epoch = 0
assert hasattr(self.dataset, 'flag')
self.flag = self.dataset.flag
self.group_sizes = np.bincount(self.flag)
self.num_samples = 0
for i, j in enumerate(self.group_sizes):
self.num_samples += int(
math.ceil(self.group_sizes[i] * 1.0 / self.samples_per_gpu /
self.num_replicas)) * self.samples_per_gpu
self.total_size = self.num_samples * self.num_replicas
def __iter__(self):
# deterministically shuffle based on epoch
g = torch.Generator()
g.manual_seed(self.epoch)
indices = []
for i, size in enumerate(self.group_sizes):
if size > 0:
indice = np.where(self.flag == i)[0]
assert len(indice) == size
indice = indice[list(torch.randperm(int(size),
generator=g))].tolist()
extra = int(
math.ceil(
size * 1.0 / self.samples_per_gpu / self.num_replicas)
) * self.samples_per_gpu * self.num_replicas - len(indice)
# pad indice
tmp = indice.copy()
for _ in range(extra // size):
indice.extend(tmp)
indice.extend(tmp[:extra % size])
indices.extend(indice)
assert len(indices) == self.total_size
indices = [
indices[j] for i in list(
torch.randperm(
len(indices) // self.samples_per_gpu, generator=g))
for j in range(i * self.samples_per_gpu, (i + 1) *
self.samples_per_gpu)
]
# subsample
offset = self.num_samples * self.rank
indices = indices[offset:offset + self.num_samples]
assert len(indices) == self.num_samples
return iter(indices)
def __len__(self):
return self.num_samples
def set_epoch(self, epoch):
self.epoch = epoch
from collections import Sequence
import mmcv
import numpy as np
import torch
def remove_dontcare(image_anno):
img_filtered_annotations = {}
relevant_annotation_indices = [
i for i, x in enumerate(image_anno['name']) if x != 'DontCare'
]
for key in image_anno.keys():
img_filtered_annotations[key] = (
image_anno[key][relevant_annotation_indices])
return img_filtered_annotations
def to_tensor(data):
# TODO: remove this duplicated method in the future
"""Convert objects of various python types to :obj:`torch.Tensor`.
Supported types are: :class:`numpy.ndarray`, :class:`torch.Tensor`,
:class:`Sequence`, :class:`int` and :class:`float`.
"""
if isinstance(data, torch.Tensor):
return data
elif isinstance(data, np.ndarray):
return torch.from_numpy(data)
elif isinstance(data, Sequence) and not mmcv.is_str(data):
return torch.tensor(data)
elif isinstance(data, int):
return torch.LongTensor([data])
elif isinstance(data, float):
return torch.FloatTensor([data])
else:
raise TypeError('type {} cannot be converted to tensor.'.format(
type(data)))
...@@ -68,8 +68,14 @@ class PartAggregationROIHead(Base3DRoIHead): ...@@ -68,8 +68,14 @@ class PartAggregationROIHead(Base3DRoIHead):
voxels_dict (dict): Contains information of voxels. voxels_dict (dict): Contains information of voxels.
img_metas (list[dict]): Meta info of each image. img_metas (list[dict]): Meta info of each image.
proposal_list (list[dict]): Proposal information from rpn. proposal_list (list[dict]): Proposal information from rpn.
gt_bboxes_3d (list[FloatTensor]): GT bboxes of each batch. The dictionary should contain the following keys:
gt_labels_3d (list[LongTensor]): GT labels of each batch. - boxes_3d (:obj:BaseInstance3DBoxes): Proposal bboxes
- labels_3d (torch.Tensor): Labels of proposals
- cls_preds (torch.Tensor): Original scores of proposals
gt_bboxes_3d (list[:obj:BaseInstance3DBoxes]):
GT bboxes of each sample. The bboxes are encapsulated
by 3D box structures.
gt_labels_3d (list[LongTensor]): GT labels of each sample.
Returns: Returns:
dict: losses from each head. dict: losses from each head.
...@@ -178,17 +184,19 @@ class PartAggregationROIHead(Base3DRoIHead): ...@@ -178,17 +184,19 @@ class PartAggregationROIHead(Base3DRoIHead):
cur_gt_labels = gt_labels_3d[batch_idx] cur_gt_labels = gt_labels_3d[batch_idx]
batch_num_gts = 0 batch_num_gts = 0
batch_gt_indis = cur_gt_labels.new_full((cur_boxes.shape[0], ), # 0 is bg
0) # 0 is bg batch_gt_indis = cur_gt_labels.new_full((len(cur_boxes), ), 0)
batch_max_overlaps = cur_boxes.new_zeros(cur_boxes.shape[0]) batch_max_overlaps = cur_boxes.tensor.new_zeros(len(cur_boxes))
batch_gt_labels = cur_gt_labels.new_full((cur_boxes.shape[0], ), # -1 is bg
-1) # -1 is bg batch_gt_labels = cur_gt_labels.new_full((len(cur_boxes), ), -1)
if isinstance(self.bbox_assigner, list): # for multi classes
# each class may have its own assigner
if isinstance(self.bbox_assigner, list):
for i, assigner in enumerate(self.bbox_assigner): for i, assigner in enumerate(self.bbox_assigner):
gt_per_cls = (cur_gt_labels == i) gt_per_cls = (cur_gt_labels == i)
pred_per_cls = (cur_labels_3d == i) pred_per_cls = (cur_labels_3d == i)
cur_assign_res = assigner.assign( cur_assign_res = assigner.assign(
cur_boxes[pred_per_cls], cur_boxes.tensor[pred_per_cls],
cur_gt_bboxes.tensor[gt_per_cls], cur_gt_bboxes.tensor[gt_per_cls],
gt_labels=cur_gt_labels[gt_per_cls]) gt_labels=cur_gt_labels[gt_per_cls])
# gather assign_results in different class into one result # gather assign_results in different class into one result
...@@ -215,10 +223,12 @@ class PartAggregationROIHead(Base3DRoIHead): ...@@ -215,10 +223,12 @@ class PartAggregationROIHead(Base3DRoIHead):
batch_gt_labels) batch_gt_labels)
else: # for single class else: # for single class
assign_result = self.bbox_assigner.assign( assign_result = self.bbox_assigner.assign(
cur_boxes, cur_gt_bboxes.tensor, gt_labels=cur_gt_labels) cur_boxes.tensor,
cur_gt_bboxes.tensor,
gt_labels=cur_gt_labels)
# sample boxes # sample boxes
sampling_result = self.bbox_sampler.sample(assign_result, sampling_result = self.bbox_sampler.sample(assign_result,
cur_boxes, cur_boxes.tensor,
cur_gt_bboxes.tensor, cur_gt_bboxes.tensor,
cur_gt_labels) cur_gt_labels)
sampling_results.append(sampling_result) sampling_results.append(sampling_result)
......
...@@ -154,7 +154,7 @@ def test_parta2_rpnhead_getboxes(): ...@@ -154,7 +154,7 @@ def test_parta2_rpnhead_getboxes():
if not torch.cuda.is_available(): if not torch.cuda.is_available():
pytest.skip('test requires GPU and torch+cuda') pytest.skip('test requires GPU and torch+cuda')
rpn_head_cfg, proposal_cfg = _get_rpn_head_cfg( rpn_head_cfg, proposal_cfg = _get_rpn_head_cfg(
'kitti/hv_PartA2_secfpn_4x8_cyclic_80e_kitti-3d-3class.py') 'kitti/hv_PartA2_secfpn_2x8_cyclic_80e_kitti-3d-3class.py')
from mmdet3d.models.builder import build_head from mmdet3d.models.builder import build_head
self = build_head(rpn_head_cfg) self = build_head(rpn_head_cfg)
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment