Repository: Lightning-AI/torchmetrics

Issue:
MultioutputWrapper does not reset cleanly
## 🐛 Bug

Calling `MultioutputWrapper.compute()` after `MultioutputWrapper.reset()` returns old metrics that should have been cleared by the reset. 

### To Reproduce

<!-- If you have a code sample, error messages, stack traces, please provide it here as well -->

#### Code sample

```py
import torch
import torchmetrics

base_metric = torchmetrics.ConfusionMatrix(task="multiclass", num_classes=2)
cf = torchmetrics.MultioutputWrapper(base_metric, num_outputs=2)

cf(torch.tensor([[0,0]]), torch.tensor([[0,0]]))
print("First result: ", cf.compute())

cf.reset()

cf(torch.tensor([[1,1]]), torch.tensor([[0,0]]))
print("Second result: ", cf.compute())
```

Output:
```
First result:  [tensor([[1, 0], [0, 0]]), tensor([[1, 0], [0, 0]])]
Second result:  [tensor([[1, 0], [0, 0]]), tensor([[1, 0], [0, 0]])]
```
The old output is returned even after resetting and entering new data. If the fist metric computation is omitted, the second metric is as expected.

Importantly, this bug only occurs when using `forward()` to enter data, while `update()` works as expected.

### Expected behavior

The result of the second computation should be independent of the first. Furthermore, forward and update should produce the same state as specified in the docs.

### Environment

- torchmetrics 0.10.3, installed from pypi
- Python 3.8.9

### Attempts to fix

Adding `super().reset()` (as done in e.g. the minmax wrapper) at the top of the reset method seems to fix the bug. 
https://github.com/Lightning-AI/metrics/blob/7b505ff1a3b88181bef2b0cdfa21ec593dcda3ff/src/torchmetrics/wrappers/multioutput.py#L133


Discussion:
Hi! thanks for your contribution!, great first issue!

Resolving patch:
diff --git a/src/torchmetrics/wrappers/multioutput.py b/src/torchmetrics/wrappers/multioutput.py
--- a/src/torchmetrics/wrappers/multioutput.py
+++ b/src/torchmetrics/wrappers/multioutput.py
@@ -1,5 +1,5 @@
 from copy import deepcopy
-from typing import Any, List, Tuple
+from typing import Any, Callable, List, Tuple
 
 import torch
 from torch import Tensor
@@ -132,3 +132,12 @@ def reset(self) -> None:
         """Reset all underlying metrics."""
         for metric in self.metrics:
             metric.reset()
+        super().reset()
+
+    def _wrap_update(self, update: Callable) -> Callable:
+        """Overwrite to do nothing."""
+        return update
+
+    def _wrap_compute(self, compute: Callable) -> Callable:
+        """Overwrite to do nothing."""
+        return compute


Test patch:
diff --git a/tests/unittests/wrappers/test_multioutput.py b/tests/unittests/wrappers/test_multioutput.py
--- a/tests/unittests/wrappers/test_multioutput.py
+++ b/tests/unittests/wrappers/test_multioutput.py
@@ -5,10 +5,10 @@
 import torch
 from sklearn.metrics import accuracy_score
 from sklearn.metrics import r2_score as sk_r2score
-from torch import Tensor
+from torch import Tensor, tensor
 
 from torchmetrics import Metric
-from torchmetrics.classification import MulticlassAccuracy
+from torchmetrics.classification import ConfusionMatrix, MulticlassAccuracy
 from torchmetrics.regression import R2Score
 from torchmetrics.wrappers.multioutput import MultioutputWrapper
 from unittests.helpers import seed_all
@@ -120,3 +120,17 @@ def test_multioutput_wrapper(
             dist_sync_on_step,
             metric_args=dict(num_outputs=num_outputs, base_metric_class=base_metric_class),
         )
+
+
+def test_reset_called_correctly():
+    """Check that underlying metric is being correctly reset when calling forward."""
+    base_metric = ConfusionMatrix(task="multiclass", num_classes=2)
+    cf = MultioutputWrapper(base_metric, num_outputs=2)
+
+    res = cf(tensor([[0, 0]]), tensor([[0, 0]]))
+    assert torch.allclose(res[0], tensor([[1, 0], [0, 0]]))
+    assert torch.allclose(res[1], tensor([[1, 0], [0, 0]]))
+    cf.reset()
+    res = cf(tensor([[1, 1]]), tensor([[0, 0]]))
+    assert torch.allclose(res[0], tensor([[0, 1], [0, 0]]))
+    assert torch.allclose(res[1], tensor([[0, 1], [0, 0]]))