Repository: pytorch/vision

Issue:
Raise error if target boxes are degenerate in Faster R-CNN
We have had a number of reports with users saying that their training loss is nan after a few iterations.

Most of the time, this is due to degenerate boxes (i.e., boxes with negative sizes or zero area). We should improve the user experience in those situations.

I think that raising an error in `GeneralizedRCNN` if the target boxes are degenerate would be a good compromise.

Related issues: https://github.com/pytorch/vision/issues/2235 https://github.com/pytorch/vision/issues/1994 https://github.com/pytorch/vision/issues/1176 https://github.com/pytorch/vision/issues/1128 #1120 and #997


Discussion:
@fmassa which option would be better, to Raise an Exception or to print a warning and do something like you suggested here: [https://github.com/pytorch/vision/issues/2235#L82](https://github.com/pytorch/vision/issues/2235#L82) i.e. Delete the degenerate boxes and carry on?
I'm leaning towards raising an error, so that it is explicit to the user that there is a problem with their data and they should fix it.

Resolving patch:
diff --git a/torchvision/models/detection/generalized_rcnn.py b/torchvision/models/detection/generalized_rcnn.py
--- a/torchvision/models/detection/generalized_rcnn.py
+++ b/torchvision/models/detection/generalized_rcnn.py
@@ -77,6 +77,21 @@ def forward(self, images, targets=None):
             original_image_sizes.append((val[0], val[1]))
 
         images, targets = self.transform(images, targets)
+
+        # Check for degenerate boxes
+        # TODO: Move this to a function
+        if targets is not None:
+            for target_idx, target in enumerate(targets):
+                boxes = target["boxes"]
+                degenerate_boxes = boxes[:, 2:] <= boxes[:, :2]
+                if degenerate_boxes.any():
+                    # print the first degenrate box
+                    bb_idx = degenerate_boxes.any(dim=1).nonzero().view(-1)[0]
+                    degen_bb: List[float] = boxes[bb_idx].tolist()
+                    raise ValueError("All bounding boxes should have positive height and width."
+                                     " Found invaid box {} for target at index {}."
+                                     .format(degen_bb, target_idx))
+
         features = self.backbone(images.tensors)
         if isinstance(features, torch.Tensor):
             features = OrderedDict([('0', features)])


Test patch:
diff --git a/test/test_models.py b/test/test_models.py
--- a/test/test_models.py
+++ b/test/test_models.py
@@ -158,7 +158,7 @@ def compute_mean_std(tensor):
     def _test_detection_model_validation(self, name):
         set_rng_seed(0)
         model = models.detection.__dict__[name](num_classes=50, pretrained_backbone=False)
-        input_shape = (1, 3, 300, 300)
+        input_shape = (3, 300, 300)
         x = [torch.rand(input_shape)]
 
         # validate that targets are present in training
@@ -173,6 +173,11 @@ def _test_detection_model_validation(self, name):
             targets = [{'boxes': boxes}]
             self.assertRaises(ValueError, model, x, targets=targets)
 
+        # validate that no degenerate boxes are present
+        boxes = torch.tensor([[1, 3, 1, 4], [2, 4, 3, 4]])
+        targets = [{'boxes': boxes}]
+        self.assertRaises(ValueError, model, x, targets=targets)
+
     def _test_video_model(self, name):
         # the default input shape is
         # bs * num_channels * clip_len * h *w