Repository: Lightning-AI/torchmetrics

Issue:
MetricCollection just recomputes the first metric after first .compute call if compute_groups is enabled
## 🐛 Bug

I think the MetricCollection was designed to be used as follows:

```
metrics = MetricCollection([...])
for _ in range(...):
    metrics.update(...)
metrics.compute()
metrics.reset()
```

But I used it the following way:

```
metrics = MetricCollection([...])
for _ in range(...):
    metrics.update(...)
    metrics.compute()
metrics.reset()
```
This does not work. After the first compute call only the first metric in the metric collection will return a new value. This is because after calling `.compute` of all metrics in the metric collection the respective `._computed` property is set to a value. If the metrics are in the same compute_group then the next `.update` call of the metric collection will invoke the `.update` method only for the first metric. However it is exactly this method that sets `._computed` to None again. For the other metrics it is still not None and thus will return the cached value of the first computation the whole time.

### To Reproduce

```
from torch import Tensor
import numpy as np
from torchmetrics import MetricCollection
from torchmetrics.classification import BinaryPrecision, BinaryAccuracy, BinaryRecall, BinarySpecificity
# Generate random predictions and labels for a binary classification problem
np.random.seed(42)  # For reproducibility
n_samples = 100  # Number of samples


# Metrics calculation
metrics = MetricCollection([
    BinaryRecall(),
    BinaryAccuracy(),
    BinaryPrecision(),
    BinarySpecificity()
])

n_epochs = 10

for _ in range(n_epochs):
    # Generate random predictions (0 or 1)
    predictions = Tensor(np.random.randint(2, size=n_samples))

    # Generate random true labels (0 or 1)
    true_labels = Tensor(np.random.randint(2, size=n_samples))

    metrics.update(predictions, true_labels)
    print(metrics.compute())


```
### Expected behavior

The compute method should return the new calculations for all metrics.

### Environment

- TorchMetrics 1.2.0, installed with pip
- Python 3.11
- PyTorch 2.1.0
- MacOs

### Additional context

<!-- Add any other context about the problem here. -->



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

Resolving patch:
diff --git a/src/torchmetrics/collections.py b/src/torchmetrics/collections.py
--- a/src/torchmetrics/collections.py
+++ b/src/torchmetrics/collections.py
@@ -304,6 +304,7 @@ def _compute_groups_create_state_ref(self, copy: bool = False) -> None:
                         # Determine if we just should set a reference or a full copy
                         setattr(mi, state, deepcopy(m0_state) if copy else m0_state)
                     mi._update_count = deepcopy(m0._update_count) if copy else m0._update_count
+                    mi._computed = deepcopy(m0._computed) if copy else m0._computed
         self._state_is_copy = copy
 
     def compute(self) -> Dict[str, Any]:


Test patch:
diff --git a/tests/unittests/bases/test_collections.py b/tests/unittests/bases/test_collections.py
--- a/tests/unittests/bases/test_collections.py
+++ b/tests/unittests/bases/test_collections.py
@@ -411,7 +411,8 @@ class TestComputeGroups:
             ("prefix_", "_postfix"),
         ],
     )
-    def test_check_compute_groups_correctness(self, metrics, expected, preds, target, prefix, postfix):
+    @pytest.mark.parametrize("with_reset", [True, False])
+    def test_check_compute_groups_correctness(self, metrics, expected, preds, target, prefix, postfix, with_reset):
         """Check that compute groups are formed after initialization and that metrics are correctly computed."""
         if isinstance(metrics, MetricCollection):
             prefix, postfix = None, None  # disable for nested collections
@@ -445,8 +446,9 @@ def test_check_compute_groups_correctness(self, metrics, expected, preds, target
             for key in res_cg:
                 assert torch.allclose(res_cg[key], res_without_cg[key])
 
-            m.reset()
-            m2.reset()
+            if with_reset:
+                m.reset()
+                m2.reset()
 
     @pytest.mark.parametrize("method", ["items", "values", "keys"])
     def test_check_compute_groups_items_and_values(self, metrics, expected, preds, target, method):