bikes.core.metrics

Evaluate model performances with metrics.

  1"""Evaluate model performances with metrics."""
  2
  3# %% IMPORTS
  4
  5from __future__ import annotations
  6
  7import abc
  8import typing as T
  9
 10import mlflow
 11import pandas as pd
 12import pydantic as pdt
 13from mlflow.metrics import MetricValue
 14from sklearn import metrics as sklearn_metrics
 15
 16from bikes.core import models, schemas
 17
 18# %% TYPINGS
 19
 20MlflowMetric: T.TypeAlias = MetricValue
 21MlflowThreshold: T.TypeAlias = mlflow.models.MetricThreshold
 22MlflowModelValidationFailedException: T.TypeAlias = mlflow.models.evaluation.validation.ModelValidationFailedException
 23
 24# %% METRICS
 25
 26
 27class Metric(abc.ABC, pdt.BaseModel, strict=True, frozen=True, extra="forbid"):
 28    """Base class for a project metric.
 29
 30    Use metrics to evaluate model performance.
 31    e.g., accuracy, precision, recall, MAE, F1, ...
 32
 33    Parameters:
 34        name (str): name of the metric for the reporting.
 35        greater_is_better (bool): maximize or minimize result.
 36    """
 37
 38    KIND: str
 39
 40    name: str
 41    greater_is_better: bool
 42
 43    @abc.abstractmethod
 44    def score(self, targets: schemas.Targets, outputs: schemas.Outputs) -> float:
 45        """Score the outputs against the targets.
 46
 47        Args:
 48            targets (schemas.Targets): expected values.
 49            outputs (schemas.Outputs): predicted values.
 50
 51        Returns:
 52            float: single result from the metric computation.
 53        """
 54
 55    def scorer(self, model: models.Model, inputs: schemas.Inputs, targets: schemas.Targets) -> float:
 56        """Score model outputs against targets.
 57
 58        Args:
 59            model (models.Model): model to evaluate.
 60            inputs (schemas.Inputs): model inputs values.
 61            targets (schemas.Targets): model expected values.
 62
 63        Returns:
 64            float: single result from the metric computation.
 65        """
 66        outputs = model.predict(inputs=inputs)
 67        return self.score(targets=targets, outputs=outputs)
 68
 69    def to_mlflow(self) -> MlflowMetric:
 70        """Convert the metric to an Mlflow metric.
 71
 72        Returns:
 73            MlflowMetric: the Mlflow metric.
 74        """
 75
 76        def eval_fn(predictions: pd.Series[int], targets: pd.Series[int]) -> MlflowMetric:
 77            """Evaluation function associated with the mlflow metric.
 78
 79            Args:
 80                predictions (pd.Series): model predictions.
 81                targets (pd.Series | None): model targets.
 82
 83            Returns:
 84                MlflowMetric: the mlflow metric.
 85            """
 86            score_targets = schemas.Targets({schemas.TargetsSchema.cnt: targets}, index=targets.index)
 87            score_outputs = schemas.Outputs({schemas.OutputsSchema.prediction: predictions}, index=predictions.index)
 88            sign = 1 if self.greater_is_better else -1  # reverse the effect
 89            score = self.score(targets=score_targets, outputs=score_outputs)
 90            return MlflowMetric(aggregate_results={self.name: score * sign})
 91
 92        return mlflow.metrics.make_metric(eval_fn=eval_fn, name=self.name, greater_is_better=self.greater_is_better)
 93
 94
 95class SklearnMetric(Metric):
 96    """Compute metrics with sklearn.
 97
 98    Parameters:
 99        name (str): name of the sklearn metric.
100        greater_is_better (bool): maximize or minimize.
101    """
102
103    KIND: T.Literal["SklearnMetric"] = "SklearnMetric"
104
105    name: str = "mean_squared_error"
106    greater_is_better: bool = False
107
108    @T.override
109    def score(self, targets: schemas.Targets, outputs: schemas.Outputs) -> float:
110        metric = getattr(sklearn_metrics, self.name)
111        sign = 1 if self.greater_is_better else -1
112        y_true = targets[schemas.TargetsSchema.cnt]
113        y_pred = outputs[schemas.OutputsSchema.prediction]
114        score = metric(y_pred=y_pred, y_true=y_true) * sign
115        return float(score)
116
117
118MetricKind = SklearnMetric
119MetricsKind: T.TypeAlias = list[T.Annotated[MetricKind, pdt.Field(discriminator="KIND")]]
120
121# %% THRESHOLDS
122
123
124class Threshold(abc.ABC, pdt.BaseModel, strict=True, frozen=True, extra="forbid"):
125    """A project threshold for a metric.
126
127    Use thresholds to monitor model performances.
128    e.g., to trigger an alert when a threshold is met.
129
130    Parameters:
131        threshold (int | float): absolute threshold value.
132        greater_is_better (bool): maximize or minimize result.
133    """
134
135    threshold: int | float
136    greater_is_better: bool
137
138    def to_mlflow(self) -> MlflowThreshold:
139        """Convert the threshold to an mlflow threshold.
140
141        Returns:
142            MlflowThreshold: the mlflow threshold.
143        """
144        return MlflowThreshold(threshold=self.threshold, greater_is_better=self.greater_is_better)
MlflowMetric: TypeAlias = mlflow.metrics.base.MetricValue
MlflowThreshold: TypeAlias = mlflow.models.evaluation.validation.MetricThreshold
MlflowModelValidationFailedException: TypeAlias = mlflow.models.evaluation.validation.ModelValidationFailedException
class Metric(abc.ABC, pydantic.main.BaseModel):
28class Metric(abc.ABC, pdt.BaseModel, strict=True, frozen=True, extra="forbid"):
29    """Base class for a project metric.
30
31    Use metrics to evaluate model performance.
32    e.g., accuracy, precision, recall, MAE, F1, ...
33
34    Parameters:
35        name (str): name of the metric for the reporting.
36        greater_is_better (bool): maximize or minimize result.
37    """
38
39    KIND: str
40
41    name: str
42    greater_is_better: bool
43
44    @abc.abstractmethod
45    def score(self, targets: schemas.Targets, outputs: schemas.Outputs) -> float:
46        """Score the outputs against the targets.
47
48        Args:
49            targets (schemas.Targets): expected values.
50            outputs (schemas.Outputs): predicted values.
51
52        Returns:
53            float: single result from the metric computation.
54        """
55
56    def scorer(self, model: models.Model, inputs: schemas.Inputs, targets: schemas.Targets) -> float:
57        """Score model outputs against targets.
58
59        Args:
60            model (models.Model): model to evaluate.
61            inputs (schemas.Inputs): model inputs values.
62            targets (schemas.Targets): model expected values.
63
64        Returns:
65            float: single result from the metric computation.
66        """
67        outputs = model.predict(inputs=inputs)
68        return self.score(targets=targets, outputs=outputs)
69
70    def to_mlflow(self) -> MlflowMetric:
71        """Convert the metric to an Mlflow metric.
72
73        Returns:
74            MlflowMetric: the Mlflow metric.
75        """
76
77        def eval_fn(predictions: pd.Series[int], targets: pd.Series[int]) -> MlflowMetric:
78            """Evaluation function associated with the mlflow metric.
79
80            Args:
81                predictions (pd.Series): model predictions.
82                targets (pd.Series | None): model targets.
83
84            Returns:
85                MlflowMetric: the mlflow metric.
86            """
87            score_targets = schemas.Targets({schemas.TargetsSchema.cnt: targets}, index=targets.index)
88            score_outputs = schemas.Outputs({schemas.OutputsSchema.prediction: predictions}, index=predictions.index)
89            sign = 1 if self.greater_is_better else -1  # reverse the effect
90            score = self.score(targets=score_targets, outputs=score_outputs)
91            return MlflowMetric(aggregate_results={self.name: score * sign})
92
93        return mlflow.metrics.make_metric(eval_fn=eval_fn, name=self.name, greater_is_better=self.greater_is_better)

