Repository: pytorch/ignite

Issue:
Improve MetricLambda implementation
User can get the following output in the `state.metrics`:
```
{'accuracy': 0.37127896829244644,
 'error': 0.6287210317075536,
 'error[0]': -0.6287210317075536,
 'error[0][0]': 0.37127896829244644,
```
when launch the below code:
```python
accuracy_metric = Accuracy()
error_metric = (accuracy_metric - 1.0) * (-1.0)

metrics = {
    "accuracy": accuracy_metric,
    "error": error_metric,
}

validator = create_supervised_evaluator(model, metrics=metrics)
validator.run(val_loader, max_epochs=1)
print(validator.state.metrics)
```

This is due to 

https://github.com/pytorch/ignite/blob/d9820451da779e0d0c393804db381e5483240b1c/ignite/metrics/metrics_lambda.py#L50-L54

and 

https://github.com/pytorch/ignite/blob/d9820451da779e0d0c393804db381e5483240b1c/ignite/metrics/metric.py#L68

IMO, user is not interested of this internal info used to compute `error_metric`. We can add some special characters to ignore the result of compute when insert to `engine.state.metrics[name]`.

cc @zasdfgbnm 


Discussion:

Resolving patch:
diff --git a/ignite/metrics/metrics_lambda.py b/ignite/metrics/metrics_lambda.py
--- a/ignite/metrics/metrics_lambda.py
+++ b/ignite/metrics/metrics_lambda.py
@@ -1,4 +1,5 @@
 from ignite.metrics.metric import Metric
+from ignite.engine import Events
 
 
 class MetricsLambda(Metric):
@@ -12,20 +13,25 @@ class MetricsLambda(Metric):
     resetted. When attach, all its dependencies would be automatically
     attached.
 
-    Arguments:
+    Args:
         f (callable): the function that defines the computation
         args (sequence): Sequence of other metrics or something
             else that will be fed to ``f`` as arguments.
 
-    Examples:
-        >>> precision = Precision(average=False)
-        >>> recall = Recall(average=False)
-        >>> def Fbeta(r, p, beta):
-        >>>     return torch.mean((1 + beta ** 2) * p * r / (beta ** 2 * p + r)).item()
-        >>> F1 = MetricsLambda(Fbeta, recall, precision, 1)
-        >>> F2 = MetricsLambda(Fbeta, recall, precision, 2)
-        >>> F3 = MetricsLambda(Fbeta, recall, precision, 3)
-        >>> F4 = MetricsLambda(Fbeta, recall, precision, 4)
+    Example:
+
+    .. code-block:: python
+
+        precision = Precision(average=False)
+        recall = Recall(average=False)
+
+        def Fbeta(r, p, beta):
+            return torch.mean((1 + beta ** 2) * p * r / (beta ** 2 * p + r + 1e-20)).item()
+
+        F1 = MetricsLambda(Fbeta, recall, precision, 1)
+        F2 = MetricsLambda(Fbeta, recall, precision, 2)
+        F3 = MetricsLambda(Fbeta, recall, precision, 3)
+        F4 = MetricsLambda(Fbeta, recall, precision, 4)
     """
     def __init__(self, f, *args):
         self.function = f
@@ -51,5 +57,8 @@ def attach(self, engine, name):
         # recursively attach all its dependencies
         for index, metric in enumerate(self.args):
             if isinstance(metric, Metric):
-                metric.attach(engine, name + '[{}]'.format(index))
+                if not engine.has_event_handler(metric.started, Events.EPOCH_STARTED):
+                    engine.add_event_handler(Events.EPOCH_STARTED, metric.started)
+                if not engine.has_event_handler(metric.iteration_completed, Events.ITERATION_COMPLETED):
+                    engine.add_event_handler(Events.ITERATION_COMPLETED, metric.iteration_completed)
         super(MetricsLambda, self).attach(engine, name)


Test patch:
diff --git a/tests/ignite/metrics/test_metrics_lambda.py b/tests/ignite/metrics/test_metrics_lambda.py
--- a/tests/ignite/metrics/test_metrics_lambda.py
+++ b/tests/ignite/metrics/test_metrics_lambda.py
@@ -121,3 +121,33 @@ def Fbeta(r, p, beta):
     assert precision_true == approx(precision), "{} vs {}".format(precision_true, precision)
     assert recall_true == approx(recall), "{} vs {}".format(recall_true, recall)
     assert f1_true == approx(state.metrics['f1']), "{} vs {}".format(f1_true, state.metrics['f1'])
+
+
+def test_state_metrics():
+
+    y_pred = torch.randint(0, 2, size=(15, 10, 4)).float()
+    y = torch.randint(0, 2, size=(15, 10, 4)).long()
+
+    def update_fn(engine, batch):
+        y_pred, y = batch
+        return y_pred, y
+
+    evaluator = Engine(update_fn)
+
+    precision = Precision(average=False)
+    recall = Recall(average=False)
+    F1 = precision * recall * 2 / (precision + recall + 1e-20)
+    F1 = MetricsLambda(lambda t: torch.mean(t).item(), F1)
+
+    precision.attach(evaluator, "precision")
+    recall.attach(evaluator, "recall")
+    F1.attach(evaluator, "f1")
+
+    def data(y_pred, y):
+        for i in range(y_pred.shape[0]):
+            yield (y_pred[i], y[i])
+
+    d = data(y_pred, y)
+    state = evaluator.run(d, max_epochs=1)
+
+    assert set(state.metrics.keys()) == set(["precision", "recall", "f1"])