Skip to content

Fix make metric aggregation use explicit logging semantics (Closes #343) - #344

Open
kshirajahere wants to merge 9 commits into
mllam:mainfrom
kshirajahere:fix-make-metric-aggregation-use-explicit-logging-semantics
Open

Fix make metric aggregation use explicit logging semantics (Closes #343)#344
kshirajahere wants to merge 9 commits into
mllam:mainfrom
kshirajahere:fix-make-metric-aggregation-use-explicit-logging-semantics

Conversation

@kshirajahere

@kshirajahere kshirajahere commented Mar 5, 2026

Copy link
Copy Markdown
Contributor

Describe your changes

This PR fixes metric aggregation/logging in ARModel.aggregate_and_plot_metrics() by replacing the current universal linear rescaling rule with explicit per-metric logging semantics.

Summary of changes

  • Added a MetricLoggingSpec in neural_lam.metrics
  • Added explicit logging metadata / helper for metric post-processing before plotting/logging
  • Updated ARModel.aggregate_and_plot_metrics() to delegate to metric-specific logging behavior instead of assuming all metrics should be linearly rescaled by state_std
  • Added focused tests covering:
    • mse -> rmse with sqrt + linear rescaling
    • mae with linear rescaling
    • output_std with linear rescaling
    • nll with no linear rescaling
    • missing metric logging specs failing clearly

Motivation / context

aggregate_and_plot_metrics() previously assumed that every aggregated metric could be converted from standardized space to logged units via the same linear rescaling rule.

That assumption is only valid for some metrics. As evaluation support expands, especially for probabilistic metrics, logging behavior needs to be explicit per metric rather than hidden in a universal post-processing rule.

Dependencies

No new external dependencies.

Issue Link

Closes #343

Type of change

  • 🐛 Bug fix (non-breaking change that fixes an issue)
  • ✨ New feature (non-breaking change that adds functionality)
  • 💥 Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • 📖 Documentation (Addition or improvements to documentation)

Checklist before requesting a review

  • My branch is up-to-date with the target branch - if not update your fork with the changes from the target branch (use pull with --rebase option if possible).
  • I have performed a self-review of my code
  • For any new/modified functions/classes I have added docstrings that clearly describe its purpose, expected inputs and returned values
  • I have placed in-line comments to clarify the intent of any hard-to-understand passages of my code
  • I have updated the README to cover introduced code changes
  • I have added tests that prove my fix is effective or that my feature works
  • I have given the PR a name that clearly describes the change, written in imperative form (context).
  • I have requested a reviewer and an assignee (assignee is responsible for merging). This applies only if you have write access to the repo, otherwise feel free to tag a maintainer to add a reviewer and assignee.

Checklist for reviewers

Each PR comes with its own improvements and flaws. The reviewer should check the following:

  • the code is readable
  • the code is well tested
  • the code is documented (including return types and parameters)
  • the code is easy to maintain

Author checklist after completed review

  • I have added a line to the CHANGELOG describing this change, in a section
    reflecting type of change (add section where missing):
    • added: when you have added new functionality
    • changed: when default behaviour of the code has been changed
    • fixes: when your contribution fixes a bug
    • maintenance: when your contribution is relates to repo maintenance, e.g. CI/CD or documentation

Checklist for assignee

  • PR is up to date with the base branch
  • the tests pass
  • (if the PR is not just maintenance/bugfix) the PR is assigned to the next milestone. If it is not, propose it for a future milestone.
  • author has added an entry to the changelog (and designated the change as added, changed, fixed or maintenance)
  • Once the PR is ready to be merged, squash commits and merge the PR.

@kshirajahere

Copy link
Copy Markdown
Contributor Author

@sadamov @joeloskarsson Friendly ping to review it when u have time :D

@sadamov
sadamov requested a review from joeloskarsson March 12, 2026 19:43
@kshirajahere
kshirajahere force-pushed the fix-make-metric-aggregation-use-explicit-logging-semantics branch from c5607c3 to 3991200 Compare March 16, 2026 10:26
@kshirajahere
kshirajahere force-pushed the fix-make-metric-aggregation-use-explicit-logging-semantics branch from 3991200 to 7ded2f1 Compare March 16, 2026 10:42

@Sir-Sloth-The-Lazy Sir-Sloth-The-Lazy left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great work🤩, the metric-as-object design is a clear improvement over the per-name spec approach. A few issues from a careful read:

Critical: output_std is initialized but never populated in test_step will crash aggregate_and_plot_metrics when self.output_std=True.
Medium: The fallback else branch in aggregate_and_plot_metrics still applies the broken universal-linear-rescaling rule for any unregistered metric. Re-introduces the exact bug #343 fixes, just behind a gate. Consider removing it.
Low: WMSE and WMAE class docstrings still describe linear rescaling they should mention they're dimensionless and not rescaled.
Tests look thorough for the registered metrics adding one end-to-end run through test_step with output_std=True would have caught #1.
If you could rebase it on the current main , it would be really helpful in reviewing !

# Compute the built-in evaluation metrics for error maps. test_metrics
# may also contain subclass-specific entries that are populated
# differently and only aggregated later.
for metric_name in ("mse", "mae"):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This loop only populates test_metrics["mse"] and test_metrics["mae"], but test_metrics["output_std"] is still initialized as an empty list at line 148 when self.output_std=True. The old manual mean_pred_std block that populated it was removed, but OutputStd() is never called anywhere to fill it.

When self.output_std=True, on_test_epoch_end will hit torch.cat([]) in aggregate_and_plot_metrics and crash with RuntimeError: torch.cat(): expected a non-empty list of Tensors.

The PR description says output_std was "made a real metric object with the same callable interface as the others" but the call site was never wired up. I suggest extending the loop:

metric_names = ["mse", "mae"]
if self.output_std:
    metric_names.append("output_std")
for metric_name in metric_names:
    ...

metric_tensor, self.state_std
)
display_name = metric_obj.display_name
else:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This else branch re-implements the exact universal-linear-rescaling rule that issue #343 calls out as incorrect just gated behind "is the metric unregistered?". Any future custom metric added to a subclass's test_metrics/val_metrics without registering in DEFINED_METRICS will silently hit the original bug again.

Given the goal of the PR is to eliminate the implicit universal-rescaling assumption, I'd argue this fallback shouldn't exist. Two cleaner options:

  1. Require registration: raise a clear error for unknown metric names, forcing any new metric to go through the BaseMetric interface. This is what the new tests already check for registered names extend the contract.
  2. Default behavior on BaseMetric: if every metric must be a BaseMetric, the fallback is unreachable by construction.

If the fallback must stay for some reason (e.g. you know of an out-of-tree subclass that depends on it), please leave a clear comment naming what it's for

Comment thread neural_lam/metrics.py
"""
Weighted Mean Squared Error (weighted by 1/pred_std^2).
Logged as WRMSE (sqrt applied after averaging, then linear rescale).
"""

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The class docstring says "Logged as WRMSE (sqrt applied after averaging, then linear rescale)", but WMSE.rescale below returns the tensor unchanged (no linear rescale because WMSE is dimensionless). Looks like a copy-paste leftover from MSE.

Suggested change
"""
"""
Weighted Mean Squared Error (weighted by 1/pred_std^2).
Logged as WRMSE (sqrt applied after averaging). Not rescaled weighted
metrics are dimensionless in normalized space.
"""

Comment thread neural_lam/metrics.py Outdated
Co-authored-by: Jeevant  Prakhar  Singh <anupamasinghsrinet1976@gmail.com>
@sadamov

sadamov commented Jun 6, 2026

Copy link
Copy Markdown
Collaborator

Hi @kshirajahere, thanks for this and sorry for the long wait! Two things have shifted since you opened this that make a fresh rebase worth doing before review:

  1. models/ar_model.py was removed by Refactor model class hierarchy into composable Forecaster/StepPredictor layers #208; aggregate_and_plot_metrics now lives in models/module.py
  2. metrics.py just gained comprehensive type hints in Add type hints to all functions in metrics.py #446 #447 today, which will conflict heavily with your metrics.py refactor

The bug it fixes (universal linear rescaling assumption in aggregate_and_plot_metrics) is real and your fix direction looks right. I'd also like to discuss the new MetricLoggingSpec abstractio, the new BaseMetric hierarchy here is essentially a custom mini-torchmetrics, which collides with @leifdenby's #597 RFC to move metric logging onto torchmetrics directly. Let me know when you have a rebase ready and I'll take a proper look. Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug] Metric plotting/logging assumes universal linear rescaling in aggregate_and_plot_metrics

3 participants