Commit ee26c6b9 authored by pangjm's avatar pangjm
Browse files

Merge branch 'master' of github.com:open-mmlab/mmdetection

parents 22298f3c cd35200f
...@@ -194,7 +194,7 @@ Here is an example. ...@@ -194,7 +194,7 @@ Here is an example.
'bboxes': <np.ndarray> (n, 4), 'bboxes': <np.ndarray> (n, 4),
'labels': <np.ndarray> (n, ), 'labels': <np.ndarray> (n, ),
'bboxes_ignore': <np.ndarray> (k, 4), 'bboxes_ignore': <np.ndarray> (k, 4),
'labels_ignore': <np.ndarray> (k, 4) (optional field) 'labels_ignore': <np.ndarray> (k, ) (optional field)
} }
}, },
... ...
...@@ -206,12 +206,12 @@ There are two ways to work with custom datasets. ...@@ -206,12 +206,12 @@ There are two ways to work with custom datasets.
- online conversion - online conversion
You can write a new Dataset class inherited from `CustomDataset`, and overwrite two methods You can write a new Dataset class inherited from `CustomDataset`, and overwrite two methods
`load_annotations(self, ann_file)` and `get_ann_info(self, idx)`, like [CocoDataset](mmdet/datasets/coco.py). `load_annotations(self, ann_file)` and `get_ann_info(self, idx)`, like [CocoDataset](mmdet/datasets/coco.py) and [VOCDataset](mmdet/datasets/voc.py).
- offline conversion - offline conversion
You can convert the annotation format to the expected format above and save it to You can convert the annotation format to the expected format above and save it to
a pickle file, like [pascal_voc.py](tools/convert_datasets/pascal_voc.py). a pickle or json file, like [pascal_voc.py](tools/convert_datasets/pascal_voc.py).
Then you can simply use `CustomDataset`. Then you can simply use `CustomDataset`.
## Technical details ## Technical details
......
# model settings
model = dict(
type='FasterRCNN',
pretrained='modelzoo://resnet50',
backbone=dict(
type='ResNet',
depth=50,
num_stages=4,
out_indices=(0, 1, 2, 3),
frozen_stages=1,
style='pytorch'),
neck=dict(
type='FPN',
in_channels=[256, 512, 1024, 2048],
out_channels=256,
num_outs=5),
rpn_head=dict(
type='RPNHead',
in_channels=256,
feat_channels=256,
anchor_scales=[8],
anchor_ratios=[0.5, 1.0, 2.0],
anchor_strides=[4, 8, 16, 32, 64],
target_means=[.0, .0, .0, .0],
target_stds=[1.0, 1.0, 1.0, 1.0],
use_sigmoid_cls=True),
bbox_roi_extractor=dict(
type='SingleRoIExtractor',
roi_layer=dict(type='RoIAlign', out_size=7, sample_num=2),
out_channels=256,
featmap_strides=[4, 8, 16, 32]),
bbox_head=dict(
type='SharedFCBBoxHead',
num_fcs=2,
in_channels=256,
fc_out_channels=1024,
roi_feat_size=7,
num_classes=81,
target_means=[0., 0., 0., 0.],
target_stds=[0.1, 0.1, 0.2, 0.2],
reg_class_agnostic=False))
# model training and testing settings
train_cfg = dict(
rpn=dict(
assigner=dict(
type='MaxIoUAssigner',
pos_iou_thr=0.7,
neg_iou_thr=0.3,
min_pos_iou=0.3,
ignore_iof_thr=-1),
sampler=dict(
type='RandomSampler',
num=256,
pos_fraction=0.5,
neg_pos_ub=-1,
add_gt_as_proposals=False),
allowed_border=0,
pos_weight=-1,
smoothl1_beta=1 / 9.0,
debug=False),
rcnn=dict(
assigner=dict(
type='MaxIoUAssigner',
pos_iou_thr=0.5,
neg_iou_thr=0.5,
min_pos_iou=0.5,
ignore_iof_thr=-1),
sampler=dict(
type='OHEMSampler',
num=512,
pos_fraction=0.25,
neg_pos_ub=-1,
add_gt_as_proposals=True),
pos_weight=-1,
debug=False))
test_cfg = dict(
rpn=dict(
nms_across_levels=False,
nms_pre=2000,
nms_post=2000,
max_num=2000,
nms_thr=0.7,
min_bbox_size=0),
rcnn=dict(
score_thr=0.05, nms=dict(type='nms', iou_thr=0.5), max_per_img=100)
# soft-nms is also supported for rcnn testing
# e.g., nms=dict(type='soft_nms', iou_thr=0.5, min_score=0.05)
)
# dataset settings
dataset_type = 'CocoDataset'
data_root = 'data/coco/'
img_norm_cfg = dict(
mean=[123.675, 116.28, 103.53], std=[58.395, 57.12, 57.375], to_rgb=True)
data = dict(
imgs_per_gpu=2,
workers_per_gpu=2,
train=dict(
type=dataset_type,
ann_file=data_root + 'annotations/instances_train2017.json',
img_prefix=data_root + 'train2017/',
img_scale=(1333, 800),
img_norm_cfg=img_norm_cfg,
size_divisor=32,
flip_ratio=0.5,
with_mask=False,
with_crowd=True,
with_label=True),
val=dict(
type=dataset_type,
ann_file=data_root + 'annotations/instances_val2017.json',
img_prefix=data_root + 'val2017/',
img_scale=(1333, 800),
img_norm_cfg=img_norm_cfg,
size_divisor=32,
flip_ratio=0,
with_mask=False,
with_crowd=True,
with_label=True),
test=dict(
type=dataset_type,
ann_file=data_root + 'annotations/instances_val2017.json',
img_prefix=data_root + 'val2017/',
img_scale=(1333, 800),
img_norm_cfg=img_norm_cfg,
size_divisor=32,
flip_ratio=0,
with_mask=False,
with_label=False,
test_mode=True))
# optimizer
optimizer = dict(type='SGD', lr=0.02, momentum=0.9, weight_decay=0.0001)
optimizer_config = dict(grad_clip=dict(max_norm=35, norm_type=2))
# learning policy
lr_config = dict(
policy='step',
warmup='linear',
warmup_iters=500,
warmup_ratio=1.0 / 3,
step=[8, 11])
checkpoint_config = dict(interval=1)
# yapf:disable
log_config = dict(
interval=50,
hooks=[
dict(type='TextLoggerHook'),
# dict(type='TensorboardLoggerHook')
])
# yapf:enable
# runtime settings
total_epochs = 12
dist_params = dict(backend='nccl')
log_level = 'INFO'
work_dir = './work_dirs/faster_rcnn_r50_fpn_1x'
load_from = None
resume_from = None
workflow = [('train', 1)]
# model settings
model = dict(
type='FasterRCNN',
pretrained='modelzoo://resnet50',
backbone=dict(
type='ResNet',
depth=50,
num_stages=4,
out_indices=(0, 1, 2, 3),
frozen_stages=1,
style='pytorch'),
neck=dict(
type='FPN',
in_channels=[256, 512, 1024, 2048],
out_channels=256,
num_outs=5),
rpn_head=dict(
type='RPNHead',
in_channels=256,
feat_channels=256,
anchor_scales=[8],
anchor_ratios=[0.5, 1.0, 2.0],
anchor_strides=[4, 8, 16, 32, 64],
target_means=[.0, .0, .0, .0],
target_stds=[1.0, 1.0, 1.0, 1.0],
use_sigmoid_cls=True),
bbox_roi_extractor=dict(
type='SingleRoIExtractor',
roi_layer=dict(type='RoIAlign', out_size=7, sample_num=2),
out_channels=256,
featmap_strides=[4, 8, 16, 32]),
bbox_head=dict(
type='SharedFCBBoxHead',
num_fcs=2,
in_channels=256,
fc_out_channels=1024,
roi_feat_size=7,
num_classes=21,
target_means=[0., 0., 0., 0.],
target_stds=[0.1, 0.1, 0.2, 0.2],
reg_class_agnostic=False))
# model training and testing settings
train_cfg = dict(
rpn=dict(
assigner=dict(
type='MaxIoUAssigner',
pos_iou_thr=0.7,
neg_iou_thr=0.3,
min_pos_iou=0.3,
ignore_iof_thr=-1),
sampler=dict(
type='RandomSampler',
num=256,
pos_fraction=0.5,
neg_pos_ub=-1,
add_gt_as_proposals=False),
allowed_border=0,
pos_weight=-1,
smoothl1_beta=1 / 9.0,
debug=False),
rcnn=dict(
assigner=dict(
type='MaxIoUAssigner',
pos_iou_thr=0.5,
neg_iou_thr=0.5,
min_pos_iou=0.5,
ignore_iof_thr=-1),
sampler=dict(
type='RandomSampler',
num=512,
pos_fraction=0.25,
neg_pos_ub=-1,
add_gt_as_proposals=True),
pos_weight=-1,
debug=False))
test_cfg = dict(
rpn=dict(
nms_across_levels=False,
nms_pre=2000,
nms_post=2000,
max_num=2000,
nms_thr=0.7,
min_bbox_size=0),
rcnn=dict(
score_thr=0.05, nms=dict(type='nms', iou_thr=0.5), max_per_img=100)
# soft-nms is also supported for rcnn testing
# e.g., nms=dict(type='soft_nms', iou_thr=0.5, min_score=0.05)
)
# dataset settings
dataset_type = 'VOCDataset'
data_root = 'data/VOCdevkit/'
img_norm_cfg = dict(
mean=[123.675, 116.28, 103.53], std=[58.395, 57.12, 57.375], to_rgb=True)
data = dict(
imgs_per_gpu=2,
workers_per_gpu=2,
train=dict(
type='RepeatDataset', # to avoid reloading datasets frequently
times=3,
dataset=dict(
type=dataset_type,
ann_file=[
data_root + 'VOC2007/ImageSets/Main/trainval.txt',
data_root + 'VOC2012/ImageSets/Main/trainval.txt'
],
img_prefix=[data_root + 'VOC2007/', data_root + 'VOC2012/'],
img_scale=(1000, 600),
img_norm_cfg=img_norm_cfg,
size_divisor=32,
flip_ratio=0.5,
with_mask=False,
with_crowd=True,
with_label=True)),
val=dict(
type=dataset_type,
ann_file=data_root + 'VOC2007/ImageSets/Main/test.txt',
img_prefix=data_root + 'VOC2007/',
img_scale=(1000, 600),
img_norm_cfg=img_norm_cfg,
size_divisor=32,
flip_ratio=0,
with_mask=False,
with_crowd=True,
with_label=True),
test=dict(
type=dataset_type,
ann_file=data_root + 'VOC2007/ImageSets/Main/test.txt',
img_prefix=data_root + 'VOC2007/',
img_scale=(1000, 600),
img_norm_cfg=img_norm_cfg,
size_divisor=32,
flip_ratio=0,
with_mask=False,
with_label=False,
test_mode=True))
# optimizer
optimizer = dict(type='SGD', lr=0.01, momentum=0.9, weight_decay=0.0001)
optimizer_config = dict(grad_clip=dict(max_norm=35, norm_type=2))
# learning policy
lr_config = dict(policy='step', step=[3]) # actual epoch = 3 * 3 = 9
checkpoint_config = dict(interval=1)
# yapf:disable
log_config = dict(
interval=50,
hooks=[
dict(type='TextLoggerHook'),
# dict(type='TensorboardLoggerHook')
])
# yapf:enable
# runtime settings
total_epochs = 4 # actual epoch = 4 * 3 = 12
dist_params = dict(backend='nccl')
log_level = 'INFO'
work_dir = './work_dirs/faster_rcnn_r50_fpn_1x_voc0712'
load_from = None
resume_from = None
workflow = [('train', 1)]
...@@ -6,8 +6,8 @@ import torch ...@@ -6,8 +6,8 @@ import torch
from mmcv.runner import Runner, DistSamplerSeedHook from mmcv.runner import Runner, DistSamplerSeedHook
from mmcv.parallel import MMDataParallel, MMDistributedDataParallel from mmcv.parallel import MMDataParallel, MMDistributedDataParallel
from mmdet.core import (DistOptimizerHook, CocoDistEvalRecallHook, from mmdet.core import (DistOptimizerHook, DistEvalmAPHook,
CocoDistEvalmAPHook) CocoDistEvalRecallHook, CocoDistEvalmAPHook)
from mmdet.datasets import build_dataloader from mmdet.datasets import build_dataloader
from mmdet.models import RPN from mmdet.models import RPN
from .env import get_root_logger from .env import get_root_logger
...@@ -81,9 +81,13 @@ def _dist_train(model, dataset, cfg, validate=False): ...@@ -81,9 +81,13 @@ def _dist_train(model, dataset, cfg, validate=False):
# register eval hooks # register eval hooks
if validate: if validate:
if isinstance(model.module, RPN): if isinstance(model.module, RPN):
# TODO: implement recall hooks for other datasets
runner.register_hook(CocoDistEvalRecallHook(cfg.data.val)) runner.register_hook(CocoDistEvalRecallHook(cfg.data.val))
elif cfg.data.val.type == 'CocoDataset': else:
if cfg.data.val.type == 'CocoDataset':
runner.register_hook(CocoDistEvalmAPHook(cfg.data.val)) runner.register_hook(CocoDistEvalmAPHook(cfg.data.val))
else:
runner.register_hook(DistEvalmAPHook(cfg.data.val))
if cfg.resume_from: if cfg.resume_from:
runner.resume(cfg.resume_from) runner.resume(cfg.resume_from)
......
...@@ -3,23 +3,23 @@ import mmcv ...@@ -3,23 +3,23 @@ import mmcv
from . import assigners, samplers from . import assigners, samplers
def build_assigner(cfg, default_args=None): def build_assigner(cfg, **kwargs):
if isinstance(cfg, assigners.BaseAssigner): if isinstance(cfg, assigners.BaseAssigner):
return cfg return cfg
elif isinstance(cfg, dict): elif isinstance(cfg, dict):
return mmcv.runner.obj_from_dict( return mmcv.runner.obj_from_dict(
cfg, assigners, default_args=default_args) cfg, assigners, default_args=kwargs)
else: else:
raise TypeError('Invalid type {} for building a sampler'.format( raise TypeError('Invalid type {} for building a sampler'.format(
type(cfg))) type(cfg)))
def build_sampler(cfg, default_args=None): def build_sampler(cfg, **kwargs):
if isinstance(cfg, samplers.BaseSampler): if isinstance(cfg, samplers.BaseSampler):
return cfg return cfg
elif isinstance(cfg, dict): elif isinstance(cfg, dict):
return mmcv.runner.obj_from_dict( return mmcv.runner.obj_from_dict(
cfg, samplers, default_args=default_args) cfg, samplers, default_args=kwargs)
else: else:
raise TypeError('Invalid type {} for building a sampler'.format( raise TypeError('Invalid type {} for building a sampler'.format(
type(cfg))) type(cfg)))
......
...@@ -4,10 +4,11 @@ from .random_sampler import RandomSampler ...@@ -4,10 +4,11 @@ from .random_sampler import RandomSampler
from .instance_balanced_pos_sampler import InstanceBalancedPosSampler from .instance_balanced_pos_sampler import InstanceBalancedPosSampler
from .iou_balanced_neg_sampler import IoUBalancedNegSampler from .iou_balanced_neg_sampler import IoUBalancedNegSampler
from .combined_sampler import CombinedSampler from .combined_sampler import CombinedSampler
from .ohem_sampler import OHEMSampler
from .sampling_result import SamplingResult from .sampling_result import SamplingResult
__all__ = [ __all__ = [
'BaseSampler', 'PseudoSampler', 'RandomSampler', 'BaseSampler', 'PseudoSampler', 'RandomSampler',
'InstanceBalancedPosSampler', 'IoUBalancedNegSampler', 'CombinedSampler', 'InstanceBalancedPosSampler', 'IoUBalancedNegSampler', 'CombinedSampler',
'SamplingResult' 'OHEMSampler', 'SamplingResult'
] ]
...@@ -7,19 +7,33 @@ from .sampling_result import SamplingResult ...@@ -7,19 +7,33 @@ from .sampling_result import SamplingResult
class BaseSampler(metaclass=ABCMeta): class BaseSampler(metaclass=ABCMeta):
def __init__(self): def __init__(self,
num,
pos_fraction,
neg_pos_ub=-1,
add_gt_as_proposals=True,
**kwargs):
self.num = num
self.pos_fraction = pos_fraction
self.neg_pos_ub = neg_pos_ub
self.add_gt_as_proposals = add_gt_as_proposals
self.pos_sampler = self self.pos_sampler = self
self.neg_sampler = self self.neg_sampler = self
@abstractmethod @abstractmethod
def _sample_pos(self, assign_result, num_expected): def _sample_pos(self, assign_result, num_expected, **kwargs):
pass pass
@abstractmethod @abstractmethod
def _sample_neg(self, assign_result, num_expected): def _sample_neg(self, assign_result, num_expected, **kwargs):
pass pass
def sample(self, assign_result, bboxes, gt_bboxes, gt_labels=None): def sample(self,
assign_result,
bboxes,
gt_bboxes,
gt_labels=None,
**kwargs):
"""Sample positive and negative bboxes. """Sample positive and negative bboxes.
This is a simple implementation of bbox sampling given candidates, This is a simple implementation of bbox sampling given candidates,
...@@ -44,8 +58,8 @@ class BaseSampler(metaclass=ABCMeta): ...@@ -44,8 +58,8 @@ class BaseSampler(metaclass=ABCMeta):
gt_flags = torch.cat([gt_ones, gt_flags]) gt_flags = torch.cat([gt_ones, gt_flags])
num_expected_pos = int(self.num * self.pos_fraction) num_expected_pos = int(self.num * self.pos_fraction)
pos_inds = self.pos_sampler._sample_pos(assign_result, pos_inds = self.pos_sampler._sample_pos(
num_expected_pos) assign_result, num_expected_pos, bboxes=bboxes, **kwargs)
# We found that sampled indices have duplicated items occasionally. # We found that sampled indices have duplicated items occasionally.
# (may be a bug of PyTorch) # (may be a bug of PyTorch)
pos_inds = pos_inds.unique() pos_inds = pos_inds.unique()
...@@ -56,8 +70,8 @@ class BaseSampler(metaclass=ABCMeta): ...@@ -56,8 +70,8 @@ class BaseSampler(metaclass=ABCMeta):
neg_upper_bound = int(self.neg_pos_ub * _pos) neg_upper_bound = int(self.neg_pos_ub * _pos)
if num_expected_neg > neg_upper_bound: if num_expected_neg > neg_upper_bound:
num_expected_neg = neg_upper_bound num_expected_neg = neg_upper_bound
neg_inds = self.neg_sampler._sample_neg(assign_result, neg_inds = self.neg_sampler._sample_neg(
num_expected_neg) assign_result, num_expected_neg, bboxes=bboxes, **kwargs)
neg_inds = neg_inds.unique() neg_inds = neg_inds.unique()
return SamplingResult(pos_inds, neg_inds, bboxes, gt_bboxes, return SamplingResult(pos_inds, neg_inds, bboxes, gt_bboxes,
......
from .random_sampler import RandomSampler from .base_sampler import BaseSampler
from ..assign_sampling import build_sampler from ..assign_sampling import build_sampler
class CombinedSampler(RandomSampler): class CombinedSampler(BaseSampler):
def __init__(self, num, pos_fraction, pos_sampler, neg_sampler, **kwargs): def __init__(self, pos_sampler, neg_sampler, **kwargs):
super(CombinedSampler, self).__init__(num, pos_fraction, **kwargs) super(CombinedSampler, self).__init__(**kwargs)
default_args = dict(num=num, pos_fraction=pos_fraction) self.pos_sampler = build_sampler(pos_sampler, **kwargs)
default_args.update(kwargs) self.neg_sampler = build_sampler(neg_sampler, **kwargs)
self.pos_sampler = build_sampler(
pos_sampler, default_args=default_args) def _sample_pos(self, **kwargs):
self.neg_sampler = build_sampler( raise NotImplementedError
neg_sampler, default_args=default_args)
def _sample_neg(self, **kwargs):
raise NotImplementedError
...@@ -6,7 +6,7 @@ from .random_sampler import RandomSampler ...@@ -6,7 +6,7 @@ from .random_sampler import RandomSampler
class InstanceBalancedPosSampler(RandomSampler): class InstanceBalancedPosSampler(RandomSampler):
def _sample_pos(self, assign_result, num_expected): def _sample_pos(self, assign_result, num_expected, **kwargs):
pos_inds = torch.nonzero(assign_result.gt_inds > 0) pos_inds = torch.nonzero(assign_result.gt_inds > 0)
if pos_inds.numel() != 0: if pos_inds.numel() != 0:
pos_inds = pos_inds.squeeze(1) pos_inds = pos_inds.squeeze(1)
......
...@@ -19,7 +19,7 @@ class IoUBalancedNegSampler(RandomSampler): ...@@ -19,7 +19,7 @@ class IoUBalancedNegSampler(RandomSampler):
self.hard_thr = hard_thr self.hard_thr = hard_thr
self.hard_fraction = hard_fraction self.hard_fraction = hard_fraction
def _sample_neg(self, assign_result, num_expected): def _sample_neg(self, assign_result, num_expected, **kwargs):
neg_inds = torch.nonzero(assign_result.gt_inds == 0) neg_inds = torch.nonzero(assign_result.gt_inds == 0)
if neg_inds.numel() != 0: if neg_inds.numel() != 0:
neg_inds = neg_inds.squeeze(1) neg_inds = neg_inds.squeeze(1)
......
import torch
from .base_sampler import BaseSampler
from ..transforms import bbox2roi
class OHEMSampler(BaseSampler):
def __init__(self,
num,
pos_fraction,
context,
neg_pos_ub=-1,
add_gt_as_proposals=True,
**kwargs):
super(OHEMSampler, self).__init__(num, pos_fraction, neg_pos_ub,
add_gt_as_proposals)
self.bbox_roi_extractor = context.bbox_roi_extractor
self.bbox_head = context.bbox_head
def hard_mining(self, inds, num_expected, bboxes, labels, feats):
with torch.no_grad():
rois = bbox2roi([bboxes])
bbox_feats = self.bbox_roi_extractor(
feats[:self.bbox_roi_extractor.num_inputs], rois)
cls_score, _ = self.bbox_head(bbox_feats)
loss = self.bbox_head.loss(
cls_score=cls_score,
bbox_pred=None,
labels=labels,
label_weights=cls_score.new_ones(cls_score.size(0)),
bbox_targets=None,
bbox_weights=None,
reduce=False)['loss_cls']
_, topk_loss_inds = loss.topk(num_expected)
return inds[topk_loss_inds]
def _sample_pos(self,
assign_result,
num_expected,
bboxes=None,
feats=None,
**kwargs):
# Sample some hard positive samples
pos_inds = torch.nonzero(assign_result.gt_inds > 0)
if pos_inds.numel() != 0:
pos_inds = pos_inds.squeeze(1)
if pos_inds.numel() <= num_expected:
return pos_inds
else:
return self.hard_mining(pos_inds, num_expected, bboxes[pos_inds],
assign_result.labels[pos_inds], feats)
def _sample_neg(self,
assign_result,
num_expected,
bboxes=None,
feats=None,
**kwargs):
# Sample some hard negative samples
neg_inds = torch.nonzero(assign_result.gt_inds == 0)
if neg_inds.numel() != 0:
neg_inds = neg_inds.squeeze(1)
if len(neg_inds) <= num_expected:
return neg_inds
else:
return self.hard_mining(neg_inds, num_expected, bboxes[neg_inds],
assign_result.labels[neg_inds], feats)
...@@ -6,16 +6,16 @@ from .sampling_result import SamplingResult ...@@ -6,16 +6,16 @@ from .sampling_result import SamplingResult
class PseudoSampler(BaseSampler): class PseudoSampler(BaseSampler):
def __init__(self): def __init__(self, **kwargs):
pass pass
def _sample_pos(self): def _sample_pos(self, **kwargs):
raise NotImplementedError raise NotImplementedError
def _sample_neg(self): def _sample_neg(self, **kwargs):
raise NotImplementedError raise NotImplementedError
def sample(self, assign_result, bboxes, gt_bboxes): def sample(self, assign_result, bboxes, gt_bboxes, **kwargs):
pos_inds = torch.nonzero( pos_inds = torch.nonzero(
assign_result.gt_inds > 0).squeeze(-1).unique() assign_result.gt_inds > 0).squeeze(-1).unique()
neg_inds = torch.nonzero( neg_inds = torch.nonzero(
......
...@@ -10,12 +10,10 @@ class RandomSampler(BaseSampler): ...@@ -10,12 +10,10 @@ class RandomSampler(BaseSampler):
num, num,
pos_fraction, pos_fraction,
neg_pos_ub=-1, neg_pos_ub=-1,
add_gt_as_proposals=True): add_gt_as_proposals=True,
super(RandomSampler, self).__init__() **kwargs):
self.num = num super(RandomSampler, self).__init__(num, pos_fraction, neg_pos_ub,
self.pos_fraction = pos_fraction add_gt_as_proposals)
self.neg_pos_ub = neg_pos_ub
self.add_gt_as_proposals = add_gt_as_proposals
@staticmethod @staticmethod
def random_choice(gallery, num): def random_choice(gallery, num):
...@@ -34,7 +32,7 @@ class RandomSampler(BaseSampler): ...@@ -34,7 +32,7 @@ class RandomSampler(BaseSampler):
rand_inds = torch.from_numpy(rand_inds).long().to(gallery.device) rand_inds = torch.from_numpy(rand_inds).long().to(gallery.device)
return gallery[rand_inds] return gallery[rand_inds]
def _sample_pos(self, assign_result, num_expected): def _sample_pos(self, assign_result, num_expected, **kwargs):
"""Randomly sample some positive samples.""" """Randomly sample some positive samples."""
pos_inds = torch.nonzero(assign_result.gt_inds > 0) pos_inds = torch.nonzero(assign_result.gt_inds > 0)
if pos_inds.numel() != 0: if pos_inds.numel() != 0:
...@@ -44,7 +42,7 @@ class RandomSampler(BaseSampler): ...@@ -44,7 +42,7 @@ class RandomSampler(BaseSampler):
else: else:
return self.random_choice(pos_inds, num_expected) return self.random_choice(pos_inds, num_expected)
def _sample_neg(self, assign_result, num_expected): def _sample_neg(self, assign_result, num_expected, **kwargs):
"""Randomly sample some negative samples.""" """Randomly sample some negative samples."""
neg_inds = torch.nonzero(assign_result.gt_inds == 0) neg_inds = torch.nonzero(assign_result.gt_inds == 0)
if neg_inds.numel() != 0: if neg_inds.numel() != 0:
......
...@@ -2,7 +2,7 @@ from .class_names import (voc_classes, imagenet_det_classes, ...@@ -2,7 +2,7 @@ from .class_names import (voc_classes, imagenet_det_classes,
imagenet_vid_classes, coco_classes, dataset_aliases, imagenet_vid_classes, coco_classes, dataset_aliases,
get_classes) get_classes)
from .coco_utils import coco_eval, fast_eval_recall, results2json from .coco_utils import coco_eval, fast_eval_recall, results2json
from .eval_hooks import (DistEvalHook, CocoDistEvalRecallHook, from .eval_hooks import (DistEvalHook, DistEvalmAPHook, CocoDistEvalRecallHook,
CocoDistEvalmAPHook) CocoDistEvalmAPHook)
from .mean_ap import average_precision, eval_map, print_map_summary from .mean_ap import average_precision, eval_map, print_map_summary
from .recall import (eval_recalls, print_recall_summary, plot_num_recall, from .recall import (eval_recalls, print_recall_summary, plot_num_recall,
...@@ -11,7 +11,7 @@ from .recall import (eval_recalls, print_recall_summary, plot_num_recall, ...@@ -11,7 +11,7 @@ from .recall import (eval_recalls, print_recall_summary, plot_num_recall,
__all__ = [ __all__ = [
'voc_classes', 'imagenet_det_classes', 'imagenet_vid_classes', 'voc_classes', 'imagenet_det_classes', 'imagenet_vid_classes',
'coco_classes', 'dataset_aliases', 'get_classes', 'coco_eval', 'coco_classes', 'dataset_aliases', 'get_classes', 'coco_eval',
'fast_eval_recall', 'results2json', 'DistEvalHook', 'fast_eval_recall', 'results2json', 'DistEvalHook', 'DistEvalmAPHook',
'CocoDistEvalRecallHook', 'CocoDistEvalmAPHook', 'average_precision', 'CocoDistEvalRecallHook', 'CocoDistEvalmAPHook', 'average_precision',
'eval_map', 'print_map_summary', 'eval_recalls', 'print_recall_summary', 'eval_map', 'print_map_summary', 'eval_recalls', 'print_recall_summary',
'plot_num_recall', 'plot_iou_recall' 'plot_num_recall', 'plot_iou_recall'
......
...@@ -63,18 +63,18 @@ def imagenet_vid_classes(): ...@@ -63,18 +63,18 @@ def imagenet_vid_classes():
def coco_classes(): def coco_classes():
return [ return [
'person', 'bicycle', 'car', 'motorcycle', 'airplane', 'bus', 'train', 'person', 'bicycle', 'car', 'motorcycle', 'airplane', 'bus', 'train',
'truck', 'boat', 'traffic light', 'fire hydrant', 'stop sign', 'truck', 'boat', 'traffic_light', 'fire_hydrant', 'stop_sign',
'parking meter', 'bench', 'bird', 'cat', 'dog', 'horse', 'sheep', 'parking_meter', 'bench', 'bird', 'cat', 'dog', 'horse', 'sheep',
'cow', 'elephant', 'bear', 'zebra', 'giraffe', 'backpack', 'umbrella', 'cow', 'elephant', 'bear', 'zebra', 'giraffe', 'backpack', 'umbrella',
'handbag', 'tie', 'suitcase', 'frisbee', 'skis', 'snowboard', 'handbag', 'tie', 'suitcase', 'frisbee', 'skis', 'snowboard',
'sports ball', 'kite', 'baseball bat', 'baseball glove', 'skateboard', 'sports_ball', 'kite', 'baseball_bat', 'baseball_glove', 'skateboard',
'surfboard', 'tennis racket', 'bottle', 'wine glass', 'cup', 'fork', 'surfboard', 'tennis_racket', 'bottle', 'wine_glass', 'cup', 'fork',
'knife', 'spoon', 'bowl', 'banana', 'apple', 'sandwich', 'orange', 'knife', 'spoon', 'bowl', 'banana', 'apple', 'sandwich', 'orange',
'broccoli', 'carrot', 'hot dog', 'pizza', 'donut', 'cake', 'chair', 'broccoli', 'carrot', 'hot_dog', 'pizza', 'donut', 'cake', 'chair',
'couch', 'potted plant', 'bed', 'dining table', 'toilet', 'tv', 'couch', 'potted_plant', 'bed', 'dining_table', 'toilet', 'tv',
'laptop', 'mouse', 'remote', 'keyboard', 'cell phone', 'microwave', 'laptop', 'mouse', 'remote', 'keyboard', 'cell_phone', 'microwave',
'oven', 'toaster', 'sink', 'refrigerator', 'book', 'clock', 'vase', 'oven', 'toaster', 'sink', 'refrigerator', 'book', 'clock', 'vase',
'scissors', 'teddy bear', 'hair drier', 'toothbrush' 'scissors', 'teddy_bear', 'hair_drier', 'toothbrush'
] ]
......
...@@ -12,6 +12,7 @@ from pycocotools.cocoeval import COCOeval ...@@ -12,6 +12,7 @@ from pycocotools.cocoeval import COCOeval
from torch.utils.data import Dataset from torch.utils.data import Dataset
from .coco_utils import results2json, fast_eval_recall from .coco_utils import results2json, fast_eval_recall
from .mean_ap import eval_map
from mmdet import datasets from mmdet import datasets
...@@ -102,6 +103,44 @@ class DistEvalHook(Hook): ...@@ -102,6 +103,44 @@ class DistEvalHook(Hook):
raise NotImplementedError raise NotImplementedError
class DistEvalmAPHook(DistEvalHook):
def evaluate(self, runner, results):
gt_bboxes = []
gt_labels = []
gt_ignore = [] if self.dataset.with_crowd else None
for i in range(len(self.dataset)):
ann = self.dataset.get_ann_info(i)
bboxes = ann['bboxes']
labels = ann['labels']
if gt_ignore is not None:
ignore = np.concatenate([
np.zeros(bboxes.shape[0], dtype=np.bool),
np.ones(ann['bboxes_ignore'].shape[0], dtype=np.bool)
])
gt_ignore.append(ignore)
bboxes = np.vstack([bboxes, ann['bboxes_ignore']])
labels = np.concatenate([labels, ann['labels_ignore']])
gt_bboxes.append(bboxes)
gt_labels.append(labels)
# If the dataset is VOC2007, then use 11 points mAP evaluation.
if hasattr(self.dataset, 'year') and self.dataset.year == 2007:
ds_name = 'voc07'
else:
ds_name = self.dataset.CLASSES
mean_ap, eval_results = eval_map(
results,
gt_bboxes,
gt_labels,
gt_ignore=gt_ignore,
scale_ranges=None,
iou_thr=0.5,
dataset=ds_name,
print_summary=True)
runner.log_buffer.output['mAP'] = mean_ap
runner.log_buffer.ready = True
class CocoDistEvalRecallHook(DistEvalHook): class CocoDistEvalRecallHook(DistEvalHook):
def __init__(self, def __init__(self,
......
...@@ -10,11 +10,15 @@ def weighted_nll_loss(pred, label, weight, avg_factor=None): ...@@ -10,11 +10,15 @@ def weighted_nll_loss(pred, label, weight, avg_factor=None):
return torch.sum(raw * weight)[None] / avg_factor return torch.sum(raw * weight)[None] / avg_factor
def weighted_cross_entropy(pred, label, weight, avg_factor=None): def weighted_cross_entropy(pred, label, weight, avg_factor=None,
reduce=True):
if avg_factor is None: if avg_factor is None:
avg_factor = max(torch.sum(weight > 0).float().item(), 1.) avg_factor = max(torch.sum(weight > 0).float().item(), 1.)
raw = F.cross_entropy(pred, label, reduction='none') raw = F.cross_entropy(pred, label, reduction='none')
if reduce:
return torch.sum(raw * weight)[None] / avg_factor return torch.sum(raw * weight)[None] / avg_factor
else:
return raw * weight / avg_factor
def weighted_binary_cross_entropy(pred, label, weight, avg_factor=None): def weighted_binary_cross_entropy(pred, label, weight, avg_factor=None):
......
from .custom import CustomDataset from .custom import CustomDataset
from .xml_style import XMLDataset
from .coco import CocoDataset from .coco import CocoDataset
from .voc import VOCDataset
from .loader import GroupSampler, DistributedGroupSampler, build_dataloader from .loader import GroupSampler, DistributedGroupSampler, build_dataloader
from .utils import to_tensor, random_scale, show_ann, get_dataset from .utils import to_tensor, random_scale, show_ann, get_dataset
from .concat_dataset import ConcatDataset from .concat_dataset import ConcatDataset
from .repeat_dataset import RepeatDataset from .repeat_dataset import RepeatDataset
__all__ = [ __all__ = [
'CustomDataset', 'CocoDataset', 'GroupSampler', 'DistributedGroupSampler', 'CustomDataset', 'XMLDataset', 'CocoDataset', 'VOCDataset', 'GroupSampler',
'build_dataloader', 'to_tensor', 'random_scale', 'show_ann', 'DistributedGroupSampler', 'build_dataloader', 'to_tensor', 'random_scale',
'get_dataset', 'ConcatDataset', 'RepeatDataset', 'show_ann', 'get_dataset', 'ConcatDataset', 'RepeatDataset'
] ]
...@@ -6,6 +6,21 @@ from .custom import CustomDataset ...@@ -6,6 +6,21 @@ from .custom import CustomDataset
class CocoDataset(CustomDataset): class CocoDataset(CustomDataset):
CLASSES = ('person', 'bicycle', 'car', 'motorcycle', 'airplane', 'bus',
'train', 'truck', 'boat', 'traffic_light', 'fire_hydrant',
'stop_sign', 'parking_meter', 'bench', 'bird', 'cat', 'dog',
'horse', 'sheep', 'cow', 'elephant', 'bear', 'zebra', 'giraffe',
'backpack', 'umbrella', 'handbag', 'tie', 'suitcase', 'frisbee',
'skis', 'snowboard', 'sports_ball', 'kite', 'baseball_bat',
'baseball_glove', 'skateboard', 'surfboard', 'tennis_racket',
'bottle', 'wine_glass', 'cup', 'fork', 'knife', 'spoon', 'bowl',
'banana', 'apple', 'sandwich', 'orange', 'broccoli', 'carrot',
'hot_dog', 'pizza', 'donut', 'cake', 'chair', 'couch',
'potted_plant', 'bed', 'dining_table', 'toilet', 'tv', 'laptop',
'mouse', 'remote', 'keyboard', 'cell_phone', 'microwave',
'oven', 'toaster', 'sink', 'refrigerator', 'book', 'clock',
'vase', 'scissors', 'teddy_bear', 'hair_drier', 'toothbrush')
def load_annotations(self, ann_file): def load_annotations(self, ann_file):
self.coco = COCO(ann_file) self.coco = COCO(ann_file)
self.cat_ids = self.coco.getCatIds() self.cat_ids = self.coco.getCatIds()
......
...@@ -3,16 +3,18 @@ from torch.utils.data.dataset import ConcatDataset as _ConcatDataset ...@@ -3,16 +3,18 @@ from torch.utils.data.dataset import ConcatDataset as _ConcatDataset
class ConcatDataset(_ConcatDataset): class ConcatDataset(_ConcatDataset):
""" """A wrapper of concatenated dataset.
Same as torch.utils.data.dataset.ConcatDataset, but
Same as :obj:`torch.utils.data.dataset.ConcatDataset`, but
concat the group flag for image aspect ratio. concat the group flag for image aspect ratio.
Args:
datasets (list[:obj:`Dataset`]): A list of datasets.
""" """
def __init__(self, datasets): def __init__(self, datasets):
"""
flag: Images with aspect ratio greater than 1 will be set as group 1,
otherwise group 0.
"""
super(ConcatDataset, self).__init__(datasets) super(ConcatDataset, self).__init__(datasets)
self.CLASSES = datasets[0].CLASSES
if hasattr(datasets[0], 'flag'): if hasattr(datasets[0], 'flag'):
flags = [] flags = []
for i in range(0, len(datasets)): for i in range(0, len(datasets)):
......
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