Source code for deeptab.distributions.tweedie

"""Tweedie distribution for zero-plus-positive compound targets (insurance, rainfall)."""

import numpy as np
import torch

from .base import BaseDistribution


[docs] class TweedieDistribution(BaseDistribution): """ Represents a Tweedie distribution for targets that are a mixture of zeros and positive continuous values — common in insurance claims, rainfall totals, and sales volumes. The Tweedie family unifies several distributions through a single *power* parameter ``p``: * ``p = 0`` — Normal * ``p = 1`` — Poisson (integer counts) * ``1 < p < 2`` — compound Poisson-Gamma (**this class targets this range**) * ``p = 2`` — Gamma The neural network predicts only the mean ``mu > 0``. The power ``p`` and dispersion ``phi`` are fixed hyperparameters set at construction time. The loss is the **Tweedie log-likelihood** (terms not depending on ``mu`` are dropped), which is equivalent to minimising the Tweedie deviance: .. math:: \\mathcal{L} = \\frac{\\mu^{2-p}}{2-p} - \\frac{y \\cdot \\mu^{1-p}}{1-p} Parameters ---------- name (str): Defaults to ``"Tweedie"``. p (float): Tweedie power parameter. Must satisfy ``1 < p < 2``. Defaults to ``1.5`` (midpoint of the compound Poisson-Gamma range). mu_transform (str or callable): Transform for the mean prediction to ensure ``mu > 0``. Defaults to ``"positive"`` (softplus). """ def __init__( self, name="Tweedie", p: float = 1.5, mu_transform="positive", ): if not (1.0 < p < 2.0): raise ValueError( f"Tweedie power p must be in the open interval (1, 2) for the compound Poisson-Gamma family, got p={p}." ) param_names = ["mu"] super().__init__(name, param_names) self.p = p self.mu_transform = self.get_transform(mu_transform)
[docs] def compute_loss(self, predictions, y_true): mu = self.mu_transform(predictions[:, self.param_names.index("mu")]) p = self.p # Tweedie log-likelihood (ignoring terms that do not depend on mu) # L = mu^(2-p)/(2-p) - y * mu^(1-p)/(1-p) term_a = torch.pow(mu, 2.0 - p) / (2.0 - p) term_b = y_true * torch.pow(mu, 1.0 - p) / (1.0 - p) loss = torch.mean(term_a - term_b) return loss
[docs] def evaluate_nll(self, y_true, y_pred): metrics = super().evaluate_nll(y_true, y_pred) y_true_tensor = torch.tensor(y_true, dtype=torch.float32) y_pred_tensor = torch.tensor(y_pred, dtype=torch.float32) mu = self.mu_transform(y_pred_tensor[:, self.param_names.index("mu")]) # Tweedie deviance: D(y, mu) = 2 * [y^(2-p)/((1-p)(2-p)) - y*mu^(1-p)/(1-p) + mu^(2-p)/(2-p)] p = self.p d = 2.0 * ( torch.pow(y_true_tensor.clamp(min=1e-8), 2.0 - p) / ((1.0 - p) * (2.0 - p)) - y_true_tensor * torch.pow(mu, 1.0 - p) / (1.0 - p) + torch.pow(mu, 2.0 - p) / (2.0 - p) ) metrics["tweedie_deviance"] = d.mean().detach().numpy() mse_loss = torch.nn.functional.mse_loss(y_true_tensor, mu) rmse = np.sqrt(mse_loss.detach().numpy()) mae = torch.nn.functional.l1_loss(y_true_tensor, mu).detach().numpy() metrics["mse"] = mse_loss.detach().numpy() metrics["mae"] = mae metrics["rmse"] = rmse return metrics