Skip to content

Commit 9e6ff22

Browse files
committed
review pass 1.0
Signed-off-by: Faakhir30 <zahidfaakhir@gmail.com>
1 parent 9c2797b commit 9e6ff22

2 files changed

Lines changed: 58 additions & 25 deletions

File tree

pytorch_forecasting/metrics/base_metrics/_base_metrics.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -267,13 +267,11 @@ def coerce_to_pytorch_forecasting_metric(
267267
Metric or torch.nn.Module
268268
Loss/metric usable in ptf training (``forward(y_pred, y_actual)``).
269269
"""
270-
if isinstance(metric, Metric | MultiLoss | CompositeMetric):
271-
return metric
272-
273270
from pytorch_forecasting.metrics.nn_loss_adapter import NNLossAdapter
274271

275-
if isinstance(metric, NNLossAdapter):
272+
if isinstance(metric, (Metric, MultiLoss, CompositeMetric, NNLossAdapter)):
276273
return metric
274+
277275
# bare torch.nn loss (nn.Module, but not a torchmetrics Metric)
278276
if isinstance(metric, torch.nn.Module) and not isinstance(metric, LightningMetric):
279277
return NNLossAdapter(metric)

pytorch_forecasting/metrics/nn_loss_adapter.py

Lines changed: 56 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -68,13 +68,13 @@ def _infer_mode(loss: nn.Module) -> _Mode:
6868
6969
Returns
7070
-------
71-
{"point", "class", "gaussian_nll"}
71+
Literal["point", "class", "gaussian_nll"]
7272
``"class"`` for ``CrossEntropyLoss`` / ``NLLLoss``,
7373
``"gaussian_nll"`` for ``GaussianNLLLoss``, else ``"point"``.
7474
"""
7575
if isinstance(loss, _CLASS_LOSSES):
7676
return "class"
77-
if isinstance(loss, _GAUSSIAN_NLL_LOSSES):
77+
elif isinstance(loss, _GAUSSIAN_NLL_LOSSES):
7878
return "gaussian_nll"
7979
return "point"
8080

@@ -112,13 +112,46 @@ def loss(self, y_pred: torch.Tensor, target: torch.Tensor) -> torch.Tensor:
112112

113113
if mode == "class":
114114
return per_elem.view(batch_size, time_idx)
115-
if per_elem.ndim == 0:
115+
elif per_elem.ndim == 0:
116116
# defensive: some losses ignore reduction="none"
117117
return per_elem.expand(batch_size, time_idx)
118118
return per_elem
119119

120-
def update(self, y_pred, target):
121-
"""Update metric state; reject multi-target lists with a clear error."""
120+
def update(
121+
self,
122+
y_pred: torch.Tensor | list[torch.Tensor],
123+
target: torch.Tensor
124+
| tuple[torch.Tensor, torch.Tensor | None]
125+
| tuple[list[torch.Tensor], torch.Tensor | None],
126+
) -> None:
127+
"""Accumulate batch loss into metric state.
128+
129+
Parameters
130+
----------
131+
y_pred : torch.Tensor or list of torch.Tensor
132+
Network prediction for a single target.
133+
134+
* point: ``[B, T, 1]`` or ``[B, T]``
135+
* class: ``[B, T, C]`` logits
136+
* gaussian_nll: ``[B, T, 2]`` as ``(mean, raw_variance)``
137+
138+
A list of prediction tensors is not supported; use
139+
:class:`~pytorch_forecasting.metrics.MultiLoss` for multi-target.
140+
target : torch.Tensor or tuple
141+
Ground truth. Either a tensor ``[B, T]``, or
142+
``(target, weight)`` where ``weight`` is ``[B, T]`` or ``None``.
143+
A list of target tensors is not supported.
144+
145+
Returns
146+
-------
147+
None
148+
149+
Raises
150+
------
151+
ValueError
152+
If ``target`` (or the first element of ``(target, weight)``) is a
153+
list of tensors.
154+
"""
122155
# MultiHorizonMetric unpacks (target, weight) before calling loss();
123156
# catch list targets here so the message is useful.
124157
raw_target = target
@@ -164,7 +197,7 @@ def _prepare_inputs(
164197
if mode == "point":
165198
if y_pred.ndim != 3:
166199
return y_pred, target
167-
if y_pred.size(-1) != 1:
200+
elif y_pred.size(-1) != 1:
168201
raise ValueError(
169202
"Error inNNLossAdapter for point prediction (H=1): "
170203
f"Got y_pred shape {list(y_pred.shape)} with "
@@ -181,25 +214,27 @@ def _prepare_inputs(
181214
"Classification losses expect logits of shape "
182215
f"(batch, time, classes), got {tuple(y_pred.shape)}."
183216
)
184-
if target.ndim != 2:
217+
elif target.ndim != 2:
185218
raise ValueError(
186219
"Classification targets must have shape (batch, time), "
187220
f"got {tuple(target.shape)}."
188221
)
189222
return y_pred.reshape(-1, y_pred.size(-1)), target.reshape(-1).long()
190223

191224
# gaussian_nll
192-
if y_pred.ndim != 3 or y_pred.size(-1) != 2:
193-
raise ValueError(
194-
"GaussianNLLLoss expects predictions of shape "
195-
f"(batch, time, 2) as (mean, raw_variance); got {tuple(y_pred.shape)}."
196-
)
197-
if target.ndim != 2:
198-
raise ValueError(
199-
"GaussianNLL targets must have shape (batch, time), "
200-
f"got {tuple(target.shape)}."
201-
)
202-
return y_pred, target
225+
else:
226+
if y_pred.ndim != 3 or y_pred.size(-1) != 2:
227+
raise ValueError(
228+
"GaussianNLLLoss expects predictions of shape "
229+
"(batch, time, 2) as (mean, raw_variance), "
230+
f"got {tuple(y_pred.shape)}."
231+
)
232+
elif target.ndim != 2:
233+
raise ValueError(
234+
"GaussianNLL targets must have shape (batch, time), "
235+
f"got {tuple(target.shape)}."
236+
)
237+
return y_pred, target
203238

204239
def _call_loss(
205240
self,
@@ -233,7 +268,7 @@ def _call_loss(
233268
mean = y_pred[..., 0]
234269
var = F.softplus(y_pred[..., 1]) + 1e-6
235270
return self._loss(mean, target, var)
236-
if mode == "class" and isinstance(self._loss, nn.NLLLoss):
271+
elif mode == "class" and isinstance(self._loss, nn.NLLLoss):
237272
# NLLLoss expects log-probabilities
238273
return self._loss(F.log_softmax(y_pred, dim=-1), target)
239274
return self._loss(y_pred, target)
@@ -261,9 +296,9 @@ def to_prediction(self, y_pred: torch.Tensor, **kwargs) -> torch.Tensor:
261296
mode = self._mode
262297
if mode == "class" and y_pred.ndim == 3:
263298
return y_pred.argmax(dim=-1)
264-
if mode == "gaussian_nll" and y_pred.ndim == 3 and y_pred.size(-1) == 2:
299+
elif mode == "gaussian_nll" and y_pred.ndim == 3 and y_pred.size(-1) == 2:
265300
return y_pred[..., 0]
266-
if y_pred.ndim == 3 and y_pred.size(-1) == 1:
301+
elif y_pred.ndim == 3 and y_pred.size(-1) == 1:
267302
return y_pred.squeeze(-1)
268303
return y_pred
269304

0 commit comments

Comments
 (0)