Skip to content

Commit dbfd38b

Browse files
[BUG] Align TimeXer v2 endogenous/exogenous usage with tslib metadata (#2009)
## Summary This PR makes the TimeXer v2 implementation consistent with the v2 / `tslib` design by removing the duplicated configuration of endogenous and exogenous variables inside `TimeXer._forecast`. Instead of re-selecting series using `self.endogenous_vars` / `self.exogenous_vars` on top of the `tslib` metadata, the model now relies solely on the tensors provided by the data pipeline (`history_target` and `history_cont`). This implements **option 1** discussed in #2003 ("not overriding or passing twice"). Fixes #2003 ## Motivation / Context In v2, feature configuration is intended to be described by the `metadata` produced by `TslibDataModule` and consumed by `TslibBaseModel`. TimeXer v2 currently has: - feature names and indices described in `metadata` - *and* additional `endogenous_vars` / `exogenous_vars` kwargs that are used in `_forecast` to re-select columns from `history_cont` This leads to two different places where the endogenous / exogenous split can be defined, which is exactly the concern raised in #2003. The maintainers confirmed that option 1 is preferred: relying on the metadata / data pipeline only, i.e. not overriding or passing the configuration twice. ## What this PR changes In `pytorch_forecasting/models/timexer/_timexer_v2.py`: - `TimeXer._forecast` no longer uses `self.endogenous_vars` or `self.exogenous_vars` to re-select columns from `history_cont`. - Instead, the method now follows the v2 convention: - endogenous information is taken from `history_target` - exogenous information is taken from all continuous covariates in `history_cont` Concretely, the previous block: ```python # explicitly set endogenous and exogenous variables endogenous_cont = history_target if self.endogenous_vars: endogenous_indices = [ self.feature_names["continuous"].index(var) for var in self.endogenous_vars ] endogenous_cont = history_cont[..., endogenous_indices] exogenous_cont = history_cont if self.exogenous_vars: exogenous_indices = [ self.feature_names["continuous"].index(var) for var in self.exogenous_vars ] exogenous_cont = history_cont[..., exogenous_indices] ``` is replaced by: ```python # v2 convention: # - endogenous information comes from the target history # - exogenous information comes from all continuous covariates endogenous_cont = history_target exogenous_cont = history_cont ``` The rest of `_forecast` (embedding, encoder, head) remains unchanged. ### API / behaviour notes - The `endogenous_vars` and `exogenous_vars` arguments are still present in `TimeXer.__init__` and are stored on `self`, but they are no longer used in `_forecast`. - This keeps the public signature unchanged for now (no immediate breaking change), while removing the duplicated configuration path that conflicted with the v2 metadata design. - In practice, this means TimeXer v2 now always behaves as if the endogenous information is given by the target history and the exogenous information by the continuous covariates provided by `TslibDataModule`. - If desired, a follow-up PR can deprecate or remove these args entirely from the public API. ## Tests On Windows with Python 3.13, I ran: ```bash python -m pytest -k "TimeXer" -q --basetemp="C:\Projects\pytorch-forecasting\.pytest_tmp" ``` - 75 tests passed - 1 test skipped - 0 failures The failures reported earlier were due to a local Windows permission issue with the default pytest temp directory; pointing `--basetemp` at a project-local directory resolved that, and the TimeXer tests now run cleanly with the change in place. ## Notes - This PR is intentionally scoped to the functional change in `_forecast` only. - I am happy to follow up with a separate PR (or extend this one if preferred) to: - deprecate/remove the `endogenous_vars` / `exogenous_vars` kwargs from `TimeXer.__init__`, and - update the class docstring and docs accordingly.
1 parent 725e0c3 commit dbfd38b

File tree

2 files changed

+5
-30
lines changed

2 files changed

+5
-30
lines changed

pytorch_forecasting/models/timexer/_timexer_v2.py

Lines changed: 5 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -64,12 +64,6 @@ class TimeXer(TslibBaseModel):
6464
optimal backend (FlashAttention-2, Memory-Efficient Attention, or their
6565
own C++ implementation) based on user's input properties, hardware
6666
capabilities, and build configuration.
67-
endogenous_vars: Optional[list[str]], default=None
68-
List of endogenous variable names to be used in the model. If None, all historical values
69-
for the target variable are used.
70-
exogenous_vars: Optional[list[str]], default=None
71-
List of exogenous variable names to be used in the model. If None, all historical values
72-
for continuous variables are used.
7367
logging_metrics: Optional[list[nn.Module]], default=None
7468
List of metrics to log during training, validation, and testing.
7569
optimizer: Optional[Union[Optimizer, str]], default='adam'
@@ -83,7 +77,8 @@ class TimeXer(TslibBaseModel):
8377
metadata: Optional[dict], default=None
8478
Metadata for the model from TslibDataModule. This can include information about the dataset,
8579
such as the number of time steps, number of features, etc. It is used to initialize the model
86-
and ensure it is compatible with the data being used.
80+
and ensure it is compatible with the data being used, including the split between endogenous
81+
(target) and exogenous covariates.
8782
8883
References
8984
----------
@@ -118,8 +113,6 @@ def __init__(
118113
factor: int = 5,
119114
activation: str = "relu",
120115
use_efficient_attention: bool = False,
121-
endogenous_vars: Optional[list[str]] = None,
122-
exogenous_vars: Optional[list[str]] = None,
123116
logging_metrics: Optional[list[nn.Module]] = None,
124117
optimizer: Optional[Union[Optimizer, str]] = "adam",
125118
optimizer_params: Optional[dict] = None,
@@ -156,8 +149,6 @@ def __init__(
156149
self.activation = activation
157150
self.use_efficient_attention = use_efficient_attention
158151
self.factor = factor
159-
self.endogenous_vars = endogenous_vars
160-
self.exogenous_vars = exogenous_vars
161152
self.save_hyperparameters(ignore=["loss", "logging_metrics", "metadata"])
162153

163154
self._init_network()
@@ -292,22 +283,11 @@ def _forecast(self, x: dict[str, torch.Tensor]) -> dict[str, torch.Tensor]:
292283
# change [batch_size, time_steps] to [batch_size, time_steps, features]
293284
history_time_idx = history_time_idx.unsqueeze(-1)
294285

295-
# explicitly set endogenous and exogenous variables
286+
# v2 convention:
287+
# - endogenous information comes from the target history
288+
# - exogenous information comes from all continuous covariates
296289
endogenous_cont = history_target
297-
if self.endogenous_vars:
298-
endogenous_indices = [
299-
self.feature_names["continuous"].index(var)
300-
for var in self.endogenous_vars # noqa: E501
301-
]
302-
endogenous_cont = history_cont[..., endogenous_indices]
303-
304290
exogenous_cont = history_cont
305-
if self.exogenous_vars:
306-
exogenous_indices = [
307-
self.feature_names["continuous"].index(var)
308-
for var in self.exogenous_vars # noqa: E501
309-
]
310-
exogenous_cont = history_cont[..., exogenous_indices]
311291

312292
en_embed, n_vars = self.en_embedding(endogenous_cont)
313293
ex_embed = self.ex_embedding(exogenous_cont, history_time_idx)

tests/test_models/test_timexer_v2.py

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -335,15 +335,10 @@ def test_missing_history_target_handling(basic_metadata):
335335
def test_endogenous_exogenous_variable_selection(basic_metadata):
336336
"""Test explicit endogenous and exogenous variable selection in TimeXer model."""
337337

338-
endo_names = basic_metadata["feature_names"]["continuous"][0]
339-
exog_names = basic_metadata["feature_names"]["continuous"][1]
340-
341338
model = TimeXer(
342339
loss=MAE(),
343340
hidden_size=64,
344341
n_heads=8,
345-
endogenous_vars=[endo_names],
346-
exogenous_vars=[exog_names],
347342
e_layers=2,
348343
metadata=basic_metadata,
349344
)

0 commit comments

Comments
 (0)