Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/static covs #966

Merged
merged 29 commits into from
Jun 5, 2022
Merged
Show file tree
Hide file tree
Changes from 24 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
125c078
added methods ``from_longitudinal_dataframe` and `add_static_covariates`
dennisbader Apr 13, 2022
fde974e
dataset adaption for static covs
dennisbader Apr 23, 2022
d6d4885
extended datasets for static covariates support and unified variable …
dennisbader May 19, 2022
4717ff1
adapted PLXCovariatesModules with static covariates
dennisbader May 19, 2022
5b6b781
adapted TFTModel for static covariate support
dennisbader May 20, 2022
55eaf3b
added temporary fix for static covariates with scalers
dennisbader May 20, 2022
3ced3da
Merge branch 'master' into feat/static_covs
dennisbader May 20, 2022
29924f4
unittests for from_longitudinal_dataframe() and set_static_covariates
dennisbader May 24, 2022
079d969
updated dataset tests
dennisbader May 24, 2022
3511b81
fixed all downstream issues from new static covariates in datasets
dennisbader May 27, 2022
eacaf3b
added check for equal static covariates between fit and predict
dennisbader May 28, 2022
55c5090
added tests for passing static covariates in TimeSeries methods
dennisbader May 28, 2022
cc07f5f
added static covariate support for stacking TimeSeries
dennisbader May 28, 2022
0aacd5a
transpose static covariates
dennisbader May 29, 2022
2845f86
added method `static_covariates_values()`
dennisbader May 29, 2022
2ac58e4
updated docs
dennisbader May 29, 2022
a6fa4fb
static covariate support for concatenation
dennisbader May 30, 2022
a4ba617
static covariate support for concatenation
dennisbader May 30, 2022
0586b7d
static covariates are now passed to the torch models
dennisbader May 30, 2022
c18e806
non-numerical dtype support for static covariates
dennisbader May 31, 2022
a048ecc
added slicing support for static covariates
dennisbader May 31, 2022
3661385
multicomponent static covariate support for TFT
dennisbader May 31, 2022
5b9258b
Merge branch 'master' into feat/static_covs
dennisbader May 31, 2022
3a9ad83
added arithmetic static covariate support
dennisbader May 31, 2022
d00c08d
Merge branch 'master' into feat/static_covs
dennisbader Jun 3, 2022
f5fa989
updated all timeseries methods/operations with static cov transfer
dennisbader Jun 4, 2022
41adf3f
applied suggestion from PR review part 1
dennisbader Jun 4, 2022
6dc7ff8
apply suggestions from code review part 2
dennisbader Jun 4, 2022
d001e17
fix black issue from PR suggestion
dennisbader Jun 4, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
7 changes: 7 additions & 0 deletions darts/dataprocessing/transformers/boxcox.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,13 @@ def __init__(
For stochastic series, it is done jointly over all samples, effectively merging all samples of
a component in order to compute the transform.

Notes
-----
The scaler will not scale the series' static covariates. This has to be done either before constructing the
series, or later on by extracting the covariates, transforming the values and then reapplying them to the
series. For this, see Timeseries properties `TimeSeries.static_covariates` and method
dennisbader marked this conversation as resolved.
Show resolved Hide resolved
`TimeSeries.with_static_covariates()`

Parameters
----------
name
Expand Down
9 changes: 9 additions & 0 deletions darts/dataprocessing/transformers/scaler.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,13 @@ def __init__(
The transformation is applied independently for each dimension (component) of the time series,
effectively merging all samples of a component in order to compute the transform.

Notes
-----
The scaler will not scale the series' static covariates. This has to be done either before constructing the
series, or later on by extracting the covariates, transforming the values and then reapplying them to the
series. For this, see Timeseries properties `TimeSeries.static_covariates` and method
dennisbader marked this conversation as resolved.
Show resolved Hide resolved
`TimeSeries.with_static_covariates()`

Parameters
----------
scaler
Expand Down Expand Up @@ -106,6 +113,7 @@ def ts_transform(series: TimeSeries, transformer, **kwargs) -> TimeSeries:
values=transformed_vals,
fill_missing_dates=False,
columns=series.columns,
static_covariates=series.static_covariates,
)

@staticmethod
Expand All @@ -126,6 +134,7 @@ def ts_inverse_transform(
values=inv_transformed_vals,
fill_missing_dates=False,
columns=series.columns,
static_covariates=series.static_covariates,
)

@staticmethod
Expand Down
3 changes: 2 additions & 1 deletion darts/models/forecasting/block_rnn_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,8 @@ def __init__(
last = feature
self.fc = nn.Sequential(*feats)

def forward(self, x):
def forward(self, x_in: Tuple):
x, _ = x_in
# data is of size (batch_size, input_chunk_length, input_size)
batch_size = x.size(0)

Expand Down
3 changes: 2 additions & 1 deletion darts/models/forecasting/nbeats.py
Original file line number Diff line number Diff line change
Expand Up @@ -490,7 +490,8 @@ def __init__(
self.stacks_list[-1].blocks[-1].backcast_linear_layer.requires_grad_(False)
self.stacks_list[-1].blocks[-1].backcast_g.requires_grad_(False)

def forward(self, x):
def forward(self, x_in: Tuple):
x, _ = x_in

# if x1, x2,... y1, y2... is one multivariate ts containing x and y, and a1, a2... one covariate ts
# we reshape into x1, y1, a1, x2, y2, a2... etc
Expand Down
3 changes: 2 additions & 1 deletion darts/models/forecasting/nhits.py
Original file line number Diff line number Diff line change
Expand Up @@ -417,7 +417,8 @@ def __init__(
# on this params (the last block backcast is not part of the final output of the net).
self.stacks_list[-1].blocks[-1].backcast_linear_layer.requires_grad_(False)

def forward(self, x):
def forward(self, x_in: Tuple):
x, _ = x_in

# if x1, x2,... y1, y2... is one multivariate ts containing x and y, and a1, a2... one covariate ts
# we reshape into x1, y1, a1, x2, y2, a2... etc
Expand Down
80 changes: 45 additions & 35 deletions darts/models/forecasting/pl_forecasting_module.py
Original file line number Diff line number Diff line change
Expand Up @@ -300,7 +300,7 @@ def _sample_tiling(input_data_tuple, batch_sample_size):
def _is_probabilistic(self) -> bool:
return self.likelihood is not None

def _produce_predict_output(self, x):
def _produce_predict_output(self, x: Tuple):
dennisbader marked this conversation as resolved.
Show resolved Hide resolved
if self.likelihood:
output = self(x)
return self.likelihood.sample(output)
Expand Down Expand Up @@ -342,12 +342,22 @@ def epochs_trained(self):

class PLPastCovariatesModule(PLForecastingModule, ABC):
def _produce_train_output(self, input_batch: Tuple):
past_target, past_covariate = input_batch
"""
Feeds PastCovariatesTorchModel with input and output chunks of a PastCovariatesSequentialDataset for
training.

Parameters:
----------
input_batch
``(past_target, past_covariates, static_covariates)``
"""
past_target, past_covariates, static_covariates = input_batch
# Currently all our PastCovariates models require past target and covariates concatenated
inpt = (
torch.cat([past_target, past_covariate], dim=2)
if past_covariate is not None
else past_target
torch.cat([past_target, past_covariates], dim=2)
if past_covariates is not None
else past_target,
static_covariates,
)
return self(inpt)

Expand All @@ -363,13 +373,18 @@ def _get_batch_prediction(
n
prediction length
input_batch
(past_target, past_covariates, future_past_covariates)
``(past_target, past_covariates, future_past_covariates, static_covariates)``
roll_size
roll input arrays after every sequence by ``roll_size``. Initially, ``roll_size`` is equivalent to
``self.output_chunk_length``
"""
dim_component = 2
past_target, past_covariates, future_past_covariates = input_batch
(
past_target,
past_covariates,
future_past_covariates,
static_covariates,
) = input_batch

n_targets = past_target.shape[dim_component]
n_past_covs = (
Expand All @@ -381,7 +396,7 @@ def _get_batch_prediction(
dim=dim_component,
)

out = self._produce_predict_output(input_past)[
out = self._produce_predict_output((input_past, static_covariates))[
:, self.first_prediction_index :, :
]

Expand Down Expand Up @@ -430,7 +445,7 @@ def _get_batch_prediction(
] = future_past_covariates[:, left_past:right_past, :]

# take only last part of the output sequence where needed
out = self._produce_predict_output(input_past)[
out = self._produce_predict_output((input_past, static_covariates))[
:, self.first_prediction_index :, :
]
batch_prediction.append(out)
Expand Down Expand Up @@ -462,63 +477,56 @@ class PLMixedCovariatesModule(PLForecastingModule, ABC):
def _produce_train_output(
self, input_batch: Tuple
) -> Tuple[torch.Tensor, torch.Tensor]:
"""
Feeds MixedCovariatesTorchModel with input and output chunks of a MixedCovariatesSequentialDataset for
training.

Parameters:
----------
input_batch
``(past_target, past_covariates, historic_future_covariates, future_covariates, static_covariates)``.
"""
return self(self._process_input_batch(input_batch))

def _process_input_batch(
self, input_batch
) -> Tuple[torch.Tensor, Optional[torch.Tensor]]:
) -> Tuple[torch.Tensor, Optional[torch.Tensor], Optional[torch.Tensor]]:
"""
Converts output of MixedCovariatesDataset (training dataset) into an input/past- and
output/future chunk.

Parameters
----------
input_batch
``(past_target, past_covariates, historic_future_covariates, future_covariates)``.
``(past_target, past_covariates, historic_future_covariates, future_covariates, static_covariates)``.

Returns
-------
tuple
``(x_past, x_future)`` the input/past and output/future chunks.
``(x_past, x_future, x_static)`` the input/past and output/future chunks.
"""

(
past_target,
past_covariates,
historic_future_covariates,
future_covariates,
static_covariates,
) = input_batch
dim_variable = 2

# TODO: impelement static covariates
static_covariates = None

x_past = torch.cat(
[
tensor
for tensor in [
past_target,
past_covariates,
historic_future_covariates,
static_covariates,
]
if tensor is not None
],
dim=dim_variable,
)

x_future = None
if future_covariates is not None or static_covariates is not None:
x_future = torch.cat(
[
tensor
for tensor in [future_covariates, static_covariates]
if tensor is not None
],
dim=dim_variable,
)

return x_past, x_future
return x_past, future_covariates, static_covariates

def _get_batch_prediction(
self, n: int, input_batch: Tuple, roll_size: int
Expand All @@ -545,6 +553,7 @@ def _get_batch_prediction(
historic_future_covariates,
future_covariates,
future_past_covariates,
static_covariates,
) = input_batch

n_targets = past_target.shape[dim_component]
Expand All @@ -557,18 +566,19 @@ def _get_batch_prediction(
else 0
)

input_past, input_future = self._process_input_batch(
input_past, input_future, input_static = self._process_input_batch(
(
past_target,
past_covariates,
historic_future_covariates,
future_covariates[:, :roll_size, :]
if future_covariates is not None
else None,
static_covariates,
)
)

out = self._produce_predict_output(x=(input_past, input_future))[
out = self._produce_predict_output(x=(input_past, input_future, input_static))[
:, self.first_prediction_index :, :
]

Expand Down Expand Up @@ -636,9 +646,9 @@ def _get_batch_prediction(
input_future = future_covariates[:, left_future:right_future, :]

# take only last part of the output sequence where needed
out = self._produce_predict_output(x=(input_past, input_future))[
:, self.first_prediction_index :, :
]
out = self._produce_predict_output(
x=(input_past, input_future, input_static)
)[:, self.first_prediction_index :, :]

batch_prediction.append(out)
prediction_length += self.output_chunk_length
Expand Down
28 changes: 21 additions & 7 deletions darts/models/forecasting/rnn_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,8 @@ def __init__(
# The RNN module needs a linear layer V that transforms hidden states into outputs, individually
self.V = nn.Linear(hidden_dim, target_size * nr_params)

def forward(self, x, h=None):
def forward(self, x_in: Tuple, h=None):
x, _ = x_in
# data is of size (batch_size, input_length, input_size)
batch_size = x.shape[0]

Expand All @@ -103,17 +104,23 @@ def forward(self, x, h=None):
return predictions, last_hidden_state

def _produce_train_output(self, input_batch: Tuple):
past_target, historic_future_covariates, future_covariates = input_batch
(
past_target,
historic_future_covariates,
future_covariates,
static_covariates,
) = input_batch
# For the RNN we concatenate the past_target with the future_covariates
# (they have the same length because we enforce a Shift dataset for RNNs)
model_input = (
torch.cat([past_target, future_covariates], dim=2)
if future_covariates is not None
else past_target
else past_target,
static_covariates,
)
return self(model_input)[0]

def _produce_predict_output(self, x, last_hidden_state=None):
def _produce_predict_output(self, x: Tuple, last_hidden_state=None):
"""overwrite parent classes `_produce_predict_output` method"""
output, hidden = self(x, last_hidden_state)
if self.likelihood:
Expand All @@ -127,7 +134,12 @@ def _get_batch_prediction(
"""
This model is recurrent, so we have to write a specific way to obtain the time series forecasts of length n.
"""
past_target, historic_future_covariates, future_covariates = input_batch
(
past_target,
historic_future_covariates,
future_covariates,
static_covariates,
) = input_batch

if historic_future_covariates is not None:
# RNNs need as inputs (target[t] and covariates[t+1]) so here we shift the covariates
Expand All @@ -144,7 +156,9 @@ def _get_batch_prediction(
cov_future = None

batch_prediction = []
out, last_hidden_state = self._produce_predict_output(input_series)
out, last_hidden_state = self._produce_predict_output(
(input_series, static_covariates)
)
batch_prediction.append(out[:, -1:, :])
prediction_length = 1

Expand All @@ -165,7 +179,7 @@ def _get_batch_prediction(

# feed new input to model, including the last hidden state from the previous iteration
out, last_hidden_state = self._produce_predict_output(
new_input, last_hidden_state
(new_input, static_covariates), last_hidden_state
)

# append prediction to batch prediction array, increase counter
Expand Down
3 changes: 2 additions & 1 deletion darts/models/forecasting/tcn_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,8 @@ def __init__(
self.res_blocks_list.append(res_block)
self.res_blocks = nn.ModuleList(self.res_blocks_list)

def forward(self, x):
def forward(self, x_in: Tuple):
x, _ = x_in
# data is of size (batch_size, input_chunk_length, input_size)
batch_size = x.size(0)
x = x.transpose(1, 2)
Expand Down