Source code for neural_lam.metrics

"""Evaluation metrics shared across training and validation routines."""

# Standard library
from collections.abc import Callable

# Third-party
import torch


[docs] def get_metric(metric_name: str) -> Callable[..., torch.Tensor]: """ Get a metric function by name. Parameters ---------- metric_name : str Name of the metric. Must be a key in ``DEFINED_METRICS``. Returns ------- callable Function implementing the requested metric. Raises ------ AssertionError If ``metric_name`` (case-insensitive) is not a key in ``DEFINED_METRICS``. """ metric_name_lower = metric_name.lower() assert metric_name_lower in DEFINED_METRICS, ( f"Unknown metric: {metric_name}" ) return DEFINED_METRICS[metric_name_lower]
[docs] def mask_and_reduce_metric( metric_entry_vals: torch.Tensor, mask: torch.Tensor | None, average_grid: bool, sum_vars: bool, ) -> torch.Tensor: """ Apply a boolean mask and optionally reduce a per-entry metric tensor. Parameters ---------- metric_entry_vals : torch.Tensor Shape ``(..., N, num_variables)``. Per-entry metric values. ``(...)`` denotes any number of broadcastable batch dimensions, ``N`` is the number of grid nodes, and ``num_variables`` is the number of variables in the gridded representation (e.g. state features). mask : torch.Tensor or None Shape ``(N,)``. Boolean mask selecting which grid nodes to include. ``None`` means all nodes are used. average_grid : bool If True, average over the grid dimension ``N``. sum_vars : bool If True, sum over the variable dimension ``num_variables``. Returns ------- torch.Tensor Reduced metric values. Shape is one of ``(...,)``, ``(..., num_variables)``, ``(..., N)``, or ``(..., N, num_variables)`` depending on ``average_grid`` and ``sum_vars``. """ # Only keep grid nodes in mask if mask is not None: metric_entry_vals = metric_entry_vals[ ..., mask, : ] # (..., num_selected_nodes, num_variables) # Optionally reduce last two dimensions if average_grid: # Reduce grid first metric_entry_vals = torch.mean( metric_entry_vals, dim=-2 ) # (..., num_variables) if sum_vars: # Reduce vars second metric_entry_vals = torch.sum( metric_entry_vals, dim=-1 ) # (..., num_grid_nodes) or (...,) return metric_entry_vals
[docs] def wmse( pred: torch.Tensor, target: torch.Tensor, pred_std: torch.Tensor, mask: torch.Tensor | None = None, average_grid: bool = True, sum_vars: bool = True, ) -> torch.Tensor: """ Weighted Mean Squared Error. Parameters ---------- pred : torch.Tensor Shape ``(..., N, num_variables)``. Model prediction. ``(...)`` denotes any number of broadcastable batch dimensions, ``N`` is the number of grid nodes, and ``num_variables`` is the number of state variables. target : torch.Tensor Shape ``(..., N, num_variables)``. Ground-truth target. Dims: same as ``pred``. pred_std : torch.Tensor Shape ``(..., N, num_variables)`` or ``(num_variables,)``. Predicted standard deviation used as per-entry weight. mask : torch.Tensor or None, optional Shape ``(N,)``. Boolean mask over grid nodes. ``None`` uses all nodes. average_grid : bool, optional If True, average over the grid dimension (default True). sum_vars : bool, optional If True, sum over the variable dimension (default True). Returns ------- torch.Tensor Reduced metric values. Shape is one of ``(...,)``, ``(..., num_variables)``, ``(..., N)``, or ``(..., N, num_variables)`` depending on ``average_grid`` and ``sum_vars``. """ entry_mse = torch.nn.functional.mse_loss( pred, target, reduction="none" ) # (..., num_grid_nodes, num_variables) entry_mse_weighted = entry_mse / ( pred_std**2 ) # (..., num_grid_nodes, num_variables) return mask_and_reduce_metric( entry_mse_weighted, mask=mask, average_grid=average_grid, sum_vars=sum_vars, )
[docs] def mse( pred: torch.Tensor, target: torch.Tensor, pred_std: torch.Tensor, mask: torch.Tensor | None = None, average_grid: bool = True, sum_vars: bool = True, ) -> torch.Tensor: """ (Unweighted) Mean Squared Error. Parameters ---------- pred : torch.Tensor Shape ``(..., N, num_variables)``. Model prediction. ``(...)`` denotes any number of broadcastable batch dimensions, ``N`` is the number of grid nodes, and ``num_variables`` is the number of state variables. target : torch.Tensor Shape ``(..., N, num_variables)``. Ground-truth target. Dims: same as ``pred``. pred_std : torch.Tensor Shape ``(..., N, num_variables)`` or ``(num_variables,)``. Predicted standard deviation (unused; ``pred_std`` is replaced by ones internally). mask : torch.Tensor or None, optional Shape ``(N,)``. Boolean mask over grid nodes. ``None`` uses all nodes. average_grid : bool, optional If True, average over the grid dimension (default True). sum_vars : bool, optional If True, sum over the variable dimension (default True). Returns ------- torch.Tensor Reduced metric values. Shape is one of ``(...,)``, ``(..., num_variables)``, ``(..., N)``, or ``(..., N, num_variables)`` depending on ``average_grid`` and ``sum_vars``. """ # Replace pred_std with constant ones return wmse( pred, target, torch.ones_like(pred_std), mask, average_grid, sum_vars )
[docs] def wmae( pred: torch.Tensor, target: torch.Tensor, pred_std: torch.Tensor, mask: torch.Tensor | None = None, average_grid: bool = True, sum_vars: bool = True, ) -> torch.Tensor: """ Weighted Mean Absolute Error. Parameters ---------- pred : torch.Tensor Shape ``(..., N, num_variables)``. Model prediction. ``(...)`` denotes any number of broadcastable batch dimensions, ``N`` is the number of grid nodes, and ``num_variables`` is the number of state variables. target : torch.Tensor Shape ``(..., N, num_variables)``. Ground-truth target. Dims: same as ``pred``. pred_std : torch.Tensor Shape ``(..., N, num_variables)`` or ``(num_variables,)``. Predicted standard deviation used as per-entry weight. mask : torch.Tensor or None, optional Shape ``(N,)``. Boolean mask over grid nodes. ``None`` uses all nodes. average_grid : bool, optional If True, average over the grid dimension (default True). sum_vars : bool, optional If True, sum over the variable dimension (default True). Returns ------- torch.Tensor Reduced metric values. Shape is one of ``(...,)``, ``(..., num_variables)``, ``(..., N)``, or ``(..., N, num_variables)`` depending on ``average_grid`` and ``sum_vars``. """ entry_mae = torch.nn.functional.l1_loss( pred, target, reduction="none" ) # (..., num_grid_nodes, num_variables) entry_mae_weighted = ( entry_mae / pred_std ) # (..., num_grid_nodes, num_variables) return mask_and_reduce_metric( entry_mae_weighted, mask=mask, average_grid=average_grid, sum_vars=sum_vars, )
[docs] def mae( pred: torch.Tensor, target: torch.Tensor, pred_std: torch.Tensor, mask: torch.Tensor | None = None, average_grid: bool = True, sum_vars: bool = True, ) -> torch.Tensor: """ (Unweighted) Mean Absolute Error. Parameters ---------- pred : torch.Tensor Shape ``(..., N, num_variables)``. Model prediction. ``(...)`` denotes any number of broadcastable batch dimensions, ``N`` is the number of grid nodes, and ``num_variables`` is the number of state variables. target : torch.Tensor Shape ``(..., N, num_variables)``. Ground-truth target. Dims: same as ``pred``. pred_std : torch.Tensor Shape ``(..., N, num_variables)`` or ``(num_variables,)``. Predicted standard deviation (unused; ``pred_std`` is replaced by ones internally). mask : torch.Tensor or None, optional Shape ``(N,)``. Boolean mask over grid nodes. ``None`` uses all nodes. average_grid : bool, optional If True, average over the grid dimension (default True). sum_vars : bool, optional If True, sum over the variable dimension (default True). Returns ------- torch.Tensor Reduced metric values. Shape is one of ``(...,)``, ``(..., num_variables)``, ``(..., N)``, or ``(..., N, num_variables)`` depending on ``average_grid`` and ``sum_vars``. """ # Replace pred_std with constant ones return wmae( pred, target, torch.ones_like(pred_std), mask, average_grid, sum_vars )
[docs] def nll( pred: torch.Tensor, target: torch.Tensor, pred_std: torch.Tensor, mask: torch.Tensor | None = None, average_grid: bool = True, sum_vars: bool = True, ) -> torch.Tensor: """ Negative Log Likelihood loss for an isotropic Gaussian likelihood. Parameters ---------- pred : torch.Tensor Shape ``(..., N, num_variables)``. Predicted mean. ``(...)`` denotes any number of broadcastable batch dimensions, ``N`` is the number of grid nodes, and ``num_variables`` is the number of state variables. target : torch.Tensor Shape ``(..., N, num_variables)``. Ground-truth target. Dims: same as ``pred``. pred_std : torch.Tensor Shape ``(..., N, num_variables)`` or ``(num_variables,)``. Predicted standard deviation of the Gaussian. mask : torch.Tensor or None, optional Shape ``(N,)``. Boolean mask over grid nodes. ``None`` uses all nodes. average_grid : bool, optional If True, average over the grid dimension (default True). sum_vars : bool, optional If True, sum over the variable dimension (default True). Returns ------- torch.Tensor Reduced metric values. Shape is one of ``(...,)``, ``(..., num_variables)``, ``(..., N)``, or ``(..., N, num_variables)`` depending on ``average_grid`` and ``sum_vars``. """ # Broadcast pred_std if shaped (num_variables,) via distribution internals dist = torch.distributions.Normal( pred, pred_std ) # (..., num_grid_nodes, num_variables) entry_nll = -dist.log_prob(target) # (..., num_grid_nodes, num_variables) return mask_and_reduce_metric( entry_nll, mask=mask, average_grid=average_grid, sum_vars=sum_vars )
[docs] def crps_gauss( pred: torch.Tensor, target: torch.Tensor, pred_std: torch.Tensor, mask: torch.Tensor | None = None, average_grid: bool = True, sum_vars: bool = True, ) -> torch.Tensor: """ Continuous Ranked Probability Score (CRPS) for a Gaussian predictive distribution (closed-form expression, negated for minimisation). Parameters ---------- pred : torch.Tensor Shape ``(..., N, num_variables)``. Predicted mean. ``(...)`` denotes any number of broadcastable batch dimensions, ``N`` is the number of grid nodes, and ``num_variables`` is the number of state variables. target : torch.Tensor Shape ``(..., N, num_variables)``. Ground-truth target. Dims: same as ``pred``. pred_std : torch.Tensor Shape ``(..., N, num_variables)`` or ``(num_variables,)``. Predicted standard deviation of the Gaussian. mask : torch.Tensor or None, optional Shape ``(N,)``. Boolean mask over grid nodes. ``None`` uses all nodes. average_grid : bool, optional If True, average over the grid dimension (default True). sum_vars : bool, optional If True, sum over the variable dimension (default True). Returns ------- torch.Tensor Reduced metric values. Shape is one of ``(...,)``, ``(..., num_variables)``, ``(..., N)``, or ``(..., N, num_variables)`` depending on ``average_grid`` and ``sum_vars``. """ std_normal = torch.distributions.Normal( torch.zeros((), device=pred.device), torch.ones((), device=pred.device) ) target_standard = ( target - pred ) / pred_std # (..., num_grid_nodes, num_variables) entry_crps = -pred_std * ( torch.pi ** (-0.5) - 2 * torch.exp(std_normal.log_prob(target_standard)) - target_standard * (2 * std_normal.cdf(target_standard) - 1) ) # (..., num_grid_nodes, num_variables) return mask_and_reduce_metric( entry_crps, mask=mask, average_grid=average_grid, sum_vars=sum_vars )
DEFINED_METRICS = { "mse": mse, "mae": mae, "wmse": wmse, "wmae": wmae, "nll": nll, "crps_gauss": crps_gauss, }