Repository: pytorch/ignite

Issue:
Bug in multiclass case with Precision and Recall
In case when `y_pred` contains less classes then `y_true`, we should raise `ValueError`:
```python
pr = Precision()
y_pred = torch.randint(0, 2, size=(10, 4)).float()
y = torch.randint(0, 5, size=(10, )).long()
pr.update((y_pred, y))
```
Actually, above code raises an error in `to_onehot`:
```
indices = tensor([2, 2, 3, 4, 1, 4, 1, 3, 3, 0]), num_classes = 4

    def to_onehot(indices, num_classes):
        """Convert a tensor of indices to a tensor of one-hot indicators."""
        onehot = torch.zeros(indices.size(0), num_classes, device=indices.device)
>       return onehot.scatter_(1, indices.unsqueeze(1), 1)
E       RuntimeError: Invalid index in scatter at /Users/administrator/nightlies/pytorch-1.0.0/wheel_build_dirs/wheel_3.7/pytorch/aten/src/TH/generic/THTensorEvenMoreMath.cpp:546
```



Discussion:

Resolving patch:
diff --git a/ignite/metrics/precision.py b/ignite/metrics/precision.py
--- a/ignite/metrics/precision.py
+++ b/ignite/metrics/precision.py
@@ -90,6 +90,9 @@ def update(self, output):
             y = y.view(-1)
         elif self._type == "multiclass":
             num_classes = y_pred.size(1)
+            if y.max() + 1 > num_classes:
+                raise ValueError("y_pred contains less classes than y. Number of classes in output is {}"
+                                 " and element in y has invalid class = {}.".format(num_classes, y.max().item() + 1))
             y = to_onehot(y.view(-1), num_classes=num_classes)
             indices = torch.max(y_pred, dim=1)[1].view(-1)
             y_pred = to_onehot(indices, num_classes=num_classes)
diff --git a/ignite/metrics/recall.py b/ignite/metrics/recall.py
--- a/ignite/metrics/recall.py
+++ b/ignite/metrics/recall.py
@@ -65,6 +65,9 @@ def update(self, output):
             y = y.view(-1)
         elif self._type == "multiclass":
             num_classes = y_pred.size(1)
+            if y.max() + 1 > num_classes:
+                raise ValueError("y_pred contains less classes than y. Number of predicted classes is {}"
+                                 " and element in y has invalid class = {}.".format(num_classes, y.max().item() + 1))
             y = to_onehot(y.view(-1), num_classes=num_classes)
             indices = torch.max(y_pred, dim=1)[1].view(-1)
             y_pred = to_onehot(indices, num_classes=num_classes)


Test patch:
diff --git a/tests/ignite/metrics/test_precision.py b/tests/ignite/metrics/test_precision.py
--- a/tests/ignite/metrics/test_precision.py
+++ b/tests/ignite/metrics/test_precision.py
@@ -643,3 +643,18 @@ def _test(average):
 
     _test(average=True)
     _test(average=False)
+
+
+def test_incorrect_y_classes():
+
+    def _test(average):
+        pr = Precision(average=average)
+
+        y_pred = torch.randint(0, 2, size=(10, 4)).float()
+        y = torch.randint(0, 5, size=(10, )).long()
+
+        with pytest.raises(ValueError):
+            pr.update((y_pred, y))
+
+    _test(average=True)
+    _test(average=False)
diff --git a/tests/ignite/metrics/test_recall.py b/tests/ignite/metrics/test_recall.py
--- a/tests/ignite/metrics/test_recall.py
+++ b/tests/ignite/metrics/test_recall.py
@@ -643,3 +643,18 @@ def _test(average):
 
     _test(average=True)
     _test(average=False)
+
+
+def test_incorrect_y_classes():
+
+    def _test(average):
+        re = Recall(average=average)
+
+        y_pred = torch.randint(0, 2, size=(10, 4)).float()
+        y = torch.randint(0, 5, size=(10, )).long()
+
+        with pytest.raises(ValueError):
+            re.update((y_pred, y))
+
+    _test(average=True)
+    _test(average=False)