PVNet 2.6.10__tar.gz → 2.6.11__tar.gz

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.
Files changed (53) hide show
  1. {PVNet-2.6.10 → PVNet-2.6.11}/PKG-INFO +1 -1
  2. {PVNet-2.6.10 → PVNet-2.6.11}/PVNet.egg-info/PKG-INFO +1 -1
  3. {PVNet-2.6.10 → PVNet-2.6.11}/PVNet.egg-info/SOURCES.txt +2 -0
  4. PVNet-2.6.11/pvnet/__init__.py +2 -0
  5. PVNet-2.6.10/pvnet/data/datamodule.py → PVNet-2.6.11/pvnet/data/base.py +1 -1
  6. PVNet-2.6.11/pvnet/data/datamodule.py +92 -0
  7. PVNet-2.6.11/pvnet/data/pv_site_datamodule.py +98 -0
  8. PVNet-2.6.11/pvnet/data/wind_datamodule.py +99 -0
  9. {PVNet-2.6.10 → PVNet-2.6.11}/pvnet/models/base_model.py +92 -24
  10. {PVNet-2.6.10 → PVNet-2.6.11}/pvnet/models/baseline/last_value.py +2 -2
  11. {PVNet-2.6.10 → PVNet-2.6.11}/pvnet/models/baseline/single_value.py +1 -1
  12. {PVNet-2.6.10 → PVNet-2.6.11}/pvnet/models/multimodal/deep_supervision.py +7 -7
  13. {PVNet-2.6.10 → PVNet-2.6.11}/pvnet/models/multimodal/encoders/encoders3d.py +24 -7
  14. {PVNet-2.6.10 → PVNet-2.6.11}/pvnet/models/multimodal/multimodal.py +101 -17
  15. {PVNet-2.6.10 → PVNet-2.6.11}/pvnet/models/multimodal/nwp_weighting.py +1 -1
  16. {PVNet-2.6.10 → PVNet-2.6.11}/pvnet/models/multimodal/site_encoders/encoders.py +48 -34
  17. {PVNet-2.6.10 → PVNet-2.6.11}/pvnet/models/multimodal/weather_residual.py +8 -8
  18. {PVNet-2.6.10 → PVNet-2.6.11}/pvnet/models/utils.py +13 -5
  19. {PVNet-2.6.10 → PVNet-2.6.11}/pvnet/utils.py +21 -9
  20. {PVNet-2.6.10 → PVNet-2.6.11}/tests/conftest.py +1 -1
  21. PVNet-2.6.10/pvnet/__init__.py +0 -2
  22. PVNet-2.6.10/pvnet/data/wind_datamodule.py +0 -141
  23. {PVNet-2.6.10 → PVNet-2.6.11}/LICENSE +0 -0
  24. {PVNet-2.6.10 → PVNet-2.6.11}/MANIFEST.in +0 -0
  25. {PVNet-2.6.10 → PVNet-2.6.11}/PVNet.egg-info/dependency_links.txt +0 -0
  26. {PVNet-2.6.10 → PVNet-2.6.11}/PVNet.egg-info/requires.txt +0 -0
  27. {PVNet-2.6.10 → PVNet-2.6.11}/PVNet.egg-info/top_level.txt +0 -0
  28. {PVNet-2.6.10 → PVNet-2.6.11}/README.md +0 -0
  29. {PVNet-2.6.10 → PVNet-2.6.11}/pvnet/callbacks.py +0 -0
  30. {PVNet-2.6.10 → PVNet-2.6.11}/pvnet/data/__init__.py +0 -0
  31. {PVNet-2.6.10 → PVNet-2.6.11}/pvnet/data/utils.py +0 -0
  32. {PVNet-2.6.10 → PVNet-2.6.11}/pvnet/models/__init__.py +0 -0
  33. {PVNet-2.6.10 → PVNet-2.6.11}/pvnet/models/baseline/__init__.py +0 -0
  34. {PVNet-2.6.10 → PVNet-2.6.11}/pvnet/models/multimodal/__init__.py +0 -0
  35. {PVNet-2.6.10 → PVNet-2.6.11}/pvnet/models/multimodal/basic_blocks.py +0 -0
  36. {PVNet-2.6.10 → PVNet-2.6.11}/pvnet/models/multimodal/encoders/__init__.py +0 -0
  37. {PVNet-2.6.10 → PVNet-2.6.11}/pvnet/models/multimodal/encoders/basic_blocks.py +0 -0
  38. {PVNet-2.6.10 → PVNet-2.6.11}/pvnet/models/multimodal/encoders/encoders2d.py +0 -0
  39. {PVNet-2.6.10 → PVNet-2.6.11}/pvnet/models/multimodal/encoders/encodersRNN.py +0 -0
  40. {PVNet-2.6.10 → PVNet-2.6.11}/pvnet/models/multimodal/linear_networks/__init__.py +0 -0
  41. {PVNet-2.6.10 → PVNet-2.6.11}/pvnet/models/multimodal/linear_networks/basic_blocks.py +0 -0
  42. {PVNet-2.6.10 → PVNet-2.6.11}/pvnet/models/multimodal/linear_networks/networks.py +0 -0
  43. {PVNet-2.6.10 → PVNet-2.6.11}/pvnet/models/multimodal/site_encoders/__init__.py +0 -0
  44. {PVNet-2.6.10 → PVNet-2.6.11}/pvnet/models/multimodal/site_encoders/basic_blocks.py +0 -0
  45. {PVNet-2.6.10 → PVNet-2.6.11}/pvnet/optimizers.py +0 -0
  46. {PVNet-2.6.10 → PVNet-2.6.11}/pvnet/training.py +0 -0
  47. {PVNet-2.6.10 → PVNet-2.6.11}/pyproject.toml +0 -0
  48. {PVNet-2.6.10 → PVNet-2.6.11}/requirements-dev.txt +0 -0
  49. {PVNet-2.6.10 → PVNet-2.6.11}/requirements.txt +0 -0
  50. {PVNet-2.6.10 → PVNet-2.6.11}/setup.cfg +0 -0
  51. {PVNet-2.6.10 → PVNet-2.6.11}/setup.py +0 -0
  52. {PVNet-2.6.10 → PVNet-2.6.11}/tests/__init__.py +0 -0
  53. {PVNet-2.6.10 → PVNet-2.6.11}/tests/test_end2end.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: PVNet
