-
-
Notifications
You must be signed in to change notification settings - Fork 692
Nested metrics are being duplicated when logging. #3513
Description
🚀 Feature
When we use nested metrics (i.e. ClassificationReport) we have a structure like this:
{
'macro_avg': {
'precision': ...,
'f1-score': ...,
'recall': ...,
}
}ignite/ignite/metrics/classification_report.py
Lines 120 to 141 in 7bb6cd7
| def _wrapper( | |
| re: torch.Tensor, pr: torch.Tensor, f: torch.Tensor, a_re: torch.Tensor, a_pr: torch.Tensor, a_f: torch.Tensor | |
| ) -> Union[Collection[str], Dict]: | |
| if pr.shape != re.shape: | |
| raise ValueError( | |
| "Internal error: Precision and Recall have mismatched shapes: " | |
| f"{pr.shape} vs {re.shape}. Please, open an issue " | |
| "with a reference on this error. Thank you!" | |
| ) | |
| dict_obj = {} | |
| for idx, p_label in enumerate(pr): | |
| dict_obj[_get_label_for_class(idx)] = { | |
| "precision": p_label.item(), | |
| "recall": re[idx].item(), | |
| f"f{beta}-score": f[idx].item(), | |
| } | |
| dict_obj["macro avg"] = { | |
| "precision": a_pr.item(), | |
| "recall": a_re.item(), | |
| f"f{beta}-score": a_f.item(), | |
| } | |
| return dict_obj if output_dict else json.dumps(dict_obj) |
Currently it goes through this code path when adding the metrics to the engine state metrics.
ignite/ignite/metrics/metric.py
Lines 497 to 503 in 7bb6cd7
| if isinstance(result, Mapping): | |
| if name in result.keys(): | |
| raise ValueError(f"Argument name '{name}' is conflicting with mapping keys: {list(result.keys())}") | |
| for key, value in result.items(): | |
| engine.state.metrics[key] = value | |
| engine.state.metrics[name] = result |
Which will fill the metrics dict in the next way:
{
'$ENGINE_METRIC_NAME': {
macro_avg': {
'precision': ...,
'f1-score': ...,
'recall': ...
},
},
macro_avg': {
'precision': ...,
'f1-score': ...,
'recall': ...
},
}$ENGINE_METRIC_NAME is the one defined here:
classification_report.attach(evaluator, "$ENGINE_METRIC_NAME")The main problem in that is for when we want to log metrics using TensorBoard logger for example, we need to either specify what metrics we want or all.
my code below
def setup_tblogger(
...
train_metric_names: list[str] | str = "all",
...
) -> TensorboardLogger:
logger.attach(
trainer,
log_handler=OutputHandler(
tag="train",
metric_names=train_metric_names, # list or all
global_step_transform=global_step_from_engine(trainer),
),
event_name=Events.EPOCH_STARTED,
)And in the case of classification report, we end with something not much desirable, that is repeated metrics in the TensorBoard logger, since we will flatten and get something like this:
$ENGINE_METRIC_NAME/macro_avg/precision
macro_avg/precision
We could potentially list only the metrics we want, or a blacklist of metrics that we don't want (not supported), but in any way, would be just a workaround of the real problem.