Skip to content

Commit 456d495

Browse files
[Minor] Torchify timenet (#1620)
* Convert to tensors * clarify ID drop * fixed tests * added vectorization * added sequential components * fixed linters * fixed cml plotting * added newlines to CML markdowns * fixed newlines rendering --------- Co-authored-by: ourownstory <ourownstory@users.noreply.github.com>
1 parent 4cf7444 commit 456d495

2 files changed

Lines changed: 94 additions & 77 deletions

File tree

.github/workflows/metrics.yml

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ on:
1111
- main
1212
- develop
1313
workflow_dispatch:
14+
1415
jobs:
1516
metrics:
1617
runs-on: ubuntu-latest # container: docker://ghcr.io/iterative/cml:0-dvc2-base1
@@ -19,24 +20,32 @@ jobs:
1920
uses: actions/checkout@v3
2021
with:
2122
ref: ${{ github.event.pull_request.head.sha }}
23+
2224
- name: Install Python 3.12
2325
uses: actions/setup-python@v5
2426
with:
2527
python-version: "3.12"
28+
2629
- name: Setup NodeJS (for CML)
2730
uses: actions/setup-node@v3 # For CML
2831
with:
2932
node-version: '16'
33+
3034
- name: Setup CML
3135
uses: iterative/setup-cml@v1
36+
3237
- name: Install Poetry
3338
uses: snok/install-poetry@v1
39+
3440
- name: Install Dependencies
3541
run: poetry install --no-interaction --no-root --with=pytest,metrics --without=dev,docs,linters
42+
3643
- name: Install Project
3744
run: poetry install --no-interaction --with=pytest,metrics --without=dev,docs,linters
45+
3846
- name: Train model
3947
run: poetry run pytest tests/test_model_performance.py -n 1 --durations=0
48+
4049
- name: Download metrics from main
4150
uses: dawidd6/action-download-artifact@v2
4251
with:
@@ -45,28 +54,40 @@ jobs:
4554
name: metrics
4655
path: tests/metrics-main/
4756
if_no_artifact_found: warn
57+
4858
- name: Open Benchmark Report
4959
run: echo "## Model Benchmark" >> report.md
60+
5061
- name: Write Benchmark Report
5162
run: poetry run python tests/metrics/compareMetrics.py >> report.md
63+
5264
- name: Publish Report with CML
5365
env:
5466
REPO_TOKEN: ${{ secrets.GITHUB_TOKEN }}
5567
run: |
56-
echo "<details>\n<summary>Model training plots</summary>\n" >> report.md
68+
echo "<details><summary>Model training plots</summary>" >> report.md
69+
echo "" >> report.md
5770
echo "## Model Training" >> report.md
71+
echo "" >> report.md
5872
echo "### PeytonManning" >> report.md
5973
cml asset publish tests/metrics/PeytonManning.svg --md >> report.md
74+
echo "" >> report.md
6075
echo "### YosemiteTemps" >> report.md
6176
cml asset publish tests/metrics/YosemiteTemps.svg --md >> report.md
77+
echo "" >> report.md
6278
echo "### AirPassengers" >> report.md
6379
cml asset publish tests/metrics/AirPassengers.svg --md >> report.md
80+
echo "" >> report.md
6481
echo "### EnergyPriceDaily" >> report.md
6582
cml asset publish tests/metrics/EnergyPriceDaily.svg --md >> report.md
66-
echo "\n</details>" >> report.md
83+
echo "" >> report.md
84+
echo "</details>" >> report.md
85+
echo "" >> report.md
6786
cml comment update --target=pr report.md # Post reports as comments in GitHub PRs
6887
cml check create --title=ModelReport report.md # update status of check in PR
88+
6989
- name: Upload metrics if on main
90+
if: github.ref == 'refs/heads/main'
7091
uses: actions/upload-artifact@v3
7192
with:
7293
name: metrics

neuralprophet/time_net.py

Lines changed: 71 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -268,28 +268,34 @@ def __init__(
268268
self.ar_layers = ar_layers
269269
self.max_lags = max_lags
270270
if self.n_lags > 0:
271-
self.ar_net = nn.ModuleList()
271+
ar_net_layers = []
272272
d_inputs = self.n_lags
273273
for d_hidden_i in self.ar_layers:
274-
self.ar_net.append(nn.Linear(d_inputs, d_hidden_i, bias=True))
274+
ar_net_layers.append(nn.Linear(d_inputs, d_hidden_i, bias=True))
275+
ar_net_layers.append(nn.ReLU())
275276
d_inputs = d_hidden_i
276277
# final layer has input size d_inputs and output size equal to no. of forecasts * no. of quantiles
277-
self.ar_net.append(nn.Linear(d_inputs, self.n_forecasts * len(self.quantiles), bias=False))
278+
ar_net_layers.append(nn.Linear(d_inputs, self.n_forecasts * len(self.quantiles), bias=False))
279+
self.ar_net = nn.Sequential(*ar_net_layers)
278280
for lay in self.ar_net:
279-
nn.init.kaiming_normal_(lay.weight, mode="fan_in")
281+
if isinstance(lay, nn.Linear):
282+
nn.init.kaiming_normal_(lay.weight, mode="fan_in")
280283

281284
# Lagged regressors
282285
self.lagged_reg_layers = lagged_reg_layers
283286
self.config_lagged_regressors = config_lagged_regressors
284287
if self.config_lagged_regressors is not None:
285-
self.covar_net = nn.ModuleList()
288+
covar_net_layers = []
286289
d_inputs = sum([covar.n_lags for _, covar in self.config_lagged_regressors.items()])
287290
for d_hidden_i in self.lagged_reg_layers:
288-
self.covar_net.append(nn.Linear(d_inputs, d_hidden_i, bias=True))
291+
covar_net_layers.append(nn.Linear(d_inputs, d_hidden_i, bias=True))
292+
covar_net_layers.append(nn.ReLU())
289293
d_inputs = d_hidden_i
290-
self.covar_net.append(nn.Linear(d_inputs, self.n_forecasts * len(self.quantiles), bias=False))
294+
covar_net_layers.append(nn.Linear(d_inputs, self.n_forecasts * len(self.quantiles), bias=False))
295+
self.covar_net = nn.Sequential(*covar_net_layers)
291296
for lay in self.covar_net:
292-
nn.init.kaiming_normal_(lay.weight, mode="fan_in")
297+
if isinstance(lay, nn.Linear):
298+
nn.init.kaiming_normal_(lay.weight, mode="fan_in")
293299

294300
# Regressors
295301
self.config_regressors = config_regressors
@@ -310,7 +316,9 @@ def __init__(
310316
def ar_weights(self) -> torch.Tensor:
311317
"""sets property auto-regression weights for regularization. Update if AR is modelled differently"""
312318
# TODO: this is wrong for deep networks, use utils_torch.interprete_model
313-
return self.ar_net[0].weight
319+
for layer in self.ar_net:
320+
if isinstance(layer, nn.Linear):
321+
return layer.weight
314322

315323
def get_covar_weights(self, covar_input=None) -> torch.Tensor:
316324
"""
@@ -393,49 +401,50 @@ def _compute_quantile_forecasts_from_diffs(self, diffs: torch.Tensor, predict_mo
393401
dim (batch, n_forecasts, no_quantiles)
394402
final forecasts
395403
"""
396-
if len(self.quantiles) > 1:
397-
# generate the actual quantile forecasts from predicted differences
398-
if any(quantile > 0.5 for quantile in self.quantiles):
399-
quantiles_divider_index = next(i for i, quantile in enumerate(self.quantiles) if quantile > 0.5)
400-
else:
401-
quantiles_divider_index = len(self.quantiles)
402-
403-
n_upper_quantiles = diffs.shape[-1] - quantiles_divider_index
404-
n_lower_quantiles = quantiles_divider_index - 1
405-
406-
out = torch.zeros_like(diffs)
407-
out[:, :, 0] = diffs[:, :, 0] # set the median where 0 is the median quantile index
408-
409-
if n_upper_quantiles > 0: # check if upper quantiles exist
410-
upper_quantile_diffs = diffs[:, :, quantiles_divider_index:]
411-
if predict_mode: # check for quantile crossing and correct them in predict mode
412-
upper_quantile_diffs[:, :, 0] = torch.max(
413-
torch.tensor(0, device=self.device), upper_quantile_diffs[:, :, 0]
414-
)
415-
for i in range(n_upper_quantiles - 1):
416-
next_diff = upper_quantile_diffs[:, :, i + 1]
417-
diff = upper_quantile_diffs[:, :, i]
418-
upper_quantile_diffs[:, :, i + 1] = torch.max(next_diff, diff)
419-
out[:, :, quantiles_divider_index:] = (
420-
upper_quantile_diffs + diffs[:, :, 0].unsqueeze(dim=2).repeat(1, 1, n_upper_quantiles).detach()
421-
) # set the upper quantiles
422-
423-
if n_lower_quantiles > 0: # check if lower quantiles exist
424-
lower_quantile_diffs = diffs[:, :, 1:quantiles_divider_index]
425-
if predict_mode: # check for quantile crossing and correct them in predict mode
426-
lower_quantile_diffs[:, :, -1] = torch.max(
427-
torch.tensor(0, device=self.device), lower_quantile_diffs[:, :, -1]
428-
)
429-
for i in range(n_lower_quantiles - 1, 0, -1):
430-
next_diff = lower_quantile_diffs[:, :, i - 1]
431-
diff = lower_quantile_diffs[:, :, i]
432-
lower_quantile_diffs[:, :, i - 1] = torch.max(next_diff, diff)
433-
lower_quantile_diffs = -lower_quantile_diffs
434-
out[:, :, 1:quantiles_divider_index] = (
435-
lower_quantile_diffs + diffs[:, :, 0].unsqueeze(dim=2).repeat(1, 1, n_lower_quantiles).detach()
436-
) # set the lower quantiles
404+
405+
if len(self.quantiles) <= 1:
406+
return diffs
407+
# generate the actual quantile forecasts from predicted differences
408+
if any(quantile > 0.5 for quantile in self.quantiles):
409+
quantiles_divider_index = next(i for i, quantile in enumerate(self.quantiles) if quantile > 0.5)
437410
else:
438-
out = diffs
411+
quantiles_divider_index = len(self.quantiles)
412+
413+
n_upper_quantiles = diffs.shape[-1] - quantiles_divider_index
414+
n_lower_quantiles = quantiles_divider_index - 1
415+
416+
out = torch.zeros_like(diffs)
417+
out[:, :, 0] = diffs[:, :, 0] # set the median where 0 is the median quantile index
418+
419+
if n_upper_quantiles > 0: # check if upper quantiles exist
420+
upper_quantile_diffs = diffs[:, :, quantiles_divider_index:]
421+
if predict_mode: # check for quantile crossing and correct them in predict mode
422+
upper_quantile_diffs[:, :, 0] = torch.max(
423+
torch.tensor(0, device=self.device), upper_quantile_diffs[:, :, 0]
424+
)
425+
for i in range(n_upper_quantiles - 1):
426+
next_diff = upper_quantile_diffs[:, :, i + 1]
427+
diff = upper_quantile_diffs[:, :, i]
428+
upper_quantile_diffs[:, :, i + 1] = torch.max(next_diff, diff)
429+
out[:, :, quantiles_divider_index:] = (
430+
upper_quantile_diffs + diffs[:, :, 0].unsqueeze(dim=2).repeat(1, 1, n_upper_quantiles).detach()
431+
) # set the upper quantiles
432+
433+
if n_lower_quantiles > 0: # check if lower quantiles exist
434+
lower_quantile_diffs = diffs[:, :, 1:quantiles_divider_index]
435+
if predict_mode: # check for quantile crossing and correct them in predict mode
436+
lower_quantile_diffs[:, :, -1] = torch.max(
437+
torch.tensor(0, device=self.device), lower_quantile_diffs[:, :, -1]
438+
)
439+
for i in range(n_lower_quantiles - 1, 0, -1):
440+
next_diff = lower_quantile_diffs[:, :, i - 1]
441+
diff = lower_quantile_diffs[:, :, i]
442+
lower_quantile_diffs[:, :, i - 1] = torch.max(next_diff, diff)
443+
lower_quantile_diffs = -lower_quantile_diffs
444+
out[:, :, 1:quantiles_divider_index] = (
445+
lower_quantile_diffs + diffs[:, :, 0].unsqueeze(dim=2).repeat(1, 1, n_lower_quantiles).detach()
446+
) # set the lower quantiles
447+
439448
return out
440449

441450
def scalar_features_effects(self, features: torch.Tensor, params: nn.Parameter, indices=None) -> torch.Tensor:
@@ -474,14 +483,9 @@ def auto_regression(self, lags: Union[torch.Tensor, float]) -> torch.Tensor:
474483
torch.Tensor
475484
Forecast component of dims: (batch, n_forecasts)
476485
"""
477-
x = lags
478-
for i in range(len(self.ar_layers) + 1):
479-
if i > 0:
480-
x = nn.functional.relu(x)
481-
x = self.ar_net[i](x)
482-
486+
x = self.ar_net(lags)
483487
# segment the last dimension to match the quantiles
484-
x = x.reshape(x.shape[0], self.n_forecasts, len(self.quantiles))
488+
x = x.view(x.shape[0], self.n_forecasts, len(self.quantiles))
485489
return x
486490

487491
def forward_covar_net(self, covariates):
@@ -501,13 +505,9 @@ def forward_covar_net(self, covariates):
501505
x = torch.cat([covar for _, covar in covariates.items()], axis=1)
502506
else:
503507
x = covariates
504-
for i in range(len(self.lagged_reg_layers) + 1):
505-
if i > 0:
506-
x = nn.functional.relu(x)
507-
x = self.covar_net[i](x)
508-
508+
x = self.covar_net(x)
509509
# segment the last dimension to match the quantiles
510-
x = x.reshape(x.shape[0], self.n_forecasts, len(self.quantiles))
510+
x = x.view(x.shape[0], self.n_forecasts, len(self.quantiles))
511511
return x
512512

513513
def forward(self, inputs: Dict, meta: Dict = None, compute_components_flag: bool = False) -> torch.Tensor:
@@ -880,8 +880,7 @@ def _get_time_based_sample_weight(self, t):
880880
end_w = self.config_train.newer_samples_weight
881881
start_t = self.config_train.newer_samples_start
882882
time = (t.detach() - start_t) / (1.0 - start_t)
883-
time = torch.maximum(torch.zeros_like(time), time)
884-
time = torch.minimum(torch.ones_like(time), time) # time = 0 to 1
883+
time = torch.clamp(time, 0.0, 1.0) # time = 0 to 1
885884
time = np.pi * (time - 1.0) # time = -pi to 0
886885
time = 0.5 * torch.cos(time) + 0.5 # time = 0 to 1
887886
# scales end to be end weight times bigger than start weight
@@ -1019,24 +1018,21 @@ class DeepNet(nn.Module):
10191018
def __init__(self, d_inputs, d_outputs, lagged_reg_layers=[]):
10201019
# Perform initialization of the pytorch superclass
10211020
super(DeepNet, self).__init__()
1022-
self.layers = nn.ModuleList()
1021+
layers = []
10231022
for d_hidden_i in lagged_reg_layers:
1024-
self.layers.append(nn.Linear(d_inputs, d_hidden_i, bias=True))
1023+
layers.append(nn.Linear(d_inputs, d_hidden_i, bias=True))
1024+
layers.append(nn.ReLU())
10251025
d_inputs = d_hidden_i
1026-
self.layers.append(nn.Linear(d_inputs, d_outputs, bias=True))
1026+
layers.append(nn.Linear(d_inputs, d_outputs, bias=True))
1027+
self.layers = nn.Sequential(*layers)
10271028
for lay in self.layers:
10281029
nn.init.kaiming_normal_(lay.weight, mode="fan_in")
10291030

10301031
def forward(self, x):
10311032
"""
10321033
This method defines the network layering and activation functions
10331034
"""
1034-
activation = nn.functional.relu
1035-
for i in range(len(self.layers)):
1036-
if i > 0:
1037-
x = activation(x)
1038-
x = self.layers[i](x)
1039-
return x
1035+
return self.layers(x)
10401036

10411037
@property
10421038
def ar_weights(self):

0 commit comments

Comments
 (0)