[docs]classMetricsLambda(Metric):""" Apply a function to other metrics to obtain a new metric. The result of the new metric is defined to be the result of applying the function to the result of argument metrics. When update, this metric recursively updates the metrics it depends on. When reset, all its dependency metrics would be resetted as well. When attach, all its dependency metrics would be attached automatically (but partially, e.g :meth:`~ignite.metrics.metric.Metric.is_attached()` will return False). Args: f: the function that defines the computation args: Sequence of other metrics or something else that will be fed to ``f`` as arguments. kwargs: Sequence of other metrics or something else that will be fed to ``f`` as keyword arguments. Examples: For more information on how metric works with :class:`~ignite.engine.engine.Engine`, visit :ref:`attach-engine`. .. include:: defaults.rst :start-after: :orphan: .. testcode:: 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) F1.attach(default_evaluator, "F1") F2.attach(default_evaluator, "F2") F3.attach(default_evaluator, "F3") F4.attach(default_evaluator, "F4") y_true = torch.tensor([1, 0, 1, 0, 0, 1]) y_pred = torch.tensor([1, 0, 1, 0, 1, 1]) state = default_evaluator.run([[y_pred, y_true]]) print(state.metrics["F1"]) print(state.metrics["F2"]) print(state.metrics["F3"]) print(state.metrics["F4"]) .. testoutput:: 0.8571... 0.9375... 0.9677... 0.9807... When check if the metric is attached, if one of its dependency metrics is detached, the metric is considered detached too. .. code-block:: python engine = ... precision = Precision(average=False) aP = precision.mean() aP.attach(engine, "aP") assert aP.is_attached(engine) # partially attached assert not precision.is_attached(engine) precision.detach(engine) assert not aP.is_attached(engine) # fully attached assert not precision.is_attached(engine) """_state_dict_all_req_keys=("_updated","args","kwargs")def__init__(self,f:Callable,*args:Any,**kwargs:Any)->None:self.function=fself.args=list(args)# we need args to be a list instead of a tuple for state_dict/load_state_dict featureself.kwargs=kwargsself.engine:Optional[Engine]=Noneself._updated=Falsesuper(MetricsLambda,self).__init__(device="cpu")
[docs]@reinit__is_reduceddefupdate(self,output:Any)->None:ifself.engine:raiseValueError("MetricsLambda is already attached to an engine, ""and MetricsLambda can't use update API while it's attached.")foriinitertools.chain(self.args,self.kwargs.values()):ifisinstance(i,Metric):i.update(output)self._updated=True
def_internal_attach(self,engine:Engine,usage:MetricUsage)->None:self.engine=engineforindex,metricinenumerate(itertools.chain(self.args,self.kwargs.values())):ifisinstance(metric,MetricsLambda):metric._internal_attach(engine,usage)elifisinstance(metric,Metric):# NB : metrics is attached partially# We must not use is_attached() but rather if these events existifnotengine.has_event_handler(metric.started,usage.STARTED):engine.add_event_handler(usage.STARTED,metric.started)ifnotengine.has_event_handler(metric.iteration_completed,usage.ITERATION_COMPLETED):engine.add_event_handler(usage.ITERATION_COMPLETED,metric.iteration_completed)
[docs]defattach(self,engine:Engine,name:str,usage:Union[str,MetricUsage]=EpochWise())->None:ifself._updated:raiseValueError("The underlying metrics are already updated, can't attach while using reset/update/compute API.")usage=self._check_usage(usage)# recursively attach all its dependencies (partially)self._internal_attach(engine,usage)# attach only handler on EPOCH_COMPLETEDengine.add_event_handler(usage.COMPLETED,self.completed,name)
[docs]defdetach(self,engine:Engine,usage:Union[str,MetricUsage]=EpochWise())->None:usage=self._check_usage(usage)# remove from enginesuper(MetricsLambda,self).detach(engine,usage)self.engine=None
[docs]defis_attached(self,engine:Engine,usage:Union[str,MetricUsage]=EpochWise())->bool:usage=self._check_usage(usage)# check recursively the dependenciesreturnsuper(MetricsLambda,self).is_attached(engine,usage)andself._internal_is_attached(engine,usage)
def_internal_is_attached(self,engine:Engine,usage:MetricUsage)->bool:# if no engine, metrics is not attachedifengineisNone:returnFalse# check recursively if metrics are attachedis_detached=Falseformetricinitertools.chain(self.args,self.kwargs.values()):ifisinstance(metric,MetricsLambda):ifnotmetric._internal_is_attached(engine,usage):is_detached=Trueelifisinstance(metric,Metric):ifnotengine.has_event_handler(metric.started,usage.STARTED):is_detached=Trueifnotengine.has_event_handler(metric.iteration_completed,usage.ITERATION_COMPLETED):is_detached=Truereturnnotis_detached