From 5e8f0db5fa1d2b48e3aab4ea48231081897da2f4 Mon Sep 17 00:00:00 2001 From: Pinaka07 Date: Wed, 9 Jul 2025 23:01:07 +0530 Subject: [PATCH 01/12] solved issue #1881 --- docs/source/models.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/models.rst b/docs/source/models.rst index 67dd7c042..53b39cae5 100644 --- a/docs/source/models.rst +++ b/docs/source/models.rst @@ -71,7 +71,7 @@ a model that can learn relations between the timeseries can improve accuracy. Not that only :ref:`models that can process covariates ` can learn relationships between different timeseries. If the timeseries denote different entities or exhibit very similar patterns accross the board, -a model such as :py:class:`~pytorch_forecasting.models.nbeats.NBeats` will work as well. +a model such as :py:class:`~pytorch_forecasting.models.nbeats.NBeats` will not work as well. If you have only one or very few timeseries, they should be very long in order for a deep learning approach to work well. Consider also From c4a9476f8df1da8457126a1b0bfaecb4578d2c80 Mon Sep 17 00:00:00 2001 From: Pinaka07 Date: Thu, 28 Aug 2025 15:58:51 +0530 Subject: [PATCH 02/12] Docs: auto-generate model overview via Sphinx extension (adds docs/source/_ext/model_overview.py; updates conf.py; simplifies models.rst) --- docs/source/_ext/model_overview.py | 126 ++++++++++++++++++++++ docs/source/conf.py | 15 +++ docs/source/models.rst | 163 +---------------------------- 3 files changed, 142 insertions(+), 162 deletions(-) create mode 100644 docs/source/_ext/model_overview.py diff --git a/docs/source/_ext/model_overview.py b/docs/source/_ext/model_overview.py new file mode 100644 index 000000000..e540ede73 --- /dev/null +++ b/docs/source/_ext/model_overview.py @@ -0,0 +1,126 @@ +""" +Sphinx extension: Auto-generate pytorch_forecasting model overview. + +This writes/overwrites docs/source/models.rst during the build, +listing all registry models with tags and links to API docs. +""" +from __future__ import annotations + +import os +from typing import List + + +def _safe_import_all_objects(): + try: + # prefer public registry interface + from pytorch_forecasting._registry import all_objects # type: ignore + + return all_objects, None + except Exception as e: # pragma: no cover - defensive + return None, e + + +def _render_lines() -> List[str]: + all_objects, err = _safe_import_all_objects() + + lines: List[str] = [] + lines.append("Models") + lines.append("======") + lines.append("") + lines.append( + "(This page is auto-generated from the registry at build time. Do not edit manually.)" + ) + lines.append("") + + if all_objects is None: + lines.extend( + [ + ".. note::", + f" Failed to import registry for model overview.", + f" Build-time error: ``{err}``", + "", + ] + ) + return lines + + try: + df = all_objects( + object_types=["forecaster_pytorch_v1", "forecaster_pytorch_v2"], + as_dataframe=True, + return_tags=[ + "object_type", + "info:name", + "capability:exogenous", + "capability:multivariate", + "capability:pred_int", + "capability:flexible_history_length", + "capability:cold_start", + ], + return_names=True, + ) + except Exception as e: # pragma: no cover - defensive + lines.extend( + [ + ".. note::", + f" Registry query failed: ``{e}``", + "", + ] + ) + return lines + + if df is None or len(df) == 0: + lines.extend([".. note::", " No models found in registry.", ""]) + return lines + + # header + lines.append(".. list-table:: Available forecasting models") + lines.append(" :header-rows: 1") + lines.append(" :widths: 28 10 10 12 14 16 12") + lines.append("") + lines.append( + " * - Model\n - Version\n - Covariates\n - Multivariate\n - Pred. intervals\n - Flexible history\n - Cold-start" + ) + + # rows + for _, row in df.sort_values("names").iterrows(): + pkg_cls = row["objects"] + try: + model_cls = pkg_cls.get_model_cls() + qualname = f"{model_cls.__module__}.{model_cls.__name__}" + except Exception: + qualname = f"{pkg_cls.__module__}.{pkg_cls.__name__}" + + def _mark(v): + if v is True: + return "x" + if v is False: + return "" + return "?" + + lines.append( + " * - " + + f":py:class:`~{qualname}`\n - {row.get('object_type', '')}\n - {_mark(row.get('capability:exogenous'))}\n - {_mark(row.get('capability:multivariate'))}\n - {_mark(row.get('capability:pred_int'))}\n - {_mark(row.get('capability:flexible_history_length'))}\n - {_mark(row.get('capability:cold_start'))}" + ) + + lines.append("") + return lines + + +def _write_models_rst(app) -> None: + # confdir is docs/source + out_file = os.path.join(app.confdir, "models.rst") + lines = _render_lines() + os.makedirs(os.path.dirname(out_file), exist_ok=True) + with open(out_file, "w", encoding="utf-8") as f: + f.write("\n".join(lines)) + + +def setup(app): + app.connect("builder-inited", _write_models_rst) + return { + "version": "1.0", + "parallel_read_safe": True, + "parallel_write_safe": True, + } + + diff --git a/docs/source/conf.py b/docs/source/conf.py index 0e58692e1..75692d0e0 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -23,6 +23,12 @@ PROJECT_PATH = SOURCE_PATH.joinpath("../..") # noqa # project root sys.path.insert(0, str(PROJECT_PATH)) # noqa +sys.path.insert(0, os.path.abspath("../..")) + +# make the local _ext folder importable +_EXT_PATH = SOURCE_PATH.joinpath("_ext") +if str(_EXT_PATH) not in sys.path: + sys.path.insert(0, str(_EXT_PATH)) import pytorch_forecasting # isort:skip @@ -137,6 +143,12 @@ def setup(app: Sphinx): app.connect("autodoc-skip-member", skip) app.add_directive("moduleautosummary", ModuleAutoSummary) app.add_js_file("https://buttons.github.io/buttons.js", **{"async": "async"}) + # load custom model overview generator if available + try: + if "model_overview" not in extensions: + extensions.append("model_overview") + except Exception: + pass # extension configuration @@ -190,3 +202,6 @@ def setup(app: Sphinx): nbsphinx_execute = "never" # always nbsphinx_allow_errors = False # False nbsphinx_timeout = 600 # seconds + + +# (model overview generation moved to docs/source/_ext/model_overview.py) diff --git a/docs/source/models.rst b/docs/source/models.rst index 53b39cae5..d21db463c 100644 --- a/docs/source/models.rst +++ b/docs/source/models.rst @@ -1,165 +1,4 @@ Models ====== -.. _models: - -.. currentmodule:: pytorch_forecasting - -Model parameters very much depend on the dataset for which they are destined. - -PyTorch Forecasting provides a ``.from_dataset()`` method for each model that -takes a :py:class:`~data.timeseries.TimeSeriesDataSet` and additional parameters -that cannot directy derived from the dataset such as, e.g. ``learning_rate`` or ``hidden_size``. - -To tune models, `optuna `_ can be used. For example, tuning of the -:py:class:`~models.temporal_fusion_transformer.TemporalFusionTransformer` -is implemented by :py:func:`~models.temporal_fusion_transformer.tuning.optimize_hyperparameters` - -Selecting an architecture --------------------------- - -Criteria for selecting an architecture depend heavily on the use-case. There are multiple selection criteria -and you should take into account. Here is an overview over the pros and cons of the implemented models: - -.. csv-table:: Model comparison - :header: "Name", "Covariates", "Multiple targets", "Regression", "Classification", "Probabilistic", "Uncertainty", "Interactions between series", "Flexible history length", "Cold-start", "Required computational resources (1-5, 5=most)" - - :py:class:`~pytorch_forecasting.models.rnn.RecurrentNetwork`, "x", "x", "x", "", "", "", "", "x", "", 2 - :py:class:`~pytorch_forecasting.models.mlp.DecoderMLP`, "x", "x", "x", "x", "", "x", "", "x", "x", 1 - :py:class:`~pytorch_forecasting.models.nbeats.NBeats`, "", "", "x", "", "", "", "", "", "", 1 - :py:class:`~pytorch_forecasting.models.nhits.NHiTS`, "x", "x", "x", "", "", "", "", "", "", 1 - :py:class:`~pytorch_forecasting.models.deepar.DeepAR`, "x", "x", "x", "", "x", "x", "x [#deepvar]_ ", "x", "", 3 - :py:class:`~pytorch_forecasting.models.temporal_fusion_transformer.TemporalFusionTransformer`, "x", "x", "x", "x", "", "x", "", "x", "x", 4 - :py:class:`~pytorch_forecasting.model.tide.TiDEModel`, "x", "x", "x", "", "", "", "", "x", "", 3 - -.. [#deepvar] Accounting for correlations using a multivariate loss function which converts the network into a DeepVAR model. - -Size and type of available data -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -One should particularly consider five criteria. - -Availability of covariates -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -.. _model-covariates: - -If you have covariates, that is variables in addition to the target variable itself that hold information -about the target, then your case will benefit from a model that can accomodate covariates. A model that -cannot use covariates is :py:class:`~pytorch_forecasting.models.nbeats.NBeats`. - -Length of timeseries -^^^^^^^^^^^^^^^^^^^^^^ - -The length of time series has a significant impact on which model will work well. Unfortunately, -most models are created and tested on very long timeseries while in practice short or a mix of short and long -timeseries are often encountered. A model that can leverage covariates well such as the -:py:class:`~pytorch_forecasting.models.temporal_fusion_transformer.TemporalFusionTransformer` -will typically perform better than other models on short timeseries. It is a significant step -from short timeseries to making cold-start predictions soley based on static covariates, i.e. -making predictions without observed history. For example, -this is only supported by the -:py:class:`~pytorch_forecasting.models.temporal_fusion_transformer.TemporalFusionTransformer` -but does not work tremendously well. - - -Number of timeseries and their relation to each other -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -If your time series are related to each other (e.g. all sales of products of the same company), -a model that can learn relations between the timeseries can improve accuracy. -Not that only :ref:`models that can process covariates ` can -learn relationships between different timeseries. -If the timeseries denote different entities or exhibit very similar patterns accross the board, -a model such as :py:class:`~pytorch_forecasting.models.nbeats.NBeats` will not work as well. - -If you have only one or very few timeseries, -they should be very long in order for a deep learning approach to work well. Consider also -more traditional approaches. - -Type of prediction task -^^^^^^^^^^^^^^^^^^^^^^^^^ - -Not every can do regression, classification or handle multiple targets. Some are exclusively -geared towards a single task. For example, :py:class:`~pytorch_forecasting.models.nbeats.NBeats` -can only be used for regression on a single target without covariates while the -:py:class:`~pytorch_forecasting.models.temporal_fusion_transformer.TemporalFusionTransformer` supports -multiple targets and even hetrogeneous targets where some are continuous variables and others categorical, -i.e. regression and classification at the same time. :py:class:`~pytorch_forecasting.models.deepar.DeepAR` -can handle multiple targets but only works for regression tasks. - -For long forecast horizon forecasts, :py:class:`~pytorch_forecasting.models.nhits.NHiTS` is an excellent choice -as it uses interpolation capabilities. - -Supporting uncertainty -~~~~~~~~~~~~~~~~~~~~~~~ - -Not all models support uncertainty estimation. Those that do, might do so in different fashions. -Non-parameteric models provide forecasts that are not bound to a given distribution -while parametric models assume that the data follows a specific distribution. - -The parametric models will be a better choice if you -know how your data (and potentially error) is distributed. However, if you are missing this information or -cannot make an educated guess that matches reality rather well, the model's uncertainty estimates will -be adversely impacted. In this case, a non-parameteric model will do much better. - -:py:class:`~pytorch_forecasting.models.deepar.DeepAR` is an example for a parameteric model while -the :py:class:`~pytorch_forecasting.models.temporal_fusion_transformer.TemporalFusionTransformer` -can output quantile forecasts that can fit any distribution. -Models based on normalizing flows marry the two worlds by providing a non-parameteric estimate -of a full probability distribution. PyTorch Forecasting currently does not provide -support for these but -`Pyro, a package for probabilistic programming `_ does -if you believe that your problem is uniquely suited to this solution. - -Computational requirements -~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Some models have simpler architectures and less parameters than others which can -lead to significantly different training times. However, this not a general rule as demonstrated -by Zhuohan et al. in `Train Large, Then Compress: Rethinking Model Size for Efficient Training and Inference of Transformers -`_. Because the data for a sample for timeseries models is often far samller than it -is for computer vision or language tasks, GPUs are often underused and increasing the width of models can be an effective way -to fully use a GPU. This can increase the speed of training while also improving accuracy. -The other path to pushing utilization of a GPU up is increasing the batch size. -However, increasing the batch size can adversly affect the generalization abilities of a trained network. -Also, take into account that often computational resources are mainly necessary for inference/prediction. The upfront task of training -a models will require developer time (also expensive!) but might be only a small part of the total compuational costs over -the lifetime of a model. - -The :py:class:`~pytorch_forecasting.models.temporal_fusion_transformer.TemporalFusionTransformer` is -a rather large model but might benefit from being trained with. -For example, :py:class:`~pytorch_forecasting.models.nbeats.NBeats` or :py:class:`~pytorch_forecasting.models.nhits.NHiTS` are -efficient models. -Autoregressive models such as :py:class:`~pytorch_forecasting.models.deepar.DeepAR` might be quick to train -but might be slow at inference time (in case of :py:class:`~pytorch_forecasting.models.deepar.DeepAR` this is -driven by sampling results probabilistically multiple times, effectively increasing the computational burden linearly with the -number of samples. - - -Implementing new architectures -------------------------------- - -Please see the :ref:`Using custom data and implementing custom models ` tutorial on how implement basic and more advanced models. - -Every model should inherit from a base model in :py:mod:`~pytorch_forecasting.models.base_model`. - -.. autoclass:: pytorch_forecasting.models.base_model.BaseModel - :noindex: - :members: __init__ - - - -Details and available models -------------------------------- - -See the API documentation for further details on available models: - -.. currentmodule:: pytorch_forecasting - -.. moduleautosummary:: - :toctree: api/ - :template: custom-module-template.rst - :recursive: - - pytorch_forecasting.models +*(This file is overwritten during the docs build by the `model_overview` extension. Do not edit manually.)* From 05fa34c04d279670b2a64725c2b7740fc8d25ac9 Mon Sep 17 00:00:00 2001 From: Pinaka07 Date: Thu, 28 Aug 2025 16:28:37 +0530 Subject: [PATCH 03/12] Fix pre-commit issues: type hints, wrap long strings, remove trailing whitespace in model_overview extension --- docs/source/_ext/model_overview.py | 67 +++++++++++++++++------------- 1 file changed, 38 insertions(+), 29 deletions(-) diff --git a/docs/source/_ext/model_overview.py b/docs/source/_ext/model_overview.py index e540ede73..831eb7a1a 100644 --- a/docs/source/_ext/model_overview.py +++ b/docs/source/_ext/model_overview.py @@ -7,7 +7,6 @@ from __future__ import annotations import os -from typing import List def _safe_import_all_objects(): @@ -20,27 +19,24 @@ def _safe_import_all_objects(): return None, e -def _render_lines() -> List[str]: +def _render_lines() -> list[str]: all_objects, err = _safe_import_all_objects() - lines: List[str] = [] + lines: list[str] = [] lines.append("Models") lines.append("======") lines.append("") - lines.append( - "(This page is auto-generated from the registry at build time. Do not edit manually.)" - ) + lines.append("(This page is auto-generated from the registry at build time.)") + lines.append("Do not edit manually.") lines.append("") if all_objects is None: - lines.extend( - [ - ".. note::", - f" Failed to import registry for model overview.", - f" Build-time error: ``{err}``", - "", - ] - ) + lines.extend([ + ".. note::", + " Failed to import registry for model overview.", + f" Build-time error: ``{err}``", + "", + ]) return lines try: @@ -59,17 +55,15 @@ def _render_lines() -> List[str]: return_names=True, ) except Exception as e: # pragma: no cover - defensive - lines.extend( - [ - ".. note::", - f" Registry query failed: ``{e}``", - "", - ] - ) + lines.extend([ + ".. note::", + f" Registry query failed: ``{e}``", + "", + ]) return lines if df is None or len(df) == 0: - lines.extend([".. note::", " No models found in registry.", ""]) + lines.extend([".. note::", " No models found in registry.", ""]) return lines # header @@ -77,9 +71,16 @@ def _render_lines() -> List[str]: lines.append(" :header-rows: 1") lines.append(" :widths: 28 10 10 12 14 16 12") lines.append("") - lines.append( - " * - Model\n - Version\n - Covariates\n - Multivariate\n - Pred. intervals\n - Flexible history\n - Cold-start" - ) + header_cols = [ + "Model", + "Version", + "Covariates", + "Multivariate", + "Pred. intervals", + "Flexible history", + "Cold-start", + ] + lines.append(" * - " + "\n - ".join(header_cols)) # rows for _, row in df.sort_values("names").iterrows(): @@ -97,15 +98,22 @@ def _mark(v): return "" return "?" - lines.append( - " * - " - + f":py:class:`~{qualname}`\n - {row.get('object_type', '')}\n - {_mark(row.get('capability:exogenous'))}\n - {_mark(row.get('capability:multivariate'))}\n - {_mark(row.get('capability:pred_int'))}\n - {_mark(row.get('capability:flexible_history_length'))}\n - {_mark(row.get('capability:cold_start'))}" - ) + row_cells = [ + f":py:class:`~{qualname}`", + f"{row.get('object_type', '')}", + _mark(row.get("capability:exogenous")), + _mark(row.get("capability:multivariate")), + _mark(row.get("capability:pred_int")), + _mark(row.get("capability:flexible_history_length")), + _mark(row.get("capability:cold_start")), + ] + lines.append(" * - " + "\n - ".join(row_cells)) lines.append("") return lines + def _write_models_rst(app) -> None: # confdir is docs/source out_file = os.path.join(app.confdir, "models.rst") @@ -115,6 +123,7 @@ def _write_models_rst(app) -> None: f.write("\n".join(lines)) + def setup(app): app.connect("builder-inited", _write_models_rst) return { From 229e74835e775429b420076675d75dbbec71a65e Mon Sep 17 00:00:00 2001 From: Pinaka07 Date: Thu, 28 Aug 2025 16:39:06 +0530 Subject: [PATCH 04/12] chore: trigger CI for docs/auto-model-overview From 3f14c6ed34f85fb724b4874a0f3715ef2afe3359 Mon Sep 17 00:00:00 2001 From: Pinaka07 Date: Thu, 28 Aug 2025 16:52:09 +0530 Subject: [PATCH 05/12] docs: log extension import failure instead of silent pass in conf.py --- docs/source/conf.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/source/conf.py b/docs/source/conf.py index 75692d0e0..e8122be58 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -147,8 +147,9 @@ def setup(app: Sphinx): try: if "model_overview" not in extensions: extensions.append("model_overview") - except Exception: - pass + except Exception as exc: + # avoid hard-failing docs builds; make the reason visible in Sphinx output + app.warn(f"model_overview extension not loaded: {exc}") # extension configuration From eb4ddc65a9033b47ee18159952e7e7d4147f9822 Mon Sep 17 00:00:00 2001 From: Pinaka07 Date: Thu, 28 Aug 2025 17:09:51 +0530 Subject: [PATCH 06/12] docs(rtd): log missing model_overview extension via sphinx logger; ensure compatibility with RTD runners --- docs/source/conf.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/docs/source/conf.py b/docs/source/conf.py index e8122be58..79cf73438 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -18,6 +18,7 @@ from sphinx.application import Sphinx from sphinx.ext.autosummary import Autosummary from sphinx.pycode import ModuleAnalyzer +from sphinx.util import logging as sphinx_logging SOURCE_PATH = Path(os.path.dirname(__file__)) # noqa # docs source PROJECT_PATH = SOURCE_PATH.joinpath("../..") # noqa # project root @@ -148,8 +149,10 @@ def setup(app: Sphinx): if "model_overview" not in extensions: extensions.append("model_overview") except Exception as exc: - # avoid hard-failing docs builds; make the reason visible in Sphinx output - app.warn(f"model_overview extension not loaded: {exc}") + # avoid hard-failing docs builds; make the reason visible in Sphinx logs + sphinx_logging.getLogger(__name__).warning( + "model_overview extension not loaded: %s", exc + ) # extension configuration From a56d8883690f0876affe16e428bf9a2b66d16dd3 Mon Sep 17 00:00:00 2001 From: Pinaka07 Date: Thu, 28 Aug 2025 17:19:33 +0530 Subject: [PATCH 07/12] docs(rtd): disable heavy registry generation on hosted builds; add safe mode fallback in model_overview extension --- docs/source/_ext/model_overview.py | 61 ++++++++++++++++++++++-------- 1 file changed, 46 insertions(+), 15 deletions(-) diff --git a/docs/source/_ext/model_overview.py b/docs/source/_ext/model_overview.py index 831eb7a1a..fa394bf46 100644 --- a/docs/source/_ext/model_overview.py +++ b/docs/source/_ext/model_overview.py @@ -4,6 +4,7 @@ This writes/overwrites docs/source/models.rst during the build, listing all registry models with tags and links to API docs. """ + from __future__ import annotations import os @@ -31,12 +32,14 @@ def _render_lines() -> list[str]: lines.append("") if all_objects is None: - lines.extend([ - ".. note::", - " Failed to import registry for model overview.", - f" Build-time error: ``{err}``", - "", - ]) + lines.extend( + [ + ".. note::", + " Failed to import registry for model overview.", + f" Build-time error: ``{err}``", + "", + ] + ) return lines try: @@ -55,11 +58,13 @@ def _render_lines() -> list[str]: return_names=True, ) except Exception as e: # pragma: no cover - defensive - lines.extend([ - ".. note::", - f" Registry query failed: ``{e}``", - "", - ]) + lines.extend( + [ + ".. note::", + f" Registry query failed: ``{e}``", + "", + ] + ) return lines if df is None or len(df) == 0: @@ -113,17 +118,45 @@ def _mark(v): return lines +def _is_safe_mode() -> bool: + """Return True if we should avoid heavy registry imports (e.g., on RTD).""" + if os.environ.get("READTHEDOCS", "").lower() in {"1", "true", "yes"}: + return True + if os.environ.get("PF_SKIP_MODEL_OVERVIEW", "").lower() in {"1", "true", "yes"}: + return True + return False + def _write_models_rst(app) -> None: # confdir is docs/source out_file = os.path.join(app.confdir, "models.rst") - lines = _render_lines() + try: + if _is_safe_mode(): + # minimal page on hosted builders to avoid heavy optional deps + lines = [ + "Models", + "======", + "", + "(Model overview generation is disabled in this build environment.)", + "Use a local build to view the full, registry-driven table.", + "", + ] + else: + lines = _render_lines() + except Exception as exc: # pragma: no cover - defensive + lines = [ + "Models", + "======", + "", + "(Model overview could not be generated due to a build-time error.)", + f"Error: ``{exc}``", + "", + ] os.makedirs(os.path.dirname(out_file), exist_ok=True) with open(out_file, "w", encoding="utf-8") as f: f.write("\n".join(lines)) - def setup(app): app.connect("builder-inited", _write_models_rst) return { @@ -131,5 +164,3 @@ def setup(app): "parallel_read_safe": True, "parallel_write_safe": True, } - - From 5963743965c884f76cdf72c4347184f5d4f0270a Mon Sep 17 00:00:00 2001 From: Pinaka07 Date: Fri, 29 Aug 2025 07:27:03 +0530 Subject: [PATCH 08/12] docs: fix KeyError in ModuleAutoSummary when module not in sys.modules --- docs/source/conf.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/source/conf.py b/docs/source/conf.py index 79cf73438..91d839648 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -125,6 +125,9 @@ class ModuleAutoSummary(Autosummary): def get_items(self, names): new_names = [] for name in names: + # Skip if module doesn't exist in sys.modules + if name not in sys.modules: + continue mod = sys.modules[name] mod_items = getattr(mod, "__all__", mod.__dict__) for t in mod_items: From f2690ae6b7013e05a077d92ca8642af981652edc Mon Sep 17 00:00:00 2001 From: Pinaka07 Date: Sun, 31 Aug 2025 20:54:04 +0530 Subject: [PATCH 09/12] docs: resolve merge conflict in models.rst - clean up for auto-generation --- docs/source/models.rst | 163 ----------------------------------------- 1 file changed, 163 deletions(-) diff --git a/docs/source/models.rst b/docs/source/models.rst index 89511c58d..d21db463c 100644 --- a/docs/source/models.rst +++ b/docs/source/models.rst @@ -1,167 +1,4 @@ Models ====== -docs/auto-model-overview *(This file is overwritten during the docs build by the `model_overview` extension. Do not edit manually.)* -======= -.. _models: - -.. currentmodule:: pytorch_forecasting - -Model parameters very much depend on the dataset for which they are destined. - -PyTorch Forecasting provides a ``.from_dataset()`` method for each model that -takes a :py:class:`~data.timeseries.TimeSeriesDataSet` and additional parameters -that cannot directy derived from the dataset such as, e.g. ``learning_rate`` or ``hidden_size``. - -To tune models, `optuna `_ can be used. For example, tuning of the -:py:class:`~models.temporal_fusion_transformer.TemporalFusionTransformer` -is implemented by :py:func:`~models.temporal_fusion_transformer.tuning.optimize_hyperparameters` - -Selecting an architecture --------------------------- - -Criteria for selecting an architecture depend heavily on the use-case. There are multiple selection criteria -and you should take into account. Here is an overview over the pros and cons of the implemented models: - -.. csv-table:: Model comparison - :header: "Name", "Covariates", "Multiple targets", "Regression", "Classification", "Probabilistic", "Uncertainty", "Interactions between series", "Flexible history length", "Cold-start", "Required computational resources (1-5, 5=most)" - - :py:class:`~pytorch_forecasting.models.rnn.RecurrentNetwork`, "x", "x", "x", "", "", "", "", "x", "", 2 - :py:class:`~pytorch_forecasting.models.mlp.DecoderMLP`, "x", "x", "x", "x", "", "x", "", "x", "x", 1 - :py:class:`~pytorch_forecasting.models.nbeats.NBeats`, "", "", "x", "", "", "", "", "", "", 1 - :py:class:`~pytorch_forecasting.models.nhits.NHiTS`, "x", "x", "x", "", "", "", "", "", "", 1 - :py:class:`~pytorch_forecasting.models.deepar.DeepAR`, "x", "x", "x", "", "x", "x", "x [#deepvar]_ ", "x", "", 3 - :py:class:`~pytorch_forecasting.models.temporal_fusion_transformer.TemporalFusionTransformer`, "x", "x", "x", "x", "", "x", "", "x", "x", 4 - :py:class:`~pytorch_forecasting.models.tide.TiDEModel`, "x", "x", "x", "", "", "", "", "x", "", 3 - :py:class:`~pytorch_forecasting.models.xlstm.xLSTMTime`, "x", "x", "x", "", "", "", "", "x", "", 3 - -.. [#deepvar] Accounting for correlations using a multivariate loss function which converts the network into a DeepVAR model. - -Size and type of available data -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -One should particularly consider five criteria. - -Availability of covariates - - -.. _model-covariates: - -If you have covariates, that is variables in addition to the target variable itself that hold information -about the target, then your case will benefit from a model that can accomodate covariates. A model that -cannot use covariates is :py:class:`~pytorch_forecasting.models.nbeats.NBeats`. - -Length of timeseries - -The length of time series has a significant impact on which model will work well. Unfortunately, -most models are created and tested on very long timeseries while in practice short or a mix of short and long -timeseries are often encountered. A model that can leverage covariates well such as the -:py:class:`~pytorch_forecasting.models.temporal_fusion_transformer.TemporalFusionTransformer` -will typically perform better than other models on short timeseries. It is a significant step -from short timeseries to making cold-start predictions soley based on static covariates, i.e. -making predictions without observed history. For example, -this is only supported by the -:py:class:`~pytorch_forecasting.models.temporal_fusion_transformer.TemporalFusionTransformer` -but does not work tremendously well. - - -Number of timeseries and their relation to each other - -If your time series are related to each other (e.g. all sales of products of the same company), -a model that can learn relations between the timeseries can improve accuracy. -Not that only :ref:`models that can process covariates ` can -learn relationships between different timeseries. -If the timeseries denote different entities or exhibit very similar patterns accross the board, -a model such as :py:class:`~pytorch_forecasting.models.nbeats.NBeats` will not work as well. - -If you have only one or very few timeseries, -they should be very long in order for a deep learning approach to work well. Consider also -more traditional approaches. - -Type of prediction task - -Not every can do regression, classification or handle multiple targets. Some are exclusively -geared towards a single task. For example, :py:class:`~pytorch_forecasting.models.nbeats.NBeats` -can only be used for regression on a single target without covariates while the -:py:class:`~pytorch_forecasting.models.temporal_fusion_transformer.TemporalFusionTransformer` supports -multiple targets and even hetrogeneous targets where some are continuous variables and others categorical, -i.e. regression and classification at the same time. :py:class:`~pytorch_forecasting.models.deepar.DeepAR` -can handle multiple targets but only works for regression tasks. - -For long forecast horizon forecasts, :py:class:`~pytorch_forecasting.models.nhits.NHiTS` is an excellent choice -as it uses interpolation capabilities. - -Supporting uncertainty -~~~~~~~~~~~~~~~~~~~~~~~ - -Not all models support uncertainty estimation. Those that do, might do so in different fashions. -Non-parameteric models provide forecasts that are not bound to a given distribution -while parametric models assume that the data follows a specific distribution. - -The parametric models will be a better choice if you -know how your data (and potentially error) is distributed. However, if you are missing this information or -cannot make an educated guess that matches reality rather well, the model's uncertainty estimates will -be adversely impacted. In this case, a non-parameteric model will do much better. - -:py:class:`~pytorch_forecasting.models.deepar.DeepAR` is an example for a parameteric model while -the :py:class:`~pytorch_forecasting.models.temporal_fusion_transformer.TemporalFusionTransformer` -can output quantile forecasts that can fit any distribution. -Models based on normalizing flows marry the two worlds by providing a non-parameteric estimate -of a full probability distribution. PyTorch Forecasting currently does not provide -support for these but -`Pyro, a package for probabilistic programming `_ does -if you believe that your problem is uniquely suited to this solution. - -Computational requirements -~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Some models have simpler architectures and less parameters than others which can -lead to significantly different training times. However, this not a general rule as demonstrated -by Zhuohan et al. in `Train Large, Then Compress: Rethinking Model Size for Efficient Training and Inference of Transformers -`_. Because the data for a sample for timeseries models is often far samller than it -is for computer vision or language tasks, GPUs are often underused and increasing the width of models can be an effective way -to fully use a GPU. This can increase the speed of training while also improving accuracy. -The other path to pushing utilization of a GPU up is increasing the batch size. -However, increasing the batch size can adversly affect the generalization abilities of a trained network. -Also, take into account that often computational resources are mainly necessary for inference/prediction. The upfront task of training -a models will require developer time (also expensive!) but might be only a small part of the total compuational costs over -the lifetime of a model. - -The :py:class:`~pytorch_forecasting.models.temporal_fusion_transformer.TemporalFusionTransformer` is -a rather large model but might benefit from being trained with. -For example, :py:class:`~pytorch_forecasting.models.nbeats.NBeats` or :py:class:`~pytorch_forecasting.models.nhits.NHiTS` are -efficient models. -Autoregressive models such as :py:class:`~pytorch_forecasting.models.deepar.DeepAR` might be quick to train -but might be slow at inference time (in case of :py:class:`~pytorch_forecasting.models.deepar.DeepAR` this is -driven by sampling results probabilistically multiple times, effectively increasing the computational burden linearly with the -number of samples. - - -Implementing new architectures -------------------------------- - -Please see the :ref:`Using custom data and implementing custom models ` tutorial on how implement basic and more advanced models. - -Every model should inherit from a base model in :py:mod:`~pytorch_forecasting.models.base_model`. - -.. autoclass:: pytorch_forecasting.models.base_model.BaseModel - :noindex: - :members: __init__ - - - -Details and available models -------------------------------- - -See the API documentation for further details on available models: - -.. currentmodule:: pytorch_forecasting - -.. moduleautosummary:: - :toctree: api/ - :template: custom-module-template.rst - :recursive: - - pytorch_forecasting.models - main From fbccf8fb463081b52a5eee27e9d78c979b980b11 Mon Sep 17 00:00:00 2001 From: Pinaka07 Date: Sun, 31 Aug 2025 22:00:24 +0530 Subject: [PATCH 10/12] docs: update model overview table to match requested format with Class Name, Estimator Type, Authors, Maintainers, Dependencies columns --- docs/source/_ext/model_overview.py | 51 +++++++++++++++--------------- 1 file changed, 26 insertions(+), 25 deletions(-) diff --git a/docs/source/_ext/model_overview.py b/docs/source/_ext/model_overview.py index fa394bf46..b943ac441 100644 --- a/docs/source/_ext/model_overview.py +++ b/docs/source/_ext/model_overview.py @@ -49,11 +49,9 @@ def _render_lines() -> list[str]: return_tags=[ "object_type", "info:name", - "capability:exogenous", - "capability:multivariate", - "capability:pred_int", - "capability:flexible_history_length", - "capability:cold_start", + "authors", + "maintainers", + "dependencies", ], return_names=True, ) @@ -74,16 +72,14 @@ def _render_lines() -> list[str]: # header lines.append(".. list-table:: Available forecasting models") lines.append(" :header-rows: 1") - lines.append(" :widths: 28 10 10 12 14 16 12") + lines.append(" :widths: 30 15 20 20 15") lines.append("") header_cols = [ - "Model", - "Version", - "Covariates", - "Multivariate", - "Pred. intervals", - "Flexible history", - "Cold-start", + "Class Name", + "Estimator Type", + "Authors", + "Maintainers", + "Dependencies", ] lines.append(" * - " + "\n - ".join(header_cols)) @@ -96,21 +92,26 @@ def _render_lines() -> list[str]: except Exception: qualname = f"{pkg_cls.__module__}.{pkg_cls.__name__}" - def _mark(v): - if v is True: - return "x" - if v is False: - return "" - return "?" + # Get object type (forecaster_pytorch_v1 or forecaster_pytorch_v2) + object_type = row.get("object_type", "") + if object_type == "forecaster_pytorch_v1": + estimator_type = "forecaster_v1" + elif object_type == "forecaster_pytorch_v2": + estimator_type = "forecaster_v2" + else: + estimator_type = object_type + + # Get authors and maintainers from tags + authors = row.get("authors", "pytorch-forecasting developers") + maintainers = row.get("maintainers", "pytorch-forecasting developers") + dependencies = row.get("dependencies", "None") row_cells = [ f":py:class:`~{qualname}`", - f"{row.get('object_type', '')}", - _mark(row.get("capability:exogenous")), - _mark(row.get("capability:multivariate")), - _mark(row.get("capability:pred_int")), - _mark(row.get("capability:flexible_history_length")), - _mark(row.get("capability:cold_start")), + estimator_type, + authors, + maintainers, + dependencies, ] lines.append(" * - " + "\n - ".join(row_cells)) From 2c4045588cc59cdb82dbb33fdf1f857f4edd16db Mon Sep 17 00:00:00 2001 From: Pinaka07 Date: Mon, 1 Sep 2025 17:54:33 +0530 Subject: [PATCH 11/12] docs: update model overview table to match requested format with Class Name, Estimator Type, Authors, Maintainers, Dependencies columns --- docs/source/_ext/model_overview.py | 29 ++++++++++++++++++++--------- 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/docs/source/_ext/model_overview.py b/docs/source/_ext/model_overview.py index b943ac441..1027a812e 100644 --- a/docs/source/_ext/model_overview.py +++ b/docs/source/_ext/model_overview.py @@ -50,8 +50,7 @@ def _render_lines() -> list[str]: "object_type", "info:name", "authors", - "maintainers", - "dependencies", + "python_dependencies", ], return_names=True, ) @@ -101,17 +100,29 @@ def _render_lines() -> list[str]: else: estimator_type = object_type - # Get authors and maintainers from tags - authors = row.get("authors", "pytorch-forecasting developers") - maintainers = row.get("maintainers", "pytorch-forecasting developers") - dependencies = row.get("dependencies", "None") + # Get authors from tags + authors = row.get("authors", []) + if isinstance(authors, list) and authors: + authors_str = ", ".join(authors) + else: + authors_str = "pytorch-forecasting developers" + + # No maintainers tag exists, so use authors as maintainers + maintainers_str = authors_str + + # Get dependencies from tags + dependencies = row.get("python_dependencies", []) + if isinstance(dependencies, list) and dependencies: + dependencies_str = ", ".join(dependencies) + else: + dependencies_str = "None" row_cells = [ f":py:class:`~{qualname}`", estimator_type, - authors, - maintainers, - dependencies, + authors_str, + maintainers_str, + dependencies_str, ] lines.append(" * - " + "\n - ".join(row_cells)) From a2e74b5736de6e9dc81da0b79e76f265704de279 Mon Sep 17 00:00:00 2001 From: Pinaka07 Date: Thu, 4 Sep 2025 21:07:00 +0530 Subject: [PATCH 12/12] docs: ensure model overview overwrites models.rst by generating on config-inited; default to enabled (opt-out via PF_SKIP_MODEL_OVERVIEW) --- docs/source/_ext/model_overview.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/docs/source/_ext/model_overview.py b/docs/source/_ext/model_overview.py index 1027a812e..b377e3555 100644 --- a/docs/source/_ext/model_overview.py +++ b/docs/source/_ext/model_overview.py @@ -131,9 +131,10 @@ def _render_lines() -> list[str]: def _is_safe_mode() -> bool: - """Return True if we should avoid heavy registry imports (e.g., on RTD).""" - if os.environ.get("READTHEDOCS", "").lower() in {"1", "true", "yes"}: - return True + """Return True if model overview generation is explicitly disabled. + + By default, generation runs in all environments. Set PF_SKIP_MODEL_OVERVIEW=1 to disable. + """ if os.environ.get("PF_SKIP_MODEL_OVERVIEW", "").lower() in {"1", "true", "yes"}: return True return False @@ -170,7 +171,8 @@ def _write_models_rst(app) -> None: def setup(app): - app.connect("builder-inited", _write_models_rst) + # generate as early as possible so Sphinx sees the written file during source discovery + app.connect("config-inited", _write_models_rst) return { "version": "1.0", "parallel_read_safe": True,