Repository: pytorch/ignite

Issue:
Breaking change in metrics behaviour
## 🐛 Bug description

Following https://github.com/pytorch/ignite/pull/968 (and associated issue ) metric's output is flatten if it returns a mapping/dict. In details, with ignite v0.4.2, for a 
custom metric's we have the following:
```python
class PerformanceIndicators(Metric):

    def compute(self):
        # ...
        return {
            'a': 12,
            'b': 23
        }

PerformanceIndicators().attach(evaluator, name="indicators")

assert "a" in evaluator.state.metrics
assert "b" in evaluator.state.metrics

# This is a breaking change introduced by the PR
assert not "indicators" in evaluator.state.metrics

print(evaluator.state.metrics)
> {'a': 12, 'b': 23}
```

The questions we would like to address in this issue:
- Should we fix the breaking change by readding the dict into `evaluator.state.metrics` such that we'll have:
```
print(evaluator.state.metrics)
> {'a': 12, 'b': 23, 'indicators': {'a': 12, 'b': 23}}
```
 
- `name` parameter (e.g. "indicators") is never used. Should we append it to the flatten names (e.g. `indicators/a`, `indicators/b`) or we can accept that it is never used ?


Thanks @lidq92 for reporting this.

## Environment

 - PyTorch Version (e.g., 1.4): 1.6.0
 - Ignite Version (e.g., 0.3.0): 0.4.2
 - OS (e.g., Linux): 
 - How you installed Ignite (`conda`, `pip`, source):
 - Python version:
 - Any other relevant information:


cc @sdesrozis @liebkne 


Discussion:
I really appreciate this idea to have flat values and dict in metrics data 
```
print(evaluator.state.metrics)
> {'a': 12, 'b': 23, 'indicators': {'a': 12, 'b': 23}}
```

It does fix the use of `name` parameter too. 

A point is that doubles stored values but atm these values in dict are numbers. 

Resolving patch:
diff --git a/ignite/metrics/metric.py b/ignite/metrics/metric.py
--- a/ignite/metrics/metric.py
+++ b/ignite/metrics/metric.py
@@ -300,11 +300,18 @@ def completed(self, engine: Engine, name: str) -> None:
 
         Args:
             engine (Engine): the engine to which the metric must be attached
+            name (str): the name of the metric used as key in dict `engine.state.metrics`
         """
         result = self.compute()
         if isinstance(result, Mapping):
+            if name in result.keys():
+                raise ValueError(
+                    "Argument name '{}' is conflicting with mapping keys: {}".format(name, list(result.keys()))
+                )
+
             for key, value in result.items():
                 engine.state.metrics[key] = value
+            engine.state.metrics[name] = result
         else:
             if isinstance(result, torch.Tensor) and len(result.size()) == 0:
                 result = result.item()


Test patch:
diff --git a/tests/ignite/metrics/test_metric.py b/tests/ignite/metrics/test_metric.py
--- a/tests/ignite/metrics/test_metric.py
+++ b/tests/ignite/metrics/test_metric.py
@@ -672,7 +672,10 @@ def update(self, output):
     engine = MagicMock(state=State(metrics={}))
     metrics = {"foo": 1, "bar": torch.tensor(2.0), "baz": {"qux": "quux"}}
     m.compute = MagicMock(return_value=metrics)
+    with pytest.raises(ValueError, match=r"Argument name 'foo' is conflicting with mapping keys"):
+        m.completed(engine, "foo")
     m.completed(engine, "metric")
+    metrics["metric"] = metrics
     assert engine.state.metrics == metrics
 
     # other