Source code for bayesflow.scores.quantile_score

from typing import Sequence

import keras
from keras.saving import register_keras_serializable as serializable

from bayesflow.types import Shape, Tensor
from bayesflow.utils import logging
from bayesflow.links import OrderedQuantiles

from .scoring_rule import ScoringRule


[docs] @serializable(package="bayesflow.scores") class QuantileScore(ScoringRule): r""":math:`S(\hat \theta_i, \theta; \tau_i) = (\hat \theta_i - \theta)(\mathbf{1}_{\hat \theta - \theta > 0} - \tau_i)` Scores predicted quantiles :math:`\hat \theta_i` with the quantile score to match the quantile levels :math:`\hat \tau_i`. """ def __init__(self, q: Sequence[float] = None, links=None, **kwargs): super().__init__(links=links, **kwargs) if q is None: q = [0.1, 0.5, 0.9] logging.info(f"QuantileScore was not provided with argument `q`. Using the default quantile levels: {q}.") # force a conversion to list for proper serialization q = list(q) self.q = q self._q = keras.ops.convert_to_tensor(q, dtype="float32") self.links = links or {"value": OrderedQuantiles(q=q)} self.config = { "q": q, }
[docs] def get_config(self): base_config = super().get_config() return base_config | self.config
[docs] def get_head_shapes_from_target_shape(self, target_shape: Shape): # keras.saving.load_model sometimes passes target_shape as a list, so we force a conversion target_shape = tuple(target_shape) return dict(value=(len(self.q),) + target_shape[1:])
[docs] def score(self, estimates: dict[str, Tensor], targets: Tensor, weights: Tensor = None) -> Tensor: estimates = estimates["value"] pointwise_differance = estimates - targets[:, None, :] scores = pointwise_differance * (keras.ops.cast(pointwise_differance > 0, float) - self._q[None, :, None]) scores = keras.ops.mean(scores, axis=1) score = self.aggregate(scores, weights) return score