stagereg 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
stagereg/__init__.py ADDED
@@ -0,0 +1 @@
1
+ """Probabilistic regression with explicit, composable training stages."""
@@ -0,0 +1,149 @@
1
+ """Backend-neutral contracts and utilities for probabilistic regression."""
2
+
3
+ from stagereg.core.checkpoints import (
4
+ Checkpoint,
5
+ CheckpointStore,
6
+ InMemoryCheckpointStore,
7
+ )
8
+ from stagereg.core.distributions import (
9
+ DistributionHead,
10
+ DistributionLoss,
11
+ Likelihood,
12
+ Loss,
13
+ PredictionLoss,
14
+ PredictiveDistribution,
15
+ )
16
+ from stagereg.core.early_stopping import (
17
+ EarlyStopper,
18
+ EarlyStoppingDecision,
19
+ EarlyStoppingState,
20
+ OptimizationMode,
21
+ )
22
+ from stagereg.core.losses import (
23
+ BetaNLLLoss,
24
+ GaussianNLLLoss,
25
+ NegativeLogLikelihoodLoss,
26
+ SquaredErrorLoss,
27
+ add_epsilon,
28
+ )
29
+ from stagereg.core.metric_registry import (
30
+ ContinuousRankedProbabilityScore,
31
+ EpochMetric,
32
+ EpochPredictionData,
33
+ EvaluationGrid,
34
+ IntervalCoverage,
35
+ MetricRequirements,
36
+ NegativeLogLikelihood,
37
+ PointContinuousRankedProbabilityScore,
38
+ PredictionInterval,
39
+ RootMeanSquaredError,
40
+ SampleContinuousRankedProbabilityScore,
41
+ WeightedSpread,
42
+ )
43
+ from stagereg.core.metrics import (
44
+ cdf,
45
+ coverage,
46
+ crps,
47
+ point_crps,
48
+ rmse,
49
+ sample_crps,
50
+ wsu,
51
+ )
52
+ from stagereg.core.naming import (
53
+ MetricTag,
54
+ Split,
55
+ flatten_parameters,
56
+ metric_tag,
57
+ parse_metric_tag,
58
+ )
59
+ from stagereg.core.protocols import (
60
+ Dataset,
61
+ LoaderFactory,
62
+ Optimizer,
63
+ Step,
64
+ ValidationStrategy,
65
+ )
66
+ from stagereg.core.stages import StageState, TrainingStage, validate_transition
67
+ from stagereg.core.tracking import (
68
+ Decision,
69
+ EventSink,
70
+ ExperimentTracker,
71
+ TrackerEventSink,
72
+ TrainingEvent,
73
+ )
74
+ from stagereg.core.types import (
75
+ Array,
76
+ Batch,
77
+ CheckpointRef,
78
+ ParameterRole,
79
+ PyTree,
80
+ StageResult,
81
+ TrainingState,
82
+ ValidationResult,
83
+ )
84
+
85
+ __all__ = [
86
+ "Array",
87
+ "Batch",
88
+ "BetaNLLLoss",
89
+ "Checkpoint",
90
+ "CheckpointRef",
91
+ "CheckpointStore",
92
+ "ContinuousRankedProbabilityScore",
93
+ "Dataset",
94
+ "Decision",
95
+ "DistributionHead",
96
+ "DistributionLoss",
97
+ "EarlyStopper",
98
+ "EarlyStoppingDecision",
99
+ "EarlyStoppingState",
100
+ "EpochMetric",
101
+ "EpochPredictionData",
102
+ "EvaluationGrid",
103
+ "EventSink",
104
+ "ExperimentTracker",
105
+ "GaussianNLLLoss",
106
+ "InMemoryCheckpointStore",
107
+ "IntervalCoverage",
108
+ "Likelihood",
109
+ "LoaderFactory",
110
+ "Loss",
111
+ "MetricRequirements",
112
+ "MetricTag",
113
+ "NegativeLogLikelihood",
114
+ "NegativeLogLikelihoodLoss",
115
+ "OptimizationMode",
116
+ "Optimizer",
117
+ "ParameterRole",
118
+ "PointContinuousRankedProbabilityScore",
119
+ "PredictionInterval",
120
+ "PredictionLoss",
121
+ "PredictiveDistribution",
122
+ "PyTree",
123
+ "RootMeanSquaredError",
124
+ "SampleContinuousRankedProbabilityScore",
125
+ "Split",
126
+ "SquaredErrorLoss",
127
+ "StageResult",
128
+ "StageState",
129
+ "Step",
130
+ "TrackerEventSink",
131
+ "TrainingEvent",
132
+ "TrainingStage",
133
+ "TrainingState",
134
+ "ValidationResult",
135
+ "ValidationStrategy",
136
+ "WeightedSpread",
137
+ "add_epsilon",
138
+ "cdf",
139
+ "coverage",
140
+ "crps",
141
+ "flatten_parameters",
142
+ "metric_tag",
143
+ "parse_metric_tag",
144
+ "point_crps",
145
+ "rmse",
146
+ "sample_crps",
147
+ "validate_transition",
148
+ "wsu",
149
+ ]
@@ -0,0 +1,100 @@
1
+ """Checkpoint value objects and persistence protocol."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from collections.abc import Mapping
6
+ from dataclasses import dataclass, field
7
+ from typing import Any, Protocol
8
+
9
+ from stagereg.core.types import TrainingState
10
+
11
+
12
+ @dataclass(frozen=True)
13
+ class Checkpoint:
14
+ """The complete state necessary to resume staged training.
15
+
16
+ Attributes:
17
+ state: Snapshot of the [`TrainingState`][stagereg.core.TrainingState] at
18
+ the time the checkpoint was saved.
19
+ epoch: Epoch of the active stage at which the checkpoint was saved.
20
+ parameters: Snapshot of the model's parameters, in the backend's own
21
+ format. Defaults to ``None``.
22
+ optimizer_state: Snapshot of the optimizer's state, in the backend's own
23
+ format. Defaults to ``None``.
24
+ rng_state: Random key to resume training with. It mirrors the
25
+ ``rng_state`` of the embedded ``state``. Defaults to ``None``.
26
+ early_stopping_state: State of the early stopper, such as an
27
+ [`EarlyStoppingState`][stagereg.core.EarlyStoppingState], so that
28
+ patience and the best value carry over on resume. Defaults to
29
+ ``None``.
30
+ metadata: Information about the checkpoint, such as the stage that saved
31
+ it. Defaults to an empty mapping.
32
+ """
33
+
34
+ state: TrainingState
35
+ epoch: int
36
+ parameters: Any | None = None
37
+ optimizer_state: Any | None = None
38
+ rng_state: Any | None = None
39
+ early_stopping_state: Any | None = None
40
+ metadata: Mapping[str, Any] = field(default_factory=dict)
41
+
42
+
43
+ class CheckpointStore(Protocol):
44
+ """Persists checkpoints under opaque keys."""
45
+
46
+ def save(self, key: str, checkpoint: Checkpoint) -> None: ...
47
+
48
+ def load(self, key: str) -> Checkpoint: ...
49
+
50
+ def exists(self, key: str) -> bool: ...
51
+
52
+
53
+ class InMemoryCheckpointStore:
54
+ """A [`CheckpointStore`][stagereg.core.CheckpointStore] kept in process memory.
55
+
56
+ Checkpoints live only for the lifetime of the store, which makes it the natural
57
+ choice for examples, tests, and short runs that only need the best checkpoint of the
58
+ current process restored at the end. Saving under an existing key replaces the
59
+ checkpoint held there.
60
+ """
61
+
62
+ def __init__(self) -> None:
63
+ self._checkpoints: dict[str, Checkpoint] = {}
64
+
65
+ def save(self, key: str, checkpoint: Checkpoint) -> None:
66
+ """Store a checkpoint under a key, replacing any checkpoint there.
67
+
68
+ Args:
69
+ key: The opaque key to store the checkpoint under.
70
+ checkpoint: The checkpoint to store.
71
+
72
+ Returns:
73
+ None.
74
+ """
75
+ self._checkpoints[key] = checkpoint
76
+
77
+ def load(self, key: str) -> Checkpoint:
78
+ """Return the checkpoint stored under a key.
79
+
80
+ Args:
81
+ key: The key the checkpoint was saved under.
82
+
83
+ Returns:
84
+ The stored checkpoint.
85
+
86
+ Raises:
87
+ KeyError: If no checkpoint has been saved under ``key``.
88
+ """
89
+ return self._checkpoints[key]
90
+
91
+ def exists(self, key: str) -> bool:
92
+ """Report whether a checkpoint is stored under a key.
93
+
94
+ Args:
95
+ key: The key to look up.
96
+
97
+ Returns:
98
+ ``True`` if a checkpoint is stored under ``key``.
99
+ """
100
+ return key in self._checkpoints
@@ -0,0 +1,60 @@
1
+ """Protocols for distribution-valued model predictions and objectives."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Protocol
6
+
7
+ from stagereg.core.types import Array, Batch
8
+
9
+
10
+ class PredictiveDistribution(Protocol):
11
+ """A probability distribution produced by a regression model.
12
+
13
+ This protocol is the backend-neutral model/loss boundary. Epoch metrics use backend
14
+ adapters to materialize host arrays rather than retaining distribution objects,
15
+ which intentionally have no concatenation or indexing contract.
16
+ """
17
+
18
+ @property
19
+ def batch_shape(self) -> tuple[int, ...]: ...
20
+
21
+ @property
22
+ def event_shape(self) -> tuple[int, ...]: ...
23
+
24
+ def log_prob(self, targets: Array) -> Array: ...
25
+
26
+ def sample(self, key: Array, sample_shape: tuple[int, ...] = ()) -> Array: ...
27
+
28
+ def mean(self) -> Array: ...
29
+
30
+ def variance(self) -> Array: ...
31
+
32
+
33
+ class DistributionHead(Protocol):
34
+ """Produces a predictive distribution from model features."""
35
+
36
+ def __call__(self, features: Array) -> PredictiveDistribution: ...
37
+
38
+
39
+ class Likelihood(Protocol):
40
+ """Evaluates a target under a predictive distribution."""
41
+
42
+ def log_prob(self, prediction: PredictiveDistribution, targets: Array) -> Array: ...
43
+
44
+
45
+ class DistributionLoss(Protocol):
46
+ """Computes an unreduced loss from a predictive distribution."""
47
+
48
+ def per_example(
49
+ self, prediction: PredictiveDistribution, batch: Batch
50
+ ) -> Array: ...
51
+
52
+
53
+ class PredictionLoss(Protocol):
54
+ """Computes an unreduced loss from deterministic array predictions."""
55
+
56
+ def per_example(self, prediction: Array, batch: Batch) -> Array: ...
57
+
58
+
59
+ # Backward-compatible alias for the original distribution-loss protocol.
60
+ Loss = DistributionLoss
@@ -0,0 +1,252 @@
1
+ """Backend-neutral early-stopping policies."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass
6
+ from enum import StrEnum
7
+ from math import isfinite
8
+
9
+ from stagereg.core.naming import Split
10
+
11
+
12
+ class OptimizationMode(StrEnum):
13
+ """The direction in which a monitored metric is considered improved."""
14
+
15
+ MIN = "min"
16
+ MAX = "max"
17
+
18
+
19
+ @dataclass(frozen=True)
20
+ class EarlyStoppingState:
21
+ """Checkpointable state for a configured early-stopping policy.
22
+
23
+ Attributes:
24
+ metric: Name of the monitored metric.
25
+ mode: Direction in which the metric is considered improved.
26
+ min_delta: Minimum change required to qualify as an improvement.
27
+ patience: Number of consecutive non-improving epochs tolerated
28
+ before stopping.
29
+ source: Split the monitored metric is measured on.
30
+ best_score: Best value observed so far, or ``None`` if no value
31
+ has been observed yet.
32
+ best_epoch: Epoch at which ``best_score`` was observed, or
33
+ ``None`` if no value has been observed yet.
34
+ non_improving_epochs: Number of consecutive epochs without an
35
+ improvement.
36
+ stopped: Whether patience has been exhausted and training should
37
+ stop.
38
+ """
39
+
40
+ metric: str
41
+ mode: OptimizationMode
42
+ min_delta: float
43
+ patience: int
44
+ source: Split = Split.VALIDATION
45
+ best_score: float | None = None
46
+ best_epoch: int | None = None
47
+ non_improving_epochs: int = 0
48
+ stopped: bool = False
49
+
50
+ def __post_init__(self) -> None:
51
+ """Validate checkpointable early-stopping state.
52
+
53
+ Raises:
54
+ ValueError: If configuration or observation fields cannot represent
55
+ a state produced by [`EarlyStopper`][stagereg.core.EarlyStopper].
56
+ """
57
+ if not self.metric:
58
+ raise ValueError("metric must be a non-empty string.")
59
+ if self.patience < 0:
60
+ raise ValueError("patience must be non-negative.")
61
+ if not isfinite(self.min_delta) or self.min_delta < 0:
62
+ raise ValueError("min_delta must be finite and non-negative.")
63
+ if self.best_score is not None and not isfinite(self.best_score):
64
+ raise ValueError("best_score must be finite when provided.")
65
+ if self.best_epoch is not None and self.best_epoch < 0:
66
+ raise ValueError("best_epoch must be non-negative when provided.")
67
+ if (self.best_score is None) != (self.best_epoch is None):
68
+ raise ValueError("best_score and best_epoch must be provided together.")
69
+ if self.non_improving_epochs < 0:
70
+ raise ValueError("non_improving_epochs must be non-negative.")
71
+ if self.best_score is None and self.non_improving_epochs != 0:
72
+ raise ValueError(
73
+ "non_improving_epochs must be zero before the first observation."
74
+ )
75
+ expected_stopped = self.non_improving_epochs > self.patience
76
+ if self.stopped is not expected_stopped:
77
+ raise ValueError(
78
+ "stopped must indicate whether non-improving epochs exceed patience."
79
+ )
80
+
81
+
82
+ @dataclass(frozen=True)
83
+ class EarlyStoppingDecision:
84
+ """The result of observing one monitored metric value.
85
+
86
+ Attributes:
87
+ state: Updated early-stopping state after the observation.
88
+ improved: Whether the observed value improved on the best score.
89
+ """
90
+
91
+ state: EarlyStoppingState
92
+ improved: bool
93
+
94
+ @property
95
+ def should_stop(self) -> bool:
96
+ """Whether the configured patience has been exhausted."""
97
+ return self.state.stopped
98
+
99
+
100
+ class EarlyStopper:
101
+ """Tracks a scalar metric and decides when training should stop."""
102
+
103
+ def __init__(
104
+ self,
105
+ *,
106
+ metric: str,
107
+ mode: OptimizationMode | str,
108
+ patience: int,
109
+ min_delta: float = 0.0,
110
+ source: Split | str = Split.VALIDATION,
111
+ state: EarlyStoppingState | None = None,
112
+ ) -> None:
113
+ """Initialize an early stopper.
114
+
115
+ Args:
116
+ metric: Name of the metric to monitor.
117
+ mode: Direction in which the metric is considered improved,
118
+ either ``"min"`` or ``"max"``.
119
+ patience: Number of consecutive non-improving epochs
120
+ tolerated before stopping.
121
+ min_delta: Minimum change required to qualify as an
122
+ improvement. Defaults to ``0.0``.
123
+ source: Split the monitored metric is measured on, either
124
+ ``"train"`` or ``"validation"``. Defaults to
125
+ ``Split.VALIDATION``.
126
+ state: Previously checkpointed state to resume from. Must
127
+ match the configuration passed to this constructor.
128
+
129
+ Raises:
130
+ ValueError: If ``metric`` is empty, ``patience`` is
131
+ negative, ``min_delta`` is not a finite non-negative
132
+ number, ``mode`` or ``source`` is not a recognised value,
133
+ or ``state`` does not match the given configuration.
134
+ """
135
+ if not metric:
136
+ raise ValueError("metric must be a non-empty string.")
137
+ if patience < 0:
138
+ raise ValueError("patience must be non-negative.")
139
+ if not isfinite(min_delta) or min_delta < 0:
140
+ raise ValueError("min_delta must be finite and non-negative.")
141
+
142
+ normalized_mode = OptimizationMode(mode)
143
+ normalized_source = Split(source)
144
+ self._state = state or EarlyStoppingState(
145
+ metric=metric,
146
+ mode=normalized_mode,
147
+ min_delta=min_delta,
148
+ patience=patience,
149
+ source=normalized_source,
150
+ )
151
+ if (
152
+ self._state.metric != metric
153
+ or self._state.mode != normalized_mode
154
+ or self._state.min_delta != min_delta
155
+ or self._state.patience != patience
156
+ or self._state.source != normalized_source
157
+ ):
158
+ raise ValueError("state does not match the early-stopper configuration.")
159
+
160
+ @property
161
+ def state(self) -> EarlyStoppingState:
162
+ """Return the current checkpointable stopping state."""
163
+ return self._state
164
+
165
+ def expects_validation(self) -> bool:
166
+ """Whether the monitored metric must come from validation.
167
+
168
+ Returns:
169
+ ``True`` if the metric configured for this stopper is
170
+ measured on the validation split (i.e. requires a
171
+ [`ValidationStrategy`][stagereg.core.ValidationStrategy] to be
172
+ supplied to the training loop), ``False`` if it is measured
173
+ on the training split.
174
+ """
175
+ return self._state.source is Split.VALIDATION
176
+
177
+ def monitored_metric_name(self) -> str:
178
+ """Return the name of the metric this stopper monitors.
179
+
180
+ Returns:
181
+ The metric name passed to the constructor, used by callers
182
+ to look up the corresponding value in per-epoch metrics.
183
+ """
184
+ return self._state.metric
185
+
186
+ def observe(self, value: float, *, epoch: int) -> EarlyStoppingDecision:
187
+ """Record a metric observed after ``epoch`` and return its decision.
188
+
189
+ Args:
190
+ value: The monitored metric value observed for this epoch.
191
+ epoch: The epoch at which ``value`` was observed.
192
+
193
+ Returns:
194
+ An [`EarlyStoppingDecision`][stagereg.core.EarlyStoppingDecision]
195
+ with the updated state and whether ``value`` improved on the best
196
+ score so far.
197
+
198
+ Raises:
199
+ ValueError: If ``epoch`` is negative or ``value`` is not
200
+ finite.
201
+ RuntimeError: If early stopping has already been triggered.
202
+ """
203
+ if epoch < 0:
204
+ raise ValueError("epoch must be non-negative.")
205
+ if not isfinite(value):
206
+ raise ValueError("observed metric value must be finite.")
207
+ if self._state.stopped:
208
+ raise RuntimeError("cannot observe a metric after early stopping.")
209
+
210
+ improved = self._is_improvement(value)
211
+ if improved:
212
+ self._state = EarlyStoppingState(
213
+ metric=self._state.metric,
214
+ mode=self._state.mode,
215
+ min_delta=self._state.min_delta,
216
+ patience=self._state.patience,
217
+ source=self._state.source,
218
+ best_score=value,
219
+ best_epoch=epoch,
220
+ )
221
+ else:
222
+ non_improving_epochs = self._state.non_improving_epochs + 1
223
+ self._state = EarlyStoppingState(
224
+ metric=self._state.metric,
225
+ mode=self._state.mode,
226
+ min_delta=self._state.min_delta,
227
+ patience=self._state.patience,
228
+ source=self._state.source,
229
+ best_score=self._state.best_score,
230
+ best_epoch=self._state.best_epoch,
231
+ non_improving_epochs=non_improving_epochs,
232
+ stopped=non_improving_epochs > self._state.patience,
233
+ )
234
+ return EarlyStoppingDecision(state=self._state, improved=improved)
235
+
236
+ def _is_improvement(self, value: float) -> bool:
237
+ """Determine whether ``value`` improves on the current best score.
238
+
239
+ Args:
240
+ value: The candidate metric value.
241
+
242
+ Returns:
243
+ ``True`` if ``value`` is an improvement over the current best
244
+ score given the configured mode and ``min_delta``.
245
+ """
246
+ if self._state.best_score is None:
247
+ return True
248
+ match self._state.mode:
249
+ case OptimizationMode.MIN:
250
+ return value < self._state.best_score - self._state.min_delta
251
+ case OptimizationMode.MAX:
252
+ return value > self._state.best_score + self._state.min_delta
@@ -0,0 +1,130 @@
1
+ """Backend-neutral per-example objectives for probabilistic regression."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import math
6
+ from collections.abc import Callable
7
+ from dataclasses import dataclass, field
8
+
9
+ from stagereg.core.distributions import PredictiveDistribution
10
+ from stagereg.core.types import Array, Batch
11
+
12
+
13
+ def _identity(value: Array) -> Array:
14
+ """Return ``value`` unchanged; the default no-op gradient-stopping hook."""
15
+ return value
16
+
17
+
18
+ @dataclass(frozen=True)
19
+ class NegativeLogLikelihoodLoss:
20
+ """Configurable negative log-likelihood for predictive distributions.
21
+
22
+ Attributes:
23
+ beta: Variance-reweighting exponent in ``[0, 1]``. Zero recovers
24
+ ordinary negative log-likelihood.
25
+ stop_gradient: Callable excluding the variance weight from gradient
26
+ propagation. Defaults to the identity, which lets gradients flow
27
+ through the weight. With JAX and ``beta > 0``, pass
28
+ ``jax.lax.stop_gradient``; otherwise a different objective from
29
+ beta-NLL is trained.
30
+ target_transform: Callable applied to targets before evaluating the
31
+ predictive distribution. Defaults to the identity transform.
32
+ """
33
+
34
+ beta: float = 0.0
35
+ stop_gradient: Callable[[Array], Array] = field(default=_identity)
36
+ target_transform: Callable[[Array], Array] = field(default=_identity)
37
+
38
+ def __post_init__(self) -> None:
39
+ """Validate the variance-reweighting exponent.
40
+
41
+ Raises:
42
+ ValueError: If ``beta`` is outside ``[0, 1]``.
43
+ """
44
+ if not 0.0 <= self.beta <= 1.0:
45
+ raise ValueError("beta must be within [0, 1].")
46
+
47
+ def per_example(self, prediction: PredictiveDistribution, batch: Batch) -> Array:
48
+ """Compute the optionally transformed and beta-weighted NLL.
49
+
50
+ Args:
51
+ prediction: The predictive distribution produced for ``batch``.
52
+ batch: Batch whose targets are scored under ``prediction``.
53
+
54
+ Returns:
55
+ An array of per-example negative log-likelihood values.
56
+
57
+ Raises:
58
+ ValueError: If ``batch.targets`` is missing.
59
+ """
60
+ if batch.targets is None:
61
+ raise ValueError("batch.targets must be provided.")
62
+ targets = self.target_transform(batch.targets)
63
+ nll = -prediction.log_prob(targets)
64
+ if self.beta == 0.0:
65
+ return nll
66
+ weight = self.stop_gradient(prediction.variance()) ** self.beta
67
+ return nll * weight
68
+
69
+
70
+ @dataclass(frozen=True)
71
+ class SquaredErrorLoss:
72
+ """Per-example squared error for deterministic predictions."""
73
+
74
+ def per_example(self, prediction: Array, batch: Batch) -> Array:
75
+ """Compute squared prediction errors.
76
+
77
+ Args:
78
+ prediction: Deterministic model predictions.
79
+ batch: Batch containing regression targets.
80
+
81
+ Returns:
82
+ Elementwise squared prediction errors.
83
+
84
+ Raises:
85
+ ValueError: If ``batch.targets`` is missing.
86
+ """
87
+ if batch.targets is None:
88
+ raise ValueError("batch.targets must be provided.")
89
+ if prediction.shape != batch.targets.shape:
90
+ raise ValueError("prediction and batch.targets must have matching shapes.")
91
+ return (prediction - batch.targets) ** 2
92
+
93
+
94
+ def add_epsilon(epsilon: float = 1e-12) -> Callable[[Array], Array]:
95
+ """Create a target transform that adds a positive stabilization offset.
96
+
97
+ Args:
98
+ epsilon: Positive finite offset added to each target.
99
+
100
+ Returns:
101
+ A callable adding ``epsilon`` to an array.
102
+
103
+ Raises:
104
+ ValueError: If ``epsilon`` is not positive and finite.
105
+ """
106
+ if not math.isfinite(epsilon) or epsilon <= 0.0:
107
+ raise ValueError("epsilon must be positive and finite.")
108
+
109
+ def transform(targets: Array) -> Array:
110
+ return targets + epsilon
111
+
112
+ return transform
113
+
114
+
115
+ # Compatibility aliases for the public pre-consolidation names. Both preserve
116
+ # their original constructor behavior while the canonical API uses
117
+ # NegativeLogLikelihoodLoss.
118
+ GaussianNLLLoss = NegativeLogLikelihoodLoss
119
+ """Alias of [`NegativeLogLikelihoodLoss`][stagereg.core.NegativeLogLikelihoodLoss].
120
+
121
+ Kept for compatibility with the pre-consolidation name; prefer the canonical name.
122
+ """
123
+
124
+ BetaNLLLoss = NegativeLogLikelihoodLoss
125
+ """Alias of [`NegativeLogLikelihoodLoss`][stagereg.core.NegativeLogLikelihoodLoss].
126
+
127
+ Kept for compatibility with the pre-consolidation name; prefer the canonical name. The
128
+ beta-NLL weighting is the `beta` argument of
129
+ [`NegativeLogLikelihoodLoss`][stagereg.core.NegativeLogLikelihoodLoss].
130
+ """