├── pytorch_forecasting ├── callbacks │ ├── __init__.py │ └── predict.py ├── utils │ ├── _maint │ │ └── __init__.py │ ├── _dependencies │ │ ├── tests │ │ │ ├── __init__.py │ │ │ └── test_safe_import.py │ │ ├── __init__.py │ │ └── _dependencies.py │ ├── _classproperty.py │ ├── _coerce.py │ └── __init__.py ├── tests │ ├── test_all_v2 │ │ ├── __init__.py │ │ ├── _test_integration.py │ │ └── utils.py │ ├── __init__.py │ ├── _config.py │ ├── test_class_register.py │ ├── _loss_mapping.py │ └── test_doctest.py ├── data │ ├── tests │ │ └── __init__.py │ ├── timeseries │ │ └── __init__.py │ └── __init__.py ├── layers │ ├── _blocks │ │ ├── __init__.py │ │ └── _residual_block_dsipts.py │ ├── _normalization │ │ ├── __init__.py │ │ └── _revin.py │ ├── _kan │ │ └── __init__.py │ ├── _output │ │ ├── __init__.py │ │ └── _flatten_head.py │ ├── _filter │ │ ├── __init__.py │ │ └── _moving_avg_filter.py │ ├── _decomposition │ │ ├── __init__.py │ │ └── _series_decomp.py │ ├── _encoders │ │ ├── __init__.py │ │ ├── _encoder.py │ │ └── _encoder_layer.py │ ├── _recurrent │ │ ├── _mlstm │ │ │ ├── __init__.py │ │ │ └── network.py │ │ ├── _slstm │ │ │ ├── __init__.py │ │ │ └── network.py │ │ └── __init__.py │ ├── _attention │ │ ├── __init__.py │ │ └── _attention_layer.py │ ├── _nbeats │ │ ├── __init__.py │ │ └── _utils.py │ ├── _embeddings │ │ ├── __init__.py │ │ ├── _positional_embedding.py │ │ ├── _data_embedding.py │ │ ├── _en_embedding.py │ │ └── _sub_nn.py │ └── __init__.py ├── _registry │ └── __init__.py ├── base │ ├── __init__.py │ └── _base_object.py ├── models │ ├── xlstm │ │ ├── __init__.py │ │ └── _xlstm_pkg.py │ ├── tide │ │ ├── _tide_dsipts │ │ │ ├── __init__.py │ │ │ └── _tide_v2_pkg.py │ │ └── __init__.py │ ├── deepar │ │ └── __init__.py │ ├── rnn │ │ ├── __init__.py │ │ └── _rnn_pkg.py │ ├── dlinear │ │ └── __init__.py │ ├── nhits │ │ └── __init__.py │ ├── samformer │ │ ├── __init__.py │ │ └── _samformer_v2_pkg.py │ ├── nbeats │ │ ├── sub_modules.py │ │ ├── __init__.py │ │ ├── _grid_callback.py │ │ ├── _nbeats_pkg.py │ │ └── _nbeatskan_pkg.py │ ├── mlp │ │ ├── __init__.py │ │ ├── submodules.py │ │ └── _decodermlp_pkg.py │ ├── nn │ │ └── __init__.py │ ├── base_model.py │ ├── base │ │ └── __init__.py │ ├── timexer │ │ ├── __init__.py │ │ └── _timexer_pkg_v2.py │ ├── temporal_fusion_transformer │ │ ├── __init__.py │ │ └── _tft_pkg_v2.py │ ├── __init__.py │ └── baseline.py ├── metrics │ ├── _quantile_pkg │ │ ├── __init__.py │ │ └── _quantile_loss_pkg.py │ ├── _point_pkg │ │ ├── _mase │ │ │ └── _mase_pkg.py │ │ ├── _mae │ │ │ └── _mae_pkg.py │ │ ├── _rmse │ │ │ └── _rmse_pkg.py │ │ ├── _mape │ │ │ └── _mape_pkg.py │ │ ├── _smape │ │ │ └── _smape_pkg.py │ │ ├── _tweedie │ │ │ └── _tweedie_loss_pkg.py │ │ ├── _poisson │ │ │ └── _poisson_loss_pkg.py │ │ ├── _cross_entropy │ │ │ └── _cross_entropy_pkg.py │ │ └── __init__.py │ ├── base_metrics │ │ ├── __init__.py │ │ └── _base_object.py │ ├── _distributions_pkg │ │ ├── _normal │ │ │ └── _normal_distribution_loss_pkg.py │ │ ├── _multivariate_normal │ │ │ └── _multivariate_normal_distribution_loss_pkg.py │ │ ├── _beta │ │ │ └── _beta_distribution_loss_pkg.py │ │ ├── _negative_binomial │ │ │ └── _negative_binomial_distribution_loss_pkg.py │ │ ├── _mqf2 │ │ │ └── _mqf2_distribution_loss_pkg.py │ │ ├── __init__.py │ │ ├── _implicit_quantile_network │ │ │ └── _implicit_quantile_network_distribution_loss_pkg.py │ │ └── _log_normal │ │ │ └── _log_normal_distribution_loss_pkg.py │ ├── tests │ │ └── _config.py │ ├── quantile.py │ └── __init__.py └── __init__.py ├── examples ├── data │ └── stallion.parquet ├── nbeats.py ├── nbeats_with_kan.py └── ar.py ├── docs ├── source │ ├── _static │ │ ├── favicon.png │ │ ├── custom.css │ │ └── favicon.svg │ ├── _templates │ │ ├── custom-base-template.rst │ │ ├── custom-module-template.rst │ │ └── custom-class-template.rst │ ├── api.rst │ ├── tutorials.rst │ ├── data.rst │ ├── metrics.rst │ ├── index.rst │ └── faq.rst ├── requirements.txt ├── Makefile └── make.bat ├── CODEOWNERS ├── codecov.yml ├── tests ├── test_models │ ├── test_baseline.py │ ├── test_nn │ │ ├── test_embeddings.py │ │ └── test_rnn.py │ ├── test_nbeats.py │ └── test_x_lstm.py ├── test_utils │ ├── test_autocorrelation.py │ ├── test_show_versions.py │ └── test_safe_import.py ├── test_data │ └── test_samplers.py └── conftest.py ├── .github ├── ISSUE_TEMPLATE │ ├── config.yml │ ├── doc_improvement.md │ ├── feature_request.md │ └── bug_report.md ├── dependabot.yml ├── workflows │ ├── autoapprove.yml.disabled │ ├── automerge.yml.disabled │ └── pypi_release.yml └── PULL_REQUEST_TEMPLATE.md ├── .pre-commit-config.yaml ├── .readthedocs.yml ├── LICENSE └── .gitignore /pytorch_forecasting/callbacks/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pytorch_forecasting/utils/_maint/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pytorch_forecasting/tests/test_all_v2/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pytorch_forecasting/tests/__init__.py: -------------------------------------------------------------------------------- 1 | """PyTorch Forecasting test suite.""" 2 | -------------------------------------------------------------------------------- /pytorch_forecasting/utils/_dependencies/tests/__init__.py: -------------------------------------------------------------------------------- 1 | """Tests for dependency utilities.""" 2 | -------------------------------------------------------------------------------- /examples/data/stallion.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sktime/pytorch-forecasting/HEAD/examples/data/stallion.parquet -------------------------------------------------------------------------------- /docs/source/_static/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sktime/pytorch-forecasting/HEAD/docs/source/_static/favicon.png -------------------------------------------------------------------------------- /pytorch_forecasting/data/tests/__init__.py: -------------------------------------------------------------------------------- 1 | """Tests for data modules and dataloaders in pytorch_forecasting.data package.""" 2 | -------------------------------------------------------------------------------- /pytorch_forecasting/layers/_blocks/__init__.py: -------------------------------------------------------------------------------- 1 | from pytorch_forecasting.layers._blocks._residual_block_dsipts import ResidualBlock 2 | 3 | __all__ = ["ResidualBlock"] 4 | -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | # The file specifies framework level core developers for automated review requests 2 | 3 | * @benheid @fkiraly @fnhirwa @jdb78 @phoeenniixx @pranavbhatp @yarnabrina 4 | -------------------------------------------------------------------------------- /docs/source/_templates/custom-base-template.rst: -------------------------------------------------------------------------------- 1 | {{ fullname.split(".")[-1] | escape | underline}} 2 | 3 | .. currentmodule:: {{ module }} 4 | 5 | .. auto{{ objtype }}:: {{ objname }} 6 | -------------------------------------------------------------------------------- /pytorch_forecasting/_registry/__init__.py: -------------------------------------------------------------------------------- 1 | """PyTorch Forecasting registry.""" 2 | 3 | from pytorch_forecasting._registry._lookup import all_objects 4 | 5 | __all__ = ["all_objects"] 6 | -------------------------------------------------------------------------------- /pytorch_forecasting/base/__init__.py: -------------------------------------------------------------------------------- 1 | """Base classes for pytorch-forecasting.""" 2 | 3 | from pytorch_forecasting.base._base_object import _BaseObject 4 | 5 | __all__ = ["_BaseObject"] 6 | -------------------------------------------------------------------------------- /pytorch_forecasting/utils/_classproperty.py: -------------------------------------------------------------------------------- 1 | """A class property decorator.""" 2 | 3 | 4 | class classproperty(property): 5 | def __get__(self, obj, cls): 6 | return self.fget(cls) 7 | -------------------------------------------------------------------------------- /pytorch_forecasting/layers/_normalization/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | RevIN: Reverse Instance Normalization 3 | """ 4 | 5 | from pytorch_forecasting.layers._normalization._revin import RevIN 6 | 7 | __all__ = ["RevIN"] 8 | -------------------------------------------------------------------------------- /pytorch_forecasting/layers/_kan/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | KAN (Kolmogorov Arnold Network) layer implementation. 3 | """ 4 | 5 | from pytorch_forecasting.layers._kan._kan_layer import KANLayer 6 | 7 | __all__ = ["KANLayer"] 8 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | coverage: 2 | precision: 2 3 | round: down 4 | range: "70...100" 5 | status: 6 | project: 7 | default: 8 | threshold: 0.2% 9 | patch: 10 | default: 11 | informational: true 12 | -------------------------------------------------------------------------------- /pytorch_forecasting/layers/_output/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Implementation of output layers for PyTorch Forecasting. 3 | """ 4 | 5 | from pytorch_forecasting.layers._output._flatten_head import FlattenHead 6 | 7 | __all__ = ["FlattenHead"] 8 | -------------------------------------------------------------------------------- /pytorch_forecasting/layers/_filter/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Filtering layers for time series forecasting models. 3 | """ 4 | 5 | from pytorch_forecasting.layers._filter._moving_avg_filter import MovingAvg 6 | 7 | __all__ = [ 8 | "MovingAvg", 9 | ] 10 | -------------------------------------------------------------------------------- /docs/source/api.rst: -------------------------------------------------------------------------------- 1 | API 2 | ==== 3 | 4 | .. currentmodule:: pytorch_forecasting 5 | 6 | .. autosummary:: 7 | :toctree: api 8 | :template: custom-module-template.rst 9 | :recursive: 10 | 11 | data 12 | models 13 | metrics 14 | utils 15 | -------------------------------------------------------------------------------- /pytorch_forecasting/layers/_decomposition/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Decomposition layers for PyTorch Forecasting. 3 | """ 4 | 5 | from pytorch_forecasting.layers._decomposition._series_decomp import SeriesDecomposition 6 | 7 | __all__ = [ 8 | "SeriesDecomposition", 9 | ] 10 | -------------------------------------------------------------------------------- /pytorch_forecasting/models/xlstm/__init__.py: -------------------------------------------------------------------------------- 1 | """xLSTMTime implementation for forecasting.""" 2 | 3 | from pytorch_forecasting.models.xlstm._xlstm import xLSTMTime 4 | from pytorch_forecasting.models.xlstm._xlstm_pkg import xLSTMTime_pkg 5 | 6 | __all__ = ["xLSTMTime", "xLSTMTime_pkg"] 7 | -------------------------------------------------------------------------------- /tests/test_models/test_baseline.py: -------------------------------------------------------------------------------- 1 | from pytorch_forecasting import Baseline 2 | 3 | 4 | def test_integration(multiple_dataloaders_with_covariates): 5 | dataloader = multiple_dataloaders_with_covariates["val"] 6 | Baseline().predict(dataloader, fast_dev_run=True) 7 | repr(Baseline()) 8 | -------------------------------------------------------------------------------- /pytorch_forecasting/layers/_encoders/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Encoder layers for neural network models. 3 | """ 4 | 5 | from pytorch_forecasting.layers._encoders._encoder import Encoder 6 | from pytorch_forecasting.layers._encoders._encoder_layer import EncoderLayer 7 | 8 | __all__ = ["Encoder", "EncoderLayer"] 9 | -------------------------------------------------------------------------------- /pytorch_forecasting/models/tide/_tide_dsipts/__init__.py: -------------------------------------------------------------------------------- 1 | """DSIPTS Tide Implementation for V2""" 2 | 3 | from pytorch_forecasting.models.tide._tide_dsipts._tide_v2 import TIDE 4 | from pytorch_forecasting.models.tide._tide_dsipts._tide_v2_pkg import TIDE_pkg_v2 5 | 6 | __all__ = ["TIDE", "TIDE_pkg_v2"] 7 | -------------------------------------------------------------------------------- /pytorch_forecasting/models/deepar/__init__.py: -------------------------------------------------------------------------------- 1 | """DeepAR: Probabilistic forecasting with autoregressive recurrent networks.""" 2 | 3 | from pytorch_forecasting.models.deepar._deepar import DeepAR 4 | from pytorch_forecasting.models.deepar._deepar_pkg import DeepAR_pkg 5 | 6 | __all__ = ["DeepAR", "DeepAR_pkg"] 7 | -------------------------------------------------------------------------------- /pytorch_forecasting/metrics/_quantile_pkg/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Package containers for quantile metrics in PyTorch Forecasting. 3 | """ 4 | 5 | from pytorch_forecasting.metrics._quantile_pkg._quantile_loss_pkg import ( 6 | QuantileLoss_pkg, 7 | ) 8 | 9 | __all__ = [ 10 | "QuantileLoss_pkg", 11 | ] 12 | -------------------------------------------------------------------------------- /pytorch_forecasting/models/rnn/__init__.py: -------------------------------------------------------------------------------- 1 | """Simple recurrent model - either with LSTM or GRU cells.""" 2 | 3 | from pytorch_forecasting.models.rnn._rnn import RecurrentNetwork 4 | from pytorch_forecasting.models.rnn._rnn_pkg import RecurrentNetwork_pkg 5 | 6 | __all__ = ["RecurrentNetwork", "RecurrentNetwork_pkg"] 7 | -------------------------------------------------------------------------------- /pytorch_forecasting/models/dlinear/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Decomposition-Linear model for time series forecasting. 3 | """ 4 | 5 | from pytorch_forecasting.models.dlinear._dlinear_pkg_v2 import DLinear_pkg_v2 6 | from pytorch_forecasting.models.dlinear._dlinear_v2 import DLinear 7 | 8 | __all__ = ["DLinear", "DLinear_pkg_v2"] 9 | -------------------------------------------------------------------------------- /pytorch_forecasting/base/_base_object.py: -------------------------------------------------------------------------------- 1 | """Base object class for pytorch-forecasting metrics.""" 2 | 3 | from pytorch_forecasting.utils._dependencies import _safe_import 4 | 5 | _SkbaseBaseObject = _safe_import("skbase.base.BaseObject", pkg_name="scikit-base") 6 | 7 | __all__ = ["_BaseObject"] 8 | 9 | 10 | class _BaseObject(_SkbaseBaseObject): 11 | pass 12 | -------------------------------------------------------------------------------- /pytorch_forecasting/layers/_recurrent/_mlstm/__init__.py: -------------------------------------------------------------------------------- 1 | """mLSTM layer""" 2 | 3 | from pytorch_forecasting.layers._recurrent._mlstm.cell import mLSTMCell 4 | from pytorch_forecasting.layers._recurrent._mlstm.layer import mLSTMLayer 5 | from pytorch_forecasting.layers._recurrent._mlstm.network import mLSTMNetwork 6 | 7 | __all__ = ["mLSTMCell", "mLSTMLayer", "mLSTMNetwork"] 8 | -------------------------------------------------------------------------------- /pytorch_forecasting/layers/_recurrent/_slstm/__init__.py: -------------------------------------------------------------------------------- 1 | """sLSTM layer""" 2 | 3 | from pytorch_forecasting.layers._recurrent._slstm.cell import sLSTMCell 4 | from pytorch_forecasting.layers._recurrent._slstm.layer import sLSTMLayer 5 | from pytorch_forecasting.layers._recurrent._slstm.network import sLSTMNetwork 6 | 7 | __all__ = ["sLSTMCell", "sLSTMLayer", "sLSTMNetwork"] 8 | -------------------------------------------------------------------------------- /pytorch_forecasting/models/nhits/__init__.py: -------------------------------------------------------------------------------- 1 | """N-HiTS model for timeseries forecasting with covariates.""" 2 | 3 | from pytorch_forecasting.models.nhits._nhits import NHiTS 4 | from pytorch_forecasting.models.nhits._nhits_pkg import NHiTS_pkg 5 | from pytorch_forecasting.models.nhits.sub_modules import NHiTS as NHiTSModule 6 | 7 | __all__ = ["NHiTS", "NHiTSModule", "NHiTS_pkg"] 8 | -------------------------------------------------------------------------------- /pytorch_forecasting/models/samformer/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | DSIPTS Implementation of Samformer for V2 3 | -------------------------------------- 4 | """ 5 | 6 | from pytorch_forecasting.models.samformer._samformer_v2 import Samformer 7 | from pytorch_forecasting.models.samformer._samformer_v2_pkg import Samformer_pkg_v2 8 | 9 | __all__ = ["Samformer", "Samformer_pkg_v2"] 10 | -------------------------------------------------------------------------------- /pytorch_forecasting/models/nbeats/sub_modules.py: -------------------------------------------------------------------------------- 1 | """ 2 | Backward-compatibility shim for N-BEATS blocks. 3 | Real implementations live in `pytorch_forecasting.layers._nbeats._blocks`. 4 | 5 | # TODO v2: remove this file. 6 | """ 7 | 8 | from pytorch_forecasting.layers._nbeats._blocks import ( 9 | NBEATSGenericBlock, 10 | NBEATSSeasonalBlock, 11 | NBEATSTrendBlock, 12 | ) 13 | -------------------------------------------------------------------------------- /pytorch_forecasting/models/mlp/__init__.py: -------------------------------------------------------------------------------- 1 | """Simple models based on fully connected networks.""" 2 | 3 | from pytorch_forecasting.models.mlp._decodermlp import DecoderMLP 4 | from pytorch_forecasting.models.mlp._decodermlp_pkg import DecoderMLP_pkg 5 | from pytorch_forecasting.models.mlp.submodules import FullyConnectedModule 6 | 7 | __all__ = ["DecoderMLP", "DecoderMLP_pkg", "FullyConnectedModule"] 8 | -------------------------------------------------------------------------------- /docs/requirements.txt: -------------------------------------------------------------------------------- 1 | sphinx >3.2 2 | nbsphinx 3 | pandoc 4 | docutils 5 | pydata-sphinx-theme 6 | lightning >=2.0.0 7 | cloudpickle 8 | torch >=2.0,!=2.0.1 9 | optuna >=3.1.0 10 | optuna-integration 11 | scipy 12 | pandas >=1.3 13 | scikit-learn >1.2 14 | matplotlib 15 | statsmodels 16 | ipython 17 | nbconvert >=6.3.0 18 | recommonmark >=0.7.1 19 | pytorch-optimizer >=2.5.1 20 | fastapi >0.80 21 | cpflows 22 | -------------------------------------------------------------------------------- /pytorch_forecasting/models/nn/__init__.py: -------------------------------------------------------------------------------- 1 | from pytorch_forecasting.models.nn.embeddings import MultiEmbedding 2 | from pytorch_forecasting.models.nn.rnn import GRU, LSTM, HiddenState, get_rnn 3 | from pytorch_forecasting.utils import TupleOutputMixIn 4 | 5 | __all__ = [ 6 | "MultiEmbedding", 7 | "get_rnn", 8 | "LSTM", 9 | "GRU", 10 | "HiddenState", 11 | "TupleOutputMixIn", 12 | ] 13 | -------------------------------------------------------------------------------- /pytorch_forecasting/layers/_attention/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Attention Layers for pytorch-forecasting models. 3 | """ 4 | 5 | from pytorch_forecasting.layers._attention._attention_layer import AttentionLayer 6 | from pytorch_forecasting.layers._attention._full_attention import ( 7 | FullAttention, 8 | TriangularCausalMask, 9 | ) 10 | 11 | __all__ = ["AttentionLayer", "FullAttention", "TriangularCausalMask"] 12 | -------------------------------------------------------------------------------- /docs/source/tutorials.rst: -------------------------------------------------------------------------------- 1 | Tutorials 2 | ========== 3 | 4 | .. _tutorials: 5 | 6 | The following tutorials can be also found as `notebooks on GitHub `_. 7 | 8 | .. toctree:: 9 | :titlesonly: 10 | :maxdepth: 2 11 | 12 | tutorials/stallion 13 | tutorials/ar 14 | tutorials/building 15 | tutorials/deepar 16 | tutorials/nhits 17 | -------------------------------------------------------------------------------- /pytorch_forecasting/models/tide/__init__.py: -------------------------------------------------------------------------------- 1 | """Tide model.""" 2 | 3 | from pytorch_forecasting.models.tide._tide import TiDEModel 4 | from pytorch_forecasting.models.tide._tide_dsipts import TIDE, TIDE_pkg_v2 5 | from pytorch_forecasting.models.tide._tide_pkg import TiDEModel_pkg 6 | from pytorch_forecasting.models.tide.sub_modules import _TideModule 7 | 8 | __all__ = ["_TideModule", "TiDEModel", "TiDEModel_pkg", "TIDE", "TIDE_pkg_v2"] 9 | -------------------------------------------------------------------------------- /pytorch_forecasting/layers/_nbeats/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Implementation of N-BEATS model blocks and utilities. 3 | """ 4 | 5 | from pytorch_forecasting.layers._nbeats._blocks import ( 6 | NBEATSBlock, 7 | NBEATSGenericBlock, 8 | NBEATSSeasonalBlock, 9 | NBEATSTrendBlock, 10 | ) 11 | 12 | __all__ = [ 13 | "NBEATSBlock", 14 | "NBEATSGenericBlock", 15 | "NBEATSSeasonalBlock", 16 | "NBEATSTrendBlock", 17 | ] 18 | -------------------------------------------------------------------------------- /pytorch_forecasting/utils/_dependencies/__init__.py: -------------------------------------------------------------------------------- 1 | """Utilities for managing dependencies.""" 2 | 3 | from pytorch_forecasting.utils._dependencies._dependencies import ( 4 | _check_matplotlib, 5 | _get_installed_packages, 6 | ) 7 | from pytorch_forecasting.utils._dependencies._safe_import import _safe_import 8 | 9 | __all__ = [ 10 | "_get_installed_packages", 11 | "_check_matplotlib", 12 | "_safe_import", 13 | ] 14 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | contact_links: 2 | - name: "\U0001F4AC All other questions and general chat" 3 | url: https://discord.com/invite/54ACzaFsn7 4 | about: Chat with the sktime community on Discord 5 | - name: "\u2709\uFE0F Code of Conduct incident reporting" 6 | url: https://www.sktime.net/en/latest/get_involved/code_of_conduct.html#incident-reporting-guidelines 7 | about: Report an incident to the Code of Conduct committee 8 | -------------------------------------------------------------------------------- /pytorch_forecasting/data/timeseries/__init__.py: -------------------------------------------------------------------------------- 1 | """Data loaders for time series data.""" 2 | 3 | from pytorch_forecasting.data.timeseries._timeseries import ( 4 | TimeSeriesDataSet, 5 | _find_end_indices, 6 | check_for_nonfinite, 7 | ) 8 | from pytorch_forecasting.data.timeseries._timeseries_v2 import TimeSeries 9 | 10 | __all__ = [ 11 | "_find_end_indices", 12 | "check_for_nonfinite", 13 | "TimeSeriesDataSet", 14 | "TimeSeries", 15 | ] 16 | -------------------------------------------------------------------------------- /pytorch_forecasting/models/base_model.py: -------------------------------------------------------------------------------- 1 | """Base classes for pytorch-foercasting models.""" 2 | 3 | from pytorch_forecasting.models.base import ( 4 | AutoRegressiveBaseModel, 5 | AutoRegressiveBaseModelWithCovariates, 6 | BaseModel, 7 | BaseModelWithCovariates, 8 | Prediction, 9 | ) 10 | 11 | __all__ = [ 12 | "AutoRegressiveBaseModel", 13 | "AutoRegressiveBaseModelWithCovariates", 14 | "BaseModel", 15 | "BaseModelWithCovariates", 16 | "Prediction", 17 | ] 18 | -------------------------------------------------------------------------------- /tests/test_utils/test_autocorrelation.py: -------------------------------------------------------------------------------- 1 | import math 2 | 3 | import torch 4 | 5 | from pytorch_forecasting.utils import autocorrelation 6 | 7 | 8 | def test_autocorrelation(): 9 | x = torch.sin(torch.linspace(0, 2 * 2 * math.pi, 201)) 10 | corr = autocorrelation(x, dim=-1) 11 | assert corr[0] == 1, "Autocorrelation of first element should be 1." 12 | assert corr[101] > 0.99, "Autocorrelation should be near 1 for sin(2*pi)" 13 | assert corr[50] < -0.99, "Autocorrelation should be near -1 for sin(pi)" 14 | -------------------------------------------------------------------------------- /pytorch_forecasting/layers/_recurrent/__init__.py: -------------------------------------------------------------------------------- 1 | """Recurrent Layers for Pytorch-Forecasting""" 2 | 3 | from pytorch_forecasting.layers._recurrent._mlstm import ( 4 | mLSTMCell, 5 | mLSTMLayer, 6 | mLSTMNetwork, 7 | ) 8 | from pytorch_forecasting.layers._recurrent._slstm import ( 9 | sLSTMCell, 10 | sLSTMLayer, 11 | sLSTMNetwork, 12 | ) 13 | 14 | __all__ = [ 15 | "mLSTMCell", 16 | "mLSTMLayer", 17 | "mLSTMNetwork", 18 | "sLSTMCell", 19 | "sLSTMLayer", 20 | "sLSTMNetwork", 21 | ] 22 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "pip" 4 | directory: "/" 5 | schedule: 6 | interval: "daily" 7 | commit-message: 8 | prefix: "[MNT] [Dependabot]" 9 | include: "scope" 10 | labels: 11 | - "maintenance" 12 | - package-ecosystem: "github-actions" 13 | directory: "/" 14 | schedule: 15 | interval: "daily" 16 | commit-message: 17 | prefix: "[MNT] [Dependabot]" 18 | include: "scope" 19 | labels: 20 | - "maintenance" 21 | -------------------------------------------------------------------------------- /pytorch_forecasting/tests/_config.py: -------------------------------------------------------------------------------- 1 | """Test configs.""" 2 | 3 | # list of str, names of estimators to exclude from testing 4 | # WARNING: tests for these estimators will be skipped 5 | EXCLUDE_ESTIMATORS = [ 6 | "DummySkipped", 7 | "ClassName", # exclude classes from extension templates 8 | ] 9 | 10 | # dictionary of lists of str, names of tests to exclude from testing 11 | # keys are class names of estimators, values are lists of test names to exclude 12 | # WARNING: tests with these names will be skipped 13 | EXCLUDED_TESTS = {} 14 | -------------------------------------------------------------------------------- /pytorch_forecasting/utils/_coerce.py: -------------------------------------------------------------------------------- 1 | """Coercion functions for various data types.""" 2 | 3 | from copy import deepcopy 4 | 5 | 6 | def _coerce_to_list(obj): 7 | """Coerce object to list. 8 | 9 | None is coerced to empty list, otherwise list constructor is used. 10 | """ 11 | if obj is None: 12 | return [] 13 | if isinstance(obj, str): 14 | return [obj] 15 | return list(obj) 16 | 17 | 18 | def _coerce_to_dict(obj): 19 | """Coerce object to dict. 20 | 21 | None is coerce to empty dict, otherwise deepcopy is used. 22 | """ 23 | if obj is None: 24 | return {} 25 | return deepcopy(obj) 26 | -------------------------------------------------------------------------------- /.github/workflows/autoapprove.yml.disabled: -------------------------------------------------------------------------------- 1 | name: Dependabot auto-approve 2 | on: pull_request 3 | 4 | permissions: 5 | pull-requests: write 6 | 7 | jobs: 8 | dependabot: 9 | runs-on: ubuntu-latest 10 | if: ${{ github.actor == 'dependabot[bot]' }} 11 | steps: 12 | - name: Dependabot metadata 13 | id: metadata 14 | uses: dependabot/fetch-metadata@v1.1.1 15 | with: 16 | github-token: "${{ secrets.GITHUB_TOKEN }}" 17 | - name: Approve a PR 18 | run: gh pr review --approve "$PR_URL" 19 | env: 20 | PR_URL: ${{github.event.pull_request.html_url}} 21 | GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} 22 | -------------------------------------------------------------------------------- /pytorch_forecasting/layers/_embeddings/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Implementation of embedding layers for PTF models imported from `nn.Modules` 3 | """ 4 | 5 | from pytorch_forecasting.layers._embeddings._data_embedding import ( 6 | DataEmbedding_inverted, 7 | ) 8 | from pytorch_forecasting.layers._embeddings._en_embedding import EnEmbedding 9 | from pytorch_forecasting.layers._embeddings._positional_embedding import ( 10 | PositionalEmbedding, 11 | ) 12 | from pytorch_forecasting.layers._embeddings._sub_nn import embedding_cat_variables 13 | 14 | __all__ = [ 15 | "PositionalEmbedding", 16 | "DataEmbedding_inverted", 17 | "EnEmbedding", 18 | "embedding_cat_variables", 19 | ] 20 | -------------------------------------------------------------------------------- /pytorch_forecasting/metrics/_point_pkg/_mase/_mase_pkg.py: -------------------------------------------------------------------------------- 1 | """ 2 | Package container for the Mean Absolute Scaled Error (MASE) metric for point forecasts. 3 | """ 4 | 5 | from pytorch_forecasting.metrics.base_metrics._base_object import _BasePtMetric 6 | 7 | 8 | class MASE_pkg(_BasePtMetric): 9 | """ 10 | Mean Average scaled Error (MASE) metric for point forecasts. 11 | """ 12 | 13 | _tags = { 14 | "metric_type": "point", 15 | "info:metric_name": "MASE", 16 | "requires:data_type": "point_forecast", 17 | } 18 | 19 | @classmethod 20 | def get_cls(cls): 21 | from pytorch_forecasting.metrics import MASE 22 | 23 | return MASE 24 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | # See https://pre-commit.com for more information 2 | # See https://pre-commit.com/hooks.html for more hooks 3 | repos: 4 | - repo: https://github.com/pre-commit/pre-commit-hooks 5 | rev: v4.6.0 6 | hooks: 7 | - id: trailing-whitespace 8 | - id: end-of-file-fixer 9 | - id: check-yaml 10 | - id: check-ast 11 | - repo: https://github.com/astral-sh/ruff-pre-commit 12 | rev: v0.6.9 13 | hooks: 14 | - id: ruff 15 | args: [--fix] 16 | - id: ruff-format 17 | - repo: https://github.com/nbQA-dev/nbQA 18 | rev: 1.8.7 19 | hooks: 20 | - id: nbqa-black 21 | - id: nbqa-ruff 22 | - id: nbqa-check-ast 23 | -------------------------------------------------------------------------------- /pytorch_forecasting/metrics/_point_pkg/_mae/_mae_pkg.py: -------------------------------------------------------------------------------- 1 | """ 2 | Package container for the Mean Absolute Error (MAE) metric for point forecasts. 3 | """ 4 | 5 | from pytorch_forecasting.metrics.base_metrics._base_object import _BasePtMetric 6 | 7 | 8 | class MAE_pkg(_BasePtMetric): 9 | """ 10 | Mean Average Error (MAE) metric for point forecasts. 11 | 12 | Defined as ``(y_pred - target).abs()``. 13 | """ 14 | 15 | _tags = { 16 | "metric_type": "point", 17 | "requires:data_type": "point_forecast", 18 | "info:metric_name": "MAE", 19 | } 20 | 21 | @classmethod 22 | def get_cls(cls): 23 | from pytorch_forecasting.metrics import MAE 24 | 25 | return MAE 26 | -------------------------------------------------------------------------------- /pytorch_forecasting/metrics/base_metrics/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Base classes for pytorch-forecasting metrics. 3 | """ 4 | 5 | from pytorch_forecasting.metrics.base_metrics._base_metrics import ( 6 | AggregationMetric, 7 | CompositeMetric, 8 | DistributionLoss, 9 | Metric, 10 | MultiHorizonMetric, 11 | MultiLoss, 12 | MultivariateDistributionLoss, 13 | convert_torchmetric_to_pytorch_forecasting_metric, 14 | ) 15 | 16 | __all__ = [ 17 | "Metric", 18 | "MultiHorizonMetric", 19 | "DistributionLoss", 20 | "MultivariateDistributionLoss", 21 | "MultiLoss", 22 | "convert_torchmetric_to_pytorch_forecasting_metric", 23 | "AggregationMetric", 24 | "CompositeMetric", 25 | ] 26 | -------------------------------------------------------------------------------- /.github/workflows/automerge.yml.disabled: -------------------------------------------------------------------------------- 1 | name: Dependabot auto-merge 2 | on: pull_request 3 | 4 | permissions: 5 | pull-requests: write 6 | contents: write 7 | 8 | jobs: 9 | dependabot: 10 | runs-on: ubuntu-latest 11 | if: ${{ github.actor == 'dependabot[bot]' }} 12 | steps: 13 | - name: Dependabot metadata 14 | id: metadata 15 | uses: dependabot/fetch-metadata@v1.1.1 16 | with: 17 | github-token: "${{ secrets.GITHUB_TOKEN }}" 18 | - name: Enable auto-merge for Dependabot PRs 19 | run: gh pr merge --auto --merge "$PR_URL" 20 | env: 21 | PR_URL: ${{github.event.pull_request.html_url}} 22 | GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} 23 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line, and also 5 | # from the environment for the first two. 6 | SPHINXOPTS ?= 7 | SPHINXBUILD ?= sphinx-build 8 | SOURCEDIR = source 9 | BUILDDIR = build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 21 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/doc_improvement.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "\U0001F4D6 Documentation improvement" 3 | about: Create a report to help us improve the documentation. Alternatively you can just open a pull request with the suggested change. 4 | title: "[DOC]" 5 | labels: documentation 6 | assignees: '' 7 | 8 | --- 9 | 10 | #### Describe the issue linked to the documentation 11 | 12 | 15 | 16 | #### Suggest a potential alternative/fix 17 | 18 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /pytorch_forecasting/models/base/__init__.py: -------------------------------------------------------------------------------- 1 | """Base classes for pytorch-foercasting models.""" 2 | 3 | from pytorch_forecasting.models.base._base_model import ( 4 | AutoRegressiveBaseModel, 5 | AutoRegressiveBaseModelWithCovariates, 6 | BaseModel, 7 | BaseModelWithCovariates, 8 | Prediction, 9 | ) 10 | from pytorch_forecasting.models.base._base_object import ( 11 | _BaseObject, 12 | _BasePtForecaster, 13 | _BasePtForecasterV2, 14 | ) 15 | 16 | __all__ = [ 17 | "_BaseObject", 18 | "_BasePtForecaster", 19 | "_BasePtForecasterV2", 20 | "AutoRegressiveBaseModel", 21 | "AutoRegressiveBaseModelWithCovariates", 22 | "BaseModel", 23 | "BaseModelWithCovariates", 24 | "Prediction", 25 | ] 26 | -------------------------------------------------------------------------------- /pytorch_forecasting/metrics/_point_pkg/_rmse/_rmse_pkg.py: -------------------------------------------------------------------------------- 1 | """ 2 | Package container for Root Mean Square Error (RMSE) metric for point forecasts. 3 | """ 4 | 5 | from pytorch_forecasting.metrics.base_metrics._base_object import _BasePtMetric 6 | 7 | 8 | class RMSE_pkg(_BasePtMetric): 9 | """ 10 | Root mean square error metric for point forecasts. 11 | 12 | Defined as ``(y_pred - target)**2``. 13 | """ 14 | 15 | _tags = { 16 | "metric_type": "point", 17 | "info:metric_name": "RMSE", 18 | "requires:data_type": "point_forecast", 19 | } # noqa: E501 20 | 21 | @classmethod 22 | def get_cls(cls): 23 | from pytorch_forecasting.metrics.point import RMSE 24 | 25 | return RMSE 26 | -------------------------------------------------------------------------------- /pytorch_forecasting/metrics/_point_pkg/_mape/_mape_pkg.py: -------------------------------------------------------------------------------- 1 | """ 2 | Package container for the Mean Absolute Percentage Error (MAPE) metric for point 3 | forecasts. 4 | """ 5 | 6 | from pytorch_forecasting.metrics.base_metrics._base_object import _BasePtMetric 7 | 8 | 9 | class MAPE_pkg(_BasePtMetric): 10 | """ 11 | Mean absolute percentage error metric for point forecasts. 12 | 13 | Defined as ``(y - y_pred).abs() / y.abs()``. 14 | Assumes ``y >= 0``. 15 | """ 16 | 17 | _tags = { 18 | "metric_type": "point", 19 | "info:metric_name": "MAPE", 20 | "requires:data_type": "point_forecast", 21 | } 22 | 23 | @classmethod 24 | def get_cls(cls): 25 | from pytorch_forecasting.metrics.point import MAPE 26 | 27 | return MAPE 28 | -------------------------------------------------------------------------------- /.readthedocs.yml: -------------------------------------------------------------------------------- 1 | # .readthedocs.yml 2 | # Read the Docs configuration file 3 | # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details 4 | 5 | # Required 6 | version: 2 7 | 8 | # Build documentation in the docs/ directory with Sphinx 9 | # reference: https://docs.readthedocs.io/en/stable/config-file/v2.html#sphinx 10 | sphinx: 11 | configuration: docs/source/conf.py 12 | # fail_on_warning: true 13 | 14 | # Build documentation with MkDocs 15 | #mkdocs: 16 | # configuration: mkdocs.yml 17 | 18 | # Optionally build your docs in additional formats such as PDF and ePub 19 | formats: 20 | - htmlzip 21 | 22 | build: 23 | os: ubuntu-22.04 24 | tools: 25 | python: "3.12" 26 | 27 | python: 28 | install: 29 | - method: pip 30 | path: . 31 | extra_requirements: 32 | - docs 33 | -------------------------------------------------------------------------------- /pytorch_forecasting/metrics/_point_pkg/_smape/_smape_pkg.py: -------------------------------------------------------------------------------- 1 | """ 2 | Package container for Symmetric Mean Absolute Percentage Error (SMAPE) metric for point 3 | forecasts. 4 | """ 5 | 6 | from pytorch_forecasting.metrics.base_metrics._base_object import _BasePtMetric 7 | 8 | 9 | class SMAPE_pkg(_BasePtMetric): 10 | """ 11 | Symmetric mean absolute percentage error metric for point forecasts. 12 | 13 | Defined as ``2*(y - y_pred).abs() / (y.abs() + y_pred.abs())``. 14 | Assumes ``y >= 0``. 15 | """ 16 | 17 | _tags = { 18 | "metric_type": "point", 19 | "info:metric_name": "SMAPE", 20 | "requires:data_type": "point_forecast", 21 | } # noqa: E501 22 | 23 | @classmethod 24 | def get_cls(cls): 25 | from pytorch_forecasting.metrics.point import SMAPE 26 | 27 | return SMAPE 28 | -------------------------------------------------------------------------------- /pytorch_forecasting/metrics/_point_pkg/_tweedie/_tweedie_loss_pkg.py: -------------------------------------------------------------------------------- 1 | """ 2 | Package container for the Tweedie loss metric for point forecasts. 3 | """ 4 | 5 | from pytorch_forecasting.metrics.base_metrics._base_object import _BasePtMetric 6 | 7 | 8 | class TweedieLoss_pkg(_BasePtMetric): 9 | """ 10 | Tweedie loss for regression with exponential dispersion models. 11 | 12 | Tweedie regression with log-link. Useful for modeling targets that might be tweedie-distributed. 13 | """ # noqa: E501 14 | 15 | _tags = { 16 | "metric_type": "point", 17 | "info:metric_name": "TweedieLoss", 18 | "requires:data_type": "point_forecast", 19 | } # noqa: E501 20 | 21 | @classmethod 22 | def get_cls(cls): 23 | from pytorch_forecasting.metrics.point import TweedieLoss 24 | 25 | return TweedieLoss 26 | -------------------------------------------------------------------------------- /pytorch_forecasting/data/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Datasets, etc. for timeseries data. 3 | 4 | Handling timeseries data is not trivial. It requires special treatment. 5 | This sub-package provides the necessary tools to abstracts the necessary work. 6 | """ 7 | 8 | from pytorch_forecasting.data.encoders import ( 9 | EncoderNormalizer, 10 | GroupNormalizer, 11 | MultiNormalizer, 12 | NaNLabelEncoder, 13 | TorchNormalizer, 14 | ) 15 | from pytorch_forecasting.data.samplers import TimeSynchronizedBatchSampler 16 | from pytorch_forecasting.data.timeseries import TimeSeries, TimeSeriesDataSet 17 | 18 | __all__ = [ 19 | "TimeSeriesDataSet", 20 | "TimeSeries", 21 | "NaNLabelEncoder", 22 | "GroupNormalizer", 23 | "TorchNormalizer", 24 | "EncoderNormalizer", 25 | "TimeSynchronizedBatchSampler", 26 | "MultiNormalizer", 27 | ] 28 | -------------------------------------------------------------------------------- /pytorch_forecasting/metrics/_distributions_pkg/_normal/_normal_distribution_loss_pkg.py: -------------------------------------------------------------------------------- 1 | """ 2 | Package container for the Normal distribution loss metric. 3 | """ 4 | 5 | from pytorch_forecasting.metrics.base_metrics._base_object import _BasePtMetric 6 | 7 | 8 | class NormalDistributionLoss_pkg(_BasePtMetric): 9 | """ 10 | Normal distribution loss metric for distribution forecasts. 11 | 12 | Defined as ``(y_pred - target)**2``. 13 | """ 14 | 15 | _tags = { 16 | "metric_type": "distribution", 17 | "distribution_type": "normal", 18 | "info:metric_name": "NormalDistributionLoss", 19 | "requires:data_type": "normal_distribution_forecast", 20 | } 21 | 22 | @classmethod 23 | def get_cls(cls): 24 | from pytorch_forecasting.metrics.distributions import NormalDistributionLoss 25 | 26 | return NormalDistributionLoss 27 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "\u2728 Feature request" 3 | about: Suggest an idea for this project 4 | title: '[ENH]' 5 | labels: enhancement 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen, ideally taking into consideration the existing toolbox design, classes and methods. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | 22 | 23 | -------------------------------------------------------------------------------- /pytorch_forecasting/metrics/_point_pkg/_poisson/_poisson_loss_pkg.py: -------------------------------------------------------------------------------- 1 | """ 2 | Package container for Poisson distribution loss metric for point forecasts. 3 | """ 4 | 5 | from pytorch_forecasting.metrics.base_metrics._base_object import _BasePtMetric 6 | 7 | 8 | class PoissonLoss_pkg(_BasePtMetric): 9 | """ 10 | Poisson loss for count data. 11 | 12 | The loss will take the exponential of the network output before it is returned as prediction. 13 | """ # noqa: E501 14 | 15 | _tags = { 16 | "metric_type": "point", 17 | "info:metric_name": "PoissonLoss", 18 | "requires:data_type": "point_forecast", 19 | "capability:quantile_generation": True, 20 | "shape:adds_quantile_dimension": True, 21 | } 22 | 23 | @classmethod 24 | def get_cls(cls): 25 | from pytorch_forecasting.metrics.point import PoissonLoss 26 | 27 | return PoissonLoss 28 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=sphinx-build 9 | ) 10 | set SOURCEDIR=source 11 | set BUILDDIR=build 12 | 13 | if "%1" == "" goto help 14 | 15 | %SPHINXBUILD% >NUL 2>NUL 16 | if errorlevel 9009 ( 17 | echo. 18 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 19 | echo.installed, then set the SPHINXBUILD environment variable to point 20 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 21 | echo.may add the Sphinx directory to PATH. 22 | echo. 23 | echo.If you don't have Sphinx installed, grab it from 24 | echo.http://sphinx-doc.org/ 25 | exit /b 1 26 | ) 27 | 28 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 29 | goto end 30 | 31 | :help 32 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 33 | 34 | :end 35 | popd 36 | -------------------------------------------------------------------------------- /pytorch_forecasting/metrics/_point_pkg/_cross_entropy/_cross_entropy_pkg.py: -------------------------------------------------------------------------------- 1 | """ 2 | Package container for the Cross Entropy Loss metric for point forecasts. 3 | """ 4 | 5 | from pytorch_forecasting.metrics.base_metrics._base_object import _BasePtMetric 6 | 7 | 8 | class CrossEntropy_pkg(_BasePtMetric): 9 | """ 10 | Package container for CrossEntropyLoss - a loss function for categorical targets. 11 | 12 | CrossEntropyLoss is used for multi-class classification tasks where the target is 13 | categorical. 14 | """ 15 | 16 | _tags = { 17 | "metric_type": "point_classification", 18 | "requires:data_type": "classification_forecast", 19 | "info:metric_name": "CrossEntropy", 20 | "no_rescaling": True, 21 | } 22 | 23 | @classmethod 24 | def get_cls(cls): 25 | from pytorch_forecasting.metrics import CrossEntropy 26 | 27 | return CrossEntropy 28 | -------------------------------------------------------------------------------- /pytorch_forecasting/models/timexer/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | TimeXer model for forecasting time series. 3 | """ 4 | 5 | from pytorch_forecasting.models.timexer._timexer import TimeXer 6 | from pytorch_forecasting.models.timexer._timexer_pkg import TimeXer_pkg 7 | from pytorch_forecasting.models.timexer._timexer_pkg_v2 import TimeXer_pkg_v2 8 | from pytorch_forecasting.models.timexer.sub_modules import ( 9 | AttentionLayer, 10 | DataEmbedding_inverted, 11 | Encoder, 12 | EncoderLayer, 13 | EnEmbedding, 14 | FlattenHead, 15 | FullAttention, 16 | PositionalEmbedding, 17 | TriangularCausalMask, 18 | ) 19 | 20 | __all__ = [ 21 | "TimeXer", 22 | "TriangularCausalMask", 23 | "FullAttention", 24 | "AttentionLayer", 25 | "DataEmbedding_inverted", 26 | "PositionalEmbedding", 27 | "FlattenHead", 28 | "EnEmbedding", 29 | "Encoder", 30 | "EncoderLayer", 31 | "TimeXer_pkg", 32 | "TimeXer_pkg_v2", 33 | ] 34 | -------------------------------------------------------------------------------- /pytorch_forecasting/metrics/_distributions_pkg/_multivariate_normal/_multivariate_normal_distribution_loss_pkg.py: -------------------------------------------------------------------------------- 1 | """ 2 | Package container for multivariate normal distribution loss metric. 3 | """ 4 | 5 | from pytorch_forecasting.metrics.base_metrics._base_object import _BasePtMetric 6 | 7 | 8 | class MultivariateNormalDistributionLoss_pkg(_BasePtMetric): 9 | """ 10 | Multivariate normal distribution loss metric for distribution forecasts. 11 | 12 | Defined as ``(y_pred - target)**2``. 13 | """ 14 | 15 | _tags = { 16 | "metric_type": "distribution", 17 | "distribution_type": "multivariate_normal", 18 | "info:metric_name": "MultivariateNormalDistributionLoss", 19 | "requires:data_type": "multivariate_normal_distribution_forecast", 20 | } 21 | 22 | @classmethod 23 | def get_cls(cls): 24 | from pytorch_forecasting.metrics.distributions import ( 25 | MultivariateNormalDistributionLoss, 26 | ) 27 | 28 | return MultivariateNormalDistributionLoss 29 | -------------------------------------------------------------------------------- /pytorch_forecasting/metrics/_quantile_pkg/_quantile_loss_pkg.py: -------------------------------------------------------------------------------- 1 | """ 2 | Package containers for Quantile Loss metric for quantile forecasts. 3 | """ 4 | 5 | from pytorch_forecasting.metrics.base_metrics._base_object import _BasePtMetric 6 | 7 | 8 | class QuantileLoss_pkg(_BasePtMetric): 9 | """ 10 | Quantile loss metric for quantile forecasts. 11 | 12 | Defined as ``(y_pred - target).abs()``. 13 | """ 14 | 15 | _tags = { 16 | "metric_type": "quantile", 17 | "info:metric_name": "QuantileLoss", 18 | "requires:data_type": "quantile_forecast", 19 | } # noqa: E501 20 | 21 | @classmethod 22 | def get_cls(cls): 23 | from pytorch_forecasting.metrics import QuantileLoss 24 | 25 | return QuantileLoss 26 | 27 | @classmethod 28 | def get_metric_test_params(cls): 29 | return [ 30 | { 31 | "quantiles": [0.1, 0.5, 0.9], 32 | }, 33 | { 34 | "quantiles": [0.2, 0.5], 35 | }, 36 | ] 37 | -------------------------------------------------------------------------------- /pytorch_forecasting/models/temporal_fusion_transformer/__init__.py: -------------------------------------------------------------------------------- 1 | """Temporal fusion transformer for forecasting timeseries.""" 2 | 3 | from pytorch_forecasting.models.temporal_fusion_transformer._tft import ( 4 | TemporalFusionTransformer, 5 | ) 6 | from pytorch_forecasting.models.temporal_fusion_transformer._tft_pkg import ( 7 | TemporalFusionTransformer_pkg, 8 | ) 9 | from pytorch_forecasting.models.temporal_fusion_transformer._tft_pkg_v2 import ( 10 | TFT_pkg_v2, 11 | ) 12 | from pytorch_forecasting.models.temporal_fusion_transformer.sub_modules import ( 13 | AddNorm, 14 | GateAddNorm, 15 | GatedLinearUnit, 16 | GatedResidualNetwork, 17 | InterpretableMultiHeadAttention, 18 | VariableSelectionNetwork, 19 | ) 20 | 21 | __all__ = [ 22 | "TemporalFusionTransformer", 23 | "AddNorm", 24 | "GateAddNorm", 25 | "GatedLinearUnit", 26 | "GatedResidualNetwork", 27 | "InterpretableMultiHeadAttention", 28 | "TFT_pkg_v2", 29 | "TemporalFusionTransformer_pkg", 30 | "VariableSelectionNetwork", 31 | ] 32 | -------------------------------------------------------------------------------- /pytorch_forecasting/metrics/_point_pkg/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Package container for point metrics in PyTorch Forecasting. 3 | """ 4 | 5 | from pytorch_forecasting.metrics._point_pkg._cross_entropy._cross_entropy_pkg import ( 6 | CrossEntropy_pkg, 7 | ) 8 | from pytorch_forecasting.metrics._point_pkg._mae._mae_pkg import MAE_pkg 9 | from pytorch_forecasting.metrics._point_pkg._mape._mape_pkg import MAPE_pkg 10 | from pytorch_forecasting.metrics._point_pkg._mase._mase_pkg import MASE_pkg 11 | from pytorch_forecasting.metrics._point_pkg._poisson._poisson_loss_pkg import ( 12 | PoissonLoss_pkg, 13 | ) 14 | from pytorch_forecasting.metrics._point_pkg._rmse._rmse_pkg import RMSE_pkg 15 | from pytorch_forecasting.metrics._point_pkg._smape._smape_pkg import SMAPE_pkg 16 | from pytorch_forecasting.metrics._point_pkg._tweedie._tweedie_loss_pkg import ( 17 | TweedieLoss_pkg, 18 | ) 19 | 20 | __all__ = [ 21 | "MAE_pkg", 22 | "MAPE_pkg", 23 | "PoissonLoss_pkg", 24 | "RMSE_pkg", 25 | "SMAPE_pkg", 26 | "TweedieLoss_pkg", 27 | "CrossEntropy_pkg", 28 | "MASE_pkg", 29 | ] 30 | -------------------------------------------------------------------------------- /pytorch_forecasting/metrics/_distributions_pkg/_beta/_beta_distribution_loss_pkg.py: -------------------------------------------------------------------------------- 1 | """ 2 | Package container for the Beta distribution loss metric. 3 | """ 4 | 5 | from pytorch_forecasting.data import TorchNormalizer 6 | from pytorch_forecasting.metrics.base_metrics._base_object import _BasePtMetric 7 | 8 | 9 | class BetaDistributionLoss_pkg(_BasePtMetric): 10 | """ 11 | Beta distribution loss metric for distribution forecasts. 12 | """ 13 | 14 | _tags = { 15 | "metric_type": "distribution", 16 | "distribution_type": "beta", 17 | "info:metric_name": "BetaDistributionLoss", 18 | "requires:data_type": "beta_distribution_forecast", 19 | } 20 | 21 | @classmethod 22 | def get_cls(cls): 23 | from pytorch_forecasting.metrics.distributions import BetaDistributionLoss 24 | 25 | return BetaDistributionLoss 26 | 27 | @classmethod 28 | def get_encoder(cls): 29 | """ 30 | Returns a TorchNormalizer instance for rescaling parameters. 31 | """ 32 | return TorchNormalizer(transformation="logit") 33 | -------------------------------------------------------------------------------- /docs/source/_static/custom.css: -------------------------------------------------------------------------------- 1 | .container-xl { 2 | max-width: 4000px; 3 | } 4 | 5 | .bd-content { 6 | flex-grow: 1; 7 | max-width: 100%; 8 | } 9 | .bd-page-width { 10 | max-width: 100rem; 11 | } 12 | .bd-main .bd-content .bd-article-container { 13 | max-width: 100%; 14 | } 15 | 16 | a.reference.internal.nav-link { 17 | color: #727991 !important; 18 | } 19 | 20 | html[data-theme="light"] { 21 | --pst-color-primary: #ee4c2c; 22 | } 23 | a.nav-link 24 | { 25 | color: #647db6 !important; 26 | } 27 | 28 | a.nav-link[href="https://github.com/sktime/pytorch-forecasting"] 29 | { 30 | color: #ee4c2c !important; 31 | } 32 | 33 | code { 34 | color: #d14 !important; 35 | } 36 | 37 | pre code, 38 | a > code, 39 | .reference { 40 | color: #647db6 !important; 41 | } 42 | 43 | dt:target, 44 | span.highlighted { 45 | background-color: #fff !important; 46 | } 47 | 48 | /* code highlighting */ 49 | .highlight > pre > .mi { 50 | color: #d14 !important; 51 | } 52 | .highlight > pre > .mf { 53 | color: #d14 !important; 54 | } 55 | 56 | .highlight > pre > .s2 { 57 | color: #647db6 !important; 58 | } 59 | -------------------------------------------------------------------------------- /pytorch_forecasting/models/nbeats/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | N-Beats model for timeseries forecasting without covariates. 3 | 4 | # TODO v2: remove compatibility imports, kept to avoid breaking existing code. 5 | """ 6 | 7 | # Import blocks from new location for backward compatibility 8 | from pytorch_forecasting.layers._nbeats._blocks import ( 9 | NBEATSGenericBlock, 10 | NBEATSSeasonalBlock, 11 | NBEATSTrendBlock, 12 | ) 13 | from pytorch_forecasting.models.nbeats._grid_callback import GridUpdateCallback 14 | from pytorch_forecasting.models.nbeats._nbeats import NBeats 15 | from pytorch_forecasting.models.nbeats._nbeats_adapter import NBeatsAdapter 16 | from pytorch_forecasting.models.nbeats._nbeats_pkg import NBeats_pkg 17 | from pytorch_forecasting.models.nbeats._nbeatskan import NBeatsKAN 18 | from pytorch_forecasting.models.nbeats._nbeatskan_pkg import NBeatsKAN_pkg 19 | 20 | __all__ = [ 21 | "NBeats", 22 | "NBeatsKAN", 23 | "NBeats_pkg", 24 | "NBeatsKAN_pkg", 25 | "NBEATSGenericBlock", 26 | "NBEATSSeasonalBlock", 27 | "NBEATSTrendBlock", 28 | "NBeatsAdapter", 29 | "GridUpdateCallback", 30 | ] 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | THE MIT License 2 | 3 | Copyright (c) 2020 - present, the pytorch-forecasting developers 4 | Copyright (c) 2020 Jan Beitner 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /pytorch_forecasting/tests/test_all_v2/_test_integration.py: -------------------------------------------------------------------------------- 1 | from typing import Any 2 | 3 | import torch 4 | 5 | from pytorch_forecasting.base._base_pkg import Base_pkg 6 | from pytorch_forecasting.data import TimeSeries 7 | 8 | 9 | def _integration( 10 | pkg: Base_pkg, 11 | test_data: dict[str, TimeSeries], 12 | datamodule_cfg: dict[str, Any], 13 | **kwargs, 14 | ): 15 | """Test integration of models with the `TimeSeries` and datamodules""" 16 | pkg.fit(test_data["train"]) 17 | 18 | predictions = pkg.predict( 19 | test_data["predict"], 20 | mode="raw", 21 | ) 22 | assert predictions is not None 23 | assert isinstance(predictions, dict) 24 | assert "prediction" in predictions 25 | 26 | pred_tensor = predictions["prediction"] 27 | assert isinstance(pred_tensor, torch.Tensor) 28 | assert pred_tensor.ndim == 3, f"Prediction must be 3D, got {pred_tensor.ndim}D" 29 | 30 | expected_pred_len = datamodule_cfg.get("prediction_length") 31 | if expected_pred_len: 32 | assert pred_tensor.shape[1] == expected_pred_len, ( 33 | f"Pred length mismatch: expected {expected_pred_len}, " 34 | f"got {pred_tensor.shape[1]}" 35 | ) 36 | -------------------------------------------------------------------------------- /pytorch_forecasting/metrics/_distributions_pkg/_negative_binomial/_negative_binomial_distribution_loss_pkg.py: -------------------------------------------------------------------------------- 1 | """ 2 | Package container for the Negative Binomial distribution loss metric. 3 | """ 4 | 5 | from pytorch_forecasting.data import TorchNormalizer 6 | from pytorch_forecasting.metrics.base_metrics._base_object import _BasePtMetric 7 | 8 | 9 | class NegativeBinomialDistributionLoss_pkg(_BasePtMetric): 10 | """ 11 | Negative binomial distribution loss metric for distribution forecasts. 12 | """ 13 | 14 | _tags = { 15 | "metric_type": "distribution", 16 | "distribution_type": "negative_binomial", 17 | "info:metric_name": "NegativeBinomialDistributionLoss", 18 | "requires:data_type": "negative_binomial_distribution_forecast", 19 | } 20 | 21 | @classmethod 22 | def get_cls(cls): 23 | from pytorch_forecasting.metrics.distributions import ( 24 | NegativeBinomialDistributionLoss, 25 | ) 26 | 27 | return NegativeBinomialDistributionLoss 28 | 29 | @classmethod 30 | def get_encoder(cls): 31 | """ 32 | Returns a TorchNormalizer instance for rescaling parameters. 33 | """ 34 | return TorchNormalizer(center=False) 35 | -------------------------------------------------------------------------------- /pytorch_forecasting/layers/_embeddings/_positional_embedding.py: -------------------------------------------------------------------------------- 1 | """ 2 | Positional Embedding Layer for PTF. 3 | """ 4 | 5 | import math 6 | from math import sqrt 7 | 8 | import numpy as np 9 | import torch 10 | import torch.nn as nn 11 | import torch.nn.functional as F 12 | 13 | 14 | class PositionalEmbedding(nn.Module): 15 | """ 16 | Positional embedding module for time series data. 17 | Args: 18 | d_model (int): Dimension of the model. 19 | max_len (int): Maximum length of the input sequence. Defaults to 5000.""" 20 | 21 | def __init__(self, d_model, max_len=5000): 22 | super().__init__() 23 | # Compute the positional encodings once in log space. 24 | pe = torch.zeros(max_len, d_model).float() 25 | pe.requires_grad = False 26 | 27 | position = torch.arange(0, max_len).float().unsqueeze(1) 28 | div_term = ( 29 | torch.arange(0, d_model, 2).float() * -(math.log(10000.0) / d_model) 30 | ).exp() 31 | 32 | pe[:, 0::2] = torch.sin(position * div_term) 33 | pe[:, 1::2] = torch.cos(position * div_term) 34 | 35 | pe = pe.unsqueeze(0) 36 | self.register_buffer("pe", pe) 37 | 38 | def forward(self, x): 39 | return self.pe[:, : x.size(1)] 40 | -------------------------------------------------------------------------------- /pytorch_forecasting/layers/_encoders/_encoder.py: -------------------------------------------------------------------------------- 1 | """ 2 | Implementation of encoder layers from `nn.Module`. 3 | """ 4 | 5 | import math 6 | from math import sqrt 7 | 8 | import numpy as np 9 | import torch 10 | import torch.nn as nn 11 | import torch.nn.functional as F 12 | 13 | 14 | class Encoder(nn.Module): 15 | """ 16 | Encoder module for the TimeXer model. 17 | Args: 18 | layers (list): List of encoder layers. 19 | norm_layer (nn.Module, optional): Normalization layer. Defaults to None. 20 | projection (nn.Module, optional): Projection layer. Defaults to None. 21 | """ 22 | 23 | def __init__(self, layers, norm_layer=None, projection=None): 24 | super().__init__() 25 | self.layers = nn.ModuleList(layers) 26 | self.norm = norm_layer 27 | self.projection = projection 28 | 29 | def forward(self, x, cross, x_mask=None, cross_mask=None, tau=None, delta=None): 30 | for layer in self.layers: 31 | x = layer( 32 | x, cross, x_mask=x_mask, cross_mask=cross_mask, tau=tau, delta=delta 33 | ) 34 | 35 | if self.norm is not None: 36 | x = self.norm(x) 37 | 38 | if self.projection is not None: 39 | x = self.projection(x) 40 | return x 41 | -------------------------------------------------------------------------------- /pytorch_forecasting/layers/_nbeats/_utils.py: -------------------------------------------------------------------------------- 1 | """ 2 | Utility functions for N-BEATS model implementation. 3 | """ 4 | 5 | import numpy as np 6 | import torch.nn as nn 7 | 8 | 9 | def linear(input_size, output_size, bias=True, dropout: int = None): 10 | """ 11 | Initialize linear layers for MLP block layers. 12 | """ 13 | lin = nn.Linear(input_size, output_size, bias=bias) 14 | if dropout is not None: 15 | return nn.Sequential(nn.Dropout(dropout), lin) 16 | else: 17 | return lin 18 | 19 | 20 | def linspace( 21 | backcast_length: int, forecast_length: int, centered: bool = False 22 | ) -> tuple[np.ndarray, np.ndarray]: 23 | """ 24 | Generate linear spaced values for backcast and forecast. 25 | """ 26 | if centered: 27 | norm = max(backcast_length, forecast_length) 28 | start = -backcast_length 29 | stop = forecast_length - 1 30 | else: 31 | norm = backcast_length + forecast_length 32 | start = 0 33 | stop = backcast_length + forecast_length - 1 34 | lin_space = np.linspace( 35 | start / norm, stop / norm, backcast_length + forecast_length, dtype=np.float32 36 | ) 37 | b_ls = lin_space[:backcast_length] 38 | f_ls = lin_space[backcast_length:] 39 | return b_ls, f_ls 40 | -------------------------------------------------------------------------------- /pytorch_forecasting/layers/_embeddings/_data_embedding.py: -------------------------------------------------------------------------------- 1 | """ 2 | Data embedding layer for exogenous variables. 3 | """ 4 | 5 | import math 6 | from math import sqrt 7 | 8 | import numpy as np 9 | import torch 10 | import torch.nn as nn 11 | import torch.nn.functional as F 12 | 13 | 14 | class DataEmbedding_inverted(nn.Module): 15 | """ 16 | Data embedding module for time series data. 17 | Args: 18 | c_in (int): Number of input features. 19 | d_model (int): Dimension of the model. 20 | embed_type (str): Type of embedding to use. Defaults to "fixed". 21 | freq (str): Frequency of the time series data. Defaults to "h". 22 | dropout (float): Dropout rate. Defaults to 0.1. 23 | """ 24 | 25 | def __init__(self, c_in, d_model, dropout=0.1): 26 | super().__init__() 27 | self.value_embedding = nn.Linear(c_in, d_model) 28 | self.dropout = nn.Dropout(p=dropout) 29 | 30 | def forward(self, x, x_mark): 31 | x = x.permute(0, 2, 1) 32 | # x: [Batch Variate Time] 33 | if x_mark is None: 34 | x = self.value_embedding(x) 35 | else: 36 | x = self.value_embedding(torch.cat([x, x_mark.permute(0, 2, 1)], 1)) 37 | # x: [Batch Variate d_model] 38 | return self.dropout(x) 39 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "\U0001F41B Bug report" 3 | about: Create a report to help us improve 4 | title: "[BUG]" 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | 14 | 15 | **To Reproduce** 16 | 21 | 22 | ```python 23 | 24 | ``` 25 | 26 | **Expected behavior** 27 | 30 | 31 | **Additional context** 32 | 35 | 36 | **Versions** 37 |
38 | 39 | 44 | 45 |
46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /pytorch_forecasting/layers/_decomposition/_series_decomp.py: -------------------------------------------------------------------------------- 1 | """ 2 | Series Decomposition Block for time series forecasting models. 3 | """ 4 | 5 | import torch 6 | import torch.nn as nn 7 | import torch.nn.functional as F 8 | 9 | from pytorch_forecasting.layers._filter._moving_avg_filter import MovingAvg 10 | 11 | 12 | class SeriesDecomposition(nn.Module): 13 | """ 14 | Series decomposition block from Autoformer. 15 | 16 | Decomposes time series into trend and seasonal components using 17 | moving average filtering. 18 | 19 | Args: 20 | kernel_size (int): 21 | Size of the moving average kernel for trend extraction. 22 | """ 23 | 24 | def __init__(self, kernel_size): 25 | super().__init__() 26 | self.moving_avg = MovingAvg(kernel_size, stride=1) 27 | 28 | def forward(self, x): 29 | """ 30 | Forward pass for series decomposition. 31 | 32 | Args: 33 | x (torch.Tensor): 34 | Input time series tensor of shape (batch_size, seq_len, features). 35 | 36 | Returns: 37 | tuple: 38 | - trend (torch.Tensor): Trend component of the time series. 39 | - seasonal (torch.Tensor): Seasonal component of the time series. 40 | """ 41 | trend = self.moving_avg(x) 42 | seasonal = x - trend 43 | return seasonal, trend 44 | -------------------------------------------------------------------------------- /pytorch_forecasting/utils/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | PyTorch Forecasting package for timeseries forecasting with PyTorch. 3 | """ 4 | 5 | from pytorch_forecasting.utils._estimator_checks import ( 6 | check_estimator, 7 | parametrize_with_checks, 8 | ) 9 | from pytorch_forecasting.utils._utils import ( 10 | InitialParameterRepresenterMixIn, 11 | OutputMixIn, 12 | TupleOutputMixIn, 13 | apply_to_list, 14 | autocorrelation, 15 | concat_sequences, 16 | create_mask, 17 | detach, 18 | get_embedding_size, 19 | groupby_apply, 20 | integer_histogram, 21 | masked_op, 22 | move_to_device, 23 | padded_stack, 24 | profile, 25 | redirect_stdout, 26 | repr_class, 27 | to_list, 28 | unpack_sequence, 29 | unsqueeze_like, 30 | ) 31 | 32 | __all__ = [ 33 | "InitialParameterRepresenterMixIn", 34 | "OutputMixIn", 35 | "TupleOutputMixIn", 36 | "apply_to_list", 37 | "autocorrelation", 38 | "get_embedding_size", 39 | "concat_sequences", 40 | "create_mask", 41 | "to_list", 42 | "RecurrentNetwork", 43 | "DecoderMLP", 44 | "detach", 45 | "masked_op", 46 | "move_to_device", 47 | "integer_histogram", 48 | "groupby_apply", 49 | "padded_stack", 50 | "profile", 51 | "redirect_stdout", 52 | "repr_class", 53 | "unpack_sequence", 54 | "unsqueeze_like", 55 | "check_estimator", 56 | "parametrize_with_checks", 57 | ] 58 | -------------------------------------------------------------------------------- /pytorch_forecasting/metrics/_distributions_pkg/_mqf2/_mqf2_distribution_loss_pkg.py: -------------------------------------------------------------------------------- 1 | """ 2 | Package container for the MQF2 distribution loss metric. 3 | """ 4 | 5 | from pytorch_forecasting.data import TorchNormalizer 6 | from pytorch_forecasting.metrics.base_metrics._base_object import _BasePtMetric 7 | 8 | 9 | class MQF2DistributionLoss_pkg(_BasePtMetric): 10 | """ 11 | MQF2 distribution loss metric for distribution forecasts. 12 | """ 13 | 14 | _tags = { 15 | "metric_type": "distribution", 16 | "distribution_type": "mqf2", 17 | "info:metric_name": "MQF2DistributionLoss", 18 | "python_dependencies": ["cpflows"], 19 | "capability:quantile_generation": True, 20 | "requires:data_type": "mqf2_distribution_forecast", 21 | } 22 | 23 | @classmethod 24 | def get_cls(cls): 25 | from pytorch_forecasting.metrics.distributions import MQF2DistributionLoss 26 | 27 | return MQF2DistributionLoss 28 | 29 | @classmethod 30 | def get_encoder(cls): 31 | """ 32 | Returns a TorchNormalizer instance for rescaling parameters. 33 | """ 34 | return TorchNormalizer() 35 | 36 | @classmethod 37 | def get_metric_test_params(cls): 38 | """ 39 | Returns test parameters for the MQF2 distribution loss metric. 40 | """ 41 | 42 | return [ 43 | { 44 | "prediction_length": 10, 45 | }, 46 | ] 47 | -------------------------------------------------------------------------------- /tests/test_data/test_samplers.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import torch 3 | from torch.utils.data.sampler import SequentialSampler 4 | 5 | from pytorch_forecasting.data import TimeSynchronizedBatchSampler 6 | 7 | 8 | @pytest.mark.parametrize( 9 | "drop_last,shuffle,as_string,batch_size", 10 | [ 11 | (True, True, True, 64), 12 | (False, False, False, 64), 13 | (True, False, False, 1000), 14 | ], 15 | ) 16 | def test_TimeSynchronizedBatchSampler( 17 | test_dataset, shuffle, drop_last, as_string, batch_size 18 | ): 19 | if as_string: 20 | dataloader = test_dataset.to_dataloader( 21 | batch_sampler="synchronized", 22 | shuffle=shuffle, 23 | drop_last=drop_last, 24 | batch_size=batch_size, 25 | ) 26 | else: 27 | sampler = TimeSynchronizedBatchSampler( 28 | SequentialSampler(test_dataset), 29 | shuffle=shuffle, 30 | drop_last=drop_last, 31 | batch_size=batch_size, 32 | ) 33 | dataloader = test_dataset.to_dataloader(batch_sampler=sampler) 34 | 35 | time_idx_pos = test_dataset.reals.index("time_idx") 36 | for x, _ in iter(dataloader): # check all samples 37 | time_idx_of_first_prediction = x["decoder_cont"][:, 0, time_idx_pos] 38 | assert torch.isclose( 39 | time_idx_of_first_prediction, time_idx_of_first_prediction[0] 40 | ).all(), "Time index should be the same for the first prediction" 41 | -------------------------------------------------------------------------------- /pytorch_forecasting/models/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Models for timeseries forecasting. 3 | """ 4 | 5 | from pytorch_forecasting.models.base import ( 6 | AutoRegressiveBaseModel, 7 | AutoRegressiveBaseModelWithCovariates, 8 | BaseModel, 9 | BaseModelWithCovariates, 10 | ) 11 | from pytorch_forecasting.models.baseline import Baseline 12 | from pytorch_forecasting.models.deepar import DeepAR 13 | from pytorch_forecasting.models.mlp import DecoderMLP 14 | from pytorch_forecasting.models.nbeats import NBeats, NBeatsKAN 15 | from pytorch_forecasting.models.nhits import NHiTS 16 | from pytorch_forecasting.models.nn import GRU, LSTM, MultiEmbedding, get_rnn 17 | from pytorch_forecasting.models.rnn import RecurrentNetwork 18 | from pytorch_forecasting.models.temporal_fusion_transformer import ( 19 | TemporalFusionTransformer, 20 | ) 21 | from pytorch_forecasting.models.tide import TiDEModel 22 | from pytorch_forecasting.models.timexer import TimeXer 23 | from pytorch_forecasting.models.xlstm import xLSTMTime 24 | 25 | __all__ = [ 26 | "NBeats", 27 | "NBeatsKAN", 28 | "NHiTS", 29 | "TemporalFusionTransformer", 30 | "RecurrentNetwork", 31 | "DeepAR", 32 | "BaseModel", 33 | "Baseline", 34 | "BaseModelWithCovariates", 35 | "AutoRegressiveBaseModel", 36 | "AutoRegressiveBaseModelWithCovariates", 37 | "get_rnn", 38 | "LSTM", 39 | "GRU", 40 | "MultiEmbedding", 41 | "DecoderMLP", 42 | "TiDEModel", 43 | "TimeXer", 44 | "xLSTMTime", 45 | ] 46 | -------------------------------------------------------------------------------- /tests/test_models/test_nn/test_embeddings.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import torch 3 | 4 | from pytorch_forecasting import MultiEmbedding 5 | 6 | 7 | @pytest.mark.parametrize( 8 | "kwargs", 9 | [ 10 | dict(embedding_sizes=(10, 10, 10)), 11 | dict(embedding_sizes=((10, 3), (10, 2), (10, 1))), 12 | dict(x_categoricals=["x1", "x2", "x3"], embedding_sizes=dict(x1=(10, 10))), 13 | dict( 14 | x_categoricals=["x1", "x2", "x3"], 15 | embedding_sizes=dict(x1=(10, 2), xg1=(10, 3)), 16 | categorical_groups=dict(xg1=["x2", "x3"]), 17 | ), 18 | ], 19 | ) 20 | def test_MultiEmbedding(kwargs): 21 | x = torch.randint(0, 10, size=(4, 3)) 22 | embedding = MultiEmbedding(**kwargs) 23 | assert embedding.input_size == x.size( 24 | 1 25 | ), "Input size should be equal to number of features" 26 | out = embedding(x) 27 | if isinstance(out, dict): 28 | assert isinstance(kwargs["embedding_sizes"], dict) 29 | for name, o in out.items(): 30 | assert ( 31 | o.size(1) == embedding.output_size[name] 32 | ), "Output size should be equal to number of embedding dimensions" 33 | elif isinstance(out, torch.Tensor): 34 | assert isinstance(kwargs["embedding_sizes"], (tuple, list)) 35 | assert ( 36 | out.size(1) == embedding.output_size 37 | ), "Output size should be equal to number of summed embedding dimensions" 38 | else: 39 | raise ValueError(f"Unknown output type {type(out)}") 40 | -------------------------------------------------------------------------------- /docs/source/_templates/custom-module-template.rst: -------------------------------------------------------------------------------- 1 | {{ fullname.split(".")[-1] | escape | underline}} 2 | 3 | .. automodule:: {{ fullname }} 4 | 5 | {% block attributes %} 6 | {% if attributes %} 7 | .. rubric:: Module Attributes 8 | 9 | .. autosummary:: 10 | :toctree: 11 | {% for item in attributes %} 12 | {{ item }} 13 | {%- endfor %} 14 | {% endif %} 15 | {% endblock %} 16 | 17 | {% block functions %} 18 | {% if functions %} 19 | .. rubric:: {{ _('Functions') }} 20 | 21 | .. autosummary:: 22 | :toctree: 23 | :template: custom-base-template.rst 24 | {% for item in functions %} 25 | {{ item }} 26 | {%- endfor %} 27 | {% endif %} 28 | {% endblock %} 29 | 30 | {% block classes %} 31 | {% if classes %} 32 | .. rubric:: {{ _('Classes') }} 33 | 34 | .. autosummary:: 35 | :toctree: 36 | :template: custom-class-template.rst 37 | {% for item in classes %} 38 | {{ item }} 39 | {%- endfor %} 40 | {% endif %} 41 | {% endblock %} 42 | 43 | {% block exceptions %} 44 | {% if exceptions %} 45 | .. rubric:: {{ _('Exceptions') }} 46 | 47 | .. autosummary:: 48 | :toctree: 49 | {% for item in exceptions %} 50 | {{ item }} 51 | {%- endfor %} 52 | {% endif %} 53 | {% endblock %} 54 | 55 | {% block modules %} 56 | {% if all_modules %} 57 | .. rubric:: Modules 58 | 59 | .. autosummary:: 60 | :toctree: 61 | :template: custom-module-template.rst 62 | :recursive: 63 | {% for item in all_modules %} 64 | {{ item }} 65 | {%- endfor %} 66 | {% endif %} 67 | {% endblock %} 68 | -------------------------------------------------------------------------------- /pytorch_forecasting/layers/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Architectural deep learning layers from `nn.Module`. 3 | """ 4 | 5 | from pytorch_forecasting.layers._attention import ( 6 | AttentionLayer, 7 | FullAttention, 8 | TriangularCausalMask, 9 | ) 10 | from pytorch_forecasting.layers._blocks import ResidualBlock 11 | from pytorch_forecasting.layers._decomposition import SeriesDecomposition 12 | from pytorch_forecasting.layers._embeddings import ( 13 | DataEmbedding_inverted, 14 | EnEmbedding, 15 | PositionalEmbedding, 16 | embedding_cat_variables, 17 | ) 18 | from pytorch_forecasting.layers._encoders import ( 19 | Encoder, 20 | EncoderLayer, 21 | ) 22 | from pytorch_forecasting.layers._normalization import RevIN 23 | from pytorch_forecasting.layers._output._flatten_head import ( 24 | FlattenHead, 25 | ) 26 | from pytorch_forecasting.layers._recurrent._mlstm import ( 27 | mLSTMCell, 28 | mLSTMLayer, 29 | mLSTMNetwork, 30 | ) 31 | from pytorch_forecasting.layers._recurrent._slstm import ( 32 | sLSTMCell, 33 | sLSTMLayer, 34 | sLSTMNetwork, 35 | ) 36 | 37 | __all__ = [ 38 | "FullAttention", 39 | "AttentionLayer", 40 | "TriangularCausalMask", 41 | "DataEmbedding_inverted", 42 | "EnEmbedding", 43 | "PositionalEmbedding", 44 | "Encoder", 45 | "EncoderLayer", 46 | "FlattenHead", 47 | "mLSTMCell", 48 | "mLSTMLayer", 49 | "mLSTMNetwork", 50 | "sLSTMCell", 51 | "sLSTMLayer", 52 | "sLSTMNetwork", 53 | "SeriesDecomposition", 54 | "RevIN", 55 | "ResidualBlock", 56 | "embedding_cat_variables", 57 | ] 58 | -------------------------------------------------------------------------------- /tests/test_utils/test_show_versions.py: -------------------------------------------------------------------------------- 1 | """Tests for the show_versions utility.""" 2 | 3 | import pathlib 4 | import uuid 5 | 6 | from pytorch_forecasting.utils._maint._show_versions import ( 7 | DEFAULT_DEPS_TO_SHOW, 8 | _get_deps_info, 9 | show_versions, 10 | ) 11 | 12 | 13 | def test_show_versions_runs(): 14 | """Test that show_versions runs without exceptions.""" 15 | # only prints, should return None 16 | assert show_versions() is None 17 | 18 | 19 | def test_show_versions_import_loc(): 20 | """Test that show_version can be imported from root.""" 21 | from pytorch_forecasting import show_versions as show_versions_imported 22 | 23 | assert show_versions == show_versions_imported 24 | 25 | 26 | def test_deps_info(): 27 | """Test that _get_deps_info returns package/version dict as per contract.""" 28 | deps_info = _get_deps_info() 29 | assert isinstance(deps_info, dict) 30 | assert set(deps_info.keys()) == {"pytorch-forecasting"} 31 | 32 | deps_info_default = _get_deps_info(DEFAULT_DEPS_TO_SHOW) 33 | assert isinstance(deps_info_default, dict) 34 | assert set(deps_info_default.keys()) == set(DEFAULT_DEPS_TO_SHOW) 35 | 36 | 37 | def test_deps_info_deps_missing_package_present_directory(): 38 | """Test that _get_deps_info does not fail if a dependency is missing.""" 39 | dummy_package_name = uuid.uuid4().hex 40 | 41 | dummy_folder_path = pathlib.Path(dummy_package_name) 42 | dummy_folder_path.mkdir() 43 | 44 | assert _get_deps_info([dummy_package_name]) == {dummy_package_name: None} 45 | 46 | dummy_folder_path.rmdir() 47 | -------------------------------------------------------------------------------- /docs/source/_templates/custom-class-template.rst: -------------------------------------------------------------------------------- 1 | {{ fullname.split(".")[-1] | escape | underline}} 2 | 3 | .. currentmodule:: {{ module }} 4 | 5 | 6 | .. autoclass:: {{ objname }} 7 | :members: 8 | :show-inheritance: 9 | :exclude-members: __init__ 10 | {% set allow_inherited = "zero_grad" not in inherited_members %} {# no inheritance for torch.nn.Modules #} 11 | {%if allow_inherited %} 12 | :inherited-members: 13 | {% endif %} 14 | 15 | {% block methods %} 16 | {% set allowed_methods = [] %} 17 | {% for item in methods %}{% if not item.startswith("_") and (item not in inherited_members or allow_inherited) %} 18 | {% set a=allowed_methods.append(item) %} 19 | {% endif %}{%- endfor %} 20 | {% if allowed_methods %} 21 | .. rubric:: {{ _('Methods') }} 22 | 23 | .. autosummary:: 24 | {% for item in allowed_methods %} 25 | ~{{ name }}.{{ item }} 26 | {%- endfor %} 27 | {% endif %} 28 | {% endblock %} 29 | 30 | {% block attributes %} 31 | {% set dynamic_attributes = [] %} {# dynamic attributes are not documented #} 32 | {% set allowed_attributes = [] %} 33 | {% for item in attributes %}{% if not item.startswith("_") and (item not in inherited_members or allow_inherited) and (item not in dynamic_attributes) and allow_inherited %} 34 | {% set a=allowed_attributes.append(item) %} 35 | {% endif %}{%- endfor %} 36 | {% if allowed_attributes %} 37 | .. rubric:: {{ _('Attributes') }} 38 | 39 | .. autosummary:: 40 | {% for item in allowed_attributes %} 41 | ~{{ name }}.{{ item }} 42 | {%- endfor %} 43 | {% endif %} 44 | {% endblock %} 45 | -------------------------------------------------------------------------------- /pytorch_forecasting/models/nbeats/_grid_callback.py: -------------------------------------------------------------------------------- 1 | from lightning.pytorch.callbacks import Callback 2 | 3 | 4 | class GridUpdateCallback(Callback): 5 | """ 6 | Custom callback to update the grid of the model during training at regular 7 | intervals. 8 | 9 | Parameters 10 | ---------- 11 | update_interval : int 12 | The frequency at which the grid is updated. 13 | 14 | Examples 15 | -------- 16 | See the full example in: 17 | `examples/nbeats_with_kan.py` 18 | """ 19 | 20 | def __init__(self, update_interval): 21 | self.update_interval = update_interval 22 | 23 | def on_train_batch_end(self, trainer, pl_module, outputs, batch, batch_idx): 24 | """ 25 | Hook called at the end of each training batch. 26 | 27 | Updates the grid of KAN layers if the current step is a multiple of the update 28 | interval. 29 | 30 | Parameters 31 | ---------- 32 | trainer : Trainer 33 | The PyTorch Lightning Trainer object. 34 | pl_module : LightningModule 35 | The model being trained (LightningModule). 36 | outputs : Any 37 | Outputs from the model for the current batch. 38 | batch : Any 39 | The current batch of data. 40 | batch_idx : int 41 | Index of the current batch. 42 | """ 43 | # Check if the current step is a multiple of the update interval 44 | if (trainer.global_step + 1) % self.update_interval == 0: 45 | # Call the model's update_kan_grid method 46 | pl_module.update_kan_grid() 47 | -------------------------------------------------------------------------------- /pytorch_forecasting/layers/_output/_flatten_head.py: -------------------------------------------------------------------------------- 1 | """ 2 | Implementation of output layers from `nn.Module` for TimeXer model. 3 | """ 4 | 5 | import math 6 | from math import sqrt 7 | 8 | import numpy as np 9 | import torch 10 | import torch.nn as nn 11 | import torch.nn.functional as F 12 | 13 | 14 | class FlattenHead(nn.Module): 15 | """ 16 | Flatten head for the output of the model. 17 | Args: 18 | n_vars (int): Number of input features. 19 | nf (int): Number of features in the last layer. 20 | target_window (int): Target window size. 21 | head_dropout (float): Dropout rate for the head. Defaults to 0. 22 | n_quantiles (int, optional): Number of quantiles. Defaults to None.""" 23 | 24 | def __init__(self, n_vars, nf, target_window, head_dropout=0, n_quantiles=None): 25 | super().__init__() 26 | self.n_vars = n_vars 27 | self.flatten = nn.Flatten(start_dim=-2) 28 | self.linear = nn.Linear(nf, target_window) 29 | self.n_quantiles = n_quantiles 30 | 31 | if self.n_quantiles is not None: 32 | self.linear = nn.Linear(nf, target_window * n_quantiles) 33 | else: 34 | self.linear = nn.Linear(nf, target_window) 35 | self.dropout = nn.Dropout(head_dropout) 36 | 37 | def forward(self, x): 38 | x = self.flatten(x) 39 | x = self.linear(x) 40 | x = self.dropout(x) 41 | x = x.permute(0, 2, 1) 42 | 43 | if self.n_quantiles is not None: 44 | batch_size = x.shape[0] 45 | x = x.reshape(batch_size, -1, self.n_quantiles) 46 | return x 47 | -------------------------------------------------------------------------------- /docs/source/data.rst: -------------------------------------------------------------------------------- 1 | Data 2 | ===== 3 | 4 | .. currentmodule:: pytorch_forecasting.data 5 | 6 | Loading data for timeseries forecasting is not trivial - in particular if covariates are included and values are missing. 7 | PyTorch Forecasting provides the :py:class:`~timeseries.TimeSeriesDataSet` which comes with a :py:meth:`~timeseries.TimeSeriesDataSet.to_dataloader` 8 | method to convert it to a dataloader and a :py:meth:`~timeseries.TimeSeriesDataSet.from_dataset` method to create, e.g. a validation 9 | or test dataset from a training dataset using the same label encoders and data normalization. 10 | 11 | Further, timeseries have to be (almost always) normalized for a neural network to learn efficiently. PyTorch Forecasting 12 | provides multiple such target normalizers (some of which can also be used for normalizing covariates). 13 | 14 | 15 | Time series data set 16 | --------------------- 17 | 18 | The time series dataset is the central data-holding object in PyTorch Forecasting. It primarily takes 19 | a pandas DataFrame along with some metadata. See the :ref:`tutorial on passing data to models ` to learn more it is coupled to models. 20 | 21 | .. autoclass:: pytorch_forecasting.data.timeseries.TimeSeriesDataSet 22 | :noindex: 23 | :members: __init__ 24 | 25 | Details 26 | -------- 27 | 28 | See the API documentation for further details on available data encoders and the :py:class:`~timeseries.TimeSeriesDataSet`: 29 | 30 | .. currentmodule:: pytorch_forecasting 31 | 32 | .. moduleautosummary:: 33 | :toctree: api/ 34 | :template: custom-module-template.rst 35 | :recursive: 36 | 37 | pytorch_forecasting.data 38 | -------------------------------------------------------------------------------- /pytorch_forecasting/metrics/tests/_config.py: -------------------------------------------------------------------------------- 1 | """Test configs.""" 2 | 3 | # list of str, names of metrics to exclude from testing 4 | # WARNING: tests for these metrics will be skipped 5 | EXCLUDE_METRICS = [ 6 | "DummySkipped", 7 | "ClassName", # exclude classes from extension templates 8 | "MASE_pkg", 9 | ] 10 | 11 | # dictionary of lists of str, names of tests to exclude from testing 12 | # keys are class names of estimators, values are lists of test names to exclude 13 | # WARNING: tests with these names will be skipped 14 | EXCLUDED_TESTS = { 15 | # MQF2DistributionLoss: Skipped because its to_quantiles and to_prediction 16 | # implementations do not conform to the base contract and are central to its 17 | # probabilistic forecasting design (uses distributional outputs). 18 | "MQF2DistributionLoss": [ 19 | "test_to_prediction", 20 | "test_to_quantiles", 21 | "test_loss_method", 22 | ], 23 | # PoissonLoss: Skipped because its output and quantile logic are not compatible 24 | # with the standard metric API contract, due to its discrete distribution nature. 25 | "PoissonLoss": [ 26 | "test_to_quantiles", 27 | ], 28 | # MultivariateNormalDistributionLoss: Skipped because its loss method is not 29 | # compatible with the standard metric API contract, as it uses a distributional 30 | # output that does not conform to the expected tensor shape. It returns a scalar 31 | # tensor with no dimensions, i.e this tensor is computed across all batches for each 32 | # timestep and then summed across all timesteps, to get a scalar value. 33 | "MultivariateNormalDistributionLoss": ["test_loss_method"], 34 | } 35 | -------------------------------------------------------------------------------- /pytorch_forecasting/metrics/_distributions_pkg/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Package container files for distribution metrics in PyTorch Forecasting. 3 | """ 4 | 5 | from pytorch_forecasting.metrics._distributions_pkg._beta._beta_distribution_loss_pkg import ( # noqa: E501 6 | BetaDistributionLoss_pkg, 7 | ) 8 | from pytorch_forecasting.metrics._distributions_pkg._implicit_quantile_network._implicit_quantile_network_distribution_loss_pkg import ( # noqa: E501 9 | ImplicitQuantileNetworkDistributionLoss_pkg, 10 | ) 11 | from pytorch_forecasting.metrics._distributions_pkg._log_normal._log_normal_distribution_loss_pkg import ( # noqa: E501 12 | LogNormalDistributionLoss_pkg, 13 | ) 14 | from pytorch_forecasting.metrics._distributions_pkg._mqf2._mqf2_distribution_loss_pkg import ( # noqa: E501 15 | MQF2DistributionLoss_pkg, 16 | ) 17 | from pytorch_forecasting.metrics._distributions_pkg._multivariate_normal._multivariate_normal_distribution_loss_pkg import ( # noqa: E501 18 | MultivariateNormalDistributionLoss_pkg, 19 | ) 20 | from pytorch_forecasting.metrics._distributions_pkg._negative_binomial._negative_binomial_distribution_loss_pkg import ( # noqa: E501 21 | NegativeBinomialDistributionLoss_pkg, 22 | ) 23 | from pytorch_forecasting.metrics._distributions_pkg._normal._normal_distribution_loss_pkg import ( # noqa: E501 24 | NormalDistributionLoss_pkg, 25 | ) 26 | 27 | __all__ = [ 28 | "BetaDistributionLoss_pkg", 29 | "ImplicitQuantileNetworkDistributionLoss_pkg", 30 | "LogNormalDistributionLoss_pkg", 31 | "MultivariateNormalDistributionLoss_pkg", 32 | "NegativeBinomialDistributionLoss_pkg", 33 | "NormalDistributionLoss_pkg", 34 | "MQF2DistributionLoss_pkg", 35 | ] 36 | -------------------------------------------------------------------------------- /pytorch_forecasting/layers/_blocks/_residual_block_dsipts.py: -------------------------------------------------------------------------------- 1 | import torch.nn as nn 2 | 3 | 4 | class ResidualBlock(nn.Module): 5 | def __init__( 6 | self, in_size: int, out_size: int, dropout_rate: float, activation_fun: str = "" 7 | ): 8 | """Residual Block as basic layer of the architecture. 9 | 10 | MLP with one hidden layer, activation and skip connection 11 | Basically dimension d_model, but better if input_dim and output_dim are explicit 12 | 13 | in_size and out_size to handle dimensions at different stages of the NN 14 | 15 | Parameters 16 | ---------- 17 | in_size: int 18 | input size 19 | out_size: int 20 | output size 21 | dropout_rate: float 22 | dropout 23 | activation_fun: str, Optional 24 | activation function to use in the Residual Block. Defaults to nn.ReLU. 25 | """ # noqa: E501 26 | import ast 27 | 28 | super().__init__() 29 | 30 | self.direct_linear = nn.Linear(in_size, out_size, bias=False) 31 | 32 | if activation_fun == "": 33 | self.act = nn.ReLU() 34 | else: 35 | activation = ast.literal_eval(activation_fun) 36 | self.act = activation() 37 | self.lin = nn.Linear(in_size, out_size) 38 | self.dropout = nn.Dropout(dropout_rate) 39 | 40 | self.final_norm = nn.LayerNorm(out_size) 41 | 42 | def forward(self, x, apply_final_norm=True): 43 | direct_x = self.direct_linear(x) 44 | 45 | x = self.dropout(self.lin(self.act(x))) 46 | 47 | out = x + direct_x 48 | if apply_final_norm: 49 | return self.final_norm(out) 50 | return out 51 | -------------------------------------------------------------------------------- /tests/test_models/test_nn/test_rnn.py: -------------------------------------------------------------------------------- 1 | import itertools 2 | 3 | import pytest 4 | import torch 5 | from torch import nn 6 | 7 | from pytorch_forecasting.models.nn.rnn import GRU, LSTM, get_rnn 8 | 9 | 10 | def test_get_lstm_cell(): 11 | cell = get_rnn("LSTM")(10, 10) 12 | assert isinstance(cell, LSTM) 13 | assert isinstance(cell, nn.LSTM) 14 | 15 | 16 | def test_get_gru_cell(): 17 | cell = get_rnn("GRU")(10, 10) 18 | assert isinstance(cell, GRU) 19 | assert isinstance(cell, nn.GRU) 20 | 21 | 22 | def test_get_cell_raises_value_error(): 23 | pytest.raises(ValueError, lambda: get_rnn("ABCDEF")) 24 | 25 | 26 | @pytest.mark.parametrize( 27 | "klass,rnn_kwargs", 28 | itertools.product( 29 | [LSTM, GRU], 30 | [ 31 | dict(batch_first=True, num_layers=1), 32 | dict(batch_first=False, num_layers=2), 33 | ], 34 | ), 35 | ) 36 | def test_zero_length_sequence(klass, rnn_kwargs): 37 | rnn = klass(input_size=2, hidden_size=5, **rnn_kwargs) 38 | x = torch.rand(100, 3, 2) 39 | lengths = torch.randint(0, 3, size=([3, 100][rnn_kwargs["batch_first"]],)) 40 | _, hidden_state = rnn(x, lengths=lengths, enforce_sorted=False) 41 | init_hidden_state = rnn.init_hidden_state(x) 42 | 43 | if isinstance(hidden_state, torch.Tensor): 44 | hidden_state = [hidden_state] 45 | init_hidden_state = [init_hidden_state] 46 | 47 | for idx in range(len(hidden_state)): 48 | assert ( 49 | hidden_state[idx].size() == init_hidden_state[idx].size() 50 | ), "Hidden state sizes should be equal" 51 | assert (hidden_state[idx][:, lengths == 0] == 0).all() and ( 52 | hidden_state[idx][:, lengths > 0] != 0 53 | ).all(), "Hidden state should be zero for zero-length sequences" 54 | -------------------------------------------------------------------------------- /pytorch_forecasting/layers/_filter/_moving_avg_filter.py: -------------------------------------------------------------------------------- 1 | """ 2 | Moving Average Filter Block 3 | """ 4 | 5 | import torch 6 | import torch.nn as nn 7 | import torch.nn.functional as F 8 | 9 | 10 | class MovingAvg(nn.Module): 11 | """ 12 | Moving Average block for smoothing and trend extraction from time series data. 13 | 14 | A moving average is a smoothing technique that creates a series of average from 15 | different subsets of a time series. 16 | 17 | For example: Given a time series ``x = [x_1, x_2, ..., x_n]``, the moving average 18 | with a kernel size of `k` calculates the average of each subset of `k` consecutive 19 | elements, resulting in a new series of averages. 20 | 21 | Args: 22 | kernel_size (int): 23 | Size of the moving average kernel. 24 | stride (int): 25 | Stride for the moving average operation, typically set to 1. 26 | """ 27 | 28 | def __init__(self, kernel_size, stride): 29 | super().__init__() 30 | self.kernel_size = kernel_size 31 | self.avg = nn.AvgPool1d(kernel_size, stride=stride, padding=0) 32 | 33 | def forward(self, x): 34 | if self.kernel_size % 2 == 0: 35 | self.padding_left = self.kernel_size // 2 - 1 36 | self.padding_right = self.kernel_size // 2 37 | else: 38 | self.padding_left = self.kernel_size // 2 39 | self.padding_right = self.kernel_size // 2 40 | 41 | front = x[:, 0:1, :].repeat(1, self.padding_left, 1) 42 | end = x[:, -1:, :].repeat(1, self.padding_right, 1) 43 | 44 | x_padded = torch.cat([front, x, end], dim=1) 45 | x_transposed = x_padded.permute(0, 2, 1) 46 | x_smoothed = self.avg(x_transposed) 47 | x_out = x_smoothed.permute(0, 2, 1) 48 | return x_out 49 | -------------------------------------------------------------------------------- /pytorch_forecasting/metrics/_distributions_pkg/_implicit_quantile_network/_implicit_quantile_network_distribution_loss_pkg.py: -------------------------------------------------------------------------------- 1 | """ 2 | Package container for the Implicit Quantile Network distribution loss metric. 3 | """ 4 | 5 | from pytorch_forecasting.data import TorchNormalizer 6 | from pytorch_forecasting.metrics.base_metrics._base_object import _BasePtMetric 7 | 8 | 9 | class ImplicitQuantileNetworkDistributionLoss_pkg(_BasePtMetric): 10 | """ 11 | Implicit quantile network distribution loss metric for distribution forecasts. 12 | """ 13 | 14 | _tags = { 15 | "metric_type": "distribution", 16 | "distribution_type": "implicit_quantile_network", 17 | "info:metric_name": "ImplicitQuantileNetworkDistributionLoss", 18 | "requires:data_type": "implicit_quantile_network_distribution_forecast", 19 | "capability:quantile_generation": True, 20 | "shape:adds_quantile_dimension": True, 21 | } 22 | 23 | @classmethod 24 | def get_cls(cls): 25 | from pytorch_forecasting.metrics.distributions import ( 26 | ImplicitQuantileNetworkDistributionLoss, 27 | ) 28 | 29 | return ImplicitQuantileNetworkDistributionLoss 30 | 31 | @classmethod 32 | def get_encoder(cls): 33 | """ 34 | Returns a TorchNormalizer instance for rescaling parameters. 35 | """ 36 | return TorchNormalizer(transformation="softplus") 37 | 38 | @classmethod 39 | def get_metric_test_params(cls): 40 | """ 41 | Returns test parameters for ImplicitQuantileNetworkDistributionLoss. 42 | 43 | This corresponds to the ``output_size`` parameter in the data preparation 44 | fixture for testing the ImplicitQuantileNetworkDistributionLoss metric. 45 | """ 46 | return [{"input_size": 5}] 47 | -------------------------------------------------------------------------------- /pytorch_forecasting/metrics/_distributions_pkg/_log_normal/_log_normal_distribution_loss_pkg.py: -------------------------------------------------------------------------------- 1 | """ 2 | Package container for the Log Normal distribution loss metric. 3 | """ 4 | 5 | import torch 6 | 7 | from pytorch_forecasting.data import TorchNormalizer 8 | from pytorch_forecasting.metrics.base_metrics._base_object import _BasePtMetric 9 | 10 | 11 | class LogNormalDistributionLoss_pkg(_BasePtMetric): 12 | """ 13 | Log-normal distribution loss metric for distribution forecasts. 14 | """ 15 | 16 | _tags = { 17 | "metric_type": "distribution", 18 | "distribution_type": "log_normal", 19 | "info:metric_name": "LogNormalDistributionLoss", 20 | "requires:data_type": "log_normal_distribution_forecast", 21 | } 22 | 23 | @classmethod 24 | def get_cls(cls): 25 | from pytorch_forecasting.metrics.distributions import LogNormalDistributionLoss 26 | 27 | return LogNormalDistributionLoss 28 | 29 | @classmethod 30 | def get_encoder(cls): 31 | """ 32 | Returns a TorchNormalizer instance for rescaling parameters. 33 | """ 34 | return TorchNormalizer(transformation="log") 35 | 36 | @classmethod 37 | def prepare_test_inputs(cls, test_case): 38 | """Prepare inputs for log normal distribution tests.""" 39 | y_pred = test_case["y_pred"] 40 | y = test_case["y"] 41 | 42 | if isinstance(y, torch.nn.utils.rnn.PackedSequence): 43 | data, lengths = torch.nn.utils.rnn.pad_packed_sequence(y, batch_first=True) 44 | data = torch.where(data <= 0, torch.tensor(1e-4, device=data.device), data) 45 | 46 | y = torch.nn.utils.rnn.pack_padded_sequence( 47 | data, lengths, batch_first=True, enforce_sorted=False 48 | ) 49 | 50 | return y_pred, y 51 | -------------------------------------------------------------------------------- /pytorch_forecasting/models/mlp/submodules.py: -------------------------------------------------------------------------------- 1 | """ 2 | MLP implementation 3 | """ 4 | 5 | import torch 6 | from torch import nn 7 | 8 | 9 | class FullyConnectedModule(nn.Module): 10 | def __init__( 11 | self, 12 | input_size: int, 13 | output_size: int, 14 | hidden_size: int, 15 | n_hidden_layers: int, 16 | activation_class: nn.ReLU, 17 | dropout: float = None, 18 | norm: bool = True, 19 | ): 20 | super().__init__() 21 | self.input_size = input_size 22 | self.output_size = output_size 23 | self.hidden_size = hidden_size 24 | self.n_hidden_layers = n_hidden_layers 25 | self.activation_class = activation_class 26 | self.dropout = dropout 27 | self.norm = norm 28 | 29 | # input layer 30 | module_list = [nn.Linear(input_size, hidden_size), activation_class()] 31 | if dropout is not None: 32 | module_list.append(nn.Dropout(dropout)) 33 | if norm: 34 | module_list.append(nn.LayerNorm(hidden_size)) 35 | # hidden layers 36 | for _ in range(n_hidden_layers): 37 | module_list.extend( 38 | [nn.Linear(hidden_size, hidden_size), activation_class()] 39 | ) 40 | if dropout is not None: 41 | module_list.append(nn.Dropout(dropout)) 42 | if norm: 43 | module_list.append(nn.LayerNorm(hidden_size)) 44 | # output layer 45 | module_list.append(nn.Linear(hidden_size, output_size)) 46 | 47 | self.sequential = nn.Sequential(*module_list) 48 | 49 | def forward(self, x: torch.Tensor) -> torch.Tensor: 50 | # x of shape: batch_size x n_timesteps_in 51 | # output of shape batch_size x n_timesteps_out 52 | return self.sequential(x) 53 | -------------------------------------------------------------------------------- /pytorch_forecasting/layers/_embeddings/_en_embedding.py: -------------------------------------------------------------------------------- 1 | """ 2 | Implementation of endogenous embedding layers from `nn.Module`. 3 | """ 4 | 5 | import math 6 | from math import sqrt 7 | 8 | import numpy as np 9 | import torch 10 | import torch.nn as nn 11 | import torch.nn.functional as F 12 | 13 | from pytorch_forecasting.layers._embeddings._positional_embedding import ( 14 | PositionalEmbedding, 15 | ) 16 | 17 | 18 | class EnEmbedding(nn.Module): 19 | """ 20 | Encoder embedding module for time series data. Handles endogenous feature 21 | embeddings in this case. 22 | Args: 23 | n_vars (int): Number of input features. 24 | d_model (int): Dimension of the model. 25 | patch_len (int): Length of the patches. 26 | dropout (float): Dropout rate. Defaults to 0.1. 27 | """ 28 | 29 | def __init__(self, n_vars, d_model, patch_len, dropout): 30 | super().__init__() 31 | 32 | self.patch_len = patch_len 33 | 34 | self.value_embedding = nn.Linear(patch_len, d_model, bias=False) 35 | self.glb_token = nn.Parameter(torch.randn(1, n_vars, 1, d_model)) 36 | self.position_embedding = PositionalEmbedding(d_model) 37 | 38 | self.dropout = nn.Dropout(dropout) 39 | 40 | def forward(self, x): 41 | x = x.permute(0, 2, 1) 42 | n_vars = x.shape[1] 43 | glb = self.glb_token.repeat((x.shape[0], 1, 1, 1)) 44 | 45 | x = x.unfold(dimension=-1, size=self.patch_len, step=self.patch_len) 46 | x = torch.reshape(x, (x.shape[0] * x.shape[1], x.shape[2], x.shape[3])) 47 | # Input encoding 48 | x = self.value_embedding(x) + self.position_embedding(x) 49 | x = torch.reshape(x, (-1, n_vars, x.shape[-2], x.shape[-1])) 50 | x = torch.cat([x, glb], dim=2) 51 | x = torch.reshape(x, (x.shape[0] * x.shape[1], x.shape[2], x.shape[3])) 52 | return self.dropout(x), n_vars 53 | -------------------------------------------------------------------------------- /docs/source/metrics.rst: -------------------------------------------------------------------------------- 1 | Metrics 2 | ========== 3 | 4 | Multiple metrics have been implemented to ease adaptation. 5 | 6 | In particular, these metrics can be applied to the multi-horizon forecasting problem, i.e. 7 | can take tensors that are not only of shape ``n_samples`` but also ``n_samples x prediction_horizon`` 8 | or even ``n_samples x prediction_horizon x n_outputs``, where ``n_outputs`` could be the number 9 | of forecasted quantiles. 10 | 11 | Metrics can be easily combined by addition, e.g. 12 | 13 | .. code-block:: python 14 | 15 | from pytorch_forecasting.metrics import SMAPE, MAE 16 | 17 | composite_metric = SMAPE() + 1e-4 * MAE() 18 | 19 | Such composite metrics are useful when training because they can reduce outliers in other metrics. 20 | In the example, SMAPE is mostly optimized, while large outliers in MAE are avoided. 21 | 22 | Further, one can modify a loss metric to reduce a mean prediction bias, i.e. ensure that 23 | predictions add up. For example: 24 | 25 | .. code-block:: python 26 | 27 | from pytorch_forecasting.metrics import MAE, AggregationMetric 28 | 29 | composite_metric = MAE() + AggregationMetric(metric=MAE()) 30 | 31 | Here we add to MAE an additional loss. This additional loss is the MAE calculated on the mean predictions 32 | and actuals. We can also use other metrics such as SMAPE to ensure aggregated results are unbiased in that metric. 33 | One important point to keep in mind is that this metric is calculated across samples, i.e. it will vary depending 34 | on the batch size. In particular, errors tend to average out with increased batch sizes. 35 | 36 | 37 | Details 38 | -------- 39 | 40 | See the API documentation for further details on available metrics: 41 | 42 | .. currentmodule:: pytorch_forecasting 43 | 44 | .. moduleautosummary:: 45 | :toctree: api 46 | :template: custom-module-template.rst 47 | :recursive: 48 | 49 | pytorch_forecasting.metrics 50 | -------------------------------------------------------------------------------- /pytorch_forecasting/tests/test_all_v2/utils.py: -------------------------------------------------------------------------------- 1 | from typing import Any 2 | 3 | from lightning.pytorch.loggers import TensorBoardLogger 4 | 5 | from pytorch_forecasting.base._base_pkg import Base_pkg 6 | from pytorch_forecasting.data import TimeSeries 7 | from pytorch_forecasting.metrics import SMAPE 8 | 9 | 10 | def _setup_pkg_and_data( 11 | estimator_cls: type[Base_pkg], 12 | trainer_kwargs: dict[str, Any], 13 | tmp_path: str, 14 | ) -> tuple[Base_pkg, dict[str, TimeSeries], dict[str, Any]]: 15 | """ 16 | Helper to initialize the Package, Datasets, and Configs. 17 | 18 | Returns 19 | ------- 20 | pkg : Base_pkg 21 | The initialized model package. 22 | test_data : dict 23 | Dictionary containing 'train' and 'predict' TimeSeries datasets. 24 | datamodule_cfg : dict 25 | The final datamodule configuration used. 26 | """ 27 | params_copy = trainer_kwargs.copy() 28 | datamodule_cfg = params_copy.pop("datamodule_cfg", {}) 29 | model_cfg = params_copy 30 | 31 | if "loss" not in model_cfg: 32 | model_cfg["loss"] = SMAPE() 33 | 34 | default_datamodule_cfg = { 35 | "train_val_test_split": (0.8, 0.2), 36 | "add_relative_time_idx": True, 37 | "batch_size": 2, 38 | } 39 | default_datamodule_cfg.update(datamodule_cfg) 40 | 41 | logger = TensorBoardLogger(str(tmp_path)) 42 | trainer_cfg = { 43 | "max_epochs": 2, 44 | "gradient_clip_val": 0.1, 45 | "enable_checkpointing": True, 46 | "default_root_dir": str(tmp_path), 47 | "limit_train_batches": 2, 48 | "limit_val_batches": 1, 49 | "accelerator": "cpu", 50 | "logger": logger, 51 | } 52 | 53 | test_data = estimator_cls.get_test_dataset_from(**default_datamodule_cfg) 54 | 55 | pkg = estimator_cls( 56 | model_cfg=model_cfg, 57 | trainer_cfg=trainer_cfg, 58 | datamodule_cfg=default_datamodule_cfg, 59 | ) 60 | 61 | return pkg, test_data, default_datamodule_cfg 62 | -------------------------------------------------------------------------------- /pytorch_forecasting/metrics/quantile.py: -------------------------------------------------------------------------------- 1 | """Quantile metrics for forecasting multiple quantiles per time step.""" 2 | 3 | from typing import Optional 4 | 5 | import torch 6 | 7 | from pytorch_forecasting.metrics.base_metrics import MultiHorizonMetric 8 | 9 | 10 | class QuantileLoss(MultiHorizonMetric): 11 | """ 12 | Quantile loss, i.e. a quantile of ``q=0.5`` will give half of the mean absolute error as it is calculated as 13 | 14 | Defined as ``max(q * (y-y_pred), (1-q) * (y_pred-y))`` 15 | """ # noqa: E501 16 | 17 | def __init__( 18 | self, 19 | quantiles: Optional[list[float]] = None, 20 | **kwargs, 21 | ): 22 | """ 23 | Quantile loss 24 | 25 | Args: 26 | quantiles: quantiles for metric 27 | """ 28 | if quantiles is None: 29 | quantiles = [0.02, 0.1, 0.25, 0.5, 0.75, 0.9, 0.98] 30 | super().__init__(quantiles=quantiles, **kwargs) 31 | 32 | def loss(self, y_pred: torch.Tensor, target: torch.Tensor) -> torch.Tensor: 33 | # calculate quantile loss 34 | losses = [] 35 | for i, q in enumerate(self.quantiles): 36 | errors = target - y_pred[..., i] 37 | losses.append(torch.max((q - 1) * errors, q * errors).unsqueeze(-1)) 38 | losses = 2 * torch.cat(losses, dim=2) 39 | 40 | return losses 41 | 42 | def to_prediction(self, y_pred: torch.Tensor) -> torch.Tensor: 43 | """ 44 | Convert network prediction into a point prediction. 45 | 46 | Args: 47 | y_pred: prediction output of network 48 | 49 | Returns: 50 | torch.Tensor: point prediction 51 | """ 52 | if y_pred.ndim == 3: 53 | idx = self.quantiles.index(0.5) 54 | y_pred = y_pred[..., idx] 55 | return y_pred 56 | 57 | def to_quantiles(self, y_pred: torch.Tensor) -> torch.Tensor: 58 | """ 59 | Convert network prediction into a quantile prediction. 60 | 61 | Args: 62 | y_pred: prediction output of network 63 | 64 | Returns: 65 | torch.Tensor: prediction quantiles 66 | """ 67 | return y_pred 68 | -------------------------------------------------------------------------------- /pytorch_forecasting/layers/_attention/_attention_layer.py: -------------------------------------------------------------------------------- 1 | """ 2 | Implementation of attention layers from `nn.Module`. 3 | """ 4 | 5 | from math import sqrt 6 | 7 | import numpy as np 8 | import torch 9 | import torch.nn as nn 10 | import torch.nn.functional as F 11 | 12 | 13 | class AttentionLayer(nn.Module): 14 | """ 15 | Attention layer that combines query, key, and value projections with an attention 16 | mechanism. 17 | Args: 18 | attention (nn.Module): Attention mechanism to use. 19 | d_model (int): Dimension of the model. 20 | n_heads (int): Number of attention heads. 21 | d_keys (int, optional): Dimension of the keys. Defaults to d_model // n_heads. 22 | d_values (int, optional): 23 | Dimension of the values. Defaults to d_model // n_heads. 24 | """ 25 | 26 | def __init__(self, attention, d_model, n_heads, d_keys=None, d_values=None): 27 | super().__init__() 28 | 29 | d_keys = d_keys or (d_model // n_heads) 30 | d_values = d_values or (d_model // n_heads) 31 | 32 | self.inner_attention = attention 33 | self.query_projection = nn.Linear(d_model, d_keys * n_heads) 34 | self.key_projection = nn.Linear(d_model, d_keys * n_heads) 35 | self.value_projection = nn.Linear(d_model, d_values * n_heads) 36 | self.out_projection = nn.Linear(d_values * n_heads, d_model) 37 | self.n_heads = n_heads 38 | 39 | def forward(self, queries, keys, values, attn_mask, tau=None, delta=None): 40 | B, L, _ = queries.shape 41 | _, S, _ = keys.shape 42 | H = self.n_heads 43 | 44 | if S == 0: 45 | # skip the cross attention process since there is no exogenous variables 46 | queries = self.query_projection(queries) 47 | return self.out_projection(queries), None 48 | 49 | queries = self.query_projection(queries).view(B, L, H, -1) 50 | keys = self.key_projection(keys).view(B, S, H, -1) 51 | values = self.value_projection(values).view(B, S, H, -1) 52 | 53 | out, attn = self.inner_attention( 54 | queries, keys, values, attn_mask, tau=tau, delta=delta 55 | ) 56 | out = out.view(B, L, -1) 57 | 58 | return self.out_projection(out), attn 59 | -------------------------------------------------------------------------------- /pytorch_forecasting/tests/test_class_register.py: -------------------------------------------------------------------------------- 1 | # copyright: pytorch-forecasting developers, BSD-3-Clause License (see LICENSE file) 2 | # mostly based on the sktime utilities of the same name (BSD-3 Clause) 3 | """Registry and dispatcher for test classes. 4 | 5 | Module does not contain tests, only test utilities. 6 | """ 7 | 8 | __author__ = ["fkiraly"] 9 | 10 | 11 | def get_test_class_registry(): 12 | """Return test class registry. 13 | 14 | Wrapped in a function to avoid circular imports. 15 | 16 | Returns 17 | ------- 18 | testclass_dict : dict 19 | test class registry 20 | keys are scitypes, values are test classes TestAll[Scitype] 21 | """ 22 | from pytorch_forecasting.tests.test_all_estimators import TestAllPtForecasters 23 | from pytorch_forecasting.tests.test_all_v2.test_all_estimators_v2 import ( 24 | TestAllPtForecastersV2, 25 | ) 26 | 27 | testclass_dict = dict() 28 | testclass_dict["forecaster_pytorch_v1"] = TestAllPtForecasters 29 | testclass_dict["forecaster_pytorch_v2"] = TestAllPtForecastersV2 30 | 31 | return testclass_dict 32 | 33 | 34 | def get_test_classes_for_obj(obj): 35 | """Get all test classes relevant for an object or estimator. 36 | 37 | Parameters 38 | ---------- 39 | obj : object or estimator, descendant of sktime BaseObject or BaseEstimator 40 | object or estimator for which to get test classes 41 | 42 | Returns 43 | ------- 44 | test_classes : list of test classes 45 | list of test classes relevant for obj 46 | these are references to the actual classes, not strings 47 | if obj was not a descendant of BaseObject or BaseEstimator, returns empty list 48 | """ 49 | if hasattr(obj, "pkg"): 50 | obj = obj.pkg 51 | 52 | testclass_dict = get_test_class_registry() 53 | 54 | if hasattr(obj, "get_class_tag"): 55 | obj_scitypes = obj.get_class_tag("object_type") 56 | if not isinstance(obj_scitypes, (list, tuple, set)): 57 | obj_scitypes = [obj_scitypes] 58 | else: 59 | obj_scitypes = [] 60 | 61 | test_clss = [] 62 | for obj_scitype in obj_scitypes: 63 | if obj_scitype in testclass_dict: 64 | test_clss += [testclass_dict[obj_scitype]] 65 | 66 | return test_clss 67 | -------------------------------------------------------------------------------- /pytorch_forecasting/metrics/base_metrics/_base_object.py: -------------------------------------------------------------------------------- 1 | """Base object class for pytorch-forecasting metrics.""" 2 | 3 | from pytorch_forecasting.base._base_object import _BaseObject 4 | 5 | 6 | class _BasePtMetric(_BaseObject): 7 | """Base class for metric object that can be discovered for testing.""" 8 | 9 | _tags = {"object_type": "metric"} 10 | 11 | @classmethod 12 | def name(cls): 13 | """Get the name of the metric. 14 | 15 | Returns 16 | ------- 17 | str 18 | The name of the metric. 19 | """ 20 | metric_cls = cls.get_cls() 21 | return metric_cls.__name__ 22 | 23 | @classmethod 24 | def get_cls(cls): 25 | """Get the metric class. 26 | 27 | Returns 28 | ------- 29 | type 30 | The metric class. 31 | """ 32 | raise NotImplementedError("get_cls must be implemented in subclasses.") 33 | 34 | @classmethod 35 | def prepare_test_inputs(cls, test_case): 36 | """Prepare test inputs for the metric. 37 | 38 | This can be overridden by subclasses to provide special handling 39 | of test inputs. 40 | 41 | Parameters 42 | ---------- 43 | test_case: dict 44 | Dictionary containing test case parameters. 45 | 46 | Returns 47 | ------- 48 | (y_pred, y_actual, kwargs): tuple 49 | Tuple containing the predicted values, actual values, and any additional 50 | keyword arguments. 51 | """ 52 | 53 | return test_case["y_pred"], test_case["y"] 54 | 55 | @classmethod 56 | def get_metric_test_params(cls): 57 | """Returns parameters for initializing the metric for testing. 58 | 59 | Returns 60 | ------- 61 | dict 62 | Dictionary containing parameters for initializing the metric.d 63 | """ 64 | 65 | return [] 66 | 67 | @classmethod 68 | def get_encoder(cls): 69 | """Get the encoder for the metric. 70 | 71 | This can be overridden by subclasses to provide a specific encoder. 72 | 73 | Returns 74 | ------- 75 | TorchNormalizer 76 | An instance of TorchNormalizer or similar encoder. 77 | """ 78 | from pytorch_forecasting.data import TorchNormalizer 79 | 80 | return TorchNormalizer() 81 | -------------------------------------------------------------------------------- /tests/test_utils/test_safe_import.py: -------------------------------------------------------------------------------- 1 | from pytorch_forecasting.utils._dependencies import _safe_import 2 | 3 | 4 | def test_present_module(): 5 | """Test importing a dependency that is installed.""" 6 | module = _safe_import("torch") 7 | assert module is not None 8 | 9 | 10 | def test_import_missing_module(): 11 | """Test importing a dependency that is not installed.""" 12 | result = _safe_import("nonexistent_module") 13 | assert hasattr(result, "__name__") 14 | assert result.__name__ == "nonexistent_module" 15 | 16 | 17 | def test_import_without_pkg_name(): 18 | """Test importing a dependency with the same name as package name.""" 19 | result = _safe_import("torch", pkg_name="torch") 20 | assert result is not None 21 | 22 | 23 | def test_import_with_different_pkg_name_1(): 24 | """Test importing a dependency with a different package name.""" 25 | result = _safe_import("skbase", pkg_name="scikit-base") 26 | assert result is not None 27 | 28 | 29 | def test_import_with_different_pkg_name_2(): 30 | """Test importing another dependency with a different package name.""" 31 | result = _safe_import("cv2", pkg_name="opencv-python") 32 | assert result is not None 33 | 34 | 35 | def test_import_submodule(): 36 | """Test importing a submodule.""" 37 | result = _safe_import("torch.nn") 38 | assert result is not None 39 | 40 | 41 | def test_import_class(): 42 | """Test importing a class.""" 43 | result = _safe_import("torch.nn.Linear") 44 | assert result is not None 45 | 46 | 47 | def test_import_existing_object(): 48 | """Test importing an existing object.""" 49 | result = _safe_import("pandas.DataFrame") 50 | assert result is not None 51 | assert result.__name__ == "DataFrame" 52 | from pandas import DataFrame 53 | 54 | assert result is DataFrame 55 | 56 | 57 | def test_multiple_inheritance_from_mock(): 58 | """Test multiple inheritance from dynamic MagicMock.""" 59 | Class1 = _safe_import("foobar.foo.FooBar") 60 | Class2 = _safe_import("barfoobar.BarFooBar") 61 | 62 | class NewClass(Class1, Class2): 63 | """This should not trigger an error. 64 | 65 | The class definition would trigger an error if multiple inheritance 66 | from Class1 and Class2 does not work, e.g., if it is simply 67 | identical to MagicMock. 68 | """ 69 | 70 | pass 71 | -------------------------------------------------------------------------------- /tests/conftest.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | 4 | import numpy as np 5 | import pytest 6 | 7 | sys.path.insert(0, os.path.abspath(os.path.join(__file__, "../.."))) # isort:skip 8 | 9 | 10 | from pytorch_forecasting import TimeSeriesDataSet # isort:skip 11 | from pytorch_forecasting.data.examples import get_stallion_data # isort:skip 12 | 13 | 14 | # for vscode debugging: https://stackoverflow.com/a/62563106/14121677 15 | if os.getenv("_PYTEST_RAISE", "0") != "0": 16 | 17 | @pytest.hookimpl(tryfirst=True) 18 | def pytest_exception_interact(call): 19 | raise call.excinfo.value 20 | 21 | @pytest.hookimpl(tryfirst=True) 22 | def pytest_internalerror(excinfo): 23 | raise excinfo.value 24 | 25 | 26 | @pytest.fixture(scope="session") 27 | def test_data(): 28 | data = get_stallion_data() 29 | data["month"] = data.date.dt.month.astype(str) 30 | data["log_volume"] = np.log1p(data.volume) 31 | data["weight"] = 1 + np.sqrt(data.volume) 32 | 33 | data["time_idx"] = data["date"].dt.year * 12 + data["date"].dt.month 34 | data["time_idx"] -= data["time_idx"].min() 35 | 36 | special_days = [ 37 | "easter_day", 38 | "good_friday", 39 | "new_year", 40 | "christmas", 41 | "labor_day", 42 | "independence_day", 43 | "revolution_day_memorial", 44 | "regional_games", 45 | "fifa_u_17_world_cup", 46 | "football_gold_cup", 47 | "beer_capital", 48 | "music_fest", 49 | ] 50 | data[special_days] = ( 51 | data[special_days].apply(lambda x: x.map({0: "", 1: x.name})).astype("category") 52 | ) 53 | 54 | data = data[lambda x: x.time_idx < 10] # downsample 55 | return data 56 | 57 | 58 | @pytest.fixture(scope="session") 59 | def test_dataset(test_data): 60 | training = TimeSeriesDataSet( 61 | test_data.copy(), 62 | time_idx="time_idx", 63 | target="volume", 64 | time_varying_known_reals=["price_regular", "time_idx"], 65 | group_ids=["agency", "sku"], 66 | static_categoricals=["agency"], 67 | max_encoder_length=5, 68 | max_prediction_length=2, 69 | min_prediction_length=1, 70 | min_encoder_length=0, 71 | randomize_length=None, 72 | ) 73 | return training 74 | 75 | 76 | @pytest.fixture(autouse=True) 77 | def disable_mps(monkeypatch): 78 | """Disable MPS for all tests""" 79 | monkeypatch.setattr("torch._C._mps_is_available", lambda: False) 80 | -------------------------------------------------------------------------------- /pytorch_forecasting/utils/_dependencies/_dependencies.py: -------------------------------------------------------------------------------- 1 | """Utilities for managing dependencies. 2 | 3 | Copied from sktime/skbase. 4 | """ 5 | 6 | from functools import lru_cache 7 | 8 | 9 | @lru_cache 10 | def _get_installed_packages_private(): 11 | """Get a dictionary of installed packages and their versions. 12 | 13 | Same as _get_installed_packages, but internal to avoid mutating the lru_cache 14 | by accident. 15 | """ 16 | from importlib.metadata import distributions, version 17 | 18 | dists = distributions() 19 | package_names = { 20 | dist.metadata["Name"] 21 | for dist in dists 22 | if dist.metadata and "Name" in dist.metadata 23 | } 24 | package_versions = {pkg_name: version(pkg_name) for pkg_name in package_names} 25 | # developer note: 26 | # we cannot just use distributions naively, 27 | # because the same top level package name may appear *twice*, 28 | # e.g., in a situation where a virtual env overrides a base env, 29 | # such as in deployment environments like databricks. 30 | # the "version" contract ensures we always get the version that corresponds 31 | # to the importable distribution, i.e., the top one in the sys.path. 32 | return package_versions 33 | 34 | 35 | def _get_installed_packages(): 36 | """Get a dictionary of installed packages and their versions. 37 | 38 | Returns 39 | ------- 40 | dict : dictionary of installed packages and their versions 41 | keys are PEP 440 compatible package names, values are package versions 42 | MAJOR.MINOR.PATCH version format is used for versions, e.g., "1.2.3" 43 | """ 44 | return _get_installed_packages_private().copy() 45 | 46 | 47 | def _check_matplotlib(ref="This feature", raise_error=True): 48 | """Check if matplotlib is installed. 49 | 50 | Parameters 51 | ---------- 52 | ref : str, optional (default="This feature") 53 | reference to the feature that requires matplotlib, used in error message 54 | raise_error : bool, optional (default=True) 55 | whether to raise an error if matplotlib is not installed 56 | 57 | Returns 58 | ------- 59 | bool : whether matplotlib is installed 60 | """ 61 | pkgs = _get_installed_packages() 62 | 63 | if raise_error and "matplotlib" not in pkgs: 64 | raise ImportError( 65 | f"{ref} requires matplotlib." 66 | " Please install matplotlib with `pip install matplotlib`." 67 | ) 68 | 69 | return "matplotlib" in pkgs 70 | -------------------------------------------------------------------------------- /pytorch_forecasting/models/samformer/_samformer_v2_pkg.py: -------------------------------------------------------------------------------- 1 | """ 2 | Samformer package container. 3 | """ 4 | 5 | from pytorch_forecasting.base._base_pkg import Base_pkg 6 | 7 | 8 | class Samformer_pkg_v2(Base_pkg): 9 | """Samformer package container.""" 10 | 11 | _tags = { 12 | "info:name": "Samformer", 13 | "authors": ["fbk_dsipts"], 14 | } 15 | 16 | @classmethod 17 | def get_cls(cls): 18 | """Get model class.""" 19 | from pytorch_forecasting.models.samformer._samformer_v2 import Samformer 20 | 21 | return Samformer 22 | 23 | @classmethod 24 | def get_datamodule_cls(cls): 25 | """Get the underlying DataModule class.""" 26 | from pytorch_forecasting.data.data_module import ( 27 | EncoderDecoderTimeSeriesDataModule, 28 | ) 29 | 30 | return EncoderDecoderTimeSeriesDataModule 31 | 32 | @classmethod 33 | def get_test_train_params(cls): 34 | """Return testing parameters settings for the trainer. 35 | 36 | Returns 37 | ------- 38 | params : dict or list of dict, default = {} 39 | Parameters to create testing instances of the class 40 | Each dict are parameters to construct an "interesting" test instance, i.e., 41 | `MyClass(**params)` or `MyClass(**params[i])` creates a valid test instance. 42 | `create_test_instance` uses the first (or only) dictionary in `params` 43 | """ 44 | import torch.nn as nn 45 | 46 | from pytorch_forecasting.metrics import QuantileLoss 47 | 48 | params = [ 49 | { 50 | # "loss": nn.MSELoss(), 51 | "hidden_size": 32, 52 | "use_revin": False, 53 | }, 54 | { 55 | # "loss": nn.MSELoss(), 56 | "hidden_size": 16, 57 | "use_revin": True, 58 | "out_channels": 1, 59 | "persistence_weight": 0.0, 60 | }, 61 | { 62 | "loss": QuantileLoss(quantiles=[0.1, 0.5, 0.9]), 63 | "hidden_size": 32, 64 | "use_revin": False, 65 | }, 66 | ] 67 | 68 | default_dm_cfg = {"max_encoder_length": 4, "max_prediction_length": 3} 69 | 70 | for param in params: 71 | current_dm_cfg = param.get("datamodule_cfg", {}) 72 | default_dm_cfg.update(current_dm_cfg) 73 | 74 | param["datamodule_cfg"] = default_dm_cfg 75 | 76 | return params 77 | -------------------------------------------------------------------------------- /pytorch_forecasting/utils/_dependencies/tests/test_safe_import.py: -------------------------------------------------------------------------------- 1 | __author__ = ["jgyasu", "fkiraly"] 2 | 3 | from pytorch_forecasting.utils._dependencies import ( 4 | _get_installed_packages, 5 | _safe_import, 6 | ) 7 | 8 | 9 | def test_import_present_module(): 10 | """Test importing a dependency that is installed.""" 11 | result = _safe_import("pandas") 12 | assert result is not None 13 | assert "pandas" in _get_installed_packages() 14 | 15 | 16 | def test_import_missing_module(): 17 | """Test importing a dependency that is not installed.""" 18 | result = _safe_import("nonexistent_module") 19 | assert hasattr(result, "__name__") 20 | assert result.__name__ == "nonexistent_module" 21 | 22 | 23 | def test_import_without_pkg_name(): 24 | """Test importing a dependency with the same name as package name.""" 25 | result = _safe_import("torch", pkg_name="torch") 26 | assert result is not None 27 | 28 | 29 | def test_import_with_different_pkg_name_1(): 30 | """Test importing a dependency with a different package name.""" 31 | result = _safe_import("skbase", pkg_name="scikit-base") 32 | assert result is not None 33 | 34 | 35 | def test_import_with_different_pkg_name_2(): 36 | """Test importing another dependency with a different package name.""" 37 | result = _safe_import("cv2", pkg_name="opencv-python") 38 | assert result is not None 39 | 40 | 41 | def test_import_submodule(): 42 | """Test importing a submodule.""" 43 | result = _safe_import("torch.nn") 44 | assert result is not None 45 | 46 | 47 | def test_import_class(): 48 | """Test importing a class.""" 49 | result = _safe_import("torch.nn.Linear") 50 | assert result is not None 51 | 52 | 53 | def test_import_existing_object(): 54 | """Test importing an existing object.""" 55 | result = _safe_import("pandas.DataFrame") 56 | assert result is not None 57 | assert result.__name__ == "DataFrame" 58 | from pandas import DataFrame 59 | 60 | assert result is DataFrame 61 | 62 | 63 | def test_multiple_inheritance_from_mock(): 64 | """Test multiple inheritance from dynamic MagicMock.""" 65 | Class1 = _safe_import("foobar.foo.FooBar") 66 | Class2 = _safe_import("barfoobar.BarFooBar") 67 | 68 | class NewClass(Class1, Class2): 69 | """This should not trigger an error. 70 | 71 | The class definition would trigger an error if multiple inheritance 72 | from Class1 and Class2 does not work, e.g., if it is simply 73 | identical to MagicMock. 74 | """ 75 | 76 | pass 77 | -------------------------------------------------------------------------------- /pytorch_forecasting/models/xlstm/_xlstm_pkg.py: -------------------------------------------------------------------------------- 1 | """xLSTMTime package container.""" 2 | 3 | from pytorch_forecasting.models.base._base_object import _BasePtForecaster 4 | 5 | 6 | class xLSTMTime_pkg(_BasePtForecaster): 7 | """xLSTMTime package container.""" 8 | 9 | _tags = { 10 | "info:name": "xLSTMTime", 11 | "info:compute": 3, 12 | "info:pred_type": ["point"], 13 | "info:y_type": ["numeric"], 14 | "authors": ["muslehal", "phoeenniixx"], 15 | "capability:exogenous": True, 16 | "capability:multivariate": True, 17 | "capability:pred_int": False, 18 | "capability:flexible_history_length": True, 19 | "capability:cold_start": False, 20 | } 21 | 22 | @classmethod 23 | def get_cls(cls): 24 | """Get model class.""" 25 | from pytorch_forecasting.models import xLSTMTime 26 | 27 | return xLSTMTime 28 | 29 | @classmethod 30 | def get_base_test_params(cls): 31 | """ 32 | Return testing parameter settings for the trainer. 33 | 34 | Returns 35 | ------- 36 | params : dict or list of dict, default = {} 37 | Parameters to create testing instances of the class 38 | """ 39 | 40 | params = [ 41 | {}, 42 | {"xlstm_type": "mlstm"}, 43 | {"num_layers": 2}, 44 | {"xlstm_type": "slstm", "input_projection_size": 32}, 45 | { 46 | "xlstm_type": "mlstm", 47 | "decomposition_kernel": 13, 48 | "dropout": 0.2, 49 | }, 50 | ] 51 | defaults = {"hidden_size": 32, "input_size": 1, "output_size": 1} 52 | for param in params: 53 | param.update(defaults) 54 | return params 55 | 56 | @classmethod 57 | def _get_test_dataloaders_from(cls, params): 58 | """ 59 | Get dataloaders from parameters. 60 | 61 | Parameters 62 | ---------- 63 | params: dict 64 | Parameters to create dataloaders. 65 | One of the elements in the list returned by ``get_test_train_params``. 66 | 67 | Returns 68 | ------- 69 | dataloaders: Dict[str, DataLoader] 70 | Dict of dataloaders created from the parameters. 71 | Train, validation, and test dataloaders created from the parameters. 72 | """ 73 | from pytorch_forecasting.tests._data_scenarios import ( 74 | dataloaders_fixed_window_without_covariates, 75 | ) 76 | 77 | return dataloaders_fixed_window_without_covariates() 78 | -------------------------------------------------------------------------------- /pytorch_forecasting/models/tide/_tide_dsipts/_tide_v2_pkg.py: -------------------------------------------------------------------------------- 1 | """TIDE package container.""" 2 | 3 | from pytorch_forecasting.base._base_pkg import Base_pkg 4 | 5 | 6 | class TIDE_pkg_v2(Base_pkg): 7 | """TIDE package container.""" 8 | 9 | _tags = { 10 | "info:name": "TIDE", 11 | "authors": ["fbk_dsipts"], 12 | } 13 | 14 | @classmethod 15 | def get_cls(cls): 16 | """Get model class.""" 17 | from pytorch_forecasting.models.tide._tide_dsipts._tide_v2 import TIDE 18 | 19 | return TIDE 20 | 21 | @classmethod 22 | def get_datamodule_cls(cls): 23 | """Get the underlying DataModule class.""" 24 | from pytorch_forecasting.data.data_module import ( 25 | EncoderDecoderTimeSeriesDataModule, 26 | ) 27 | 28 | return EncoderDecoderTimeSeriesDataModule 29 | 30 | @classmethod 31 | def get_test_train_params(cls): 32 | """Return testing parameter settings for the trainer. 33 | 34 | Returns 35 | ------- 36 | params : dict or list of dict, default = {} 37 | Parameters to create testing instances of the class 38 | Each dict are parameters to construct an "interesting" test instance, i.e., 39 | `MyClass(**params)` or `MyClass(**params[i])` creates a valid test instance. 40 | `create_test_instance` uses the first (or only) dictionary in `params` 41 | """ 42 | from pytorch_forecasting.metrics import MAE, MAPE 43 | 44 | params = [ 45 | dict( 46 | hidden_size=16, 47 | d_model=8, 48 | n_add_enc=1, 49 | n_add_dec=1, 50 | dropout_rate=0.1, 51 | ), 52 | dict( 53 | hidden_size=32, 54 | d_model=16, 55 | n_add_enc=2, 56 | n_add_dec=2, 57 | dropout_rate=0.2, 58 | datamodule_cfg=dict(max_encoder_length=5, max_prediction_length=3), 59 | loss=MAE(), 60 | ), 61 | dict( 62 | hidden_size=64, 63 | d_model=32, 64 | n_add_enc=3, 65 | n_add_dec=2, 66 | dropout_rate=0.1, 67 | datamodule_cfg=dict(max_encoder_length=4, max_prediction_length=2), 68 | loss=MAPE(), 69 | ), 70 | ] 71 | default_dm_cfg = {"max_encoder_length": 4, "max_prediction_length": 3} 72 | 73 | for param in params: 74 | current_dm_cfg = param.get("datamodule_cfg", {}) 75 | default_dm_cfg.update(current_dm_cfg) 76 | 77 | param["datamodule_cfg"] = default_dm_cfg 78 | 79 | return params 80 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 6 | 7 | #### Reference Issues/PRs 8 | 16 | 17 | #### What does this implement/fix? Explain your changes. 18 | 21 | 22 | #### What should a reviewer concentrate their feedback on? 23 | 24 | 25 | 26 | #### Did you add any tests for the change? 27 | 28 | 30 | 31 | #### Any other comments? 32 | 36 | 37 | #### PR checklist 38 | 41 | 42 | - [ ] The PR title starts with either [ENH], [MNT], [DOC], or [BUG]. [BUG] - bugfix, [MNT] - CI, test framework, [ENH] - adding or improving code, [DOC] - writing or improving documentation or docstrings. 43 | - [ ] Added/modified tests 44 | - [ ] Used pre-commit hooks when committing to ensure that code is compliant with hooks. Install hooks with `pre-commit install`. 45 | To run hooks independent of commit, execute `pre-commit run --all-files` 46 | 47 | 50 | 51 | -------------------------------------------------------------------------------- /pytorch_forecasting/layers/_encoders/_encoder_layer.py: -------------------------------------------------------------------------------- 1 | """ 2 | Implementation of EncoderLayer for encoder-decoder architectures from `nn.Module`. 3 | """ 4 | 5 | import math 6 | from math import sqrt 7 | 8 | import numpy as np 9 | import torch 10 | import torch.nn as nn 11 | import torch.nn.functional as F 12 | 13 | 14 | class EncoderLayer(nn.Module): 15 | """ 16 | Encoder layer for the TimeXer model. 17 | Args: 18 | self_attention (nn.Module): Self-attention mechanism. 19 | cross_attention (nn.Module): Cross-attention mechanism. 20 | d_model (int): Dimension of the model. 21 | d_ff (int, optional): 22 | Dimension of the feedforward layer. Defaults to 4 * d_model. 23 | dropout (float): Dropout rate. Defaults to 0.1. 24 | activation (str): Activation function. Defaults to "relu". 25 | """ 26 | 27 | def __init__( 28 | self, 29 | self_attention, 30 | cross_attention, 31 | d_model, 32 | d_ff=None, 33 | dropout=0.1, 34 | activation="relu", 35 | ): 36 | super().__init__() 37 | d_ff = d_ff or 4 * d_model 38 | self.self_attention = self_attention 39 | self.cross_attention = cross_attention 40 | self.conv1 = nn.Conv1d(in_channels=d_model, out_channels=d_ff, kernel_size=1) 41 | self.conv2 = nn.Conv1d(in_channels=d_ff, out_channels=d_model, kernel_size=1) 42 | self.norm1 = nn.LayerNorm(d_model) 43 | self.norm2 = nn.LayerNorm(d_model) 44 | self.norm3 = nn.LayerNorm(d_model) 45 | self.dropout = nn.Dropout(dropout) 46 | self.activation = F.relu if activation == "relu" else F.gelu 47 | 48 | def forward(self, x, cross, x_mask=None, cross_mask=None, tau=None, delta=None): 49 | B, L, D = cross.shape 50 | x = x + self.dropout( 51 | self.self_attention(x, x, x, attn_mask=x_mask, tau=tau, delta=None)[0] 52 | ) 53 | x = self.norm1(x) 54 | 55 | x_glb_ori = x[:, -1, :].unsqueeze(1) 56 | x_glb = torch.reshape(x_glb_ori, (B, -1, D)) 57 | x_glb_attn = self.dropout( 58 | self.cross_attention( 59 | x_glb, cross, cross, attn_mask=cross_mask, tau=tau, delta=delta 60 | )[0] 61 | ) 62 | x_glb_attn = torch.reshape( 63 | x_glb_attn, (x_glb_attn.shape[0] * x_glb_attn.shape[1], x_glb_attn.shape[2]) 64 | ).unsqueeze(1) 65 | x_glb = x_glb_ori + x_glb_attn 66 | x_glb = self.norm2(x_glb) 67 | 68 | y = x = torch.cat([x[:, :-1, :], x_glb], dim=1) 69 | 70 | y = self.dropout(self.activation(self.conv1(y.transpose(-1, 1)))) 71 | y = self.dropout(self.conv2(y).transpose(-1, 1)) 72 | 73 | return self.norm3(x + y) 74 | -------------------------------------------------------------------------------- /pytorch_forecasting/models/temporal_fusion_transformer/_tft_pkg_v2.py: -------------------------------------------------------------------------------- 1 | """TFT package container.""" 2 | 3 | from pytorch_forecasting.base._base_pkg import Base_pkg 4 | 5 | 6 | class TFT_pkg_v2(Base_pkg): 7 | """TFT package container.""" 8 | 9 | _tags = { 10 | "info:name": "TFT", 11 | "authors": ["phoeenniixx"], 12 | "capability:exogenous": True, 13 | "capability:multivariate": True, 14 | "capability:pred_int": True, 15 | "capability:flexible_history_length": False, 16 | } 17 | 18 | @classmethod 19 | def get_cls(cls): 20 | """Get model class.""" 21 | from pytorch_forecasting.models.temporal_fusion_transformer._tft_v2 import TFT 22 | 23 | return TFT 24 | 25 | @classmethod 26 | def get_datamodule_cls(cls): 27 | """Get the underlying DataModule class.""" 28 | from pytorch_forecasting.data.data_module import ( 29 | EncoderDecoderTimeSeriesDataModule, 30 | ) 31 | 32 | return EncoderDecoderTimeSeriesDataModule 33 | 34 | @classmethod 35 | def get_test_train_params(cls): 36 | """Return testing parameter settings for the trainer. 37 | 38 | Returns 39 | ------- 40 | params : dict or list of dict, default = {} 41 | Parameters to create testing instances of the class 42 | Each dict are parameters to construct an "interesting" test instance, i.e., 43 | `MyClass(**params)` or `MyClass(**params[i])` creates a valid test instance. 44 | `create_test_instance` uses the first (or only) dictionary in `params` 45 | """ 46 | params = [ 47 | {}, 48 | dict( 49 | hidden_size=25, 50 | attention_head_size=5, 51 | ), 52 | dict(datamodule_cfg=dict(max_encoder_length=5, max_prediction_length=3)), 53 | dict( 54 | hidden_size=24, 55 | attention_head_size=8, 56 | datamodule_cfg=dict( 57 | max_encoder_length=5, 58 | max_prediction_length=3, 59 | add_relative_time_idx=False, 60 | ), 61 | ), 62 | dict( 63 | hidden_size=12, 64 | datamodule_cfg=dict(max_encoder_length=7, max_prediction_length=10), 65 | ), 66 | dict(attention_head_size=2), 67 | ] 68 | 69 | default_dm_cfg = {"max_encoder_length": 4, "max_prediction_length": 3} 70 | 71 | for param in params: 72 | current_dm_cfg = param.get("datamodule_cfg", {}) 73 | default_dm_cfg.update(current_dm_cfg) 74 | 75 | param["datamodule_cfg"] = default_dm_cfg 76 | 77 | return params 78 | -------------------------------------------------------------------------------- /pytorch_forecasting/models/baseline.py: -------------------------------------------------------------------------------- 1 | """ 2 | Baseline model. 3 | """ 4 | 5 | from typing import Any 6 | 7 | import torch 8 | 9 | from pytorch_forecasting.models import BaseModel 10 | 11 | 12 | class Baseline(BaseModel): 13 | """ 14 | Baseline model that uses last known target value to make prediction. 15 | 16 | Example: 17 | 18 | .. code-block:: python 19 | 20 | from pytorch_forecasting import BaseModel, MAE 21 | 22 | # generating predictions 23 | predictions = Baseline().predict(dataloader) 24 | 25 | # calculate baseline performance in terms of mean absolute error (MAE) 26 | metric = MAE() 27 | model = Baseline() 28 | for x, y in dataloader: 29 | metric.update(model(x), y) 30 | 31 | metric.compute() 32 | """ 33 | 34 | def forward(self, x: dict[str, torch.Tensor]) -> dict[str, torch.Tensor]: 35 | """ 36 | Network forward pass. 37 | 38 | Args: 39 | x (Dict[str, torch.Tensor]): network input 40 | 41 | Returns: 42 | Dict[str, torch.Tensor]: network outputs 43 | """ 44 | if isinstance(x["encoder_target"], (list, tuple)): # multiple targets 45 | prediction = [ 46 | self.forward_one_target( 47 | encoder_lengths=x["encoder_lengths"], 48 | decoder_lengths=x["decoder_lengths"], 49 | encoder_target=encoder_target, 50 | ) 51 | for encoder_target in x["encoder_target"] 52 | ] 53 | else: # one target 54 | prediction = self.forward_one_target( 55 | encoder_lengths=x["encoder_lengths"], 56 | decoder_lengths=x["decoder_lengths"], 57 | encoder_target=x["encoder_target"], 58 | ) 59 | return self.to_network_output(prediction=prediction) 60 | 61 | def forward_one_target( 62 | self, 63 | encoder_lengths: torch.Tensor, 64 | decoder_lengths: torch.Tensor, 65 | encoder_target: torch.Tensor, 66 | ): 67 | max_prediction_length = decoder_lengths.max() 68 | assert ( 69 | encoder_lengths.min() > 0 70 | ), "Encoder lengths of at least 1 required to obtain last value" 71 | last_values = encoder_target[ 72 | torch.arange(encoder_target.size(0)), encoder_lengths - 1 73 | ] 74 | prediction = last_values[:, None].expand(-1, max_prediction_length) 75 | return prediction 76 | 77 | def to_prediction(self, out: dict[str, Any], use_metric: bool = True, **kwargs): 78 | return out.prediction 79 | 80 | def to_quantiles(self, out: dict[str, Any], use_metric: bool = True, **kwargs): 81 | return out.prediction[..., None] 82 | -------------------------------------------------------------------------------- /pytorch_forecasting/metrics/__init__.py: -------------------------------------------------------------------------------- 1 | """Metrics for (multi-horizon) timeseries forecasting.""" 2 | 3 | from pytorch_forecasting.metrics._distributions_pkg import ( 4 | BetaDistributionLoss_pkg, 5 | ImplicitQuantileNetworkDistributionLoss_pkg, 6 | LogNormalDistributionLoss_pkg, 7 | MQF2DistributionLoss_pkg, 8 | MultivariateNormalDistributionLoss_pkg, 9 | NegativeBinomialDistributionLoss_pkg, 10 | NormalDistributionLoss_pkg, 11 | ) 12 | from pytorch_forecasting.metrics._point_pkg import ( 13 | CrossEntropy_pkg, 14 | MAE_pkg, 15 | MAPE_pkg, 16 | MASE_pkg, 17 | PoissonLoss_pkg, 18 | RMSE_pkg, 19 | SMAPE_pkg, 20 | TweedieLoss_pkg, 21 | ) 22 | from pytorch_forecasting.metrics._quantile_pkg import QuantileLoss_pkg 23 | from pytorch_forecasting.metrics.base_metrics import ( 24 | DistributionLoss, 25 | Metric, 26 | MultiHorizonMetric, 27 | MultiLoss, 28 | MultivariateDistributionLoss, 29 | convert_torchmetric_to_pytorch_forecasting_metric, 30 | ) 31 | from pytorch_forecasting.metrics.distributions import ( 32 | BetaDistributionLoss, 33 | ImplicitQuantileNetworkDistributionLoss, 34 | LogNormalDistributionLoss, 35 | MQF2DistributionLoss, 36 | MultivariateNormalDistributionLoss, 37 | NegativeBinomialDistributionLoss, 38 | NormalDistributionLoss, 39 | ) 40 | from pytorch_forecasting.metrics.point import ( 41 | MAE, 42 | MAPE, 43 | MASE, 44 | RMSE, 45 | SMAPE, 46 | CrossEntropy, 47 | PoissonLoss, 48 | TweedieLoss, 49 | ) 50 | from pytorch_forecasting.metrics.quantile import QuantileLoss 51 | 52 | __all__ = [ 53 | "MultiHorizonMetric", 54 | "DistributionLoss", 55 | "MultivariateDistributionLoss", 56 | "MultiLoss", 57 | "Metric", 58 | "convert_torchmetric_to_pytorch_forecasting_metric", 59 | "MAE", 60 | "MAPE", 61 | "MASE", 62 | "PoissonLoss", 63 | "TweedieLoss", 64 | "CrossEntropy", 65 | "SMAPE", 66 | "RMSE", 67 | "BetaDistributionLoss", 68 | "NegativeBinomialDistributionLoss", 69 | "NormalDistributionLoss", 70 | "LogNormalDistributionLoss", 71 | "MultivariateNormalDistributionLoss", 72 | "ImplicitQuantileNetworkDistributionLoss", 73 | "QuantileLoss", 74 | "MQF2DistributionLoss", 75 | "MAE_pkg", 76 | "MAPE_pkg", 77 | "MASE_pkg", 78 | "SMAPE_pkg", 79 | "RMSE_pkg", 80 | "PoissonLoss_pkg", 81 | "TweedieLoss_pkg", 82 | "CrossEntropy_pkg", 83 | "QuantileLoss_pkg", 84 | "BetaDistributionLoss_pkg", 85 | "ImplicitQuantileNetworkDistributionLoss_pkg", 86 | "LogNormalDistributionLoss_pkg", 87 | "MultivariateNormalDistributionLoss_pkg", 88 | "NegativeBinomialDistributionLoss_pkg", 89 | "NormalDistributionLoss_pkg", 90 | "MQF2DistributionLoss_pkg", 91 | ] 92 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | docs/source/api/ 74 | docs/source/CHANGELOG.md 75 | 76 | # PyBuilder 77 | target/ 78 | 79 | # Jupyter Notebook 80 | .ipynb_checkpoints 81 | 82 | # IPython 83 | profile_default/ 84 | ipython_config.py 85 | 86 | # pyenv 87 | .python-version 88 | 89 | # pipenv 90 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 91 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 92 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 93 | # install all needed dependencies. 94 | #Pipfile.lock 95 | 96 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 97 | __pypackages__/ 98 | 99 | # Celery stuff 100 | celerybeat-schedule 101 | celerybeat.pid 102 | 103 | # SageMath parsed files 104 | *.sage.py 105 | 106 | # Environments 107 | .env 108 | .venv 109 | env/ 110 | venv/ 111 | ENV/ 112 | env.bak/ 113 | venv.bak/ 114 | 115 | # Spyder project settings 116 | .spyderproject 117 | .spyproject 118 | 119 | # Rope project settings 120 | .ropeproject 121 | 122 | # mkdocs documentation 123 | /site 124 | 125 | # mypy 126 | .mypy_cache/ 127 | .dmypy.json 128 | dmypy.json 129 | 130 | # Pyre type checker 131 | .pyre/ 132 | 133 | # pycharm 134 | .idea 135 | 136 | # vscode 137 | .vscode 138 | 139 | # logs 140 | lightning_logs 141 | .history 142 | 143 | # checkpoints 144 | *.ckpt 145 | *.pkl 146 | .DS_Store 147 | 148 | # data 149 | pytorch_forecasting/data/*.parquet 150 | -------------------------------------------------------------------------------- /pytorch_forecasting/layers/_normalization/_revin.py: -------------------------------------------------------------------------------- 1 | """ 2 | Reverse Instance Normalization (RevIN) layer. 3 | --------------------------------------------- 4 | """ 5 | 6 | import torch 7 | import torch.nn as nn 8 | 9 | 10 | class RevIN(nn.Module): 11 | def __init__(self, num_features, eps=1e-5, affine=True, subtract_last=False): 12 | """ 13 | Reverse Instance Normalization (RevIN) layer. 14 | 15 | Parameters 16 | ---------- 17 | num_features : int 18 | Number of input features. 19 | eps : float, optional 20 | A small value added to the denominator for numerical stability (default: 1e-5). 21 | affine : bool, optional 22 | If True, the layer will have learnable affine parameters (default: True). 23 | subtract_last: bool, optional 24 | If True, the last feature will be subtracted from the mean (default: False). 25 | """ # noqa: E501 26 | super().__init__() 27 | self.num_features = num_features 28 | self.eps = eps 29 | self.affine = affine 30 | self.subtract_last = subtract_last 31 | 32 | if self.affine: 33 | self._init_params() 34 | 35 | def forward(self, x, mode: str): 36 | if mode == "norm": 37 | self._get_statistics(x) 38 | x = self._normalize(x) 39 | elif mode == "denorm": 40 | x = self._denormalize(x) 41 | else: 42 | raise NotImplementedError 43 | return x 44 | 45 | def _init_params(self): 46 | """Initialize learnable parameters if affine is True.""" 47 | self.affine_weight = nn.Parameter(torch.ones(self.num_features)) 48 | self.affine_bias = nn.Parameter(torch.zeros(self.num_features)) 49 | 50 | def _get_statistics(self, x): 51 | dim2reduce = tuple(range(1, x.ndim - 1)) 52 | if self.subtract_last: 53 | self.last = x[:, -1, :].unsqueeze(1) 54 | else: 55 | self.mean = torch.mean(x, dim=dim2reduce, keepdim=True).detach() 56 | self.stdev = torch.sqrt( 57 | torch.var(x, dim=dim2reduce, keepdim=True, unbiased=False) + self.eps 58 | ).detach() # noqa: E501 59 | 60 | def _normalize(self, x): 61 | if self.subtract_last: 62 | x = x - self.last 63 | else: 64 | x = x - self.mean 65 | x = x / self.stdev 66 | if self.affine: 67 | x = x * self.affine_weight 68 | x = x + self.affine_bias 69 | return x 70 | 71 | def _denormalize(self, x): 72 | if self.affine: 73 | x = x - self.affine_bias 74 | x = x / (self.affine_weight + self.eps * self.eps) 75 | x = x * self.stdev 76 | if self.subtract_last: 77 | x = x + self.last 78 | else: 79 | x = x + self.mean 80 | return x 81 | -------------------------------------------------------------------------------- /docs/source/index.rst: -------------------------------------------------------------------------------- 1 | .. pytorch-forecasting documentation master file, created by 2 | sphinx-quickstart on Sun Aug 16 22:17:24 2020. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | PyTorch Forecasting Documentation 7 | ================================== 8 | 9 | .. raw:: html 10 | 11 | GitHub 12 | 13 | 14 | Our article on `Towards Data Science `_ 15 | introduces the package and provides background information. 16 | 17 | PyTorch Forecasting aims to ease state-of-the-art 18 | timeseries forecasting with neural networks for both real-world cases and 19 | research alike. The goal is to provide a high-level API with maximum flexibility for 20 | professionals and reasonable defaults for beginners. 21 | Specifically, the package provides 22 | 23 | * A timeseries dataset class which abstracts handling variable transformations, missing values, 24 | randomized subsampling, multiple history lengths, etc. 25 | * A base model class which provides basic training of timeseries models along with logging in tensorboard 26 | and generic visualizations such actual vs predictions and dependency plots 27 | * Multiple neural network architectures for timeseries forecasting that have been enhanced 28 | for real-world deployment and come with in-built interpretation capabilities 29 | * Multi-horizon timeseries metrics 30 | * Hyperparameter tuning with `optuna `_ 31 | 32 | The package is built on `PyTorch Lightning `_ to allow 33 | training on CPUs, single and multiple GPUs out-of-the-box. 34 | 35 | If you do not have pytorch already installed, follow the :ref:`detailed installation instructions`. 36 | 37 | Otherwise, proceed to install the package by executing 38 | 39 | .. code-block:: 40 | 41 | pip install pytorch-forecasting 42 | 43 | or to install via conda 44 | 45 | .. code-block:: 46 | 47 | conda install pytorch-forecasting pytorch>=1.7 -c pytorch -c conda-forge 48 | 49 | To use the MQF2 loss (multivariate quantile loss), also execute 50 | 51 | .. code-block:: 52 | 53 | pip install pytorch-forecasting[mqf2] 54 | 55 | Visit :ref:`Getting started ` to learn more about the package and detailed installation instruction. 56 | The :ref:`Tutorials ` section provides guidance on how to use models and implement new ones. 57 | 58 | .. toctree:: 59 | :titlesonly: 60 | :hidden: 61 | :maxdepth: 6 62 | 63 | getting-started 64 | tutorials 65 | data 66 | models 67 | metrics 68 | faq 69 | installation 70 | api 71 | CHANGELOG 72 | 73 | 74 | Indices and tables 75 | ================== 76 | 77 | * :ref:`genindex` 78 | * :ref:`modindex` 79 | * :ref:`search` 80 | -------------------------------------------------------------------------------- /pytorch_forecasting/models/nbeats/_nbeats_pkg.py: -------------------------------------------------------------------------------- 1 | """NBeats package container.""" 2 | 3 | from pytorch_forecasting.models.base._base_object import _BasePtForecaster 4 | 5 | 6 | class NBeats_pkg(_BasePtForecaster): 7 | """NBeats package container.""" 8 | 9 | _tags = { 10 | "info:name": "NBeats", 11 | "info:compute": 1, 12 | "info:pred_type": ["point"], 13 | "info:y_type": ["numeric"], 14 | "authors": ["jdb78"], 15 | "capability:exogenous": False, 16 | "capability:multivariate": False, 17 | "capability:pred_int": False, 18 | "capability:flexible_history_length": False, 19 | "capability:cold_start": False, 20 | } 21 | 22 | @classmethod 23 | def get_cls(cls): 24 | """Get model class.""" 25 | from pytorch_forecasting.models import NBeats 26 | 27 | return NBeats 28 | 29 | @classmethod 30 | def get_base_test_params(cls): 31 | """Return testing parameter settings for the trainer. 32 | 33 | Returns 34 | ------- 35 | params : dict or list of dict, default = {} 36 | Parameters to create testing instances of the class 37 | Each dict are parameters to construct an "interesting" test instance, i.e., 38 | `MyClass(**params)` or `MyClass(**params[i])` creates a valid test instance. 39 | `create_test_instance` uses the first (or only) dictionary in `params` 40 | """ 41 | return [{}, {"backcast_loss_ratio": 1.0}] 42 | 43 | @classmethod 44 | def _get_test_dataloaders_from(cls, params): 45 | """Get dataloaders from parameters. 46 | 47 | Parameters 48 | ---------- 49 | params : dict 50 | Parameters to create dataloaders. 51 | One of the elements in the list returned by ``get_test_train_params``. 52 | 53 | Returns 54 | ------- 55 | dataloaders : dict with keys "train", "val", "test", values torch DataLoader 56 | Dict of dataloaders created from the parameters. 57 | Train, validation, and test dataloaders, in this order. 58 | """ 59 | loss = params.get("loss", None) 60 | data_loader_kwargs = params.get("data_loader_kwargs", {}) 61 | from pytorch_forecasting.metrics import TweedieLoss 62 | from pytorch_forecasting.tests._data_scenarios import ( 63 | data_with_covariates, 64 | dataloaders_fixed_window_without_covariates, 65 | make_dataloaders, 66 | ) 67 | 68 | if isinstance(loss, TweedieLoss): 69 | dwc = data_with_covariates() 70 | dl_default_kwargs = dict( 71 | target="target", 72 | time_varying_unknown_reals=["target"], 73 | add_relative_time_idx=False, 74 | ) 75 | dl_default_kwargs.update(data_loader_kwargs) 76 | dataloaders_with_covariates = make_dataloaders(dwc, **dl_default_kwargs) 77 | return dataloaders_with_covariates 78 | 79 | return dataloaders_fixed_window_without_covariates() 80 | -------------------------------------------------------------------------------- /pytorch_forecasting/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | PyTorch Forecasting package for timeseries forecasting with PyTorch. 3 | """ 4 | 5 | __version__ = "1.5.0" 6 | 7 | from pytorch_forecasting.data import ( 8 | EncoderNormalizer, 9 | GroupNormalizer, 10 | MultiNormalizer, 11 | NaNLabelEncoder, 12 | TimeSeriesDataSet, 13 | ) 14 | from pytorch_forecasting.metrics import ( 15 | MAE, 16 | MAPE, 17 | MASE, 18 | RMSE, 19 | SMAPE, 20 | BetaDistributionLoss, 21 | CrossEntropy, 22 | DistributionLoss, 23 | ImplicitQuantileNetworkDistributionLoss, 24 | LogNormalDistributionLoss, 25 | MQF2DistributionLoss, 26 | MultiHorizonMetric, 27 | MultiLoss, 28 | MultivariateNormalDistributionLoss, 29 | NegativeBinomialDistributionLoss, 30 | NormalDistributionLoss, 31 | PoissonLoss, 32 | QuantileLoss, 33 | ) 34 | from pytorch_forecasting.models import ( 35 | GRU, 36 | LSTM, 37 | AutoRegressiveBaseModel, 38 | AutoRegressiveBaseModelWithCovariates, 39 | Baseline, 40 | BaseModel, 41 | BaseModelWithCovariates, 42 | DecoderMLP, 43 | DeepAR, 44 | MultiEmbedding, 45 | NBeats, 46 | NBeatsKAN, 47 | NHiTS, 48 | RecurrentNetwork, 49 | TemporalFusionTransformer, 50 | TiDEModel, 51 | get_rnn, 52 | ) 53 | from pytorch_forecasting.utils import ( 54 | apply_to_list, 55 | autocorrelation, 56 | create_mask, 57 | detach, 58 | get_embedding_size, 59 | groupby_apply, 60 | integer_histogram, 61 | move_to_device, 62 | profile, 63 | to_list, 64 | unpack_sequence, 65 | ) 66 | from pytorch_forecasting.utils._maint._show_versions import show_versions 67 | 68 | __all__ = [ 69 | "TimeSeriesDataSet", 70 | "GroupNormalizer", 71 | "EncoderNormalizer", 72 | "NaNLabelEncoder", 73 | "MultiNormalizer", 74 | "TemporalFusionTransformer", 75 | "TiDEModel", 76 | "NBeats", 77 | "NBeatsKAN", 78 | "NHiTS", 79 | "Baseline", 80 | "DeepAR", 81 | "BaseModel", 82 | "BaseModelWithCovariates", 83 | "AutoRegressiveBaseModel", 84 | "AutoRegressiveBaseModelWithCovariates", 85 | "MultiHorizonMetric", 86 | "MultiLoss", 87 | "MAE", 88 | "MAPE", 89 | "MASE", 90 | "SMAPE", 91 | "DistributionLoss", 92 | "BetaDistributionLoss", 93 | "LogNormalDistributionLoss", 94 | "NegativeBinomialDistributionLoss", 95 | "NormalDistributionLoss", 96 | "ImplicitQuantileNetworkDistributionLoss", 97 | "MultivariateNormalDistributionLoss", 98 | "MQF2DistributionLoss", 99 | "CrossEntropy", 100 | "PoissonLoss", 101 | "QuantileLoss", 102 | "RMSE", 103 | "get_rnn", 104 | "LSTM", 105 | "GRU", 106 | "MultiEmbedding", 107 | "apply_to_list", 108 | "autocorrelation", 109 | "get_embedding_size", 110 | "create_mask", 111 | "to_list", 112 | "RecurrentNetwork", 113 | "DecoderMLP", 114 | "detach", 115 | "move_to_device", 116 | "integer_histogram", 117 | "groupby_apply", 118 | "profile", 119 | "show_versions", 120 | "unpack_sequence", 121 | ] 122 | -------------------------------------------------------------------------------- /pytorch_forecasting/layers/_recurrent/_slstm/network.py: -------------------------------------------------------------------------------- 1 | import torch 2 | import torch.nn as nn 3 | 4 | from pytorch_forecasting.layers._recurrent._slstm.layer import sLSTMLayer 5 | 6 | 7 | class sLSTMNetwork(nn.Module): 8 | """Implements the Stabilized LSTM Network with multiple sLSTM layers. 9 | 10 | This network combines sLSTM layers with a fully connected output layer for 11 | prediction. 12 | 13 | Parameters 14 | ---------- 15 | input_size : int 16 | Number of features in the input. 17 | hidden_size : int 18 | Number of features in the hidden state of each sLSTM layer. 19 | num_layers : int 20 | Number of stacked sLSTM layers in the network. 21 | output_size : int 22 | Number of features in the output prediction. 23 | dropout : float, optional 24 | Dropout probability for the input of each sLSTM layer, by default 0.0. 25 | use_layer_norm : bool, optional 26 | Whether to use layer normalization in each sLSTM layer, by default True. 27 | 28 | Attributes 29 | ---------- 30 | slstm_layer : sLSTMLayer 31 | Stacked sLSTM layers used for processing input sequences. 32 | fc : nn.Linear 33 | Fully connected layer to generate the final output predictions. 34 | """ 35 | 36 | def __init__( 37 | self, 38 | input_size, 39 | hidden_size, 40 | num_layers, 41 | output_size, 42 | dropout=0.0, 43 | use_layer_norm=True, 44 | ): 45 | super().__init__() 46 | self.input_size = input_size 47 | self.hidden_size = hidden_size 48 | self.num_layers = num_layers 49 | self.output_size = output_size 50 | self.dropout = dropout 51 | 52 | self.slstm_layer = sLSTMLayer( 53 | input_size, 54 | hidden_size, 55 | num_layers, 56 | dropout, 57 | use_layer_norm, 58 | ) 59 | self.fc = nn.Linear(hidden_size, output_size) 60 | 61 | def forward(self, x, h=None, c=None): 62 | """ 63 | Forward pass through the sLSTM network. 64 | 65 | Parameters 66 | ---------- 67 | x : torch.Tensor 68 | The number of features in the input. 69 | h : list of torch.Tensor, optional 70 | Initial hidden states for each layer. 71 | If None, hidden states are initialized to zeros. 72 | c : list of torch.Tensor, optional 73 | Initial cell states for each layer. 74 | If None, cell states are initialized to zeros. 75 | 76 | Returns 77 | ------- 78 | output : torch.Tensor 79 | Tensor containing the final output predictions. 80 | (h, c) : tuple of lists 81 | Final hidden and cell states for each layer. 82 | """ 83 | output, (h, c) = self.slstm_layer(x, h, c) 84 | output = self.fc(output[-1]) 85 | return output, (h, c) 86 | 87 | def init_hidden(self, batch_size, device=None): 88 | """Initialize hidden and cell states for the entire network.""" 89 | if device is None: 90 | device = next(self.parameters()).device 91 | return self.slstm_layer.init_hidden(batch_size, device=device) 92 | -------------------------------------------------------------------------------- /pytorch_forecasting/models/nbeats/_nbeatskan_pkg.py: -------------------------------------------------------------------------------- 1 | """NBeatsKAN package container.""" 2 | 3 | from pytorch_forecasting.models.base._base_object import _BasePtForecaster 4 | 5 | 6 | class NBeatsKAN_pkg(_BasePtForecaster): 7 | """NBeatsKAN package container.""" 8 | 9 | _tags = { 10 | "info:name": "NBeatsKAN", 11 | "info:compute": 1, 12 | "info:pred_type": ["point"], 13 | "info:y_type": ["numeric"], 14 | "authors": ["Sohaib-Ahmed21"], 15 | "capability:exogenous": False, 16 | "capability:multivariate": False, 17 | "capability:pred_int": False, 18 | "capability:flexible_history_length": False, 19 | "capability:cold_start": False, 20 | } 21 | 22 | @classmethod 23 | def get_cls(cls): 24 | """Get model class.""" 25 | from pytorch_forecasting.models import NBeatsKAN 26 | 27 | return NBeatsKAN 28 | 29 | @classmethod 30 | def get_base_test_params(cls): 31 | """Return testing parameter settings for the trainer. 32 | 33 | Returns 34 | ------- 35 | params : dict or list of dict, default = {} 36 | Parameters to create testing instances of the class 37 | Each dict are parameters to construct an "interesting" test instance, i.e., 38 | `MyClass(**params)` or `MyClass(**params[i])` creates a valid test instance. 39 | `create_test_instance` uses the first (or only) dictionary in `params` 40 | """ 41 | return [ 42 | {"backcast_loss_ratio": 0.0}, # pure forecast loss 43 | {"backcast_loss_ratio": 1.0}, # equal forecast/backcast 44 | { 45 | "stack_types": ["generic"], 46 | "expansion_coefficient_lengths": [16], 47 | }, 48 | { 49 | "num_blocks": [1, 2], 50 | "num_block_layers": [2, 3], 51 | }, # varying block structure 52 | { 53 | "num": 7, 54 | "k": 4, 55 | "sparse_init": True, 56 | "grid_range": [-0.5, 0.5], 57 | "sp_trainable": False, 58 | }, # complex KAN config 59 | ] 60 | 61 | @classmethod 62 | def _get_test_dataloaders_from(cls, params): 63 | loss = params.get("loss", None) 64 | data_loader_kwargs = params.get("data_loader_kwargs", {}) 65 | from pytorch_forecasting.metrics import TweedieLoss 66 | from pytorch_forecasting.tests._data_scenarios import ( 67 | data_with_covariates, 68 | dataloaders_fixed_window_without_covariates, 69 | make_dataloaders, 70 | ) 71 | 72 | if isinstance(loss, TweedieLoss): 73 | dwc = data_with_covariates() 74 | dl_default_kwargs = dict( 75 | target="target", 76 | time_varying_unknown_reals=["target"], 77 | add_relative_time_idx=False, 78 | ) 79 | dl_default_kwargs.update(data_loader_kwargs) 80 | dataloaders_with_covariates = make_dataloaders(dwc, **dl_default_kwargs) 81 | return dataloaders_with_covariates 82 | 83 | return dataloaders_fixed_window_without_covariates() 84 | -------------------------------------------------------------------------------- /examples/nbeats.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | import lightning.pytorch as pl 4 | from lightning.pytorch.callbacks import EarlyStopping 5 | import pandas as pd 6 | 7 | from pytorch_forecasting import NBeats, TimeSeriesDataSet 8 | from pytorch_forecasting.data import NaNLabelEncoder 9 | from pytorch_forecasting.data.examples import generate_ar_data 10 | 11 | sys.path.append("..") 12 | 13 | 14 | print("load data") 15 | data = generate_ar_data(seasonality=10.0, timesteps=400, n_series=100) 16 | data["static"] = 2 17 | data["date"] = pd.Timestamp("2020-01-01") + pd.to_timedelta(data.time_idx, "D") 18 | validation = data.series.sample(20) 19 | 20 | 21 | max_encoder_length = 150 22 | max_prediction_length = 20 23 | 24 | training_cutoff = data["time_idx"].max() - max_prediction_length 25 | 26 | context_length = max_encoder_length 27 | prediction_length = max_prediction_length 28 | 29 | training = TimeSeriesDataSet( 30 | data[lambda x: x.time_idx < training_cutoff], 31 | time_idx="time_idx", 32 | target="value", 33 | categorical_encoders={"series": NaNLabelEncoder().fit(data.series)}, 34 | group_ids=["series"], 35 | min_encoder_length=context_length, 36 | max_encoder_length=context_length, 37 | max_prediction_length=prediction_length, 38 | min_prediction_length=prediction_length, 39 | time_varying_unknown_reals=["value"], 40 | randomize_length=None, 41 | add_relative_time_idx=False, 42 | add_target_scales=False, 43 | ) 44 | 45 | validation = TimeSeriesDataSet.from_dataset( 46 | training, data, min_prediction_idx=training_cutoff 47 | ) 48 | batch_size = 128 49 | train_dataloader = training.to_dataloader( 50 | train=True, batch_size=batch_size, num_workers=2 51 | ) 52 | val_dataloader = validation.to_dataloader( 53 | train=False, batch_size=batch_size, num_workers=2 54 | ) 55 | 56 | 57 | early_stop_callback = EarlyStopping( 58 | monitor="val_loss", min_delta=1e-4, patience=10, verbose=False, mode="min" 59 | ) 60 | trainer = pl.Trainer( 61 | max_epochs=100, 62 | accelerator="auto", 63 | gradient_clip_val=0.1, 64 | callbacks=[early_stop_callback], 65 | limit_train_batches=15, 66 | # limit_val_batches=1, 67 | # fast_dev_run=True, 68 | # logger=logger, 69 | # profiler=True, 70 | ) 71 | 72 | 73 | net = NBeats.from_dataset( 74 | training, 75 | learning_rate=3e-2, 76 | log_interval=10, 77 | log_val_interval=1, 78 | log_gradient_flow=False, 79 | weight_decay=1e-2, 80 | ) 81 | print(f"Number of parameters in network: {net.size() / 1e3:.1f}k") 82 | 83 | # # find optimal learning rate 84 | # # remove logging and artificial epoch size 85 | # net.hparams.log_interval = -1 86 | # net.hparams.log_val_interval = -1 87 | # trainer.limit_train_batches = 1.0 88 | # # run learning rate finder 89 | # res = Tuner(trainer).lr_find( 90 | # net, train_dataloaders=train_dataloader, val_dataloaders=val_dataloader, min_lr=1e-5, max_lr=1e2 # noqa: E501 91 | # ) 92 | # print(f"suggested learning rate: {res.suggestion()}") 93 | # fig = res.plot(show=True, suggest=True) 94 | # fig.show() 95 | # net.hparams.learning_rate = res.suggestion() 96 | 97 | trainer.fit( 98 | net, 99 | train_dataloaders=train_dataloader, 100 | val_dataloaders=val_dataloader, 101 | ) 102 | -------------------------------------------------------------------------------- /.github/workflows/pypi_release.yml: -------------------------------------------------------------------------------- 1 | name: PyPI Release 2 | 3 | on: 4 | release: 5 | types: [published] 6 | 7 | jobs: 8 | check_tag: 9 | name: Check tag 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - uses: actions/checkout@v6 14 | 15 | - uses: actions/setup-python@v6 16 | with: 17 | python-version: '3.11' 18 | 19 | - shell: bash 20 | run: | 21 | TAG="${{ github.event.release.tag_name }}" 22 | GH_TAG_NAME="${TAG#v}" 23 | PY_VERSION=$(python - <<'PY' 24 | import pathlib, tomllib 25 | data = tomllib.loads(pathlib.Path("pyproject.toml").read_text(encoding="utf-8")) 26 | print(data.get("project").get("version")) 27 | PY 28 | ) 29 | if [ "${GH_TAG_NAME}" != "${PY_VERSION}" ]; then 30 | echo "::error::Tag (${GH_TAG_NAME}) does not match pyproject.toml version (${PY_VERSION})." 31 | exit 2 32 | fi 33 | 34 | build_wheels: 35 | name: Build wheels 36 | runs-on: ubuntu-latest 37 | 38 | steps: 39 | - uses: actions/checkout@v6 40 | 41 | - uses: actions/setup-python@v6 42 | with: 43 | python-version: '3.11' 44 | 45 | - name: Build wheel 46 | run: | 47 | python -m pip install build 48 | python -m build --wheel --sdist --outdir wheelhouse 49 | 50 | - name: Store wheels 51 | uses: actions/upload-artifact@v6 52 | with: 53 | name: wheels 54 | path: wheelhouse/* 55 | 56 | pytest-nosoftdeps: 57 | name: no-softdeps 58 | runs-on: ${{ matrix.os }} 59 | needs: [build_wheels] 60 | strategy: 61 | fail-fast: false 62 | matrix: 63 | os: [ubuntu-latest, macos-latest, windows-latest] 64 | python-version: ["3.10", "3.11", "3.12", "3.13"] 65 | 66 | steps: 67 | - uses: actions/checkout@v6 68 | 69 | - name: Set up Python ${{ matrix.python-version }} 70 | uses: actions/setup-python@v6 71 | with: 72 | python-version: ${{ matrix.python-version }} 73 | 74 | - name: Setup macOS 75 | if: runner.os == 'macOS' 76 | run: | 77 | brew install libomp # https://github.com/pytorch/pytorch/issues/20030 78 | 79 | - name: Get full Python version 80 | id: full-python-version 81 | shell: bash 82 | run: echo version=$(python -c "import sys; print('-'.join(str(v) for v in sys.version_info))") >> $GITHUB_OUTPUT 83 | 84 | - name: Install dependencies 85 | shell: bash 86 | run: | 87 | pip install ".[dev,github-actions]" 88 | 89 | - name: Show dependencies 90 | run: python -m pip list 91 | 92 | - name: Run pytest 93 | shell: bash 94 | run: python -m pytest tests 95 | 96 | upload_wheels: 97 | name: Upload wheels to PyPI 98 | runs-on: ubuntu-latest 99 | needs: [pytest-nosoftdeps] 100 | 101 | permissions: 102 | id-token: write 103 | 104 | steps: 105 | - uses: actions/download-artifact@v7 106 | with: 107 | name: wheels 108 | path: wheelhouse 109 | 110 | - name: Publish package to PyPI 111 | uses: pypa/gh-action-pypi-publish@release/v1 112 | with: 113 | packages-dir: wheelhouse/ 114 | -------------------------------------------------------------------------------- /tests/test_models/test_nbeats.py: -------------------------------------------------------------------------------- 1 | import pickle 2 | import shutil 3 | 4 | import lightning.pytorch as pl 5 | from lightning.pytorch.callbacks import EarlyStopping 6 | from lightning.pytorch.loggers import TensorBoardLogger 7 | import pytest 8 | 9 | from pytorch_forecasting.models import NBeats 10 | from pytorch_forecasting.utils._dependencies import _get_installed_packages 11 | 12 | 13 | def test_integration(dataloaders_fixed_window_without_covariates, tmp_path): 14 | train_dataloader = dataloaders_fixed_window_without_covariates["train"] 15 | val_dataloader = dataloaders_fixed_window_without_covariates["val"] 16 | test_dataloader = dataloaders_fixed_window_without_covariates["test"] 17 | 18 | early_stop_callback = EarlyStopping( 19 | monitor="val_loss", min_delta=1e-4, patience=1, verbose=False, mode="min" 20 | ) 21 | 22 | logger = TensorBoardLogger(tmp_path) 23 | trainer = pl.Trainer( 24 | max_epochs=2, 25 | gradient_clip_val=0.1, 26 | callbacks=[early_stop_callback], 27 | enable_checkpointing=True, 28 | default_root_dir=tmp_path, 29 | limit_train_batches=2, 30 | limit_val_batches=2, 31 | limit_test_batches=2, 32 | logger=logger, 33 | ) 34 | 35 | net = NBeats.from_dataset( 36 | train_dataloader.dataset, 37 | learning_rate=0.15, 38 | log_gradient_flow=True, 39 | widths=[4, 4, 4], 40 | log_interval=1000, 41 | backcast_loss_ratio=1.0, 42 | ) 43 | net.size() 44 | try: 45 | trainer.fit( 46 | net, 47 | train_dataloaders=train_dataloader, 48 | val_dataloaders=val_dataloader, 49 | ) 50 | test_outputs = trainer.test(net, dataloaders=test_dataloader) 51 | assert len(test_outputs) > 0 52 | # check loading 53 | net = NBeats.load_from_checkpoint(trainer.checkpoint_callback.best_model_path) 54 | 55 | # check prediction 56 | net.predict( 57 | val_dataloader, 58 | fast_dev_run=True, 59 | return_index=True, 60 | return_decoder_lengths=True, 61 | ) 62 | finally: 63 | shutil.rmtree(tmp_path, ignore_errors=True) 64 | 65 | net.predict( 66 | val_dataloader, 67 | fast_dev_run=True, 68 | return_index=True, 69 | return_decoder_lengths=True, 70 | ) 71 | 72 | 73 | @pytest.fixture(scope="session") 74 | def model(dataloaders_fixed_window_without_covariates): 75 | dataset = dataloaders_fixed_window_without_covariates["train"].dataset 76 | net = NBeats.from_dataset( 77 | dataset, 78 | learning_rate=0.15, 79 | log_gradient_flow=True, 80 | widths=[4, 4, 4], 81 | log_interval=1000, 82 | backcast_loss_ratio=1.0, 83 | ) 84 | return net 85 | 86 | 87 | def test_pickle(model): 88 | pkl = pickle.dumps(model) 89 | pickle.loads(pkl) # noqa: S301 90 | 91 | 92 | @pytest.mark.skipif( 93 | "matplotlib" not in _get_installed_packages(), 94 | reason="skip test if required package matplotlib not installed", 95 | ) 96 | def test_interpretation(model, dataloaders_fixed_window_without_covariates): 97 | raw_predictions = model.predict( 98 | dataloaders_fixed_window_without_covariates["val"], 99 | mode="raw", 100 | return_x=True, 101 | fast_dev_run=True, 102 | ) 103 | model.plot_interpretation(raw_predictions.x, raw_predictions.output, idx=0) 104 | -------------------------------------------------------------------------------- /examples/nbeats_with_kan.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | import lightning.pytorch as pl 4 | from lightning.pytorch.callbacks import EarlyStopping 5 | import pandas as pd 6 | 7 | from pytorch_forecasting import NBeatsKAN, TimeSeriesDataSet 8 | from pytorch_forecasting.data import NaNLabelEncoder 9 | from pytorch_forecasting.data.examples import generate_ar_data 10 | from pytorch_forecasting.models.nbeats import GridUpdateCallback 11 | 12 | sys.path.append("..") 13 | 14 | 15 | print("load data") 16 | data = generate_ar_data(seasonality=10.0, timesteps=400, n_series=100) 17 | data["static"] = 2 18 | data["date"] = pd.Timestamp("2020-01-01") + pd.to_timedelta(data.time_idx, "D") 19 | validation = data.series.sample(20) 20 | 21 | 22 | max_encoder_length = 150 23 | max_prediction_length = 20 24 | 25 | training_cutoff = data["time_idx"].max() - max_prediction_length 26 | 27 | context_length = max_encoder_length 28 | prediction_length = max_prediction_length 29 | 30 | training = TimeSeriesDataSet( 31 | data[lambda x: x.time_idx < training_cutoff], 32 | time_idx="time_idx", 33 | target="value", 34 | categorical_encoders={"series": NaNLabelEncoder().fit(data.series)}, 35 | group_ids=["series"], 36 | min_encoder_length=context_length, 37 | max_encoder_length=context_length, 38 | max_prediction_length=prediction_length, 39 | min_prediction_length=prediction_length, 40 | time_varying_unknown_reals=["value"], 41 | randomize_length=None, 42 | add_relative_time_idx=False, 43 | add_target_scales=False, 44 | ) 45 | 46 | validation = TimeSeriesDataSet.from_dataset( 47 | training, data, min_prediction_idx=training_cutoff 48 | ) 49 | batch_size = 128 50 | train_dataloader = training.to_dataloader( 51 | train=True, batch_size=batch_size, num_workers=0 52 | ) 53 | val_dataloader = validation.to_dataloader( 54 | train=False, batch_size=batch_size, num_workers=0 55 | ) 56 | 57 | 58 | early_stop_callback = EarlyStopping( 59 | monitor="val_loss", min_delta=1e-4, patience=10, verbose=False, mode="min" 60 | ) 61 | # updates KAN layers' grid after every 3 steps during training 62 | grid_update_callback = GridUpdateCallback(update_interval=3) 63 | 64 | trainer = pl.Trainer( 65 | max_epochs=1, 66 | accelerator="auto", 67 | gradient_clip_val=0.1, 68 | callbacks=[early_stop_callback, grid_update_callback], 69 | limit_train_batches=15, 70 | # limit_val_batches=1, 71 | # fast_dev_run=True, 72 | # logger=logger, 73 | # profiler=True, 74 | ) 75 | 76 | 77 | net = NBeatsKAN.from_dataset( 78 | training, 79 | learning_rate=3e-2, 80 | log_interval=10, 81 | log_val_interval=1, 82 | log_gradient_flow=False, 83 | weight_decay=1e-2, 84 | ) 85 | print(f"Number of parameters in network: {net.size() / 1e3:.1f}k") 86 | 87 | # # find optimal learning rate 88 | # # remove logging and artificial epoch size 89 | # net.hparams.log_interval = -1 90 | # net.hparams.log_val_interval = -1 91 | # trainer.limit_train_batches = 1.0 92 | # # run learning rate finder 93 | # res = Tuner(trainer).lr_find( 94 | # net, train_dataloaders=train_dataloader, val_dataloaders=val_dataloader, min_lr=1e-5, max_lr=1e2 # noqa: E501 95 | # ) 96 | # print(f"suggested learning rate: {res.suggestion()}") 97 | # fig = res.plot(show=True, suggest=True) 98 | # fig.show() 99 | # net.hparams.learning_rate = res.suggestion() 100 | 101 | trainer.fit( 102 | net, 103 | train_dataloaders=train_dataloader, 104 | val_dataloaders=val_dataloader, 105 | ) 106 | -------------------------------------------------------------------------------- /tests/test_models/test_x_lstm.py: -------------------------------------------------------------------------------- 1 | import shutil 2 | 3 | import lightning.pytorch as pl 4 | from lightning.pytorch.callbacks import EarlyStopping 5 | from lightning.pytorch.loggers import TensorBoardLogger 6 | import pytest 7 | 8 | from pytorch_forecasting.metrics import SMAPE 9 | from pytorch_forecasting.models.xlstm._xlstm import xLSTMTime 10 | 11 | 12 | def _integration( 13 | dataloaders_fixed_window_without_covariates, tmp_path, xlstm_type="slstm", **kwargs 14 | ): 15 | train_dataloader = dataloaders_fixed_window_without_covariates["train"] 16 | val_dataloader = dataloaders_fixed_window_without_covariates["val"] 17 | test_dataloader = dataloaders_fixed_window_without_covariates["test"] 18 | 19 | early_stop_callback = EarlyStopping( 20 | monitor="val_loss", min_delta=1e-4, patience=1, verbose=False, mode="min" 21 | ) 22 | 23 | logger = TensorBoardLogger(tmp_path) 24 | trainer = pl.Trainer( 25 | max_epochs=3, 26 | gradient_clip_val=0.1, 27 | callbacks=[early_stop_callback], 28 | enable_checkpointing=True, 29 | default_root_dir=tmp_path, 30 | limit_train_batches=2, 31 | limit_val_batches=2, 32 | limit_test_batches=2, 33 | logger=logger, 34 | ) 35 | 36 | model_kwargs = { 37 | "input_size": 1, 38 | "output_size": 1, 39 | "hidden_size": 32, 40 | "xlstm_type": xlstm_type, 41 | "learning_rate": 0.01, 42 | "loss": SMAPE(), 43 | } 44 | 45 | model_kwargs.update(kwargs) 46 | 47 | net = xLSTMTime.from_dataset(train_dataloader.dataset, **model_kwargs) 48 | 49 | try: 50 | trainer.fit( 51 | net, 52 | train_dataloaders=train_dataloader, 53 | val_dataloaders=val_dataloader, 54 | ) 55 | 56 | test_outputs = trainer.test(net, dataloaders=test_dataloader) 57 | assert len(test_outputs) > 0 58 | 59 | net = xLSTMTime.load_from_checkpoint( 60 | trainer.checkpoint_callback.best_model_path 61 | ) 62 | 63 | net.predict( 64 | val_dataloader, 65 | fast_dev_run=True, 66 | return_index=True, 67 | return_decoder_lengths=True, 68 | ) 69 | finally: 70 | shutil.rmtree(tmp_path, ignore_errors=True) 71 | 72 | net.predict( 73 | val_dataloader, 74 | fast_dev_run=True, 75 | return_index=True, 76 | return_decoder_lengths=True, 77 | ) 78 | 79 | 80 | @pytest.mark.parametrize( 81 | "kwargs", 82 | [ 83 | {}, 84 | {"xlstm_type": "mlstm"}, 85 | {"num_layers": 2}, 86 | {"xlstm_type": "slstm", "input_projection_size": 32}, 87 | { 88 | "xlstm_type": "mlstm", 89 | "decomposition_kernel": 13, 90 | "dropout": 0.2, 91 | }, 92 | ], 93 | ) 94 | def test_integration(dataloaders_fixed_window_without_covariates, tmp_path, kwargs): 95 | _integration(dataloaders_fixed_window_without_covariates, tmp_path, **kwargs) 96 | 97 | 98 | @pytest.fixture(scope="session") 99 | def model(dataloaders_fixed_window_without_covariates): 100 | dataset = dataloaders_fixed_window_without_covariates["train"].dataset 101 | net = xLSTMTime.from_dataset( 102 | dataset, 103 | input_size=1, 104 | hidden_size=32, 105 | output_size=1, 106 | xlstm_type="slstm", 107 | learning_rate=0.01, 108 | loss=SMAPE(), 109 | ) 110 | return net 111 | -------------------------------------------------------------------------------- /pytorch_forecasting/layers/_recurrent/_mlstm/network.py: -------------------------------------------------------------------------------- 1 | import torch 2 | import torch.nn as nn 3 | 4 | from pytorch_forecasting.layers._recurrent._mlstm.layer import mLSTMLayer 5 | 6 | 7 | class mLSTMNetwork(nn.Module): 8 | """Implements the mLSTM Network, a complete model based on stacked mLSTM layers. 9 | 10 | This network combines stacked mLSTM layers and a fully connected output layer. 11 | 12 | Parameters 13 | ---------- 14 | input_size : int 15 | Number of features in the input. 16 | hidden_size : int 17 | Number of features in the hidden state of each mLSTM layer. 18 | num_layers : int 19 | Number of mLSTM layers to stack. 20 | output_size : int 21 | Number of features in the output. 22 | dropout : float, optional 23 | Dropout probability for the mLSTM layers, by default 0.0. 24 | use_layer_norm : bool, optional 25 | Whether to use layer normalization in the mLSTM layers, by default True. 26 | use_residual : bool, optional 27 | Whether to use residual connections in the mLSTM layers, by default True. 28 | 29 | Attributes 30 | ---------- 31 | mlstm_layer : mLSTMLayer 32 | Stacked mLSTM layers used for processing input sequences. 33 | fc : nn.Linear 34 | Fully connected layer to generate final output. 35 | 36 | 37 | """ 38 | 39 | def __init__( 40 | self, 41 | input_size, 42 | hidden_size, 43 | num_layers, 44 | output_size, 45 | dropout=0.0, 46 | use_layer_norm=True, 47 | use_residual=True, 48 | ): 49 | super().__init__() 50 | 51 | self.mlstm_layer = mLSTMLayer( 52 | input_size, 53 | hidden_size, 54 | num_layers, 55 | dropout, 56 | use_layer_norm, 57 | use_residual, 58 | ) 59 | self.fc = nn.Linear(hidden_size, output_size) 60 | 61 | def forward(self, x, h=None, c=None, n=None): 62 | """Forward pass through the mLSTM Network. 63 | 64 | Parameters 65 | ---------- 66 | x : torch.Tensor 67 | The number of features in the input. 68 | h : torch.Tensor, optional 69 | Initial hidden states for all layers. 70 | If None, initialized to zeros, by default None. 71 | c : torch.Tensor, optional 72 | Initial cell states for all layers. 73 | If None, initialized to zeros, by default None. 74 | n : torch.Tensor, optional 75 | Initial normalized states for all layers. 76 | If None, initialized to zeros, by default None. 77 | 78 | Returns 79 | ------- 80 | tuple 81 | output : torch.Tensor 82 | Final output tensor from the fully connected layer. 83 | (h, c, n) : tuple of torch.Tensor 84 | Final hidden, cell, and normalized states for all layers: 85 | - h : torch.Tensor 86 | - c : torch.Tensor 87 | - n : torch.Tensor 88 | """ 89 | output, (h, c, n) = self.mlstm_layer(x, h, c, n) 90 | 91 | output = self.fc(output[-1]) 92 | 93 | return output, (h, c, n) 94 | 95 | def init_hidden(self, batch_size, device=None): 96 | """Initialize hidden, cell, and normalization states.""" 97 | if device is None: 98 | device = next(self.parameters()).device 99 | return self.mlstm_layer.init_hidden(batch_size, device=device) 100 | -------------------------------------------------------------------------------- /pytorch_forecasting/tests/_loss_mapping.py: -------------------------------------------------------------------------------- 1 | from pytorch_forecasting.data.encoders import GroupNormalizer 2 | from pytorch_forecasting.metrics import ( 3 | MAE, 4 | MAPE, 5 | MASE, 6 | RMSE, 7 | SMAPE, 8 | BetaDistributionLoss, 9 | CrossEntropy, 10 | ImplicitQuantileNetworkDistributionLoss, 11 | LogNormalDistributionLoss, 12 | MQF2DistributionLoss, 13 | MultivariateNormalDistributionLoss, 14 | NegativeBinomialDistributionLoss, 15 | NormalDistributionLoss, 16 | PoissonLoss, 17 | QuantileLoss, 18 | TweedieLoss, 19 | ) 20 | 21 | POINT_LOSSES_NUMERIC = [ 22 | MAE(), 23 | RMSE(), 24 | SMAPE(), 25 | MAPE(), 26 | PoissonLoss(), 27 | MASE(), 28 | TweedieLoss(), 29 | ] 30 | 31 | POINT_LOSSES_CATEGORY = [ 32 | CrossEntropy(), 33 | ] 34 | 35 | QUANTILE_LOSSES_NUMERIC = [ 36 | QuantileLoss(), 37 | ] 38 | 39 | DISTR_LOSSES_NUMERIC = [ 40 | NormalDistributionLoss(), 41 | NegativeBinomialDistributionLoss(), 42 | MultivariateNormalDistributionLoss(), 43 | LogNormalDistributionLoss(), 44 | BetaDistributionLoss(), 45 | ImplicitQuantileNetworkDistributionLoss(), 46 | # todo: still need some debugging to add the MQF2DistributionLoss 47 | ] 48 | 49 | LOSSES_BY_PRED_AND_Y_TYPE = { 50 | ("point", "numeric"): POINT_LOSSES_NUMERIC, 51 | ("point", "category"): POINT_LOSSES_CATEGORY, 52 | ("quantile", "numeric"): QUANTILE_LOSSES_NUMERIC, 53 | ("quantile", "category"): [], 54 | ("distr", "numeric"): DISTR_LOSSES_NUMERIC, 55 | ("distr", "category"): [], 56 | } 57 | 58 | 59 | LOSS_SPECIFIC_PARAMS = { 60 | "BetaDistributionLoss": { 61 | "clip_target": True, 62 | "data_loader_kwargs": { 63 | "target_normalizer": GroupNormalizer( 64 | groups=["agency", "sku"], transformation="logit" 65 | ) 66 | }, 67 | }, 68 | "LogNormalDistributionLoss": { 69 | "clip_target": True, 70 | "data_loader_kwargs": { 71 | "target_normalizer": GroupNormalizer( 72 | groups=["agency", "sku"], transformation="log1p" 73 | ) 74 | }, 75 | }, 76 | "NegativeBinomialDistributionLoss": { 77 | "clip_target": False, 78 | "data_loader_kwargs": { 79 | "target_normalizer": GroupNormalizer(groups=["agency", "sku"], center=False) 80 | }, 81 | }, 82 | "MultivariateNormalDistributionLoss": { 83 | "data_loader_kwargs": { 84 | "target_normalizer": GroupNormalizer( 85 | groups=["agency", "sku"], transformation="log1p" 86 | ) 87 | }, 88 | }, 89 | "MQF2DistributionLoss": { 90 | "clip_target": True, 91 | "data_loader_kwargs": { 92 | "target_normalizer": GroupNormalizer( 93 | groups=["agency", "sku"], center=False, transformation="log1p" 94 | ) 95 | }, 96 | "trainer_kwargs": dict(accelerator="cpu"), 97 | }, 98 | } 99 | 100 | 101 | def get_compatible_losses(pred_types, y_types): 102 | """Get compatible losses based on prediction types and target types. 103 | 104 | Parameters 105 | ---------- 106 | pred_types : list of str 107 | Prediction types, e.g., ["point", "distr"] 108 | y_types : list of str 109 | Target types, e.g., ["numeric", "category"] 110 | 111 | Returns 112 | ------- 113 | list 114 | List of compatible loss instances 115 | """ 116 | compatible_losses = [] 117 | 118 | for pred_type in pred_types: 119 | for y_type in y_types: 120 | key = (pred_type, y_type) 121 | if key in LOSSES_BY_PRED_AND_Y_TYPE: 122 | compatible_losses.extend(LOSSES_BY_PRED_AND_Y_TYPE[key]) 123 | 124 | return compatible_losses 125 | -------------------------------------------------------------------------------- /docs/source/faq.rst: -------------------------------------------------------------------------------- 1 | FAQ 2 | ==== 3 | 4 | .. currentmodule:: pytorch_forecasting 5 | 6 | Common issues and answers. Other places to seek help from: 7 | 8 | * :ref:`Tutorials ` 9 | * `PyTorch Lightning documentation `_ and issues 10 | * `PyTorch documentation `_ and issues 11 | * `Stack Overflow `_ 12 | 13 | 14 | Creating datasets 15 | ----------------- 16 | 17 | * **How do I create a dataset for new samples?** 18 | 19 | Use the :py:class:`~data.timeseries.TimeSeriesDataSet` method of your training dataset to 20 | create datasets on which you can run inference. 21 | 22 | * **How long should the encoder and decoder/prediction length be?** 23 | 24 | .. _faq_encoder_decoder_length: 25 | 26 | Choose something reasonably long, but not much longer than 500 for the encoder length and 27 | 200 for the decoder length. Consider that longer lengths increase the time it takes 28 | for your model to train. 29 | 30 | The ratio of decoder and encoder length depends on the used algorithm. 31 | Look at :ref:`documentation ` to get clues. 32 | 33 | * **It takes very long to create the dataset. Why is that?** 34 | 35 | If you set ``allow_missing_timesteps=True`` in your dataset, the creation of an index 36 | might take far more time as all missing values in the timeseries have to be identified. 37 | The algorithm might be possible to speed up but currently, it might be faster for you to 38 | not allow missing values and fill them yourself. 39 | 40 | 41 | * **How are missing values treated?** 42 | 43 | #. Missing values between time points are either filled up with a fill 44 | forward or a constant fill-in strategy 45 | #. Missing values indicated by NaNs are a problem and 46 | should be filled in up-front, e.g. with the median value and another missing indicator categorical variable. 47 | #. Missing values in the future (out of range) are not filled in and 48 | simply not predicted. You have to provide values into the future. 49 | If those values are amongst the unknown future values, they will simply be ignored. 50 | 51 | 52 | Training models 53 | --------------- 54 | 55 | * **My training seems to freeze - nothing seem to be happening although my CPU/GPU is working at 100%. 56 | How to fix this issue?** 57 | 58 | Probably, your model is too big (check the number of parameters with ``model.size()`` or 59 | the dataset encoder and decoder length are unrealistically large. See 60 | :ref:`How long should the encoder and decoder/prediction length be? ` 61 | 62 | * **Why does the learning rate finder not finish?** 63 | 64 | First, ensure that the trainer does not have the keyword ``fast_dev_run=True`` and 65 | ``limit_train_batches=...`` set. Second, use a target normalizer in your training dataset. 66 | Third, increase the ``early_stop_threshold`` argument 67 | of the ``lr_find`` method to a large number. 68 | 69 | * **Why do I get lots of matplotlib warnings when running the learning rate finder?** 70 | 71 | This is because you keep on creating plots for logging but without a logger. 72 | Set ``log_interval=-1`` in your model to avoid this behaviour. 73 | 74 | * **How do I choose hyperparameters?** 75 | 76 | Consult the :ref:`model documentation ` to understand which parameters 77 | are important and which ranges are reasonable. Choose the learning rate with 78 | the learning rate finder. To tune hyperparameters, the `optuna package `_ 79 | is a great place to start with. 80 | 81 | 82 | Interpreting models 83 | ------------------- 84 | 85 | * **What interpretation is built into PyTorch Forecasting?** 86 | 87 | Look up the :ref:`model documentation ` for the model you use for model-specific interpretation. 88 | Further, all models come with some basic methods inherited from :py:class:`~models.base_model.BaseModel`. 89 | -------------------------------------------------------------------------------- /pytorch_forecasting/tests/test_doctest.py: -------------------------------------------------------------------------------- 1 | # copyright: pytorch-forecasting developers, BSD-3-Clause License (see LICENSE file) 2 | # copy of the sktime utility of the same name (BSD-3) 3 | """Doctest checks directed through pytest with conditional skipping.""" 4 | 5 | from functools import lru_cache 6 | import importlib 7 | import inspect 8 | import pkgutil 9 | 10 | EXCLUDE_MODULES_STARTING_WITH = ("all", "test") 11 | 12 | 13 | def _all_functions(module_name): 14 | """Get all functions from a module, including submodules. 15 | 16 | Excludes: 17 | 18 | * modules starting with 'all' or 'test'. 19 | * if the flag ``ONLY_CHANGED_MODULES`` is set, modules that have not changed, 20 | compared to the ``main`` branch. 21 | 22 | Parameters 23 | ---------- 24 | module_name : str 25 | Name of the module. 26 | 27 | Returns 28 | ------- 29 | functions_list : list 30 | List of tuples (function_name, function_object). 31 | """ 32 | res = _all_functions_cached(module_name) 33 | # copy the result to avoid modifying the cached result 34 | return res.copy() 35 | 36 | 37 | @lru_cache 38 | def _all_functions_cached(module_name, only_changed_modules=False): 39 | """Get all functions from a module, including submodules. 40 | 41 | Excludes: 42 | 43 | * modules starting with 'all' or 'test'. 44 | * if ``only_changed_modules`` is ``True``, modules that have not changed, 45 | compared to the ``main`` branch. 46 | 47 | Parameters 48 | ---------- 49 | module_name : str 50 | Name of the module. 51 | only_changed_modules : bool, optional (default=False) 52 | If True, only functions from modules that have changed are returned. 53 | 54 | Returns 55 | ------- 56 | functions_list : list 57 | List of tuples (function_name, function_object). 58 | """ 59 | # Import the package 60 | package = importlib.import_module(module_name) 61 | 62 | # Initialize an empty list to hold all functions 63 | functions_list = [] 64 | 65 | # Walk through the package's modules 66 | package_path = package.__path__[0] 67 | for _, modname, _ in pkgutil.walk_packages( 68 | path=[package_path], prefix=package.__name__ + "." 69 | ): 70 | # Skip modules starting with 'all' or 'test' 71 | if modname.split(".")[-1].startswith(EXCLUDE_MODULES_STARTING_WITH): 72 | continue 73 | 74 | # Import the module 75 | module = importlib.import_module(modname) 76 | 77 | # Get all functions from the module 78 | for name, obj in inspect.getmembers(module, inspect.isfunction): 79 | # if function is imported from another module, skip it 80 | if obj.__module__ != module.__name__: 81 | continue 82 | # add the function to the list 83 | functions_list.append((name, obj)) 84 | 85 | return functions_list 86 | 87 | 88 | def pytest_generate_tests(metafunc): 89 | """Test parameterization routine for pytest. 90 | 91 | Fixtures parameterized 92 | ---------------------- 93 | func : all functions from sktime, as returned by _all_functions 94 | if ONLY_CHANGED_MODULES is set, only functions from modules that have changed 95 | """ 96 | # we assume all four arguments are present in the test below 97 | funcs_and_names = _all_functions("pytorch_forecasting") 98 | 99 | if len(funcs_and_names) > 0: 100 | names, funcs = zip(*funcs_and_names) 101 | 102 | metafunc.parametrize("func", funcs, ids=names) 103 | else: 104 | metafunc.parametrize("func", []) 105 | 106 | 107 | def test_all_functions_doctest(func): 108 | """Run doctest for all functions in pytorch-forecasting.""" 109 | from skbase.utils.doctest_run import run_doctest 110 | 111 | run_doctest(func, name=f"function {func.__name__}") 112 | -------------------------------------------------------------------------------- /pytorch_forecasting/layers/_embeddings/_sub_nn.py: -------------------------------------------------------------------------------- 1 | from typing import Union 2 | 3 | import torch 4 | import torch.nn as nn 5 | 6 | 7 | class embedding_cat_variables(nn.Module): 8 | # at the moment cat_past and cat_fut together 9 | def __init__(self, seq_len: int, lag: int, d_model: int, emb_dims: list, device): 10 | """Class for embedding categorical variables, adding 3 positional variables during forward 11 | 12 | Parameters 13 | ---------- 14 | seq_len: int 15 | length of the sequence (sum of past and future steps) 16 | lag: (int): 17 | number of future step to be predicted 18 | hidden_size: int 19 | dimension of all variables after they are embedded 20 | emb_dims: list 21 | size of the dictionary for embedding. One dimension for each categorical variable 22 | device : torch.device 23 | """ # noqa: E501 24 | super().__init__() 25 | self.seq_len = seq_len 26 | self.lag = lag 27 | self.device = device 28 | self.cat_embeds = emb_dims + [seq_len, lag + 1, 2] # 29 | self.cat_n_embd = nn.ModuleList( 30 | [nn.Embedding(emb_dim, d_model) for emb_dim in self.cat_embeds] 31 | ) 32 | 33 | def forward( 34 | self, x: Union[torch.Tensor, int], device: torch.device 35 | ) -> torch.Tensor: 36 | """All components of x are concatenated with 3 new variables for data augmentation, in the order: 37 | 38 | - pos_seq: assign at each step its time-position 39 | - pos_fut: assign at each step its future position. 0 if it is a past step 40 | - is_fut: explicit for each step if it is a future(1) or past one(0) 41 | 42 | Parameters 43 | ---------- 44 | x: torch.Tensor 45 | `[bs, seq_len, num_vars]` 46 | 47 | Returns 48 | ------ 49 | torch.Tensor: 50 | `[bs, seq_len, num_vars+3, n_embd]` 51 | """ # noqa: E501 52 | if isinstance(x, int): 53 | no_emb = True 54 | B = x 55 | else: 56 | no_emb = False 57 | B, _, _ = x.shape 58 | 59 | pos_seq = self.get_pos_seq(bs=B).to(device) 60 | pos_fut = self.get_pos_fut(bs=B).to(device) 61 | is_fut = self.get_is_fut(bs=B).to(device) 62 | 63 | if no_emb: 64 | cat_vars = torch.cat((pos_seq, pos_fut, is_fut), dim=2) 65 | else: 66 | cat_vars = torch.cat((x, pos_seq, pos_fut, is_fut), dim=2) 67 | cat_vars = cat_vars.long() 68 | cat_n_embd = self.get_cat_n_embd(cat_vars) 69 | return cat_n_embd 70 | 71 | def get_pos_seq(self, bs): 72 | pos_seq = torch.arange(0, self.seq_len) 73 | pos_seq = pos_seq.repeat(bs, 1).unsqueeze(2).to(self.device) 74 | return pos_seq 75 | 76 | def get_pos_fut(self, bs): 77 | pos_fut = torch.cat( 78 | ( 79 | torch.zeros((self.seq_len - self.lag), dtype=torch.long), 80 | torch.arange(1, self.lag + 1), 81 | ) 82 | ) 83 | pos_fut = pos_fut.repeat(bs, 1).unsqueeze(2).to(self.device) 84 | return pos_fut 85 | 86 | def get_is_fut(self, bs): 87 | is_fut = torch.cat( 88 | ( 89 | torch.zeros((self.seq_len - self.lag), dtype=torch.long), 90 | torch.ones((self.lag), dtype=torch.long), 91 | ) 92 | ) 93 | is_fut = is_fut.repeat(bs, 1).unsqueeze(2).to(self.device) 94 | return is_fut 95 | 96 | def get_cat_n_embd(self, cat_vars): 97 | cat_n_embd = torch.Tensor().to(cat_vars.device) 98 | for index, layer in enumerate(self.cat_n_embd): 99 | emb = layer(cat_vars[:, :, index]) 100 | cat_n_embd = torch.cat((cat_n_embd, emb.unsqueeze(2)), dim=2) 101 | return cat_n_embd 102 | -------------------------------------------------------------------------------- /pytorch_forecasting/models/rnn/_rnn_pkg.py: -------------------------------------------------------------------------------- 1 | """RecurrentNetwork package container.""" 2 | 3 | from pytorch_forecasting.models.base import _BasePtForecaster 4 | 5 | 6 | class RecurrentNetwork_pkg(_BasePtForecaster): 7 | """RecurrentNetwork package container.""" 8 | 9 | _tags = { 10 | "info:name": "RecurrentNetwork", 11 | "info:compute": 2, 12 | "info:pred_type": ["point"], 13 | "info:y_type": ["numeric"], 14 | "authors": ["jdb78"], 15 | "capability:exogenous": True, 16 | "capability:multivariate": True, 17 | "capability:pred_int": True, 18 | "capability:flexible_history_length": True, 19 | "capability:cold_start": True, 20 | "tests:skip_by_name": [ 21 | "test_integration[RecurrentNetwork-base_params-2-PoissonLoss]" 22 | ], 23 | } 24 | 25 | @classmethod 26 | def get_cls(cls): 27 | """Get model class.""" 28 | from pytorch_forecasting.models import RecurrentNetwork 29 | 30 | return RecurrentNetwork 31 | 32 | @classmethod 33 | def get_base_test_params(cls): 34 | """Return testing parameter settings for the trainer. 35 | 36 | Returns 37 | ------- 38 | params : dict or list of dict, default = {} 39 | Parameters to create testing instances of the class 40 | Each dict are parameters to construct an "interesting" test instance, i.e., 41 | `MyClass(**params)` or `MyClass(**params[i])` creates a valid test instance. 42 | `create_test_instance` uses the first (or only) dictionary in `params` 43 | """ 44 | return [ 45 | {}, 46 | {"cell_type": "GRU"}, 47 | dict( 48 | data_loader_kwargs=dict( 49 | lags={"volume": [2, 5]}, 50 | target="volume", 51 | time_varying_unknown_reals=["volume"], 52 | min_encoder_length=2, 53 | ) 54 | ), 55 | ] 56 | 57 | @classmethod 58 | def _get_test_dataloaders_from(cls, params): 59 | """Get dataloaders from parameters. 60 | 61 | Parameters 62 | ---------- 63 | params : dict 64 | Parameters to create dataloaders. 65 | One of the elements in the list returned by ``get_test_train_params``. 66 | 67 | Returns 68 | ------- 69 | dataloaders : dict with keys "train", "val", "test", values torch DataLoader 70 | Dict of dataloaders created from the parameters. 71 | Train, validation, and test dataloaders. 72 | """ 73 | loss = params.get("loss", None) 74 | clip_target = params.get("clip_target", False) 75 | data_loader_kwargs = params.get("data_loader_kwargs", {}) 76 | 77 | from pytorch_forecasting.metrics import ( 78 | PoissonLoss, 79 | TweedieLoss, 80 | ) 81 | from pytorch_forecasting.tests._conftest import make_dataloaders 82 | from pytorch_forecasting.tests._data_scenarios import data_with_covariates 83 | 84 | dwc = data_with_covariates() 85 | 86 | if isinstance(loss, (TweedieLoss, PoissonLoss)): 87 | clip_target = True 88 | 89 | dwc = dwc.copy() 90 | if clip_target: 91 | dwc["target"] = dwc["volume"].clip(1e-3, 1.0) 92 | else: 93 | dwc["target"] = dwc["volume"] 94 | data_loader_default_kwargs = dict( 95 | target="target", 96 | time_varying_known_reals=["price_actual"], 97 | time_varying_unknown_reals=["target"], 98 | static_categoricals=["agency"], 99 | add_relative_time_idx=True, 100 | ) 101 | data_loader_default_kwargs.update(data_loader_kwargs) 102 | dataloaders_w_covariates = make_dataloaders(dwc, **data_loader_default_kwargs) 103 | return dataloaders_w_covariates 104 | -------------------------------------------------------------------------------- /docs/source/_static/favicon.svg: -------------------------------------------------------------------------------- 1 | 2 | 20 | 22 | 50 | 52 | 53 | 55 | image/svg+xml 56 | 58 | 59 | 60 | 61 | 62 | 67 | 70 | 76 | 82 | 88 | 94 | 99 | 104 | 105 | 106 | 107 | -------------------------------------------------------------------------------- /pytorch_forecasting/models/timexer/_timexer_pkg_v2.py: -------------------------------------------------------------------------------- 1 | """ 2 | Metadata container for TimeXer v2. 3 | """ 4 | 5 | from pytorch_forecasting.base._base_pkg import Base_pkg 6 | 7 | 8 | class TimeXer_pkg_v2(Base_pkg): 9 | """TimeXer metadata container.""" 10 | 11 | _tags = { 12 | "info:name": "TimeXer", 13 | "authors": ["PranavBhatP"], 14 | "capability:exogenous": True, 15 | "capability:multivariate": True, 16 | "capability:pred_int": True, 17 | "capability:flexible_history_length": False, 18 | } 19 | 20 | @classmethod 21 | def get_cls(cls): 22 | """Get model class.""" 23 | from pytorch_forecasting.models.timexer._timexer_v2 import TimeXer 24 | 25 | return TimeXer 26 | 27 | @classmethod 28 | def get_datamodule_cls(cls): 29 | """Get the underlying DataModule class.""" 30 | from pytorch_forecasting.data._tslib_data_module import TslibDataModule 31 | 32 | return TslibDataModule 33 | 34 | @classmethod 35 | def get_test_train_params(cls): 36 | """Return testing parameter settings for the trainer. 37 | 38 | Returns 39 | ------- 40 | params : dict or list of dict, default = {} 41 | Parameters to create testing instances of the class 42 | Each dict are parameters to construct an "interesting" test instance, i.e., 43 | `MyClass(**params)` or `MyClass(**params[i])` creates a valid test instance. 44 | `create_test_instance` uses the first (or only) dictionary in `params` 45 | """ 46 | from pytorch_forecasting.metrics import QuantileLoss 47 | 48 | params = [ 49 | {}, 50 | dict( 51 | hidden_size=64, 52 | n_heads=4, 53 | ), 54 | dict(datamodule_cfg=dict(context_length=12, prediction_length=3)), 55 | dict( 56 | hidden_size=32, 57 | n_heads=2, 58 | datamodule_cfg=dict( 59 | context_length=12, 60 | prediction_length=3, 61 | add_relative_time_idx=False, 62 | ), 63 | ), 64 | dict( 65 | hidden_size=128, 66 | patch_length=12, 67 | datamodule_cfg=dict(context_length=16, prediction_length=4), 68 | ), 69 | dict( 70 | n_heads=2, 71 | e_layers=1, 72 | patch_length=6, 73 | ), 74 | dict( 75 | hidden_size=256, 76 | n_heads=8, 77 | e_layers=3, 78 | d_ff=1024, 79 | patch_length=8, 80 | factor=3, 81 | activation="gelu", 82 | dropout=0.2, 83 | ), 84 | dict( 85 | hidden_size=32, 86 | n_heads=2, 87 | e_layers=1, 88 | d_ff=64, 89 | patch_length=4, 90 | factor=2, 91 | activation="relu", 92 | dropout=0.05, 93 | datamodule_cfg=dict( 94 | context_length=16, 95 | prediction_length=4, 96 | ), 97 | loss=QuantileLoss(quantiles=[0.1, 0.5, 0.9]), 98 | ), 99 | dict( 100 | hidden_size=32, 101 | patch_length=1, 102 | n_heads=4, 103 | e_layers=1, 104 | d_ff=32, 105 | dropout=0.1, 106 | use_efficient_attention=True, 107 | ), 108 | ] 109 | default_dm_cfg = {"context_length": 12, "prediction_length": 4} 110 | 111 | for param in params: 112 | current_dm_cfg = param.get("datamodule_cfg", {}) 113 | default_dm_cfg.update(current_dm_cfg) 114 | 115 | param["datamodule_cfg"] = default_dm_cfg 116 | 117 | return params 118 | -------------------------------------------------------------------------------- /pytorch_forecasting/models/mlp/_decodermlp_pkg.py: -------------------------------------------------------------------------------- 1 | """DecoderMLP package container.""" 2 | 3 | from pytorch_forecasting.models.base._base_object import _BasePtForecaster 4 | 5 | 6 | class DecoderMLP_pkg(_BasePtForecaster): 7 | """DecoderMLP package container.""" 8 | 9 | _tags = { 10 | "info:name": "DecoderMLP", 11 | "info:compute": 1, 12 | "info:pred_type": ["distr", "point", "quantile"], 13 | "info:y_type": ["category", "numeric"], 14 | "authors": ["jdb78"], 15 | "capability:exogenous": True, 16 | "capability:multivariate": True, 17 | "capability:pred_int": True, 18 | "capability:flexible_history_length": True, 19 | "capability:cold_start": True, 20 | "python_dependencies": ["cpflows"], 21 | "tests:skip_by_name": [ 22 | "test_integration[DecoderMLP-base_params-1-LogNormalDistributionLoss]" 23 | ], 24 | } 25 | 26 | @classmethod 27 | def get_cls(cls): 28 | """Get model class.""" 29 | from pytorch_forecasting.models import DecoderMLP 30 | 31 | return DecoderMLP 32 | 33 | @classmethod 34 | def get_base_test_params(cls): 35 | """Return testing parameter settings for the trainer. 36 | 37 | Returns 38 | ------- 39 | params : dict or list of dict, default = {} 40 | Parameters to create testing instances of the class 41 | Each dict are parameters to construct an "interesting" test instance, i.e., 42 | `MyClass(**params)` or `MyClass(**params[i])` creates a valid test instance. 43 | `create_test_instance` uses the first (or only) dictionary in `params` 44 | """ 45 | 46 | return [ 47 | {}, 48 | dict( 49 | data_loader_kwargs=dict(min_prediction_length=2, min_encoder_length=2), 50 | ), 51 | ] 52 | 53 | @classmethod 54 | def _get_test_dataloaders_from(cls, params): 55 | """Get dataloaders from parameters. 56 | 57 | Parameters 58 | ---------- 59 | params : dict 60 | Parameters to create dataloaders. 61 | One of the elements in the list returned by ``get_test_train_params``. 62 | 63 | Returns 64 | ------- 65 | dataloaders : dict with keys "train", "val", "test", values torch DataLoader 66 | Dict of dataloaders created from the parameters. 67 | Train, validation, and test dataloaders, in this order. 68 | """ 69 | data_loader_kwargs = params.get("data_loader_kwargs", {}) 70 | loss = params.get("loss", None) 71 | import inspect 72 | 73 | from pytorch_forecasting.metrics import ( 74 | CrossEntropy, 75 | MQF2DistributionLoss, 76 | NegativeBinomialDistributionLoss, 77 | ) 78 | from pytorch_forecasting.tests._data_scenarios import ( 79 | data_with_covariates, 80 | make_dataloaders, 81 | ) 82 | 83 | dwc = data_with_covariates() 84 | dwc.assign(target=lambda x: x.volume) 85 | if isinstance(loss, NegativeBinomialDistributionLoss): 86 | dwc = dwc.assign(target=lambda x: x.volume.round()) 87 | # todo: still need some debugging to add the MQF2DistributionLoss 88 | # elif inspect.isclass(loss) and issubclass(loss, MQF2DistributionLoss): 89 | # dwc = dwc.assign(volume=lambda x: x.volume.round()) 90 | # data_loader_kwargs["target"] = "volume" 91 | # data_loader_kwargs["time_varying_unknown_reals"] = ["volume"] 92 | elif isinstance(loss, CrossEntropy): 93 | data_loader_kwargs["target"] = "agency" 94 | dl_default_kwargs = dict( 95 | target="target", 96 | time_varying_known_reals=["price_actual"], 97 | time_varying_unknown_reals=["target"], 98 | static_categoricals=["agency"], 99 | add_relative_time_idx=True, 100 | ) 101 | dl_default_kwargs.update(data_loader_kwargs) 102 | dataloaders_with_covariates = make_dataloaders(dwc, **dl_default_kwargs) 103 | return dataloaders_with_covariates 104 | -------------------------------------------------------------------------------- /examples/ar.py: -------------------------------------------------------------------------------- 1 | import warnings 2 | 3 | import lightning.pytorch as pl 4 | from lightning.pytorch.callbacks import EarlyStopping, LearningRateMonitor 5 | import pandas as pd 6 | from pandas.core.common import SettingWithCopyWarning 7 | import torch 8 | 9 | from pytorch_forecasting import GroupNormalizer, TimeSeriesDataSet 10 | from pytorch_forecasting.data import NaNLabelEncoder 11 | from pytorch_forecasting.data.examples import generate_ar_data 12 | from pytorch_forecasting.metrics import NormalDistributionLoss 13 | from pytorch_forecasting.models.deepar import DeepAR 14 | 15 | warnings.simplefilter("error", category=SettingWithCopyWarning) 16 | 17 | 18 | data = generate_ar_data(seasonality=10.0, timesteps=400, n_series=100) 19 | data["static"] = "2" 20 | data["date"] = pd.Timestamp("2020-01-01") + pd.to_timedelta(data.time_idx, "D") 21 | validation = data.series.sample(20) 22 | 23 | max_encoder_length = 60 24 | max_prediction_length = 20 25 | 26 | training_cutoff = data["time_idx"].max() - max_prediction_length 27 | 28 | training = TimeSeriesDataSet( 29 | data[lambda x: ~x.series.isin(validation)], 30 | time_idx="time_idx", 31 | target="value", 32 | categorical_encoders={"series": NaNLabelEncoder().fit(data.series)}, 33 | group_ids=["series"], 34 | static_categoricals=["static"], 35 | min_encoder_length=max_encoder_length, 36 | max_encoder_length=max_encoder_length, 37 | min_prediction_length=max_prediction_length, 38 | max_prediction_length=max_prediction_length, 39 | time_varying_unknown_reals=["value"], 40 | time_varying_known_reals=["time_idx"], 41 | target_normalizer=GroupNormalizer(groups=["series"]), 42 | add_relative_time_idx=False, 43 | add_target_scales=True, 44 | randomize_length=None, 45 | ) 46 | 47 | validation = TimeSeriesDataSet.from_dataset( 48 | training, 49 | data[lambda x: x.series.isin(validation)], 50 | # predict=True, 51 | stop_randomization=True, 52 | ) 53 | batch_size = 64 54 | train_dataloader = training.to_dataloader( 55 | train=True, batch_size=batch_size, num_workers=0 56 | ) 57 | val_dataloader = validation.to_dataloader( 58 | train=False, batch_size=batch_size, num_workers=0 59 | ) 60 | 61 | # save datasets 62 | training.save("training.pkl") 63 | validation.save("validation.pkl") 64 | 65 | early_stop_callback = EarlyStopping( 66 | monitor="val_loss", min_delta=1e-4, patience=5, verbose=False, mode="min" 67 | ) 68 | lr_logger = LearningRateMonitor() 69 | 70 | trainer = pl.Trainer( 71 | max_epochs=10, 72 | accelerator="gpu", 73 | devices="auto", 74 | gradient_clip_val=0.1, 75 | limit_train_batches=30, 76 | limit_val_batches=3, 77 | # fast_dev_run=True, 78 | # logger=logger, 79 | # profiler=True, 80 | callbacks=[lr_logger, early_stop_callback], 81 | ) 82 | 83 | 84 | deepar = DeepAR.from_dataset( 85 | training, 86 | learning_rate=0.1, 87 | hidden_size=32, 88 | dropout=0.1, 89 | loss=NormalDistributionLoss(), 90 | log_interval=10, 91 | log_val_interval=3, 92 | # reduce_on_plateau_patience=3, 93 | ) 94 | print(f"Number of parameters in network: {deepar.size() / 1e3:.1f}k") 95 | 96 | # # find optimal learning rate 97 | # deepar.hparams.log_interval = -1 98 | # deepar.hparams.log_val_interval = -1 99 | # trainer.limit_train_batches = 1.0 100 | # res = Tuner(trainer).lr_find( 101 | # deepar, train_dataloaders=train_dataloader, val_dataloaders=val_dataloader, min_lr=1e-5, max_lr=1e2 # noqa: E501 102 | # ) 103 | 104 | # print(f"suggested learning rate: {res.suggestion()}") 105 | # fig = res.plot(show=True, suggest=True) 106 | # fig.show() 107 | # deepar.hparams.learning_rate = res.suggestion() 108 | 109 | torch.set_num_threads(10) 110 | trainer.fit( 111 | deepar, 112 | train_dataloaders=train_dataloader, 113 | val_dataloaders=val_dataloader, 114 | ) 115 | 116 | # calculate mean absolute error on validation set 117 | actuals = torch.cat([y for x, (y, weight) in iter(val_dataloader)]) 118 | predictions = deepar.predict(val_dataloader) 119 | print(f"Mean absolute error of model: {(actuals - predictions).abs().mean()}") 120 | 121 | # # plot actual vs. predictions 122 | # raw_predictions, x = deepar.predict(val_dataloader, mode="raw", return_x=True) 123 | # for idx in range(10): # plot 10 examples 124 | # deepar.plot_prediction(x, raw_predictions, idx=idx, add_loss_to_title=True) 125 | -------------------------------------------------------------------------------- /pytorch_forecasting/callbacks/predict.py: -------------------------------------------------------------------------------- 1 | from typing import Any, Optional 2 | from warnings import warn 3 | 4 | from lightning import Trainer 5 | from lightning.pytorch import LightningModule 6 | from lightning.pytorch.callbacks import BasePredictionWriter 7 | import torch 8 | 9 | 10 | class PredictCallback(BasePredictionWriter): 11 | """ 12 | Callback to capture predictions and related information internally. 13 | 14 | This callback is used by ``BaseModel.predict()`` to process raw model outputs 15 | into the desired format (``prediction``, ``quantiles``, or ``raw``) and collect 16 | any additional requested info (``x``, ``y``, ``index``, etc.). The results are 17 | collated and stored in memory, accessible via the ``.result`` property. 18 | 19 | Parameters 20 | ---------- 21 | mode : str 22 | The prediction mode ("prediction", "quantiles", or "raw"). 23 | return_info : list[str], optional 24 | Additional information to return. 25 | **kwargs : 26 | Additional keyword arguments for `to_prediction` or `to_quantiles`. 27 | """ 28 | 29 | def __init__( 30 | self, 31 | mode: str = "prediction", 32 | return_info: Optional[list[str]] = None, 33 | mode_kwargs: dict[str, Any] = None, 34 | ): 35 | super().__init__(write_interval="epoch") 36 | self.mode = mode 37 | self.return_info = return_info or [] 38 | self.mode_kwargs = mode_kwargs or {} 39 | self._reset_data() 40 | 41 | def _reset_data(self, result: bool = True): 42 | """Clear collected data for a new prediction run.""" 43 | self.predictions = [] 44 | self.info = {key: [] for key in self.return_info} 45 | if result: 46 | self._result = None 47 | 48 | def on_predict_batch_end( 49 | self, 50 | trainer: Trainer, 51 | pl_module: LightningModule, 52 | outputs: Any, 53 | batch: Any, 54 | batch_idx: int, 55 | dataloader_idx: int = 0, 56 | ): 57 | """Process and store predictions for a single batch.""" 58 | x, y = batch 59 | 60 | if self.mode == "raw": 61 | processed_output = outputs 62 | elif self.mode == "prediction": 63 | processed_output = pl_module.to_prediction(outputs, **self.mode_kwargs) 64 | elif self.mode == "quantiles": 65 | processed_output = pl_module.to_quantiles(outputs, **self.mode_kwargs) 66 | else: 67 | raise ValueError(f"Invalid prediction mode: {self.mode}") 68 | 69 | self.predictions.append(processed_output) 70 | 71 | for key in self.return_info: 72 | if key == "x": 73 | self.info[key].append(x) 74 | elif key == "y": 75 | self.info[key].append(y[0]) 76 | elif key == "index": 77 | self.info[key].append(y[1]) 78 | elif key == "decoder_lengths": 79 | self.info[key].append(x["decoder_lengths"]) 80 | else: 81 | warn(f"Unknown return_info key: {key}") 82 | 83 | def on_predict_epoch_end(self, trainer: Trainer, pl_module: LightningModule): 84 | """Collate all batch results into final tensors.""" 85 | if self.mode == "raw" and isinstance(self.predictions[0], dict): 86 | keys = self.predictions[0].keys() 87 | collated_preds = { 88 | key: torch.cat([p[key] for p in self.predictions]) for key in keys 89 | } 90 | else: 91 | collated_preds = {"prediction": torch.cat(self.predictions)} 92 | 93 | final_result = collated_preds 94 | 95 | for key, data_list in self.info.items(): 96 | if isinstance(data_list[0], dict): 97 | collated_info = { 98 | k: torch.cat([d[k] for d in data_list]) for k in data_list[0].keys() 99 | } 100 | else: 101 | collated_info = torch.cat(data_list) 102 | final_result[key] = collated_info 103 | 104 | self._result = final_result 105 | self._reset_data(result=False) 106 | 107 | @property 108 | def result(self) -> dict[str, torch.Tensor]: 109 | if self._result is None: 110 | raise RuntimeError("Prediction results are not yet available.") 111 | return self._result 112 | --------------------------------------------------------------------------------