Base class for a project metric.

Use metrics to evaluate model performance. e.g., accuracy, precision, recall, MAE, F1, ...

Arguments:
  • name (str): name of the metric for the reporting.
  • greater_is_better (bool): maximize or minimize result.
KIND: str = PydanticUndefined
name: str = PydanticUndefined
greater_is_better: bool = PydanticUndefined
@abc.abstractmethod
def score( self, targets: pandera.typing.pandas.DataFrame[bikes.core.schemas.TargetsSchema], outputs: pandera.typing.pandas.DataFrame[bikes.core.schemas.OutputsSchema]) -> float:
44    @abc.abstractmethod
45    def score(self, targets: schemas.Targets, outputs: schemas.Outputs) -> float:
46        """Score the outputs against the targets.
47
48        Args:
49            targets (schemas.Targets): expected values.
50            outputs (schemas.Outputs): predicted values.
51
52        Returns:
53            float: single result from the metric computation.
54        """

Score the outputs against the targets.

Arguments:
  • targets (schemas.Targets): expected values.
  • outputs (schemas.Outputs): predicted values.
Returns:

float: single result from the metric computation.

def scorer( self, model: bikes.core.models.Model, inputs: pandera.typing.pandas.DataFrame[bikes.core.schemas.InputsSchema], targets: pandera.typing.pandas.DataFrame[bikes.core.schemas.TargetsSchema]) -> float:
56    def scorer(self, model: models.Model, inputs: schemas.Inputs, targets: schemas.Targets) -> float:
57        """Score model outputs against targets.
58
59        Args:
60            model (models.Model): model to evaluate.
61            inputs (schemas.Inputs): model inputs values.
62            targets (schemas.Targets): model expected values.
63
64        Returns:
65            float: single result from the metric computation.
66        """
67        outputs = model.predict(inputs=inputs)
68        return self.score(targets=targets, outputs=outputs)

Score model outputs against targets.

Arguments:
  • model (models.Model): model to evaluate.
  • inputs (schemas.Inputs): model inputs values.
  • targets (schemas.Targets): model expected values.
Returns:

float: single result from the metric computation.

