Repository: Lightning-AI/torchmetrics

Issue:
Prefix clash with MetricCollection
## 🐛 Bug

There is a bug that happens with metrics that returns dictionaries when used in MetricCollection.

### To Reproduce

Steps to reproduce the behavior...

```python
import torch
from torchmetrics import Metric, MetricCollection

class CustomAccuracy(Metric):
    def __init__(self):
        super().__init__()
        self.prefix = 'accuracy'
    
    def update(self, preds: torch.Tensor, target: torch.Tensor) -> None:
        self.correct = torch.sum(preds == target)
        self.total = preds.numel()
        

    def compute(self) -> torch.Tensor:
        res = self.correct.float() / self.total
        return {f"{self.prefix}/value": res}

class CustomPrecision(Metric):
    def __init__(self):
        super().__init__()
        self.prefix = 'precision'
        
    def update(self, preds: torch.Tensor, target: torch.Tensor) -> None:
        self.true_positives = torch.sum((preds == target) & (preds == 1))
        self.predicted_positives = torch.sum(preds == 1)

    def compute(self) -> torch.Tensor:
        res =  self.true_positives.float() / self.predicted_positives
        return {f"{self.prefix}/value": res}

# Initialize MetricCollection with Accuracy and Precision
metrics = MetricCollection([CustomAccuracy(), CustomPrecision()])

# Mock predictions and targets
preds = torch.tensor([1, 0, 0, 1])
targets = torch.tensor([1, 0, 0, 0])

# Update metrics with current batch
metrics(preds, targets)

# Print the calculated metrics
print(metrics.compute())
# Returns {'precisionaccuracy/value': tensor(0.7500), 'precisionprecision/value': tensor(0.5000)}
```

<details>
  <summary>Code sample</summary>

<!-- Ideally attach a minimal code sample to reproduce the decried issue.
Minimal means having the shortest code but still preserving the bug. -->

</details>

### Expected behavior

I expect the results to be 

`{'accuracy/value': tensor(0.7500), 'precision/value': tensor(0.5000)}`

and not 

`{'precisionaccuracy/value': tensor(0.7500), 'precisionprecision/value': tensor(0.5000)}`


### Environment

- TorchMetrics version (and how you installed TM, e.g. `conda`, `pip`, build from source): 1.1.1, via pip
- Python & PyTorch Version: 3.10 and 1.13
- Any other relevant information such as OS (e.g., Linux):

### Additional context

Please note that this works correctly in torchmetrics 0.11.4



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
@@ -339,7 +339,8 @@ def _compute_and_reduce(
         _, duplicates = _flatten_dict(result)
 
         flattened_results = {}
-        for k, res in result.items():
+        for k, m in self.items(keep_base=True, copy_state=False):
+            res = result[k]
             if isinstance(res, dict):
                 for key, v in res.items():
                     # if duplicates of keys we need to add unique prefix to each key
@@ -347,9 +348,9 @@ def _compute_and_reduce(
                         stripped_k = k.replace(getattr(m, "prefix", ""), "")
                         stripped_k = stripped_k.replace(getattr(m, "postfix", ""), "")
                         key = f"{stripped_k}_{key}"
-                    if hasattr(m, "prefix") and m.prefix is not None:
+                    if getattr(m, "_from_collection", None) and m.prefix is not None:
                         key = f"{m.prefix}{key}"
-                    if hasattr(m, "postfix") and m.postfix is not None:
+                    if getattr(m, "_from_collection", None) and m.postfix is not None:
                         key = f"{key}{m.postfix}"
                     flattened_results[key] = v
             else:
@@ -425,6 +426,7 @@ def add_metrics(
                     for k, v in metric.items(keep_base=False):
                         v.postfix = metric.postfix
                         v.prefix = metric.prefix
+                        v._from_collection = True
                         self[f"{name}_{k}"] = v
         elif isinstance(metrics, Sequence):
             for metric in metrics:
@@ -442,6 +444,7 @@ def add_metrics(
                     for k, v in metric.items(keep_base=False):
                         v.postfix = metric.postfix
                         v.prefix = metric.prefix
+                        v._from_collection = True
                         self[k] = v
         else:
             raise ValueError(


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
@@ -644,3 +644,36 @@ def test_double_nested_collections(base_metrics, expected):
 
     for key in val:
         assert key in expected
+
+
+def test_with_custom_prefix_postfix():
+    """Test that metric colection does not clash with custom prefix and postfix in users metrics.
+
+    See issue: https://github.com/Lightning-AI/torchmetrics/issues/2065
+
+    """
+
+    class CustomAccuracy(MulticlassAccuracy):
+        prefix = "my_prefix"
+        postfix = "my_postfix"
+
+        def compute(self):
+            value = super().compute()
+            return {f"{self.prefix}/accuracy/{self.postfix}": value}
+
+    class CustomPrecision(MulticlassAccuracy):
+        prefix = "my_prefix"
+        postfix = "my_postfix"
+
+        def compute(self):
+            value = super().compute()
+            return {f"{self.prefix}/precision/{self.postfix}": value}
+
+    metrics = MetricCollection([CustomAccuracy(num_classes=2), CustomPrecision(num_classes=2)])
+
+    # Update metrics with current batch
+    res = metrics(torch.tensor([1, 0, 0, 1]), torch.tensor([1, 0, 0, 0]))
+
+    # Print the calculated metrics
+    assert "my_prefix/accuracy/my_postfix" in res
+    assert "my_prefix/precision/my_postfix" in res