From 0f6fa3998cdd5d9478b3ae23fb170232e8b31770 Mon Sep 17 00:00:00 2001 From: Vasilis Vryniotis Date: Fri, 18 Feb 2022 21:35:49 +0000 Subject: [PATCH 01/29] Use frozen BN only if pre-trained. --- torchvision/models/detection/faster_rcnn.py | 18 ++++++++---------- torchvision/models/detection/fcos.py | 8 ++++---- torchvision/models/detection/keypoint_rcnn.py | 8 ++++---- torchvision/models/detection/mask_rcnn.py | 8 ++++---- torchvision/models/detection/retinanet.py | 8 ++++---- .../prototype/models/detection/faster_rcnn.py | 17 +++++++++-------- torchvision/prototype/models/detection/fcos.py | 9 +++++---- .../models/detection/keypoint_rcnn.py | 9 +++++---- .../prototype/models/detection/mask_rcnn.py | 9 +++++---- .../prototype/models/detection/retinanet.py | 9 +++++---- 10 files changed, 53 insertions(+), 50 deletions(-) diff --git a/torchvision/models/detection/faster_rcnn.py b/torchvision/models/detection/faster_rcnn.py index 8f2a96e2be1..790740fe9c5 100644 --- a/torchvision/models/detection/faster_rcnn.py +++ b/torchvision/models/detection/faster_rcnn.py @@ -383,15 +383,15 @@ def fasterrcnn_resnet50_fpn( Valid values are between 0 and 5, with 5 meaning all backbone layers are trainable. If ``None`` is passed (the default) this value is set to 3. """ - trainable_backbone_layers = _validate_trainable_layers( - pretrained or pretrained_backbone, trainable_backbone_layers, 5, 3 - ) + is_trained = pretrained or pretrained_backbone + trainable_backbone_layers = _validate_trainable_layers(is_trained, trainable_backbone_layers, 5, 3) + norm_layer = misc_nn_ops.FrozenBatchNorm2d if is_trained else nn.BatchNorm2d if pretrained: # no need to download the backbone if pretrained is set pretrained_backbone = False - backbone = resnet50(pretrained=pretrained_backbone, progress=progress, norm_layer=misc_nn_ops.FrozenBatchNorm2d) + backbone = resnet50(pretrained=pretrained_backbone, progress=progress, norm_layer=norm_layer) backbone = _resnet_fpn_extractor(backbone, trainable_backbone_layers) model = FasterRCNN(backbone, num_classes, **kwargs) if pretrained: @@ -410,16 +410,14 @@ def _fasterrcnn_mobilenet_v3_large_fpn( trainable_backbone_layers=None, **kwargs, ): - trainable_backbone_layers = _validate_trainable_layers( - pretrained or pretrained_backbone, trainable_backbone_layers, 6, 3 - ) + is_trained = pretrained or pretrained_backbone + trainable_backbone_layers = _validate_trainable_layers(is_trained, trainable_backbone_layers, 6, 3) + norm_layer = misc_nn_ops.FrozenBatchNorm2d if is_trained else nn.BatchNorm2d if pretrained: pretrained_backbone = False - backbone = mobilenet_v3_large( - pretrained=pretrained_backbone, progress=progress, norm_layer=misc_nn_ops.FrozenBatchNorm2d - ) + backbone = mobilenet_v3_large(pretrained=pretrained_backbone, progress=progress, norm_layer=norm_layer) backbone = _mobilenet_extractor(backbone, True, trainable_backbone_layers) anchor_sizes = ( diff --git a/torchvision/models/detection/fcos.py b/torchvision/models/detection/fcos.py index 91baf1d0b29..1508ec84b66 100644 --- a/torchvision/models/detection/fcos.py +++ b/torchvision/models/detection/fcos.py @@ -686,15 +686,15 @@ def fcos_resnet50_fpn( from final block. Valid values are between 0 and 5, with 5 meaning all backbone layers are trainable. If ``None`` is passed (the default) this value is set to 3. Default: None """ - trainable_backbone_layers = _validate_trainable_layers( - pretrained or pretrained_backbone, trainable_backbone_layers, 5, 3 - ) + is_trained = pretrained or pretrained_backbone + trainable_backbone_layers = _validate_trainable_layers(is_trained, trainable_backbone_layers, 5, 3) + norm_layer = misc_nn_ops.FrozenBatchNorm2d if is_trained else nn.BatchNorm2d if pretrained: # no need to download the backbone if pretrained is set pretrained_backbone = False - backbone = resnet50(pretrained=pretrained_backbone, progress=progress, norm_layer=misc_nn_ops.FrozenBatchNorm2d) + backbone = resnet50(pretrained=pretrained_backbone, progress=progress, norm_layer=norm_layer) backbone = _resnet_fpn_extractor( backbone, trainable_backbone_layers, returned_layers=[2, 3, 4], extra_blocks=LastLevelP6P7(256, 256) ) diff --git a/torchvision/models/detection/keypoint_rcnn.py b/torchvision/models/detection/keypoint_rcnn.py index 93e966bae4b..9f23e66e0c5 100644 --- a/torchvision/models/detection/keypoint_rcnn.py +++ b/torchvision/models/detection/keypoint_rcnn.py @@ -365,15 +365,15 @@ def keypointrcnn_resnet50_fpn( Valid values are between 0 and 5, with 5 meaning all backbone layers are trainable. If ``None`` is passed (the default) this value is set to 3. """ - trainable_backbone_layers = _validate_trainable_layers( - pretrained or pretrained_backbone, trainable_backbone_layers, 5, 3 - ) + is_trained = pretrained or pretrained_backbone + trainable_backbone_layers = _validate_trainable_layers(is_trained, trainable_backbone_layers, 5, 3) + norm_layer = misc_nn_ops.FrozenBatchNorm2d if is_trained else nn.BatchNorm2d if pretrained: # no need to download the backbone if pretrained is set pretrained_backbone = False - backbone = resnet50(pretrained=pretrained_backbone, progress=progress, norm_layer=misc_nn_ops.FrozenBatchNorm2d) + backbone = resnet50(pretrained=pretrained_backbone, progress=progress, norm_layer=norm_layer) backbone = _resnet_fpn_extractor(backbone, trainable_backbone_layers) model = KeypointRCNN(backbone, num_classes, num_keypoints=num_keypoints, **kwargs) if pretrained: diff --git a/torchvision/models/detection/mask_rcnn.py b/torchvision/models/detection/mask_rcnn.py index f4278cfb502..37f88116c5e 100644 --- a/torchvision/models/detection/mask_rcnn.py +++ b/torchvision/models/detection/mask_rcnn.py @@ -360,15 +360,15 @@ def maskrcnn_resnet50_fpn( Valid values are between 0 and 5, with 5 meaning all backbone layers are trainable. If ``None`` is passed (the default) this value is set to 3. """ - trainable_backbone_layers = _validate_trainable_layers( - pretrained or pretrained_backbone, trainable_backbone_layers, 5, 3 - ) + is_trained = pretrained or pretrained_backbone + trainable_backbone_layers = _validate_trainable_layers(is_trained, trainable_backbone_layers, 5, 3) + norm_layer = misc_nn_ops.FrozenBatchNorm2d if is_trained else nn.BatchNorm2d if pretrained: # no need to download the backbone if pretrained is set pretrained_backbone = False - backbone = resnet50(pretrained=pretrained_backbone, progress=progress, norm_layer=misc_nn_ops.FrozenBatchNorm2d) + backbone = resnet50(pretrained=pretrained_backbone, progress=progress, norm_layer=norm_layer) backbone = _resnet_fpn_extractor(backbone, trainable_backbone_layers) model = MaskRCNN(backbone, num_classes, **kwargs) if pretrained: diff --git a/torchvision/models/detection/retinanet.py b/torchvision/models/detection/retinanet.py index e5ced9870ba..92fa231e401 100644 --- a/torchvision/models/detection/retinanet.py +++ b/torchvision/models/detection/retinanet.py @@ -626,15 +626,15 @@ def retinanet_resnet50_fpn( Valid values are between 0 and 5, with 5 meaning all backbone layers are trainable. If ``None`` is passed (the default) this value is set to 3. """ - trainable_backbone_layers = _validate_trainable_layers( - pretrained or pretrained_backbone, trainable_backbone_layers, 5, 3 - ) + is_trained = pretrained or pretrained_backbone + trainable_backbone_layers = _validate_trainable_layers(is_trained, trainable_backbone_layers, 5, 3) + norm_layer = misc_nn_ops.FrozenBatchNorm2d if is_trained else nn.BatchNorm2d if pretrained: # no need to download the backbone if pretrained is set pretrained_backbone = False - backbone = resnet50(pretrained=pretrained_backbone, progress=progress, norm_layer=misc_nn_ops.FrozenBatchNorm2d) + backbone = resnet50(pretrained=pretrained_backbone, progress=progress, norm_layer=norm_layer) # skip P2 because it generates too many anchors (according to their paper) backbone = _resnet_fpn_extractor( backbone, trainable_backbone_layers, returned_layers=[2, 3, 4], extra_blocks=LastLevelP6P7(256, 256) diff --git a/torchvision/prototype/models/detection/faster_rcnn.py b/torchvision/prototype/models/detection/faster_rcnn.py index 764cc3fe042..4fbbbc0c1e8 100644 --- a/torchvision/prototype/models/detection/faster_rcnn.py +++ b/torchvision/prototype/models/detection/faster_rcnn.py @@ -1,5 +1,6 @@ from typing import Any, Optional, Union +from torch import nn from torchvision.prototype.transforms import CocoEval from torchvision.transforms.functional import InterpolationMode @@ -103,11 +104,11 @@ def fasterrcnn_resnet50_fpn( elif num_classes is None: num_classes = 91 - trainable_backbone_layers = _validate_trainable_layers( - weights is not None or weights_backbone is not None, trainable_backbone_layers, 5, 3 - ) + is_trained = weights is not None or weights_backbone is not None + trainable_backbone_layers = _validate_trainable_layers(is_trained, trainable_backbone_layers, 5, 3) + norm_layer = misc_nn_ops.FrozenBatchNorm2d if is_trained else nn.BatchNorm2d - backbone = resnet50(weights=weights_backbone, progress=progress, norm_layer=misc_nn_ops.FrozenBatchNorm2d) + backbone = resnet50(weights=weights_backbone, progress=progress, norm_layer=norm_layer) backbone = _resnet_fpn_extractor(backbone, trainable_backbone_layers) model = FasterRCNN(backbone, num_classes=num_classes, **kwargs) @@ -134,11 +135,11 @@ def _fasterrcnn_mobilenet_v3_large_fpn( elif num_classes is None: num_classes = 91 - trainable_backbone_layers = _validate_trainable_layers( - weights is not None or weights_backbone is not None, trainable_backbone_layers, 6, 3 - ) + is_trained = weights is not None or weights_backbone is not None + trainable_backbone_layers = _validate_trainable_layers(is_trained, trainable_backbone_layers, 6, 3) + norm_layer = misc_nn_ops.FrozenBatchNorm2d if is_trained else nn.BatchNorm2d - backbone = mobilenet_v3_large(weights=weights_backbone, progress=progress, norm_layer=misc_nn_ops.FrozenBatchNorm2d) + backbone = mobilenet_v3_large(weights=weights_backbone, progress=progress, norm_layer=norm_layer) backbone = _mobilenet_extractor(backbone, True, trainable_backbone_layers) anchor_sizes = ( ( diff --git a/torchvision/prototype/models/detection/fcos.py b/torchvision/prototype/models/detection/fcos.py index cf3007290a8..faa181b60b0 100644 --- a/torchvision/prototype/models/detection/fcos.py +++ b/torchvision/prototype/models/detection/fcos.py @@ -1,5 +1,6 @@ from typing import Any, Optional +from torch import nn from torchvision.prototype.transforms import CocoEval from torchvision.transforms.functional import InterpolationMode @@ -63,11 +64,11 @@ def fcos_resnet50_fpn( elif num_classes is None: num_classes = 91 - trainable_backbone_layers = _validate_trainable_layers( - weights is not None or weights_backbone is not None, trainable_backbone_layers, 5, 3 - ) + is_trained = weights is not None or weights_backbone is not None + trainable_backbone_layers = _validate_trainable_layers(is_trained, trainable_backbone_layers, 5, 3) + norm_layer = misc_nn_ops.FrozenBatchNorm2d if is_trained else nn.BatchNorm2d - backbone = resnet50(weights=weights_backbone, progress=progress, norm_layer=misc_nn_ops.FrozenBatchNorm2d) + backbone = resnet50(weights=weights_backbone, progress=progress, norm_layer=norm_layer) backbone = _resnet_fpn_extractor( backbone, trainable_backbone_layers, returned_layers=[2, 3, 4], extra_blocks=LastLevelP6P7(256, 256) ) diff --git a/torchvision/prototype/models/detection/keypoint_rcnn.py b/torchvision/prototype/models/detection/keypoint_rcnn.py index 976cccadd39..c10d761fa26 100644 --- a/torchvision/prototype/models/detection/keypoint_rcnn.py +++ b/torchvision/prototype/models/detection/keypoint_rcnn.py @@ -1,5 +1,6 @@ from typing import Any, Optional +from torch import nn from torchvision.prototype.transforms import CocoEval from torchvision.transforms.functional import InterpolationMode @@ -91,11 +92,11 @@ def keypointrcnn_resnet50_fpn( if num_keypoints is None: num_keypoints = 17 - trainable_backbone_layers = _validate_trainable_layers( - weights is not None or weights_backbone is not None, trainable_backbone_layers, 5, 3 - ) + is_trained = weights is not None or weights_backbone is not None + trainable_backbone_layers = _validate_trainable_layers(is_trained, trainable_backbone_layers, 5, 3) + norm_layer = misc_nn_ops.FrozenBatchNorm2d if is_trained else nn.BatchNorm2d - backbone = resnet50(weights=weights_backbone, progress=progress, norm_layer=misc_nn_ops.FrozenBatchNorm2d) + backbone = resnet50(weights=weights_backbone, progress=progress, norm_layer=norm_layer) backbone = _resnet_fpn_extractor(backbone, trainable_backbone_layers) model = KeypointRCNN(backbone, num_classes, num_keypoints=num_keypoints, **kwargs) diff --git a/torchvision/prototype/models/detection/mask_rcnn.py b/torchvision/prototype/models/detection/mask_rcnn.py index af67f21c3e1..3e438dab160 100644 --- a/torchvision/prototype/models/detection/mask_rcnn.py +++ b/torchvision/prototype/models/detection/mask_rcnn.py @@ -1,5 +1,6 @@ from typing import Any, Optional +from torch import nn from torchvision.prototype.transforms import CocoEval from torchvision.transforms.functional import InterpolationMode @@ -64,11 +65,11 @@ def maskrcnn_resnet50_fpn( elif num_classes is None: num_classes = 91 - trainable_backbone_layers = _validate_trainable_layers( - weights is not None or weights_backbone is not None, trainable_backbone_layers, 5, 3 - ) + is_trained = weights is not None or weights_backbone is not None + trainable_backbone_layers = _validate_trainable_layers(is_trained, trainable_backbone_layers, 5, 3) + norm_layer = misc_nn_ops.FrozenBatchNorm2d if is_trained else nn.BatchNorm2d - backbone = resnet50(weights=weights_backbone, progress=progress, norm_layer=misc_nn_ops.FrozenBatchNorm2d) + backbone = resnet50(weights=weights_backbone, progress=progress, norm_layer=norm_layer) backbone = _resnet_fpn_extractor(backbone, trainable_backbone_layers) model = MaskRCNN(backbone, num_classes=num_classes, **kwargs) diff --git a/torchvision/prototype/models/detection/retinanet.py b/torchvision/prototype/models/detection/retinanet.py index b0c02b1e30c..b819150ade0 100644 --- a/torchvision/prototype/models/detection/retinanet.py +++ b/torchvision/prototype/models/detection/retinanet.py @@ -1,5 +1,6 @@ from typing import Any, Optional +from torch import nn from torchvision.prototype.transforms import CocoEval from torchvision.transforms.functional import InterpolationMode @@ -64,11 +65,11 @@ def retinanet_resnet50_fpn( elif num_classes is None: num_classes = 91 - trainable_backbone_layers = _validate_trainable_layers( - weights is not None or weights_backbone is not None, trainable_backbone_layers, 5, 3 - ) + is_trained = weights is not None or weights_backbone is not None + trainable_backbone_layers = _validate_trainable_layers(is_trained, trainable_backbone_layers, 5, 3) + norm_layer = misc_nn_ops.FrozenBatchNorm2d if is_trained else nn.BatchNorm2d - backbone = resnet50(weights=weights_backbone, progress=progress, norm_layer=misc_nn_ops.FrozenBatchNorm2d) + backbone = resnet50(weights=weights_backbone, progress=progress, norm_layer=norm_layer) # skip P2 because it generates too many anchors (according to their paper) backbone = _resnet_fpn_extractor( backbone, trainable_backbone_layers, returned_layers=[2, 3, 4], extra_blocks=LastLevelP6P7(256, 256) From 7a94595006e60e0c4145ee156b3b69d4446b25a2 Mon Sep 17 00:00:00 2001 From: Vasilis Vryniotis Date: Sat, 19 Feb 2022 11:31:53 +0000 Subject: [PATCH 02/29] Add LSJ and ability to from scratch training. --- references/detection/presets.py | 9 +++++++++ references/detection/train.py | 7 ++++--- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/references/detection/presets.py b/references/detection/presets.py index 88d8c697d2a..9e184712b65 100644 --- a/references/detection/presets.py +++ b/references/detection/presets.py @@ -12,6 +12,15 @@ def __init__(self, data_augmentation, hflip_prob=0.5, mean=(123.0, 117.0, 104.0) T.ConvertImageDtype(torch.float), ] ) + elif data_augmentation == "lsj": + self.transforms = T.Compose( + [ + T.ScaleJitter(target_size=(1024, 1024)), + T.RandomHorizontalFlip(p=hflip_prob), + T.PILToTensor(), + T.ConvertImageDtype(torch.float), + ] + ) elif data_augmentation == "ssd": self.transforms = T.Compose( [ diff --git a/references/detection/train.py b/references/detection/train.py index 765f8144364..be9f296827e 100644 --- a/references/detection/train.py +++ b/references/detection/train.py @@ -151,6 +151,7 @@ def get_args_parser(add_help=True): action="store_true", ) parser.add_argument("--weights", default=None, type=str, help="the weights enum name to load") + parser.add_argument("--weights-backbone", default=None, type=str, help="the backbone weights enum name to load") # Mixed precision training parameters parser.add_argument("--amp", action="store_true", help="Use torch.cuda.amp for mixed precision training") @@ -161,8 +162,8 @@ def get_args_parser(add_help=True): def main(args): if args.prototype and prototype is None: raise ImportError("The prototype module couldn't be found. Please install the latest torchvision nightly.") - if not args.prototype and args.weights: - raise ValueError("The weights parameter works only in prototype mode. Please pass the --prototype argument.") + if not args.prototype and (args.weights or args.weights_backbone): + raise ValueError("The weights parameters works only in prototype mode. Please pass the --prototype argument.") if args.output_dir: utils.mkdir(args.output_dir) @@ -209,7 +210,7 @@ def main(args): pretrained=args.pretrained, num_classes=num_classes, **kwargs ) else: - model = prototype.models.detection.__dict__[args.model](weights=args.weights, num_classes=num_classes, **kwargs) + model = prototype.models.detection.__dict__[args.model](weights=args.weights, weights_backbone=args.weights_backbone, num_classes=num_classes, **kwargs) model.to(device) if args.distributed and args.sync_bn: model = torch.nn.SyncBatchNorm.convert_sync_batchnorm(model) From 89a5b9df806ed8f9e27c9ce9646b7d8a00a344bc Mon Sep 17 00:00:00 2001 From: Vasilis Vryniotis Date: Sat, 19 Feb 2022 11:47:50 +0000 Subject: [PATCH 03/29] Fixing formatter --- references/detection/train.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/references/detection/train.py b/references/detection/train.py index be9f296827e..0cf38a7a82d 100644 --- a/references/detection/train.py +++ b/references/detection/train.py @@ -210,7 +210,9 @@ def main(args): pretrained=args.pretrained, num_classes=num_classes, **kwargs ) else: - model = prototype.models.detection.__dict__[args.model](weights=args.weights, weights_backbone=args.weights_backbone, num_classes=num_classes, **kwargs) + model = prototype.models.detection.__dict__[args.model]( + weights=args.weights, weights_backbone=args.weights_backbone, num_classes=num_classes, **kwargs + ) model.to(device) if args.distributed and args.sync_bn: model = torch.nn.SyncBatchNorm.convert_sync_batchnorm(model) From 8537c4868325a18926c40fdaae8049ae63c49dc6 Mon Sep 17 00:00:00 2001 From: Vasilis Vryniotis Date: Sat, 5 Mar 2022 11:26:03 +0000 Subject: [PATCH 04/29] Adding `--opt` and `--norm-weight-decay` support in Detection. --- references/classification/train.py | 2 +- references/detection/train.py | 23 +++++++++++++++++++++-- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/references/classification/train.py b/references/classification/train.py index b00a11fcac3..365c308decf 100644 --- a/references/classification/train.py +++ b/references/classification/train.py @@ -248,7 +248,7 @@ def main(args): criterion = nn.CrossEntropyLoss(label_smoothing=args.label_smoothing) if args.norm_weight_decay is None: - parameters = model.parameters() + parameters = [p for p in model.parameters() if p.requires_grad] else: param_groups = torchvision.ops._utils.split_normalization_params(model) wd_groups = [args.norm_weight_decay, args.weight_decay] diff --git a/references/detection/train.py b/references/detection/train.py index 0cf38a7a82d..4e40ec23494 100644 --- a/references/detection/train.py +++ b/references/detection/train.py @@ -76,6 +76,7 @@ def get_args_parser(add_help=True): parser.add_argument( "-j", "--workers", default=4, type=int, metavar="N", help="number of data loading workers (default: 4)" ) + parser.add_argument("--opt", default="sgd", type=str, help="optimizer") parser.add_argument( "--lr", default=0.02, @@ -92,6 +93,12 @@ def get_args_parser(add_help=True): help="weight decay (default: 1e-4)", dest="weight_decay", ) + parser.add_argument( + "--norm-weight-decay", + default=None, + type=float, + help="weight decay for Normalization layers (default: None, same value as --wd)", + ) parser.add_argument( "--lr-scheduler", default="multisteplr", type=str, help="name of lr scheduler (default: multisteplr)" ) @@ -222,8 +229,20 @@ def main(args): model = torch.nn.parallel.DistributedDataParallel(model, device_ids=[args.gpu]) model_without_ddp = model.module - params = [p for p in model.parameters() if p.requires_grad] - optimizer = torch.optim.SGD(params, lr=args.lr, momentum=args.momentum, weight_decay=args.weight_decay) + if args.norm_weight_decay is None: + parameters = [p for p in model.parameters() if p.requires_grad] + else: + param_groups = torchvision.ops._utils.split_normalization_params(model) + wd_groups = [args.norm_weight_decay, args.weight_decay] + parameters = [{"params": p, "weight_decay": w} for p, w in zip(param_groups, wd_groups) if p] + + opt_name = args.opt.lower() + if opt_name.startswith("sgd"): + optimizer = torch.optim.SGD(parameters, lr=args.lr, momentum=args.momentum, weight_decay=args.weight_decay) + elif opt_name == "adamw": + optimizer = torch.optim.AdamW(parameters, lr=args.lr, weight_decay=args.weight_decay) + else: + raise RuntimeError(f"Invalid optimizer {args.opt}. Only SGD, RMSprop and AdamW are supported.") scaler = torch.cuda.amp.GradScaler() if args.amp else None From f7f8e2f29f3acf932bce85fbfc264b4a55bb047c Mon Sep 17 00:00:00 2001 From: Vasilis Vryniotis Date: Sat, 5 Mar 2022 12:35:11 +0000 Subject: [PATCH 05/29] Fix error message --- references/detection/train.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/references/detection/train.py b/references/detection/train.py index 4e40ec23494..cd0fc6eeea2 100644 --- a/references/detection/train.py +++ b/references/detection/train.py @@ -242,7 +242,7 @@ def main(args): elif opt_name == "adamw": optimizer = torch.optim.AdamW(parameters, lr=args.lr, weight_decay=args.weight_decay) else: - raise RuntimeError(f"Invalid optimizer {args.opt}. Only SGD, RMSprop and AdamW are supported.") + raise RuntimeError(f"Invalid optimizer {args.opt}. Only SGD and AdamW are supported.") scaler = torch.cuda.amp.GradScaler() if args.amp else None From ed2a24c5828b569e4bfed801f7f3c6384a908978 Mon Sep 17 00:00:00 2001 From: Vasilis Vryniotis Date: Sun, 6 Mar 2022 12:59:37 +0000 Subject: [PATCH 06/29] Make ScaleJitter proportional. --- references/detection/transforms.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/references/detection/transforms.py b/references/detection/transforms.py index 1b39910d11e..48cc53d928c 100644 --- a/references/detection/transforms.py +++ b/references/detection/transforms.py @@ -310,9 +310,10 @@ def forward( _, orig_height, orig_width = F.get_dimensions(image) - r = self.scale_range[0] + torch.rand(1) * (self.scale_range[1] - self.scale_range[0]) - new_width = int(self.target_size[1] * r) - new_height = int(self.target_size[0] * r) + scale = self.scale_range[0] + torch.rand(1) * (self.scale_range[1] - self.scale_range[0]) + r = min(self.target_size[1] / orig_height, self.target_size[0] / orig_width) * scale + new_width = int(orig_width * r) + new_height = int(orig_height * r) image = F.resize(image, [new_height, new_width], interpolation=self.interpolation) From bcf0afc21a645de0117e4c890efaa1ca4d16e3d0 Mon Sep 17 00:00:00 2001 From: Vasilis Vryniotis Date: Tue, 8 Mar 2022 00:04:49 +0000 Subject: [PATCH 07/29] Adding more norm layers in split_normalization_params. --- torchvision/ops/_utils.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/torchvision/ops/_utils.py b/torchvision/ops/_utils.py index 3a07c747f58..332b557b7f1 100644 --- a/torchvision/ops/_utils.py +++ b/torchvision/ops/_utils.py @@ -43,7 +43,13 @@ def split_normalization_params( ) -> Tuple[List[Tensor], List[Tensor]]: # Adapted from https://github.com/facebookresearch/ClassyVision/blob/659d7f78/classy_vision/generic/util.py#L501 if not norm_classes: - norm_classes = [nn.modules.batchnorm._BatchNorm, nn.LayerNorm, nn.GroupNorm] + norm_classes = [ + nn.modules.batchnorm._BatchNorm, + nn.LayerNorm, + nn.GroupNorm, + nn.modules.instancenorm._InstanceNorm, + nn.LocalResponseNorm, + ] for t in norm_classes: if not issubclass(t, nn.Module): From 65e4116f967daea8073e8a72a9544e509ba7b9a1 Mon Sep 17 00:00:00 2001 From: Vasilis Vryniotis Date: Thu, 10 Mar 2022 20:33:30 +0000 Subject: [PATCH 08/29] Add FixedSizeCrop --- references/detection/presets.py | 1 + references/detection/transforms.py | 75 ++++++++++++++++++++++++++++++ 2 files changed, 76 insertions(+) diff --git a/references/detection/presets.py b/references/detection/presets.py index 67df27553e0..98e13766db7 100644 --- a/references/detection/presets.py +++ b/references/detection/presets.py @@ -16,6 +16,7 @@ def __init__(self, data_augmentation, hflip_prob=0.5, mean=(123.0, 117.0, 104.0) self.transforms = T.Compose( [ T.ScaleJitter(target_size=(1024, 1024)), + T.FixedSizeCrop(size=(1024, 1024), fill=mean), T.RandomHorizontalFlip(p=hflip_prob), T.PILToTensor(), T.ConvertImageDtype(torch.float), diff --git a/references/detection/transforms.py b/references/detection/transforms.py index 48cc53d928c..3e63316ddd2 100644 --- a/references/detection/transforms.py +++ b/references/detection/transforms.py @@ -326,3 +326,78 @@ def forward( ) return image, target + + +class FixedSizeCrop(nn.Module): + def __init__(self, size, fill=0, padding_mode="constant"): + super().__init__() + size = tuple(T._setup_size(size, error_msg="Please provide only two dimensions (h, w) for size.")) + self.crop_height = size[0] + self.crop_width = size[1] + self.fill = fill # TODO: Fill is currently respected only on PIL. Apply tensor patch. + self.padding_mode = padding_mode + + def _pad(self, img, target, padding): + # Taken from the functional_tensor.py pad + if isinstance(padding, int): + pad_left = pad_right = pad_top = pad_bottom = padding + elif len(padding) == 1: + pad_left = pad_right = pad_top = pad_bottom = padding[0] + elif len(padding) == 2: + pad_left = pad_right = padding[0] + pad_top = pad_bottom = padding[1] + else: + pad_left = padding[0] + pad_top = padding[1] + pad_right = padding[2] + pad_bottom = padding[3] + + padding = [pad_left, pad_top, pad_right, pad_bottom] + img = F.pad(img, padding, self.fill, self.padding_mode) + if target is not None: + target["boxes"][:, 0::2] += pad_left + target["boxes"][:, 1::2] += pad_top + if "masks" in target: + target["masks"] = F.pad(target["masks"], padding, 0, "constant") + + return img, target + + def _crop(self, img, target, top, left, height, width): + img = F.crop(img, top, left, height, width) + if target is not None: + boxes = target["boxes"] + boxes[:, 0::2] -= left + boxes[:, 1::2] -= top + boxes[:, 0::2].clamp_(min=0, max=width) + boxes[:, 1::2].clamp_(min=0, max=height) + + is_valid = (boxes[:, 0] < boxes[:, 2]) & (boxes[:, 1] < boxes[:, 3]) + + target["boxes"] = boxes[is_valid] + target["labels"] = target["labels"][is_valid] + if "masks" in target: + target["masks"] = F.crop(target["masks"][is_valid], top, left, height, width) + + return img, target + + def forward(self, img, target=None): + _, height, width = F.get_dimensions(img) + new_height = min(height, self.crop_height) + new_width = min(width, self.crop_width) + + if new_height != height or new_width != width: + offset_height = max(height - self.crop_height, 0) + offset_width = max(width - self.crop_width, 0) + + r = torch.rand(1) + top = int(offset_height * r) + left = int(offset_width * r) + + img, target = self._crop(img, target, top, left, new_height, new_width) + + pad_bottom = max(self.crop_height - new_height, 0) + pad_right = max(self.crop_width - new_width, 0) + if pad_bottom != 0 or pad_right != 0: + img, target = self._pad(img, target, [0, 0, pad_right, pad_bottom]) + + return img, target From ab63af61b7e2cfee99f995eacbd4ac00d778846e Mon Sep 17 00:00:00 2001 From: Vasilis Vryniotis Date: Thu, 10 Mar 2022 21:20:28 +0000 Subject: [PATCH 09/29] Temporary fix for fill values on PIL --- references/detection/transforms.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/references/detection/transforms.py b/references/detection/transforms.py index 3e63316ddd2..0b469695b2a 100644 --- a/references/detection/transforms.py +++ b/references/detection/transforms.py @@ -334,7 +334,7 @@ def __init__(self, size, fill=0, padding_mode="constant"): size = tuple(T._setup_size(size, error_msg="Please provide only two dimensions (h, w) for size.")) self.crop_height = size[0] self.crop_width = size[1] - self.fill = fill # TODO: Fill is currently respected only on PIL. Apply tensor patch. + self.fill = tuple(int(x) for x in fill) # TODO: Fill is currently respected only on PIL. Apply tensor patch. self.padding_mode = padding_mode def _pad(self, img, target, padding): From c714c66f02b6b07a8710a5da540e7a391396018c Mon Sep 17 00:00:00 2001 From: Vasilis Vryniotis Date: Fri, 11 Mar 2022 18:54:56 +0000 Subject: [PATCH 10/29] Fix the bug on fill. --- references/detection/transforms.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/references/detection/transforms.py b/references/detection/transforms.py index 0b469695b2a..3e63316ddd2 100644 --- a/references/detection/transforms.py +++ b/references/detection/transforms.py @@ -334,7 +334,7 @@ def __init__(self, size, fill=0, padding_mode="constant"): size = tuple(T._setup_size(size, error_msg="Please provide only two dimensions (h, w) for size.")) self.crop_height = size[0] self.crop_width = size[1] - self.fill = tuple(int(x) for x in fill) # TODO: Fill is currently respected only on PIL. Apply tensor patch. + self.fill = fill # TODO: Fill is currently respected only on PIL. Apply tensor patch. self.padding_mode = padding_mode def _pad(self, img, target, padding): From 13fb5b398383d5394ee9bff4ef3f3c7a501f07af Mon Sep 17 00:00:00 2001 From: Vasilis Vryniotis Date: Sat, 12 Mar 2022 12:46:48 +0000 Subject: [PATCH 11/29] Add RandomShortestSize. --- references/detection/presets.py | 9 +++++ references/detection/train.py | 2 + references/detection/transforms.py | 38 ++++++++++++++++++- torchvision/models/detection/faster_rcnn.py | 3 +- torchvision/models/detection/fcos.py | 3 +- torchvision/models/detection/keypoint_rcnn.py | 2 + torchvision/models/detection/mask_rcnn.py | 2 + torchvision/models/detection/retinanet.py | 3 +- torchvision/models/detection/ssd.py | 3 +- torchvision/models/detection/transform.py | 6 ++- 10 files changed, 65 insertions(+), 6 deletions(-) diff --git a/references/detection/presets.py b/references/detection/presets.py index 98e13766db7..436ad257d97 100644 --- a/references/detection/presets.py +++ b/references/detection/presets.py @@ -22,6 +22,15 @@ def __init__(self, data_augmentation, hflip_prob=0.5, mean=(123.0, 117.0, 104.0) T.ConvertImageDtype(torch.float), ] ) + elif data_augmentation == "multiscale": + self.transforms = T.Compose( + [ + T.RandomShortestSize(min_size=(640, 672, 704, 736, 768, 800), max_size=1333), + T.RandomHorizontalFlip(p=hflip_prob), + T.PILToTensor(), + T.ConvertImageDtype(torch.float), + ] + ) elif data_augmentation == "ssd": self.transforms = T.Compose( [ diff --git a/references/detection/train.py b/references/detection/train.py index eefb5e29514..bb66812b899 100644 --- a/references/detection/train.py +++ b/references/detection/train.py @@ -209,6 +209,8 @@ def main(args): print("Creating model") kwargs = {"trainable_backbone_layers": args.trainable_backbone_layers} + if args.data_augmentation == "multiscale": + kwargs["_skip_resize"] = True if "rcnn" in args.model: if args.rpn_score_thresh is not None: kwargs["rpn_score_thresh"] = args.rpn_score_thresh diff --git a/references/detection/transforms.py b/references/detection/transforms.py index 3e63316ddd2..bb4540d1aae 100644 --- a/references/detection/transforms.py +++ b/references/detection/transforms.py @@ -1,4 +1,4 @@ -from typing import List, Tuple, Dict, Optional +from typing import List, Tuple, Dict, Optional, Union import torch import torchvision @@ -401,3 +401,39 @@ def forward(self, img, target=None): img, target = self._pad(img, target, [0, 0, pad_right, pad_bottom]) return img, target + + +class RandomShortestSize(nn.Module): + def __init__( + self, + min_size: Union[List[int], Tuple[int], int], + max_size: int, + interpolation: InterpolationMode = InterpolationMode.BILINEAR, + ): + super().__init__() + self.min_size = [min_size] if isinstance(min_size, int) else list(min_size) + self.max_size = max_size + self.interpolation = interpolation + + def forward( + self, image: Tensor, target: Optional[Dict[str, Tensor]] = None + ) -> Tuple[Tensor, Optional[Dict[str, Tensor]]]: + _, orig_height, orig_width = F.get_dimensions(image) + + min_size = self.min_size[torch.randint(len(self.min_size), (1,)).item()] + r = min(min_size / min(orig_height, orig_width), self.max_size / max(orig_height, orig_width)) + + new_width = int(orig_width * r) + new_height = int(orig_height * r) + + image = F.resize(image, [new_height, new_width], interpolation=self.interpolation) + + if target is not None: + target["boxes"][:, 0::2] *= new_width / orig_width + target["boxes"][:, 1::2] *= new_height / orig_height + if "masks" in target: + target["masks"] = F.resize( + target["masks"], [new_height, new_width], interpolation=InterpolationMode.NEAREST + ) + + return image, target diff --git a/torchvision/models/detection/faster_rcnn.py b/torchvision/models/detection/faster_rcnn.py index 790740fe9c5..f235dbabd60 100644 --- a/torchvision/models/detection/faster_rcnn.py +++ b/torchvision/models/detection/faster_rcnn.py @@ -178,6 +178,7 @@ def __init__( box_batch_size_per_image=512, box_positive_fraction=0.25, bbox_reg_weights=None, + **kwargs, ): if not hasattr(backbone, "out_channels"): @@ -253,7 +254,7 @@ def __init__( image_mean = [0.485, 0.456, 0.406] if image_std is None: image_std = [0.229, 0.224, 0.225] - transform = GeneralizedRCNNTransform(min_size, max_size, image_mean, image_std) + transform = GeneralizedRCNNTransform(min_size, max_size, image_mean, image_std, **kwargs) super().__init__(backbone, rpn, roi_heads, transform) diff --git a/torchvision/models/detection/fcos.py b/torchvision/models/detection/fcos.py index c4c2e6f5842..2651f1f0e29 100644 --- a/torchvision/models/detection/fcos.py +++ b/torchvision/models/detection/fcos.py @@ -366,6 +366,7 @@ def __init__( nms_thresh: float = 0.6, detections_per_img: int = 100, topk_candidates: int = 1000, + **kwargs, ): super().__init__() _log_api_usage_once(self) @@ -397,7 +398,7 @@ def __init__( image_mean = [0.485, 0.456, 0.406] if image_std is None: image_std = [0.229, 0.224, 0.225] - self.transform = GeneralizedRCNNTransform(min_size, max_size, image_mean, image_std) + self.transform = GeneralizedRCNNTransform(min_size, max_size, image_mean, image_std, **kwargs) self.center_sampling_radius = center_sampling_radius self.score_thresh = score_thresh diff --git a/torchvision/models/detection/keypoint_rcnn.py b/torchvision/models/detection/keypoint_rcnn.py index 9f23e66e0c5..6cdc23a71ea 100644 --- a/torchvision/models/detection/keypoint_rcnn.py +++ b/torchvision/models/detection/keypoint_rcnn.py @@ -189,6 +189,7 @@ def __init__( keypoint_head=None, keypoint_predictor=None, num_keypoints=None, + **kwargs, ): assert isinstance(keypoint_roi_pool, (MultiScaleRoIAlign, type(None))) @@ -247,6 +248,7 @@ def __init__( box_batch_size_per_image, box_positive_fraction, bbox_reg_weights, + **kwargs, ) self.roi_heads.keypoint_roi_pool = keypoint_roi_pool diff --git a/torchvision/models/detection/mask_rcnn.py b/torchvision/models/detection/mask_rcnn.py index 37f88116c5e..4b6d4def01b 100644 --- a/torchvision/models/detection/mask_rcnn.py +++ b/torchvision/models/detection/mask_rcnn.py @@ -189,6 +189,7 @@ def __init__( mask_roi_pool=None, mask_head=None, mask_predictor=None, + **kwargs, ): assert isinstance(mask_roi_pool, (MultiScaleRoIAlign, type(None))) @@ -245,6 +246,7 @@ def __init__( box_batch_size_per_image, box_positive_fraction, bbox_reg_weights, + **kwargs, ) self.roi_heads.mask_roi_pool = mask_roi_pool diff --git a/torchvision/models/detection/retinanet.py b/torchvision/models/detection/retinanet.py index 4f79b5ddbfc..a80af7803ce 100644 --- a/torchvision/models/detection/retinanet.py +++ b/torchvision/models/detection/retinanet.py @@ -335,6 +335,7 @@ def __init__( fg_iou_thresh=0.5, bg_iou_thresh=0.4, topk_candidates=1000, + **kwargs, ): super().__init__() _log_api_usage_once(self) @@ -373,7 +374,7 @@ def __init__( image_mean = [0.485, 0.456, 0.406] if image_std is None: image_std = [0.229, 0.224, 0.225] - self.transform = GeneralizedRCNNTransform(min_size, max_size, image_mean, image_std) + self.transform = GeneralizedRCNNTransform(min_size, max_size, image_mean, image_std, **kwargs) self.score_thresh = score_thresh self.nms_thresh = nms_thresh diff --git a/torchvision/models/detection/ssd.py b/torchvision/models/detection/ssd.py index 08a9ed68e4e..395f3bca47e 100644 --- a/torchvision/models/detection/ssd.py +++ b/torchvision/models/detection/ssd.py @@ -180,6 +180,7 @@ def __init__( iou_thresh: float = 0.5, topk_candidates: int = 400, positive_fraction: float = 0.25, + **kwargs: Any, ): super().__init__() _log_api_usage_once(self) @@ -209,7 +210,7 @@ def __init__( if image_std is None: image_std = [0.229, 0.224, 0.225] self.transform = GeneralizedRCNNTransform( - min(size), max(size), image_mean, image_std, size_divisible=1, fixed_size=size + min(size), max(size), image_mean, image_std, size_divisible=1, fixed_size=size, **kwargs ) self.score_thresh = score_thresh diff --git a/torchvision/models/detection/transform.py b/torchvision/models/detection/transform.py index 960e28500a1..fa121bad52d 100644 --- a/torchvision/models/detection/transform.py +++ b/torchvision/models/detection/transform.py @@ -1,5 +1,5 @@ import math -from typing import List, Tuple, Dict, Optional +from typing import List, Tuple, Dict, Optional, Any import torch import torchvision @@ -91,6 +91,7 @@ def __init__( image_std: List[float], size_divisible: int = 32, fixed_size: Optional[Tuple[int, int]] = None, + **kwargs: Any, ): super().__init__() if not isinstance(min_size, (list, tuple)): @@ -101,6 +102,7 @@ def __init__( self.image_std = image_std self.size_divisible = size_divisible self.fixed_size = fixed_size + self._skip_resize = kwargs.pop("_skip_resize", False) def forward( self, images: List[Tensor], targets: Optional[List[Dict[str, Tensor]]] = None @@ -167,6 +169,8 @@ def resize( ) -> Tuple[Tensor, Optional[Dict[str, Tensor]]]: h, w = image.shape[-2:] if self.training: + if self._skip_resize: + return image, target size = float(self.torch_choice(self.min_size)) else: # FIXME assume for now that testing uses the largest scale From 0d230abf51c2348e8269bd1e2105e7ae6c7ed644 Mon Sep 17 00:00:00 2001 From: Vasilis Vryniotis Date: Sat, 12 Mar 2022 17:56:29 +0000 Subject: [PATCH 12/29] Skip resize when an augmentation method is used. --- references/detection/train.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/references/detection/train.py b/references/detection/train.py index bb66812b899..91409d86634 100644 --- a/references/detection/train.py +++ b/references/detection/train.py @@ -209,7 +209,7 @@ def main(args): print("Creating model") kwargs = {"trainable_backbone_layers": args.trainable_backbone_layers} - if args.data_augmentation == "multiscale": + if args.data_augmentation in ["multiscale", "lsj"]: kwargs["_skip_resize"] = True if "rcnn" in args.model: if args.rpn_score_thresh is not None: From a187917e255c40f8526792b5f914fd116dafd02c Mon Sep 17 00:00:00 2001 From: Vasilis Vryniotis Date: Sun, 13 Mar 2022 08:51:02 +0000 Subject: [PATCH 13/29] multiscale in [480, 800] --- references/detection/presets.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/references/detection/presets.py b/references/detection/presets.py index 436ad257d97..01b29f39a31 100644 --- a/references/detection/presets.py +++ b/references/detection/presets.py @@ -25,7 +25,9 @@ def __init__(self, data_augmentation, hflip_prob=0.5, mean=(123.0, 117.0, 104.0) elif data_augmentation == "multiscale": self.transforms = T.Compose( [ - T.RandomShortestSize(min_size=(640, 672, 704, 736, 768, 800), max_size=1333), + T.RandomShortestSize( + min_size=(480, 512, 544, 576, 608, 640, 672, 704, 736, 768, 800), max_size=1333 + ), T.RandomHorizontalFlip(p=hflip_prob), T.PILToTensor(), T.ConvertImageDtype(torch.float), From 7542a94ef7bae5920f77764451e24d7f3b73166b Mon Sep 17 00:00:00 2001 From: Vasilis Vryniotis Date: Wed, 23 Mar 2022 09:23:23 +0000 Subject: [PATCH 14/29] Add missing star --- references/detection/presets.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/references/detection/presets.py b/references/detection/presets.py index 01b29f39a31..779f3f218ca 100644 --- a/references/detection/presets.py +++ b/references/detection/presets.py @@ -3,7 +3,7 @@ class DetectionPresetTrain: - def __init__(self, data_augmentation, hflip_prob=0.5, mean=(123.0, 117.0, 104.0)): + def __init__(self, *, data_augmentation, hflip_prob=0.5, mean=(123.0, 117.0, 104.0)): if data_augmentation == "hflip": self.transforms = T.Compose( [ From c67893ce05ae252c3a4930e19fc65d396da2ebba Mon Sep 17 00:00:00 2001 From: Vasilis Vryniotis Date: Wed, 23 Mar 2022 16:20:17 +0000 Subject: [PATCH 15/29] Add new RetinaNet variant. --- torchvision/models/detection/retinanet.py | 165 ++++++++++++++++++++-- 1 file changed, 152 insertions(+), 13 deletions(-) diff --git a/torchvision/models/detection/retinanet.py b/torchvision/models/detection/retinanet.py index 7dabf80aac0..5ec917a9766 100644 --- a/torchvision/models/detection/retinanet.py +++ b/torchvision/models/detection/retinanet.py @@ -1,7 +1,8 @@ import math import warnings from collections import OrderedDict -from typing import Any, Dict, List, Tuple, Optional +from functools import partial +from typing import Any, Callable, Dict, List, Tuple, Optional import torch from torch import nn, Tensor @@ -37,6 +38,21 @@ def _sum(x: List[Tensor]) -> Tensor: return res +def _v1_to_v2_weights(state_dict, prefix): + for i in range(4): + for type in ["weight", "bias"]: + old_key = f"{prefix}conv.{2*i}.{type}" + new_key = f"{prefix}conv.{i}.0.{type}" + state_dict[new_key] = state_dict.pop(old_key) + + +def _default_anchorgen(): + anchor_sizes = tuple((x, int(x * 2 ** (1.0 / 3)), int(x * 2 ** (2.0 / 3))) for x in [32, 64, 128, 256, 512]) + aspect_ratios = ((0.5, 1.0, 2.0),) * len(anchor_sizes) + anchor_generator = AnchorGenerator(anchor_sizes, aspect_ratios) + return anchor_generator + + class RetinaNetHead(nn.Module): """ A regression and classification head for use in RetinaNet. @@ -45,12 +61,15 @@ class RetinaNetHead(nn.Module): in_channels (int): number of channels of the input feature num_anchors (int): number of anchors to be predicted num_classes (int): number of classes to be predicted + norm_layer (callable, optional): Module specifying the normalization layer to use. Default: None """ - def __init__(self, in_channels, num_anchors, num_classes): + def __init__(self, in_channels, num_anchors, num_classes, norm_layer: Optional[Callable[..., nn.Module]] = None): super().__init__() - self.classification_head = RetinaNetClassificationHead(in_channels, num_anchors, num_classes) - self.regression_head = RetinaNetRegressionHead(in_channels, num_anchors) + self.classification_head = RetinaNetClassificationHead( + in_channels, num_anchors, num_classes, norm_layer=norm_layer + ) + self.regression_head = RetinaNetRegressionHead(in_channels, num_anchors, norm_layer=norm_layer) def compute_loss(self, targets, head_outputs, anchors, matched_idxs): # type: (List[Dict[str, Tensor]], Dict[str, Tensor], List[Tensor], List[Tensor]) -> Dict[str, Tensor] @@ -72,15 +91,24 @@ class RetinaNetClassificationHead(nn.Module): in_channels (int): number of channels of the input feature num_anchors (int): number of anchors to be predicted num_classes (int): number of classes to be predicted + norm_layer (callable, optional): Module specifying the normalization layer to use. Default: None """ - def __init__(self, in_channels, num_anchors, num_classes, prior_probability=0.01): + _version = 2 + + def __init__( + self, + in_channels, + num_anchors, + num_classes, + prior_probability=0.01, + norm_layer: Optional[Callable[..., nn.Module]] = None, + ): super().__init__() conv = [] for _ in range(4): - conv.append(nn.Conv2d(in_channels, in_channels, kernel_size=3, stride=1, padding=1)) - conv.append(nn.ReLU()) + conv.append(misc_nn_ops.Conv2dNormActivation(in_channels, in_channels, norm_layer=norm_layer)) self.conv = nn.Sequential(*conv) for layer in self.conv.children(): @@ -100,6 +128,31 @@ def __init__(self, in_channels, num_anchors, num_classes, prior_probability=0.01 # https://github.com/pytorch/vision/pull/1697#issuecomment-630255584 self.BETWEEN_THRESHOLDS = det_utils.Matcher.BETWEEN_THRESHOLDS + def _load_from_state_dict( + self, + state_dict, + prefix, + local_metadata, + strict, + missing_keys, + unexpected_keys, + error_msgs, + ): + version = local_metadata.get("version", None) + + if version is None or version < 2: + _v1_to_v2_weights(state_dict, prefix) + + super()._load_from_state_dict( + state_dict, + prefix, + local_metadata, + strict, + missing_keys, + unexpected_keys, + error_msgs, + ) + def compute_loss(self, targets, head_outputs, matched_idxs): # type: (List[Dict[str, Tensor]], Dict[str, Tensor], List[Tensor]) -> Tensor losses = [] @@ -159,19 +212,21 @@ class RetinaNetRegressionHead(nn.Module): Args: in_channels (int): number of channels of the input feature num_anchors (int): number of anchors to be predicted + norm_layer (callable, optional): Module specifying the normalization layer to use. Default: None """ + _version = 2 + __annotations__ = { "box_coder": det_utils.BoxCoder, } - def __init__(self, in_channels, num_anchors): + def __init__(self, in_channels, num_anchors, norm_layer: Optional[Callable[..., nn.Module]] = None): super().__init__() conv = [] for _ in range(4): - conv.append(nn.Conv2d(in_channels, in_channels, kernel_size=3, stride=1, padding=1)) - conv.append(nn.ReLU()) + conv.append(misc_nn_ops.Conv2dNormActivation(in_channels, in_channels, norm_layer=norm_layer)) self.conv = nn.Sequential(*conv) self.bbox_reg = nn.Conv2d(in_channels, num_anchors * 4, kernel_size=3, stride=1, padding=1) @@ -185,6 +240,31 @@ def __init__(self, in_channels, num_anchors): self.box_coder = det_utils.BoxCoder(weights=(1.0, 1.0, 1.0, 1.0)) + def _load_from_state_dict( + self, + state_dict, + prefix, + local_metadata, + strict, + missing_keys, + unexpected_keys, + error_msgs, + ): + version = local_metadata.get("version", None) + + if version is None or version < 2: + _v1_to_v2_weights(state_dict, prefix) + + super()._load_from_state_dict( + state_dict, + prefix, + local_metadata, + strict, + missing_keys, + unexpected_keys, + error_msgs, + ) + def compute_loss(self, targets, head_outputs, anchors, matched_idxs): # type: (List[Dict[str, Tensor]], Dict[str, Tensor], List[Tensor], List[Tensor]) -> Tensor losses = [] @@ -361,9 +441,7 @@ def __init__( ) if anchor_generator is None: - anchor_sizes = tuple((x, int(x * 2 ** (1.0 / 3)), int(x * 2 ** (2.0 / 3))) for x in [32, 64, 128, 256, 512]) - aspect_ratios = ((0.5, 1.0, 2.0),) * len(anchor_sizes) - anchor_generator = AnchorGenerator(anchor_sizes, aspect_ratios) + anchor_generator = _default_anchorgen() self.anchor_generator = anchor_generator if head is None: @@ -604,6 +682,10 @@ class RetinaNet_ResNet50_FPN_Weights(WeightsEnum): DEFAULT = COCO_V1 +class RetinaNet_ResNet50_FPN_V2_Weights(WeightsEnum): + pass + + @handle_legacy_interface( weights=("pretrained", RetinaNet_ResNet50_FPN_Weights.COCO_V1), weights_backbone=("pretrained_backbone", ResNet50_Weights.IMAGENET1K_V1), @@ -690,3 +772,60 @@ def retinanet_resnet50_fpn( overwrite_eps(model, 0.0) return model + + +def retinanet_resnet50_fpn_v2( + *, + weights: Optional[RetinaNet_ResNet50_FPN_V2_Weights] = None, + progress: bool = True, + num_classes: Optional[int] = None, + weights_backbone: Optional[ResNet50_Weights] = None, + trainable_backbone_layers: Optional[int] = None, + **kwargs: Any, +) -> RetinaNet: + """ + Constructs an improved RetinaNet model with a ResNet-50-FPN backbone. + + Reference: `"Bridging the Gap Between Anchor-based and Anchor-free Detection via Adaptive Training Sample Selection" + `_. + + :func:`~torchvision.models.detection.retinanet_resnet50_fpn` for more details. + + Args: + weights (RetinaNet_ResNet50_FPN_V2_Weights, optional): The pretrained weights for the model + progress (bool): If True, displays a progress bar of the download to stderr + num_classes (int, optional): number of output classes of the model (including the background) + weights_backbone (ResNet50_Weights, optional): The pretrained weights for the backbone + trainable_backbone_layers (int, optional): number of trainable (not frozen) layers starting from final block. + Valid values are between 0 and 5, with 5 meaning all backbone layers are trainable. If ``None`` is + passed (the default) this value is set to 3. + """ + weights = RetinaNet_ResNet50_FPN_V2_Weights.verify(weights) + weights_backbone = ResNet50_Weights.verify(weights_backbone) + + if weights is not None: + weights_backbone = None + num_classes = _ovewrite_value_param(num_classes, len(weights.meta["categories"])) + elif num_classes is None: + num_classes = 91 + + is_trained = weights is not None or weights_backbone is not None + trainable_backbone_layers = _validate_trainable_layers(is_trained, trainable_backbone_layers, 5, 3) + + backbone = resnet50(weights=weights_backbone, progress=progress) + backbone = _resnet_fpn_extractor( + backbone, trainable_backbone_layers, returned_layers=[2, 3, 4], extra_blocks=LastLevelP6P7(2048, 256) + ) + anchor_generator = _default_anchorgen() + head = RetinaNetHead( + backbone.out_channels, + anchor_generator.num_anchors_per_location()[0], + num_classes, + norm_layer=partial(nn.GroupNorm, 32), + ) + model = RetinaNet(backbone, num_classes, anchor_generator=anchor_generator, head=head, **kwargs) + + if weights is not None: + model.load_state_dict(weights.get_state_dict(progress=progress)) + + return model From 0d7917cd9a2a9dcc05fedbf136fba9cd57661d56 Mon Sep 17 00:00:00 2001 From: Vasilis Vryniotis Date: Wed, 23 Mar 2022 17:19:30 +0000 Subject: [PATCH 16/29] Add tests. --- ...ter.test_retinanet_resnet50_fpn_v2_expect.pkl | Bin 0 -> 9571 bytes test/test_models.py | 12 ++++++++++++ torchvision/models/detection/retinanet.py | 1 + 3 files changed, 13 insertions(+) create mode 100644 test/expect/ModelTester.test_retinanet_resnet50_fpn_v2_expect.pkl diff --git a/test/expect/ModelTester.test_retinanet_resnet50_fpn_v2_expect.pkl b/test/expect/ModelTester.test_retinanet_resnet50_fpn_v2_expect.pkl new file mode 100644 index 0000000000000000000000000000000000000000..7a352ebd5aa30584fe4317c364aa0ecef5dae895 GIT binary patch literal 9571 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 literal 0 HcmV?d00001 diff --git a/test/test_models.py b/test/test_models.py index 9a051a61eab..8aa6abde02c 100644 --- a/test/test_models.py +++ b/test/test_models.py @@ -200,6 +200,7 @@ def _check_input_backprop(model, inputs): "maskrcnn_resnet50_fpn": lambda x: x[1], "keypointrcnn_resnet50_fpn": lambda x: x[1], "retinanet_resnet50_fpn": lambda x: x[1], + "retinanet_resnet50_fpn_v2": lambda x: x[1], "ssd300_vgg16": lambda x: x[1], "ssdlite320_mobilenet_v3_large": lambda x: x[1], "fcos_resnet50_fpn": lambda x: x[1], @@ -246,6 +247,13 @@ def _check_input_backprop(model, inputs): "max_size": 224, "input_shape": (3, 224, 224), }, + "retinanet_resnet50_fpn_v2": { + "num_classes": 20, + "score_thresh": 0.01, + "min_size": 224, + "max_size": 224, + "input_shape": (3, 224, 224), + }, "keypointrcnn_resnet50_fpn": { "num_classes": 2, "min_size": 224, @@ -307,6 +315,10 @@ def _check_input_backprop(model, inputs): "max_trainable": 5, "n_trn_params_per_layer": [36, 46, 65, 78, 88, 89], }, + "retinanet_resnet50_fpn_v2": { + "max_trainable": 5, + "n_trn_params_per_layer": [44, 74, 131, 170, 200, 203], + }, "keypointrcnn_resnet50_fpn": { "max_trainable": 5, "n_trn_params_per_layer": [48, 58, 77, 90, 100, 101], diff --git a/torchvision/models/detection/retinanet.py b/torchvision/models/detection/retinanet.py index 5ec917a9766..3e72b2e1f2f 100644 --- a/torchvision/models/detection/retinanet.py +++ b/torchvision/models/detection/retinanet.py @@ -28,6 +28,7 @@ "RetinaNet", "RetinaNet_ResNet50_FPN_Weights", "retinanet_resnet50_fpn", + "retinanet_resnet50_fpn_v2", ] From 7354684f95ac5f14a3a674aebbd0d31b5a52dc0d Mon Sep 17 00:00:00 2001 From: Vasilis Vryniotis Date: Wed, 23 Mar 2022 17:53:38 +0000 Subject: [PATCH 17/29] Update expected file for old retina --- ...ter.test_retinanet_resnet50_fpn_expect.pkl | Bin 9571 -> 9571 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/test/expect/ModelTester.test_retinanet_resnet50_fpn_expect.pkl b/test/expect/ModelTester.test_retinanet_resnet50_fpn_expect.pkl index 7fb8d66b080dfdcebb4bed386cd752b99398b779..c982653f058fe5235a00db3a80eb0b9bdfb620df 100644 GIT binary patch literal 9571 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 Z9yjNF^B!H&9ON&@tqVCZOM>sg{tpEr-C_U$ From 38ef84387e63ea73b2abfdec7686c6f45c677068 Mon Sep 17 00:00:00 2001 From: Vasilis Vryniotis Date: Wed, 23 Mar 2022 18:17:49 +0000 Subject: [PATCH 18/29] Fixing tests --- test/test_extended_models.py | 1 - torchvision/models/detection/retinanet.py | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_extended_models.py b/test/test_extended_models.py index a07b501e15b..577be1d2cd6 100644 --- a/test/test_extended_models.py +++ b/test/test_extended_models.py @@ -64,7 +64,6 @@ def test_get_weight(name, weight): ) def test_naming_conventions(model_fn): weights_enum = _get_model_weights(model_fn) - print(weights_enum) assert weights_enum is not None assert len(weights_enum) == 0 or hasattr(weights_enum, "DEFAULT") diff --git a/torchvision/models/detection/retinanet.py b/torchvision/models/detection/retinanet.py index 3e72b2e1f2f..9c34d7a097c 100644 --- a/torchvision/models/detection/retinanet.py +++ b/torchvision/models/detection/retinanet.py @@ -27,6 +27,7 @@ __all__ = [ "RetinaNet", "RetinaNet_ResNet50_FPN_Weights", + "RetinaNet_ResNet50_FPN_V2_Weights", "retinanet_resnet50_fpn", "retinanet_resnet50_fpn_v2", ] From cd9c3025fae07fcf15ca4db357fdf43cc86fe763 Mon Sep 17 00:00:00 2001 From: Vasilis Vryniotis Date: Thu, 24 Mar 2022 08:29:57 +0000 Subject: [PATCH 19/29] Add FrozenBN to retinav2 --- test/test_models.py | 2 +- torchvision/models/detection/retinanet.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/test/test_models.py b/test/test_models.py index 8aa6abde02c..ceef41b62f4 100644 --- a/test/test_models.py +++ b/test/test_models.py @@ -317,7 +317,7 @@ def _check_input_backprop(model, inputs): }, "retinanet_resnet50_fpn_v2": { "max_trainable": 5, - "n_trn_params_per_layer": [44, 74, 131, 170, 200, 203], + "n_trn_params_per_layer": [44, 54, 73, 86, 96, 97], }, "keypointrcnn_resnet50_fpn": { "max_trainable": 5, diff --git a/torchvision/models/detection/retinanet.py b/torchvision/models/detection/retinanet.py index 9c34d7a097c..69d819ac58f 100644 --- a/torchvision/models/detection/retinanet.py +++ b/torchvision/models/detection/retinanet.py @@ -813,8 +813,9 @@ def retinanet_resnet50_fpn_v2( is_trained = weights is not None or weights_backbone is not None trainable_backbone_layers = _validate_trainable_layers(is_trained, trainable_backbone_layers, 5, 3) + norm_layer = misc_nn_ops.FrozenBatchNorm2d if is_trained else nn.BatchNorm2d - backbone = resnet50(weights=weights_backbone, progress=progress) + backbone = resnet50(weights=weights_backbone, progress=progress, norm_layer=norm_layer) backbone = _resnet_fpn_extractor( backbone, trainable_backbone_layers, returned_layers=[2, 3, 4], extra_blocks=LastLevelP6P7(2048, 256) ) From 29c57f6573f2a027cf497b2b22292183cc4989b7 Mon Sep 17 00:00:00 2001 From: Vasilis Vryniotis Date: Thu, 24 Mar 2022 10:14:31 +0000 Subject: [PATCH 20/29] Fix network initialization issues --- ...ter.test_retinanet_resnet50_fpn_expect.pkl | Bin 9571 -> 9571 bytes ....test_retinanet_resnet50_fpn_v2_expect.pkl | Bin 9571 -> 9571 bytes torchvision/models/detection/retinanet.py | 10 ++++++---- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/test/expect/ModelTester.test_retinanet_resnet50_fpn_expect.pkl b/test/expect/ModelTester.test_retinanet_resnet50_fpn_expect.pkl index c982653f058fe5235a00db3a80eb0b9bdfb620df..7fb8d66b080dfdcebb4bed386cd752b99398b779 100644 GIT binary patch literal 9571 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 Z9yjNF^B!H&9ON&@tqVCZOM>sg{tpEr-C_U$ literal 9571 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 diff --git a/torchvision/models/detection/retinanet.py b/torchvision/models/detection/retinanet.py index 69d819ac58f..97cbc934d9c 100644 --- a/torchvision/models/detection/retinanet.py +++ b/torchvision/models/detection/retinanet.py @@ -113,10 +113,11 @@ def __init__( conv.append(misc_nn_ops.Conv2dNormActivation(in_channels, in_channels, norm_layer=norm_layer)) self.conv = nn.Sequential(*conv) - for layer in self.conv.children(): + for layer in self.conv.modules(): if isinstance(layer, nn.Conv2d): torch.nn.init.normal_(layer.weight, std=0.01) - torch.nn.init.constant_(layer.bias, 0) + if layer.bias is not None: + torch.nn.init.constant_(layer.bias, 0) self.cls_logits = nn.Conv2d(in_channels, num_anchors * num_classes, kernel_size=3, stride=1, padding=1) torch.nn.init.normal_(self.cls_logits.weight, std=0.01) @@ -235,10 +236,11 @@ def __init__(self, in_channels, num_anchors, norm_layer: Optional[Callable[..., torch.nn.init.normal_(self.bbox_reg.weight, std=0.01) torch.nn.init.zeros_(self.bbox_reg.bias) - for layer in self.conv.children(): + for layer in self.conv.modules(): if isinstance(layer, nn.Conv2d): torch.nn.init.normal_(layer.weight, std=0.01) - torch.nn.init.zeros_(layer.bias) + if layer.bias is not None: + torch.nn.init.zeros_(layer.bias) self.box_coder = det_utils.BoxCoder(weights=(1.0, 1.0, 1.0, 1.0)) From 19f2b25e9a0f21f81e716b9364bee00f2f05e7b6 Mon Sep 17 00:00:00 2001 From: Vasilis Vryniotis Date: Thu, 24 Mar 2022 15:34:10 +0000 Subject: [PATCH 21/29] Adding BN support in MaskRCNNHeads and FPN --- torchvision/models/detection/mask_rcnn.py | 58 ++++++++++++++++------ torchvision/models/detection/retinanet.py | 1 - torchvision/ops/feature_pyramid_network.py | 48 ++++++++++++++++-- 3 files changed, 88 insertions(+), 19 deletions(-) diff --git a/torchvision/models/detection/mask_rcnn.py b/torchvision/models/detection/mask_rcnn.py index d46cd721513..2e57e7839bb 100644 --- a/torchvision/models/detection/mask_rcnn.py +++ b/torchvision/models/detection/mask_rcnn.py @@ -1,5 +1,5 @@ from collections import OrderedDict -from typing import Any, Optional +from typing import Any, Callable, Optional from torch import nn from torchvision.ops import MultiScaleRoIAlign @@ -264,28 +264,58 @@ def __init__( class MaskRCNNHeads(nn.Sequential): - def __init__(self, in_channels, layers, dilation): + _version = 2 + + def __init__(self, in_channels, layers, dilation, norm_layer: Optional[Callable[..., nn.Module]] = None): """ Args: in_channels (int): number of input channels layers (list): feature dimensions of each FCN layer dilation (int): dilation rate of kernel + norm_layer (callable, optional): Module specifying the normalization layer to use. Default: None """ - d = OrderedDict() + l = [] next_feature = in_channels - for layer_idx, layer_features in enumerate(layers, 1): - d[f"mask_fcn{layer_idx}"] = nn.Conv2d( - next_feature, layer_features, kernel_size=3, stride=1, padding=dilation, dilation=dilation - ) - d[f"relu{layer_idx}"] = nn.ReLU(inplace=True) + for layer_features in layers: + l.append(misc_nn_ops.Conv2dNormActivation(next_feature, layer_features, kernel_size=3, stride=1, padding=dilation, dilation=dilation, norm_layer=norm_layer)) next_feature = layer_features - super().__init__(d) - for name, param in self.named_parameters(): - if "weight" in name: - nn.init.kaiming_normal_(param, mode="fan_out", nonlinearity="relu") - # elif "bias" in name: - # nn.init.constant_(param, 0) + super().__init__(*l) + for layer in self.modules(): + if isinstance(layer, nn.Conv2d): + nn.init.kaiming_normal_(layer.weight, mode="fan_out", nonlinearity="relu") + if layer.bias is not None: + nn.init.zeros_(layer.bias) + + def _load_from_state_dict( + self, + state_dict, + prefix, + local_metadata, + strict, + missing_keys, + unexpected_keys, + error_msgs, + ): + version = local_metadata.get("version", None) + + if version is None or version < 2: + num_blocks = len(self) + for i in range(num_blocks): + for type in ["weight", "bias"]: + old_key = f"{prefix}mask_fcn{i+1}.{type}" + new_key = f"{prefix}{i}.0.{type}" + state_dict[new_key] = state_dict.pop(old_key) + + super()._load_from_state_dict( + state_dict, + prefix, + local_metadata, + strict, + missing_keys, + unexpected_keys, + error_msgs, + ) class MaskRCNNPredictor(nn.Sequential): diff --git a/torchvision/models/detection/retinanet.py b/torchvision/models/detection/retinanet.py index 97cbc934d9c..898503c02c6 100644 --- a/torchvision/models/detection/retinanet.py +++ b/torchvision/models/detection/retinanet.py @@ -217,7 +217,6 @@ class RetinaNetRegressionHead(nn.Module): num_anchors (int): number of anchors to be predicted norm_layer (callable, optional): Module specifying the normalization layer to use. Default: None """ - _version = 2 __annotations__ = { diff --git a/torchvision/ops/feature_pyramid_network.py b/torchvision/ops/feature_pyramid_network.py index 2e1ac0cd8cf..56b41cfb30e 100644 --- a/torchvision/ops/feature_pyramid_network.py +++ b/torchvision/ops/feature_pyramid_network.py @@ -1,9 +1,10 @@ from collections import OrderedDict -from typing import Tuple, List, Dict, Optional +from typing import Tuple, List, Dict, Callable, Optional import torch.nn.functional as F from torch import nn, Tensor +from ..ops.misc import Conv2dNormActivation from ..utils import _log_api_usage_once @@ -70,11 +71,14 @@ class FeaturePyramidNetwork(nn.Module): """ + _version = 2 + def __init__( self, in_channels_list: List[int], out_channels: int, extra_blocks: Optional[ExtraFPNBlock] = None, + norm_layer: Optional[Callable[..., nn.Module]] = None, ): super().__init__() _log_api_usage_once(self) @@ -83,8 +87,12 @@ def __init__( for in_channels in in_channels_list: if in_channels == 0: raise ValueError("in_channels=0 is currently not supported") - inner_block_module = nn.Conv2d(in_channels, out_channels, 1) - layer_block_module = nn.Conv2d(out_channels, out_channels, 3, padding=1) + inner_block_module = Conv2dNormActivation( + in_channels, out_channels, kernel_size=1, padding=0, norm_layer=norm_layer, activation_layer=None + ) + layer_block_module = Conv2dNormActivation( + out_channels, out_channels, kernel_size=3, norm_layer=norm_layer, activation_layer=None + ) self.inner_blocks.append(inner_block_module) self.layer_blocks.append(layer_block_module) @@ -92,13 +100,45 @@ def __init__( for m in self.modules(): if isinstance(m, nn.Conv2d): nn.init.kaiming_uniform_(m.weight, a=1) - nn.init.constant_(m.bias, 0) + if m.bias is not None: + nn.init.constant_(m.bias, 0) if extra_blocks is not None: if not isinstance(extra_blocks, ExtraFPNBlock): raise TypeError(f"extra_blocks should be of type ExtraFPNBlock not {type(extra_blocks)}") self.extra_blocks = extra_blocks + def _load_from_state_dict( + self, + state_dict, + prefix, + local_metadata, + strict, + missing_keys, + unexpected_keys, + error_msgs, + ): + version = local_metadata.get("version", None) + + if version is None or version < 2: + num_blocks = len(self.inner_blocks) + for block in ["inner_blocks", "layer_blocks"]: + for i in range(num_blocks): + for type in ["weight", "bias"]: + old_key = f"{prefix}{block}.{i}.{type}" + new_key = f"{prefix}{block}.{i}.0.{type}" + state_dict[new_key] = state_dict.pop(old_key) + + super()._load_from_state_dict( + state_dict, + prefix, + local_metadata, + strict, + missing_keys, + unexpected_keys, + error_msgs, + ) + def get_result_from_inner_blocks(self, x: Tensor, idx: int) -> Tensor: """ This is equivalent to self.inner_blocks[idx](x), From 124fd8a2dec40dee960f0be5a38223410154f7e4 Mon Sep 17 00:00:00 2001 From: Vasilis Vryniotis Date: Thu, 24 Mar 2022 21:41:46 +0000 Subject: [PATCH 22/29] Adding support of FasterRCNNHeads --- .../models/detection/backbone_utils.py | 3 ++ torchvision/models/detection/faster_rcnn.py | 31 ++++++++++++++++++- torchvision/ops/feature_pyramid_network.py | 1 + 3 files changed, 34 insertions(+), 1 deletion(-) diff --git a/torchvision/models/detection/backbone_utils.py b/torchvision/models/detection/backbone_utils.py index 24215322b84..560b9b952d1 100644 --- a/torchvision/models/detection/backbone_utils.py +++ b/torchvision/models/detection/backbone_utils.py @@ -25,6 +25,7 @@ class BackboneWithFPN(nn.Module): in_channels_list (List[int]): number of channels for each feature map that is returned, in the order they are present in the OrderedDict out_channels (int): number of channels in the FPN. + norm_layer (callable, optional): Module specifying the normalization layer to use. Default: None Attributes: out_channels (int): the number of channels in the FPN """ @@ -36,6 +37,7 @@ def __init__( in_channels_list: List[int], out_channels: int, extra_blocks: Optional[ExtraFPNBlock] = None, + norm_layer: Optional[Callable[..., nn.Module]] = None, ) -> None: super().__init__() @@ -47,6 +49,7 @@ def __init__( in_channels_list=in_channels_list, out_channels=out_channels, extra_blocks=extra_blocks, + norm_layer=norm_layer, ) self.out_channels = out_channels diff --git a/torchvision/models/detection/faster_rcnn.py b/torchvision/models/detection/faster_rcnn.py index b1cdcaeec90..d2315242bbe 100644 --- a/torchvision/models/detection/faster_rcnn.py +++ b/torchvision/models/detection/faster_rcnn.py @@ -1,4 +1,4 @@ -from typing import Any, Optional, Union +from typing import Any, Callable, Optional, Union import torch.nn.functional as F from torch import nn @@ -297,6 +297,35 @@ def forward(self, x): return x +class FastRCNNHeads(nn.Sequential): + + def __init__(self, input_size, layers, output_channels, norm_layer: Optional[Callable[..., nn.Module]] = None): + """ + Args: + input_size (Tuple[int, int, int]): the input size in CHW format. + layers (list): feature dimensions of each FCN layer + output_channels (int): output channels + norm_layer (callable, optional): Module specifying the normalization layer to use. Default: None + """ + in_channels, in_height, in_width = input_size + + l = [] + previous_channels = in_channels + for layer_channels in layers: + l.append(misc_nn_ops.Conv2dNormActivation(previous_channels, layer_channels, norm_layer=norm_layer)) + previous_channels = layer_channels + l.append(nn.Flatten()) + l.append(nn.Linear(previous_channels * in_height * in_width, output_channels)) + l.append(nn.ReLU(inplace=True)) + + super().__init__(*l) + for layer in self.modules(): + if isinstance(layer, nn.Conv2d): + nn.init.kaiming_normal_(layer.weight, mode="fan_out", nonlinearity="relu") + if layer.bias is not None: + nn.init.zeros_(layer.bias) + + class FastRCNNPredictor(nn.Module): """ Standard classification + bounding box regression layers diff --git a/torchvision/ops/feature_pyramid_network.py b/torchvision/ops/feature_pyramid_network.py index 56b41cfb30e..056ecbdc120 100644 --- a/torchvision/ops/feature_pyramid_network.py +++ b/torchvision/ops/feature_pyramid_network.py @@ -52,6 +52,7 @@ class FeaturePyramidNetwork(nn.Module): be performed. It is expected to take the fpn features, the original features and the names of the original features as input, and returns a new list of feature maps and their corresponding names + norm_layer (callable, optional): Module specifying the normalization layer to use. Default: None Examples:: From 53aa8b7250ab2b562959478c6732bc2b9f2c5afa Mon Sep 17 00:00:00 2001 From: Vasilis Vryniotis Date: Fri, 25 Mar 2022 13:40:08 +0000 Subject: [PATCH 23/29] Introduce norm_layers in backbone utils. --- .../models/detection/backbone_utils.py | 10 ++++++-- torchvision/models/detection/faster_rcnn.py | 23 +++++++++++-------- torchvision/models/detection/mask_rcnn.py | 16 ++++++++++--- torchvision/models/detection/retinanet.py | 4 ++-- 4 files changed, 37 insertions(+), 16 deletions(-) diff --git a/torchvision/models/detection/backbone_utils.py b/torchvision/models/detection/backbone_utils.py index 560b9b952d1..65fe45c4cbd 100644 --- a/torchvision/models/detection/backbone_utils.py +++ b/torchvision/models/detection/backbone_utils.py @@ -118,6 +118,7 @@ def _resnet_fpn_extractor( trainable_layers: int, returned_layers: Optional[List[int]] = None, extra_blocks: Optional[ExtraFPNBlock] = None, + norm_layer: Optional[Callable[..., nn.Module]] = None, ) -> BackboneWithFPN: # select layers that wont be frozen @@ -142,7 +143,9 @@ def _resnet_fpn_extractor( in_channels_stage2 = backbone.inplanes // 8 in_channels_list = [in_channels_stage2 * 2 ** (i - 1) for i in returned_layers] out_channels = 256 - return BackboneWithFPN(backbone, return_layers, in_channels_list, out_channels, extra_blocks=extra_blocks) + return BackboneWithFPN( + backbone, return_layers, in_channels_list, out_channels, extra_blocks=extra_blocks, norm_layer=norm_layer + ) def _validate_trainable_layers( @@ -197,6 +200,7 @@ def _mobilenet_extractor( trainable_layers: int, returned_layers: Optional[List[int]] = None, extra_blocks: Optional[ExtraFPNBlock] = None, + norm_layer: Optional[Callable[..., nn.Module]] = None, ) -> nn.Module: backbone = backbone.features # Gather the indices of blocks which are strided. These are the locations of C1, ..., Cn-1 blocks. @@ -225,7 +229,9 @@ def _mobilenet_extractor( return_layers = {f"{stage_indices[k]}": str(v) for v, k in enumerate(returned_layers)} in_channels_list = [backbone[stage_indices[i]].out_channels for i in returned_layers] - return BackboneWithFPN(backbone, return_layers, in_channels_list, out_channels, extra_blocks=extra_blocks) + return BackboneWithFPN( + backbone, return_layers, in_channels_list, out_channels, extra_blocks=extra_blocks, norm_layer=norm_layer + ) else: m = nn.Sequential( backbone, diff --git a/torchvision/models/detection/faster_rcnn.py b/torchvision/models/detection/faster_rcnn.py index d2315242bbe..57d13db7e90 100644 --- a/torchvision/models/detection/faster_rcnn.py +++ b/torchvision/models/detection/faster_rcnn.py @@ -1,4 +1,4 @@ -from typing import Any, Callable, Optional, Union +from typing import Any, Callable, List, Optional, Tuple, Union import torch.nn.functional as F from torch import nn @@ -298,8 +298,13 @@ def forward(self, x): class FastRCNNHeads(nn.Sequential): - - def __init__(self, input_size, layers, output_channels, norm_layer: Optional[Callable[..., nn.Module]] = None): + def __init__( + self, + input_size: Tuple[int, int, int], + layers: List[int], + output_channels: int, + norm_layer: Optional[Callable[..., nn.Module]] = None, + ): """ Args: input_size (Tuple[int, int, int]): the input size in CHW format. @@ -309,16 +314,16 @@ def __init__(self, input_size, layers, output_channels, norm_layer: Optional[Cal """ in_channels, in_height, in_width = input_size - l = [] + blocks = [] previous_channels = in_channels for layer_channels in layers: - l.append(misc_nn_ops.Conv2dNormActivation(previous_channels, layer_channels, norm_layer=norm_layer)) + blocks.append(misc_nn_ops.Conv2dNormActivation(previous_channels, layer_channels, norm_layer=norm_layer)) previous_channels = layer_channels - l.append(nn.Flatten()) - l.append(nn.Linear(previous_channels * in_height * in_width, output_channels)) - l.append(nn.ReLU(inplace=True)) + blocks.append(nn.Flatten()) + blocks.append(nn.Linear(previous_channels * in_height * in_width, output_channels)) + blocks.append(nn.ReLU(inplace=True)) - super().__init__(*l) + super().__init__(*blocks) for layer in self.modules(): if isinstance(layer, nn.Conv2d): nn.init.kaiming_normal_(layer.weight, mode="fan_out", nonlinearity="relu") diff --git a/torchvision/models/detection/mask_rcnn.py b/torchvision/models/detection/mask_rcnn.py index 2e57e7839bb..1996f4369b4 100644 --- a/torchvision/models/detection/mask_rcnn.py +++ b/torchvision/models/detection/mask_rcnn.py @@ -274,13 +274,23 @@ def __init__(self, in_channels, layers, dilation, norm_layer: Optional[Callable[ dilation (int): dilation rate of kernel norm_layer (callable, optional): Module specifying the normalization layer to use. Default: None """ - l = [] + blocks = [] next_feature = in_channels for layer_features in layers: - l.append(misc_nn_ops.Conv2dNormActivation(next_feature, layer_features, kernel_size=3, stride=1, padding=dilation, dilation=dilation, norm_layer=norm_layer)) + blocks.append( + misc_nn_ops.Conv2dNormActivation( + next_feature, + layer_features, + kernel_size=3, + stride=1, + padding=dilation, + dilation=dilation, + norm_layer=norm_layer, + ) + ) next_feature = layer_features - super().__init__(*l) + super().__init__(*blocks) for layer in self.modules(): if isinstance(layer, nn.Conv2d): nn.init.kaiming_normal_(layer.weight, mode="fan_out", nonlinearity="relu") diff --git a/torchvision/models/detection/retinanet.py b/torchvision/models/detection/retinanet.py index 898503c02c6..e922d9a17e7 100644 --- a/torchvision/models/detection/retinanet.py +++ b/torchvision/models/detection/retinanet.py @@ -217,6 +217,7 @@ class RetinaNetRegressionHead(nn.Module): num_anchors (int): number of anchors to be predicted norm_layer (callable, optional): Module specifying the normalization layer to use. Default: None """ + _version = 2 __annotations__ = { @@ -814,9 +815,8 @@ def retinanet_resnet50_fpn_v2( is_trained = weights is not None or weights_backbone is not None trainable_backbone_layers = _validate_trainable_layers(is_trained, trainable_backbone_layers, 5, 3) - norm_layer = misc_nn_ops.FrozenBatchNorm2d if is_trained else nn.BatchNorm2d - backbone = resnet50(weights=weights_backbone, progress=progress, norm_layer=norm_layer) + backbone = resnet50(weights=weights_backbone, progress=progress) backbone = _resnet_fpn_extractor( backbone, trainable_backbone_layers, returned_layers=[2, 3, 4], extra_blocks=LastLevelP6P7(2048, 256) ) From f9ba509ff83903599b77aa0424474c80dbaa298d Mon Sep 17 00:00:00 2001 From: Vasilis Vryniotis Date: Fri, 25 Mar 2022 15:33:14 +0000 Subject: [PATCH 24/29] Bigger RPN head + 2x rcnn v2 models. --- ...test_fasterrcnn_resnet50_fpn_v2_expect.pkl | Bin 0 -> 3939 bytes ...r.test_maskrcnn_resnet50_fpn_v2_expect.pkl | Bin 0 -> 4507 bytes test/test_models.py | 23 ++++++ torchvision/models/detection/faster_rcnn.py | 74 +++++++++++++++++- torchvision/models/detection/mask_rcnn.py | 68 +++++++++++++++- torchvision/models/detection/rpn.py | 49 ++++++++++-- 6 files changed, 204 insertions(+), 10 deletions(-) create mode 100644 test/expect/ModelTester.test_fasterrcnn_resnet50_fpn_v2_expect.pkl create mode 100644 test/expect/ModelTester.test_maskrcnn_resnet50_fpn_v2_expect.pkl diff --git a/test/expect/ModelTester.test_fasterrcnn_resnet50_fpn_v2_expect.pkl b/test/expect/ModelTester.test_fasterrcnn_resnet50_fpn_v2_expect.pkl new file mode 100644 index 0000000000000000000000000000000000000000..c2875679efd98e7d3454084ddc67054a8dec047e GIT binary patch literal 3939 zcmeH~c~BHr9>;r_102EOMno>pAtI+n&8ltxo|!cc=!hs}SmhY*AlSpe8l!e3giOR* zQDaPu@kn{WMqN$Ra#xLI)HR7>FiJ`z2#Oagm!RN<`|H=lW!Osg&&?lsRiAqGo!|AY zp6+MR%M}dc;K2M>b7owbWV0zVt1vAzHQACJvha-@9SmTMHtlV@c|5a}mucgbm?0%^ zQCdE4XR_pBU`S$tB`YUiooG%=Dagu6O|+!t=I5Cc3nSFLT&s<@$AEnFOwLH-6;g;= zmy?%lnJxXO=9Td>^aY3)$G;><&)GU)l&G!$A;TKUWI9Ll2g)h@_9`rjv@2sx>0$#886B@ z)3V{bOQyX()xx{Z(kt}t@A7VX<-5H5`)G0q&>0E<)g zWxB}PI{-=Mn2PetBrNg$hB zR78y5?ly?9w`P*j6~0vKoSew?gjlklccMD|_7k|0F^fF2ccA)AkerOTG@a~ma-_Oy zn>Vr4mx0F1pX#kiF2uiU2=U2wrP}J@K`wU25yKuYs&(~_q%gAV@1l)uu&OwYoNa2SI;rM4EHius&J8l^AN<^pXm7qmrp=M! zeYbQ9bU*eh{Kdf>Q} zgS<*c^_-eW*tPpF(9VycdeqWRE`H}uSeN&Rvo_5m|JXDD(%&zJsz433uPr+a6RY+E zn{tlo%9Gu2)mKk``hcPHG0%AiirZ_TE3|~#$L#bWhVjwlYMr!xEwRqz4=)=?;D_$i ze}dVWxaUqKtLJ)<=2zwuZGP5ZV|-^DnLQgbycaA$f(gpId&g zgVd&h(C}wD^*ElOcuD^RuV=F}Jkbi+ox5l-BP*@(YM~s{_XE|2y?R9pnoR zS3=;#cfM3tW`~pVVN=OOWhAZlrBeWG&#}UhH38H<EcQ~%Ocja=*9$KZ4H9v#nut19?DeJpn%OL~60 zwOX(yYzE^-E%hI^U@tURoPej{2dTENaEGuW2DqS{*B#m(Dv8cJ#((R#(f_1H7a^?;k)l0+kUZ~ioJ^zb7!vkcUK^|=U`zjg~5m@<+_Jl%cfBz^4l(| zh4MjUvDd5QXsY!8EWUsWl&az>_-YjSwHJ`W_5~EK!;0cdTx`Ghzpump^BmwuEN#EF zb0|Jv`}S8_1LO=N@@vo6Fx%%VLI(kTzP4>Xy{WkMTwtq_${x;Oe*{TK;t>T>*)6aq zks`!`{0SL@Xc0fe6>&uFKNHwimB5phKBbqH)7QjUCr+-Miry~tSP(tUy5jkLE3>|3n@`=CW& zosbF50y`SvA`us){hq)cMMfYi?qZEQSOXb@T)8c<;(p)Mue?|$V;GTN z-fs;P`C`8d)6DsJbJ0QQ|J?9CM<7xyR;=2TF~<(izlcFE6T268y583dXVGBO`rh#R zM)!U<46un@g|q7&9U9g*w)eTWjdjNRrcG>k-`L)3XdAn*f1lWhzOlVmLt``MB&Ywx z_N=)haT^8Gge C%kz-{ literal 0 HcmV?d00001 diff --git a/test/expect/ModelTester.test_maskrcnn_resnet50_fpn_v2_expect.pkl b/test/expect/ModelTester.test_maskrcnn_resnet50_fpn_v2_expect.pkl new file mode 100644 index 0000000000000000000000000000000000000000..c6d1fd14081505a25ffea7ddc8e279078a917b3b GIT binary patch literal 4507 zcmeHLc~F$c5?}69BtR4rB_JN4ghLQear>JdvML*q!ziM%EQfOJf*-pe_o9L#F@Q!v zMZgPBLX43>6hWg=;iUv>hk_gjCm$JaeP>0RAPiyDR%cp)nw-`36B-N6gqq7(J+4t(#_H#Ug~drE z5n=MExLBF~^!V_I_=s??s4%%cYhdTk8p_!L{;W};HfKg64vmn=*nyFp50S5VKaPuC z@P^j7ht`>WCsIckE@w^rgqlLLY}Qn$mCc&1h*TPUSI!RdX9xEfl!e8`bE`J*H5l3J z^jOxS7yaHV^!rNmkenX$P&sSq&wkJoGO1V=BV(=7S!>%sE)zUyggBNRrsAAeTtcWM z;*EPYJ?^=%!#M&j7$f|IJUhEy!z1PFD1X+rCtNalIBWOHr(TFFJGvJ#rVqqk&VK06 zIwT~B9ejm4y4B~qN1WoKu2kPYtqmidhe~ZYItkFVUuCG2$)9!#cD_#)b;0zm?igCM zUqq*zT?J7OFX0!>mL$gAimEO4pmP@o(?dHL8Y&jiem8_V8s;(q z_9yXP{RUEGtw&D=eg%z-Cm^ePiYZ^4jpwYbn2U~4czx_K=14*fGO6uk=$FB?Vu}#U z_lT%>ngXgS2=cH4*YZ!f{3L=S5CdlXqEoI+zg^`K*K zISzJS3mfTa++vHuwEw)+v>KCi&Fi!I1$FI~E2uz;Fc1=9OkoKr6Jd9)1_-Te{sre0zO?|z4RNlwER zx6AllPzvKA(xI90o}@HrAiel(JtD47E} zO>)rq`%z@Tfhv4Fhb3i;S}^x=GMSV#nAXKk#BA5`R6XB{q%{qsn+L5VNdrHi9#TN-D9~m*3e-zgl)DhR;@8gt|tE91E82u?^6|x?;VyWhf}HDolc`z4ETzZ$%}XU|FdO9Q-o z^d8ghcnE(u>qlO6w4t_qGLcN&iYqIViO;da=r~ji*S6or_BB6&-&YpYGGRQa%v_Hb z8dJ%+pc>SuA4yA!gXtxYv2^+f5v{ednY1sX1DPY`lcKyWXwEP0J$!B9mN=m+6ZdLr zV#O16nm0Ncvuejt_o?P2y14;|nu>|{m|wBwPCj$)=SIAdG=`w!1wLD0MLbTQ#m^3g zllj{pV@s|XIs9ln$}5A&nvV|PrX$sisQXI{?X)CS4L@V)i(%9!H;4|;^Povb=Ftrc zcMyv`W;>a|Vp>FZ6LL8YlniS?rGIQ^?)qOKc4dmPJCeDsgi62C{Mb57-rANuxW1C{N9WcHIo78v9Df&7Ce( zeD)mqH2x!cFwdNJC(oo~+}vs5)*!0+yDH$UOE*E ztTRba{imq^eLk7#vjevc+(kM^G~&tNYO?m}@7NH!q-VaNNWy%ZJD!yC_A^Snc~=<; zF(1#|Xg4DFH?^bM`1$1ABm)|6T}8US-G|2mPLLTHcknm&62go;kF7qtiMIN4+|aO> zEc(%kwsuWHsV$!-IhPT`*?hXr>MHSe64I{w$4Q;w5Ee8ylhW+RXgYB{30SfRO-Fx0 zj@X{UgOlrtb4W8v-OdnOT|K&ddmhn#+Jw()%gBb@hqygp4H@lSj}sDi5kb;8pbzA|STNGef ztN@446cDgj0i{U_SjH(vE8w7?0_wdK-~&=wCZ<8XV;Yo=N`qU^QsMUXRJeX96*P-eVQp?IH0Y(mrkWIR7@7k9 z>M3xqI~kr|N`_ha$)Mq$3|i?)U?EC^HbD}c?_2~sTNeTKTLdP?iSWWH0nXl(!i7F8dT2_!m8{k;5mN^1QrQEnePKnjY4Hzxxm7FGSE#0J-n_~CzQ~M~11i%0h29_C_iQv$K@Q~bAN2#8dAkN8TP}D?T_tS2dY>ruB*c9(jVJ5hpO0h{jsk7v3;{J zP$gF8T=zBatDO4^wYBYEO|sPZ@Xd7PzZyz;-R|Hvky2K_vdH0jhQ`0s`&~KgE7a;a NCv-TJa(?gJKLGbJ$A$m^ literal 0 HcmV?d00001 diff --git a/test/test_models.py b/test/test_models.py index ceef41b62f4..bea1648c85d 100644 --- a/test/test_models.py +++ b/test/test_models.py @@ -195,9 +195,11 @@ def _check_input_backprop(model, inputs): "googlenet": lambda x: x.logits, "inception_v3": lambda x: x.logits, "fasterrcnn_resnet50_fpn": lambda x: x[1], + "fasterrcnn_resnet50_fpn_v2": lambda x: x[1], "fasterrcnn_mobilenet_v3_large_fpn": lambda x: x[1], "fasterrcnn_mobilenet_v3_large_320_fpn": lambda x: x[1], "maskrcnn_resnet50_fpn": lambda x: x[1], + "maskrcnn_resnet50_fpn_v2": lambda x: x[1], "keypointrcnn_resnet50_fpn": lambda x: x[1], "retinanet_resnet50_fpn": lambda x: x[1], "retinanet_resnet50_fpn_v2": lambda x: x[1], @@ -228,6 +230,7 @@ def _check_input_backprop(model, inputs): "fcn_resnet101", "lraspp_mobilenet_v3_large", "maskrcnn_resnet50_fpn", + "maskrcnn_resnet50_fpn_v2", ) # The tests for the following quantized models are flaky possibly due to inconsistent @@ -267,6 +270,12 @@ def _check_input_backprop(model, inputs): "max_size": 224, "input_shape": (3, 224, 224), }, + "fasterrcnn_resnet50_fpn_v2": { + "num_classes": 20, + "min_size": 224, + "max_size": 224, + "input_shape": (3, 224, 224), + }, "fcos_resnet50_fpn": { "num_classes": 2, "score_thresh": 0.05, @@ -280,6 +289,12 @@ def _check_input_backprop(model, inputs): "max_size": 224, "input_shape": (3, 224, 224), }, + "maskrcnn_resnet50_fpn_v2": { + "num_classes": 10, + "min_size": 224, + "max_size": 224, + "input_shape": (3, 224, 224), + }, "fasterrcnn_mobilenet_v3_large_fpn": { "box_score_thresh": 0.02076, }, @@ -327,10 +342,18 @@ def _check_input_backprop(model, inputs): "max_trainable": 5, "n_trn_params_per_layer": [30, 40, 59, 72, 82, 83], }, + "fasterrcnn_resnet50_fpn_v2": { + "max_trainable": 5, + "n_trn_params_per_layer": [50, 80, 137, 176, 206, 209], + }, "maskrcnn_resnet50_fpn": { "max_trainable": 5, "n_trn_params_per_layer": [42, 52, 71, 84, 94, 95], }, + "maskrcnn_resnet50_fpn_v2": { + "max_trainable": 5, + "n_trn_params_per_layer": [66, 96, 153, 192, 222, 225], + }, "fasterrcnn_mobilenet_v3_large_fpn": { "max_trainable": 6, "n_trn_params_per_layer": [22, 23, 44, 70, 91, 97, 100], diff --git a/torchvision/models/detection/faster_rcnn.py b/torchvision/models/detection/faster_rcnn.py index 57d13db7e90..d83d7724464 100644 --- a/torchvision/models/detection/faster_rcnn.py +++ b/torchvision/models/detection/faster_rcnn.py @@ -23,14 +23,22 @@ __all__ = [ "FasterRCNN", "FasterRCNN_ResNet50_FPN_Weights", + "FasterRCNN_ResNet50_FPN_V2_Weights", "FasterRCNN_MobileNet_V3_Large_FPN_Weights", "FasterRCNN_MobileNet_V3_Large_320_FPN_Weights", "fasterrcnn_resnet50_fpn", + "fasterrcnn_resnet50_fpn_v2", "fasterrcnn_mobilenet_v3_large_fpn", "fasterrcnn_mobilenet_v3_large_320_fpn", ] +def _default_anchorgen(): + anchor_sizes = ((32,), (64,), (128,), (256,), (512,)) + aspect_ratios = ((0.5, 1.0, 2.0),) * len(anchor_sizes) + return AnchorGenerator(anchor_sizes, aspect_ratios) + + class FasterRCNN(GeneralizedRCNN): """ Implements Faster R-CNN. @@ -215,9 +223,7 @@ def __init__( out_channels = backbone.out_channels if rpn_anchor_generator is None: - anchor_sizes = ((32,), (64,), (128,), (256,), (512,)) - aspect_ratios = ((0.5, 1.0, 2.0),) * len(anchor_sizes) - rpn_anchor_generator = AnchorGenerator(anchor_sizes, aspect_ratios) + rpn_anchor_generator = _default_anchorgen() if rpn_head is None: rpn_head = RPNHead(out_channels, rpn_anchor_generator.num_anchors_per_location()[0]) @@ -382,6 +388,10 @@ class FasterRCNN_ResNet50_FPN_Weights(WeightsEnum): DEFAULT = COCO_V1 +class FasterRCNN_ResNet50_FPN_V2_Weights(WeightsEnum): + pass + + class FasterRCNN_MobileNet_V3_Large_FPN_Weights(WeightsEnum): COCO_V1 = Weights( url="https://download.pytorch.org/models/fasterrcnn_mobilenet_v3_large_fpn-fb6a3cc7.pth", @@ -514,6 +524,64 @@ def fasterrcnn_resnet50_fpn( return model +def fasterrcnn_resnet50_fpn_v2( + *, + weights: Optional[FasterRCNN_ResNet50_FPN_V2_Weights] = None, + progress: bool = True, + num_classes: Optional[int] = None, + weights_backbone: Optional[ResNet50_Weights] = None, + trainable_backbone_layers: Optional[int] = None, + **kwargs: Any, +) -> FasterRCNN: + """ + Constructs an improved Faster R-CNN model with a ResNet-50-FPN backbone. + + Reference: `"Benchmarking Detection Transfer Learning with Vision Transformers" + `_. + + :func:`~torchvision.models.detection.fasterrcnn_resnet50_fpn` for more details. + + Args: + weights (FasterRCNN_ResNet50_FPN_V2_Weights, optional): The pretrained weights for the model + progress (bool): If True, displays a progress bar of the download to stderr + num_classes (int, optional): number of output classes of the model (including the background) + weights_backbone (ResNet50_Weights, optional): The pretrained weights for the backbone + trainable_backbone_layers (int, optional): number of trainable (not frozen) layers starting from final block. + Valid values are between 0 and 5, with 5 meaning all backbone layers are trainable. If ``None`` is + passed (the default) this value is set to 3. + """ + weights = FasterRCNN_ResNet50_FPN_V2_Weights.verify(weights) + weights_backbone = ResNet50_Weights.verify(weights_backbone) + + if weights is not None: + weights_backbone = None + num_classes = _ovewrite_value_param(num_classes, len(weights.meta["categories"])) + elif num_classes is None: + num_classes = 91 + + is_trained = weights is not None or weights_backbone is not None + trainable_backbone_layers = _validate_trainable_layers(is_trained, trainable_backbone_layers, 5, 3) + + backbone = resnet50(weights=weights_backbone, progress=progress) + backbone = _resnet_fpn_extractor(backbone, trainable_backbone_layers, norm_layer=nn.BatchNorm2d) + rpn_anchor_generator = _default_anchorgen() + rpn_head = RPNHead(backbone.out_channels, rpn_anchor_generator.num_anchors_per_location()[0], conv_depth=2) + box_head = FastRCNNHeads((backbone.out_channels, 7, 7), [256, 256, 256, 256], 1024, norm_layer=nn.BatchNorm2d) + model = FasterRCNN( + backbone, + num_classes=num_classes, + rpn_anchor_generator=rpn_anchor_generator, + rpn_head=rpn_head, + box_head=box_head, + **kwargs, + ) + + if weights is not None: + model.load_state_dict(weights.get_state_dict(progress=progress)) + + return model + + def _fasterrcnn_mobilenet_v3_large_fpn( *, weights: Optional[Union[FasterRCNN_MobileNet_V3_Large_FPN_Weights, FasterRCNN_MobileNet_V3_Large_320_FPN_Weights]], diff --git a/torchvision/models/detection/mask_rcnn.py b/torchvision/models/detection/mask_rcnn.py index 1996f4369b4..179f2c176ed 100644 --- a/torchvision/models/detection/mask_rcnn.py +++ b/torchvision/models/detection/mask_rcnn.py @@ -12,13 +12,15 @@ from ..resnet import ResNet50_Weights, resnet50 from ._utils import overwrite_eps from .backbone_utils import _resnet_fpn_extractor, _validate_trainable_layers -from .faster_rcnn import FasterRCNN +from .faster_rcnn import FasterRCNN, FastRCNNHeads, RPNHead, _default_anchorgen __all__ = [ "MaskRCNN", "MaskRCNN_ResNet50_FPN_Weights", + "MaskRCNN_ResNet50_FPN_V2_Weights", "maskrcnn_resnet50_fpn", + "maskrcnn_resnet50_fpn_v2", ] @@ -366,6 +368,10 @@ class MaskRCNN_ResNet50_FPN_Weights(WeightsEnum): DEFAULT = COCO_V1 +class MaskRCNN_ResNet50_FPN_V2_Weights(WeightsEnum): + pass + + @handle_legacy_interface( weights=("pretrained", MaskRCNN_ResNet50_FPN_Weights.COCO_V1), weights_backbone=("pretrained_backbone", ResNet50_Weights.IMAGENET1K_V1), @@ -458,3 +464,63 @@ def maskrcnn_resnet50_fpn( overwrite_eps(model, 0.0) return model + + +def maskrcnn_resnet50_fpn_v2( + *, + weights: Optional[MaskRCNN_ResNet50_FPN_V2_Weights] = None, + progress: bool = True, + num_classes: Optional[int] = None, + weights_backbone: Optional[ResNet50_Weights] = None, + trainable_backbone_layers: Optional[int] = None, + **kwargs: Any, +) -> MaskRCNN: + """ + Constructs an improved MaskRCNN model with a ResNet-50-FPN backbone. + + Reference: `"Benchmarking Detection Transfer Learning with Vision Transformers" + `_. + + :func:`~torchvision.models.detection.maskrcnn_resnet50_fpn` for more details. + + Args: + weights (MaskRCNN_ResNet50_FPN_V2_Weights, optional): The pretrained weights for the model + progress (bool): If True, displays a progress bar of the download to stderr + num_classes (int, optional): number of output classes of the model (including the background) + weights_backbone (ResNet50_Weights, optional): The pretrained weights for the backbone + trainable_backbone_layers (int, optional): number of trainable (not frozen) layers starting from final block. + Valid values are between 0 and 5, with 5 meaning all backbone layers are trainable. If ``None`` is + passed (the default) this value is set to 3. + """ + weights = MaskRCNN_ResNet50_FPN_V2_Weights.verify(weights) + weights_backbone = ResNet50_Weights.verify(weights_backbone) + + if weights is not None: + weights_backbone = None + num_classes = _ovewrite_value_param(num_classes, len(weights.meta["categories"])) + elif num_classes is None: + num_classes = 91 + + is_trained = weights is not None or weights_backbone is not None + trainable_backbone_layers = _validate_trainable_layers(is_trained, trainable_backbone_layers, 5, 3) + + backbone = resnet50(weights=weights_backbone, progress=progress) + backbone = _resnet_fpn_extractor(backbone, trainable_backbone_layers, norm_layer=nn.BatchNorm2d) + rpn_anchor_generator = _default_anchorgen() + rpn_head = RPNHead(backbone.out_channels, rpn_anchor_generator.num_anchors_per_location()[0], conv_depth=2) + box_head = FastRCNNHeads((backbone.out_channels, 7, 7), [256, 256, 256, 256], 1024, norm_layer=nn.BatchNorm2d) + mask_head = MaskRCNNHeads(backbone.out_channels, [256, 256, 256, 256], 1, norm_layer=nn.BatchNorm2d) + model = MaskRCNN( + backbone, + num_classes=num_classes, + rpn_anchor_generator=rpn_anchor_generator, + rpn_head=rpn_head, + box_head=box_head, + mask_head=mask_head, + **kwargs, + ) + + if weights is not None: + model.load_state_dict(weights.get_state_dict(progress=progress)) + + return model diff --git a/torchvision/models/detection/rpn.py b/torchvision/models/detection/rpn.py index 18379ac25f6..2b7ccb7b9ae 100644 --- a/torchvision/models/detection/rpn.py +++ b/torchvision/models/detection/rpn.py @@ -3,6 +3,7 @@ import torch from torch import nn, Tensor from torch.nn import functional as F +from torchvision.ops import Conv2dNormActivation from torchvision.ops import boxes as box_ops from . import _utils as det_utils @@ -19,23 +20,59 @@ class RPNHead(nn.Module): Args: in_channels (int): number of channels of the input feature num_anchors (int): number of anchors to be predicted + conv_depth (int, optional): number of convolutions """ - def __init__(self, in_channels: int, num_anchors: int) -> None: + _version = 2 + + def __init__(self, in_channels: int, num_anchors: int, conv_depth=1) -> None: super().__init__() - self.conv = nn.Conv2d(in_channels, in_channels, kernel_size=3, stride=1, padding=1) + convs = [] + for _ in range(conv_depth): + convs.append(Conv2dNormActivation(in_channels, in_channels, kernel_size=3, norm_layer=None)) + self.conv = nn.Sequential(*convs) self.cls_logits = nn.Conv2d(in_channels, num_anchors, kernel_size=1, stride=1) self.bbox_pred = nn.Conv2d(in_channels, num_anchors * 4, kernel_size=1, stride=1) - for layer in self.children(): - torch.nn.init.normal_(layer.weight, std=0.01) # type: ignore[arg-type] - torch.nn.init.constant_(layer.bias, 0) # type: ignore[arg-type] + for layer in self.modules(): + if isinstance(layer, nn.Conv2d): + torch.nn.init.normal_(layer.weight, std=0.01) # type: ignore[arg-type] + if layer.bias is not None: + torch.nn.init.constant_(layer.bias, 0) # type: ignore[arg-type] + + def _load_from_state_dict( + self, + state_dict, + prefix, + local_metadata, + strict, + missing_keys, + unexpected_keys, + error_msgs, + ): + version = local_metadata.get("version", None) + + if version is None or version < 2: + for type in ["weight", "bias"]: + old_key = f"{prefix}conv.{type}" + new_key = f"{prefix}conv.0.0.{type}" + state_dict[new_key] = state_dict.pop(old_key) + + super()._load_from_state_dict( + state_dict, + prefix, + local_metadata, + strict, + missing_keys, + unexpected_keys, + error_msgs, + ) def forward(self, x: List[Tensor]) -> Tuple[List[Tensor], List[Tensor]]: logits = [] bbox_reg = [] for feature in x: - t = F.relu(self.conv(feature)) + t = self.conv(feature) logits.append(self.cls_logits(t)) bbox_reg.append(self.bbox_pred(t)) return logits, bbox_reg From 592784dfdfb8b2b4977315af1dd8d224bc49636f Mon Sep 17 00:00:00 2001 From: Vasilis Vryniotis Date: Wed, 30 Mar 2022 18:20:16 +0100 Subject: [PATCH 25/29] Adding gIoU support to retinanet --- test/test_models.py | 2 +- torchvision/models/detection/_utils.py | 28 +++++++++++++++++++++-- torchvision/models/detection/retinanet.py | 15 ++++++++---- 3 files changed, 37 insertions(+), 8 deletions(-) diff --git a/test/test_models.py b/test/test_models.py index 27a7ad983a7..9c7dc358eaf 100644 --- a/test/test_models.py +++ b/test/test_models.py @@ -332,7 +332,7 @@ def _check_input_backprop(model, inputs): }, "retinanet_resnet50_fpn_v2": { "max_trainable": 5, - "n_trn_params_per_layer": [44, 54, 73, 86, 96, 97], + "n_trn_params_per_layer": [44, 74, 131, 170, 200, 203], }, "keypointrcnn_resnet50_fpn": { "max_trainable": 5, diff --git a/torchvision/models/detection/_utils.py b/torchvision/models/detection/_utils.py index 40923794edf..b68bab27c92 100644 --- a/torchvision/models/detection/_utils.py +++ b/torchvision/models/detection/_utils.py @@ -1,10 +1,11 @@ import math from collections import OrderedDict -from typing import List, Tuple +from typing import Dict, List, Optional, Tuple import torch from torch import Tensor, nn -from torchvision.ops.misc import FrozenBatchNorm2d +from torch.nn import functional as F +from torchvision.ops import FrozenBatchNorm2d, generalized_box_iou_loss class BalancedPositiveNegativeSampler: @@ -507,3 +508,26 @@ def _topk_min(input: Tensor, orig_kval: int, axis: int) -> int: axis_dim_val = torch._shape_as_tensor(input)[axis].unsqueeze(0) min_kval = torch.min(torch.cat((torch.tensor([orig_kval], dtype=axis_dim_val.dtype), axis_dim_val), 0)) return _fake_cast_onnx(min_kval) + + +def _box_loss( + type: str, + box_coder: BoxCoder, + anchors_per_image: Tensor, + matched_gt_boxes_per_image: Tensor, + bbox_regression_per_image: Tensor, + cnf: Optional[Dict[str, float]] = None, +) -> Tensor: + torch._assert(type not in ["l1", "smooth_l1", "giou"], f"Unsupported loss: {type}") + + if type == "l1": + target_regression = box_coder.encode_single(matched_gt_boxes_per_image, anchors_per_image) + return F.l1_loss(bbox_regression_per_image, target_regression, reduction="sum") + elif type == "smooth_l1": + target_regression = box_coder.encode_single(matched_gt_boxes_per_image, anchors_per_image) + beta = cnf["beta"] if cnf is not None and "beta" in cnf else 1.0 + return F.smooth_l1_loss(bbox_regression_per_image, target_regression, reduction="sum", beta=beta) + else: # giou + bbox_per_image = box_coder.decode_single(bbox_regression_per_image, anchors_per_image) + eps = cnf["eps"] if cnf is not None and "eps" in cnf else 1e-7 + return generalized_box_iou_loss(bbox_per_image, matched_gt_boxes_per_image, reduction="sum", eps=eps) diff --git a/torchvision/models/detection/retinanet.py b/torchvision/models/detection/retinanet.py index c7eb47d20d2..39b7edc8c13 100644 --- a/torchvision/models/detection/retinanet.py +++ b/torchvision/models/detection/retinanet.py @@ -18,7 +18,7 @@ from .._utils import handle_legacy_interface, _ovewrite_value_param from ..resnet import ResNet50_Weights, resnet50 from . import _utils as det_utils -from ._utils import overwrite_eps +from ._utils import overwrite_eps, _box_loss from .anchor_utils import AnchorGenerator from .backbone_utils import _resnet_fpn_extractor, _validate_trainable_layers from .transform import GeneralizedRCNNTransform @@ -243,6 +243,7 @@ def __init__(self, in_channels, num_anchors, norm_layer: Optional[Callable[..., torch.nn.init.zeros_(layer.bias) self.box_coder = det_utils.BoxCoder(weights=(1.0, 1.0, 1.0, 1.0)) + self._loss_type = "l1" def _load_from_state_dict( self, @@ -287,12 +288,15 @@ def compute_loss(self, targets, head_outputs, anchors, matched_idxs): bbox_regression_per_image = bbox_regression_per_image[foreground_idxs_per_image, :] anchors_per_image = anchors_per_image[foreground_idxs_per_image, :] - # compute the regression targets - target_regression = self.box_coder.encode_single(matched_gt_boxes_per_image, anchors_per_image) - # compute the loss losses.append( - torch.nn.functional.l1_loss(bbox_regression_per_image, target_regression, reduction="sum") + _box_loss( + self._loss_type, + self.box_coder, + anchors_per_image, + matched_gt_boxes_per_image, + bbox_regression_per_image, + ) / max(1, num_foreground) ) @@ -827,6 +831,7 @@ def retinanet_resnet50_fpn_v2( num_classes, norm_layer=partial(nn.GroupNorm, 32), ) + head.regression_head._loss_type = "giou" model = RetinaNet(backbone, num_classes, anchor_generator=anchor_generator, head=head, **kwargs) if weights is not None: From 2cff6401d912b0570b02f26457c6a406984b326f Mon Sep 17 00:00:00 2001 From: Vasilis Vryniotis Date: Wed, 30 Mar 2022 18:38:44 +0100 Subject: [PATCH 26/29] Fix assert --- torchvision/models/detection/_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/torchvision/models/detection/_utils.py b/torchvision/models/detection/_utils.py index b68bab27c92..f4c426691c0 100644 --- a/torchvision/models/detection/_utils.py +++ b/torchvision/models/detection/_utils.py @@ -518,7 +518,7 @@ def _box_loss( bbox_regression_per_image: Tensor, cnf: Optional[Dict[str, float]] = None, ) -> Tensor: - torch._assert(type not in ["l1", "smooth_l1", "giou"], f"Unsupported loss: {type}") + torch._assert(type in ["l1", "smooth_l1", "giou"], f"Unsupported loss: {type}") if type == "l1": target_regression = box_coder.encode_single(matched_gt_boxes_per_image, anchors_per_image) From 61412dfbaa869685a8467c0bd127367f9bf8694c Mon Sep 17 00:00:00 2001 From: Vasilis Vryniotis Date: Fri, 1 Apr 2022 08:30:00 +0100 Subject: [PATCH 27/29] Add back nesterov momentum --- references/detection/train.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/references/detection/train.py b/references/detection/train.py index efe6208012f..758171013e8 100644 --- a/references/detection/train.py +++ b/references/detection/train.py @@ -209,7 +209,13 @@ def main(args): opt_name = args.opt.lower() if opt_name.startswith("sgd"): - optimizer = torch.optim.SGD(parameters, lr=args.lr, momentum=args.momentum, weight_decay=args.weight_decay) + optimizer = torch.optim.SGD( + parameters, + lr=args.lr, + momentum=args.momentum, + weight_decay=args.weight_decay, + nesterov="nesterov" in opt_name, + ) elif opt_name == "adamw": optimizer = torch.optim.AdamW(parameters, lr=args.lr, weight_decay=args.weight_decay) else: From 24b86430d537ff4bac8093077726627549f5be14 Mon Sep 17 00:00:00 2001 From: Vasilis Vryniotis Date: Mon, 4 Apr 2022 10:33:24 +0100 Subject: [PATCH 28/29] Rename and extend `FastRCNNConvFCHead` to support arbitrary FCs --- torchvision/models/detection/faster_rcnn.py | 25 ++++++++++++--------- torchvision/models/detection/mask_rcnn.py | 4 ++-- 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/torchvision/models/detection/faster_rcnn.py b/torchvision/models/detection/faster_rcnn.py index f5bf350e442..51aae780386 100644 --- a/torchvision/models/detection/faster_rcnn.py +++ b/torchvision/models/detection/faster_rcnn.py @@ -304,31 +304,34 @@ def forward(self, x): return x -class FastRCNNHeads(nn.Sequential): +class FastRCNNConvFCHead(nn.Sequential): def __init__( self, input_size: Tuple[int, int, int], - layers: List[int], - output_channels: int, + conv_layers: List[int], + fc_layers: List[int], norm_layer: Optional[Callable[..., nn.Module]] = None, ): """ Args: input_size (Tuple[int, int, int]): the input size in CHW format. - layers (list): feature dimensions of each FCN layer - output_channels (int): output channels + conv_layers (list): feature dimensions of each Convolution layer + fc_layers (list): feature dimensions of each FCN layer norm_layer (callable, optional): Module specifying the normalization layer to use. Default: None """ in_channels, in_height, in_width = input_size blocks = [] previous_channels = in_channels - for layer_channels in layers: - blocks.append(misc_nn_ops.Conv2dNormActivation(previous_channels, layer_channels, norm_layer=norm_layer)) - previous_channels = layer_channels + for current_channels in conv_layers: + blocks.append(misc_nn_ops.Conv2dNormActivation(previous_channels, current_channels, norm_layer=norm_layer)) + previous_channels = current_channels blocks.append(nn.Flatten()) - blocks.append(nn.Linear(previous_channels * in_height * in_width, output_channels)) - blocks.append(nn.ReLU(inplace=True)) + previous_channels = previous_channels * in_height * in_width + for current_channels in fc_layers: + blocks.append(nn.Linear(previous_channels, current_channels)) + blocks.append(nn.ReLU(inplace=True)) + previous_channels = current_channels super().__init__(*blocks) for layer in self.modules(): @@ -567,7 +570,7 @@ def fasterrcnn_resnet50_fpn_v2( backbone = _resnet_fpn_extractor(backbone, trainable_backbone_layers, norm_layer=nn.BatchNorm2d) rpn_anchor_generator = _default_anchorgen() rpn_head = RPNHead(backbone.out_channels, rpn_anchor_generator.num_anchors_per_location()[0], conv_depth=2) - box_head = FastRCNNHeads((backbone.out_channels, 7, 7), [256, 256, 256, 256], 1024, norm_layer=nn.BatchNorm2d) + box_head = FastRCNNConvFCHead((backbone.out_channels, 7, 7), [256, 256, 256, 256], [1024], norm_layer=nn.BatchNorm2d) model = FasterRCNN( backbone, num_classes=num_classes, diff --git a/torchvision/models/detection/mask_rcnn.py b/torchvision/models/detection/mask_rcnn.py index 179f2c176ed..2940936c5ec 100644 --- a/torchvision/models/detection/mask_rcnn.py +++ b/torchvision/models/detection/mask_rcnn.py @@ -12,7 +12,7 @@ from ..resnet import ResNet50_Weights, resnet50 from ._utils import overwrite_eps from .backbone_utils import _resnet_fpn_extractor, _validate_trainable_layers -from .faster_rcnn import FasterRCNN, FastRCNNHeads, RPNHead, _default_anchorgen +from .faster_rcnn import FasterRCNN, FastRCNNConvFCHead, RPNHead, _default_anchorgen __all__ = [ @@ -508,7 +508,7 @@ def maskrcnn_resnet50_fpn_v2( backbone = _resnet_fpn_extractor(backbone, trainable_backbone_layers, norm_layer=nn.BatchNorm2d) rpn_anchor_generator = _default_anchorgen() rpn_head = RPNHead(backbone.out_channels, rpn_anchor_generator.num_anchors_per_location()[0], conv_depth=2) - box_head = FastRCNNHeads((backbone.out_channels, 7, 7), [256, 256, 256, 256], 1024, norm_layer=nn.BatchNorm2d) + box_head = FastRCNNConvFCHead((backbone.out_channels, 7, 7), [256, 256, 256, 256], [1024], norm_layer=nn.BatchNorm2d) mask_head = MaskRCNNHeads(backbone.out_channels, [256, 256, 256, 256], 1, norm_layer=nn.BatchNorm2d) model = MaskRCNN( backbone, From 6488c41e40bc25cd09451d985e9f90de26715a1c Mon Sep 17 00:00:00 2001 From: Vasilis Vryniotis Date: Mon, 4 Apr 2022 11:37:34 +0100 Subject: [PATCH 29/29] Fix linter --- torchvision/models/detection/faster_rcnn.py | 4 +++- torchvision/models/detection/mask_rcnn.py | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/torchvision/models/detection/faster_rcnn.py b/torchvision/models/detection/faster_rcnn.py index 51aae780386..91aacba7a0a 100644 --- a/torchvision/models/detection/faster_rcnn.py +++ b/torchvision/models/detection/faster_rcnn.py @@ -570,7 +570,9 @@ def fasterrcnn_resnet50_fpn_v2( backbone = _resnet_fpn_extractor(backbone, trainable_backbone_layers, norm_layer=nn.BatchNorm2d) rpn_anchor_generator = _default_anchorgen() rpn_head = RPNHead(backbone.out_channels, rpn_anchor_generator.num_anchors_per_location()[0], conv_depth=2) - box_head = FastRCNNConvFCHead((backbone.out_channels, 7, 7), [256, 256, 256, 256], [1024], norm_layer=nn.BatchNorm2d) + box_head = FastRCNNConvFCHead( + (backbone.out_channels, 7, 7), [256, 256, 256, 256], [1024], norm_layer=nn.BatchNorm2d + ) model = FasterRCNN( backbone, num_classes=num_classes, diff --git a/torchvision/models/detection/mask_rcnn.py b/torchvision/models/detection/mask_rcnn.py index 2940936c5ec..01e56c7a108 100644 --- a/torchvision/models/detection/mask_rcnn.py +++ b/torchvision/models/detection/mask_rcnn.py @@ -508,7 +508,9 @@ def maskrcnn_resnet50_fpn_v2( backbone = _resnet_fpn_extractor(backbone, trainable_backbone_layers, norm_layer=nn.BatchNorm2d) rpn_anchor_generator = _default_anchorgen() rpn_head = RPNHead(backbone.out_channels, rpn_anchor_generator.num_anchors_per_location()[0], conv_depth=2) - box_head = FastRCNNConvFCHead((backbone.out_channels, 7, 7), [256, 256, 256, 256], [1024], norm_layer=nn.BatchNorm2d) + box_head = FastRCNNConvFCHead( + (backbone.out_channels, 7, 7), [256, 256, 256, 256], [1024], norm_layer=nn.BatchNorm2d + ) mask_head = MaskRCNNHeads(backbone.out_channels, [256, 256, 256, 256], 1, norm_layer=nn.BatchNorm2d) model = MaskRCNN( backbone,