def to_mlflow(self) -> mlflow.metrics.base.MetricValue:
70    def to_mlflow(self) -> MlflowMetric:
71        """Convert the metric to an Mlflow metric.
72
73        Returns:
74            MlflowMetric: the Mlflow metric.
75        """
76
77        def eval_fn(predictions: pd.Series[int], targets: pd.Series[int]) -> MlflowMetric:
78            """Evaluation function associated with the mlflow metric.
79
80            Args:
81                predictions (pd.Series): model predictions.
82                targets (pd.Series | None): model targets.
83
84            Returns:
85                MlflowMetric: the mlflow metric.
86            """
87            score_targets = schemas.Targets({schemas.TargetsSchema.cnt: targets}, index=targets.index)
88            score_outputs = schemas.Outputs({schemas.OutputsSchema.prediction: predictions}, index=predictions.index)
89            sign = 1 if self.greater_is_better else -1  # reverse the effect
90            score = self.score(targets=score_targets, outputs=score_outputs)
91            return MlflowMetric(aggregate_results={self.name: score * sign})
92
93        return mlflow.metrics.make_metric(eval_fn=eval_fn, name=self.name, greater_is_better=self.greater_is_better)

Convert the metric to an Mlflow metric.

Returns:

MlflowMetric: the Mlflow metric.

class SklearnMetric(Metric):
 96class SklearnMetric(Metric):
 97    """Compute metrics with sklearn.
 98
 99    Parameters:
100        name (str): name of the sklearn metric.
101        greater_is_better (bool): maximize or minimize.
102    """
103
104    KIND: T.Literal["SklearnMetric"] = "SklearnMetric"
105
106    name: str = "mean_squared_error"
107    greater_is_better: bool = False
108
109    @T.override
110    def score(self, targets: schemas.Targets, outputs: schemas.Outputs) -> float:
111        metric = getattr(sklearn_metrics, self.name)
112        sign = 1 if self.greater_is_better else -1
113        y_true = targets[schemas.TargetsSchema.cnt]
114        y_pred = outputs[schemas.OutputsSchema.prediction]
115        score = metric(y_pred=y_pred, y_true=y_true) * sign
116        return float(score)

Compute metrics with sklearn.

Arguments:
  • name (str): name of the sklearn metric.
  • greater_is_better (bool): maximize or minimize.
KIND: Literal['SklearnMetric'] = 'SklearnMetric'
name: str = 'mean_squared_error'
greater_is_better: bool = False
@T.override
def score( self, targets: pandera.typing.pandas.DataFrame[bikes.core.schemas.TargetsSchema], outputs: pandera.typing.pandas.DataFrame[bikes.core.schemas.OutputsSchema]) -> float:
109    @T.override
110    def score(self, targets: schemas.Targets, outputs: schemas.Outputs) -> float:
111        metric = getattr(sklearn_metrics, self.name)
112        sign = 1 if self.greater_is_better else -1
113        y_true = targets[schemas.TargetsSchema.cnt]
114        y_pred = outputs[schemas.OutputsSchema.prediction]
115        score = metric(y_pred=y_pred, y_true=y_true) * sign
116        return float(score)

Score the outputs against the targets.

Arguments:
  • targets (schemas.Targets): expected values.
  • outputs (schemas.Outputs): predicted values.
Returns:

float: single result from the metric computation.

Inherited Members
Metric
scorer
to_mlflow
MetricKind = <class 'SklearnMetric'>
MetricsKind: TypeAlias = list[typing.Annotated[SklearnMetric, FieldInfo(annotation=NoneType, required=True, discriminator='KIND')]]
class Threshold(abc.ABC, pydantic.main.BaseModel):
125class Threshold(abc.ABC, pdt.BaseModel, strict=True, frozen=True, extra="forbid"):
126    """A project threshold for a metric.
127
128    Use thresholds to monitor model performances.
129    e.g., to trigger an alert when a threshold is met.
130
131    Parameters:
132        threshold (int | float): absolute threshold value.
133        greater_is_better (bool): maximize or minimize result.
134    """
135
136    threshold: int | float
137    greater_is_better: bool
138
139    def to_mlflow(self) -> MlflowThreshold:
140        """Convert the threshold to an mlflow threshold.
141
142        Returns:
143            MlflowThreshold: the mlflow threshold.
144        """
145        return MlflowThreshold(threshold=self.threshold, greater_is_better=self.greater_is_better)

A project threshold for a metric.

Use thresholds to monitor model performances. e.g., to trigger an alert when a threshold is met.

Arguments:
  • threshold (int | float): absolute threshold value.
  • greater_is_better (bool): maximize or minimize result.
threshold: int | float = PydanticUndefined
greater_is_better: bool = PydanticUndefined
def to_mlflow(self) -> mlflow.models.evaluation.validation.MetricThreshold:
139    def to_mlflow(self) -> MlflowThreshold:
140        """Convert the threshold to an mlflow threshold.
141
142        Returns:
143            MlflowThreshold: the mlflow threshold.
144        """
145        return MlflowThreshold(threshold=self.threshold, greater_is_better=self.greater_is_better)

Convert the threshold to an mlflow threshold.

Returns:

MlflowThreshold: the mlflow threshold.