3
- Version: 2.6.10
3
+ Version: 2.6.11
4
4
  Summary: PVNet
5
5
  Author: Peter Dudfield
6
6
  Author-email: info@openclimatefix.org
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: PVNet
3
- Version: 2.6.10
3
+ Version: 2.6.11
4
4
  Summary: PVNet
5
5
  Author: Peter Dudfield
6
6
  Author-email: info@openclimatefix.org
@@ -16,7 +16,9 @@ pvnet/optimizers.py
16
16
  pvnet/training.py
17
17
  pvnet/utils.py
18
18
  pvnet/data/__init__.py
19
+ pvnet/data/base.py
19
20
  pvnet/data/datamodule.py
21
+ pvnet/data/pv_site_datamodule.py
20
22
  pvnet/data/utils.py
21
23
  pvnet/data/wind_datamodule.py
22
24
  pvnet/models/__init__.py
@@ -0,0 +1,2 @@
1
+ """PVNet"""
2
+ __version__ = "2.6.11"
@@ -11,7 +11,7 @@ from torch.utils.data.datapipes.iter import FileLister
11
11
  from pvnet.data.utils import batch_to_tensor
12
12
 
13
13
 
14
- class DataModule(LightningDataModule):
14
+ class BaseDataModule(LightningDataModule):
15
15
  """Datamodule for training pvnet and using pvnet pipeline in `ocf_datapipes`."""
16
16
 
