-
-
Notifications
You must be signed in to change notification settings - Fork 654
Add support for nested metric values. (#959) #968
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
ignite/metrics/metric.py
Outdated
return tensor.item() | ||
return tensor | ||
|
||
def ensure_metric_value(self, value: Any, raise_error=True) -> Union[numbers.Number, torch.Tensor]: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don’t know if an exception should be raised. Metrics are very generics, it can work with ˋAny`. Metrics associated to loggers should use restricted types.
@vfdev-5 ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree metrics are generics. Maybe remove type checking here and move to Logger
later when necessary?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, let’s try this! Just flat dict here (with compatible existing tests)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I just found one more issue, if Any
type is allowed, shall we deep flatten a dict in this case? Or shall me only care about dict
contains all number values?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IMO, we should not check the output type and raise an error and just keep previous behaviour.
Previously, we had a part of code that applied .item()
on torch scalars. Maybe just factorize this code can be enough.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree. At the moment, we should flat only the result of metrics. IMHO, what is embedded in dict is related to the metric itself.
We would notice in doc that generic result type ˋAny` is a generic purpose but interoperability with others handlers / features implies some restrictions : scalars (number, tensor) and dict of scalars.
What do you think ?
And thank you again for your help 🙏🏻
ignite/metrics/metric.py
Outdated
if torch.is_tensor(result) and len(result.shape) == 0: | ||
result = result.item() | ||
engine.state.metrics[name] = result | ||
if isinstance(result, dict): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if isinstance(result, dict): | |
if isinstance(result, Mapping): |
IMO, idea of the PR is to unnest one level of mappings (e.g. dict) as @Isolet's use-case. Thoughts ? |
My original thought is single enough just to flatten a level one dict of metrics. But as metrics is so generic, now I think maybe we should also not to make level one dict anything special? Before I opened the issue, I used the following snippet to get my goal. class MultipleMetricsOutputHandler(OutputHandler):
def _setup_output_metrics(self, engine):
metrics = {}
if self.metric_names is not None:
if (isinstance(self.metric_names, str)
and self.metric_names == 'all'):
metrics = flatten_dict(engine.state.metrics)
else:
for name in self.metric_names:
if name not in engine.state.metrics:
warnings.warn('Provided metric name \'{}\' is missing '
'in engine\'s state metrics: {}'.format(
name,
list(engine.state.metrics.keys())))
continue
metrics[name] = engine.state.metrics[name]
if self.output_transform is not None:
output_dict = self.output_transform(engine.state.output)
if not isinstance(output_dict, dict):
output_dict = {'output': output_dict}
metrics.update(
{name: value
for name, value in output_dict.items()})
return metrics How about keeping anything of But still, I found some functions like |
@Isolet I think one level flattening is a good level of simplifying things and keeping Metric rather generic. For other non-trivial cases, use should write its adapters. |
ignite/metrics/metric.py
Outdated
Returns: | ||
Any: the actual quantity of interest. | ||
Any: the actual quantity of interest. However, if a :class:`dict` is returned, it should contain | ||
:class:`numbers.Number` or :class:`torch.tensor` values and will flattened into |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's remove those type limitations:
Any: the actual quantity of interest. However, if a :class:`dict` is returned, it will flattened
into `engine.state.metrics` when :func:`~ignite.metrics.Metric.completed` is called.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done!
LGTM but CI quite unstable... |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM! Thank you very much !!
Thanks! |
Related to #959
Description:
This commit add support for nested metric values. If
Metric.compute
returns adict
, its values will be flattened intoengine.state.metrics
inMetric.completed
.Note that, in this case the value types of the returned
dict
is limited(i.e.Dict[Any, non number type]
is not allowed), while the original allowed type ofMetric.compute
isAny
. We can also allow other types ofdict
values, but this may be lead unclear behaviors to users when they try to return (different kinds of )dict
s.Check list: