Skip to content

Faithful Heteroscedastic & CRPS Losses

← Beta-NLL | Wasserstein Bound →

This page covers two specialized Gaussian loss variants: - FaithfulGaussianLoss — decouples mean and variance training to prevent the variance head from distorting point predictions - GaussianCRPSLoss — a proper scoring rule alternative to NLL that is less sensitive to tail miscalibration


FaithfulGaussianLoss

The Problem with Joint NLL

Standard GaussianNLLLoss couples gradients from the variance head into the mean through the residual term:

\[\mathcal{L}_{\text{NLL}}(y, \mu, \sigma^2) = \frac{1}{2} \log(2\pi\sigma^2) + \frac{(y - \mu)^2}{2\sigma^2}\]

The gradient with respect to \(\mu\) is \(\frac{\mu - y}{\sigma^2}\), which means the variance head's outputs directly scale the gradients flowing into the mean head. If the model learns an inflated \(\sigma^2\) in high-error regions, the mean head receives weaker gradients there — effectively "explaining away" prediction errors as aleatoric noise.

The Faithful Solution

FaithfulGaussianLoss decouples the two objectives (see API: FaithfulGaussianLoss):

\[\boxed{\;\mathcal{L}_{\text{Faithful}} = \lambda_{\mu} (\mu - y)^2 + \lambda_{v} \cdot \frac{1}{2} \left( \log(2\pi\sigma^2) + \frac{(y - \operatorname{sg}(\mu))^2}{\sigma^2} \right)\;}\]

where \(\operatorname{sg}\) is stop-gradient. The mean receives gradients only from the MSE term; the variance head receives gradients from the NLL term with a detached mean. This mirrors the intent of faithful heteroscedastic training: preserve point prediction quality while learning a noise model.

Usage

from torchregress.losses import FaithfulGaussianLoss

loss_fn = FaithfulGaussianLoss(mean_weight=1.0, variance_weight=1.0)

# Model outputs (mean, log_variance) tuple
mean, logvar = model(x)
loss = loss_fn((mean, logvar), y_true)
Parameter Type Default Description
mean_weight float or 1D sequence 1.0 Multiplier on the mean term. A scalar applies to all outputs; a sequence of length D gives one weight per output. Set to 0 to train variance only.
mean_loss str "mse" "mse" or "huber". Huber is scaled by 2, so it equals the MSE below huber_delta and is linear beyond it.
huber_delta float 1.0 Huber threshold in target units; used only when mean_loss="huber".
variance_weight float 1.0 Multiplier on the Gaussian NLL terms. Set to 0 to train mean only.
min_variance float 1e-6 Minimum variance for numerical stability
eps float 1e-8 Epsilon for log-stability
reduction str "mean" "mean", "sum", or "none"
split_dim int -1 Dimension to split concatenated [mean, log_var] output

With a vector mean_weight, the weight \(\lambda_{\mu,d}\) multiplies output \(d\) of the mean term, so multi-output problems can give each target its own emphasis (for example to down-weight a target that cannot be measured). Normalise the weights, e.g. to sum to \(D\), so the overall loss scale stays comparable between runs.

# Equal emphasis on three targets except the last, which is down-weighted
loss_fn = FaithfulGaussianLoss(mean_weight=[1.0, 1.0, 0.25], mean_loss="huber", huber_delta=3.0)

Z-score the targets first

The mean term is not scale-invariant. With several outputs, standardise each target so that a weight of 1 means the same thing for all of them.

Comparison: Faithful vs Beta-NLL vs Joint NLL

Feature GaussianNLLLoss BetaNLLLoss FaithfulGaussianLoss
Mean/variance coupling Coupled Variance-detached reweighting Explicitly split
Mean objective NLL residual NLL residual (detached variance) Pure MSE
Variance objective Joint NLL Reweighted NLL Separate NLL term
Gradient path \(\mu\) sees \(\sigma^2\) \(\mu\) sees detached \(\sigma^{2\beta}\) \(\mu\) sees only MSE
Best for Standard heteroscedastic Stabilized variance learning Preserving point accuracy

When to use FaithfulGaussianLoss

Use when you care more about point prediction accuracy than perfectly calibrated variance, but still want a meaningful noise model. Common in scientific applications where the mean prediction is the primary deliverable.

Detach tradeoff

Because the mean head only sees MSE gradients, it does not receive curvature information from the NLL about heteroscedasticity. On datasets where mean and variance are strongly coupled (e.g., Poisson-like count data), this decoupling can slow convergence of the mean head.

Shared features

The stop-gradient is on the predicted mean only. If the mean and variance heads share a trunk, the variance term's gradient still reaches the shared features. That gradient is bounded (no \(1/\sigma^2\) factor), but the trunk is not fully decoupled from the variance task. Detach the features inside the model for a stricter separation.

Edge cases

  • mean_weight=0: The mean head receives zero gradients, but the (detached) mean still feeds the variance head's residual. If the mean is poorly initialized and never trained, the variance head receives a bad signal.
  • variance_weight=0: The variance head receives zero gradients. The loss reduces to plain MSE.

GaussianCRPSLoss

Why CRPS Instead of NLL?

The Continuous Ranked Probability Score (CRPS) is a proper scoring rule that evaluates the full predictive distribution. See the GaussianCRPSLoss API for parameters and the closed-form definition.

Unlike NLL, CRPS:

  • Has the same units as the target variable (interpretable)
  • Is less sensitive to tail events than NLL (a single very-low-probability point doesn't dominate)
  • Penalizes both calibration and sharpness simultaneously
  • Reduces to MAE when the predictive distribution is a point mass

Analytic Gaussian CRPS

For a Gaussian predictive distribution \(\mathcal{N}(\mu, \sigma^2)\), the CRPS has a closed form:

\[\boxed{\;\text{CRPS}(F, y) = \sigma \left[ z \cdot (2\Phi(z) - 1) + 2\phi(z) - \frac{1}{\sqrt{\pi}} \right]\;}\]

where \(z = \frac{y - \mu}{\sigma}\), \(\Phi\) is the standard normal CDF, and \(\phi\) is the standard normal PDF.

Usage

from torchregress.losses import GaussianCRPSLoss

loss_fn = GaussianCRPSLoss()

# Same interface as GaussianNLLLoss
mean, logvar = model(x)
loss = loss_fn((mean, logvar), y_true)

Supports the same prediction formats as GaussianNLLLoss: - Tuple (mean, log_variance) - Concatenated tensor [mean, log_variance] - Mean-only when fixed_variance is set

Parameter Type Default Description
min_variance float 1e-6 Minimum variance for numerical stability
eps float 1e-8 Epsilon for division stability
fixed_variance float or None None Fixed variance for homoscedastic models
reduction str "mean" "mean", "sum", or "none"

When to Prefer CRPS over NLL

Use CRPS when

  • You want interpretable loss values in target units
  • Your data has heavy tails where NLL is dominated by a few extreme points
  • You're comparing models across different distributional families (CRPS is a universal scoring rule)
  • You're evaluating ensemble or Bayesian model predictions via the Energy Score (multivariate CRPS)

Performance note

CRPS is approximately 2-3× slower to compute per iteration than NLL, due to the additional ndtr (normal CDF) and exp operations per element. For large-scale training where NLL is adequate, the speed difference may matter.

Use NLL when

  • You need the tightest possible likelihood-based training signal
  • Your model is well-specified and residuals are approximately Gaussian
  • You're computing information criteria (AIC, BIC) or Bayes factors

Complete Example

import torch
import torch.nn as nn
from torchregress.losses import FaithfulGaussianLoss, GaussianCRPSLoss

# Heteroscedastic model
class HeteroModel(nn.Module):
    def __init__(self, in_dim):
        super().__init__()
        self.shared = nn.Sequential(nn.Linear(in_dim, 64), nn.ReLU(), nn.Linear(64, 32), nn.ReLU())
        self.mean_head = nn.Linear(32, 1)
        self.logvar_head = nn.Linear(32, 1)

    def forward(self, x):
        h = self.shared(x)
        return self.mean_head(h), self.logvar_head(h)

torch.manual_seed(42)
x = torch.randn(200, 5)
y = 2.0 * x[:, [0]] + 0.3 * torch.randn(200, 1) * torch.exp(0.5 * x[:, [1]])

model = HeteroModel(5)
faithful_loss = FaithfulGaussianLoss(mean_weight=1.0, variance_weight=1.0)
crps_loss = GaussianCRPSLoss()

optimizer = torch.optim.Adam(model.parameters(), lr=1e-3)

for epoch in range(100):
    mean, logvar = model(x)
    # Train with faithful loss
    loss = faithful_loss((mean, logvar), y)
    optimizer.zero_grad()
    loss.backward()
    optimizer.step()

    # Monitor CRPS
    with torch.no_grad():
        crps = crps_loss((mean, logvar), y)
    if epoch % 20 == 0:
        print(f"Epoch {epoch}: Faithful Loss = {loss.item():.4f}, CRPS = {crps.item():.4f}")

Limitations

  1. Detach tradeoff: The stop_gradient on \(\mu\) in the NLL residual means the mean head receives only MSE gradients — it does not see heteroscedastic curvature information. On datasets where mean and variance are strongly coupled (e.g., Poisson-like count data), this can slow convergence of the mean head.
  2. Edge case mean_weight=0: The variance head still receives the detached \(\mu\) in its residual term. If \(\mu\) is poorly initialised and never trained, the variance head gets a bad signal.
  3. Edge case variance_weight=0: The loss reduces to plain MSE. The variance head receives zero gradients.
  4. CRPS cost: GaussianCRPSLoss is 2–3× slower per iteration than NLL due to ndtr (normal CDF) and exp operations. For large-scale training where NLL is adequate, prefer NLL for speed.

Recommendations

  • Use when point accuracy matters more than variance calibration: FaithfulGaussianLoss is ideal for scientific applications where the mean prediction is the primary deliverable.
  • Prefer NLL for pure uncertainty quantification: If calibrated variance is your goal, standard GaussianNLLLoss or BetaNLLLoss are better choices.
  • Prefer CRPS for interpretability: GaussianCRPSLoss produces loss values in target units, is less sensitive to tail events, and penalises both calibration and sharpness. See GaussianCRPSLoss in the API reference.
  • Monitor both objectives separately: Track MSE and NLL independently during training to diagnose whether one term is dominating.

Next steps


References

# Reference
1 Stirn et al. "Faithful Heteroscedastic Regression with Deep Neural Networks." AISTATS, 2023.
2 Hersbach, H. "Decomposition of the Continuous Ranked Probability Score for Ensemble Prediction Systems." Weather and Forecasting, 15(5):559–570, 2000.
3 Gneiting & Raftery. "Strictly Proper Scoring Rules, Prediction, and Estimation." JASA, 102(477):359–378, 2007.