17
17
  def __init__(
@@ -0,0 +1,92 @@
1
+ """ Data module for pytorch lightning """
2
+
3
+ import torch
4
+ from ocf_datapipes.batch import stack_np_examples_into_batch
5
+ from ocf_datapipes.training.pvnet import pvnet_datapipe
6
+ from torch.utils.data.datapipes.iter import FileLister
7
+
8
+ from pvnet.data.base import BaseDataModule
9
+ from pvnet.data.utils import batch_to_tensor
10
+
11
+
12
+ class DataModule(BaseDataModule):
13
+ """Datamodule for training pvnet and using pvnet pipeline in `ocf_datapipes`."""
14
+
15
+ def __init__(
16
+ self,
17
+ configuration=None,
18
+ batch_size=16,
19
+ num_workers=0,
20
+ prefetch_factor=None,
21
+ train_period=[None, None],
22
+ val_period=[None, None],
23
+ test_period=[None, None],
24
+ batch_dir=None,
25
+ ):
26
+ """Datamodule for training pvnet and using pvnet pipeline in `ocf_datapipes`.
27
+
28
+ Can also be used with pre-made batches if `batch_dir` is set.
29
+
30
+
31
+ Args:
32
+ configuration: Path to datapipe configuration file.
33
+ batch_size: Batch size.
34
+ num_workers: Number of workers to use in multiprocess batch loading.
35
+ prefetch_factor: Number of data will be prefetched at the end of each worker process.
36
+ train_period: Date range filter for train dataloader.
37
+ val_period: Date range filter for val dataloader.
38
+ test_period: Date range filter for test dataloader.
39
+ batch_dir: Path to the directory of pre-saved batches. Cannot be used together with
40
+ `configuration` or 'train/val/test_period'.
41
+
42
+ """
43
+ super().__init__(
44
+ configuration=configuration,
45
+ batch_size=batch_size,
46
+ num_workers=num_workers,
47
+ prefetch_factor=prefetch_factor,
48
+ train_period=train_period,
49
+ val_period=val_period,
50
+ test_period=test_period,
51
+ batch_dir=batch_dir,
52
+ )
53
+
54
+ def _get_datapipe(self, start_time, end_time):
55
+ data_pipeline = pvnet_datapipe(
56
+ self.configuration,
57
+ start_time=start_time,
58
+ end_time=end_time,
59
+ )
60
+
61
+ data_pipeline = (
62
+ data_pipeline.batch(self.batch_size)
63
+ .map(stack_np_examples_into_batch)
64
+ .map(batch_to_tensor)
65
+ )
66
+ return data_pipeline
67
+
68
+ def _get_premade_batches_datapipe(self, subdir, shuffle=False):
69
+ data_pipeline = FileLister(f"{self.batch_dir}/{subdir}", masks="*.pt", recursive=False)
70
+ if shuffle:
71
+ data_pipeline = (
72
+ data_pipeline.shuffle(buffer_size=10_000)
73
+ .sharding_filter()
74
+ .map(torch.load)
75
+ # Split the batches and reshuffle them to be combined into new batches
76
+ .split_batches()
77
+ .shuffle(buffer_size=100 * self.batch_size)
78
+ )
79
+ else:
80
+ data_pipeline = (
81
+ data_pipeline.sharding_filter().map(torch.load)
82
+ # Split the batches so we can use any batch-size
83
+ .split_batches()
84
+ )
85
+
86
+ data_pipeline = (
87
+ data_pipeline.batch(self.batch_size)
88
+ .map(stack_np_examples_into_batch)
89
+ .map(batch_to_tensor)
90
+ )
91
+
92
+ return data_pipeline
@@ -0,0 +1,98 @@
1
+ """ Data module for pytorch lightning """
2
+ import glob
3
+
4
+ from ocf_datapipes.batch import BatchKey, stack_np_examples_into_batch
5
+ from ocf_datapipes.training.pvnet_site import pvnet_site_netcdf_datapipe
6
+
7
+ from pvnet.data.base import BaseDataModule
8
+ from pvnet.data.utils import batch_to_tensor
9
+
10
+
11
+ class PVSiteDataModule(BaseDataModule):
12
+ """Datamodule for training pvnet site and using pvnet site pipeline in `ocf_datapipes`."""
13
+
14
+ def __init__(
15
+ self,
16
+ configuration=None,
17
+ batch_size=16,
18
+ num_workers=0,
19
+ prefetch_factor=None,
20
+ train_period=[None, None],
21
+ val_period=[None, None],
22
+ test_period=[None, None],
23
+ batch_dir=None,
24
+ ):
25
+ """Datamodule for training pvnet stie and using pvnet site pipeline in `ocf_datapipes`.
26
+
27
+ Can also be used with pre-made batches if `batch_dir` is set.
28
+
29
+
30
+ Args:
31
+ configuration: Path to datapipe configuration file.
32
+ batch_size: Batch size.
33
+ num_workers: Number of workers to use in multiprocess batch loading.
34
+ train_period: Date range filter for train dataloader.
35
+ val_period: Date range filter for val dataloader.
36
+ test_period: Date range filter for test dataloader.
37
+ prefetch_factor: Number of data will be prefetched at the end of each worker process.
38
+ batch_dir: Path to the directory of pre-saved batches. Cannot be used together with
39
+ 'train/val/test_period'.
40
+
41
+ """
42
+ super().__init__(
43
+ configuration=configuration,
44
+ batch_size=batch_size,
45
+ num_workers=num_workers,
46
+ prefetch_factor=prefetch_factor,
47
+ train_period=train_period,
48
+ val_period=val_period,
49
+ test_period=test_period,
50
+ batch_dir=batch_dir,
51
+ )
52
+
53
+ def _get_datapipe(self, start_time, end_time):
54
+ data_pipeline = pvnet_site_netcdf_datapipe(
55
+ keys=["pv", "nwp"],
56
+ )
57
+
58
+ data_pipeline = (
59
+ data_pipeline.batch(self.batch_size)
60
+ .map(stack_np_examples_into_batch)
61
+ .map(batch_to_tensor)
62
+ )
63
+ return data_pipeline
64
+
65
+ def _get_premade_batches_datapipe(self, subdir, shuffle=False):
66
+ filenames = list(glob.glob(f"{self.batch_dir}/{subdir}/*.nc"))
67
+ data_pipeline = pvnet_site_netcdf_datapipe(
68
+ keys=["pv", "nwp"],
69
+ filenames=filenames,
70
+ )
71
+ data_pipeline = (
72
+ data_pipeline.batch(self.batch_size)
73
+ .map(stack_np_examples_into_batch)
74
+ .map(batch_to_tensor)
75
+ )
76
+ if shuffle:
77
+ data_pipeline = (
78
+ data_pipeline.shuffle(buffer_size=100)
79
+ .sharding_filter()
80
+ # Split the batches and reshuffle them to be combined into new batches
81
+ .split_batches(splitting_key=BatchKey.pv)
82
+ .shuffle(buffer_size=100 * self.batch_size)
83
+ )
84
+ else:
85
+ data_pipeline = (
86
+ data_pipeline.sharding_filter()
87
+ # Split the batches so we can use any batch-size
88
+ .split_batches(splitting_key=BatchKey.pv)
89
+ )
90
+
91
+ data_pipeline = (
92
+ data_pipeline.batch(self.batch_size)
93
+ .map(stack_np_examples_into_batch)
94
+ .map(batch_to_tensor)
95
+ .set_length(int(len(filenames) / self.batch_size))
96
+ )
97
+
98
+ return data_pipeline
@@ -0,0 +1,99 @@
1
+ """ Data module for pytorch lightning """
2
+ import glob
3
+
4
+ from ocf_datapipes.batch import BatchKey, stack_np_examples_into_batch
5
+ from ocf_datapipes.training.windnet import windnet_netcdf_datapipe
6
+
7
+ from pvnet.data.base import BaseDataModule
8
+ from pvnet.data.utils import batch_to_tensor
9
+
10
+
11
+ class WindDataModule(BaseDataModule):
12
+ """Datamodule for training windnet and using windnet pipeline in `ocf_datapipes`."""
13
+
14
+ def __init__(
15
+ self,
16
+ configuration=None,
17
+ batch_size=16,
18
+ num_workers=0,
19
+ prefetch_factor=None,
20
+ train_period=[None, None],
21
+ val_period=[None, None],
22
+ test_period=[None, None],
23
+ batch_dir=None,
24
+ ):
25
+ """Datamodule for training windnet and using windnet pipeline in `ocf_datapipes`.
26
+
27
+ Can also be used with pre-made batches if `batch_dir` is set.
28
+
29
+
30
+ Args:
31
+ configuration: Path to datapipe configuration file.
32
+ batch_size: Batch size.
33
+ train_period: Date range filter for train dataloader.
34
+ val_period: Date range filter for val dataloader.
35
+ test_period: Date range filter for test dataloader.
36
+ num_workers: Number of workers to use in multiprocess batch loading.
37
+ prefetch_factor: Number of data will be prefetched at the end of each worker process.
38
+ batch_dir: Path to the directory of pre-saved batches. Cannot be used together with
39
+ 'train/val/test_period'.
40
+
41
+ """
42
+ super().__init__(
43
+ configuration=configuration,
44
+ batch_size=batch_size,
45
+ num_workers=num_workers,
46
+ prefetch_factor=prefetch_factor,
47
+ train_period=train_period,
48
+ val_period=val_period,
49
+ test_period=test_period,
50
+ batch_dir=batch_dir,
51
+ )
52
+
53
+ def _get_datapipe(self, start_time, end_time):
54
+ data_pipeline = windnet_netcdf_datapipe(
55
+ self.configuration,
56
+ keys=["wind", "nwp"],
57
+ )
58
+
59
+ data_pipeline = (
60
+ data_pipeline.batch(self.batch_size)
61
+ .map(stack_np_examples_into_batch)
62
+ .map(batch_to_tensor)
63
+ )
64
+ return data_pipeline
65
+
66
+ def _get_premade_batches_datapipe(self, subdir, shuffle=False):
67
+ filenames = list(glob.glob(f"{self.batch_dir}/{subdir}/*.nc"))
68
+ data_pipeline = windnet_netcdf_datapipe(
69
+ keys=["wind", "nwp"],
70
+ filenames=filenames,
71
+ )
72
+ data_pipeline = (
73
+ data_pipeline.batch(self.batch_size)
74
+ .map(stack_np_examples_into_batch)
75
+ .map(batch_to_tensor)
76
+ )
77
+ if shuffle:
78
+ data_pipeline = (
79
+ data_pipeline.shuffle(buffer_size=100)
80
+ .sharding_filter()
81
+ # Split the batches and reshuffle them to be combined into new batches
82
+ .split_batches(splitting_key=BatchKey.wind)
83
+ .shuffle(buffer_size=100 * self.batch_size)
84
+ )
85
+ else:
86
+ data_pipeline = (
87
+ data_pipeline.sharding_filter()
88
+ # Split the batches so we can use any batch-size
89
+ .split_batches(splitting_key=BatchKey.wind)
90
+ )
91
+
92
+ data_pipeline = (
93
+ data_pipeline.batch(self.batch_size)
94
+ .map(stack_np_examples_into_batch)
95
+ .map(batch_to_tensor)
96
+ .set_length(int(len(filenames) / self.batch_size))
97
+ )
98
+
99
+ return data_pipeline
@@ -7,6 +7,7 @@ from typing import Dict, Optional, Union
7
7
 
8
8
  import hydra
9
9
  import lightning.pytorch as pl
10
+ import matplotlib.pyplot as plt
10
11
  import pandas as pd
11
12
  import torch
12
13
  import torch.nn.functional as F
@@ -19,7 +20,6 @@ from huggingface_hub.hf_api import HfApi
19
20
  from huggingface_hub.utils._deprecation import _deprecate_positional_args
20
21
  from ocf_datapipes.batch import BatchKey
21
22
  from ocf_ml_metrics.evaluation.evaluation import evaluation
22
- from ocf_ml_metrics.metrics.errors import common_metrics
23
23
 
24
24
  from pvnet.models.utils import (
25
25
  BatchAccumulator,
@@ -65,6 +65,11 @@ def make_clean_data_config(input_path, output_path, placeholder="PLACEHOLDER"):
65
65
  d["pv_filename"] = f"{placeholder}.netcdf"
66
66
  d["pv_metadata_filename"] = f"{placeholder}.csv"
67
67
 
68
+ if "sensor" in config["input_data"]:
69
+ # If not empty - i.e. if used
70
+ if config["input_data"][source][f"{source}_filename"] != "":
71
+ config["input_data"][source][f"{source}_filename"] = f"{placeholder}.nc"
72
+
68
73
  with open(output_path, "w") as outfile:
69
74
  yaml.dump(config, outfile, default_flow_style=False)
70
75
 
@@ -234,7 +239,9 @@ class BaseModel(pl.LightningModule, PVNetModelHubMixin):
234
239
  forecast_minutes: int,
235
240
  optimizer: AbstractOptimizer,
236
241
  output_quantiles: Optional[list[float]] = None,
237
- target_key: BatchKey = BatchKey.gsp,
242
+ target_key: str = "gsp",
243
+ interval_minutes: int = 30,
244
+ timestep_intervals_to_plot: Optional[list[int]] = None,
238
245
  ):
239
246
  """Abtstract base class for PVNet submodels.
240
247
 
@@ -245,11 +252,21 @@ class BaseModel(pl.LightningModule, PVNetModelHubMixin):
245
252
  output_quantiles: A list of float (0.0, 1.0) quantiles to predict values for. If set to
246
253
  None the output is a single value.
247
254
  target_key: The key of the target variable in the batch
255
+ interval_minutes: The interval in minutes between each timestep in the data
256
+ timestep_intervals_to_plot: Intervals, in timesteps, to plot during training
248
257
  """
249
258
  super().__init__()
250
259
 
251
260
  self._optimizer = optimizer
252
- self._target_key = target_key
261
+ self._target_key_name = target_key
262
+ self._target_key = BatchKey[f"{target_key}"]
263
+ if timestep_intervals_to_plot is not None:
264
+ for interval in timestep_intervals_to_plot:
265
+ assert type(interval) in [list, tuple] and len(interval) == 2, ValueError(
266
+ f"timestep_intervals_to_plot must be a list of tuples or lists of length 2, "
267
+ f"but got {timestep_intervals_to_plot=}"
268
+ )
269
+ self.time_step_intervals_to_plot = timestep_intervals_to_plot
253
270
 
254
271
  # Model must have lr to allow tuning
255
272
  # This setting is only used when lr is tuned with callback
@@ -260,13 +277,13 @@ class BaseModel(pl.LightningModule, PVNetModelHubMixin):
260
277
  self.output_quantiles = output_quantiles
261
278
 
262
279
  # Number of timestemps for 30 minutely data
263
- self.history_len_30 = history_minutes // 30
264
- self.forecast_len_30 = forecast_minutes // 30
280
+ self.history_len = history_minutes // interval_minutes
281
+ self.forecast_len = forecast_minutes // interval_minutes
265
282
 
266
- self.weighted_losses = WeightedLosses(forecast_length=self.forecast_len_30)
283
+ self.weighted_losses = WeightedLosses(forecast_length=self.forecast_len)
267
284
 
268
285
  self._accumulated_metrics = MetricAccumulator()
269
- self._accumulated_batches = BatchAccumulator()
286
+ self._accumulated_batches = BatchAccumulator(key_to_keep=self._target_key_name)
270
287
  self._accumulated_y_hat = PredAccumulator()
271
288
 
272
289
  @property
@@ -278,9 +295,9 @@ class BaseModel(pl.LightningModule, PVNetModelHubMixin):
278
295
  def num_output_features(self):
279
296
  """Number of ouput features he model chould predict for"""
280
297
  if self.use_quantile_regression:
281
- out_features = self.forecast_len_30 * len(self.output_quantiles)
298
+ out_features = self.forecast_len * len(self.output_quantiles)
282
299
  else:
283
- out_features = self.forecast_len_30
300
+ out_features = self.forecast_len
284
301
  return out_features
285
302
 
286
303
  def _quantiles_to_prediction(self, y_quantiles):
@@ -372,15 +389,12 @@ class BaseModel(pl.LightningModule, PVNetModelHubMixin):
372
389
 
373
390
  # Take median value for remaining metric calculations
374
391
  y_hat = self._quantiles_to_prediction(y_hat)
392
+ mse_each_step = torch.mean((y_hat - y) ** 2, dim=0)
393
+ mae_each_step = torch.mean(torch.abs(y_hat - y), dim=0)
375
394
 
376
- common_metrics_each_step = common_metrics(
377
- predictions=y_hat.cpu().numpy(), target=y.cpu().numpy()
378
- )
379
- mse_each_step = common_metrics_each_step["rmse"] ** 2
380
- mae_each_step = common_metrics_each_step["mae"]
395
+ losses.update({f"MSE_horizon/step_{i:03}": m for i, m in enumerate(mse_each_step)})
396
+ losses.update({f"MAE_horizon/step_{i:03}": m for i, m in enumerate(mae_each_step)})
381
397
 
382
- losses.update({f"MSE_horizon/step_{i:02}": m for i, m in enumerate(mse_each_step)})
383
- losses.update({f"MAE_horizon/step_{i:02}": m for i, m in enumerate(mae_each_step)})
384
398
  return losses
385
399
 
386
400
  def _calculate_test_losses(self, y, y_hat):
@@ -421,13 +435,20 @@ class BaseModel(pl.LightningModule, PVNetModelHubMixin):
421
435
  # We only create the figure every 8 log steps
422
436
  # This was reduced as it was creating figures too often
423
437
  if grad_batch_num % (8 * self.trainer.log_every_n_steps) == 0:
424
- fig = plot_batch_forecasts(batch, y_hat, batch_idx, quantiles=self.output_quantiles)
438
+ fig = plot_batch_forecasts(
439
+ batch,
440
+ y_hat,
441
+ batch_idx,
442
+ quantiles=self.output_quantiles,
443
+ key_to_plot=self._target_key_name,
444
+ )
425
445
  fig.savefig("latest_logged_train_batch.png")
446
+ plt.close(fig)
426
447
 
427
448
  def training_step(self, batch, batch_idx):
428
449
  """Run training step"""
429
450
  y_hat = self(batch)
430
- y = batch[self._target_key][:, -self.forecast_len_30 :, 0]
451
+ y = batch[self._target_key][:, -self.forecast_len :, 0]
431
452
 
432
453
  losses = self._calculate_common_losses(y, y_hat)
433
454
  losses = {f"{k}/train": v for k, v in losses.items()}
@@ -443,12 +464,36 @@ class BaseModel(pl.LightningModule, PVNetModelHubMixin):
443
464
  def validation_step(self, batch: dict, batch_idx):
444
465
  """Run validation step"""
445
466
  y_hat = self(batch)
446
- y = batch[self._target_key][:, -self.forecast_len_30 :, 0]
467
+ # Sensor seems to be in batch, station, time order
468
+ y = batch[self._target_key][:, -self.forecast_len :, 0]
447
469
 
448
470
  losses = self._calculate_common_losses(y, y_hat)
449
471
  losses.update(self._calculate_val_losses(y, y_hat))
450
472
 
451
473
  logged_losses = {f"{k}/val": v for k, v in losses.items()}
474
+ # Get the losses in the format of {VAL>_horizon/step_000: 0.1, VAL>_horizon/step_001: 0.2}
475
+ # for each step in the forecast horizon
476
+ # This is needed for the custom plot
477
+ # And needs to be in order of step
478
+ x_values = [
479
+ int(k.split("_")[-1].split("/")[0])
480
+ for k in logged_losses.keys()
481
+ if "MAE_horizon/step" in k
482
+ ]
483
+ y_values = []
484
+ for x in x_values:
485
+ y_values.append(logged_losses[f"MAE_horizon/step_{x:03}/val"])
486
+ per_step_losses = [[x, y] for (x, y) in zip(x_values, y_values)]
487
+ # Check if WandBLogger is being used
488
+ if isinstance(self.logger, pl.loggers.WandbLogger):
489
+ table = wandb.Table(data=per_step_losses, columns=["timestep", "MAE"])
490
+ wandb.log(
491
+ {
492
+ "mae_vs_timestep": wandb.plot.line(
493
+ table, "timestep", "MAE", title="MAE vs Timestep"
494
+ )
495
+ }
496
+ )
452
497
 
453
498
  self.log_dict(
454
499
  logged_losses,
@@ -462,23 +507,46 @@ class BaseModel(pl.LightningModule, PVNetModelHubMixin):
462
507
  # Store these temporarily under self
463
508
  if not hasattr(self, "_val_y_hats"):
464
509
  self._val_y_hats = PredAccumulator()
465
- self._val_batches = BatchAccumulator()
510
+ self._val_batches = BatchAccumulator(key_to_keep=self._target_key_name)
466
511
 
467
512
  self._val_y_hats.append(y_hat)
468
513
  self._val_batches.append(batch)
469
-
470
514
  # if batch had accumulated
471
515
  if (batch_idx + 1) % self.trainer.accumulate_grad_batches == 0:
472
516
  y_hat = self._val_y_hats.flush()
473
517
  batch = self._val_batches.flush()
474
518
 
475
- fig = plot_batch_forecasts(batch, y_hat, quantiles=self.output_quantiles)
519
+ fig = plot_batch_forecasts(
520
+ batch,
521
+ y_hat,
522
+ quantiles=self.output_quantiles,
523
+ key_to_plot=self._target_key_name,
524
+ )
476
525
 
477
526
  self.logger.experiment.log(
478
527
  {
479
- f"val_forecast_samples/batch_idx_{accum_batch_num}": wandb.Image(fig),
528
+ f"val_forecast_samples/batch_idx_{accum_batch_num}_all": wandb.Image(fig),
480
529
  }
481
530
  )
531
+ plt.close(fig)
532
+
533
+ if self.time_step_intervals_to_plot is not None:
534
+ for interval in self.time_step_intervals_to_plot:
535
+ fig = plot_batch_forecasts(
536
+ batch,
537
+ y_hat,
538
+ quantiles=self.output_quantiles,
539
+ key_to_plot=self._target_key_name,
540
+ timesteps_to_plot=interval,
541
+ )
542
+ self.logger.experiment.log(
543
+ {
544
+ f"val_forecast_samples/batch_idx_{accum_batch_num}_"
545
+ f"timestep_{interval}": wandb.Image(fig),
546
+ }
547
+ )
548
+ plt.close(fig)
549
+
482
550
  del self._val_y_hats
483
551
  del self._val_batches
484
552
 
@@ -487,7 +555,7 @@ class BaseModel(pl.LightningModule, PVNetModelHubMixin):
487
555
  def test_step(self, batch, batch_idx):
488
556
  """Run test step"""
489
557
  y_hat = self(batch)
490
- y = batch[self._target_key][:, -self.forecast_len_30 :, 0]
558
+ y = batch[self._target_key][:, -self.forecast_len :, 0]
491
559
 
492
560
  losses = self._calculate_common_losses(y, y_hat)
493
561
  losses.update(self._calculate_val_losses(y, y_hat))
@@ -36,8 +36,8 @@ class Model(BaseModel):
36
36
 
37
37
  # take the last value non forecaster value and the first in the pv yeild
38
38
  # (this is the pv site we are preditcting for)
39
- y_hat = gsp_yield[:, -self.forecast_len_30 - 1, 0]
39
+ y_hat = gsp_yield[:, -self.forecast_len - 1, 0]
40
40
 
41
41
  # expand the last valid forward n predict steps
42
- out = y_hat.unsqueeze(1).repeat(1, self.forecast_len_30)
42
+ out = y_hat.unsqueeze(1).repeat(1, self.forecast_len)
43
43
  return out
@@ -33,5 +33,5 @@ class Model(BaseModel):
33
33
  def forward(self, x: dict):
34
34
  """Run model forward on dict batch of data"""
35
35
  # Returns a single value at all steps
36
- y_hat = torch.zeros_like(x[BatchKey.gsp][:, : self.forecast_len_30, 0]) + self._value
36
+ y_hat = torch.zeros_like(x[BatchKey.gsp][:, : self.forecast_len, 0]) + self._value
37
37
  return y_hat
@@ -161,7 +161,7 @@ class Model(BaseModel):
161
161
  if self.include_sun:
162
162
  # the minus 12 is bit of hard coded smudge for pvnet
163
163
  self.sun_fc1 = nn.Linear(
164
- in_features=2 * (self.forecast_len_30 + self.history_len_30 + 1),
164
+ in_features=2 * (self.forecast_len + self.history_len + 1),
165
165
  out_features=16,
166
166
  )
167
167
 
@@ -170,18 +170,18 @@ class Model(BaseModel):
170
170
  num_cat_features += encoder_out_features
171
171
  self.sat_output_network = output_network(
172
172
  in_features=encoder_out_features,
173
- out_features=self.forecast_len_30,
173
+ out_features=self.forecast_len,
174
174
  **output_network_kwargs,
175
175
  )
176
176
  if include_nwp:
177
177
  num_cat_features += encoder_out_features
178
178
  self.nwp_output_network = output_network(
179
179
  in_features=encoder_out_features,
180
- out_features=self.forecast_len_30,
180
+ out_features=self.forecast_len,
181
181
  **output_network_kwargs,
182
182
  )
183
183
  if include_gsp_yield_history:
184
- num_cat_features += self.history_len_30
184
+ num_cat_features += self.history_len
185
185
  if embedding_dim:
186
186
  num_cat_features += embedding_dim
187
187
  if include_sun:
@@ -189,7 +189,7 @@ class Model(BaseModel):
189
189
 
190
190
  self.output_network = output_network(
191
191
  in_features=num_cat_features,
192
- out_features=self.forecast_len_30,
192
+ out_features=self.forecast_len,
193
193
  **output_network_kwargs,
194
194
  )
195
195
 
@@ -226,7 +226,7 @@ class Model(BaseModel):
226
226
  # *********************** GSP Data ************************************
227
227
  # add gsp yield history
228
228
  if self.include_gsp_yield_history:
229
- gsp_history = x[BatchKey.gsp][:, : self.history_len_30].float()
229
+ gsp_history = x[BatchKey.gsp][:, : self.history_len].float()
230
230
  gsp_history = gsp_history.reshape(gsp_history.shape[0], -1)
231
231
  gsp_history = self.source_dropout_0d(gsp_history)
232
232
  modes["gsp"] = gsp_history
@@ -266,7 +266,7 @@ class Model(BaseModel):
266
266
  def training_step(self, batch, batch_idx):
267
267
  """Training step"""
268
268
  y_hats = self.multi_mode_forward(batch)
269
- y = batch[BatchKey.gsp][:, -self.forecast_len_30 :, 0]
269
+ y = batch[BatchKey.gsp][:, -self.forecast_len :, 0]
270
270
 
271
271
  losses = self._calculate_common_losses(y, y_hats["all"])
272
272
  losses = {f"{k}/train": v for k, v in losses.items()}