From b3644a65b041a790b94756fb1d9bbf2797236d0a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Franz=20Kir=C3=A1ly?= Date: Sat, 22 Feb 2025 23:18:51 +0100 Subject: [PATCH 001/139] test suite --- pyproject.toml | 1 + pytorch_forecasting/tests/__init__.py | 1 + 2 files changed, 2 insertions(+) create mode 100644 pytorch_forecasting/tests/__init__.py diff --git a/pyproject.toml b/pyproject.toml index f3d1e339c..8c661db1c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -102,6 +102,7 @@ dev = [ "pytest-dotenv>=0.5.2,<1.0.0", "tensorboard>=2.12.1,<3.0.0", "pandoc>=2.3,<3.0.0", + "scikit-base", ] # docs - dependencies for building the documentation diff --git a/pytorch_forecasting/tests/__init__.py b/pytorch_forecasting/tests/__init__.py new file mode 100644 index 000000000..6c2d26856 --- /dev/null +++ b/pytorch_forecasting/tests/__init__.py @@ -0,0 +1 @@ +"""PyTorch Forecasting test suite.""" From 4b2486e083ca93d8f4c1a29a6a25d882027815f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Franz=20Kir=C3=A1ly?= Date: Sat, 22 Feb 2025 23:33:29 +0100 Subject: [PATCH 002/139] skeleton --- pytorch_forecasting/_registry/__init__.py | 16 ++ pytorch_forecasting/_registry/_lookup.py | 214 ++++++++++++++++++ pytorch_forecasting/models/base/__init__.py | 2 + .../models/base/_base_object.py | 8 + pytorch_forecasting/tests/_config.py | 13 ++ .../tests/test_all_estimators.py | 118 ++++++++++ 6 files changed, 371 insertions(+) create mode 100644 pytorch_forecasting/_registry/__init__.py create mode 100644 pytorch_forecasting/_registry/_lookup.py create mode 100644 pytorch_forecasting/models/base/_base_object.py create mode 100644 pytorch_forecasting/tests/_config.py create mode 100644 pytorch_forecasting/tests/test_all_estimators.py diff --git a/pytorch_forecasting/_registry/__init__.py b/pytorch_forecasting/_registry/__init__.py new file mode 100644 index 000000000..bb0b88e61 --- /dev/null +++ b/pytorch_forecasting/_registry/__init__.py @@ -0,0 +1,16 @@ +"""PyTorch Forecasting registry.""" + +from pytorch_forecasting._registry._lookup import all_objects, all_tags +from pytorch_forecasting._registry._tags import ( + OBJECT_TAG_LIST, + OBJECT_TAG_REGISTER, + check_tag_is_valid, +) + +__all__ = [ + "OBJECT_TAG_LIST", + "OBJECT_TAG_REGISTER", + "all_objects", + "all_tags", + "check_tag_is_valid", +] diff --git a/pytorch_forecasting/_registry/_lookup.py b/pytorch_forecasting/_registry/_lookup.py new file mode 100644 index 000000000..ea3210cb0 --- /dev/null +++ b/pytorch_forecasting/_registry/_lookup.py @@ -0,0 +1,214 @@ +"""Registry lookup methods. + +This module exports the following methods for registry lookup: + +all_objects(object_types, filter_tags) + lookup and filtering of objects +""" +# copyright: skpro developers, BSD-3-Clause License (see LICENSE file) +# based on the sktime module of same name + +__author__ = ["fkiraly"] +# all_objects is based on the sklearn utility all_estimators + + +from copy import deepcopy +from operator import itemgetter +from pathlib import Path + +import pandas as pd +from skbase.lookup import all_objects as _all_objects + +from pytorch_forecasting.models.base import _BaseObject + + +def all_objects( + object_types=None, + filter_tags=None, + exclude_objects=None, + return_names=True, + as_dataframe=False, + return_tags=None, + suppress_import_stdout=True, +): + """Get a list of all objects from pytorch_forecasting. + + This function crawls the module and gets all classes that inherit + from skbase compatible base classes. + + Not included are: the base classes themselves, classes defined in test + modules. + + Parameters + ---------- + object_types: str, list of str, optional (default=None) + Which kind of objects should be returned. + if None, no filter is applied and all objects are returned. + if str or list of str, strings define scitypes specified in search + only objects that are of (at least) one of the scitypes are returned + possible str values are entries of registry.BASE_CLASS_REGISTER (first col) + for instance 'regrssor_proba', 'distribution, 'metric' + + return_names: bool, optional (default=True) + + if True, estimator class name is included in the ``all_objects`` + return in the order: name, estimator class, optional tags, either as + a tuple or as pandas.DataFrame columns + + if False, estimator class name is removed from the ``all_objects`` return. + + filter_tags: dict of (str or list of str), optional (default=None) + For a list of valid tag strings, use the registry.all_tags utility. + + ``filter_tags`` subsets the returned estimators as follows: + + * each key/value pair is statement in "and"/conjunction + * key is tag name to sub-set on + * value str or list of string are tag values + * condition is "key must be equal to value, or in set(value)" + + exclude_estimators: str, list of str, optional (default=None) + Names of estimators to exclude. + + as_dataframe: bool, optional (default=False) + + True: ``all_objects`` will return a pandas.DataFrame with named + columns for all of the attributes being returned. + + False: ``all_objects`` will return a list (either a list of + estimators or a list of tuples, see Returns) + + return_tags: str or list of str, optional (default=None) + Names of tags to fetch and return each estimator's value of. + For a list of valid tag strings, use the registry.all_tags utility. + if str or list of str, + the tag values named in return_tags will be fetched for each + estimator and will be appended as either columns or tuple entries. + + suppress_import_stdout : bool, optional. Default=True + whether to suppress stdout printout upon import. + + Returns + ------- + all_objects will return one of the following: + 1. list of objects, if return_names=False, and return_tags is None + 2. list of tuples (optional object name, class, ~optional object + tags), if return_names=True or return_tags is not None. + 3. pandas.DataFrame if as_dataframe = True + if list of objects: + entries are objects matching the query, + in alphabetical order of object name + if list of tuples: + list of (optional object name, object, optional object + tags) matching the query, in alphabetical order of object name, + where + ``name`` is the object name as string, and is an + optional return + ``object`` is the actual object + ``tags`` are the object's values for each tag in return_tags + and is an optional return. + if dataframe: + all_objects will return a pandas.DataFrame. + column names represent the attributes contained in each column. + "objects" will be the name of the column of objects, "names" + will be the name of the column of object class names and the string(s) + passed in return_tags will serve as column names for all columns of + tags that were optionally requested. + + Examples + -------- + >>> from skpro.registry import all_objects + >>> # return a complete list of objects as pd.Dataframe + >>> all_objects(as_dataframe=True) # doctest: +SKIP + >>> # return all probabilistic regressors by filtering for object type + >>> all_objects("regressor_proba", as_dataframe=True) # doctest: +SKIP + >>> # return all regressors which handle missing data in the input by tag filtering + >>> all_objects( + ... "regressor_proba", + ... filter_tags={"capability:missing": True}, + ... as_dataframe=True + ... ) # doctest: +SKIP + + References + ---------- + Adapted version of sktime's ``all_estimators``, + which is an evolution of scikit-learn's ``all_estimators`` + """ + MODULES_TO_IGNORE = ( + "tests", + "setup", + "contrib", + "utils", + "all", + ) + + result = [] + ROOT = str(Path(__file__).parent.parent) # skpro package root directory + + if isinstance(filter_tags, str): + filter_tags = {filter_tags: True} + filter_tags = filter_tags.copy() if filter_tags else None + + if object_types: + if filter_tags and "object_type" not in filter_tags.keys(): + object_tag_filter = {"object_type": object_types} + elif filter_tags: + filter_tags_filter = filter_tags.get("object_type", []) + if isinstance(object_types, str): + object_types = [object_types] + object_tag_update = {"object_type": object_types + filter_tags_filter} + filter_tags.update(object_tag_update) + else: + object_tag_filter = {"object_type": object_types} + if filter_tags: + filter_tags.update(object_tag_filter) + else: + filter_tags = object_tag_filter + + result = _all_objects( + object_types=[_BaseObject], + filter_tags=filter_tags, + exclude_objects=exclude_objects, + return_names=return_names, + as_dataframe=as_dataframe, + return_tags=return_tags, + suppress_import_stdout=suppress_import_stdout, + package_name="skpro", + path=ROOT, + modules_to_ignore=MODULES_TO_IGNORE, + ) + + return result + + +def _check_list_of_str_or_error(arg_to_check, arg_name): + """Check that certain arguments are str or list of str. + + Parameters + ---------- + arg_to_check: argument we are testing the type of + arg_name: str, + name of the argument we are testing, will be added to the error if + ``arg_to_check`` is not a str or a list of str + + Returns + ------- + arg_to_check: list of str, + if arg_to_check was originally a str it converts it into a list of str + so that it can be iterated over. + + Raises + ------ + TypeError if arg_to_check is not a str or list of str + """ + # check that return_tags has the right type: + if isinstance(arg_to_check, str): + arg_to_check = [arg_to_check] + if not isinstance(arg_to_check, list) or not all( + isinstance(value, str) for value in arg_to_check + ): + raise TypeError( + f"Error in all_objects! Argument {arg_name} must be either\ + a str or list of str" + ) + return arg_to_check diff --git a/pytorch_forecasting/models/base/__init__.py b/pytorch_forecasting/models/base/__init__.py index 4860e4838..474e7d564 100644 --- a/pytorch_forecasting/models/base/__init__.py +++ b/pytorch_forecasting/models/base/__init__.py @@ -7,8 +7,10 @@ BaseModelWithCovariates, Prediction, ) +from pytorch_forecasting.models.base._base_object import _BaseObject __all__ = [ + "_BaseObject", "AutoRegressiveBaseModel", "AutoRegressiveBaseModelWithCovariates", "BaseModel", diff --git a/pytorch_forecasting/models/base/_base_object.py b/pytorch_forecasting/models/base/_base_object.py new file mode 100644 index 000000000..7330867b1 --- /dev/null +++ b/pytorch_forecasting/models/base/_base_object.py @@ -0,0 +1,8 @@ +"""Base Classes for pytorch-forecasting models, skbase compatible for indexing.""" + +from skbase.base import BaseObject as _SkbaseBaseObject + + +class _BaseObject(_SkbaseBaseObject): + + pass diff --git a/pytorch_forecasting/tests/_config.py b/pytorch_forecasting/tests/_config.py new file mode 100644 index 000000000..dd9c2e889 --- /dev/null +++ b/pytorch_forecasting/tests/_config.py @@ -0,0 +1,13 @@ +"""Test configs.""" + +# list of str, names of estimators to exclude from testing +# WARNING: tests for these estimators will be skipped +EXCLUDE_ESTIMATORS = [ + "DummySkipped", + "ClassName", # exclude classes from extension templates +] + +# dictionary of lists of str, names of tests to exclude from testing +# keys are class names of estimators, values are lists of test names to exclude +# WARNING: tests with these names will be skipped +EXCLUDED_TESTS = {} diff --git a/pytorch_forecasting/tests/test_all_estimators.py b/pytorch_forecasting/tests/test_all_estimators.py new file mode 100644 index 000000000..c806691fa --- /dev/null +++ b/pytorch_forecasting/tests/test_all_estimators.py @@ -0,0 +1,118 @@ +"""Automated tests based on the skbase test suite template.""" +from inspect import isclass + +from skbase.testing import BaseFixtureGenerator as _BaseFixtureGenerator +from skbase.testing import TestAllObjects as _TestAllObjects + +from pytorch_forecasting._registry import all_objects +from pytorch_forecasting.tests._config import EXCLUDE_ESTIMATORS, EXCLUDED_TESTS + + +# whether to test only estimators from modules that are changed w.r.t. main +# default is False, can be set to True by pytest --only_changed_modules True flag +ONLY_CHANGED_MODULES = False + + +class PackageConfig: + """Contains package config variables for test classes.""" + + # class variables which can be overridden by descendants + # ------------------------------------------------------ + + # package to search for objects + # expected type: str, package/module name, relative to python environment root + package_name = "pytroch_forecasting" + + # list of object types (class names) to exclude + # expected type: list of str, str are class names + exclude_objects = EXCLUDE_ESTIMATORS + + # list of tests to exclude + # expected type: dict of lists, key:str, value: List[str] + # keys are class names of estimators, values are lists of test names to exclude + excluded_tests = EXCLUDED_TESTS + + +class BaseFixtureGenerator(_BaseFixtureGenerator): + """Fixture generator for base testing functionality in sktime. + + Test classes inheriting from this and not overriding pytest_generate_tests + will have estimator and scenario fixtures parametrized out of the box. + + Descendants can override: + estimator_type_filter: str, class variable; None or scitype string + e.g., "forecaster", "transformer", "classifier", see BASE_CLASS_SCITYPE_LIST + which estimators are being retrieved and tested + fixture_sequence: list of str + sequence of fixture variable names in conditional fixture generation + _generate_[variable]: object methods, all (test_name: str, **kwargs) -> list + generating list of fixtures for fixture variable with name [variable] + to be used in test with name test_name + can optionally use values for fixtures earlier in fixture_sequence, + these must be input as kwargs in a call + is_excluded: static method (test_name: str, est: class) -> bool + whether test with name test_name should be excluded for estimator est + should be used only for encoding general rules, not individual skips + individual skips should go on the EXCLUDED_TESTS list in _config + requires _generate_object_class and _generate_object_instance as is + _excluded_scenario: static method (test_name: str, scenario) -> bool + whether scenario should be skipped in test with test_name test_name + requires _generate_estimator_scenario as is + + Fixtures parametrized + --------------------- + object_class: estimator inheriting from BaseObject + ranges over estimator classes not excluded by EXCLUDE_ESTIMATORS, EXCLUDED_TESTS + object_instance: instance of estimator inheriting from BaseObject + ranges over estimator classes not excluded by EXCLUDE_ESTIMATORS, EXCLUDED_TESTS + instances are generated by create_test_instance class method of object_class + """ + + # overrides object retrieval in scikit-base + def _all_objects(self): + """Retrieve list of all object classes of type self.object_type_filter. + + If self.object_type_filter is None, retrieve all objects. + If class, retrieve all classes inheriting from self.object_type_filter. + Otherwise (assumed str or list of str), retrieve all classes with tags + object_type in self.object_type_filter. + """ + filter = getattr(self, "object_type_filter", None) + + if isclass(filter): + object_types = filter.get_class_tag("object_type", None) + else: + object_types = filter + + obj_list = all_objects( + object_types=object_types, + return_names=False, + exclude_objects=self.exclude_objects, + ) + + if isclass(filter): + obj_list = [obj for obj in obj_list if issubclass(obj, filter)] + + # run_test_for_class selects the estimators to run + # based on whether they have changed, and whether they have all dependencies + # internally, uses the ONLY_CHANGED_MODULES flag, + # and checks the python env against python_dependencies tag + # obj_list = [obj for obj in obj_list if run_test_for_class(obj)] + + return obj_list + + # which sequence the conditional fixtures are generated in + fixture_sequence = [ + "object_class", + "object_instance", + ] + + +class TestAllPtForecasters(PackageConfig, BaseFixtureGenerator, _TestAllObjects): + """Generic tests for all objects in the mini package.""" + + def test_doctest_examples(self, object_class): + """Runs doctests for estimator class.""" + import doctest + + doctest.run_docstring_examples(object_class, globals()) From 02b0ce6fa53443044fffce8cbbce54a0c6d6b947 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Franz=20Kir=C3=A1ly?= Date: Sat, 22 Feb 2025 23:33:58 +0100 Subject: [PATCH 003/139] skeleton --- pytorch_forecasting/_registry/__init__.py | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/pytorch_forecasting/_registry/__init__.py b/pytorch_forecasting/_registry/__init__.py index bb0b88e61..f71836bfe 100644 --- a/pytorch_forecasting/_registry/__init__.py +++ b/pytorch_forecasting/_registry/__init__.py @@ -1,16 +1,5 @@ """PyTorch Forecasting registry.""" -from pytorch_forecasting._registry._lookup import all_objects, all_tags -from pytorch_forecasting._registry._tags import ( - OBJECT_TAG_LIST, - OBJECT_TAG_REGISTER, - check_tag_is_valid, -) +from pytorch_forecasting._registry._lookup import all_objects -__all__ = [ - "OBJECT_TAG_LIST", - "OBJECT_TAG_REGISTER", - "all_objects", - "all_tags", - "check_tag_is_valid", -] +__all__ = ["all_objects"] From 41cbf667f9aea5848c3390778a53612338319504 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Franz=20Kir=C3=A1ly?= Date: Sun, 23 Feb 2025 06:42:02 +0100 Subject: [PATCH 004/139] Update test_all_estimators.py --- pytorch_forecasting/tests/test_all_estimators.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/pytorch_forecasting/tests/test_all_estimators.py b/pytorch_forecasting/tests/test_all_estimators.py index c806691fa..704ddfc20 100644 --- a/pytorch_forecasting/tests/test_all_estimators.py +++ b/pytorch_forecasting/tests/test_all_estimators.py @@ -1,13 +1,15 @@ """Automated tests based on the skbase test suite template.""" + from inspect import isclass -from skbase.testing import BaseFixtureGenerator as _BaseFixtureGenerator -from skbase.testing import TestAllObjects as _TestAllObjects +from skbase.testing import ( + BaseFixtureGenerator as _BaseFixtureGenerator, + TestAllObjects as _TestAllObjects, +) from pytorch_forecasting._registry import all_objects from pytorch_forecasting.tests._config import EXCLUDE_ESTIMATORS, EXCLUDED_TESTS - # whether to test only estimators from modules that are changed w.r.t. main # default is False, can be set to True by pytest --only_changed_modules True flag ONLY_CHANGED_MODULES = False From cef62d36df5eceff5238a2d6c7fd829319028446 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Franz=20Kir=C3=A1ly?= Date: Sun, 23 Feb 2025 06:47:24 +0100 Subject: [PATCH 005/139] Update _base_object.py --- pytorch_forecasting/models/base/_base_object.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pytorch_forecasting/models/base/_base_object.py b/pytorch_forecasting/models/base/_base_object.py index 7330867b1..a91b10525 100644 --- a/pytorch_forecasting/models/base/_base_object.py +++ b/pytorch_forecasting/models/base/_base_object.py @@ -1,6 +1,8 @@ """Base Classes for pytorch-forecasting models, skbase compatible for indexing.""" -from skbase.base import BaseObject as _SkbaseBaseObject +from pytorch_forecasting.utils._dependencies import _safe_import + +_SkbaseBaseObject = _safe_import("skbase._base_object._BaseObject") class _BaseObject(_SkbaseBaseObject): From bc2e93b606095440772f7236eeebb070109c649f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Franz=20Kir=C3=A1ly?= Date: Sun, 23 Feb 2025 09:45:10 +0100 Subject: [PATCH 006/139] Update _lookup.py --- pytorch_forecasting/_registry/_lookup.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pytorch_forecasting/_registry/_lookup.py b/pytorch_forecasting/_registry/_lookup.py index ea3210cb0..517bfa9af 100644 --- a/pytorch_forecasting/_registry/_lookup.py +++ b/pytorch_forecasting/_registry/_lookup.py @@ -5,7 +5,6 @@ all_objects(object_types, filter_tags) lookup and filtering of objects """ -# copyright: skpro developers, BSD-3-Clause License (see LICENSE file) # based on the sktime module of same name __author__ = ["fkiraly"] From eee1c86859dc1d66d46eb85c7b39938639f8231e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Franz=20Kir=C3=A1ly?= Date: Sun, 23 Feb 2025 10:37:22 +0100 Subject: [PATCH 007/139] Update _lookup.py --- pytorch_forecasting/_registry/_lookup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pytorch_forecasting/_registry/_lookup.py b/pytorch_forecasting/_registry/_lookup.py index 517bfa9af..1ab4cfdb3 100644 --- a/pytorch_forecasting/_registry/_lookup.py +++ b/pytorch_forecasting/_registry/_lookup.py @@ -5,6 +5,7 @@ all_objects(object_types, filter_tags) lookup and filtering of objects """ + # based on the sktime module of same name __author__ = ["fkiraly"] From 164fe0d238ebe6b9f888c416f998a73948b365f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Franz=20Kir=C3=A1ly?= Date: Sun, 23 Feb 2025 13:05:54 +0100 Subject: [PATCH 008/139] base metadatda --- .../models/base/_base_object.py | 98 ++++++++++++++ .../models/deepar/_deepar_metadata.py | 128 ++++++++++++++++++ 2 files changed, 226 insertions(+) create mode 100644 pytorch_forecasting/models/deepar/_deepar_metadata.py diff --git a/pytorch_forecasting/models/base/_base_object.py b/pytorch_forecasting/models/base/_base_object.py index a91b10525..62fad456b 100644 --- a/pytorch_forecasting/models/base/_base_object.py +++ b/pytorch_forecasting/models/base/_base_object.py @@ -1,5 +1,7 @@ """Base Classes for pytorch-forecasting models, skbase compatible for indexing.""" +import inspect + from pytorch_forecasting.utils._dependencies import _safe_import _SkbaseBaseObject = _safe_import("skbase._base_object._BaseObject") @@ -8,3 +10,99 @@ class _BaseObject(_SkbaseBaseObject): pass + + +class _BasePtForecaster(_BaseObject): + """Base class for all PyTorch Forecasting forecaster metadata. + + This class points to model objects and contains metadata as tags. + """ + + _tags = { + "object_type": "forecaster_pytorch", + } + + @classmethod + def get_model_cls(cls): + """Get model class.""" + raise NotImplementedError + + + @classmethod + def create_test_instance(cls, parameter_set="default"): + """Construct an instance of the class, using first test parameter set. + + Parameters + ---------- + parameter_set : str, default="default" + Name of the set of test parameters to return, for use in tests. If no + special parameters are defined for a value, will return `"default"` set. + + Returns + ------- + instance : instance of the class with default parameters + + """ + if "parameter_set" in inspect.getfullargspec(cls.get_test_params).args: + params = cls.get_test_params(parameter_set=parameter_set) + else: + params = cls.get_test_params() + + if isinstance(params, list) and isinstance(params[0], dict): + params = params[0] + elif isinstance(params, dict): + pass + else: + raise TypeError( + "get_test_params should either return a dict or list of dict." + ) + + return cls.get_model_cls()(**params) + + @classmethod + def create_test_instances_and_names(cls, parameter_set="default"): + """Create list of all test instances and a list of names for them. + + Parameters + ---------- + parameter_set : str, default="default" + Name of the set of test parameters to return, for use in tests. If no + special parameters are defined for a value, will return `"default"` set. + + Returns + ------- + objs : list of instances of cls + i-th instance is ``cls(**cls.get_test_params()[i])`` + names : list of str, same length as objs + i-th element is name of i-th instance of obj in tests. + The naming convention is ``{cls.__name__}-{i}`` if more than one instance, + otherwise ``{cls.__name__}`` + """ + if "parameter_set" in inspect.getfullargspec(cls.get_test_params).args: + param_list = cls.get_test_params(parameter_set=parameter_set) + else: + param_list = cls.get_test_params() + + objs = [] + if not isinstance(param_list, (dict, list)): + raise RuntimeError( + f"Error in {cls.__name__}.get_test_params, " + "return must be param dict for class, or list thereof" + ) + if isinstance(param_list, dict): + param_list = [param_list] + for params in param_list: + if not isinstance(params, dict): + raise RuntimeError( + f"Error in {cls.__name__}.get_test_params, " + "return must be param dict for class, or list thereof" + ) + objs += [cls.get_model_cls()(**params)] + + num_instances = len(param_list) + if num_instances > 1: + names = [cls.__name__ + "-" + str(i) for i in range(num_instances)] + else: + names = [cls.__name__] + + return objs, names diff --git a/pytorch_forecasting/models/deepar/_deepar_metadata.py b/pytorch_forecasting/models/deepar/_deepar_metadata.py new file mode 100644 index 000000000..330b86e80 --- /dev/null +++ b/pytorch_forecasting/models/deepar/_deepar_metadata.py @@ -0,0 +1,128 @@ +"""DeepAR metadata container.""" + +from pytorch_forecasting.models.base._base_object import _BasePtForecaster + + +class _DeepARMetadata(_BasePtForecaster): + """DeepAR metadata container.""" + + _tags = { + "capability:exogenous": True, + "capability:multivariate": True, + "capability:pred_int": True, + "capability:flexible_history_length": True, + "capability:cold_start": False, + "info:compute": 3, + } + + @classmethod + def get_model_cls(cls): + """Get model class.""" + from pytorch_forecasting.models import DeepAR + + return DeepAR + + @classmethod + def get_test_params(cls, parameter_set="default"): + """Return testing parameter settings for the skbase object. + + ``get_test_params`` is a unified interface point to store + parameter settings for testing purposes. This function is also + used in ``create_test_instance`` and ``create_test_instances_and_names`` + to construct test instances. + + ``get_test_params`` should return a single ``dict``, or a ``list`` of ``dict``. + + Each ``dict`` is a parameter configuration for testing, + and can be used to construct an "interesting" test instance. + A call to ``cls(**params)`` should + be valid for all dictionaries ``params`` in the return of ``get_test_params``. + + The ``get_test_params`` need not return fixed lists of dictionaries, + it can also return dynamic or stochastic parameter settings. + + Parameters + ---------- + parameter_set : str, default="default" + Name of the set of test parameters to return, for use in tests. If no + special parameters are defined for a value, will return `"default"` set. + + Returns + ------- + params : dict or list of dict, default = {} + Parameters to create testing instances of the class + Each dict are parameters to construct an "interesting" test instance, i.e., + `MyClass(**params)` or `MyClass(**params[i])` creates a valid test instance. + `create_test_instance` uses the first (or only) dictionary in `params` + """ + from pytorch_forecasting.data.encoders import GroupNormalizer + from pytorch_forecasting.metrics import ( + BetaDistributionLoss, + ImplicitQuantileNetworkDistributionLoss, + LogNormalDistributionLoss, + MultivariateNormalDistributionLoss, + NegativeBinomialDistributionLoss, + ) + + return [ + {}, + {"cell_type": "GRU"}, + dict( + loss=LogNormalDistributionLoss(), + clip_target=True, + data_loader_kwargs=dict( + target_normalizer=GroupNormalizer( + groups=["agency", "sku"], transformation="log" + ) + ), + ), + dict( + loss=NegativeBinomialDistributionLoss(), + clip_target=False, + data_loader_kwargs=dict( + target_normalizer=GroupNormalizer( + groups=["agency", "sku"], center=False + ) + ), + ), + dict( + loss=BetaDistributionLoss(), + clip_target=True, + data_loader_kwargs=dict( + target_normalizer=GroupNormalizer( + groups=["agency", "sku"], transformation="logit" + ) + ), + ), + dict( + data_loader_kwargs=dict( + lags={"volume": [2, 5]}, + target="volume", + time_varying_unknown_reals=["volume"], + min_encoder_length=2, + ) + ), + dict( + data_loader_kwargs=dict( + time_varying_unknown_reals=["volume", "discount"], + target=["volume", "discount"], + lags={"volume": [2], "discount": [2]}, + ) + ), + dict( + loss=ImplicitQuantileNetworkDistributionLoss(hidden_size=8), + ), + dict( + loss=MultivariateNormalDistributionLoss(), + trainer_kwargs=dict(accelerator="cpu"), + ), + dict( + loss=MultivariateNormalDistributionLoss(), + data_loader_kwargs=dict( + target_normalizer=GroupNormalizer( + groups=["agency", "sku"], transformation="log1p" + ) + ), + trainer_kwargs=dict(accelerator="cpu"), + ), + ] From 20e88d09993f3fed62ab52f93d0a4678f1a0c068 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Franz=20Kir=C3=A1ly?= Date: Sun, 23 Feb 2025 13:21:34 +0100 Subject: [PATCH 009/139] registry --- pytorch_forecasting/_registry/_lookup.py | 18 +++--------------- pytorch_forecasting/models/base/__init__.py | 6 +++++- .../models/base/_base_object.py | 2 +- .../utils/_dependencies/_safe_import.py | 3 +++ 4 files changed, 12 insertions(+), 17 deletions(-) diff --git a/pytorch_forecasting/_registry/_lookup.py b/pytorch_forecasting/_registry/_lookup.py index 1ab4cfdb3..828c448b1 100644 --- a/pytorch_forecasting/_registry/_lookup.py +++ b/pytorch_forecasting/_registry/_lookup.py @@ -11,12 +11,8 @@ __author__ = ["fkiraly"] # all_objects is based on the sklearn utility all_estimators - -from copy import deepcopy -from operator import itemgetter from pathlib import Path -import pandas as pd from skbase.lookup import all_objects as _all_objects from pytorch_forecasting.models.base import _BaseObject @@ -117,17 +113,9 @@ def all_objects( Examples -------- - >>> from skpro.registry import all_objects + >>> from pytorch_forecasting._registry import all_objects >>> # return a complete list of objects as pd.Dataframe >>> all_objects(as_dataframe=True) # doctest: +SKIP - >>> # return all probabilistic regressors by filtering for object type - >>> all_objects("regressor_proba", as_dataframe=True) # doctest: +SKIP - >>> # return all regressors which handle missing data in the input by tag filtering - >>> all_objects( - ... "regressor_proba", - ... filter_tags={"capability:missing": True}, - ... as_dataframe=True - ... ) # doctest: +SKIP References ---------- @@ -143,7 +131,7 @@ def all_objects( ) result = [] - ROOT = str(Path(__file__).parent.parent) # skpro package root directory + ROOT = str(Path(__file__).parent.parent) # package root directory if isinstance(filter_tags, str): filter_tags = {filter_tags: True} @@ -173,7 +161,7 @@ def all_objects( as_dataframe=as_dataframe, return_tags=return_tags, suppress_import_stdout=suppress_import_stdout, - package_name="skpro", + package_name="pytorch_forecasting", path=ROOT, modules_to_ignore=MODULES_TO_IGNORE, ) diff --git a/pytorch_forecasting/models/base/__init__.py b/pytorch_forecasting/models/base/__init__.py index 474e7d564..7b69ec246 100644 --- a/pytorch_forecasting/models/base/__init__.py +++ b/pytorch_forecasting/models/base/__init__.py @@ -7,10 +7,14 @@ BaseModelWithCovariates, Prediction, ) -from pytorch_forecasting.models.base._base_object import _BaseObject +from pytorch_forecasting.models.base._base_object import ( + _BaseObject, + _BasePtForecaster, +) __all__ = [ "_BaseObject", + "_BasePtForecaster", "AutoRegressiveBaseModel", "AutoRegressiveBaseModelWithCovariates", "BaseModel", diff --git a/pytorch_forecasting/models/base/_base_object.py b/pytorch_forecasting/models/base/_base_object.py index 62fad456b..8895b4c2c 100644 --- a/pytorch_forecasting/models/base/_base_object.py +++ b/pytorch_forecasting/models/base/_base_object.py @@ -4,7 +4,7 @@ from pytorch_forecasting.utils._dependencies import _safe_import -_SkbaseBaseObject = _safe_import("skbase._base_object._BaseObject") +_SkbaseBaseObject = _safe_import("skbase.base.BaseObject", pkg_name="scikit-base") class _BaseObject(_SkbaseBaseObject): diff --git a/pytorch_forecasting/utils/_dependencies/_safe_import.py b/pytorch_forecasting/utils/_dependencies/_safe_import.py index f4805f9c1..ffbde8b5d 100644 --- a/pytorch_forecasting/utils/_dependencies/_safe_import.py +++ b/pytorch_forecasting/utils/_dependencies/_safe_import.py @@ -70,6 +70,9 @@ def _safe_import(import_path, pkg_name=None): if pkg_name is None: path_list = import_path.split(".") pkg_name = path_list[0] + else: + path_list = import_path.split(".") + path_list = [pkg_name] + path_list[1:] if pkg_name in _get_installed_packages(): try: From 318c1fbdbfface24fdc67568cf9a01a6dde1650c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Franz=20Kir=C3=A1ly?= Date: Sun, 23 Feb 2025 13:33:42 +0100 Subject: [PATCH 010/139] fix private name --- pytorch_forecasting/models/deepar/__init__.py | 3 ++- pytorch_forecasting/models/deepar/_deepar_metadata.py | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/pytorch_forecasting/models/deepar/__init__.py b/pytorch_forecasting/models/deepar/__init__.py index 679f296f6..149e19e0d 100644 --- a/pytorch_forecasting/models/deepar/__init__.py +++ b/pytorch_forecasting/models/deepar/__init__.py @@ -1,5 +1,6 @@ """DeepAR: Probabilistic forecasting with autoregressive recurrent networks.""" from pytorch_forecasting.models.deepar._deepar import DeepAR +from pytorch_forecasting.models.deepar._deepar_metadata import DeepARMetadata -__all__ = ["DeepAR"] +__all__ = ["DeepAR", "DeepARMetadata"] diff --git a/pytorch_forecasting/models/deepar/_deepar_metadata.py b/pytorch_forecasting/models/deepar/_deepar_metadata.py index 330b86e80..89aefc1b0 100644 --- a/pytorch_forecasting/models/deepar/_deepar_metadata.py +++ b/pytorch_forecasting/models/deepar/_deepar_metadata.py @@ -3,7 +3,7 @@ from pytorch_forecasting.models.base._base_object import _BasePtForecaster -class _DeepARMetadata(_BasePtForecaster): +class DeepARMetadata(_BasePtForecaster): """DeepAR metadata container.""" _tags = { From 012ab3d78ed8a99e6920f3df704188834bbe1c14 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Franz=20Kir=C3=A1ly?= Date: Sun, 23 Feb 2025 13:48:02 +0100 Subject: [PATCH 011/139] Update _base_object.py --- pytorch_forecasting/models/base/_base_object.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pytorch_forecasting/models/base/_base_object.py b/pytorch_forecasting/models/base/_base_object.py index 8895b4c2c..4fcb1bd22 100644 --- a/pytorch_forecasting/models/base/_base_object.py +++ b/pytorch_forecasting/models/base/_base_object.py @@ -27,7 +27,6 @@ def get_model_cls(cls): """Get model class.""" raise NotImplementedError - @classmethod def create_test_instance(cls, parameter_set="default"): """Construct an instance of the class, using first test parameter set. From 86365a00d88cda407674dbcac5c4d53bd26f3fce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Franz=20Kir=C3=A1ly?= Date: Sun, 23 Feb 2025 13:57:16 +0100 Subject: [PATCH 012/139] test failure --- pytorch_forecasting/tests/_conftest.py | 262 ++++++++++++++++++ .../tests/test_all_estimators.py | 101 +++++++ 2 files changed, 363 insertions(+) create mode 100644 pytorch_forecasting/tests/_conftest.py diff --git a/pytorch_forecasting/tests/_conftest.py b/pytorch_forecasting/tests/_conftest.py new file mode 100644 index 000000000..e276446a6 --- /dev/null +++ b/pytorch_forecasting/tests/_conftest.py @@ -0,0 +1,262 @@ +import numpy as np +import pytest +import torch + +from pytorch_forecasting import TimeSeriesDataSet +from pytorch_forecasting.data import EncoderNormalizer, GroupNormalizer, NaNLabelEncoder +from pytorch_forecasting.data.examples import generate_ar_data, get_stallion_data + +torch.manual_seed(23) + + +@pytest.fixture(scope="session") +def gpus(): + if torch.cuda.is_available(): + return [0] + else: + return 0 + + +@pytest.fixture(scope="session") +def data_with_covariates(): + data = get_stallion_data() + data["month"] = data.date.dt.month.astype(str) + data["log_volume"] = np.log1p(data.volume) + data["weight"] = 1 + np.sqrt(data.volume) + + data["time_idx"] = data["date"].dt.year * 12 + data["date"].dt.month + data["time_idx"] -= data["time_idx"].min() + + # convert special days into strings + special_days = [ + "easter_day", + "good_friday", + "new_year", + "christmas", + "labor_day", + "independence_day", + "revolution_day_memorial", + "regional_games", + "fifa_u_17_world_cup", + "football_gold_cup", + "beer_capital", + "music_fest", + ] + data[special_days] = ( + data[special_days].apply(lambda x: x.map({0: "", 1: x.name})).astype("category") + ) + data = data.astype(dict(industry_volume=float)) + + # select data subset + data = data[lambda x: x.sku.isin(data.sku.unique()[:2])][ + lambda x: x.agency.isin(data.agency.unique()[:2]) + ] + + # default target + data["target"] = data["volume"].clip(1e-3, 1.0) + + return data + + +def make_dataloaders(data_with_covariates, **kwargs): + training_cutoff = "2016-09-01" + max_encoder_length = 4 + max_prediction_length = 3 + + kwargs.setdefault("target", "volume") + kwargs.setdefault("group_ids", ["agency", "sku"]) + kwargs.setdefault("add_relative_time_idx", True) + kwargs.setdefault("time_varying_unknown_reals", ["volume"]) + + training = TimeSeriesDataSet( + data_with_covariates[lambda x: x.date < training_cutoff].copy(), + time_idx="time_idx", + max_encoder_length=max_encoder_length, + max_prediction_length=max_prediction_length, + **kwargs, # fixture parametrization + ) + + validation = TimeSeriesDataSet.from_dataset( + training, + data_with_covariates.copy(), + min_prediction_idx=training.index.time.max() + 1, + ) + train_dataloader = training.to_dataloader(train=True, batch_size=2, num_workers=0) + val_dataloader = validation.to_dataloader(train=False, batch_size=2, num_workers=0) + test_dataloader = validation.to_dataloader(train=False, batch_size=1, num_workers=0) + + return dict(train=train_dataloader, val=val_dataloader, test=test_dataloader) + + +@pytest.fixture( + params=[ + dict(), + dict( + static_categoricals=["agency", "sku"], + static_reals=["avg_population_2017", "avg_yearly_household_income_2017"], + time_varying_known_categoricals=["special_days", "month"], + variable_groups=dict( + special_days=[ + "easter_day", + "good_friday", + "new_year", + "christmas", + "labor_day", + "independence_day", + "revolution_day_memorial", + "regional_games", + "fifa_u_17_world_cup", + "football_gold_cup", + "beer_capital", + "music_fest", + ] + ), + time_varying_known_reals=[ + "time_idx", + "price_regular", + "price_actual", + "discount", + "discount_in_percent", + ], + time_varying_unknown_categoricals=[], + time_varying_unknown_reals=[ + "volume", + "log_volume", + "industry_volume", + "soda_volume", + "avg_max_temp", + ], + constant_fill_strategy={"volume": 0}, + categorical_encoders={"sku": NaNLabelEncoder(add_nan=True)}, + ), + dict(static_categoricals=["agency", "sku"]), + dict(randomize_length=True, min_encoder_length=2), + dict(target_normalizer=EncoderNormalizer(), min_encoder_length=2), + dict(target_normalizer=GroupNormalizer(transformation="log1p")), + dict( + target_normalizer=GroupNormalizer( + groups=["agency", "sku"], transformation="softplus", center=False + ) + ), + dict(target="agency"), + # test multiple targets + dict(target=["industry_volume", "volume"]), + dict(target=["agency", "volume"]), + dict( + target=["agency", "volume"], min_encoder_length=1, min_prediction_length=1 + ), + dict(target=["agency", "volume"], weight="volume"), + # test weights + dict(target="volume", weight="volume"), + ], + scope="session", +) +def multiple_dataloaders_with_covariates(data_with_covariates, request): + return make_dataloaders(data_with_covariates, **request.param) + + +@pytest.fixture(scope="session") +def dataloaders_with_different_encoder_decoder_length(data_with_covariates): + return make_dataloaders( + data_with_covariates.copy(), + target="target", + time_varying_known_categoricals=["special_days", "month"], + variable_groups=dict( + special_days=[ + "easter_day", + "good_friday", + "new_year", + "christmas", + "labor_day", + "independence_day", + "revolution_day_memorial", + "regional_games", + "fifa_u_17_world_cup", + "football_gold_cup", + "beer_capital", + "music_fest", + ] + ), + time_varying_known_reals=[ + "time_idx", + "price_regular", + "price_actual", + "discount", + "discount_in_percent", + ], + time_varying_unknown_categoricals=[], + time_varying_unknown_reals=[ + "target", + "volume", + "log_volume", + "industry_volume", + "soda_volume", + "avg_max_temp", + ], + static_categoricals=["agency"], + add_relative_time_idx=False, + target_normalizer=GroupNormalizer(groups=["agency", "sku"], center=False), + ) + + +@pytest.fixture(scope="session") +def dataloaders_with_covariates(data_with_covariates): + return make_dataloaders( + data_with_covariates.copy(), + target="target", + time_varying_known_reals=["discount"], + time_varying_unknown_reals=["target"], + static_categoricals=["agency"], + add_relative_time_idx=False, + target_normalizer=GroupNormalizer(groups=["agency", "sku"], center=False), + ) + + +@pytest.fixture(scope="session") +def dataloaders_multi_target(data_with_covariates): + return make_dataloaders( + data_with_covariates.copy(), + time_varying_unknown_reals=["target", "discount"], + target=["target", "discount"], + add_relative_time_idx=False, + ) + + +@pytest.fixture(scope="session") +def dataloaders_fixed_window_without_covariates(): + data = generate_ar_data(seasonality=10.0, timesteps=50, n_series=2) + validation = data.series.iloc[:2] + + max_encoder_length = 30 + max_prediction_length = 10 + + training = TimeSeriesDataSet( + data[lambda x: ~x.series.isin(validation)], + time_idx="time_idx", + target="value", + categorical_encoders={"series": NaNLabelEncoder().fit(data.series)}, + group_ids=["series"], + static_categoricals=[], + max_encoder_length=max_encoder_length, + max_prediction_length=max_prediction_length, + time_varying_unknown_reals=["value"], + target_normalizer=EncoderNormalizer(), + ) + + validation = TimeSeriesDataSet.from_dataset( + training, + data[lambda x: x.series.isin(validation)], + stop_randomization=True, + ) + batch_size = 2 + train_dataloader = training.to_dataloader( + train=True, batch_size=batch_size, num_workers=0 + ) + val_dataloader = validation.to_dataloader( + train=False, batch_size=batch_size, num_workers=0 + ) + test_dataloader = validation.to_dataloader( + train=False, batch_size=batch_size, num_workers=0 + ) + + return dict(train=train_dataloader, val=val_dataloader, test=test_dataloader) diff --git a/pytorch_forecasting/tests/test_all_estimators.py b/pytorch_forecasting/tests/test_all_estimators.py index 704ddfc20..609761e21 100644 --- a/pytorch_forecasting/tests/test_all_estimators.py +++ b/pytorch_forecasting/tests/test_all_estimators.py @@ -1,7 +1,11 @@ """Automated tests based on the skbase test suite template.""" from inspect import isclass +import shutil +import lightning.pytorch as pl +from lightning.pytorch.callbacks import EarlyStopping +from lightning.pytorch.loggers import TensorBoardLogger from skbase.testing import ( BaseFixtureGenerator as _BaseFixtureGenerator, TestAllObjects as _TestAllObjects, @@ -9,6 +13,7 @@ from pytorch_forecasting._registry import all_objects from pytorch_forecasting.tests._config import EXCLUDE_ESTIMATORS, EXCLUDED_TESTS +from pytorch_forecasting.tests._conftest import make_dataloaders # whether to test only estimators from modules that are changed w.r.t. main # default is False, can be set to True by pytest --only_changed_modules True flag @@ -110,6 +115,98 @@ def _all_objects(self): ] +def _integration( + data_with_covariates, + tmp_path, + cell_type="LSTM", + data_loader_kwargs={}, + clip_target: bool = False, + trainer_kwargs=None, + **kwargs, +): + data_with_covariates = data_with_covariates.copy() + if clip_target: + data_with_covariates["target"] = data_with_covariates["volume"].clip(1e-3, 1.0) + else: + data_with_covariates["target"] = data_with_covariates["volume"] + data_loader_default_kwargs = dict( + target="target", + time_varying_known_reals=["price_actual"], + time_varying_unknown_reals=["target"], + static_categoricals=["agency"], + add_relative_time_idx=True, + ) + data_loader_default_kwargs.update(data_loader_kwargs) + dataloaders_with_covariates = make_dataloaders( + data_with_covariates, **data_loader_default_kwargs + ) + + train_dataloader = dataloaders_with_covariates["train"] + val_dataloader = dataloaders_with_covariates["val"] + test_dataloader = dataloaders_with_covariates["test"] + + early_stop_callback = EarlyStopping( + monitor="val_loss", min_delta=1e-4, patience=1, verbose=False, mode="min" + ) + + logger = TensorBoardLogger(tmp_path) + if trainer_kwargs is None: + trainer_kwargs = {} + trainer = pl.Trainer( + max_epochs=3, + gradient_clip_val=0.1, + callbacks=[early_stop_callback], + enable_checkpointing=True, + default_root_dir=tmp_path, + limit_train_batches=2, + limit_val_batches=2, + limit_test_batches=2, + logger=logger, + **trainer_kwargs, + ) + + net = DeepAR.from_dataset( + train_dataloader.dataset, + hidden_size=5, + cell_type=cell_type, + learning_rate=0.01, + log_gradient_flow=True, + log_interval=1000, + n_plotting_samples=100, + **kwargs, + ) + net.size() + try: + trainer.fit( + net, + train_dataloaders=train_dataloader, + val_dataloaders=val_dataloader, + ) + test_outputs = trainer.test(net, dataloaders=test_dataloader) + assert len(test_outputs) > 0 + # check loading + net = DeepAR.load_from_checkpoint(trainer.checkpoint_callback.best_model_path) + + # check prediction + net.predict( + val_dataloader, + fast_dev_run=True, + return_index=True, + return_decoder_lengths=True, + trainer_kwargs=trainer_kwargs, + ) + finally: + shutil.rmtree(tmp_path, ignore_errors=True) + + net.predict( + val_dataloader, + fast_dev_run=True, + return_index=True, + return_decoder_lengths=True, + trainer_kwargs=trainer_kwargs, + ) + + class TestAllPtForecasters(PackageConfig, BaseFixtureGenerator, _TestAllObjects): """Generic tests for all objects in the mini package.""" @@ -118,3 +215,7 @@ def test_doctest_examples(self, object_class): import doctest doctest.run_docstring_examples(object_class, globals()) + + def certain_failure(self, object_class): + """Fails for certain, for testing.""" + assert False From f6dee46efaa6853afa299d5edca80d11a80367ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Franz=20Kir=C3=A1ly?= Date: Sun, 23 Feb 2025 13:59:52 +0100 Subject: [PATCH 013/139] Update test_all_estimators.py --- pytorch_forecasting/tests/test_all_estimators.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pytorch_forecasting/tests/test_all_estimators.py b/pytorch_forecasting/tests/test_all_estimators.py index 609761e21..a5f2c3783 100644 --- a/pytorch_forecasting/tests/test_all_estimators.py +++ b/pytorch_forecasting/tests/test_all_estimators.py @@ -116,6 +116,7 @@ def _all_objects(self): def _integration( + estimator_cls, data_with_covariates, tmp_path, cell_type="LSTM", @@ -165,7 +166,7 @@ def _integration( **trainer_kwargs, ) - net = DeepAR.from_dataset( + net = estimator_cls.from_dataset( train_dataloader.dataset, hidden_size=5, cell_type=cell_type, @@ -185,7 +186,7 @@ def _integration( test_outputs = trainer.test(net, dataloaders=test_dataloader) assert len(test_outputs) > 0 # check loading - net = DeepAR.load_from_checkpoint(trainer.checkpoint_callback.best_model_path) + net = estimator_cls.load_from_checkpoint(trainer.checkpoint_callback.best_model_path) # check prediction net.predict( From 9b0e4ec4c7d47dc0115a87ea4297a22a2f0fe5eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Franz=20Kir=C3=A1ly?= Date: Sun, 23 Feb 2025 15:51:07 +0100 Subject: [PATCH 014/139] Update test_all_estimators.py --- pytorch_forecasting/tests/test_all_estimators.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/pytorch_forecasting/tests/test_all_estimators.py b/pytorch_forecasting/tests/test_all_estimators.py index a5f2c3783..2934d42db 100644 --- a/pytorch_forecasting/tests/test_all_estimators.py +++ b/pytorch_forecasting/tests/test_all_estimators.py @@ -116,7 +116,7 @@ def _all_objects(self): def _integration( - estimator_cls, + estimator_cls, data_with_covariates, tmp_path, cell_type="LSTM", @@ -186,7 +186,9 @@ def _integration( test_outputs = trainer.test(net, dataloaders=test_dataloader) assert len(test_outputs) > 0 # check loading - net = estimator_cls.load_from_checkpoint(trainer.checkpoint_callback.best_model_path) + net = estimator_cls.load_from_checkpoint( + trainer.checkpoint_callback.best_model_path + ) # check prediction net.predict( From 7de528537d6fe36dd554f3cad5550d6f66c512e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Franz=20Kir=C3=A1ly?= Date: Sun, 23 Feb 2025 16:02:28 +0100 Subject: [PATCH 015/139] Update test_all_estimators.py --- pytorch_forecasting/tests/test_all_estimators.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pytorch_forecasting/tests/test_all_estimators.py b/pytorch_forecasting/tests/test_all_estimators.py index 2934d42db..37b597712 100644 --- a/pytorch_forecasting/tests/test_all_estimators.py +++ b/pytorch_forecasting/tests/test_all_estimators.py @@ -28,7 +28,7 @@ class PackageConfig: # package to search for objects # expected type: str, package/module name, relative to python environment root - package_name = "pytroch_forecasting" + package_name = "pytorch_forecasting" # list of object types (class names) to exclude # expected type: list of str, str are class names From 57dfe3a4e47ac3a34199d787cc6282f43b18a9f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Franz=20Kir=C3=A1ly?= Date: Sun, 23 Feb 2025 16:42:01 +0100 Subject: [PATCH 016/139] test folders --- pytest.ini | 4 +- .../tests/test_all_estimators.py | 43 ++++++++++++++++--- 2 files changed, 40 insertions(+), 7 deletions(-) diff --git a/pytest.ini b/pytest.ini index 457863f87..52f4fa1c1 100644 --- a/pytest.ini +++ b/pytest.ini @@ -10,7 +10,9 @@ addopts = --no-cov-on-fail markers = -testpaths = tests/ +testpaths = + tests/ + pytorch_forecasting/tests/ log_cli_level = ERROR log_format = %(asctime)s %(levelname)s %(message)s log_date_format = %Y-%m-%d %H:%M:%S diff --git a/pytorch_forecasting/tests/test_all_estimators.py b/pytorch_forecasting/tests/test_all_estimators.py index 37b597712..8fbcc6ffe 100644 --- a/pytorch_forecasting/tests/test_all_estimators.py +++ b/pytorch_forecasting/tests/test_all_estimators.py @@ -6,10 +6,8 @@ import lightning.pytorch as pl from lightning.pytorch.callbacks import EarlyStopping from lightning.pytorch.loggers import TensorBoardLogger -from skbase.testing import ( - BaseFixtureGenerator as _BaseFixtureGenerator, - TestAllObjects as _TestAllObjects, -) +import pytest +from skbase.testing import BaseFixtureGenerator as _BaseFixtureGenerator from pytorch_forecasting._registry import all_objects from pytorch_forecasting.tests._config import EXCLUDE_ESTIMATORS, EXCLUDED_TESTS @@ -110,10 +108,43 @@ def _all_objects(self): # which sequence the conditional fixtures are generated in fixture_sequence = [ + "object_metadata", "object_class", "object_instance", ] + def _generate_object_metadata(self, test_name, **kwargs): + """Return object class fixtures. + + Fixtures parametrized + --------------------- + object_class: object inheriting from BaseObject + ranges over all object classes not excluded by self.excluded_tests + """ + object_classes_to_test = [ + est for est in self._all_objects() if not self.is_excluded(test_name, est) + ] + object_names = [est.__name__ for est in object_classes_to_test] + + return object_classes_to_test, object_names + + def _generate_object_class(self, test_name, **kwargs): + """Return object class fixtures. + + Fixtures parametrized + --------------------- + object_class: object inheriting from BaseObject + ranges over all object classes not excluded by self.excluded_tests + """ + all_metadata = self._all_objects() + all_cls = [est.get_model_cls() for est in all_metadata] + object_classes_to_test = [ + est for est in all_cls if not self.is_excluded(test_name, est) + ] + object_names = [est.__name__ for est in object_classes_to_test] + + return object_classes_to_test, object_names + def _integration( estimator_cls, @@ -210,7 +241,7 @@ def _integration( ) -class TestAllPtForecasters(PackageConfig, BaseFixtureGenerator, _TestAllObjects): +class TestAllPtForecasters(PackageConfig, BaseFixtureGenerator): """Generic tests for all objects in the mini package.""" def test_doctest_examples(self, object_class): @@ -219,6 +250,6 @@ def test_doctest_examples(self, object_class): doctest.run_docstring_examples(object_class, globals()) - def certain_failure(self, object_class): + def test_certain_failure(self, object_class): """Fails for certain, for testing.""" assert False From c9f12dbdeea4aa431b52620b226cd57193a9a249 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Franz=20Kir=C3=A1ly?= Date: Sun, 23 Feb 2025 16:59:30 +0100 Subject: [PATCH 017/139] Update test.yml --- .github/workflows/test.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 5fcd9c1ff..0083302dd 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -71,7 +71,7 @@ jobs: - name: Run pytest shell: bash - run: python -m pytest tests + run: python -m pytest pytest: name: Run pytest @@ -110,7 +110,7 @@ jobs: - name: Run pytest shell: bash - run: python -m pytest tests + run: python -m pytest - name: Statistics run: | From fa8144ebae6312d34458a2464da2d1bc3f186754 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Franz=20Kir=C3=A1ly?= Date: Sun, 23 Feb 2025 18:56:39 +0100 Subject: [PATCH 018/139] test integration --- .../models/deepar/_deepar_metadata.py | 25 +---------- .../tests/test_all_estimators.py | 41 ++++++++++++++++++- 2 files changed, 41 insertions(+), 25 deletions(-) diff --git a/pytorch_forecasting/models/deepar/_deepar_metadata.py b/pytorch_forecasting/models/deepar/_deepar_metadata.py index 89aefc1b0..206f113f0 100644 --- a/pytorch_forecasting/models/deepar/_deepar_metadata.py +++ b/pytorch_forecasting/models/deepar/_deepar_metadata.py @@ -23,29 +23,8 @@ def get_model_cls(cls): return DeepAR @classmethod - def get_test_params(cls, parameter_set="default"): - """Return testing parameter settings for the skbase object. - - ``get_test_params`` is a unified interface point to store - parameter settings for testing purposes. This function is also - used in ``create_test_instance`` and ``create_test_instances_and_names`` - to construct test instances. - - ``get_test_params`` should return a single ``dict``, or a ``list`` of ``dict``. - - Each ``dict`` is a parameter configuration for testing, - and can be used to construct an "interesting" test instance. - A call to ``cls(**params)`` should - be valid for all dictionaries ``params`` in the return of ``get_test_params``. - - The ``get_test_params`` need not return fixed lists of dictionaries, - it can also return dynamic or stochastic parameter settings. - - Parameters - ---------- - parameter_set : str, default="default" - Name of the set of test parameters to return, for use in tests. If no - special parameters are defined for a value, will return `"default"` set. + def get_test_train_params(cls): + """Return testing parameter settings for the trainer. Returns ------- diff --git a/pytorch_forecasting/tests/test_all_estimators.py b/pytorch_forecasting/tests/test_all_estimators.py index 8fbcc6ffe..378316d7a 100644 --- a/pytorch_forecasting/tests/test_all_estimators.py +++ b/pytorch_forecasting/tests/test_all_estimators.py @@ -71,6 +71,8 @@ class BaseFixtureGenerator(_BaseFixtureGenerator): object_instance: instance of estimator inheriting from BaseObject ranges over estimator classes not excluded by EXCLUDE_ESTIMATORS, EXCLUDED_TESTS instances are generated by create_test_instance class method of object_class + trainer_kwargs: list of dict + ranges over dictionaries of kwargs for the trainer """ # overrides object retrieval in scikit-base @@ -111,6 +113,7 @@ def _all_objects(self): "object_metadata", "object_class", "object_instance", + "trainer_kwargs", ] def _generate_object_metadata(self, test_name, **kwargs): @@ -145,6 +148,30 @@ def _generate_object_class(self, test_name, **kwargs): return object_classes_to_test, object_names + def _generate_trainer_kwargs(self, test_name, **kwargs): + """Return kwargs for the trainer. + + Fixtures parametrized + --------------------- + trainer_kwargs: dict + ranges over all kwargs for the trainer + """ + # call _generate_object_class to get all the classes + object_meta_to_test, _ = self._generate_object_metadata(test_name=test_name) + + # create instances from the classes + train_kwargs_to_test = [] + train_kwargs_names = [] + # retrieve all object parameters if multiple, construct instances + for est in object_meta_to_test: + est_name = est.__name__ + all_train_kwargs = est.get_test_train_params() + train_kwargs_to_test += all_train_kwargs + rg = range(len(all_train_kwargs)) + train_kwargs_names += [f"{est_name}_{i}" for i in rg] + + return train_kwargs_to_test, train_kwargs_names + def _integration( estimator_cls, @@ -250,6 +277,16 @@ def test_doctest_examples(self, object_class): doctest.run_docstring_examples(object_class, globals()) - def test_certain_failure(self, object_class): + def test_integration( + self, object_class, trainer_kwargs, data_with_covariates, tmp_path + ): """Fails for certain, for testing.""" - assert False + from pytorch_forecasting.metrics import NegativeBinomialDistributionLoss + + if "loss" in trainer_kwargs and isinstance( + trainer_kwargs["loss"], NegativeBinomialDistributionLoss + ): + data_with_covariates = data_with_covariates.assign( + volume=lambda x: x.volume.round() + ) + _integration(object_class, data_with_covariates, tmp_path, **trainer_kwargs) From 232a510bc6820786ef1ce46ab3115a04126054ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Franz=20Kir=C3=A1ly?= Date: Sun, 23 Feb 2025 19:07:35 +0100 Subject: [PATCH 019/139] fixes --- .../models/base/_base_object.py | 8 +++++ .../models/deepar/_deepar_metadata.py | 4 ++- .../tests/test_all_estimators.py | 31 ++++++++++--------- 3 files changed, 27 insertions(+), 16 deletions(-) diff --git a/pytorch_forecasting/models/base/_base_object.py b/pytorch_forecasting/models/base/_base_object.py index 4fcb1bd22..7fd59d6a4 100644 --- a/pytorch_forecasting/models/base/_base_object.py +++ b/pytorch_forecasting/models/base/_base_object.py @@ -27,6 +27,14 @@ def get_model_cls(cls): """Get model class.""" raise NotImplementedError + @classmethod + def name(cls): + """Get model name.""" + name = cls.get_class_tags().get("info:name", None) + if name is None: + name = cls.get_model_cls().__name__ + return name + @classmethod def create_test_instance(cls, parameter_set="default"): """Construct an instance of the class, using first test parameter set. diff --git a/pytorch_forecasting/models/deepar/_deepar_metadata.py b/pytorch_forecasting/models/deepar/_deepar_metadata.py index 206f113f0..e477d63b0 100644 --- a/pytorch_forecasting/models/deepar/_deepar_metadata.py +++ b/pytorch_forecasting/models/deepar/_deepar_metadata.py @@ -7,12 +7,14 @@ class DeepARMetadata(_BasePtForecaster): """DeepAR metadata container.""" _tags = { + "info:name": "DeepAR", + "info:compute": 3, + "authors": ["jdb78"], "capability:exogenous": True, "capability:multivariate": True, "capability:pred_int": True, "capability:flexible_history_length": True, "capability:cold_start": False, - "info:compute": 3, } @classmethod diff --git a/pytorch_forecasting/tests/test_all_estimators.py b/pytorch_forecasting/tests/test_all_estimators.py index 378316d7a..c2d86e40e 100644 --- a/pytorch_forecasting/tests/test_all_estimators.py +++ b/pytorch_forecasting/tests/test_all_estimators.py @@ -127,7 +127,7 @@ def _generate_object_metadata(self, test_name, **kwargs): object_classes_to_test = [ est for est in self._all_objects() if not self.is_excluded(test_name, est) ] - object_names = [est.__name__ for est in object_classes_to_test] + object_names = [est.name() for est in object_classes_to_test] return object_classes_to_test, object_names @@ -156,21 +156,16 @@ def _generate_trainer_kwargs(self, test_name, **kwargs): trainer_kwargs: dict ranges over all kwargs for the trainer """ - # call _generate_object_class to get all the classes - object_meta_to_test, _ = self._generate_object_metadata(test_name=test_name) + if "object_metadata" in kwargs.keys(): + obj_meta = kwargs["object_metadata"] + else: + return [] - # create instances from the classes - train_kwargs_to_test = [] - train_kwargs_names = [] - # retrieve all object parameters if multiple, construct instances - for est in object_meta_to_test: - est_name = est.__name__ - all_train_kwargs = est.get_test_train_params() - train_kwargs_to_test += all_train_kwargs - rg = range(len(all_train_kwargs)) - train_kwargs_names += [f"{est_name}_{i}" for i in rg] + all_train_kwargs = obj_meta.get_test_train_params() + rg = range(len(all_train_kwargs)) + train_kwargs_names = [str(i) for i in rg] - return train_kwargs_to_test, train_kwargs_names + return all_train_kwargs, train_kwargs_names def _integration( @@ -278,11 +273,17 @@ def test_doctest_examples(self, object_class): doctest.run_docstring_examples(object_class, globals()) def test_integration( - self, object_class, trainer_kwargs, data_with_covariates, tmp_path + self, + object_metadata, + trainer_kwargs, + data_with_covariates, + tmp_path, ): """Fails for certain, for testing.""" from pytorch_forecasting.metrics import NegativeBinomialDistributionLoss + object_class = object_metadata.get_model_cls() + if "loss" in trainer_kwargs and isinstance( trainer_kwargs["loss"], NegativeBinomialDistributionLoss ): From 1c8d4b5c4fbf8dca91ec28e36e8781bb08a291bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Franz=20Kir=C3=A1ly?= Date: Sun, 23 Feb 2025 19:30:39 +0100 Subject: [PATCH 020/139] Update _conftest.py --- pytorch_forecasting/tests/_conftest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pytorch_forecasting/tests/_conftest.py b/pytorch_forecasting/tests/_conftest.py index e276446a6..20cad22c5 100644 --- a/pytorch_forecasting/tests/_conftest.py +++ b/pytorch_forecasting/tests/_conftest.py @@ -17,7 +17,7 @@ def gpus(): return 0 -@pytest.fixture(scope="session") +@pytest.fixture(scope="package") def data_with_covariates(): data = get_stallion_data() data["month"] = data.date.dt.month.astype(str) From f632e32325a657fe975f9c76344eaba0585e17e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Franz=20Kir=C3=A1ly?= Date: Sun, 23 Feb 2025 19:37:01 +0100 Subject: [PATCH 021/139] try scenarios --- pytorch_forecasting/tests/_conftest.py | 2 +- pytorch_forecasting/tests/_data_scenarios.py | 261 ++++++++++++++++++ .../tests/test_all_estimators.py | 5 +- 3 files changed, 265 insertions(+), 3 deletions(-) create mode 100644 pytorch_forecasting/tests/_data_scenarios.py diff --git a/pytorch_forecasting/tests/_conftest.py b/pytorch_forecasting/tests/_conftest.py index 20cad22c5..e276446a6 100644 --- a/pytorch_forecasting/tests/_conftest.py +++ b/pytorch_forecasting/tests/_conftest.py @@ -17,7 +17,7 @@ def gpus(): return 0 -@pytest.fixture(scope="package") +@pytest.fixture(scope="session") def data_with_covariates(): data = get_stallion_data() data["month"] = data.date.dt.month.astype(str) diff --git a/pytorch_forecasting/tests/_data_scenarios.py b/pytorch_forecasting/tests/_data_scenarios.py new file mode 100644 index 000000000..062db97dd --- /dev/null +++ b/pytorch_forecasting/tests/_data_scenarios.py @@ -0,0 +1,261 @@ +import numpy as np +import pytest +import torch + +from pytorch_forecasting import TimeSeriesDataSet +from pytorch_forecasting.data import EncoderNormalizer, GroupNormalizer, NaNLabelEncoder +from pytorch_forecasting.data.examples import generate_ar_data, get_stallion_data + +torch.manual_seed(23) + + +@pytest.fixture(scope="session") +def gpus(): + if torch.cuda.is_available(): + return [0] + else: + return 0 + + +def data_with_covariates(): + data = get_stallion_data() + data["month"] = data.date.dt.month.astype(str) + data["log_volume"] = np.log1p(data.volume) + data["weight"] = 1 + np.sqrt(data.volume) + + data["time_idx"] = data["date"].dt.year * 12 + data["date"].dt.month + data["time_idx"] -= data["time_idx"].min() + + # convert special days into strings + special_days = [ + "easter_day", + "good_friday", + "new_year", + "christmas", + "labor_day", + "independence_day", + "revolution_day_memorial", + "regional_games", + "fifa_u_17_world_cup", + "football_gold_cup", + "beer_capital", + "music_fest", + ] + data[special_days] = ( + data[special_days].apply(lambda x: x.map({0: "", 1: x.name})).astype("category") + ) + data = data.astype(dict(industry_volume=float)) + + # select data subset + data = data[lambda x: x.sku.isin(data.sku.unique()[:2])][ + lambda x: x.agency.isin(data.agency.unique()[:2]) + ] + + # default target + data["target"] = data["volume"].clip(1e-3, 1.0) + + return data + + +def make_dataloaders(data_with_covariates, **kwargs): + training_cutoff = "2016-09-01" + max_encoder_length = 4 + max_prediction_length = 3 + + kwargs.setdefault("target", "volume") + kwargs.setdefault("group_ids", ["agency", "sku"]) + kwargs.setdefault("add_relative_time_idx", True) + kwargs.setdefault("time_varying_unknown_reals", ["volume"]) + + training = TimeSeriesDataSet( + data_with_covariates[lambda x: x.date < training_cutoff].copy(), + time_idx="time_idx", + max_encoder_length=max_encoder_length, + max_prediction_length=max_prediction_length, + **kwargs, # fixture parametrization + ) + + validation = TimeSeriesDataSet.from_dataset( + training, + data_with_covariates.copy(), + min_prediction_idx=training.index.time.max() + 1, + ) + train_dataloader = training.to_dataloader(train=True, batch_size=2, num_workers=0) + val_dataloader = validation.to_dataloader(train=False, batch_size=2, num_workers=0) + test_dataloader = validation.to_dataloader(train=False, batch_size=1, num_workers=0) + + return dict(train=train_dataloader, val=val_dataloader, test=test_dataloader) + + +@pytest.fixture( + params=[ + dict(), + dict( + static_categoricals=["agency", "sku"], + static_reals=["avg_population_2017", "avg_yearly_household_income_2017"], + time_varying_known_categoricals=["special_days", "month"], + variable_groups=dict( + special_days=[ + "easter_day", + "good_friday", + "new_year", + "christmas", + "labor_day", + "independence_day", + "revolution_day_memorial", + "regional_games", + "fifa_u_17_world_cup", + "football_gold_cup", + "beer_capital", + "music_fest", + ] + ), + time_varying_known_reals=[ + "time_idx", + "price_regular", + "price_actual", + "discount", + "discount_in_percent", + ], + time_varying_unknown_categoricals=[], + time_varying_unknown_reals=[ + "volume", + "log_volume", + "industry_volume", + "soda_volume", + "avg_max_temp", + ], + constant_fill_strategy={"volume": 0}, + categorical_encoders={"sku": NaNLabelEncoder(add_nan=True)}, + ), + dict(static_categoricals=["agency", "sku"]), + dict(randomize_length=True, min_encoder_length=2), + dict(target_normalizer=EncoderNormalizer(), min_encoder_length=2), + dict(target_normalizer=GroupNormalizer(transformation="log1p")), + dict( + target_normalizer=GroupNormalizer( + groups=["agency", "sku"], transformation="softplus", center=False + ) + ), + dict(target="agency"), + # test multiple targets + dict(target=["industry_volume", "volume"]), + dict(target=["agency", "volume"]), + dict( + target=["agency", "volume"], min_encoder_length=1, min_prediction_length=1 + ), + dict(target=["agency", "volume"], weight="volume"), + # test weights + dict(target="volume", weight="volume"), + ], + scope="session", +) +def multiple_dataloaders_with_covariates(data_with_covariates, request): + return make_dataloaders(data_with_covariates, **request.param) + + +@pytest.fixture(scope="session") +def dataloaders_with_different_encoder_decoder_length(data_with_covariates): + return make_dataloaders( + data_with_covariates.copy(), + target="target", + time_varying_known_categoricals=["special_days", "month"], + variable_groups=dict( + special_days=[ + "easter_day", + "good_friday", + "new_year", + "christmas", + "labor_day", + "independence_day", + "revolution_day_memorial", + "regional_games", + "fifa_u_17_world_cup", + "football_gold_cup", + "beer_capital", + "music_fest", + ] + ), + time_varying_known_reals=[ + "time_idx", + "price_regular", + "price_actual", + "discount", + "discount_in_percent", + ], + time_varying_unknown_categoricals=[], + time_varying_unknown_reals=[ + "target", + "volume", + "log_volume", + "industry_volume", + "soda_volume", + "avg_max_temp", + ], + static_categoricals=["agency"], + add_relative_time_idx=False, + target_normalizer=GroupNormalizer(groups=["agency", "sku"], center=False), + ) + + +@pytest.fixture(scope="session") +def dataloaders_with_covariates(data_with_covariates): + return make_dataloaders( + data_with_covariates.copy(), + target="target", + time_varying_known_reals=["discount"], + time_varying_unknown_reals=["target"], + static_categoricals=["agency"], + add_relative_time_idx=False, + target_normalizer=GroupNormalizer(groups=["agency", "sku"], center=False), + ) + + +@pytest.fixture(scope="session") +def dataloaders_multi_target(data_with_covariates): + return make_dataloaders( + data_with_covariates.copy(), + time_varying_unknown_reals=["target", "discount"], + target=["target", "discount"], + add_relative_time_idx=False, + ) + + +@pytest.fixture(scope="session") +def dataloaders_fixed_window_without_covariates(): + data = generate_ar_data(seasonality=10.0, timesteps=50, n_series=2) + validation = data.series.iloc[:2] + + max_encoder_length = 30 + max_prediction_length = 10 + + training = TimeSeriesDataSet( + data[lambda x: ~x.series.isin(validation)], + time_idx="time_idx", + target="value", + categorical_encoders={"series": NaNLabelEncoder().fit(data.series)}, + group_ids=["series"], + static_categoricals=[], + max_encoder_length=max_encoder_length, + max_prediction_length=max_prediction_length, + time_varying_unknown_reals=["value"], + target_normalizer=EncoderNormalizer(), + ) + + validation = TimeSeriesDataSet.from_dataset( + training, + data[lambda x: x.series.isin(validation)], + stop_randomization=True, + ) + batch_size = 2 + train_dataloader = training.to_dataloader( + train=True, batch_size=batch_size, num_workers=0 + ) + val_dataloader = validation.to_dataloader( + train=False, batch_size=batch_size, num_workers=0 + ) + test_dataloader = validation.to_dataloader( + train=False, batch_size=batch_size, num_workers=0 + ) + + return dict(train=train_dataloader, val=val_dataloader, test=test_dataloader) diff --git a/pytorch_forecasting/tests/test_all_estimators.py b/pytorch_forecasting/tests/test_all_estimators.py index c2d86e40e..b8a21cc6a 100644 --- a/pytorch_forecasting/tests/test_all_estimators.py +++ b/pytorch_forecasting/tests/test_all_estimators.py @@ -6,7 +6,6 @@ import lightning.pytorch as pl from lightning.pytorch.callbacks import EarlyStopping from lightning.pytorch.loggers import TensorBoardLogger -import pytest from skbase.testing import BaseFixtureGenerator as _BaseFixtureGenerator from pytorch_forecasting._registry import all_objects @@ -276,11 +275,13 @@ def test_integration( self, object_metadata, trainer_kwargs, - data_with_covariates, tmp_path, ): """Fails for certain, for testing.""" from pytorch_forecasting.metrics import NegativeBinomialDistributionLoss + from pytorch_forecasting.tests._data_scenarios import data_with_covariates + + data_with_covariates = data_with_covariates() object_class = object_metadata.get_model_cls() From 252598d2ce3f31244a422cd9206961776ea79615 Mon Sep 17 00:00:00 2001 From: Aryan Saini Date: Sun, 6 Apr 2025 18:43:51 +0530 Subject: [PATCH 022/139] D1, D2 layer commit --- pytorch_forecasting/data/data_module.py | 633 ++++++++++++++++++++++++ pytorch_forecasting/data/timeseries.py | 257 ++++++++++ 2 files changed, 890 insertions(+) create mode 100644 pytorch_forecasting/data/data_module.py diff --git a/pytorch_forecasting/data/data_module.py b/pytorch_forecasting/data/data_module.py new file mode 100644 index 000000000..56917696d --- /dev/null +++ b/pytorch_forecasting/data/data_module.py @@ -0,0 +1,633 @@ +####################################################################################### +# Disclaimer: This data-module is still work in progress and experimental, please +# use with care. This data-module is a basic skeleton of how the data-handling pipeline +# may look like in the future. +# This is D2 layer that will handle the preprocessing and data loaders. +# For now, this pipeline handles the simplest situation: The whole data can be loaded +# into the memory. +####################################################################################### + +from typing import Any, Dict, List, Optional, Tuple, Union + +from lightning.pytorch import LightningDataModule +from sklearn.preprocessing import RobustScaler, StandardScaler +import torch +from torch.utils.data import DataLoader, Dataset + +from pytorch_forecasting.data.encoders import ( + EncoderNormalizer, + NaNLabelEncoder, + TorchNormalizer, +) +from pytorch_forecasting.data.timeseries import TimeSeries, _coerce_to_dict + +NORMALIZER = Union[TorchNormalizer, NaNLabelEncoder, EncoderNormalizer] + + +class EncoderDecoderTimeSeriesDataModule(LightningDataModule): + """ + Lightning DataModule for processing time series data in an encoder-decoder format. + + This module handles preprocessing, splitting, and batching of time series data + for use in deep learning models. It supports categorical and continuous features, + various scalers, and automatic target normalization. + + Parameters + ---------- + time_series_dataset : TimeSeries + The dataset containing time series data. + max_encoder_length : int, default=30 + Maximum length of the encoder input sequence. + min_encoder_length : Optional[int], default=None + Minimum length of the encoder input sequence. + Defaults to `max_encoder_length` if not specified. + max_prediction_length : int, default=1 + Maximum length of the decoder output sequence. + min_prediction_length : Optional[int], default=None + Minimum length of the decoder output sequence. + Defaults to `max_prediction_length` if not specified. + min_prediction_idx : Optional[int], default=None + Minimum index from which predictions start. + allow_missing_timesteps : bool, default=False + Whether to allow missing timesteps in the dataset. + add_relative_time_idx : bool, default=False + Whether to add a relative time index feature. + add_target_scales : bool, default=False + Whether to add target scaling information. + add_encoder_length : Union[bool, str], default="auto" + Whether to include encoder length information. + target_normalizer : + Union[NORMALIZER, str, List[NORMALIZER], Tuple[NORMALIZER], None], + default="auto" + Normalizer for the target variable. If "auto", uses `RobustScaler`. + + categorical_encoders : Optional[Dict[str, NaNLabelEncoder]], default=None + Dictionary of categorical encoders. + + scalers : + Optional[Dict[str, Union[StandardScaler, RobustScaler, + TorchNormalizer, EncoderNormalizer]]], default=None + Dictionary of feature scalers. + + randomize_length : Union[None, Tuple[float, float], bool], default=False + Whether to randomize input sequence length. + batch_size : int, default=32 + Batch size for DataLoader. + num_workers : int, default=0 + Number of workers for DataLoader. + train_val_test_split : tuple, default=(0.7, 0.15, 0.15) + Proportions for train, validation, and test dataset splits. + """ + + def __init__( + self, + time_series_dataset: TimeSeries, + max_encoder_length: int = 30, + min_encoder_length: Optional[int] = None, + max_prediction_length: int = 1, + min_prediction_length: Optional[int] = None, + min_prediction_idx: Optional[int] = None, + allow_missing_timesteps: bool = False, + add_relative_time_idx: bool = False, + add_target_scales: bool = False, + add_encoder_length: Union[bool, str] = "auto", + target_normalizer: Union[ + NORMALIZER, str, List[NORMALIZER], Tuple[NORMALIZER], None + ] = "auto", + categorical_encoders: Optional[Dict[str, NaNLabelEncoder]] = None, + scalers: Optional[ + Dict[ + str, + Union[StandardScaler, RobustScaler, TorchNormalizer, EncoderNormalizer], + ] + ] = None, + randomize_length: Union[None, Tuple[float, float], bool] = False, + batch_size: int = 32, + num_workers: int = 0, + train_val_test_split: tuple = (0.7, 0.15, 0.15), + ): + super().__init__() + self.time_series_dataset = time_series_dataset + self.time_series_metadata = time_series_dataset.get_metadata() + + self.max_encoder_length = max_encoder_length + self.min_encoder_length = min_encoder_length or max_encoder_length + self.max_prediction_length = max_prediction_length + self.min_prediction_length = min_prediction_length or max_prediction_length + self.min_prediction_idx = min_prediction_idx + + self.allow_missing_timesteps = allow_missing_timesteps + self.add_relative_time_idx = add_relative_time_idx + self.add_target_scales = add_target_scales + self.add_encoder_length = add_encoder_length + self.randomize_length = randomize_length + + self.batch_size = batch_size + self.num_workers = num_workers + self.train_val_test_split = train_val_test_split + + if isinstance(target_normalizer, str) and target_normalizer.lower() == "auto": + self.target_normalizer = RobustScaler() + else: + self.target_normalizer = target_normalizer + + self.categorical_encoders = _coerce_to_dict(categorical_encoders) + self.scalers = _coerce_to_dict(scalers) + + self.categorical_indices = [] + self.continuous_indices = [] + self._metadata = None + + for idx, col in enumerate(self.time_series_metadata["cols"]["x"]): + if self.time_series_metadata["col_type"].get(col) == "C": + self.categorical_indices.append(idx) + else: + self.continuous_indices.append(idx) + + def _prepare_metadata(self): + """Prepare metadata for model initialisation. + + Returns + ------- + dict + dictionary containing the following keys: + + * ``encoder_cat``: Number of categorical variables in the encoder. + Computed as ``len(self.categorical_indices)``, which counts the + categorical feature indices. + * ``encoder_cont``: Number of continuous variables in the encoder. + Computed as ``len(self.continuous_indices)``, which counts the + continuous feature indices. + * ``decoder_cat``: Number of categorical variables in the decoder that + are known in advance. + Computed by filtering ``self.time_series_metadata["cols"]["x"]`` + where col_type == "C"(categorical) and col_known == "K" (known) + * ``decoder_cont``: Number of continuous variables in the decoder that + are known in advance. + Computed by filtering ``self.time_series_metadata["cols"]["x"]`` + where col_type == "F"(continuous) and col_known == "K"(known) + * ``target``: Number of target variables. + Computed as ``len(self.time_series_metadata["cols"]["y"])``, which + gives the number of output target columns.. + * ``static_categorical_features``: Number of static categorical features + Computed by filtering ``self.time_series_metadata["cols"]["st"]`` + (static features) where col_type == "C" (categorical). + * ``static_continuous_features``: Number of static continuous features + Computed as difference of + ``len(self.time_series_metadata["cols"]["st"])`` (static features) + and static_categorical_features that gives static continuous feature + * ``max_encoder_length``: maximum encoder length + Taken directly from `self.max_encoder_length`. + * ``max_prediction_length``: maximum prediction length + Taken directly from `self.max_prediction_length`. + * ``min_encoder_length``: minimum encoder length + Taken directly from `self.min_encoder_length`. + * ``min_prediction_length``: minimum prediction length + Taken directly from `self.min_prediction_length`. + + """ + encoder_cat_count = len(self.categorical_indices) + encoder_cont_count = len(self.continuous_indices) + + decoder_cat_count = len( + [ + col + for col in self.time_series_metadata["cols"]["x"] + if self.time_series_metadata["col_type"].get(col) == "C" + and self.time_series_metadata["col_known"].get(col) == "K" + ] + ) + decoder_cont_count = len( + [ + col + for col in self.time_series_metadata["cols"]["x"] + if self.time_series_metadata["col_type"].get(col) == "F" + and self.time_series_metadata["col_known"].get(col) == "K" + ] + ) + + target_count = len(self.time_series_metadata["cols"]["y"]) + metadata = { + "encoder_cat": encoder_cat_count, + "encoder_cont": encoder_cont_count, + "decoder_cat": decoder_cat_count, + "decoder_cont": decoder_cont_count, + "target": target_count, + } + if self.time_series_metadata["cols"]["st"]: + static_cat_count = len( + [ + col + for col in self.time_series_metadata["cols"]["st"] + if self.time_series_metadata["col_type"].get(col) == "C" + ] + ) + static_cont_count = ( + len(self.time_series_metadata["cols"]["st"]) - static_cat_count + ) + + metadata["static_categorical_features"] = static_cat_count + metadata["static_continuous_features"] = static_cont_count + else: + metadata["static_categorical_features"] = 0 + metadata["static_continuous_features"] = 0 + + metadata.update( + { + "max_encoder_length": self.max_encoder_length, + "max_prediction_length": self.max_prediction_length, + "min_encoder_length": self.min_encoder_length, + "min_prediction_length": self.min_prediction_length, + } + ) + + return metadata + + @property + def metadata(self): + """Compute metadata for model initialization. + + This property returns a dictionary containing the shapes and key information + related to the time series model. The metadata includes: + + * ``encoder_cat``: Number of categorical variables in the encoder. + * ``encoder_cont``: Number of continuous variables in the encoder. + * ``decoder_cat``: Number of categorical variables in the decoder that are + known in advance. + * ``decoder_cont``: Number of continuous variables in the decoder that are + known in advance. + * ``target``: Number of target variables. + + If static features are present, the following keys are added: + + * ``static_categorical_features``: Number of static categorical features + * ``static_continuous_features``: Number of static continuous features + + It also contains the following information: + + * ``max_encoder_length``: maximum encoder length + * ``max_prediction_length``: maximum prediction length + * ``min_encoder_length``: minimum encoder length + * ``min_prediction_length``: minimum prediction length + """ + if self._metadata is None: + self._metadata = self._prepare_metadata() + return self._metadata + + def _preprocess_data(self, indices: torch.Tensor) -> List[Dict[str, Any]]: + """Preprocess the data before feeding it into _ProcessedEncoderDecoderDataset. + + Preprocessing steps + -------------------- + + * Converts target (`y`) and features (`x`) to `torch.float32`. + * Masks time points that are at or before the cutoff time. + * Splits features into categorical and continuous subsets based on + predefined indices. + + + TODO: add scalers, target normalizers etc. + """ + processed_data = [] + + for idx in indices: + sample = self.time_series_dataset[idx.item()] + + target = sample["y"] + features = sample["x"] + times = sample["t"] + cutoff_time = sample["cutoff_time"] + + time_mask = torch.tensor(times <= cutoff_time, dtype=torch.bool) + + if isinstance(target, torch.Tensor): + target = target.float() + else: + target = torch.tensor(target, dtype=torch.float32) + + if isinstance(features, torch.Tensor): + features = features.float() + else: + features = torch.tensor(features, dtype=torch.float32) + + # TODO: add scalers, target normalizers etc. + + categorical = ( + features[:, self.categorical_indices] + if self.categorical_indices + else torch.zeros((features.shape[0], 0)) + ) + continuous = ( + features[:, self.continuous_indices] + if self.continuous_indices + else torch.zeros((features.shape[0], 0)) + ) + + processed_data.append( + { + "features": {"categorical": categorical, "continuous": continuous}, + "target": target, + "static": sample.get("st", None), + "group": sample.get("group", torch.tensor([0])), + "length": len(target), + "time_mask": time_mask, + "times": times, + "cutoff_time": cutoff_time, + } + ) + + return processed_data + + class _ProcessedEncoderDecoderDataset(Dataset): + """PyTorch Dataset for processed encoder-decoder time series data. + + Parameters + ---------- + processed_data : List[Dict[str, Any]] + List of preprocessed time series samples. + windows : List[Tuple[int, int, int, int]] + List of window tuples containing + (series_idx, start_idx, enc_length, pred_length). + add_relative_time_idx : bool, default=False + Whether to include relative time indices. + """ + + def __init__( + self, + processed_data: List[Dict[str, Any]], + windows: List[Tuple[int, int, int, int]], + add_relative_time_idx: bool = False, + ): + self.processed_data = processed_data + self.windows = windows + self.add_relative_time_idx = add_relative_time_idx + + def __len__(self): + return len(self.windows) + + def __getitem__(self, idx): + """Retrieve a processed time series window for dataloader input. + + x : dict + Dictionary containing model inputs: + + * ``encoder_cat`` : tensor of shape (enc_length, n_cat_features) + Categorical features for the encoder. + * ``encoder_cont`` : tensor of shape (enc_length, n_cont_features) + Continuous features for the encoder. + * ``decoder_cat`` : tensor of shape (pred_length, n_cat_features) + Categorical features for the decoder. + * ``decoder_cont`` : tensor of shape (pred_length, n_cont_features) + Continuous features for the decoder. + * ``encoder_lengths`` : tensor of shape (1,) + Length of the encoder sequence. + * ``decoder_lengths`` : tensor of shape (1,) + Length of the decoder sequence. + * ``decoder_target_lengths`` : tensor of shape (1,) + Length of the decoder target sequence. + * ``groups`` : tensor of shape (1,) + Group identifier for the time series instance. + * ``encoder_time_idx`` : tensor of shape (enc_length,) + Time indices for the encoder sequence. + * ``decoder_time_idx`` : tensor of shape (pred_length,) + Time indices for the decoder sequence. + * ``target_scale`` : tensor of shape (1,) + Scaling factor for the target values. + * ``encoder_mask`` : tensor of shape (enc_length,) + Boolean mask indicating valid encoder time points. + * ``decoder_mask`` : tensor of shape (pred_length,) + Boolean mask indicating valid decoder time points. + + If static features are present, the following keys are added: + + * ``static_categorical_features`` : tensor of shape + (1, n_static_cat_features), optional + Static categorical features, if available. + * ``static_continuous_features`` : tensor of shape (1, 0), optional + Placeholder for static continuous features (currently empty). + + y : tensor of shape ``(pred_length, n_targets)`` + Target values for the decoder sequence. + """ + series_idx, start_idx, enc_length, pred_length = self.windows[idx] + data = self.processed_data[series_idx] + + end_idx = start_idx + enc_length + pred_length + encoder_indices = slice(start_idx, start_idx + enc_length) + decoder_indices = slice(start_idx + enc_length, end_idx) + + target_scale = data["target"][encoder_indices] + target_scale = target_scale[~torch.isnan(target_scale)].abs().mean() + if torch.isnan(target_scale) or target_scale == 0: + target_scale = torch.tensor(1.0) + + encoder_mask = ( + data["time_mask"][encoder_indices] + if "time_mask" in data + else torch.ones(enc_length, dtype=torch.bool) + ) + decoder_mask = ( + data["time_mask"][decoder_indices] + if "time_mask" in data + else torch.zeros(pred_length, dtype=torch.bool) + ) + + x = { + "encoder_cat": data["features"]["categorical"][encoder_indices], + "encoder_cont": data["features"]["continuous"][encoder_indices], + "decoder_cat": data["features"]["categorical"][decoder_indices], + "decoder_cont": data["features"]["continuous"][decoder_indices], + "encoder_lengths": torch.tensor(enc_length), + "decoder_lengths": torch.tensor(pred_length), + "decoder_target_lengths": torch.tensor(pred_length), + "groups": data["group"], + "encoder_time_idx": torch.arange(enc_length), + "decoder_time_idx": torch.arange(enc_length, enc_length + pred_length), + "target_scale": target_scale, + "encoder_mask": encoder_mask, + "decoder_mask": decoder_mask, + } + if data["static"] is not None: + x["static_categorical_features"] = data["static"].unsqueeze(0) + x["static_continuous_features"] = torch.zeros((1, 0)) + + y = data["target"][decoder_indices] + if y.ndim == 1: + y = y.unsqueeze(-1) + + return x, y + + def _create_windows( + self, processed_data: List[Dict[str, Any]] + ) -> List[Tuple[int, int, int, int]]: + """Generate sliding windows for training, validation, and testing. + + Returns + ------- + List[Tuple[int, int, int, int]] + A list of tuples, where each tuple consists of: + - ``series_idx`` : int + Index of the time series in `processed_data`. + - ``start_idx`` : int + Start index of the encoder window. + - ``enc_length`` : int + Length of the encoder input sequence. + - ``pred_length`` : int + Length of the decoder output sequence. + """ + windows = [] + + for idx, data in enumerate(processed_data): + sequence_length = data["length"] + + if sequence_length < self.max_encoder_length + self.max_prediction_length: + continue + + effective_min_prediction_idx = ( + self.min_prediction_idx + if self.min_prediction_idx is not None + else self.max_encoder_length + ) + + max_prediction_idx = sequence_length - self.max_prediction_length + 1 + + if max_prediction_idx <= effective_min_prediction_idx: + continue + + for start_idx in range( + 0, max_prediction_idx - effective_min_prediction_idx + ): + if ( + start_idx + self.max_encoder_length + self.max_prediction_length + <= sequence_length + ): + windows.append( + ( + idx, + start_idx, + self.max_encoder_length, + self.max_prediction_length, + ) + ) + + return windows + + def setup(self, stage: Optional[str] = None): + """Prepare the datasets for training, validation, testing, or prediction. + + Parameters + ---------- + stage : Optional[str], default=None + Specifies the stage of setup. Can be one of: + - ``"fit"`` : Prepares training and validation datasets. + - ``"test"`` : Prepares the test dataset. + - ``"predict"`` : Prepares the dataset for inference. + - ``None`` : Prepares all datasets. + """ + total_series = len(self.time_series_dataset) + self._split_indices = torch.randperm(total_series) + + self._train_size = int(self.train_val_test_split[0] * total_series) + self._val_size = int(self.train_val_test_split[1] * total_series) + + self._train_indices = self._split_indices[: self._train_size] + self._val_indices = self._split_indices[ + self._train_size : self._train_size + self._val_size + ] + self._test_indices = self._split_indices[self._train_size + self._val_size :] + + if stage is None or stage == "fit": + if not hasattr(self, "train_dataset") or not hasattr(self, "val_dataset"): + self.train_processed = self._preprocess_data(self._train_indices) + self.val_processed = self._preprocess_data(self._val_indices) + + self.train_windows = self._create_windows(self.train_processed) + self.val_windows = self._create_windows(self.val_processed) + + self.train_dataset = self._ProcessedEncoderDecoderDataset( + self.train_processed, self.train_windows, self.add_relative_time_idx + ) + self.val_dataset = self._ProcessedEncoderDecoderDataset( + self.val_processed, self.val_windows, self.add_relative_time_idx + ) + # print(self.val_dataset[0]) + + elif stage is None or stage == "test": + if not hasattr(self, "test_dataset"): + self.test_processed = self._preprocess_data(self._test_indices) + self.test_windows = self._create_windows(self.test_processed) + + self.test_dataset = self._ProcessedEncoderDecoderDataset( + self.test_processed, self.test_windows, self.add_relative_time_idx + ) + elif stage == "predict": + predict_indices = torch.arange(len(self.time_series_dataset)) + self.predict_processed = self._preprocess_data(predict_indices) + self.predict_windows = self._create_windows(self.predict_processed) + self.predict_dataset = self._ProcessedEncoderDecoderDataset( + self.predict_processed, self.predict_windows, self.add_relative_time_idx + ) + + def train_dataloader(self): + return DataLoader( + self.train_dataset, + batch_size=self.batch_size, + num_workers=self.num_workers, + shuffle=True, + collate_fn=self.collate_fn, + ) + + def val_dataloader(self): + return DataLoader( + self.val_dataset, + batch_size=self.batch_size, + num_workers=self.num_workers, + collate_fn=self.collate_fn, + ) + + def test_dataloader(self): + return DataLoader( + self.test_dataset, + batch_size=self.batch_size, + num_workers=self.num_workers, + collate_fn=self.collate_fn, + ) + + def predict_dataloader(self): + return DataLoader( + self.predict_dataset, + batch_size=self.batch_size, + num_workers=self.num_workers, + collate_fn=self.collate_fn, + ) + + @staticmethod + def collate_fn(batch): + x_batch = { + "encoder_cat": torch.stack([x["encoder_cat"] for x, _ in batch]), + "encoder_cont": torch.stack([x["encoder_cont"] for x, _ in batch]), + "decoder_cat": torch.stack([x["decoder_cat"] for x, _ in batch]), + "decoder_cont": torch.stack([x["decoder_cont"] for x, _ in batch]), + "encoder_lengths": torch.stack([x["encoder_lengths"] for x, _ in batch]), + "decoder_lengths": torch.stack([x["decoder_lengths"] for x, _ in batch]), + "decoder_target_lengths": torch.stack( + [x["decoder_target_lengths"] for x, _ in batch] + ), + "groups": torch.stack([x["groups"] for x, _ in batch]), + "encoder_time_idx": torch.stack([x["encoder_time_idx"] for x, _ in batch]), + "decoder_time_idx": torch.stack([x["decoder_time_idx"] for x, _ in batch]), + "target_scale": torch.stack([x["target_scale"] for x, _ in batch]), + "encoder_mask": torch.stack([x["encoder_mask"] for x, _ in batch]), + "decoder_mask": torch.stack([x["decoder_mask"] for x, _ in batch]), + } + + if "static_categorical_features" in batch[0][0]: + x_batch["static_categorical_features"] = torch.stack( + [x["static_categorical_features"] for x, _ in batch] + ) + x_batch["static_continuous_features"] = torch.stack( + [x["static_continuous_features"] for x, _ in batch] + ) + + y_batch = torch.stack([y for _, y in batch]) + return x_batch, y_batch diff --git a/pytorch_forecasting/data/timeseries.py b/pytorch_forecasting/data/timeseries.py index 336eecd5f..bc8300300 100644 --- a/pytorch_forecasting/data/timeseries.py +++ b/pytorch_forecasting/data/timeseries.py @@ -2657,6 +2657,8 @@ def _coerce_to_list(obj): """ if obj is None: return [] + if isinstance(obj, str): + return [obj] return list(obj) @@ -2668,3 +2670,258 @@ def _coerce_to_dict(obj): if obj is None: return {} return deepcopy(obj) + + +####################################################################################### +# Disclaimer: This dataset class is still work in progress and experimental, please +# use with care. This class is a basic skeleton of how the data-handling pipeline may +# look like in the future. +# This is the D1 layer that is a "Raw Dataset Layer" mainly for raw data ingestion +# and turning the data to tensors. +# For now, this pipeline handles the simplest situation: The whole data can be loaded +# into the memory. +####################################################################################### + + +class TimeSeries(Dataset): + """PyTorch Dataset for time series data stored in pandas DataFrame. + + Parameters + ---------- + data : pd.DataFrame + data frame with sequence data. + Column names must all be str, and contain str as referred to below. + data_future : pd.DataFrame, optional, default=None + data frame with future data. + Column names must all be str, and contain str as referred to below. + May contain only columns that are in time, group, weight, known, or static. + time : str, optional, default = first col not in group_ids, weight, target, static. + integer typed column denoting the time index within ``data``. + This column is used to determine the sequence of samples. + If there are no missing observations, + the time index should increase by ``+1`` for each subsequent sample. + The first time_idx for each series does not necessarily + have to be ``0`` but any value is allowed. + target : str or List[str], optional, default = last column (at iloc -1) + column(s) in ``data`` denoting the forecasting target. + Can be categorical or numerical dtype. + group : List[str], optional, default = None + list of column names identifying a time series instance within ``data``. + This means that the ``group`` together uniquely identify an instance, + and ``group`` together with ``time`` uniquely identify a single observation + within a time series instance. + If ``None``, the dataset is assumed to be a single time series. + weight : str, optional, default=None + column name for weights. + If ``None``, it is assumed that there is no weight column. + num : list of str, optional, default = all columns with dtype in "fi" + list of numerical variables in ``data``, + list may also contain list of str, which are then grouped together. + cat : list of str, optional, default = all columns with dtype in "Obc" + list of categorical variables in ``data``, + list may also contain list of str, which are then grouped together + (e.g. useful for product categories). + known : list of str, optional, default = all variables + list of variables that change over time and are known in the future, + list may also contain list of str, which are then grouped together + (e.g. useful for special days or promotion categories). + unknown : list of str, optional, default = no variables + list of variables that are not known in the future, + list may also contain list of str, which are then grouped together + (e.g. useful for weather categories). + static : list of str, optional, default = all variables not in known, unknown + list of variables that do not change over time, + list may also contain list of str, which are then grouped together. + """ + + def __init__( + self, + data: pd.DataFrame, + data_future: Optional[pd.DataFrame] = None, + time: Optional[str] = None, + target: Optional[Union[str, List[str]]] = None, + group: Optional[List[str]] = None, + weight: Optional[str] = None, + num: Optional[List[Union[str, List[str]]]] = None, + cat: Optional[List[Union[str, List[str]]]] = None, + known: Optional[List[Union[str, List[str]]]] = None, + unknown: Optional[List[Union[str, List[str]]]] = None, + static: Optional[List[Union[str, List[str]]]] = None, + ): + + self.data = data + self.data_future = data_future + self.time = time + self.target = _coerce_to_list(target) + self.group = _coerce_to_list(group) + self.weight = weight + self.num = _coerce_to_list(num) + self.cat = _coerce_to_list(cat) + self.known = _coerce_to_list(known) + self.unknown = _coerce_to_list(unknown) + self.static = _coerce_to_list(static) + + self.feature_cols = [ + col + for col in data.columns + if col not in [self.time] + self.group + [self.weight] + self.target + ] + if self.group: + self._groups = self.data.groupby(self.group).groups + self._group_ids = list(self._groups.keys()) + else: + self._groups = {"_single_group": self.data.index} + self._group_ids = ["_single_group"] + + self._prepare_metadata() + + def _prepare_metadata(self): + """Prepare metadata for the dataset. + + The funcion returns metadata that contains: + + * ``cols``: dict { 'y': list[str], 'x': list[str], 'st': list[str] } + Names of columns for y, x, and static features. + List elements are in same order as column dimensions. + Columns not appearing are assumed to be named (x0, x1, etc.), + (y0, y1, etc.), (st0, st1, etc.). + * ``col_type``: dict[str, str] + maps column names to data types "F" (numerical) and "C" (categorical). + Column names not occurring are assumed "F". + * ``col_known``: dict[str, str] + maps column names to "K" (future known) or "U" (future unknown). + Column names not occurring are assumed "K". + """ + self.metadata = { + "cols": { + "y": self.target, + "x": self.feature_cols, + "st": self.static, + }, + "col_type": {}, + "col_known": {}, + } + + all_cols = self.target + self.feature_cols + self.static + for col in all_cols: + self.metadata["col_type"][col] = "C" if col in self.cat else "F" + + self.metadata["col_known"][col] = "K" if col in self.known else "U" + + def __len__(self) -> int: + """Return number of time series in the dataset.""" + return len(self._group_ids) + + def __getitem__(self, index: int) -> Dict[str, torch.Tensor]: + """Get time series data for given index. + + It returns: + + * ``t``: ``numpy.ndarray`` of shape (n_timepoints,) + Time index for each time point in the past or present. Aligned with ``y``, + and ``x`` not ending in ``f``. + * ``y``: tensor of shape (n_timepoints, n_targets) + Target values for each time point. Rows are time points, aligned with ``t``. + * ``x``: tensor of shape (n_timepoints, n_features) + Features for each time point. Rows are time points, aligned with ``t``. + * ``group``: tensor of shape (n_groups) + Group identifiers for time series instances. + * ``st``: tensor of shape (n_static_features) + Static features. + * ``cutoff_time``: float or ``numpy.float64`` + Cutoff time for the time series instance. + + Optionally, the following str-keyed entry can be included: + + * ``weights``: tensor of shape (n_timepoints), only if weight is not None + """ + group_id = self._group_ids[index] + + if self.group: + mask = self._groups[group_id] + data = self.data.loc[mask] + else: + data = self.data + + cutoff_time = data[self.time].max() + + result = { + "t": data[self.time].values, + "y": torch.tensor(data[self.target].values), + "x": torch.tensor(data[self.feature_cols].values), + "group": torch.tensor([hash(str(group_id))]), + "st": torch.tensor(data[self.static].iloc[0].values if self.static else []), + "cutoff_time": cutoff_time, + } + + if self.data_future is not None: + if self.group: + future_mask = self.data_future.groupby(self.group).groups[group_id] + future_data = self.data_future.loc[future_mask] + else: + future_data = self.data_future + + combined_times = np.concatenate( + [data[self.time].values, future_data[self.time].values] + ) + combined_times = np.unique(combined_times) + combined_times.sort() + + num_timepoints = len(combined_times) + x_merged = np.full((num_timepoints, len(self.feature_cols)), np.nan) + y_merged = np.full((num_timepoints, len(self.target)), np.nan) + + current_time_indices = {t: i for i, t in enumerate(combined_times)} + for i, t in enumerate(data[self.time].values): + idx = current_time_indices[t] + x_merged[idx] = data[self.feature_cols].values[i] + y_merged[idx] = data[self.target].values[i] + + for i, t in enumerate(future_data[self.time].values): + if t in current_time_indices: + idx = current_time_indices[t] + for j, col in enumerate(self.known): + if col in self.feature_cols: + feature_idx = self.feature_cols.index(col) + x_merged[idx, feature_idx] = future_data[col].values[i] + + result.update( + { + "t": combined_times, + "x": torch.tensor(x_merged, dtype=torch.float32), + "y": torch.tensor(y_merged, dtype=torch.float32), + } + ) + + if self.weight: + if self.data_future is not None and self.weight in self.data_future.columns: + weights_merged = np.full(num_timepoints, np.nan) + for i, t in enumerate(data[self.time].values): + idx = current_time_indices[t] + weights_merged[idx] = data[self.weight].values[i] + + for i, t in enumerate(future_data[self.time].values): + if t in current_time_indices and self.weight in future_data.columns: + idx = current_time_indices[t] + weights_merged[idx] = future_data[self.weight].values[i] + + result["weights"] = torch.tensor(weights_merged, dtype=torch.float32) + else: + result["weights"] = torch.tensor( + data[self.weight].values, dtype=torch.float32 + ) + + return result + + def get_metadata(self) -> Dict: + """Return metadata about the dataset. + + Returns + ------- + Dict + Dictionary containing: + - cols: column names for y, x, and static features + - col_type: mapping of columns to their types (F/C) + - col_known: mapping of columns to their future known status (K/U) + """ + return self.metadata From d0d1c3ec7fb3bdee8e80d9ff83cd43e8990a5319 Mon Sep 17 00:00:00 2001 From: Aryan Saini Date: Sun, 6 Apr 2025 18:47:46 +0530 Subject: [PATCH 023/139] remove one comment --- pytorch_forecasting/data/data_module.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pytorch_forecasting/data/data_module.py b/pytorch_forecasting/data/data_module.py index 56917696d..2958f1705 100644 --- a/pytorch_forecasting/data/data_module.py +++ b/pytorch_forecasting/data/data_module.py @@ -550,7 +550,6 @@ def setup(self, stage: Optional[str] = None): self.val_dataset = self._ProcessedEncoderDecoderDataset( self.val_processed, self.val_windows, self.add_relative_time_idx ) - # print(self.val_dataset[0]) elif stage is None or stage == "test": if not hasattr(self, "test_dataset"): From 80e64d218a744557bd493ea07547f0f42b029573 Mon Sep 17 00:00:00 2001 From: Aryan Saini Date: Sun, 6 Apr 2025 19:07:01 +0530 Subject: [PATCH 024/139] model layer commit --- .../models/base/base_model_refactor.py | 283 ++++++++++++++++++ .../tft_version_two.py | 218 ++++++++++++++ 2 files changed, 501 insertions(+) create mode 100644 pytorch_forecasting/models/base/base_model_refactor.py create mode 100644 pytorch_forecasting/models/temporal_fusion_transformer/tft_version_two.py diff --git a/pytorch_forecasting/models/base/base_model_refactor.py b/pytorch_forecasting/models/base/base_model_refactor.py new file mode 100644 index 000000000..ccd2c2600 --- /dev/null +++ b/pytorch_forecasting/models/base/base_model_refactor.py @@ -0,0 +1,283 @@ +######################################################################################## +# Disclaimer: This baseclass is still work in progress and experimental, please +# use with care. This class is a basic skeleton of how the base classes may look like +# in the version-2. +######################################################################################## + + +from typing import Dict, List, Optional, Tuple, Union + +from lightning.pytorch import LightningModule +from lightning.pytorch.utilities.types import STEP_OUTPUT +import torch +import torch.nn as nn +from torch.optim import Optimizer + + +class BaseModel(LightningModule): + def __init__( + self, + loss: nn.Module, + logging_metrics: Optional[List[nn.Module]] = None, + optimizer: Optional[Union[Optimizer, str]] = "adam", + optimizer_params: Optional[Dict] = None, + lr_scheduler: Optional[str] = None, + lr_scheduler_params: Optional[Dict] = None, + ): + """ + Base model for time series forecasting. + + Parameters + ---------- + loss : nn.Module + Loss function to use for training. + logging_metrics : Optional[List[nn.Module]], optional + List of metrics to log during training, validation, and testing. + optimizer : Optional[Union[Optimizer, str]], optional + Optimizer to use for training. + Can be a string ("adam", "sgd") or an instance of `torch.optim.Optimizer`. + optimizer_params : Optional[Dict], optional + Parameters for the optimizer. + lr_scheduler : Optional[str], optional + Learning rate scheduler to use. + Supported values: "reduce_lr_on_plateau", "step_lr". + lr_scheduler_params : Optional[Dict], optional + Parameters for the learning rate scheduler. + """ + super().__init__() + self.loss = loss + self.logging_metrics = logging_metrics if logging_metrics is not None else [] + self.optimizer = optimizer + self.optimizer_params = optimizer_params if optimizer_params is not None else {} + self.lr_scheduler = lr_scheduler + self.lr_scheduler_params = ( + lr_scheduler_params if lr_scheduler_params is not None else {} + ) + + def forward(self, x: Dict[str, torch.Tensor]) -> Dict[str, torch.Tensor]: + """ + Forward pass of the model. + + Parameters + ---------- + x : Dict[str, torch.Tensor] + Dictionary containing input tensors + + Returns + ------- + Dict[str, torch.Tensor] + Dictionary containing output tensors + """ + raise NotImplementedError("Forward method must be implemented by subclass.") + + def training_step( + self, batch: Tuple[Dict[str, torch.Tensor]], batch_idx: int + ) -> STEP_OUTPUT: + """ + Training step for the model. + + Parameters + ---------- + batch : Tuple[Dict[str, torch.Tensor]] + Batch of data containing input and target tensors. + batch_idx : int + Index of the batch. + + Returns + ------- + STEP_OUTPUT + Dictionary containing the loss and other metrics. + """ + x, y = batch + y_hat_dict = self(x) + y_hat = y_hat_dict["prediction"] + loss = self.loss(y_hat, y) + self.log( + "train_loss", loss, on_step=True, on_epoch=True, prog_bar=True, logger=True + ) + self.log_metrics(y_hat, y, prefix="train") + return {"loss": loss} + + def validation_step( + self, batch: Tuple[Dict[str, torch.Tensor]], batch_idx: int + ) -> STEP_OUTPUT: + """ + Validation step for the model. + + Parameters + ---------- + batch : Tuple[Dict[str, torch.Tensor]] + Batch of data containing input and target tensors. + batch_idx : int + Index of the batch. + + Returns + ------- + STEP_OUTPUT + Dictionary containing the loss and other metrics. + """ + x, y = batch + y_hat_dict = self(x) + y_hat = y_hat_dict["prediction"] + loss = self.loss(y_hat, y) + self.log( + "val_loss", loss, on_step=False, on_epoch=True, prog_bar=True, logger=True + ) + self.log_metrics(y_hat, y, prefix="val") + return {"val_loss": loss} + + def test_step( + self, batch: Tuple[Dict[str, torch.Tensor]], batch_idx: int + ) -> STEP_OUTPUT: + """ + Test step for the model. + + Parameters + ---------- + batch : Tuple[Dict[str, torch.Tensor]] + Batch of data containing input and target tensors. + batch_idx : int + Index of the batch. + + Returns + ------- + STEP_OUTPUT + Dictionary containing the loss and other metrics. + """ + x, y = batch + y_hat_dict = self(x) + y_hat = y_hat_dict["prediction"] + loss = self.loss(y_hat, y) + self.log( + "test_loss", loss, on_step=False, on_epoch=True, prog_bar=True, logger=True + ) + self.log_metrics(y_hat, y, prefix="test") + return {"test_loss": loss} + + def predict_step( + self, + batch: Tuple[Dict[str, torch.Tensor]], + batch_idx: int, + dataloader_idx: int = 0, + ) -> torch.Tensor: + """ + Prediction step for the model. + + Parameters + ---------- + batch : Tuple[Dict[str, torch.Tensor]] + Batch of data containing input tensors. + batch_idx : int + Index of the batch. + dataloader_idx : int + Index of the dataloader. + + Returns + ------- + torch.Tensor + Predicted output tensor. + """ + x, _ = batch + y_hat = self(x) + return y_hat + + def configure_optimizers(self) -> Dict: + """ + Configure the optimizer and learning rate scheduler. + + Returns + ------- + Dict + Dictionary containing the optimizer and scheduler configuration. + """ + optimizer = self._get_optimizer() + if self.lr_scheduler is not None: + scheduler = self._get_scheduler(optimizer) + if isinstance(scheduler, torch.optim.lr_scheduler.ReduceLROnPlateau): + return { + "optimizer": optimizer, + "lr_scheduler": { + "scheduler": scheduler, + "monitor": "val_loss", + }, + } + else: + return {"optimizer": optimizer, "lr_scheduler": scheduler} + return {"optimizer": optimizer} + + def _get_optimizer(self) -> Optimizer: + """ + Get the optimizer based on the specified optimizer name and parameters. + + Returns + ------- + Optimizer + The optimizer instance. + """ + if isinstance(self.optimizer, str): + if self.optimizer.lower() == "adam": + return torch.optim.Adam(self.parameters(), **self.optimizer_params) + elif self.optimizer.lower() == "sgd": + return torch.optim.SGD(self.parameters(), **self.optimizer_params) + else: + raise ValueError(f"Optimizer {self.optimizer} not supported.") + elif isinstance(self.optimizer, Optimizer): + return self.optimizer + else: + raise ValueError( + "Optimizer must be either a string or " + "an instance of torch.optim.Optimizer." + ) + + def _get_scheduler( + self, optimizer: Optimizer + ) -> torch.optim.lr_scheduler._LRScheduler: + """ + Get the lr scheduler based on the specified scheduler name and params. + + Parameters + ---------- + optimizer : Optimizer + The optimizer instance. + + Returns + ------- + torch.optim.lr_scheduler._LRScheduler + The learning rate scheduler instance. + """ + if self.lr_scheduler.lower() == "reduce_lr_on_plateau": + return torch.optim.lr_scheduler.ReduceLROnPlateau( + optimizer, **self.lr_scheduler_params + ) + elif self.lr_scheduler.lower() == "step_lr": + return torch.optim.lr_scheduler.StepLR( + optimizer, **self.lr_scheduler_params + ) + else: + raise ValueError(f"Scheduler {self.lr_scheduler} not supported.") + + def log_metrics( + self, y_hat: torch.Tensor, y: torch.Tensor, prefix: str = "val" + ) -> None: + """ + Log additional metrics during training, validation, or testing. + + Parameters + ---------- + y_hat : torch.Tensor + Predicted output tensor. + y : torch.Tensor + Target output tensor. + prefix : str + Prefix for the logged metrics (e.g., "train", "val", "test"). + """ + for metric in self.logging_metrics: + metric_value = metric(y_hat, y) + self.log( + f"{prefix}_{metric.__class__.__name__}", + metric_value, + on_step=False, + on_epoch=True, + prog_bar=True, + logger=True, + ) diff --git a/pytorch_forecasting/models/temporal_fusion_transformer/tft_version_two.py b/pytorch_forecasting/models/temporal_fusion_transformer/tft_version_two.py new file mode 100644 index 000000000..30f70f98e --- /dev/null +++ b/pytorch_forecasting/models/temporal_fusion_transformer/tft_version_two.py @@ -0,0 +1,218 @@ +######################################################################################## +# Disclaimer: This implementation is based on the new version of data pipeline and is +# experimental, please use with care. +######################################################################################## + +from typing import Dict, List, Optional, Union + +import torch +import torch.nn as nn +from torch.optim import Optimizer + +from pytorch_forecasting.models.base.base_model_refactor import BaseModel + + +class TFT(BaseModel): + def __init__( + self, + loss: nn.Module, + logging_metrics: Optional[List[nn.Module]] = None, + optimizer: Optional[Union[Optimizer, str]] = "adam", + optimizer_params: Optional[Dict] = None, + lr_scheduler: Optional[str] = None, + lr_scheduler_params: Optional[Dict] = None, + hidden_size: int = 64, + num_layers: int = 2, + attention_head_size: int = 4, + dropout: float = 0.1, + metadata: Optional[Dict] = None, + output_size: int = 1, + ): + super().__init__( + loss=loss, + logging_metrics=logging_metrics, + optimizer=optimizer, + optimizer_params=optimizer_params, + lr_scheduler=lr_scheduler, + lr_scheduler_params=lr_scheduler_params, + ) + self.hidden_size = hidden_size + self.num_layers = num_layers + self.attention_head_size = attention_head_size + self.dropout = dropout + self.metadata = metadata + self.output_size = output_size + + self.max_encoder_length = self.metadata["max_encoder_length"] + self.max_prediction_length = self.metadata["max_prediction_length"] + self.encoder_cont = self.metadata["encoder_cont"] + self.encoder_cat = self.metadata["encoder_cat"] + self.static_categorical_features = self.metadata["static_categorical_features"] + self.static_continuous_features = self.metadata["static_continuous_features"] + + total_feature_size = self.encoder_cont + self.encoder_cat + total_static_size = ( + self.static_categorical_features + self.static_continuous_features + ) + + self.encoder_var_selection = nn.Sequential( + nn.Linear(total_feature_size, hidden_size), + nn.ReLU(), + nn.Linear(hidden_size, total_feature_size), + nn.Sigmoid(), + ) + + self.decoder_var_selection = nn.Sequential( + nn.Linear(total_feature_size, hidden_size), + nn.ReLU(), + nn.Linear(hidden_size, total_feature_size), + nn.Sigmoid(), + ) + + self.static_context_linear = ( + nn.Linear(total_static_size, hidden_size) if total_static_size > 0 else None + ) + + self.lstm_encoder = nn.LSTM( + input_size=total_feature_size, + hidden_size=hidden_size, + num_layers=num_layers, + dropout=dropout, + batch_first=True, + ) + + self.lstm_decoder = nn.LSTM( + input_size=total_feature_size, + hidden_size=hidden_size, + num_layers=num_layers, + dropout=dropout, + batch_first=True, + ) + + self.self_attention = nn.MultiheadAttention( + embed_dim=hidden_size, + num_heads=attention_head_size, + dropout=dropout, + batch_first=True, + ) + + self.pre_output = nn.Linear(hidden_size, hidden_size) + self.output_layer = nn.Linear(hidden_size, output_size) + + def forward(self, x: Dict[str, torch.Tensor]) -> Dict[str, torch.Tensor]: + """ + Forward pass of the TFT model. + + Parameters + ---------- + x : Dict[str, torch.Tensor] + Dictionary containing input tensors: + - encoder_cat: Categorical encoder features + - encoder_cont: Continuous encoder features + - decoder_cat: Categorical decoder features + - decoder_cont: Continuous decoder features + - static_categorical_features: Static categorical features + - static_continuous_features: Static continuous features + + Returns + ------- + Dict[str, torch.Tensor] + Dictionary containing output tensors: + - prediction: Prediction output (batch_size, prediction_length, output_size) + """ + batch_size = x["encoder_cont"].shape[0] + + encoder_cat = x.get( + "encoder_cat", + torch.zeros(batch_size, self.max_encoder_length, 0, device=self.device), + ) + encoder_cont = x.get( + "encoder_cont", + torch.zeros(batch_size, self.max_encoder_length, 0, device=self.device), + ) + decoder_cat = x.get( + "decoder_cat", + torch.zeros(batch_size, self.max_prediction_length, 0, device=self.device), + ) + decoder_cont = x.get( + "decoder_cont", + torch.zeros(batch_size, self.max_prediction_length, 0, device=self.device), + ) + + encoder_input = torch.cat([encoder_cont, encoder_cat], dim=2) + decoder_input = torch.cat([decoder_cont, decoder_cat], dim=2) + + static_context = None + if self.static_context_linear is not None: + static_cat = x.get( + "static_categorical_features", + torch.zeros(batch_size, 0, device=self.device), + ) + static_cont = x.get( + "static_continuous_features", + torch.zeros(batch_size, 0, device=self.device), + ) + + if static_cat.size(2) == 0 and static_cont.size(2) == 0: + static_context = None + elif static_cat.size(2) == 0: + static_input = static_cont.to( + dtype=self.static_context_linear.weight.dtype + ) + static_context = self.static_context_linear(static_input) + static_context = static_context.view(batch_size, self.hidden_size) + elif static_cont.size(2) == 0: + static_input = static_cat.to( + dtype=self.static_context_linear.weight.dtype + ) + static_context = self.static_context_linear(static_input) + static_context = static_context.view(batch_size, self.hidden_size) + else: + + static_input = torch.cat([static_cont, static_cat], dim=1).to( + dtype=self.static_context_linear.weight.dtype + ) + static_context = self.static_context_linear(static_input) + static_context = static_context.view(batch_size, self.hidden_size) + + encoder_weights = self.encoder_var_selection(encoder_input) + encoder_input = encoder_input * encoder_weights + + decoder_weights = self.decoder_var_selection(decoder_input) + decoder_input = decoder_input * decoder_weights + + if static_context is not None: + encoder_static_context = static_context.unsqueeze(1).expand( + -1, self.max_encoder_length, -1 + ) + decoder_static_context = static_context.unsqueeze(1).expand( + -1, self.max_prediction_length, -1 + ) + + encoder_output, (h_n, c_n) = self.lstm_encoder(encoder_input) + encoder_output = encoder_output + encoder_static_context + decoder_output, _ = self.lstm_decoder(decoder_input, (h_n, c_n)) + decoder_output = decoder_output + decoder_static_context + else: + encoder_output, (h_n, c_n) = self.lstm_encoder(encoder_input) + decoder_output, _ = self.lstm_decoder(decoder_input, (h_n, c_n)) + + sequence = torch.cat([encoder_output, decoder_output], dim=1) + + if static_context is not None: + expanded_static_context = static_context.unsqueeze(1).expand( + -1, sequence.size(1), -1 + ) + + attended_output, _ = self.self_attention( + sequence + expanded_static_context, sequence, sequence + ) + else: + attended_output, _ = self.self_attention(sequence, sequence, sequence) + + decoder_attended = attended_output[:, -self.max_prediction_length :, :] + + output = nn.functional.relu(self.pre_output(decoder_attended)) + prediction = self.output_layer(output) + + return {"prediction": prediction} From 6364780ae121298e3d98a2c14c6f6747bf62a7b4 Mon Sep 17 00:00:00 2001 From: Aryan Saini Date: Sun, 6 Apr 2025 19:34:57 +0530 Subject: [PATCH 025/139] update docstring --- pytorch_forecasting/data/timeseries.py | 44 +++++++++++++++----------- 1 file changed, 25 insertions(+), 19 deletions(-) diff --git a/pytorch_forecasting/data/timeseries.py b/pytorch_forecasting/data/timeseries.py index bc8300300..9da02d3a0 100644 --- a/pytorch_forecasting/data/timeseries.py +++ b/pytorch_forecasting/data/timeseries.py @@ -2815,25 +2815,31 @@ def __len__(self) -> int: def __getitem__(self, index: int) -> Dict[str, torch.Tensor]: """Get time series data for given index. - It returns: - - * ``t``: ``numpy.ndarray`` of shape (n_timepoints,) - Time index for each time point in the past or present. Aligned with ``y``, - and ``x`` not ending in ``f``. - * ``y``: tensor of shape (n_timepoints, n_targets) - Target values for each time point. Rows are time points, aligned with ``t``. - * ``x``: tensor of shape (n_timepoints, n_features) - Features for each time point. Rows are time points, aligned with ``t``. - * ``group``: tensor of shape (n_groups) - Group identifiers for time series instances. - * ``st``: tensor of shape (n_static_features) - Static features. - * ``cutoff_time``: float or ``numpy.float64`` - Cutoff time for the time series instance. - - Optionally, the following str-keyed entry can be included: - - * ``weights``: tensor of shape (n_timepoints), only if weight is not None + Returns + ------- + t : numpy.ndarray of shape (n_timepoints,) + Time index for each time point in the past or present. Aligned with `y`, + and `x` not ending in `f`. + + y : torch.Tensor of shape (n_timepoints, n_targets) + Target values for each time point. Rows are time points, aligned with `t`. + + x : torch.Tensor of shape (n_timepoints, n_features) + Features for each time point. Rows are time points, aligned with `t`. + + group : torch.Tensor of shape (n_groups,) + Group identifiers for time series instances. + + st : torch.Tensor of shape (n_static_features,) + Static features. + + cutoff_time : float or numpy.float64 + Cutoff time for the time series instance. + + Other Returns + ------------- + weights : torch.Tensor of shape (n_timepoints,), optional + Only included if weights are not `None`. """ group_id = self._group_ids[index] From 257183ce4d2b1f7fd40c95ecd7dc38c8004a017b Mon Sep 17 00:00:00 2001 From: Aryan Saini Date: Fri, 11 Apr 2025 01:54:50 +0530 Subject: [PATCH 026/139] update data_module.py --- pytorch_forecasting/data/data_module.py | 160 ++++++++++++------------ 1 file changed, 80 insertions(+), 80 deletions(-) diff --git a/pytorch_forecasting/data/data_module.py b/pytorch_forecasting/data/data_module.py index 2958f1705..c796b85fa 100644 --- a/pytorch_forecasting/data/data_module.py +++ b/pytorch_forecasting/data/data_module.py @@ -1,15 +1,6 @@ -####################################################################################### -# Disclaimer: This data-module is still work in progress and experimental, please -# use with care. This data-module is a basic skeleton of how the data-handling pipeline -# may look like in the future. -# This is D2 layer that will handle the preprocessing and data loaders. -# For now, this pipeline handles the simplest situation: The whole data can be loaded -# into the memory. -####################################################################################### - from typing import Any, Dict, List, Optional, Tuple, Union -from lightning.pytorch import LightningDataModule +from lightning.pytorch import LightningDataModule, LightningModule from sklearn.preprocessing import RobustScaler, StandardScaler import torch from torch.utils.data import DataLoader, Dataset @@ -19,7 +10,11 @@ NaNLabelEncoder, TorchNormalizer, ) -from pytorch_forecasting.data.timeseries import TimeSeries, _coerce_to_dict +from pytorch_forecasting.data.timeseries import ( + TimeSeries, + _coerce_to_dict, + _coerce_to_list, +) NORMALIZER = Union[TorchNormalizer, NaNLabelEncoder, EncoderNormalizer] @@ -274,7 +269,7 @@ def metadata(self): self._metadata = self._prepare_metadata() return self._metadata - def _preprocess_data(self, indices: torch.Tensor) -> List[Dict[str, Any]]: + def _preprocess_data(self, series_idx: torch.Tensor) -> List[Dict[str, Any]]: """Preprocess the data before feeding it into _ProcessedEncoderDecoderDataset. Preprocessing steps @@ -288,63 +283,58 @@ def _preprocess_data(self, indices: torch.Tensor) -> List[Dict[str, Any]]: TODO: add scalers, target normalizers etc. """ - processed_data = [] + sample = self.time_series_dataset[series_idx] - for idx in indices: - sample = self.time_series_dataset[idx.item()] + target = sample["y"] + features = sample["x"] + times = sample["t"] + cutoff_time = sample["cutoff_time"] - target = sample["y"] - features = sample["x"] - times = sample["t"] - cutoff_time = sample["cutoff_time"] + time_mask = torch.tensor(times <= cutoff_time, dtype=torch.bool) - time_mask = torch.tensor(times <= cutoff_time, dtype=torch.bool) - - if isinstance(target, torch.Tensor): - target = target.float() - else: - target = torch.tensor(target, dtype=torch.float32) - - if isinstance(features, torch.Tensor): - features = features.float() - else: - features = torch.tensor(features, dtype=torch.float32) + if isinstance(target, torch.Tensor): + target = target.float() + else: + target = torch.tensor(target, dtype=torch.float32) - # TODO: add scalers, target normalizers etc. + if isinstance(features, torch.Tensor): + features = features.float() + else: + features = torch.tensor(features, dtype=torch.float32) - categorical = ( - features[:, self.categorical_indices] - if self.categorical_indices - else torch.zeros((features.shape[0], 0)) - ) - continuous = ( - features[:, self.continuous_indices] - if self.continuous_indices - else torch.zeros((features.shape[0], 0)) - ) + # TODO: add scalers, target normalizers etc. - processed_data.append( - { - "features": {"categorical": categorical, "continuous": continuous}, - "target": target, - "static": sample.get("st", None), - "group": sample.get("group", torch.tensor([0])), - "length": len(target), - "time_mask": time_mask, - "times": times, - "cutoff_time": cutoff_time, - } - ) + categorical = ( + features[:, self.categorical_indices] + if self.categorical_indices + else torch.zeros((features.shape[0], 0)) + ) + continuous = ( + features[:, self.continuous_indices] + if self.continuous_indices + else torch.zeros((features.shape[0], 0)) + ) - return processed_data + return { + "features": {"categorical": categorical, "continuous": continuous}, + "target": target, + "static": sample.get("st", None), + "group": sample.get("group", torch.tensor([0])), + "length": len(target), + "time_mask": time_mask, + "times": times, + "cutoff_time": cutoff_time, + } class _ProcessedEncoderDecoderDataset(Dataset): """PyTorch Dataset for processed encoder-decoder time series data. Parameters ---------- - processed_data : List[Dict[str, Any]] - List of preprocessed time series samples. + dataset : TimeSeries + The base time series dataset that provides access to raw data and metadata. + data_module : EncoderDecoderTimeSeriesDataModule + The data module handling preprocessing and metadata configuration. windows : List[Tuple[int, int, int, int]] List of window tuples containing (series_idx, start_idx, enc_length, pred_length). @@ -354,11 +344,13 @@ class _ProcessedEncoderDecoderDataset(Dataset): def __init__( self, - processed_data: List[Dict[str, Any]], + dataset: TimeSeries, + data_module: "EncoderDecoderTimeSeriesDataModule", windows: List[Tuple[int, int, int, int]], add_relative_time_idx: bool = False, ): - self.processed_data = processed_data + self.dataset = dataset + self.data_module = data_module self.windows = windows self.add_relative_time_idx = add_relative_time_idx @@ -410,7 +402,7 @@ def __getitem__(self, idx): Target values for the decoder sequence. """ series_idx, start_idx, enc_length, pred_length = self.windows[idx] - data = self.processed_data[series_idx] + data = self.data_module._preprocess_data(series_idx) end_idx = start_idx + enc_length + pred_length encoder_indices = slice(start_idx, start_idx + enc_length) @@ -457,9 +449,7 @@ def __getitem__(self, idx): return x, y - def _create_windows( - self, processed_data: List[Dict[str, Any]] - ) -> List[Tuple[int, int, int, int]]: + def _create_windows(self, indices: torch.Tensor) -> List[Tuple[int, int, int, int]]: """Generate sliding windows for training, validation, and testing. Returns @@ -477,8 +467,10 @@ def _create_windows( """ windows = [] - for idx, data in enumerate(processed_data): - sequence_length = data["length"] + for idx in indices: + series_idx = idx.item() + sample = self.time_series_dataset[series_idx] + sequence_length = len(sample["y"]) if sequence_length < self.max_encoder_length + self.max_prediction_length: continue @@ -503,7 +495,7 @@ def _create_windows( ): windows.append( ( - idx, + series_idx, start_idx, self.max_encoder_length, self.max_prediction_length, @@ -538,33 +530,41 @@ def setup(self, stage: Optional[str] = None): if stage is None or stage == "fit": if not hasattr(self, "train_dataset") or not hasattr(self, "val_dataset"): - self.train_processed = self._preprocess_data(self._train_indices) - self.val_processed = self._preprocess_data(self._val_indices) - - self.train_windows = self._create_windows(self.train_processed) - self.val_windows = self._create_windows(self.val_processed) + self.train_windows = self._create_windows(self._train_indices) + self.val_windows = self._create_windows(self._val_indices) self.train_dataset = self._ProcessedEncoderDecoderDataset( - self.train_processed, self.train_windows, self.add_relative_time_idx + self.time_series_dataset, + self, + self.train_windows, + self.add_relative_time_idx, ) self.val_dataset = self._ProcessedEncoderDecoderDataset( - self.val_processed, self.val_windows, self.add_relative_time_idx + self.time_series_dataset, + self, + self.val_windows, + self.add_relative_time_idx, ) - elif stage is None or stage == "test": + elif stage == "test": if not hasattr(self, "test_dataset"): - self.test_processed = self._preprocess_data(self._test_indices) - self.test_windows = self._create_windows(self.test_processed) - + self.test_windows = self._create_windows(self._test_indices) self.test_dataset = self._ProcessedEncoderDecoderDataset( - self.test_processed, self.test_windows, self.add_relative_time_idx + self.time_series_dataset, + self, + self.test_windows, + self, + self.add_relative_time_idx, ) elif stage == "predict": predict_indices = torch.arange(len(self.time_series_dataset)) - self.predict_processed = self._preprocess_data(predict_indices) - self.predict_windows = self._create_windows(self.predict_processed) + self.predict_windows = self._create_windows(predict_indices) self.predict_dataset = self._ProcessedEncoderDecoderDataset( - self.predict_processed, self.predict_windows, self.add_relative_time_idx + self.time_series_dataset, + self, + self.predict_windows, + self, + self.add_relative_time_idx, ) def train_dataloader(self): From 9cdcb195c4c9e3f9b6d0e76ef3b6ed889bc14998 Mon Sep 17 00:00:00 2001 From: Aryan Saini Date: Fri, 11 Apr 2025 01:56:55 +0530 Subject: [PATCH 027/139] update data_module.py --- pytorch_forecasting/data/data_module.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/pytorch_forecasting/data/data_module.py b/pytorch_forecasting/data/data_module.py index c796b85fa..9a4a5bf5e 100644 --- a/pytorch_forecasting/data/data_module.py +++ b/pytorch_forecasting/data/data_module.py @@ -553,7 +553,6 @@ def setup(self, stage: Optional[str] = None): self.time_series_dataset, self, self.test_windows, - self, self.add_relative_time_idx, ) elif stage == "predict": @@ -563,7 +562,6 @@ def setup(self, stage: Optional[str] = None): self.time_series_dataset, self, self.predict_windows, - self, self.add_relative_time_idx, ) From ac56d4fd56aeeb1287f162559c67e785de4446f4 Mon Sep 17 00:00:00 2001 From: Aryan Saini Date: Fri, 11 Apr 2025 02:05:58 +0530 Subject: [PATCH 028/139] Add disclaimer --- pytorch_forecasting/data/data_module.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/pytorch_forecasting/data/data_module.py b/pytorch_forecasting/data/data_module.py index 9a4a5bf5e..b33a11d47 100644 --- a/pytorch_forecasting/data/data_module.py +++ b/pytorch_forecasting/data/data_module.py @@ -1,6 +1,15 @@ +####################################################################################### +# Disclaimer: This data-module is still work in progress and experimental, please +# use with care. This data-module is a basic skeleton of how the data-handling pipeline +# may look like in the future. +# This is D2 layer that will handle the preprocessing and data loaders. +# For now, this pipeline handles the simplest situation: The whole data can be loaded +# into the memory. +####################################################################################### + from typing import Any, Dict, List, Optional, Tuple, Union -from lightning.pytorch import LightningDataModule, LightningModule +from lightning.pytorch import LightningDataModule from sklearn.preprocessing import RobustScaler, StandardScaler import torch from torch.utils.data import DataLoader, Dataset @@ -13,7 +22,6 @@ from pytorch_forecasting.data.timeseries import ( TimeSeries, _coerce_to_dict, - _coerce_to_list, ) NORMALIZER = Union[TorchNormalizer, NaNLabelEncoder, EncoderNormalizer] From 4bfff21de1a75be0c93dcb713cb91defe6bc2fad Mon Sep 17 00:00:00 2001 From: Aryan Saini Date: Fri, 11 Apr 2025 12:44:44 +0530 Subject: [PATCH 029/139] update docstring --- pytorch_forecasting/data/data_module.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pytorch_forecasting/data/data_module.py b/pytorch_forecasting/data/data_module.py index b33a11d47..9d4e0b02f 100644 --- a/pytorch_forecasting/data/data_module.py +++ b/pytorch_forecasting/data/data_module.py @@ -465,7 +465,7 @@ def _create_windows(self, indices: torch.Tensor) -> List[Tuple[int, int, int, in List[Tuple[int, int, int, int]] A list of tuples, where each tuple consists of: - ``series_idx`` : int - Index of the time series in `processed_data`. + Index of the time series in `time_series_dataset`. - ``start_idx`` : int Start index of the encoder window. - ``enc_length`` : int @@ -522,7 +522,7 @@ def setup(self, stage: Optional[str] = None): - ``"fit"`` : Prepares training and validation datasets. - ``"test"`` : Prepares the test dataset. - ``"predict"`` : Prepares the dataset for inference. - - ``None`` : Prepares all datasets. + - ``None`` : Prepares ``fit`` datasets. """ total_series = len(self.time_series_dataset) self._split_indices = torch.randperm(total_series) From 8a53ed63933b0b92d752eaa707eadc7c45d35566 Mon Sep 17 00:00:00 2001 From: Aryan Saini Date: Sat, 19 Apr 2025 19:37:40 +0530 Subject: [PATCH 030/139] Add tests for D1,D2 layer --- pytorch_forecasting/data/data_module.py | 56 ++- tests/test_data/test_d1.py | 379 +++++++++++++++++++ tests/test_data/test_data_module.py | 464 ++++++++++++++++++++++++ 3 files changed, 895 insertions(+), 4 deletions(-) create mode 100644 tests/test_data/test_d1.py create mode 100644 tests/test_data/test_data_module.py diff --git a/pytorch_forecasting/data/data_module.py b/pytorch_forecasting/data/data_module.py index 9d4e0b02f..1203e83ac 100644 --- a/pytorch_forecasting/data/data_module.py +++ b/pytorch_forecasting/data/data_module.py @@ -432,11 +432,59 @@ def __getitem__(self, idx): else torch.zeros(pred_length, dtype=torch.bool) ) + encoder_cat = data["features"]["categorical"][encoder_indices] + encoder_cont = data["features"]["continuous"][encoder_indices] + + features = data["features"] + metadata = self.data_module.time_series_metadata + + known_cat_indices = [ + i + for i, col in enumerate(metadata["cols"]["x"]) + if metadata["col_type"].get(col) == "C" + and metadata["col_known"].get(col) == "K" + ] + + known_cont_indices = [ + i + for i, col in enumerate(metadata["cols"]["x"]) + if metadata["col_type"].get(col) == "F" + and metadata["col_known"].get(col) == "K" + ] + + cat_map = { + orig_idx: i + for i, orig_idx in enumerate(self.data_module.categorical_indices) + } + cont_map = { + orig_idx: i + for i, orig_idx in enumerate(self.data_module.continuous_indices) + } + + mapped_known_cat_indices = [ + cat_map[idx] for idx in known_cat_indices if idx in cat_map + ] + mapped_known_cont_indices = [ + cont_map[idx] for idx in known_cont_indices if idx in cont_map + ] + + decoder_cat = ( + features["categorical"][decoder_indices][:, mapped_known_cat_indices] + if mapped_known_cat_indices + else torch.zeros((pred_length, 0)) + ) + + decoder_cont = ( + features["continuous"][decoder_indices][:, mapped_known_cont_indices] + if mapped_known_cont_indices + else torch.zeros((pred_length, 0)) + ) + x = { - "encoder_cat": data["features"]["categorical"][encoder_indices], - "encoder_cont": data["features"]["continuous"][encoder_indices], - "decoder_cat": data["features"]["categorical"][decoder_indices], - "decoder_cont": data["features"]["continuous"][decoder_indices], + "encoder_cat": encoder_cat, + "encoder_cont": encoder_cont, + "decoder_cat": decoder_cat, + "decoder_cont": decoder_cont, "encoder_lengths": torch.tensor(enc_length), "decoder_lengths": torch.tensor(pred_length), "decoder_target_lengths": torch.tensor(pred_length), diff --git a/tests/test_data/test_d1.py b/tests/test_data/test_d1.py new file mode 100644 index 000000000..b32c13213 --- /dev/null +++ b/tests/test_data/test_d1.py @@ -0,0 +1,379 @@ +import numpy as np +import pandas as pd +import pytest +import torch + +from pytorch_forecasting.data.timeseries import TimeSeries + + +@pytest.fixture +def sample_data(): + """Create time series data for testing.""" + dates = pd.date_range(start="2023-01-01", periods=10, freq="D") + data = pd.DataFrame( + { + "timestamp": dates, + "target_value": np.sin(np.arange(10)) + 10, + "feature1": np.random.randn(10), + "feature2": np.random.randn(10), + "feature3": np.random.randn(10), + "group_id": [1, 1, 1, 1, 1, 2, 2, 2, 2, 2], + "weight": np.abs(np.random.randn(10)) + 0.1, + "static_feat": [10, 10, 10, 10, 10, 20, 20, 20, 20, 20], + } + ) + return data + + +@pytest.fixture +def future_data(): + """Create future time series data.""" + dates = pd.date_range(start="2023-01-11", periods=5, freq="D") + data = pd.DataFrame( + { + "timestamp": dates, + "feature1": np.random.randn(5), + "feature2": np.random.randn(5), + "feature3": np.random.randn(5), + "group_id": [1, 1, 1, 2, 2], + "weight": np.abs(np.random.randn(5)) + 0.1, + "static_feat": [10, 10, 10, 20, 20], + } + ) + return data + + +def test_init_basic(sample_data): + """Test basic initialization of TimeSeries class. + + Ensures that the class stores time, target, and correctly detects feature columns + when no group, known/unknown features, or static/weight features are specified.""" + ts = TimeSeries(data=sample_data, time="timestamp", target="target_value") + + assert ts.time == "timestamp" + assert ts.target == ["target_value"] + assert len(ts.feature_cols) == 6 # All columns except timestamp, target_value + assert len(ts) == 1 # Single group by default + + +def test_init_with_groups(sample_data): + """Test initialization with group parameter. + + Verifies that data is grouped correctly and each group is handled as a + separate time series. + """ + ts = TimeSeries( + data=sample_data, time="timestamp", target="target_value", group=["group_id"] + ) + + assert ts.group == ["group_id"] + assert len(ts) == 2 # Two groups (1 and 2) + assert set(ts._group_ids) == {1, 2} + + +def test_init_with_features_categorization(sample_data): + """Test feature categorization. + + Ensures that numeric, categorical, and static features are categorized and + stored correctly in metadata.""" + ts = TimeSeries( + data=sample_data, + time="timestamp", + target="target_value", + num=["feature1", "feature2", "feature3"], + cat=[], + static=["static_feat"], + ) + + assert ts.num == ["feature1", "feature2", "feature3"] + assert ts.cat == [] + assert ts.static == ["static_feat"] + assert ts.metadata["col_type"]["feature1"] == "F" + assert ts.metadata["col_type"]["feature2"] == "F" + + +def test_init_with_known_unknown(sample_data): + """Test known and unknown features classification. + + Checks if the known and unknown feature categorization is correctly set + and stored in metadata.""" + ts = TimeSeries( + data=sample_data, + time="timestamp", + target="target_value", + known=["feature1"], + unknown=["feature2", "feature3"], + ) + + assert ts.known == ["feature1"] + assert ts.unknown == ["feature2", "feature3"] + assert ts.metadata["col_known"]["feature1"] == "K" + assert ts.metadata["col_known"]["feature2"] == "U" + + +def test_init_with_weight(sample_data): + """Test initialization with weight parameter. + + Verifies that the weight column is stored correctly and excluded + from the feature columns.""" + ts = TimeSeries( + data=sample_data, time="timestamp", target="target_value", weight="weight" + ) + + assert ts.weight == "weight" + assert "weight" not in ts.feature_cols + + +def test_getitem_basic(sample_data): + """Test __getitem__ with basic configuration. + + Checks the output structure of a single time series without grouping, + ensuring x, y are tensors of correct shapes.""" + ts = TimeSeries(data=sample_data, time="timestamp", target="target_value") + + result = ts[0] + assert torch.is_tensor(result["y"]) + assert torch.is_tensor(result["x"]) + assert "t" in result + assert "cutoff_time" in result + assert len(result["y"]) == 10 # 10 data points + assert result["y"].shape == (10, 1) # One target variable + assert result["x"].shape[1] == 6 # Six feature columns + + +def test_getitem_with_groups(sample_data): + """Test __getitem__ with groups parameter. + + Verifies the per-group access using index and checks that each group + has the correct number of time steps.""" + ts = TimeSeries( + data=sample_data, time="timestamp", target="target_value", group=["group_id"] + ) + + # group (1) + result_g1 = ts[0] + assert len(result_g1["t"]) == 5 # 5 data points in group 1 + + # group (2) + result_g2 = ts[1] + assert len(result_g2["t"]) == 5 # 5 data points in group 2 + + +def test_getitem_with_static(sample_data): + """Test __getitem__ with static features. + + Ensures static features are included in the output and correctly + mapped per group.""" + ts = TimeSeries( + data=sample_data, + time="timestamp", + target="target_value", + group=["group_id"], + static=["static_feat"], + ) + + result_g1 = ts[0] + result_g2 = ts[1] + + assert torch.is_tensor(result_g1["st"]) + assert result_g1["st"].item() == 10 # Static feature for group 1 + assert result_g2["st"].item() == 20 # Static feature for group 2 + + +def test_getitem_with_weight(sample_data): + """Test __getitem__ with weight parameter. + + Validates that weights are correctly returned in the output and have the + expected length and type.""" + ts = TimeSeries( + data=sample_data, time="timestamp", target="target_value", weight="weight" + ) + + result = ts[0] + assert "weights" in result + assert torch.is_tensor(result["weights"]) + assert len(result["weights"]) == 10 + + +def test_with_future_data(sample_data, future_data): + """Test with future data provided. + + Verifies that future time steps are appended to the end of each group, + especially for known features.""" + ts = TimeSeries( + data=sample_data, + data_future=future_data, + time="timestamp", + target="target_value", + group=["group_id"], + known=["feature1"], + ) + + result_g1 = ts[0] # Group 1 + + assert len(result_g1["t"]) == 8 # 5 original + 3 future for group 1 + + feature1_idx = ts.feature_cols.index("feature1") + assert not torch.isnan( + result_g1["x"][-1, feature1_idx] + ) # feature1 is not NaN in last row + + +def test_future_data_with_weights(sample_data, future_data): + """Test handling of weights with future data. + + Ensures that weights from future data are combined properly and match the + time indices.""" + ts = TimeSeries( + data=sample_data, + data_future=future_data, + time="timestamp", + target="target_value", + group=["group_id"], + weight="weight", + ) + + result = ts[0] # Group 1 + assert "weights" in result + assert torch.is_tensor(result["weights"]) + assert len(result["weights"]) == len(result["t"]) + + +def test_future_data_missing_columns(sample_data): + """Test handling when future data is missing some columns. + + Verifies the handling of missing feature columns in future data by + checking NaN padding.""" + dates = pd.date_range(start="2023-01-11", periods=5, freq="D") + incomplete_future = pd.DataFrame( + { + "timestamp": dates, + "feature1": np.random.randn(5), + # Missing feature2, feature3 + "group_id": [1, 1, 1, 2, 2], + "weight": np.abs(np.random.randn(5)) + 0.1, + } + ) + + ts = TimeSeries( + data=sample_data, + data_future=incomplete_future, + time="timestamp", + target="target_value", + group=["group_id"], + known=["feature1"], + ) + + result = ts[0] + # Check that missing features are NaN in future timepoints + future_indices = np.where(result["t"] >= np.datetime64("2023-01-11"))[0] + feature2_idx = ts.feature_cols.index("feature2") + feature3_idx = ts.feature_cols.index("feature3") + assert torch.isnan(result["x"][future_indices[0], feature2_idx]) + assert torch.isnan(result["x"][future_indices[0], feature3_idx]) + + +def test_different_future_groups(sample_data): + """Test with future data that has different groups than original data. + + Ensures that groups present only in future data are ignored if not + in the original dataset.""" + dates = pd.date_range(start="2023-01-11", periods=5, freq="D") + future_with_new_group = pd.DataFrame( + { + "timestamp": dates, + "feature1": np.random.randn(5), + "feature2": np.random.randn(5), + "feature3": np.random.randn(5), + "group_id": [1, 1, 3, 3, 3], # Group 3 is new + "weight": np.abs(np.random.randn(5)) + 0.1, + "static_feat": [10, 10, 30, 30, 30], + } + ) + + ts = TimeSeries( + data=sample_data, + data_future=future_with_new_group, + time="timestamp", + target="target_value", + group=["group_id"], + ) + + # Original data has groups 1 and 2, but not 3 + assert len(ts) == 2 + assert 3 not in ts._group_ids + + +def test_multiple_targets(sample_data): + """Test handling of multiple target variables. + + Verifies that multiple target columns are handled and returned + as the correct shape in the output.""" + sample_data["target_value2"] = np.cos(np.arange(10)) + 5 + + ts = TimeSeries( + data=sample_data, time="timestamp", target=["target_value", "target_value2"] + ) + + result = ts[0] + assert result["y"].shape == (10, 2) # Two target variables + + +def test_empty_groups(): + """Test handling of empty groups. + + Confirms that the class handles datasets with a single group and + no empty group errors occur.""" + data = pd.DataFrame( + { + "timestamp": pd.date_range(start="2023-01-01", periods=5, freq="D"), + "target_value": np.random.randn(5), + "group_id": [1, 1, 1, 1, 1], # Only one group + } + ) + + ts = TimeSeries( + data=data, time="timestamp", target="target_value", group=["group_id"] + ) + + assert len(ts) == 1 # Only one group + + +def test_metadata_structure(sample_data): + """Test the structure of metadata. + + Ensures the metadata dictionary includes the expected keys and + correct mappings of feature roles.""" + ts = TimeSeries( + data=sample_data, + time="timestamp", + target="target_value", + num=["feature1", "feature2", "feature3"], + cat=[], # No categorical features + static=["static_feat"], + known=["feature1"], + unknown=["feature2", "feature3"], + ) + + metadata = ts.get_metadata() + + assert "cols" in metadata + assert "col_type" in metadata + assert "col_known" in metadata + + assert metadata["cols"]["y"] == ["target_value"] + assert set(metadata["cols"]["x"]) == { + "feature1", + "feature2", + "feature3", + "group_id", + "weight", + "static_feat", + } + assert metadata["cols"]["st"] == ["static_feat"] + + assert metadata["col_type"]["feature1"] == "F" + assert metadata["col_type"]["feature2"] == "F" + + assert metadata["col_known"]["feature1"] == "K" + assert metadata["col_known"]["feature2"] == "U" diff --git a/tests/test_data/test_data_module.py b/tests/test_data/test_data_module.py new file mode 100644 index 000000000..c14e3d8f4 --- /dev/null +++ b/tests/test_data/test_data_module.py @@ -0,0 +1,464 @@ +import numpy as np +import pandas as pd +import pytest + +from pytorch_forecasting.data.data_module import EncoderDecoderTimeSeriesDataModule +from pytorch_forecasting.data.timeseries import TimeSeries + + +@pytest.fixture +def sample_timeseries_data(): + """Create a sample time series dataset with only numerical values.""" + num_groups = 5 + seq_length = 100 + + groups = [] + times = [] + values = [] + categorical_feature = [] + continuous_feature1 = [] + continuous_feature2 = [] + known_future = [] + + for g in range(num_groups): + for t in range(seq_length): + groups.append(g) + times.append(pd.Timestamp("2020-01-01") + pd.Timedelta(days=t)) + + value = 10 + 0.1 * t + 5 * np.sin(t / 10) + g * 2 + np.random.normal(0, 1) + values.append(value) + + categorical_feature.append(np.random.choice([0, 1, 2])) + + continuous_feature1.append(np.random.normal(g, 1)) + continuous_feature2.append(value * 0.5 + np.random.normal(0, 0.5)) + + known_future.append(t % 7) + + df = pd.DataFrame( + { + "group": groups, + "time": times, + "target": values, + "cat_feat": categorical_feature, + "cont_feat1": continuous_feature1, + "cont_feat2": continuous_feature2, + "known_future": known_future, + } + ) + + time_series = TimeSeries( + data=df, + time="time", + target="target", + group=["group"], + num=["cont_feat1", "cont_feat2", "known_future"], + cat=["cat_feat"], + known=["known_future"], + ) + + return time_series + + +@pytest.fixture +def data_module(sample_timeseries_data): + """Create a data module instance.""" + dm = EncoderDecoderTimeSeriesDataModule( + time_series_dataset=sample_timeseries_data, + max_encoder_length=24, + max_prediction_length=12, + batch_size=4, + train_val_test_split=(0.7, 0.15, 0.15), + ) + return dm + + +def test_init(sample_timeseries_data): + """Test the initialization of the data module. + + Verifies hyperparameter assignment and basic time_series_metadata creation.""" + dm = EncoderDecoderTimeSeriesDataModule( + time_series_dataset=sample_timeseries_data, + max_encoder_length=24, + max_prediction_length=12, + batch_size=8, + ) + + assert dm.max_encoder_length == 24 + assert dm.max_prediction_length == 12 + assert dm.min_encoder_length == 24 + assert dm.min_prediction_length == 12 + assert dm.batch_size == 8 + assert dm.train_val_test_split == (0.7, 0.15, 0.15) + + assert isinstance(dm.time_series_metadata, dict) + assert "cols" in dm.time_series_metadata + + +def test_prepare_metadata(data_module): + """Test the metadata preparation method. + + Ensures that internal metadata keys are created correctly.""" + metadata = data_module._prepare_metadata() + + assert "encoder_cat" in metadata + assert "encoder_cont" in metadata + assert "decoder_cat" in metadata + assert "decoder_cont" in metadata + assert "target" in metadata + assert "max_encoder_length" in metadata + assert "max_prediction_length" in metadata + + assert metadata["max_encoder_length"] == 24 + assert metadata["max_prediction_length"] == 12 + + +def test_metadata_property(data_module): + """Test the metadata property. + + Confirms caching behavior and correct feature counts.""" + metadata = data_module.metadata + + # Should return the same object when called multiple times (caching) + assert data_module.metadata is metadata + + assert metadata["encoder_cat"] == 1 # cat_feat + assert metadata["encoder_cont"] == 3 # cont_feat1, cont_feat2, known_future + assert metadata["decoder_cat"] == 0 # No categorical features marked as known + assert metadata["decoder_cont"] == 1 # Only known_future marked as known + + +# def test_setup(data_module): +# """Test the setup method that prepares the datasets.""" +# data_module.setup(stage="fit") +# print(data_module._val_indices) +# assert hasattr(data_module, "train_dataset") +# assert hasattr(data_module, "val_dataset") +# assert len(data_module.train_windows) > 0 +# assert len(data_module.val_windows) > 0 +# +# data_module.setup(stage="test") +# assert hasattr(data_module, "test_dataset") +# assert len(data_module.test_windows) > 0 +# +# data_module.setup(stage="predict") +# assert hasattr(data_module, "predict_dataset") +# assert len(data_module.predict_windows) > 0 + + +def test_create_windows(data_module): + """Test the window creation logic. + + Validates window structure and length settings.""" + data_module.setup() + + windows = data_module._create_windows(data_module._train_indices) + + assert len(windows) > 0 + + for window in windows: + assert len(window) == 4 + assert window[2] == data_module.max_encoder_length + assert window[3] == data_module.max_prediction_length + + +def test_dataloader_creation(data_module): + """Test that dataloaders are created correctly. + + Checks batch sizes and dataloader instantiation across all stages.""" + data_module.setup() + + train_loader = data_module.train_dataloader() + assert train_loader.batch_size == data_module.batch_size + assert train_loader.num_workers == data_module.num_workers + + val_loader = data_module.val_dataloader() + assert val_loader.batch_size == data_module.batch_size + + data_module.setup(stage="test") + test_loader = data_module.test_dataloader() + assert test_loader.batch_size == data_module.batch_size + + data_module.setup(stage="predict") + predict_loader = data_module.predict_dataloader() + assert predict_loader.batch_size == data_module.batch_size + + +def test_processed_dataset(data_module): + """Test the internal ProcessedEncoderDecoderDataset class. + + Verifies sample structure and tensor dimensions for encoder/decoder inputs.""" + data_module.setup() + + assert len(data_module.train_dataset) == len(data_module.train_windows) + assert len(data_module.val_dataset) == len(data_module.val_windows) + + x, y = data_module.train_dataset[0] + + required_keys = [ + "encoder_cat", + "encoder_cont", + "decoder_cat", + "decoder_cont", + "encoder_lengths", + "decoder_lengths", + "decoder_target_lengths", + "groups", + "encoder_time_idx", + "decoder_time_idx", + "target_scale", + "encoder_mask", + "decoder_mask", + ] + + for key in required_keys: + assert key in x + + assert x["encoder_cat"].shape[0] == data_module.max_encoder_length + assert x["decoder_cat"].shape[0] == data_module.max_prediction_length + + metadata = data_module.time_series_metadata + known_cat_count = len( + [ + col + for col in metadata["cols"]["x"] + if metadata["col_type"].get(col) == "C" + and metadata["col_known"].get(col) == "K" + ] + ) + + known_cont_count = len( + [ + col + for col in metadata["cols"]["x"] + if metadata["col_type"].get(col) == "F" + and metadata["col_known"].get(col) == "K" + ] + ) + + assert x["decoder_cat"].shape[1] == known_cat_count + assert x["decoder_cont"].shape[1] == known_cont_count + + assert y.shape[0] == data_module.max_prediction_length + + +def test_collate_fn(data_module): + """Test the collate function that combines batch samples. + + Ensures proper stacking of dictionary keys and batch outputs.""" + data_module.setup() + + batch_size = 3 + batch = [data_module.train_dataset[i] for i in range(batch_size)] + + x_batch, y_batch = data_module.collate_fn(batch) + + for key in x_batch: + assert x_batch[key].shape[0] == batch_size + + metadata = data_module.time_series_metadata + known_cat_count = len( + [ + col + for col in metadata["cols"]["x"] + if metadata["col_type"].get(col) == "C" + and metadata["col_known"].get(col) == "K" + ] + ) + + known_cont_count = len( + [ + col + for col in metadata["cols"]["x"] + if metadata["col_type"].get(col) == "F" + and metadata["col_known"].get(col) == "K" + ] + ) + + assert x_batch["decoder_cat"].shape[2] == known_cat_count + assert x_batch["decoder_cont"].shape[2] == known_cont_count + assert y_batch.shape[0] == batch_size + assert y_batch.shape[1] == data_module.max_prediction_length + + +def test_full_dataloader_iteration(data_module): + """Test a full iteration through the train dataloader. + + Confirms batch retrieval and tensor dimensions match configuration.""" + data_module.setup() + train_loader = data_module.train_dataloader() + + batch = next(iter(train_loader)) + x_batch, y_batch = batch + + assert x_batch["encoder_cat"].shape[0] == data_module.batch_size + assert x_batch["encoder_cat"].shape[1] == data_module.max_encoder_length + + metadata = data_module.time_series_metadata + known_cat_count = len( + [ + col + for col in metadata["cols"]["x"] + if metadata["col_type"].get(col) == "C" + and metadata["col_known"].get(col) == "K" + ] + ) + + known_cont_count = len( + [ + col + for col in metadata["cols"]["x"] + if metadata["col_type"].get(col) == "F" + and metadata["col_known"].get(col) == "K" + ] + ) + + assert x_batch["decoder_cat"].shape[0] == data_module.batch_size + assert x_batch["decoder_cat"].shape[2] == known_cat_count + assert x_batch["decoder_cont"].shape[0] == data_module.batch_size + assert x_batch["decoder_cont"].shape[2] == known_cont_count + assert y_batch.shape[0] == data_module.batch_size + assert y_batch.shape[1] == data_module.max_prediction_length + + +def test_variable_encoder_lengths(sample_timeseries_data): + """Test with variable encoder lengths. + + Ensures random length behavior is respected and functional.""" + dm = EncoderDecoderTimeSeriesDataModule( + time_series_dataset=sample_timeseries_data, + max_encoder_length=24, + min_encoder_length=12, + max_prediction_length=12, + batch_size=4, + randomize_length=True, + ) + + dm.setup() + assert dm.min_encoder_length == 12 + assert dm.max_encoder_length == 24 + + +def test_preprocess_data(data_module, sample_timeseries_data): + """Test the _preprocess_data method. + + Checks preprocessing output structure and alignment with raw data.""" + if not hasattr(data_module, "_split_indices"): + data_module.setup() + + series_idx = data_module._train_indices[0] + + processed = data_module._preprocess_data(series_idx) + + assert "features" in processed + assert "categorical" in processed["features"] + assert "continuous" in processed["features"] + assert "target" in processed + assert "time_mask" in processed + + original_sample = sample_timeseries_data[series_idx.item()] + expected_length = len(original_sample["y"]) + + assert processed["features"]["categorical"].shape[0] == expected_length + assert processed["features"]["continuous"].shape[0] == expected_length + assert processed["target"].shape[0] == expected_length + + +def test_with_static_features(): + """Test with static features included. + + Validates static feature support in both metadata and sample input.""" + df = pd.DataFrame( + { + "group": [0, 0, 0, 1, 1, 1], + "time": pd.date_range("2020-01-01", periods=6), + "target": [1, 2, 3, 4, 5, 6], + "static_cat": [0, 0, 0, 1, 1, 1], + "static_num": [10, 10, 10, 20, 20, 20], + "feature1": [0.1, 0.2, 0.3, 0.4, 0.5, 0.6], + } + ) + + ts = TimeSeries( + data=df, + time="time", + target="target", + group=["group"], + num=["feature1", "static_num"], + static=["static_cat", "static_num"], + cat=["static_cat"], + ) + + dm = EncoderDecoderTimeSeriesDataModule( + time_series_dataset=ts, + max_encoder_length=2, + max_prediction_length=1, + batch_size=2, + ) + + dm.setup() + + metadata = dm.metadata + assert metadata["static_categorical_features"] == 1 + assert metadata["static_continuous_features"] == 1 + + x, y = dm.train_dataset[0] + assert "static_categorical_features" in x + assert "static_continuous_features" in x + + +# def test_different_train_val_test_split(sample_timeseries_data): +# """Test with different train/val/test split ratios.""" +# dm = EncoderDecoderTimeSeriesDataModule( +# time_series_dataset=sample_timeseries_data, +# max_encoder_length=24, +# max_prediction_length=12, +# batch_size=4, +# train_val_test_split=(0.8, 0.1, 0.1), +# ) +# +# dm.setup() +# +# total_series = len(sample_timeseries_data) +# expected_train = int(0.8 * total_series) +# expected_val = int(0.1 * total_series) +# +# assert len(dm._train_indices) == expected_train +# assert len(dm._val_indices) == expected_val +# assert len(dm._test_indices) == total_series - expected_train - expected_val + + +def test_multivariate_target(): + """Test with multivariate target (multiple target columns). + + Verifies correct handling of multivariate targets in data pipeline.""" + df = pd.DataFrame( + { + "group": np.repeat([0, 1], 50), + "time": np.tile(pd.date_range("2020-01-01", periods=50), 2), + "target1": np.random.normal(0, 1, 100), + "target2": np.random.normal(5, 2, 100), + "feature1": np.random.normal(0, 1, 100), + "feature2": np.random.normal(0, 1, 100), + } + ) + + ts = TimeSeries( + data=df, + time="time", + target=["target1", "target2"], + group=["group"], + num=["feature1", "feature2"], + ) + + dm = EncoderDecoderTimeSeriesDataModule( + time_series_dataset=ts, + max_encoder_length=10, + max_prediction_length=5, + batch_size=4, + ) + + dm.setup() + + x, y = dm.train_dataset[0] + assert y.shape[-1] == 2 From cdecb770a63269c965261cee3a54744449b445a4 Mon Sep 17 00:00:00 2001 From: Aryan Saini Date: Sat, 19 Apr 2025 19:44:16 +0530 Subject: [PATCH 031/139] Code quality --- pytorch_forecasting/data/timeseries.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pytorch_forecasting/data/timeseries.py b/pytorch_forecasting/data/timeseries.py index 6b2662e95..fda08d561 100644 --- a/pytorch_forecasting/data/timeseries.py +++ b/pytorch_forecasting/data/timeseries.py @@ -9,7 +9,7 @@ from copy import copy as _copy, deepcopy from functools import lru_cache import inspect -from typing import Any, Callable, Optional, Type, TypeVar, Union +from typing import Any, Callable, Dict, List, Optional, Type, TypeVar, Union import warnings import numpy as np From 20aafb749cfebdb1f9789b4dff5120fa8527db74 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Franz=20Kir=C3=A1ly?= Date: Wed, 30 Apr 2025 18:40:01 +0200 Subject: [PATCH 032/139] refactor file --- pytorch_forecasting/data/__init__.py | 3 +- .../data/timeseries/__init__.py | 9 + .../data/timeseries/_coerce.py | 25 ++ .../_timeseries.py} | 286 +----------------- .../data/timeseries/_timeseries_v2.py | 276 +++++++++++++++++ 5 files changed, 314 insertions(+), 285 deletions(-) create mode 100644 pytorch_forecasting/data/timeseries/__init__.py create mode 100644 pytorch_forecasting/data/timeseries/_coerce.py rename pytorch_forecasting/data/{timeseries.py => timeseries/_timeseries.py} (90%) create mode 100644 pytorch_forecasting/data/timeseries/_timeseries_v2.py diff --git a/pytorch_forecasting/data/__init__.py b/pytorch_forecasting/data/__init__.py index 301c8394d..17be285a0 100644 --- a/pytorch_forecasting/data/__init__.py +++ b/pytorch_forecasting/data/__init__.py @@ -13,10 +13,11 @@ TorchNormalizer, ) from pytorch_forecasting.data.samplers import TimeSynchronizedBatchSampler -from pytorch_forecasting.data.timeseries import TimeSeriesDataSet +from pytorch_forecasting.data.timeseries import TimeSeries, TimeSeriesDataSet __all__ = [ "TimeSeriesDataSet", + "TimeSeries", "NaNLabelEncoder", "GroupNormalizer", "TorchNormalizer", diff --git a/pytorch_forecasting/data/timeseries/__init__.py b/pytorch_forecasting/data/timeseries/__init__.py new file mode 100644 index 000000000..7734cccf2 --- /dev/null +++ b/pytorch_forecasting/data/timeseries/__init__.py @@ -0,0 +1,9 @@ +"""Data loaders for time series data.""" + +from pytorch_forecasting.data.timeseries._timeseries_v2 import TimeSeries +from pytorch_forecasting.data.timeseries._timeseries import TimeSeriesDataSet + +__all__ = [ + "TimeSeriesDataSet", + "TimeSeries", +] diff --git a/pytorch_forecasting/data/timeseries/_coerce.py b/pytorch_forecasting/data/timeseries/_coerce.py new file mode 100644 index 000000000..328431aa8 --- /dev/null +++ b/pytorch_forecasting/data/timeseries/_coerce.py @@ -0,0 +1,25 @@ +"""Coercion functions for various data types.""" + +from copy import deepcopy + + +def _coerce_to_list(obj): + """Coerce object to list. + + None is coerced to empty list, otherwise list constructor is used. + """ + if obj is None: + return [] + if isinstance(obj, str): + return [obj] + return list(obj) + + +def _coerce_to_dict(obj): + """Coerce object to dict. + + None is coerce to empty dict, otherwise deepcopy is used. + """ + if obj is None: + return {} + return deepcopy(obj) diff --git a/pytorch_forecasting/data/timeseries.py b/pytorch_forecasting/data/timeseries/_timeseries.py similarity index 90% rename from pytorch_forecasting/data/timeseries.py rename to pytorch_forecasting/data/timeseries/_timeseries.py index fda08d561..263e0ea3a 100644 --- a/pytorch_forecasting/data/timeseries.py +++ b/pytorch_forecasting/data/timeseries/_timeseries.py @@ -9,7 +9,7 @@ from copy import copy as _copy, deepcopy from functools import lru_cache import inspect -from typing import Any, Callable, Dict, List, Optional, Type, TypeVar, Union +from typing import Any, Callable, Optional, Type, TypeVar, Union import warnings import numpy as np @@ -31,6 +31,7 @@ TorchNormalizer, ) from pytorch_forecasting.data.samplers import TimeSynchronizedBatchSampler +from pytorch_forecasting.data.timeseries._coerce import _coerce_to_dict, _coerce_to_list from pytorch_forecasting.utils import repr_class from pytorch_forecasting.utils._dependencies import _check_matplotlib @@ -2663,286 +2664,3 @@ def __repr__(self) -> str: attributes=self.get_parameters(), extra_attributes=dict(length=len(self)), ) - - -def _coerce_to_list(obj): - """Coerce object to list. - - None is coerced to empty list, otherwise list constructor is used. - """ - if obj is None: - return [] - if isinstance(obj, str): - return [obj] - return list(obj) - - -def _coerce_to_dict(obj): - """Coerce object to dict. - - None is coerce to empty dict, otherwise deepcopy is used. - """ - if obj is None: - return {} - return deepcopy(obj) - - -####################################################################################### -# Disclaimer: This dataset class is still work in progress and experimental, please -# use with care. This class is a basic skeleton of how the data-handling pipeline may -# look like in the future. -# This is the D1 layer that is a "Raw Dataset Layer" mainly for raw data ingestion -# and turning the data to tensors. -# For now, this pipeline handles the simplest situation: The whole data can be loaded -# into the memory. -####################################################################################### - - -class TimeSeries(Dataset): - """PyTorch Dataset for time series data stored in pandas DataFrame. - - Parameters - ---------- - data : pd.DataFrame - data frame with sequence data. - Column names must all be str, and contain str as referred to below. - data_future : pd.DataFrame, optional, default=None - data frame with future data. - Column names must all be str, and contain str as referred to below. - May contain only columns that are in time, group, weight, known, or static. - time : str, optional, default = first col not in group_ids, weight, target, static. - integer typed column denoting the time index within ``data``. - This column is used to determine the sequence of samples. - If there are no missing observations, - the time index should increase by ``+1`` for each subsequent sample. - The first time_idx for each series does not necessarily - have to be ``0`` but any value is allowed. - target : str or List[str], optional, default = last column (at iloc -1) - column(s) in ``data`` denoting the forecasting target. - Can be categorical or numerical dtype. - group : List[str], optional, default = None - list of column names identifying a time series instance within ``data``. - This means that the ``group`` together uniquely identify an instance, - and ``group`` together with ``time`` uniquely identify a single observation - within a time series instance. - If ``None``, the dataset is assumed to be a single time series. - weight : str, optional, default=None - column name for weights. - If ``None``, it is assumed that there is no weight column. - num : list of str, optional, default = all columns with dtype in "fi" - list of numerical variables in ``data``, - list may also contain list of str, which are then grouped together. - cat : list of str, optional, default = all columns with dtype in "Obc" - list of categorical variables in ``data``, - list may also contain list of str, which are then grouped together - (e.g. useful for product categories). - known : list of str, optional, default = all variables - list of variables that change over time and are known in the future, - list may also contain list of str, which are then grouped together - (e.g. useful for special days or promotion categories). - unknown : list of str, optional, default = no variables - list of variables that are not known in the future, - list may also contain list of str, which are then grouped together - (e.g. useful for weather categories). - static : list of str, optional, default = all variables not in known, unknown - list of variables that do not change over time, - list may also contain list of str, which are then grouped together. - """ - - def __init__( - self, - data: pd.DataFrame, - data_future: Optional[pd.DataFrame] = None, - time: Optional[str] = None, - target: Optional[Union[str, List[str]]] = None, - group: Optional[List[str]] = None, - weight: Optional[str] = None, - num: Optional[List[Union[str, List[str]]]] = None, - cat: Optional[List[Union[str, List[str]]]] = None, - known: Optional[List[Union[str, List[str]]]] = None, - unknown: Optional[List[Union[str, List[str]]]] = None, - static: Optional[List[Union[str, List[str]]]] = None, - ): - - self.data = data - self.data_future = data_future - self.time = time - self.target = _coerce_to_list(target) - self.group = _coerce_to_list(group) - self.weight = weight - self.num = _coerce_to_list(num) - self.cat = _coerce_to_list(cat) - self.known = _coerce_to_list(known) - self.unknown = _coerce_to_list(unknown) - self.static = _coerce_to_list(static) - - self.feature_cols = [ - col - for col in data.columns - if col not in [self.time] + self.group + [self.weight] + self.target - ] - if self.group: - self._groups = self.data.groupby(self.group).groups - self._group_ids = list(self._groups.keys()) - else: - self._groups = {"_single_group": self.data.index} - self._group_ids = ["_single_group"] - - self._prepare_metadata() - - def _prepare_metadata(self): - """Prepare metadata for the dataset. - - The funcion returns metadata that contains: - - * ``cols``: dict { 'y': list[str], 'x': list[str], 'st': list[str] } - Names of columns for y, x, and static features. - List elements are in same order as column dimensions. - Columns not appearing are assumed to be named (x0, x1, etc.), - (y0, y1, etc.), (st0, st1, etc.). - * ``col_type``: dict[str, str] - maps column names to data types "F" (numerical) and "C" (categorical). - Column names not occurring are assumed "F". - * ``col_known``: dict[str, str] - maps column names to "K" (future known) or "U" (future unknown). - Column names not occurring are assumed "K". - """ - self.metadata = { - "cols": { - "y": self.target, - "x": self.feature_cols, - "st": self.static, - }, - "col_type": {}, - "col_known": {}, - } - - all_cols = self.target + self.feature_cols + self.static - for col in all_cols: - self.metadata["col_type"][col] = "C" if col in self.cat else "F" - - self.metadata["col_known"][col] = "K" if col in self.known else "U" - - def __len__(self) -> int: - """Return number of time series in the dataset.""" - return len(self._group_ids) - - def __getitem__(self, index: int) -> Dict[str, torch.Tensor]: - """Get time series data for given index. - - Returns - ------- - t : numpy.ndarray of shape (n_timepoints,) - Time index for each time point in the past or present. Aligned with `y`, - and `x` not ending in `f`. - - y : torch.Tensor of shape (n_timepoints, n_targets) - Target values for each time point. Rows are time points, aligned with `t`. - - x : torch.Tensor of shape (n_timepoints, n_features) - Features for each time point. Rows are time points, aligned with `t`. - - group : torch.Tensor of shape (n_groups,) - Group identifiers for time series instances. - - st : torch.Tensor of shape (n_static_features,) - Static features. - - cutoff_time : float or numpy.float64 - Cutoff time for the time series instance. - - Other Returns - ------------- - weights : torch.Tensor of shape (n_timepoints,), optional - Only included if weights are not `None`. - """ - group_id = self._group_ids[index] - - if self.group: - mask = self._groups[group_id] - data = self.data.loc[mask] - else: - data = self.data - - cutoff_time = data[self.time].max() - - result = { - "t": data[self.time].values, - "y": torch.tensor(data[self.target].values), - "x": torch.tensor(data[self.feature_cols].values), - "group": torch.tensor([hash(str(group_id))]), - "st": torch.tensor(data[self.static].iloc[0].values if self.static else []), - "cutoff_time": cutoff_time, - } - - if self.data_future is not None: - if self.group: - future_mask = self.data_future.groupby(self.group).groups[group_id] - future_data = self.data_future.loc[future_mask] - else: - future_data = self.data_future - - combined_times = np.concatenate( - [data[self.time].values, future_data[self.time].values] - ) - combined_times = np.unique(combined_times) - combined_times.sort() - - num_timepoints = len(combined_times) - x_merged = np.full((num_timepoints, len(self.feature_cols)), np.nan) - y_merged = np.full((num_timepoints, len(self.target)), np.nan) - - current_time_indices = {t: i for i, t in enumerate(combined_times)} - for i, t in enumerate(data[self.time].values): - idx = current_time_indices[t] - x_merged[idx] = data[self.feature_cols].values[i] - y_merged[idx] = data[self.target].values[i] - - for i, t in enumerate(future_data[self.time].values): - if t in current_time_indices: - idx = current_time_indices[t] - for j, col in enumerate(self.known): - if col in self.feature_cols: - feature_idx = self.feature_cols.index(col) - x_merged[idx, feature_idx] = future_data[col].values[i] - - result.update( - { - "t": combined_times, - "x": torch.tensor(x_merged, dtype=torch.float32), - "y": torch.tensor(y_merged, dtype=torch.float32), - } - ) - - if self.weight: - if self.data_future is not None and self.weight in self.data_future.columns: - weights_merged = np.full(num_timepoints, np.nan) - for i, t in enumerate(data[self.time].values): - idx = current_time_indices[t] - weights_merged[idx] = data[self.weight].values[i] - - for i, t in enumerate(future_data[self.time].values): - if t in current_time_indices and self.weight in future_data.columns: - idx = current_time_indices[t] - weights_merged[idx] = future_data[self.weight].values[i] - - result["weights"] = torch.tensor(weights_merged, dtype=torch.float32) - else: - result["weights"] = torch.tensor( - data[self.weight].values, dtype=torch.float32 - ) - - return result - - def get_metadata(self) -> Dict: - """Return metadata about the dataset. - - Returns - ------- - Dict - Dictionary containing: - - cols: column names for y, x, and static features - - col_type: mapping of columns to their types (F/C) - - col_known: mapping of columns to their future known status (K/U) - """ - return self.metadata diff --git a/pytorch_forecasting/data/timeseries/_timeseries_v2.py b/pytorch_forecasting/data/timeseries/_timeseries_v2.py new file mode 100644 index 000000000..53bf7228d --- /dev/null +++ b/pytorch_forecasting/data/timeseries/_timeseries_v2.py @@ -0,0 +1,276 @@ +""" +Timeseries dataset - v2 prototype. + +Beta version, experimental - use for testing but not in production. +""" + +from typing import Dict, List, Optional, Union +import warnings + +import numpy as np +import pandas as pd +import torch +from torch.utils.data import Dataset + +from pytorch_forecasting.data.timeseries._coerce import _coerce_to_list + + +####################################################################################### +# Disclaimer: This dataset class is still work in progress and experimental, please +# use with care. This class is a basic skeleton of how the data-handling pipeline may +# look like in the future. +# This is the D1 layer that is a "Raw Dataset Layer" mainly for raw data ingestion +# and turning the data to tensors. +# For now, this pipeline handles the simplest situation: The whole data can be loaded +# into the memory. +####################################################################################### + + +class TimeSeries(Dataset): + """PyTorch Dataset for time series data stored in pandas DataFrame. + + Parameters + ---------- + data : pd.DataFrame + data frame with sequence data. + Column names must all be str, and contain str as referred to below. + data_future : pd.DataFrame, optional, default=None + data frame with future data. + Column names must all be str, and contain str as referred to below. + May contain only columns that are in time, group, weight, known, or static. + time : str, optional, default = first col not in group_ids, weight, target, static. + integer typed column denoting the time index within ``data``. + This column is used to determine the sequence of samples. + If there are no missing observations, + the time index should increase by ``+1`` for each subsequent sample. + The first time_idx for each series does not necessarily + have to be ``0`` but any value is allowed. + target : str or List[str], optional, default = last column (at iloc -1) + column(s) in ``data`` denoting the forecasting target. + Can be categorical or numerical dtype. + group : List[str], optional, default = None + list of column names identifying a time series instance within ``data``. + This means that the ``group`` together uniquely identify an instance, + and ``group`` together with ``time`` uniquely identify a single observation + within a time series instance. + If ``None``, the dataset is assumed to be a single time series. + weight : str, optional, default=None + column name for weights. + If ``None``, it is assumed that there is no weight column. + num : list of str, optional, default = all columns with dtype in "fi" + list of numerical variables in ``data``, + list may also contain list of str, which are then grouped together. + cat : list of str, optional, default = all columns with dtype in "Obc" + list of categorical variables in ``data``, + list may also contain list of str, which are then grouped together + (e.g. useful for product categories). + known : list of str, optional, default = all variables + list of variables that change over time and are known in the future, + list may also contain list of str, which are then grouped together + (e.g. useful for special days or promotion categories). + unknown : list of str, optional, default = no variables + list of variables that are not known in the future, + list may also contain list of str, which are then grouped together + (e.g. useful for weather categories). + static : list of str, optional, default = all variables not in known, unknown + list of variables that do not change over time, + list may also contain list of str, which are then grouped together. + """ + + def __init__( + self, + data: pd.DataFrame, + data_future: Optional[pd.DataFrame] = None, + time: Optional[str] = None, + target: Optional[Union[str, List[str]]] = None, + group: Optional[List[str]] = None, + weight: Optional[str] = None, + num: Optional[List[Union[str, List[str]]]] = None, + cat: Optional[List[Union[str, List[str]]]] = None, + known: Optional[List[Union[str, List[str]]]] = None, + unknown: Optional[List[Union[str, List[str]]]] = None, + static: Optional[List[Union[str, List[str]]]] = None, + ): + + self.data = data + self.data_future = data_future + self.time = time + self.target = _coerce_to_list(target) + self.group = _coerce_to_list(group) + self.weight = weight + self.num = _coerce_to_list(num) + self.cat = _coerce_to_list(cat) + self.known = _coerce_to_list(known) + self.unknown = _coerce_to_list(unknown) + self.static = _coerce_to_list(static) + + self.feature_cols = [ + col + for col in data.columns + if col not in [self.time] + self.group + [self.weight] + self.target + ] + if self.group: + self._groups = self.data.groupby(self.group).groups + self._group_ids = list(self._groups.keys()) + else: + self._groups = {"_single_group": self.data.index} + self._group_ids = ["_single_group"] + + self._prepare_metadata() + + def _prepare_metadata(self): + """Prepare metadata for the dataset. + + The funcion returns metadata that contains: + + * ``cols``: dict { 'y': list[str], 'x': list[str], 'st': list[str] } + Names of columns for y, x, and static features. + List elements are in same order as column dimensions. + Columns not appearing are assumed to be named (x0, x1, etc.), + (y0, y1, etc.), (st0, st1, etc.). + * ``col_type``: dict[str, str] + maps column names to data types "F" (numerical) and "C" (categorical). + Column names not occurring are assumed "F". + * ``col_known``: dict[str, str] + maps column names to "K" (future known) or "U" (future unknown). + Column names not occurring are assumed "K". + """ + self.metadata = { + "cols": { + "y": self.target, + "x": self.feature_cols, + "st": self.static, + }, + "col_type": {}, + "col_known": {}, + } + + all_cols = self.target + self.feature_cols + self.static + for col in all_cols: + self.metadata["col_type"][col] = "C" if col in self.cat else "F" + + self.metadata["col_known"][col] = "K" if col in self.known else "U" + + def __len__(self) -> int: + """Return number of time series in the dataset.""" + return len(self._group_ids) + + def __getitem__(self, index: int) -> Dict[str, torch.Tensor]: + """Get time series data for given index. + + Returns + ------- + t : numpy.ndarray of shape (n_timepoints,) + Time index for each time point in the past or present. Aligned with `y`, + and `x` not ending in `f`. + + y : torch.Tensor of shape (n_timepoints, n_targets) + Target values for each time point. Rows are time points, aligned with `t`. + + x : torch.Tensor of shape (n_timepoints, n_features) + Features for each time point. Rows are time points, aligned with `t`. + + group : torch.Tensor of shape (n_groups,) + Group identifiers for time series instances. + + st : torch.Tensor of shape (n_static_features,) + Static features. + + cutoff_time : float or numpy.float64 + Cutoff time for the time series instance. + + Other Returns + ------------- + weights : torch.Tensor of shape (n_timepoints,), optional + Only included if weights are not `None`. + """ + group_id = self._group_ids[index] + + if self.group: + mask = self._groups[group_id] + data = self.data.loc[mask] + else: + data = self.data + + cutoff_time = data[self.time].max() + + result = { + "t": data[self.time].values, + "y": torch.tensor(data[self.target].values), + "x": torch.tensor(data[self.feature_cols].values), + "group": torch.tensor([hash(str(group_id))]), + "st": torch.tensor(data[self.static].iloc[0].values if self.static else []), + "cutoff_time": cutoff_time, + } + + if self.data_future is not None: + if self.group: + future_mask = self.data_future.groupby(self.group).groups[group_id] + future_data = self.data_future.loc[future_mask] + else: + future_data = self.data_future + + combined_times = np.concatenate( + [data[self.time].values, future_data[self.time].values] + ) + combined_times = np.unique(combined_times) + combined_times.sort() + + num_timepoints = len(combined_times) + x_merged = np.full((num_timepoints, len(self.feature_cols)), np.nan) + y_merged = np.full((num_timepoints, len(self.target)), np.nan) + + current_time_indices = {t: i for i, t in enumerate(combined_times)} + for i, t in enumerate(data[self.time].values): + idx = current_time_indices[t] + x_merged[idx] = data[self.feature_cols].values[i] + y_merged[idx] = data[self.target].values[i] + + for i, t in enumerate(future_data[self.time].values): + if t in current_time_indices: + idx = current_time_indices[t] + for j, col in enumerate(self.known): + if col in self.feature_cols: + feature_idx = self.feature_cols.index(col) + x_merged[idx, feature_idx] = future_data[col].values[i] + + result.update( + { + "t": combined_times, + "x": torch.tensor(x_merged, dtype=torch.float32), + "y": torch.tensor(y_merged, dtype=torch.float32), + } + ) + + if self.weight: + if self.data_future is not None and self.weight in self.data_future.columns: + weights_merged = np.full(num_timepoints, np.nan) + for i, t in enumerate(data[self.time].values): + idx = current_time_indices[t] + weights_merged[idx] = data[self.weight].values[i] + + for i, t in enumerate(future_data[self.time].values): + if t in current_time_indices and self.weight in future_data.columns: + idx = current_time_indices[t] + weights_merged[idx] = future_data[self.weight].values[i] + + result["weights"] = torch.tensor(weights_merged, dtype=torch.float32) + else: + result["weights"] = torch.tensor( + data[self.weight].values, dtype=torch.float32 + ) + + return result + + def get_metadata(self) -> Dict: + """Return metadata about the dataset. + + Returns + ------- + Dict + Dictionary containing: + - cols: column names for y, x, and static features + - col_type: mapping of columns to their types (F/C) + - col_known: mapping of columns to their future known status (K/U) + """ + return self.metadata From 043820dd3be3041a019fd9cd2cb1e681d25a79a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Franz=20Kir=C3=A1ly?= Date: Wed, 30 Apr 2025 18:43:50 +0200 Subject: [PATCH 033/139] warning --- .../data/timeseries/_timeseries_v2.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/pytorch_forecasting/data/timeseries/_timeseries_v2.py b/pytorch_forecasting/data/timeseries/_timeseries_v2.py index 53bf7228d..1c91d2525 100644 --- a/pytorch_forecasting/data/timeseries/_timeseries_v2.py +++ b/pytorch_forecasting/data/timeseries/_timeseries_v2.py @@ -104,6 +104,18 @@ def __init__( self.unknown = _coerce_to_list(unknown) self.static = _coerce_to_list(static) + warnings.warn( + "TimeSeries is part of an experimental rework of the " + "pytorch-forecasting data layer, " + "scheduled for release with v2.0.0. " + "The API is not stable and may change without prior warning. " + "For beta testing, but not for stable production use. " + "Feedback and suggestions are very welcome in " + "pytorch-forecasting issue 1736, " + "https://github.com/sktime/pytorch-forecasting/issues/1736", + UserWarning, + ) + self.feature_cols = [ col for col in data.columns From 1720a15e9cff3e5c3ebcd0bf3ec03995d068e4b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Franz=20Kir=C3=A1ly?= Date: Thu, 1 May 2025 13:58:09 +0200 Subject: [PATCH 034/139] linting --- pytorch_forecasting/data/timeseries/__init__.py | 2 +- pytorch_forecasting/data/timeseries/_timeseries_v2.py | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/pytorch_forecasting/data/timeseries/__init__.py b/pytorch_forecasting/data/timeseries/__init__.py index 7734cccf2..85973267a 100644 --- a/pytorch_forecasting/data/timeseries/__init__.py +++ b/pytorch_forecasting/data/timeseries/__init__.py @@ -1,7 +1,7 @@ """Data loaders for time series data.""" -from pytorch_forecasting.data.timeseries._timeseries_v2 import TimeSeries from pytorch_forecasting.data.timeseries._timeseries import TimeSeriesDataSet +from pytorch_forecasting.data.timeseries._timeseries_v2 import TimeSeries __all__ = [ "TimeSeriesDataSet", diff --git a/pytorch_forecasting/data/timeseries/_timeseries_v2.py b/pytorch_forecasting/data/timeseries/_timeseries_v2.py index 1c91d2525..76972ab4d 100644 --- a/pytorch_forecasting/data/timeseries/_timeseries_v2.py +++ b/pytorch_forecasting/data/timeseries/_timeseries_v2.py @@ -14,7 +14,6 @@ from pytorch_forecasting.data.timeseries._coerce import _coerce_to_list - ####################################################################################### # Disclaimer: This dataset class is still work in progress and experimental, please # use with care. This class is a basic skeleton of how the data-handling pipeline may From af44474d16b3fcdf5e99acb4b9d1f7345119d8cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Franz=20Kir=C3=A1ly?= Date: Thu, 1 May 2025 14:21:58 +0200 Subject: [PATCH 035/139] move coercion to utils --- pytorch_forecasting/data/data_module.py | 6 ++---- pytorch_forecasting/{data/timeseries => utils}/_coerce.py | 0 2 files changed, 2 insertions(+), 4 deletions(-) rename pytorch_forecasting/{data/timeseries => utils}/_coerce.py (100%) diff --git a/pytorch_forecasting/data/data_module.py b/pytorch_forecasting/data/data_module.py index 1203e83ac..9d3ebbedb 100644 --- a/pytorch_forecasting/data/data_module.py +++ b/pytorch_forecasting/data/data_module.py @@ -19,10 +19,8 @@ NaNLabelEncoder, TorchNormalizer, ) -from pytorch_forecasting.data.timeseries import ( - TimeSeries, - _coerce_to_dict, -) +from pytorch_forecasting.data.timeseries import TimeSeries +from pytorch_forecasting.utils._coerce import _coerce_to_dict NORMALIZER = Union[TorchNormalizer, NaNLabelEncoder, EncoderNormalizer] diff --git a/pytorch_forecasting/data/timeseries/_coerce.py b/pytorch_forecasting/utils/_coerce.py similarity index 100% rename from pytorch_forecasting/data/timeseries/_coerce.py rename to pytorch_forecasting/utils/_coerce.py From a3cb8b736b0b134c8faa97f5ef2993deb28fb75b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Franz=20Kir=C3=A1ly?= Date: Thu, 1 May 2025 14:22:18 +0200 Subject: [PATCH 036/139] linting --- pytorch_forecasting/data/timeseries/_timeseries.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pytorch_forecasting/data/timeseries/_timeseries.py b/pytorch_forecasting/data/timeseries/_timeseries.py index 263e0ea3a..30fe9e0bb 100644 --- a/pytorch_forecasting/data/timeseries/_timeseries.py +++ b/pytorch_forecasting/data/timeseries/_timeseries.py @@ -31,8 +31,8 @@ TorchNormalizer, ) from pytorch_forecasting.data.samplers import TimeSynchronizedBatchSampler -from pytorch_forecasting.data.timeseries._coerce import _coerce_to_dict, _coerce_to_list from pytorch_forecasting.utils import repr_class +from pytorch_forecasting.utils._coerce import _coerce_to_dict, _coerce_to_list from pytorch_forecasting.utils._dependencies import _check_matplotlib From 75d7fb54d8405ef493197c5a4d2fc86a5e9e9d5b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Franz=20Kir=C3=A1ly?= Date: Thu, 1 May 2025 14:25:51 +0200 Subject: [PATCH 037/139] Update _timeseries_v2.py --- pytorch_forecasting/data/timeseries/_timeseries_v2.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pytorch_forecasting/data/timeseries/_timeseries_v2.py b/pytorch_forecasting/data/timeseries/_timeseries_v2.py index 76972ab4d..afa45725b 100644 --- a/pytorch_forecasting/data/timeseries/_timeseries_v2.py +++ b/pytorch_forecasting/data/timeseries/_timeseries_v2.py @@ -12,7 +12,7 @@ import torch from torch.utils.data import Dataset -from pytorch_forecasting.data.timeseries._coerce import _coerce_to_list +from pytorch_forecasting.utils._coerce import _coerce_to_list ####################################################################################### # Disclaimer: This dataset class is still work in progress and experimental, please From 1b946e699be9db2e201a2361779a695356a0460b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Franz=20Kir=C3=A1ly?= Date: Thu, 1 May 2025 14:30:13 +0200 Subject: [PATCH 038/139] Update __init__.py --- pytorch_forecasting/data/timeseries/__init__.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/pytorch_forecasting/data/timeseries/__init__.py b/pytorch_forecasting/data/timeseries/__init__.py index 85973267a..b359a0aa9 100644 --- a/pytorch_forecasting/data/timeseries/__init__.py +++ b/pytorch_forecasting/data/timeseries/__init__.py @@ -1,9 +1,15 @@ """Data loaders for time series data.""" -from pytorch_forecasting.data.timeseries._timeseries import TimeSeriesDataSet +from pytorch_forecasting.data.timeseries._timeseries import ( + _find_end_indices, + check_for_nonfinite, + TimeSeriesDataSet, +) from pytorch_forecasting.data.timeseries._timeseries_v2 import TimeSeries __all__ = [ + "_find_end_indices", + "check_for_nonfinite", "TimeSeriesDataSet", "TimeSeries", ] From 3edb08b7ea1b97d06b47b0ebcc83aaef9bec8083 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Franz=20Kir=C3=A1ly?= Date: Thu, 1 May 2025 14:33:17 +0200 Subject: [PATCH 039/139] Update __init__.py --- pytorch_forecasting/data/timeseries/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pytorch_forecasting/data/timeseries/__init__.py b/pytorch_forecasting/data/timeseries/__init__.py index b359a0aa9..788c08201 100644 --- a/pytorch_forecasting/data/timeseries/__init__.py +++ b/pytorch_forecasting/data/timeseries/__init__.py @@ -1,9 +1,9 @@ """Data loaders for time series data.""" from pytorch_forecasting.data.timeseries._timeseries import ( + TimeSeriesDataSet, _find_end_indices, check_for_nonfinite, - TimeSeriesDataSet, ) from pytorch_forecasting.data.timeseries._timeseries_v2 import TimeSeries From a6691341001f813ac4c5d12aafb173645087d679 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Franz=20Kir=C3=A1ly?= Date: Sun, 4 May 2025 18:28:04 +0200 Subject: [PATCH 040/139] Update _lookup.py --- pytorch_forecasting/_registry/_lookup.py | 49 ++++++++++++++++-------- 1 file changed, 32 insertions(+), 17 deletions(-) diff --git a/pytorch_forecasting/_registry/_lookup.py b/pytorch_forecasting/_registry/_lookup.py index 828c448b1..b4238f980 100644 --- a/pytorch_forecasting/_registry/_lookup.py +++ b/pytorch_forecasting/_registry/_lookup.py @@ -11,6 +11,7 @@ __author__ = ["fkiraly"] # all_objects is based on the sklearn utility all_estimators +from inspect import isclass from pathlib import Path from skbase.lookup import all_objects as _all_objects @@ -133,25 +134,39 @@ def all_objects( result = [] ROOT = str(Path(__file__).parent.parent) # package root directory - if isinstance(filter_tags, str): - filter_tags = {filter_tags: True} - filter_tags = filter_tags.copy() if filter_tags else None - - if object_types: - if filter_tags and "object_type" not in filter_tags.keys(): - object_tag_filter = {"object_type": object_types} - elif filter_tags: - filter_tags_filter = filter_tags.get("object_type", []) - if isinstance(object_types, str): - object_types = [object_types] - object_tag_update = {"object_type": object_types + filter_tags_filter} - filter_tags.update(object_tag_update) + def _coerce_to_str(obj): + if isinstance(obj, (list, tuple)): + return [_coerce_to_str(o) for o in obj] + if isclass(obj): + obj = obj.get_tag("object_type") + return obj + + def _coerce_to_list_of_str(obj): + obj = _coerce_to_str(obj) + if isinstance(obj, str): + return [obj] + return obj + + if object_types is not None: + object_types = _coerce_to_list_of_str(object_types) + object_types = list(set(object_types)) + + if object_types is not None: + if filter_tags is None: + filter_tags = {} + elif isinstance(filter_tags, str): + filter_tags = {filter_tags: True} else: - object_tag_filter = {"object_type": object_types} - if filter_tags: - filter_tags.update(object_tag_filter) + filter_tags = filter_tags.copy() + + if "object_type" in filter_tags: + obj_field = filter_tags["object_type"] + obj_field = _coerce_to_list_of_str(obj_field) + obj_field = obj_field + object_types else: - filter_tags = object_tag_filter + obj_field = object_types + + filter_tags["object_type"] = obj_field result = _all_objects( object_types=[_BaseObject], From d78bf5dc19cef1e659e8258552691c1713b2dd4f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Franz=20Kir=C3=A1ly?= Date: Sun, 4 May 2025 18:32:38 +0200 Subject: [PATCH 041/139] Update _lookup.py --- pytorch_forecasting/_registry/_lookup.py | 95 +++++++++++++++--------- 1 file changed, 60 insertions(+), 35 deletions(-) diff --git a/pytorch_forecasting/_registry/_lookup.py b/pytorch_forecasting/_registry/_lookup.py index b4238f980..0fb4c0c9d 100644 --- a/pytorch_forecasting/_registry/_lookup.py +++ b/pytorch_forecasting/_registry/_lookup.py @@ -40,44 +40,64 @@ def all_objects( ---------- object_types: str, list of str, optional (default=None) Which kind of objects should be returned. - if None, no filter is applied and all objects are returned. - if str or list of str, strings define scitypes specified in search - only objects that are of (at least) one of the scitypes are returned - possible str values are entries of registry.BASE_CLASS_REGISTER (first col) - for instance 'regrssor_proba', 'distribution, 'metric' - return_names: bool, optional (default=True) + * if None, no filter is applied and all objects are returned. + * if str or list of str, strings define scitypes specified in search + only objects that are of (at least) one of the scitypes are returned - if True, estimator class name is included in the ``all_objects`` - return in the order: name, estimator class, optional tags, either as - a tuple or as pandas.DataFrame columns + return_names: bool, optional (default=True) - if False, estimator class name is removed from the ``all_objects`` return. + * if True, estimator class name is included in the ``all_objects`` + return in the order: name, estimator class, optional tags, either as + a tuple or as pandas.DataFrame columns + * if False, estimator class name is removed from the ``all_objects`` return. - filter_tags: dict of (str or list of str), optional (default=None) + filter_tags: dict of (str or list of str or re.Pattern), optional (default=None) For a list of valid tag strings, use the registry.all_tags utility. - ``filter_tags`` subsets the returned estimators as follows: + ``filter_tags`` subsets the returned objects as follows: * each key/value pair is statement in "and"/conjunction * key is tag name to sub-set on * value str or list of string are tag values * condition is "key must be equal to value, or in set(value)" - exclude_estimators: str, list of str, optional (default=None) - Names of estimators to exclude. + In detail, he return will be filtered to keep exactly the classes + where tags satisfy all the filter conditions specified by ``filter_tags``. + Filter conditions are as follows, for ``tag_name: search_value`` pairs in + the ``filter_tags`` dict, applied to a class ``klass``: + + - If ``klass`` does not have a tag with name ``tag_name``, it is excluded. + Otherwise, let ``tag_value`` be the value of the tag with name ``tag_name``. + - If ``search_value`` is a string, and ``tag_value`` is a string, + the filter condition is that ``search_value`` must match the tag value. + - If ``search_value`` is a string, and ``tag_value`` is a list, + the filter condition is that ``search_value`` is contained in ``tag_value``. + - If ``search_value`` is a ``re.Pattern``, and ``tag_value`` is a string, + the filter condition is that ``search_value.fullmatch(tag_value)`` + is true, i.e., the regex matches the tag value. + - If ``search_value`` is a ``re.Pattern``, and ``tag_value`` is a list, + the filter condition is that at least one element of ``tag_value`` + matches the regex. + - If ``search_value`` is iterable, then the filter condition is that + at least one element of ``search_value`` satisfies the above conditions, + applied to ``tag_value``. + + Note: ``re.Pattern`` is supported only from ``scikit-base`` version 0.8.0. + + exclude_objects: str, list of str, optional (default=None) + Names of objects to exclude. as_dataframe: bool, optional (default=False) - True: ``all_objects`` will return a pandas.DataFrame with named - columns for all of the attributes being returned. - - False: ``all_objects`` will return a list (either a list of - estimators or a list of tuples, see Returns) + * True: ``all_objects`` will return a ``pandas.DataFrame`` with named + columns for all of the attributes being returned. + * False: ``all_objects`` will return a list (either a list of + objects or a list of tuples, see Returns) return_tags: str or list of str, optional (default=None) Names of tags to fetch and return each estimator's value of. - For a list of valid tag strings, use the registry.all_tags utility. + For a list of valid tag strings, use the ``registry.all_tags`` utility. if str or list of str, the tag values named in return_tags will be fetched for each estimator and will be appended as either columns or tuple entries. @@ -88,27 +108,32 @@ def all_objects( Returns ------- all_objects will return one of the following: - 1. list of objects, if return_names=False, and return_tags is None - 2. list of tuples (optional object name, class, ~optional object - tags), if return_names=True or return_tags is not None. - 3. pandas.DataFrame if as_dataframe = True + + 1. list of objects, if ``return_names=False``, and ``return_tags`` is None + + 2. list of tuples (optional estimator name, class, optional estimator + tags), if ``return_names=True`` or ``return_tags`` is not ``None``. + + 3. ``pandas.DataFrame`` if ``as_dataframe = True`` + if list of objects: entries are objects matching the query, - in alphabetical order of object name + in alphabetical order of estimator name + if list of tuples: - list of (optional object name, object, optional object - tags) matching the query, in alphabetical order of object name, + list of (optional estimator name, estimator, optional estimator + tags) matching the query, in alphabetical order of estimator name, where - ``name`` is the object name as string, and is an - optional return - ``object`` is the actual object - ``tags`` are the object's values for each tag in return_tags - and is an optional return. - if dataframe: - all_objects will return a pandas.DataFrame. + ``name`` is the estimator name as string, and is an + optional return + ``estimator`` is the actual estimator + ``tags`` are the estimator's values for each tag in return_tags + and is an optional return. + + if ``DataFrame``: column names represent the attributes contained in each column. "objects" will be the name of the column of objects, "names" - will be the name of the column of object class names and the string(s) + will be the name of the column of estimator class names and the string(s) passed in return_tags will serve as column names for all columns of tags that were optionally requested. From e350291c110f567e69946e0e113f2471b7472738 Mon Sep 17 00:00:00 2001 From: Aryan Saini Date: Sun, 11 May 2025 22:10:01 +0530 Subject: [PATCH 042/139] update tests --- tests/test_data/test_data_module.py | 72 ++++++++++++++--------------- 1 file changed, 36 insertions(+), 36 deletions(-) diff --git a/tests/test_data/test_data_module.py b/tests/test_data/test_data_module.py index c14e3d8f4..4051b852c 100644 --- a/tests/test_data/test_data_module.py +++ b/tests/test_data/test_data_module.py @@ -9,7 +9,7 @@ @pytest.fixture def sample_timeseries_data(): """Create a sample time series dataset with only numerical values.""" - num_groups = 5 + num_groups = 10 seq_length = 100 groups = [] @@ -128,22 +128,22 @@ def test_metadata_property(data_module): assert metadata["decoder_cont"] == 1 # Only known_future marked as known -# def test_setup(data_module): -# """Test the setup method that prepares the datasets.""" -# data_module.setup(stage="fit") -# print(data_module._val_indices) -# assert hasattr(data_module, "train_dataset") -# assert hasattr(data_module, "val_dataset") -# assert len(data_module.train_windows) > 0 -# assert len(data_module.val_windows) > 0 -# -# data_module.setup(stage="test") -# assert hasattr(data_module, "test_dataset") -# assert len(data_module.test_windows) > 0 -# -# data_module.setup(stage="predict") -# assert hasattr(data_module, "predict_dataset") -# assert len(data_module.predict_windows) > 0 +def test_setup(data_module): + """Test the setup method that prepares the datasets.""" + data_module.setup(stage="fit") + print(data_module._val_indices) + assert hasattr(data_module, "train_dataset") + assert hasattr(data_module, "val_dataset") + assert len(data_module.train_windows) > 0 + assert len(data_module.val_windows) > 0 + + data_module.setup(stage="test") + assert hasattr(data_module, "test_dataset") + assert len(data_module.test_windows) > 0 + + data_module.setup(stage="predict") + assert hasattr(data_module, "predict_dataset") + assert len(data_module.predict_windows) > 0 def test_create_windows(data_module): @@ -407,25 +407,25 @@ def test_with_static_features(): assert "static_continuous_features" in x -# def test_different_train_val_test_split(sample_timeseries_data): -# """Test with different train/val/test split ratios.""" -# dm = EncoderDecoderTimeSeriesDataModule( -# time_series_dataset=sample_timeseries_data, -# max_encoder_length=24, -# max_prediction_length=12, -# batch_size=4, -# train_val_test_split=(0.8, 0.1, 0.1), -# ) -# -# dm.setup() -# -# total_series = len(sample_timeseries_data) -# expected_train = int(0.8 * total_series) -# expected_val = int(0.1 * total_series) -# -# assert len(dm._train_indices) == expected_train -# assert len(dm._val_indices) == expected_val -# assert len(dm._test_indices) == total_series - expected_train - expected_val +def test_different_train_val_test_split(sample_timeseries_data): + """Test with different train/val/test split ratios.""" + dm = EncoderDecoderTimeSeriesDataModule( + time_series_dataset=sample_timeseries_data, + max_encoder_length=24, + max_prediction_length=12, + batch_size=4, + train_val_test_split=(0.8, 0.1, 0.1), + ) + + dm.setup() + + total_series = len(sample_timeseries_data) + expected_train = int(0.8 * total_series) + expected_val = int(0.1 * total_series) + + assert len(dm._train_indices) == expected_train + assert len(dm._val_indices) == expected_val + assert len(dm._test_indices) == total_series - expected_train - expected_val def test_multivariate_target(): From 3099691d3cc792bd528f50ff3c51a0fa4a9ce28a Mon Sep 17 00:00:00 2001 From: Aryan Saini Date: Mon, 12 May 2025 00:22:27 +0530 Subject: [PATCH 043/139] update tft_v2 --- .../tft_version_two.py | 65 +++++++++++-------- 1 file changed, 38 insertions(+), 27 deletions(-) diff --git a/pytorch_forecasting/models/temporal_fusion_transformer/tft_version_two.py b/pytorch_forecasting/models/temporal_fusion_transformer/tft_version_two.py index 30f70f98e..2bfe407d7 100644 --- a/pytorch_forecasting/models/temporal_fusion_transformer/tft_version_two.py +++ b/pytorch_forecasting/models/temporal_fusion_transformer/tft_version_two.py @@ -36,6 +36,8 @@ def __init__( lr_scheduler=lr_scheduler, lr_scheduler_params=lr_scheduler_params, ) + self.save_hyperparameters(ignore=["loss", "logging_metrics", "metadata"]) + self.hidden_size = hidden_size self.num_layers = num_layers self.attention_head_size = attention_head_size @@ -47,42 +49,51 @@ def __init__( self.max_prediction_length = self.metadata["max_prediction_length"] self.encoder_cont = self.metadata["encoder_cont"] self.encoder_cat = self.metadata["encoder_cat"] - self.static_categorical_features = self.metadata["static_categorical_features"] - self.static_continuous_features = self.metadata["static_continuous_features"] - - total_feature_size = self.encoder_cont + self.encoder_cat - total_static_size = ( - self.static_categorical_features + self.static_continuous_features - ) - - self.encoder_var_selection = nn.Sequential( - nn.Linear(total_feature_size, hidden_size), - nn.ReLU(), - nn.Linear(hidden_size, total_feature_size), - nn.Sigmoid(), - ) - - self.decoder_var_selection = nn.Sequential( - nn.Linear(total_feature_size, hidden_size), - nn.ReLU(), - nn.Linear(hidden_size, total_feature_size), - nn.Sigmoid(), - ) + self.encoder_input_dim = self.encoder_cont + self.encoder_cat + self.decoder_cont = self.metadata["decoder_cont"] + self.decoder_cat = self.metadata["decoder_cat"] + self.decoder_input_dim = self.decoder_cont + self.decoder_cat + self.static_cat_dim = self.metadata.get("static_categorical_features", 0) + self.static_cont_dim = self.metadata.get("static_continuous_features", 0) + self.static_input_dim = self.static_cat_dim + self.static_cont_dim + + if self.encoder_input_dim > 0: + self.encoder_var_selection = nn.Sequential( + nn.Linear(self.encoder_input_dim, hidden_size), + nn.ReLU(), + nn.Linear(hidden_size, self.encoder_input_dim), + nn.Sigmoid(), + ) + else: + self.encoder_var_selection = None + + if self.decoder_input_dim > 0: + self.decoder_var_selection = nn.Sequential( + nn.Linear(self.decoder_input_dim, hidden_size), + nn.ReLU(), + nn.Linear(hidden_size, self.decoder_input_dim), + nn.Sigmoid(), + ) + else: + self.decoder_var_selection = None - self.static_context_linear = ( - nn.Linear(total_static_size, hidden_size) if total_static_size > 0 else None - ) + if self.static_input_dim > 0: + self.static_context_linear = nn.Linear(self.static_input_dim, hidden_size) + else: + self.static_context_linear = None + _lstm_encoder_input_actual_dim = self.encoder_input_dim self.lstm_encoder = nn.LSTM( - input_size=total_feature_size, + input_size=max(1, _lstm_encoder_input_actual_dim), hidden_size=hidden_size, num_layers=num_layers, dropout=dropout, batch_first=True, ) + _lstm_decoder_input_actual_dim = self.decoder_input_dim self.lstm_decoder = nn.LSTM( - input_size=total_feature_size, + input_size=max(1, _lstm_decoder_input_actual_dim), hidden_size=hidden_size, num_layers=num_layers, dropout=dropout, @@ -97,7 +108,7 @@ def __init__( ) self.pre_output = nn.Linear(hidden_size, hidden_size) - self.output_layer = nn.Linear(hidden_size, output_size) + self.output_layer = nn.Linear(hidden_size, self.output_size) def forward(self, x: Dict[str, torch.Tensor]) -> Dict[str, torch.Tensor]: """ From 77cb979808d83cbcfb4e7c3ed5ffd888c0828d31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Franz=20Kir=C3=A1ly?= Date: Tue, 13 May 2025 08:14:03 +0200 Subject: [PATCH 044/139] warnings and init attr handling --- pytorch_forecasting/data/data_module.py | 44 +++++++++---- .../data/timeseries/_timeseries_v2.py | 61 +++++++++++-------- 2 files changed, 67 insertions(+), 38 deletions(-) diff --git a/pytorch_forecasting/data/data_module.py b/pytorch_forecasting/data/data_module.py index 9d3ebbedb..690fb6057 100644 --- a/pytorch_forecasting/data/data_module.py +++ b/pytorch_forecasting/data/data_module.py @@ -8,6 +8,7 @@ ####################################################################################### from typing import Any, Dict, List, Optional, Tuple, Union +from warnings import warn from lightning.pytorch import LightningDataModule from sklearn.preprocessing import RobustScaler, StandardScaler @@ -107,33 +108,50 @@ def __init__( num_workers: int = 0, train_val_test_split: tuple = (0.7, 0.15, 0.15), ): - super().__init__() - self.time_series_dataset = time_series_dataset - self.time_series_metadata = time_series_dataset.get_metadata() + self.time_series_dataset = time_series_dataset self.max_encoder_length = max_encoder_length - self.min_encoder_length = min_encoder_length or max_encoder_length + self.min_encoder_length = min_encoder_length self.max_prediction_length = max_prediction_length - self.min_prediction_length = min_prediction_length or max_prediction_length + self.min_prediction_length = min_prediction_length self.min_prediction_idx = min_prediction_idx - self.allow_missing_timesteps = allow_missing_timesteps self.add_relative_time_idx = add_relative_time_idx self.add_target_scales = add_target_scales self.add_encoder_length = add_encoder_length self.randomize_length = randomize_length - + self.target_normalizer = target_normalizer + self.categorical_encoders = categorical_encoders + self.scalers = scalers self.batch_size = batch_size self.num_workers = num_workers self.train_val_test_split = train_val_test_split + warn( + "TimeSeries is part of an experimental rework of the " + "pytorch-forecasting data layer, " + "scheduled for release with v2.0.0. " + "The API is not stable and may change without prior warning. " + "For beta testing, but not for stable production use. " + "Feedback and suggestions are very welcome in " + "pytorch-forecasting issue 1736, " + "https://github.com/sktime/pytorch-forecasting/issues/1736", + UserWarning, + ) + + super().__init__() + + # handle defaults and derived attributes if isinstance(target_normalizer, str) and target_normalizer.lower() == "auto": - self.target_normalizer = RobustScaler() + self._target_normalizer = RobustScaler() else: - self.target_normalizer = target_normalizer + self._target_normalizer = target_normalizer - self.categorical_encoders = _coerce_to_dict(categorical_encoders) - self.scalers = _coerce_to_dict(scalers) + self.time_series_metadata = time_series_dataset.get_metadata() + self._min_prediction_length = min_prediction_length or max_prediction_length + self._min_encoder_length = min_encoder_length or max_encoder_length + self._categorical_encoders = _coerce_to_dict(categorical_encoders) + self._scalers = _coerce_to_dict(scalers) self.categorical_indices = [] self.continuous_indices = [] @@ -237,8 +255,8 @@ def _prepare_metadata(self): { "max_encoder_length": self.max_encoder_length, "max_prediction_length": self.max_prediction_length, - "min_encoder_length": self.min_encoder_length, - "min_prediction_length": self.min_prediction_length, + "min_encoder_length": self._min_encoder_length, + "min_prediction_length": self._min_prediction_length, } ) diff --git a/pytorch_forecasting/data/timeseries/_timeseries_v2.py b/pytorch_forecasting/data/timeseries/_timeseries_v2.py index afa45725b..1f0ba6820 100644 --- a/pytorch_forecasting/data/timeseries/_timeseries_v2.py +++ b/pytorch_forecasting/data/timeseries/_timeseries_v2.py @@ -5,7 +5,7 @@ """ from typing import Dict, List, Optional, Union -import warnings +from warnings import warn import numpy as np import pandas as pd @@ -94,16 +94,16 @@ def __init__( self.data = data self.data_future = data_future self.time = time - self.target = _coerce_to_list(target) - self.group = _coerce_to_list(group) + self.target = target + self.group = group self.weight = weight - self.num = _coerce_to_list(num) - self.cat = _coerce_to_list(cat) - self.known = _coerce_to_list(known) - self.unknown = _coerce_to_list(unknown) - self.static = _coerce_to_list(static) + self.num = num + self.cat = cat + self.known = known + self.unknown = unknown + self.static = static - warnings.warn( + warn( "TimeSeries is part of an experimental rework of the " "pytorch-forecasting data layer, " "scheduled for release with v2.0.0. " @@ -115,13 +115,24 @@ def __init__( UserWarning, ) + super.__init__() + + # handle defaults, coercion, and derived attributes + self._target = _coerce_to_list(target) + self._group = _coerce_to_list(group) + self._num = _coerce_to_list(num) + self._cat = _coerce_to_list(cat) + self._known = _coerce_to_list(known) + self._unknown = _coerce_to_list(unknown) + self._static = _coerce_to_list(static) + self.feature_cols = [ col for col in data.columns - if col not in [self.time] + self.group + [self.weight] + self.target + if col not in [self.time] + self._group + [self.weight] + self._target ] - if self.group: - self._groups = self.data.groupby(self.group).groups + if self._group: + self._groups = self.data.groupby(self._group).groups self._group_ids = list(self._groups.keys()) else: self._groups = {"_single_group": self.data.index} @@ -148,19 +159,19 @@ def _prepare_metadata(self): """ self.metadata = { "cols": { - "y": self.target, + "y": self._target, "x": self.feature_cols, - "st": self.static, + "st": self._static, }, "col_type": {}, "col_known": {}, } - all_cols = self.target + self.feature_cols + self.static + all_cols = self._target + self.feature_cols + self._static for col in all_cols: - self.metadata["col_type"][col] = "C" if col in self.cat else "F" + self.metadata["col_type"][col] = "C" if col in self._cat else "F" - self.metadata["col_known"][col] = "K" if col in self.known else "U" + self.metadata["col_known"][col] = "K" if col in self._known else "U" def __len__(self) -> int: """Return number of time series in the dataset.""" @@ -197,7 +208,7 @@ def __getitem__(self, index: int) -> Dict[str, torch.Tensor]: """ group_id = self._group_ids[index] - if self.group: + if self._group: mask = self._groups[group_id] data = self.data.loc[mask] else: @@ -207,16 +218,16 @@ def __getitem__(self, index: int) -> Dict[str, torch.Tensor]: result = { "t": data[self.time].values, - "y": torch.tensor(data[self.target].values), + "y": torch.tensor(data[self._target].values), "x": torch.tensor(data[self.feature_cols].values), "group": torch.tensor([hash(str(group_id))]), - "st": torch.tensor(data[self.static].iloc[0].values if self.static else []), + "st": torch.tensor(data[self._static].iloc[0].values if self._static else []), "cutoff_time": cutoff_time, } if self.data_future is not None: - if self.group: - future_mask = self.data_future.groupby(self.group).groups[group_id] + if self._group: + future_mask = self.data_future.groupby(self._group).groups[group_id] future_data = self.data_future.loc[future_mask] else: future_data = self.data_future @@ -229,18 +240,18 @@ def __getitem__(self, index: int) -> Dict[str, torch.Tensor]: num_timepoints = len(combined_times) x_merged = np.full((num_timepoints, len(self.feature_cols)), np.nan) - y_merged = np.full((num_timepoints, len(self.target)), np.nan) + y_merged = np.full((num_timepoints, len(self._target)), np.nan) current_time_indices = {t: i for i, t in enumerate(combined_times)} for i, t in enumerate(data[self.time].values): idx = current_time_indices[t] x_merged[idx] = data[self.feature_cols].values[i] - y_merged[idx] = data[self.target].values[i] + y_merged[idx] = data[self._target].values[i] for i, t in enumerate(future_data[self.time].values): if t in current_time_indices: idx = current_time_indices[t] - for j, col in enumerate(self.known): + for j, col in enumerate(self._known): if col in self.feature_cols: feature_idx = self.feature_cols.index(col) x_merged[idx, feature_idx] = future_data[col].values[i] From f8c94e626010d165cf022e0fd3f0a22c994759c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Franz=20Kir=C3=A1ly?= Date: Tue, 13 May 2025 08:25:53 +0200 Subject: [PATCH 045/139] simplify TimeSeries.__getitem__ --- .../data/timeseries/_timeseries_v2.py | 73 +++++++++++-------- 1 file changed, 44 insertions(+), 29 deletions(-) diff --git a/pytorch_forecasting/data/timeseries/_timeseries_v2.py b/pytorch_forecasting/data/timeseries/_timeseries_v2.py index 1f0ba6820..5e24f6454 100644 --- a/pytorch_forecasting/data/timeseries/_timeseries_v2.py +++ b/pytorch_forecasting/data/timeseries/_timeseries_v2.py @@ -206,54 +206,69 @@ def __getitem__(self, index: int) -> Dict[str, torch.Tensor]: weights : torch.Tensor of shape (n_timepoints,), optional Only included if weights are not `None`. """ - group_id = self._group_ids[index] - - if self._group: - mask = self._groups[group_id] + time = self.time + feature_cols = self.feature_cols + _target = self._target + _known = self._known + _static = self._static + _group = self._group + _groups = self._groups + _group_ids = self._group_ids + weight = self.weight + data_future = self.data_future + + group_id = _group_ids[index] + + if _group: + mask = _groups[group_id] data = self.data.loc[mask] else: data = self.data - cutoff_time = data[self.time].max() + cutoff_time = data[time].max() + + data_vals = data[time].values + data_tgt_vals = data[_target].values + data_feat_vals = data[feature_cols].values result = { - "t": data[self.time].values, - "y": torch.tensor(data[self._target].values), - "x": torch.tensor(data[self.feature_cols].values), + "t": data_vals, + "y": torch.tensor(data_tgt_vals), + "x": torch.tensor(data_feat_vals), "group": torch.tensor([hash(str(group_id))]), - "st": torch.tensor(data[self._static].iloc[0].values if self._static else []), + "st": torch.tensor(data[_static].iloc[0].values if _static else []), "cutoff_time": cutoff_time, } - if self.data_future is not None: - if self._group: - future_mask = self.data_future.groupby(self._group).groups[group_id] + if data_future is not None: + if _group: + future_mask = self.data_future.groupby(_group).groups[group_id] future_data = self.data_future.loc[future_mask] else: future_data = self.data_future - combined_times = np.concatenate( - [data[self.time].values, future_data[self.time].values] - ) + data_fut_vals = future_data[time].values + + combined_times = np.concatenate([data_vals, data_fut_vals]) combined_times = np.unique(combined_times) combined_times.sort() num_timepoints = len(combined_times) - x_merged = np.full((num_timepoints, len(self.feature_cols)), np.nan) - y_merged = np.full((num_timepoints, len(self._target)), np.nan) + x_merged = np.full((num_timepoints, len(feature_cols)), np.nan) + y_merged = np.full((num_timepoints, len(_target)), np.nan) current_time_indices = {t: i for i, t in enumerate(combined_times)} - for i, t in enumerate(data[self.time].values): + for i, t in enumerate(data_vals): idx = current_time_indices[t] - x_merged[idx] = data[self.feature_cols].values[i] - y_merged[idx] = data[self._target].values[i] + x_merged[idx] = data_feat_vals[i] + y_merged[idx] = data_tgt_vals[i] - for i, t in enumerate(future_data[self.time].values): + for i, t in enumerate(data_fut_vals): if t in current_time_indices: idx = current_time_indices[t] - for j, col in enumerate(self._known): - if col in self.feature_cols: - feature_idx = self.feature_cols.index(col) + for j, col in enumerate(_known): + if col in feature_cols: + feature_idx = feature_cols.index(col) x_merged[idx, feature_idx] = future_data[col].values[i] result.update( @@ -264,17 +279,17 @@ def __getitem__(self, index: int) -> Dict[str, torch.Tensor]: } ) - if self.weight: + if weight: if self.data_future is not None and self.weight in self.data_future.columns: weights_merged = np.full(num_timepoints, np.nan) - for i, t in enumerate(data[self.time].values): + for i, t in enumerate(data_vals): idx = current_time_indices[t] - weights_merged[idx] = data[self.weight].values[i] + weights_merged[idx] = data[weight].values[i] - for i, t in enumerate(future_data[self.time].values): + for i, t in enumerate(data_fut_vals): if t in current_time_indices and self.weight in future_data.columns: idx = current_time_indices[t] - weights_merged[idx] = future_data[self.weight].values[i] + weights_merged[idx] = future_data[weight].values[i] result["weights"] = torch.tensor(weights_merged, dtype=torch.float32) else: From c289255286540b96ddcf5667851f06edf7af0c7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Franz=20Kir=C3=A1ly?= Date: Tue, 13 May 2025 08:36:17 +0200 Subject: [PATCH 046/139] Update _timeseries_v2.py --- pytorch_forecasting/data/timeseries/_timeseries_v2.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pytorch_forecasting/data/timeseries/_timeseries_v2.py b/pytorch_forecasting/data/timeseries/_timeseries_v2.py index 5e24f6454..178b273bc 100644 --- a/pytorch_forecasting/data/timeseries/_timeseries_v2.py +++ b/pytorch_forecasting/data/timeseries/_timeseries_v2.py @@ -115,7 +115,7 @@ def __init__( UserWarning, ) - super.__init__() + super().__init__() # handle defaults, coercion, and derived attributes self._target = _coerce_to_list(target) From 9467f387287f3ba4a56ef1a1a4673c2215deb355 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Franz=20Kir=C3=A1ly?= Date: Tue, 13 May 2025 08:44:38 +0200 Subject: [PATCH 047/139] Update data_module.py --- pytorch_forecasting/data/data_module.py | 65 ++++++++++++------------- 1 file changed, 32 insertions(+), 33 deletions(-) diff --git a/pytorch_forecasting/data/data_module.py b/pytorch_forecasting/data/data_module.py index 690fb6057..7b0d45312 100644 --- a/pytorch_forecasting/data/data_module.py +++ b/pytorch_forecasting/data/data_module.py @@ -171,39 +171,38 @@ def _prepare_metadata(self): dict dictionary containing the following keys: - * ``encoder_cat``: Number of categorical variables in the encoder. - Computed as ``len(self.categorical_indices)``, which counts the - categorical feature indices. - * ``encoder_cont``: Number of continuous variables in the encoder. - Computed as ``len(self.continuous_indices)``, which counts the - continuous feature indices. - * ``decoder_cat``: Number of categorical variables in the decoder that - are known in advance. - Computed by filtering ``self.time_series_metadata["cols"]["x"]`` - where col_type == "C"(categorical) and col_known == "K" (known) - * ``decoder_cont``: Number of continuous variables in the decoder that - are known in advance. - Computed by filtering ``self.time_series_metadata["cols"]["x"]`` - where col_type == "F"(continuous) and col_known == "K"(known) - * ``target``: Number of target variables. - Computed as ``len(self.time_series_metadata["cols"]["y"])``, which - gives the number of output target columns.. - * ``static_categorical_features``: Number of static categorical features - Computed by filtering ``self.time_series_metadata["cols"]["st"]`` - (static features) where col_type == "C" (categorical). - * ``static_continuous_features``: Number of static continuous features - Computed as difference of - ``len(self.time_series_metadata["cols"]["st"])`` (static features) - and static_categorical_features that gives static continuous feature - * ``max_encoder_length``: maximum encoder length - Taken directly from `self.max_encoder_length`. - * ``max_prediction_length``: maximum prediction length - Taken directly from `self.max_prediction_length`. - * ``min_encoder_length``: minimum encoder length - Taken directly from `self.min_encoder_length`. - * ``min_prediction_length``: minimum prediction length - Taken directly from `self.min_prediction_length`. - + * ``encoder_cat``: Number of categorical variables in the encoder. + Computed as ``len(self.categorical_indices)``, which counts the + categorical feature indices. + * ``encoder_cont``: Number of continuous variables in the encoder. + Computed as ``len(self.continuous_indices)``, which counts the + continuous feature indices. + * ``decoder_cat``: Number of categorical variables in the decoder that + are known in advance. + Computed by filtering ``self.time_series_metadata["cols"]["x"]`` + where col_type == "C"(categorical) and col_known == "K" (known) + * ``decoder_cont``: Number of continuous variables in the decoder that + are known in advance. + Computed by filtering ``self.time_series_metadata["cols"]["x"]`` + where col_type == "F"(continuous) and col_known == "K"(known) + * ``target``: Number of target variables. + Computed as ``len(self.time_series_metadata["cols"]["y"])``, which + gives the number of output target columns.. + * ``static_categorical_features``: Number of static categorical features + Computed by filtering ``self.time_series_metadata["cols"]["st"]`` + (static features) where col_type == "C" (categorical). + * ``static_continuous_features``: Number of static continuous features + Computed as difference of + ``len(self.time_series_metadata["cols"]["st"])`` (static features) + and static_categorical_features that gives static continuous feature + * ``max_encoder_length``: maximum encoder length + Taken directly from `self.max_encoder_length`. + * ``max_prediction_length``: maximum prediction length + Taken directly from `self.max_prediction_length`. + * ``min_encoder_length``: minimum encoder length + Taken directly from `self.min_encoder_length`. + * ``min_prediction_length``: minimum prediction length + Taken directly from `self.min_prediction_length`. """ encoder_cat_count = len(self.categorical_indices) encoder_cont_count = len(self.continuous_indices) From c3b40ad0f3298e84b70b12a050614da3909799e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Franz=20Kir=C3=A1ly?= Date: Tue, 13 May 2025 08:50:43 +0200 Subject: [PATCH 048/139] backwards compat of private/public attrs --- pytorch_forecasting/data/data_module.py | 8 ++++++++ pytorch_forecasting/data/timeseries/_timeseries_v2.py | 10 ++++++++++ 2 files changed, 18 insertions(+) diff --git a/pytorch_forecasting/data/data_module.py b/pytorch_forecasting/data/data_module.py index 7b0d45312..c8252014d 100644 --- a/pytorch_forecasting/data/data_module.py +++ b/pytorch_forecasting/data/data_module.py @@ -163,6 +163,14 @@ def __init__( else: self.continuous_indices.append(idx) + # overwrite __init__ params for upwards compatibility with AS PRs + # todo: should we avoid this and ensure classes are dataclass-like? + self.min_prediction_length = self._min_prediction_length + self.min_encoder_length = self._min_encoder_length + self.categorical_encoders = self._categorical_encoders + self.scalers = self._scalers + self.target_normalizer = self._target_normalizer + def _prepare_metadata(self): """Prepare metadata for model initialisation. diff --git a/pytorch_forecasting/data/timeseries/_timeseries_v2.py b/pytorch_forecasting/data/timeseries/_timeseries_v2.py index 178b273bc..d5ecbcabb 100644 --- a/pytorch_forecasting/data/timeseries/_timeseries_v2.py +++ b/pytorch_forecasting/data/timeseries/_timeseries_v2.py @@ -140,6 +140,16 @@ def __init__( self._prepare_metadata() + # overwrite __init__ params for upwards compatibility with AS PRs + # todo: should we avoid this and ensure classes are dataclass-like? + self.group = self._group + self.target = self._target + self.num = self._num + self.cat = self._cat + self.known = self._known + self.unknown = self._unknown + self.static = self._static + def _prepare_metadata(self): """Prepare metadata for the dataset. From 38c28dc031ecebddca3385bb0f1c58b4423a1b35 Mon Sep 17 00:00:00 2001 From: Aryan Saini Date: Wed, 14 May 2025 18:51:05 +0530 Subject: [PATCH 049/139] add tests --- .../tft_version_two.py | 38 +- tests/test_models/test_tft_v2.py | 367 ++++++++++++++++++ 2 files changed, 398 insertions(+), 7 deletions(-) create mode 100644 tests/test_models/test_tft_v2.py diff --git a/pytorch_forecasting/models/temporal_fusion_transformer/tft_version_two.py b/pytorch_forecasting/models/temporal_fusion_transformer/tft_version_two.py index 2bfe407d7..1a1634356 100644 --- a/pytorch_forecasting/models/temporal_fusion_transformer/tft_version_two.py +++ b/pytorch_forecasting/models/temporal_fusion_transformer/tft_version_two.py @@ -157,11 +157,11 @@ def forward(self, x: Dict[str, torch.Tensor]) -> Dict[str, torch.Tensor]: if self.static_context_linear is not None: static_cat = x.get( "static_categorical_features", - torch.zeros(batch_size, 0, device=self.device), + torch.zeros(batch_size, 1, 0, device=self.device), ) static_cont = x.get( "static_continuous_features", - torch.zeros(batch_size, 0, device=self.device), + torch.zeros(batch_size, 1, 0, device=self.device), ) if static_cat.size(2) == 0 and static_cont.size(2) == 0: @@ -180,17 +180,41 @@ def forward(self, x: Dict[str, torch.Tensor]) -> Dict[str, torch.Tensor]: static_context = static_context.view(batch_size, self.hidden_size) else: - static_input = torch.cat([static_cont, static_cat], dim=1).to( + static_input = torch.cat([static_cont, static_cat], dim=2).to( dtype=self.static_context_linear.weight.dtype ) static_context = self.static_context_linear(static_input) static_context = static_context.view(batch_size, self.hidden_size) - encoder_weights = self.encoder_var_selection(encoder_input) - encoder_input = encoder_input * encoder_weights + if self.encoder_var_selection is not None: + encoder_weights = self.encoder_var_selection(encoder_input) + encoder_input = encoder_input * encoder_weights + else: + if self.encoder_input_dim == 0: + encoder_input = torch.zeros( + batch_size, + self.max_encoder_length, + 1, + device=self.device, + dtype=encoder_input.dtype, + ) + else: + encoder_input = encoder_input - decoder_weights = self.decoder_var_selection(decoder_input) - decoder_input = decoder_input * decoder_weights + if self.decoder_var_selection is not None: + decoder_weights = self.decoder_var_selection(decoder_input) + decoder_input = decoder_input * decoder_weights + else: + if self.decoder_input_dim == 0: + decoder_input = torch.zeros( + batch_size, + self.max_prediction_length, + 1, + device=self.device, + dtype=decoder_input.dtype, + ) + else: + decoder_input = decoder_input if static_context is not None: encoder_static_context = static_context.unsqueeze(1).expand( diff --git a/tests/test_models/test_tft_v2.py b/tests/test_models/test_tft_v2.py new file mode 100644 index 000000000..e69d3d06d --- /dev/null +++ b/tests/test_models/test_tft_v2.py @@ -0,0 +1,367 @@ +import numpy as np +import pandas as pd +import pytest +import torch +import torch.nn as nn + +from pytorch_forecasting.data.data_module import EncoderDecoderTimeSeriesDataModule +from pytorch_forecasting.data.timeseries import TimeSeries +from pytorch_forecasting.models.temporal_fusion_transformer.tft_version_two import TFT + +BATCH_SIZE_TEST = 2 +MAX_ENCODER_LENGTH_TEST = 10 +MAX_PREDICTION_LENGTH_TEST = 5 +HIDDEN_SIZE_TEST = 8 +OUTPUT_SIZE_TEST = 1 +ATTENTION_HEAD_SIZE_TEST = 2 +NUM_LAYERS_TEST = 1 +DROPOUT_TEST = 0.1 + + +def get_default_test_metadata( + enc_cont=2, + enc_cat=1, + dec_cont=1, + dec_cat=1, + static_cat=1, + static_cont=1, + output_size=OUTPUT_SIZE_TEST, +): + return { + "max_encoder_length": MAX_ENCODER_LENGTH_TEST, + "max_prediction_length": MAX_PREDICTION_LENGTH_TEST, + "encoder_cont": enc_cont, + "encoder_cat": enc_cat, + "decoder_cont": dec_cont, + "decoder_cat": dec_cat, + "static_categorical_features": static_cat, + "static_continuous_features": static_cont, + "target": output_size, + } + + +def create_tft_input_batch_for_test(metadata, batch_size=BATCH_SIZE_TEST, device="cpu"): + def _get_dim_val(key): + return metadata.get(key, 0) + + x = { + "encoder_cont": torch.randn( + batch_size, + metadata["max_encoder_length"], + _get_dim_val("encoder_cont"), + device=device, + ), + "encoder_cat": torch.randn( + batch_size, + metadata["max_encoder_length"], + _get_dim_val("encoder_cat"), + device=device, + ), + "decoder_cont": torch.randn( + batch_size, + metadata["max_prediction_length"], + _get_dim_val("decoder_cont"), + device=device, + ), + "decoder_cat": torch.randn( + batch_size, + metadata["max_prediction_length"], + _get_dim_val("decoder_cat"), + device=device, + ), + "static_categorical_features": torch.randn( + batch_size, 1, _get_dim_val("static_categorical_features"), device=device + ), + "static_continuous_features": torch.randn( + batch_size, 1, _get_dim_val("static_continuous_features"), device=device + ), + "encoder_lengths": torch.full( + (batch_size,), + metadata["max_encoder_length"], + dtype=torch.long, + device=device, + ), + "decoder_lengths": torch.full( + (batch_size,), + metadata["max_prediction_length"], + dtype=torch.long, + device=device, + ), + "groups": torch.arange(batch_size, device=device).unsqueeze(1), + "encoder_time_idx": torch.stack( + [torch.arange(metadata["max_encoder_length"], device=device)] * batch_size + ), + "decoder_time_idx": torch.stack( + [ + torch.arange( + metadata["max_encoder_length"], + metadata["max_encoder_length"] + metadata["max_prediction_length"], + device=device, + ) + ] + * batch_size + ), + "target_scale": torch.ones((batch_size, 1), device=device), + } + return x + + +dummy_loss_for_test = nn.MSELoss() + + +@pytest.fixture(scope="module") +def tft_model_params_fixture_func(): + return { + "loss": dummy_loss_for_test, + "hidden_size": HIDDEN_SIZE_TEST, + "num_layers": NUM_LAYERS_TEST, + "attention_head_size": ATTENTION_HEAD_SIZE_TEST, + "dropout": DROPOUT_TEST, + "output_size": OUTPUT_SIZE_TEST, + } + + +class TestTFTInitialization: + def test_basic_initialization(self, tft_model_params_fixture_func): + metadata = get_default_test_metadata(output_size=OUTPUT_SIZE_TEST) + model = TFT(**tft_model_params_fixture_func, metadata=metadata) + assert model.hidden_size == HIDDEN_SIZE_TEST + assert model.num_layers == NUM_LAYERS_TEST + assert hasattr(model, "metadata") and model.metadata == metadata + assert ( + model.encoder_input_dim + == metadata["encoder_cont"] + metadata["encoder_cat"] + ) + assert ( + model.static_input_dim + == metadata["static_categorical_features"] + + metadata["static_continuous_features"] + ) + assert isinstance(model.lstm_encoder, nn.LSTM) + assert model.lstm_encoder.input_size == max(1, model.encoder_input_dim) + assert isinstance(model.self_attention, nn.MultiheadAttention) + if hasattr(model, "hparams") and model.hparams: + assert model.hparams.get("hidden_size") == HIDDEN_SIZE_TEST + assert model.output_size == OUTPUT_SIZE_TEST + + def test_initialization_no_time_varying_features( + self, tft_model_params_fixture_func + ): + metadata = get_default_test_metadata( + enc_cont=0, enc_cat=0, dec_cont=0, dec_cat=0, output_size=OUTPUT_SIZE_TEST + ) + model = TFT(**tft_model_params_fixture_func, metadata=metadata) + assert model.encoder_input_dim == 0 + assert model.encoder_var_selection is None + assert model.lstm_encoder.input_size == 1 + assert model.decoder_input_dim == 0 + assert model.decoder_var_selection is None + assert model.lstm_decoder.input_size == 1 + + def test_initialization_no_static_features(self, tft_model_params_fixture_func): + metadata = get_default_test_metadata( + static_cat=0, static_cont=0, output_size=OUTPUT_SIZE_TEST + ) + model = TFT(**tft_model_params_fixture_func, metadata=metadata) + assert model.static_input_dim == 0 + assert model.static_context_linear is None + + +class TestTFTForwardPass: + @pytest.mark.parametrize( + "enc_c, enc_k, dec_c, dec_k, stat_c, stat_k", + [ + (2, 1, 1, 1, 1, 1), + (2, 0, 1, 0, 0, 0), + (0, 0, 0, 0, 1, 1), + (0, 0, 0, 0, 0, 0), + (1, 0, 1, 0, 1, 0), + (1, 0, 1, 0, 0, 1), + ], + ) + def test_forward_pass_configs( + self, tft_model_params_fixture_func, enc_c, enc_k, dec_c, dec_k, stat_c, stat_k + ): + current_tft_actual_output_size = tft_model_params_fixture_func["output_size"] + metadata = get_default_test_metadata( + enc_cont=enc_c, + enc_cat=enc_k, + dec_cont=dec_c, + dec_cat=dec_k, + static_cat=stat_c, + static_cont=stat_k, + output_size=current_tft_actual_output_size, + ) + model_params = tft_model_params_fixture_func.copy() + model_params["output_size"] = current_tft_actual_output_size + model = TFT(**model_params, metadata=metadata) + model.eval() + device = torch.device("cuda" if torch.cuda.is_available() else "cpu") + model.to(device) + x = create_tft_input_batch_for_test( + metadata, batch_size=BATCH_SIZE_TEST, device=device + ) + output_dict = model(x) + predictions = output_dict["prediction"] + assert predictions.shape == ( + BATCH_SIZE_TEST, + MAX_PREDICTION_LENGTH_TEST, + current_tft_actual_output_size, + ) + assert not torch.isnan(predictions).any(), "NaNs in prediction" + assert not torch.isinf(predictions).any(), "Infs in prediction" + + +@pytest.fixture +def sample_pandas_data_for_test(): + """Create sample data ensuring all feature columns are numeric (float32).""" + series_len = MAX_ENCODER_LENGTH_TEST + MAX_PREDICTION_LENGTH_TEST + 5 + num_groups = 6 + data = [] + + for i in range(num_groups): + static_cont_val = np.float32(i * 10.0) + static_cat_code = np.float32(i % 2) + + df_group = pd.DataFrame( + { + "time_idx": np.arange(series_len, dtype=np.int64), + "group_id_str": np.repeat(f"g{i}", series_len), + "target": np.random.rand(series_len).astype(np.float32) + i, + "enc_cont1": np.random.rand(series_len).astype(np.float32), + "enc_cat1_codes": np.random.randint(0, 3, series_len).astype( + np.float32 + ), + "dec_known_cont": np.sin(np.arange(series_len) / 5.0).astype( + np.float32 + ), + "dec_known_cat_codes": np.random.randint(0, 2, series_len).astype( + np.float32 + ), + "static_cat_feat_codes": np.full( + series_len, static_cat_code, dtype=np.float32 + ), + "static_cont_feat": np.full( + series_len, static_cont_val, dtype=np.float32 + ), + } + ) + data.append(df_group) + + df = pd.concat(data, ignore_index=True) + + df["group_id"] = df["group_id_str"].astype("category") + df.drop(columns=["group_id_str"], inplace=True) + + return df + + +@pytest.fixture +def timeseries_obj_for_test(sample_pandas_data_for_test): + df = sample_pandas_data_for_test + + return TimeSeries( + data=df, + time="time_idx", + target="target", + group=["group_id"], + num=[ + "enc_cont1", + "enc_cat1_codes", + "dec_known_cont", + "dec_known_cat_codes", + "static_cat_feat_codes", + "static_cont_feat", + ], + cat=[], + known=["dec_known_cont", "dec_known_cat_codes", "time_idx"], + static=["static_cat_feat_codes", "static_cont_feat"], + ) + + +@pytest.fixture +def data_module_for_test(timeseries_obj_for_test): + dm = EncoderDecoderTimeSeriesDataModule( + time_series_dataset=timeseries_obj_for_test, + batch_size=BATCH_SIZE_TEST, + max_encoder_length=MAX_ENCODER_LENGTH_TEST, + max_prediction_length=MAX_PREDICTION_LENGTH_TEST, + train_val_test_split=(0.5, 0.25, 0.25), + num_workers=0, # Added for consistency + ) + dm.setup("fit") + dm.setup("test") + return dm + + +class TestTFTWithDataModule: + def test_model_with_datamodule_integration( + self, tft_model_params_fixture_func, data_module_for_test + ): + dm = data_module_for_test + model_metadata_from_dm = dm.metadata + + assert ( + model_metadata_from_dm["encoder_cont"] == 6 + ), f"Actual encoder_cont: {model_metadata_from_dm['encoder_cont']}" + assert ( + model_metadata_from_dm["encoder_cat"] == 0 + ), f"Actual encoder_cat: {model_metadata_from_dm['encoder_cat']}" + assert ( + model_metadata_from_dm["decoder_cont"] == 2 + ), f"Actual decoder_cont: {model_metadata_from_dm['decoder_cont']}" + assert ( + model_metadata_from_dm["decoder_cat"] == 0 + ), f"Actual decoder_cat: {model_metadata_from_dm['decoder_cat']}" + assert ( + model_metadata_from_dm["static_categorical_features"] == 0 + ), f"Actual static_cat: {model_metadata_from_dm['static_categorical_features']}" + assert ( + model_metadata_from_dm["static_continuous_features"] == 2 + ), f"Actual static_cont: {model_metadata_from_dm['static_continuous_features']}" + assert model_metadata_from_dm["target"] == 1 + + tft_init_args = tft_model_params_fixture_func.copy() + tft_init_args["output_size"] = model_metadata_from_dm["target"] + model = TFT(**tft_init_args, metadata=model_metadata_from_dm) + device = torch.device("cuda" if torch.cuda.is_available() else "cpu") + model.to(device) + model.eval() + + train_loader = dm.train_dataloader() + batch_x, batch_y = next(iter(train_loader)) + + actual_batch_size = batch_x["encoder_cont"].shape[0] + batch_x = {k: v.to(device) for k, v in batch_x.items()} + batch_y = batch_y.to(device) + + assert ( + batch_x["encoder_cont"].shape[2] == model_metadata_from_dm["encoder_cont"] + ) + assert batch_x["encoder_cat"].shape[2] == model_metadata_from_dm["encoder_cat"] + assert ( + batch_x["decoder_cont"].shape[2] == model_metadata_from_dm["decoder_cont"] + ) + assert batch_x["decoder_cat"].shape[2] == model_metadata_from_dm["decoder_cat"] + # assert ( + # batch_x["static_categorical_features"].shape[2] + # == model_metadata_from_dm["static_categorical_features"] + # ) + # assert ( + # batch_x["static_continuous_features"].shape[2] + # == model_metadata_from_dm["static_continuous_features"] + # ) + + output_dict = model(batch_x) + predictions = output_dict["prediction"] + assert predictions.shape == ( + actual_batch_size, + MAX_PREDICTION_LENGTH_TEST, + model_metadata_from_dm["target"], + ) + assert not torch.isnan(predictions).any() + assert batch_y.shape == ( + actual_batch_size, + MAX_PREDICTION_LENGTH_TEST, + model_metadata_from_dm["target"], + ) From 9d80eb822e47c92e3b542cd70fe98103e00bd829 Mon Sep 17 00:00:00 2001 From: Aryan Saini Date: Wed, 14 May 2025 19:10:57 +0530 Subject: [PATCH 050/139] add tests --- tests/test_models/test_tft_v2.py | 311 +++++++++++++++---------------- 1 file changed, 152 insertions(+), 159 deletions(-) diff --git a/tests/test_models/test_tft_v2.py b/tests/test_models/test_tft_v2.py index e69d3d06d..0455ad818 100644 --- a/tests/test_models/test_tft_v2.py +++ b/tests/test_models/test_tft_v2.py @@ -121,95 +121,92 @@ def tft_model_params_fixture_func(): } -class TestTFTInitialization: - def test_basic_initialization(self, tft_model_params_fixture_func): - metadata = get_default_test_metadata(output_size=OUTPUT_SIZE_TEST) - model = TFT(**tft_model_params_fixture_func, metadata=metadata) - assert model.hidden_size == HIDDEN_SIZE_TEST - assert model.num_layers == NUM_LAYERS_TEST - assert hasattr(model, "metadata") and model.metadata == metadata - assert ( - model.encoder_input_dim - == metadata["encoder_cont"] + metadata["encoder_cat"] - ) - assert ( - model.static_input_dim - == metadata["static_categorical_features"] - + metadata["static_continuous_features"] - ) - assert isinstance(model.lstm_encoder, nn.LSTM) - assert model.lstm_encoder.input_size == max(1, model.encoder_input_dim) - assert isinstance(model.self_attention, nn.MultiheadAttention) - if hasattr(model, "hparams") and model.hparams: - assert model.hparams.get("hidden_size") == HIDDEN_SIZE_TEST - assert model.output_size == OUTPUT_SIZE_TEST - - def test_initialization_no_time_varying_features( - self, tft_model_params_fixture_func - ): - metadata = get_default_test_metadata( - enc_cont=0, enc_cat=0, dec_cont=0, dec_cat=0, output_size=OUTPUT_SIZE_TEST - ) - model = TFT(**tft_model_params_fixture_func, metadata=metadata) - assert model.encoder_input_dim == 0 - assert model.encoder_var_selection is None - assert model.lstm_encoder.input_size == 1 - assert model.decoder_input_dim == 0 - assert model.decoder_var_selection is None - assert model.lstm_decoder.input_size == 1 - - def test_initialization_no_static_features(self, tft_model_params_fixture_func): - metadata = get_default_test_metadata( - static_cat=0, static_cont=0, output_size=OUTPUT_SIZE_TEST - ) - model = TFT(**tft_model_params_fixture_func, metadata=metadata) - assert model.static_input_dim == 0 - assert model.static_context_linear is None - - -class TestTFTForwardPass: - @pytest.mark.parametrize( - "enc_c, enc_k, dec_c, dec_k, stat_c, stat_k", - [ - (2, 1, 1, 1, 1, 1), - (2, 0, 1, 0, 0, 0), - (0, 0, 0, 0, 1, 1), - (0, 0, 0, 0, 0, 0), - (1, 0, 1, 0, 1, 0), - (1, 0, 1, 0, 0, 1), - ], +# Converted from TestTFTInitialization class +def test_basic_initialization(tft_model_params_fixture_func): + metadata = get_default_test_metadata(output_size=OUTPUT_SIZE_TEST) + model = TFT(**tft_model_params_fixture_func, metadata=metadata) + assert model.hidden_size == HIDDEN_SIZE_TEST + assert model.num_layers == NUM_LAYERS_TEST + assert hasattr(model, "metadata") and model.metadata == metadata + assert model.encoder_input_dim == metadata["encoder_cont"] + metadata["encoder_cat"] + assert ( + model.static_input_dim + == metadata["static_categorical_features"] + + metadata["static_continuous_features"] ) - def test_forward_pass_configs( - self, tft_model_params_fixture_func, enc_c, enc_k, dec_c, dec_k, stat_c, stat_k - ): - current_tft_actual_output_size = tft_model_params_fixture_func["output_size"] - metadata = get_default_test_metadata( - enc_cont=enc_c, - enc_cat=enc_k, - dec_cont=dec_c, - dec_cat=dec_k, - static_cat=stat_c, - static_cont=stat_k, - output_size=current_tft_actual_output_size, - ) - model_params = tft_model_params_fixture_func.copy() - model_params["output_size"] = current_tft_actual_output_size - model = TFT(**model_params, metadata=metadata) - model.eval() - device = torch.device("cuda" if torch.cuda.is_available() else "cpu") - model.to(device) - x = create_tft_input_batch_for_test( - metadata, batch_size=BATCH_SIZE_TEST, device=device - ) - output_dict = model(x) - predictions = output_dict["prediction"] - assert predictions.shape == ( - BATCH_SIZE_TEST, - MAX_PREDICTION_LENGTH_TEST, - current_tft_actual_output_size, - ) - assert not torch.isnan(predictions).any(), "NaNs in prediction" - assert not torch.isinf(predictions).any(), "Infs in prediction" + assert isinstance(model.lstm_encoder, nn.LSTM) + assert model.lstm_encoder.input_size == max(1, model.encoder_input_dim) + assert isinstance(model.self_attention, nn.MultiheadAttention) + if hasattr(model, "hparams") and model.hparams: + assert model.hparams.get("hidden_size") == HIDDEN_SIZE_TEST + assert model.output_size == OUTPUT_SIZE_TEST + + +def test_initialization_no_time_varying_features(tft_model_params_fixture_func): + metadata = get_default_test_metadata( + enc_cont=0, enc_cat=0, dec_cont=0, dec_cat=0, output_size=OUTPUT_SIZE_TEST + ) + model = TFT(**tft_model_params_fixture_func, metadata=metadata) + assert model.encoder_input_dim == 0 + assert model.encoder_var_selection is None + assert model.lstm_encoder.input_size == 1 + assert model.decoder_input_dim == 0 + assert model.decoder_var_selection is None + assert model.lstm_decoder.input_size == 1 + + +def test_initialization_no_static_features(tft_model_params_fixture_func): + metadata = get_default_test_metadata( + static_cat=0, static_cont=0, output_size=OUTPUT_SIZE_TEST + ) + model = TFT(**tft_model_params_fixture_func, metadata=metadata) + assert model.static_input_dim == 0 + assert model.static_context_linear is None + + +# Converted from TestTFTForwardPass class +@pytest.mark.parametrize( + "enc_c, enc_k, dec_c, dec_k, stat_c, stat_k", + [ + (2, 1, 1, 1, 1, 1), + (2, 0, 1, 0, 0, 0), + (0, 0, 0, 0, 1, 1), + (0, 0, 0, 0, 0, 0), + (1, 0, 1, 0, 1, 0), + (1, 0, 1, 0, 0, 1), + ], +) +def test_forward_pass_configs( + tft_model_params_fixture_func, enc_c, enc_k, dec_c, dec_k, stat_c, stat_k +): + current_tft_actual_output_size = tft_model_params_fixture_func["output_size"] + metadata = get_default_test_metadata( + enc_cont=enc_c, + enc_cat=enc_k, + dec_cont=dec_c, + dec_cat=dec_k, + static_cat=stat_c, + static_cont=stat_k, + output_size=current_tft_actual_output_size, + ) + model_params = tft_model_params_fixture_func.copy() + model_params["output_size"] = current_tft_actual_output_size + model = TFT(**model_params, metadata=metadata) + model.eval() + device = torch.device("cuda" if torch.cuda.is_available() else "cpu") + model.to(device) + x = create_tft_input_batch_for_test( + metadata, batch_size=BATCH_SIZE_TEST, device=device + ) + output_dict = model(x) + predictions = output_dict["prediction"] + assert predictions.shape == ( + BATCH_SIZE_TEST, + MAX_PREDICTION_LENGTH_TEST, + current_tft_actual_output_size, + ) + assert not torch.isnan(predictions).any(), "NaNs in prediction" + assert not torch.isinf(predictions).any(), "Infs in prediction" @pytest.fixture @@ -294,74 +291,70 @@ def data_module_for_test(timeseries_obj_for_test): return dm -class TestTFTWithDataModule: - def test_model_with_datamodule_integration( - self, tft_model_params_fixture_func, data_module_for_test - ): - dm = data_module_for_test - model_metadata_from_dm = dm.metadata - - assert ( - model_metadata_from_dm["encoder_cont"] == 6 - ), f"Actual encoder_cont: {model_metadata_from_dm['encoder_cont']}" - assert ( - model_metadata_from_dm["encoder_cat"] == 0 - ), f"Actual encoder_cat: {model_metadata_from_dm['encoder_cat']}" - assert ( - model_metadata_from_dm["decoder_cont"] == 2 - ), f"Actual decoder_cont: {model_metadata_from_dm['decoder_cont']}" - assert ( - model_metadata_from_dm["decoder_cat"] == 0 - ), f"Actual decoder_cat: {model_metadata_from_dm['decoder_cat']}" - assert ( - model_metadata_from_dm["static_categorical_features"] == 0 - ), f"Actual static_cat: {model_metadata_from_dm['static_categorical_features']}" - assert ( - model_metadata_from_dm["static_continuous_features"] == 2 - ), f"Actual static_cont: {model_metadata_from_dm['static_continuous_features']}" - assert model_metadata_from_dm["target"] == 1 - - tft_init_args = tft_model_params_fixture_func.copy() - tft_init_args["output_size"] = model_metadata_from_dm["target"] - model = TFT(**tft_init_args, metadata=model_metadata_from_dm) - device = torch.device("cuda" if torch.cuda.is_available() else "cpu") - model.to(device) - model.eval() - - train_loader = dm.train_dataloader() - batch_x, batch_y = next(iter(train_loader)) - - actual_batch_size = batch_x["encoder_cont"].shape[0] - batch_x = {k: v.to(device) for k, v in batch_x.items()} - batch_y = batch_y.to(device) - - assert ( - batch_x["encoder_cont"].shape[2] == model_metadata_from_dm["encoder_cont"] - ) - assert batch_x["encoder_cat"].shape[2] == model_metadata_from_dm["encoder_cat"] - assert ( - batch_x["decoder_cont"].shape[2] == model_metadata_from_dm["decoder_cont"] - ) - assert batch_x["decoder_cat"].shape[2] == model_metadata_from_dm["decoder_cat"] - # assert ( - # batch_x["static_categorical_features"].shape[2] - # == model_metadata_from_dm["static_categorical_features"] - # ) - # assert ( - # batch_x["static_continuous_features"].shape[2] - # == model_metadata_from_dm["static_continuous_features"] - # ) - - output_dict = model(batch_x) - predictions = output_dict["prediction"] - assert predictions.shape == ( - actual_batch_size, - MAX_PREDICTION_LENGTH_TEST, - model_metadata_from_dm["target"], - ) - assert not torch.isnan(predictions).any() - assert batch_y.shape == ( - actual_batch_size, - MAX_PREDICTION_LENGTH_TEST, - model_metadata_from_dm["target"], - ) +# Converted from TestTFTWithDataModule class +def test_model_with_datamodule_integration( + tft_model_params_fixture_func, data_module_for_test +): + dm = data_module_for_test + model_metadata_from_dm = dm.metadata + + assert ( + model_metadata_from_dm["encoder_cont"] == 6 + ), f"Actual encoder_cont: {model_metadata_from_dm['encoder_cont']}" + assert ( + model_metadata_from_dm["encoder_cat"] == 0 + ), f"Actual encoder_cat: {model_metadata_from_dm['encoder_cat']}" + assert ( + model_metadata_from_dm["decoder_cont"] == 2 + ), f"Actual decoder_cont: {model_metadata_from_dm['decoder_cont']}" + assert ( + model_metadata_from_dm["decoder_cat"] == 0 + ), f"Actual decoder_cat: {model_metadata_from_dm['decoder_cat']}" + assert ( + model_metadata_from_dm["static_categorical_features"] == 0 + ), f"Actual static_cat: {model_metadata_from_dm['static_categorical_features']}" + assert ( + model_metadata_from_dm["static_continuous_features"] == 2 + ), f"Actual static_cont: {model_metadata_from_dm['static_continuous_features']}" + assert model_metadata_from_dm["target"] == 1 + + tft_init_args = tft_model_params_fixture_func.copy() + tft_init_args["output_size"] = model_metadata_from_dm["target"] + model = TFT(**tft_init_args, metadata=model_metadata_from_dm) + device = torch.device("cuda" if torch.cuda.is_available() else "cpu") + model.to(device) + model.eval() + + train_loader = dm.train_dataloader() + batch_x, batch_y = next(iter(train_loader)) + + actual_batch_size = batch_x["encoder_cont"].shape[0] + batch_x = {k: v.to(device) for k, v in batch_x.items()} + batch_y = batch_y.to(device) + + assert batch_x["encoder_cont"].shape[2] == model_metadata_from_dm["encoder_cont"] + assert batch_x["encoder_cat"].shape[2] == model_metadata_from_dm["encoder_cat"] + assert batch_x["decoder_cont"].shape[2] == model_metadata_from_dm["decoder_cont"] + assert batch_x["decoder_cat"].shape[2] == model_metadata_from_dm["decoder_cat"] + # assert ( + # batch_x["static_categorical_features"].shape[2] + # == model_metadata_from_dm["static_categorical_features"] + # ) + # assert ( + # batch_x["static_continuous_features"].shape[2] + # == model_metadata_from_dm["static_continuous_features"] + # ) + + output_dict = model(batch_x) + predictions = output_dict["prediction"] + assert predictions.shape == ( + actual_batch_size, + MAX_PREDICTION_LENGTH_TEST, + model_metadata_from_dm["target"], + ) + assert not torch.isnan(predictions).any() + assert batch_y.shape == ( + actual_batch_size, + MAX_PREDICTION_LENGTH_TEST, + model_metadata_from_dm["target"], + ) From a8ccfe36d383191ba6bd23902543aed40dbe0d39 Mon Sep 17 00:00:00 2001 From: Aryan Saini Date: Wed, 14 May 2025 19:17:36 +0530 Subject: [PATCH 051/139] add tests --- tests/test_models/test_tft_v2.py | 37 +++++++++++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/tests/test_models/test_tft_v2.py b/tests/test_models/test_tft_v2.py index 0455ad818..ae74d59fc 100644 --- a/tests/test_models/test_tft_v2.py +++ b/tests/test_models/test_tft_v2.py @@ -123,6 +123,14 @@ def tft_model_params_fixture_func(): # Converted from TestTFTInitialization class def test_basic_initialization(tft_model_params_fixture_func): + """Test basic initialization of the TFT model with default metadata. + + Verifies: + - Model attributes match the provided metadata (e.g., hidden_size, num_layers). + - Proper construction of key model components (LSTM, attention, etc.). + - Correct dimensionality of input layers based on metadata. + - Model retains metadata and hyperparameters as expected. + """ metadata = get_default_test_metadata(output_size=OUTPUT_SIZE_TEST) model = TFT(**tft_model_params_fixture_func, metadata=metadata) assert model.hidden_size == HIDDEN_SIZE_TEST @@ -143,6 +151,13 @@ def test_basic_initialization(tft_model_params_fixture_func): def test_initialization_no_time_varying_features(tft_model_params_fixture_func): + """Test TFT initialization with no time-varying (encoder/decoder) features. + + Verifies: + - Model handles zero encoder/decoder input dimensions correctly. + - Skips creation of encoder/decoder variable selection networks. + - Defaults to input size 1 for LSTMs when no time-varying features exist. + """ metadata = get_default_test_metadata( enc_cont=0, enc_cat=0, dec_cont=0, dec_cat=0, output_size=OUTPUT_SIZE_TEST ) @@ -156,6 +171,12 @@ def test_initialization_no_time_varying_features(tft_model_params_fixture_func): def test_initialization_no_static_features(tft_model_params_fixture_func): + """Test TFT initialization with no static features. + + Verifies: + - Model static input dim is 0. + - Static context linear layer is not created. + """ metadata = get_default_test_metadata( static_cat=0, static_cont=0, output_size=OUTPUT_SIZE_TEST ) @@ -179,6 +200,13 @@ def test_initialization_no_static_features(tft_model_params_fixture_func): def test_forward_pass_configs( tft_model_params_fixture_func, enc_c, enc_k, dec_c, dec_k, stat_c, stat_k ): + """Test TFT forward pass across multiple feature configurations. + + Verifies: + - Model can forward pass without errors for varying combinations of input types. + - Output prediction tensor has expected shape. + - Output contains no NaNs or infinities. + """ current_tft_actual_output_size = tft_model_params_fixture_func["output_size"] metadata = get_default_test_metadata( enc_cont=enc_c, @@ -211,7 +239,6 @@ def test_forward_pass_configs( @pytest.fixture def sample_pandas_data_for_test(): - """Create sample data ensuring all feature columns are numeric (float32).""" series_len = MAX_ENCODER_LENGTH_TEST + MAX_PREDICTION_LENGTH_TEST + 5 num_groups = 6 data = [] @@ -295,6 +322,14 @@ def data_module_for_test(timeseries_obj_for_test): def test_model_with_datamodule_integration( tft_model_params_fixture_func, data_module_for_test ): + """Integration test to ensure TFT works correctly with data module. + + Verifies: + - Metadata inferred from data module matches expected input dimensions. + - Model processes real dataloader batches correctly. + - Output and target tensors from model and data module align in shape. + - No NaNs in predictions. + """ dm = data_module_for_test model_metadata_from_dm = dm.metadata From f900ba5e4d4912573e7dc79c398386e683d5e807 Mon Sep 17 00:00:00 2001 From: Aryan Saini Date: Wed, 14 May 2025 19:24:21 +0530 Subject: [PATCH 052/139] add more docstrings --- tests/test_models/test_tft_v2.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/tests/test_models/test_tft_v2.py b/tests/test_models/test_tft_v2.py index ae74d59fc..d79eac874 100644 --- a/tests/test_models/test_tft_v2.py +++ b/tests/test_models/test_tft_v2.py @@ -27,6 +27,7 @@ def get_default_test_metadata( static_cont=1, output_size=OUTPUT_SIZE_TEST, ): + """Return a dict representing default metadata for TFT model initialization.""" return { "max_encoder_length": MAX_ENCODER_LENGTH_TEST, "max_prediction_length": MAX_PREDICTION_LENGTH_TEST, @@ -41,6 +42,8 @@ def get_default_test_metadata( def create_tft_input_batch_for_test(metadata, batch_size=BATCH_SIZE_TEST, device="cpu"): + """Create a synthetic input batch dictionary for testing TFT forward passes.""" + def _get_dim_val(key): return metadata.get(key, 0) @@ -111,6 +114,7 @@ def _get_dim_val(key): @pytest.fixture(scope="module") def tft_model_params_fixture_func(): + """Create a default set of model parameters for TFT.""" return { "loss": dummy_loss_for_test, "hidden_size": HIDDEN_SIZE_TEST, @@ -121,7 +125,6 @@ def tft_model_params_fixture_func(): } -# Converted from TestTFTInitialization class def test_basic_initialization(tft_model_params_fixture_func): """Test basic initialization of the TFT model with default metadata. @@ -239,6 +242,7 @@ def test_forward_pass_configs( @pytest.fixture def sample_pandas_data_for_test(): + """Create synthetic multivariate time series data as a pandas DataFrame.""" series_len = MAX_ENCODER_LENGTH_TEST + MAX_PREDICTION_LENGTH_TEST + 5 num_groups = 6 data = [] @@ -282,6 +286,7 @@ def sample_pandas_data_for_test(): @pytest.fixture def timeseries_obj_for_test(sample_pandas_data_for_test): + """Convert sample DataFrame into a TimeSeries object.""" df = sample_pandas_data_for_test return TimeSeries( @@ -305,6 +310,7 @@ def timeseries_obj_for_test(sample_pandas_data_for_test): @pytest.fixture def data_module_for_test(timeseries_obj_for_test): + """Initialize and sets up an EncoderDecoderTimeSeriesDataModule.""" dm = EncoderDecoderTimeSeriesDataModule( time_series_dataset=timeseries_obj_for_test, batch_size=BATCH_SIZE_TEST, @@ -318,7 +324,6 @@ def data_module_for_test(timeseries_obj_for_test): return dm -# Converted from TestTFTWithDataModule class def test_model_with_datamodule_integration( tft_model_params_fixture_func, data_module_for_test ): From ed1b79936df9c4cb18c29393f964228997001b98 Mon Sep 17 00:00:00 2001 From: Aryan Saini Date: Wed, 14 May 2025 19:26:40 +0530 Subject: [PATCH 053/139] add note about the commented out tests --- tests/test_models/test_tft_v2.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/test_models/test_tft_v2.py b/tests/test_models/test_tft_v2.py index d79eac874..57a50e75e 100644 --- a/tests/test_models/test_tft_v2.py +++ b/tests/test_models/test_tft_v2.py @@ -188,7 +188,6 @@ def test_initialization_no_static_features(tft_model_params_fixture_func): assert model.static_context_linear is None -# Converted from TestTFTForwardPass class @pytest.mark.parametrize( "enc_c, enc_k, dec_c, dec_k, stat_c, stat_k", [ @@ -334,6 +333,8 @@ def test_model_with_datamodule_integration( - Model processes real dataloader batches correctly. - Output and target tensors from model and data module align in shape. - No NaNs in predictions. + + Note: The commented out tests are to test a bug in data_module """ dm = data_module_for_test model_metadata_from_dm = dm.metadata From fd59bac5f807a50e89bbe3a98a8a2642f5ef3b34 Mon Sep 17 00:00:00 2001 From: PranavBhatP Date: Fri, 16 May 2025 00:58:48 +0530 Subject: [PATCH 054/139] initial commit - design decisions pending --- pytorch_forecasting/data/tslib_data_module.py | 141 ++++++++++++++++++ .../models/base/_tslib_base_model.py | 0 pytorch_forecasting/models/modules.py | 0 3 files changed, 141 insertions(+) create mode 100644 pytorch_forecasting/data/tslib_data_module.py create mode 100644 pytorch_forecasting/models/base/_tslib_base_model.py create mode 100644 pytorch_forecasting/models/modules.py diff --git a/pytorch_forecasting/data/tslib_data_module.py b/pytorch_forecasting/data/tslib_data_module.py new file mode 100644 index 000000000..7037471ea --- /dev/null +++ b/pytorch_forecasting/data/tslib_data_module.py @@ -0,0 +1,141 @@ +""" +Experimmental data module for integrating `tslib` time series deep learning library. +""" + +from typing import Any, Dict, List, Optional, Tuple, Union +import warnings + +from lightning.pytorch import LightningDataModule +import numpy as np +import pandas as pd +from sklearn.preprocessing import RobustScaler, StandardScaler +import torch +from torch.utils.data import DataLoader, Dataset + +from pytorch_forecasting.data.encoders import ( + EncoderNormalizer, + NaNLabelEncoder, + TorchNormalizer, +) +from pytorch_forecasting.data.timeseries._timeseries_v2 import TimeSeries +from pytorch_forecasting.utils._coerce import _coerce_to_dict + +NORMALIZER = Union[TorchNormalizer, EncoderNormalizer, NaNLabelEncoder] + + +class TslibDataModule(LightningDataModule): + """ + Experimental data module for integrating `tslib` time series into + PyTorch Forecasting. + + This module serves as the D2 layer for `tslib` models including transformer-based + architectures like Informer, AutoFormer, TimeXer and other model deep learning model + architectures. + + Parameters + ---------- + time_series_dataset: TimeSeries + The time series dataset to be used for training and validation. This is the + newly implemented D1 layer. + model_family: str = "transformer" + The model family to be used. Currently, only "transformer" is supported. Ensures + modularity and extensibility for future model families, while considering the + diversity of model architectures currently available in the `tslib` library. + context_length: int = 96 + The length of the context window for the model. This is the number of time steps + used as input to the model. + prediction_length: int = 24 + The length of the prediction window for the model. This is the number of time + steps to be predicted by the model. + freq: str = "h" + The frequency of the time series data. This is used to determine the time steps + for the model. + add_time_features: bool = True + Whether to add frequency-based time features to the model. + features: str = "MS" + features : str, default="MS" + Feature combination mode: + - "S": Single variable forecasting (target only) + - "M": Multivariate forecasting, using all variables + - "MS": Multivariate to single, using all variables to predict target + min_prediction_idx: Optional[int] = None + The minimum index for the prediction window. This is used to ensure that the + prediction window does not exceed the length of the time series. + allow_missing_timesteps: bool = False + Whether to allow missing timesteps in the time series. If True, the model will + be able to handle missing timesteps in the input data. + add_relative_time_idx: bool = False + Whether to allow the relative time index to be used with the model. + add_target_scales: bool = False + Whether to add target scaling info. + target_normalizer : + Union[NORMALIZER, str, List[NORMALIZER], Tuple[NORMALIZER], None], + default="auto" + Normalizer for the target variable. If "auto", uses `RobustScaler`. + scalers : Optional[Dict[str, Union[StandardScaler, RobustScaler, TorchNormalizer]]], default=None #noqa: E501 + Dictionary of feature scalers. + batch_size : int, default=32 + Batch size for dataloader. + num_workers : int, default=0 + Number of workers for dataloader. + train_val_test_split : tuple, default=(0.7, 0.15, 0.15) + Proportions for train, validation, and test dataset splits. + """ # noqa: E501 + + def __init__( + self, + time_series_dataset: TimeSeries, + model_family: str = "transformer", + context_length: int = 96, + prediction_length: int = 24, + freq: str = "h", + add_time_features: bool = True, + features: str = "MS", + min_prediction_idx: Optional[int] = None, + allow_missing_timesteps: bool = False, + add_relative_time_idx: bool = False, + add_target_scales: bool = False, + target_normalizer: Union[ + NORMALIZER, str, List[NORMALIZER], Tuple[NORMALIZER], None + ] = "auto", # noqa: E501 + scalers: Optional[ + Dict[str, Union[StandardScaler, RobustScaler, TorchNormalizer]] + ] = None, # noqa: E501 + batch_size: int = 32, + num_workers: int = 0, + train_val_test_split: Tuple[float, float, float] = (0.7, 0.15, 0.15), + ) -> None: + super().__init__() + + self.time_series_dataset = time_series_dataset + self.model_family = model_family.lower() + self.context_length = context_length + self.prediction_length = prediction_length + self.min_prediction_idx = min_prediction_idx + self.freq = freq + self.add_time_features = add_time_features + self.features = features + self.allow_missing_timesteps = allow_missing_timesteps + self.add_relative_time_idx = add_relative_time_idx + self.add_target_scales = add_target_scales + self.batch_size = batch_size + self.num_workers = num_workers + self.train_val_test_split = train_val_test_split + + warnings.warn( + "TslibDataModule is experimental and subject to change. " + "The API is not stable and may change without prior warning.", + UserWarning, + ) + + if isinstance(target_normalizer, str) and target_normalizer.lower() == "auto": + self._target_normalizer = RobustScaler() + else: + self._target_normalizer = target_normalizer + + self.scalers = scalers or {} + self._train_indices = None + self._val_indices = None + self._test_indices = None + self._metadata = None + self.time_series_metadata = time_series_dataset.get_metadata() diff --git a/pytorch_forecasting/models/base/_tslib_base_model.py b/pytorch_forecasting/models/base/_tslib_base_model.py new file mode 100644 index 000000000..e69de29bb diff --git a/pytorch_forecasting/models/modules.py b/pytorch_forecasting/models/modules.py new file mode 100644 index 000000000..e69de29bb From c0ceb8a16703573144e3d0bd3aa6ab978157a341 Mon Sep 17 00:00:00 2001 From: Aryan Saini Date: Sat, 17 May 2025 02:08:06 +0530 Subject: [PATCH 055/139] add the commented out tests --- tests/test_models/test_tft_v2.py | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/tests/test_models/test_tft_v2.py b/tests/test_models/test_tft_v2.py index 57a50e75e..f541082ce 100644 --- a/tests/test_models/test_tft_v2.py +++ b/tests/test_models/test_tft_v2.py @@ -316,7 +316,6 @@ def data_module_for_test(timeseries_obj_for_test): max_encoder_length=MAX_ENCODER_LENGTH_TEST, max_prediction_length=MAX_PREDICTION_LENGTH_TEST, train_val_test_split=(0.5, 0.25, 0.25), - num_workers=0, # Added for consistency ) dm.setup("fit") dm.setup("test") @@ -377,14 +376,14 @@ def test_model_with_datamodule_integration( assert batch_x["encoder_cat"].shape[2] == model_metadata_from_dm["encoder_cat"] assert batch_x["decoder_cont"].shape[2] == model_metadata_from_dm["decoder_cont"] assert batch_x["decoder_cat"].shape[2] == model_metadata_from_dm["decoder_cat"] - # assert ( - # batch_x["static_categorical_features"].shape[2] - # == model_metadata_from_dm["static_categorical_features"] - # ) - # assert ( - # batch_x["static_continuous_features"].shape[2] - # == model_metadata_from_dm["static_continuous_features"] - # ) + assert ( + batch_x["static_categorical_features"].shape[2] + == model_metadata_from_dm["static_categorical_features"] + ) + assert ( + batch_x["static_continuous_features"].shape[2] + == model_metadata_from_dm["static_continuous_features"] + ) output_dict = model(batch_x) predictions = output_dict["prediction"] From 3828c260d4b32ee7fcd9fc300776126c70f6a3b6 Mon Sep 17 00:00:00 2001 From: Aryan Saini Date: Sat, 17 May 2025 02:09:16 +0530 Subject: [PATCH 056/139] remove note --- tests/test_models/test_tft_v2.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/test_models/test_tft_v2.py b/tests/test_models/test_tft_v2.py index f541082ce..791ea10ef 100644 --- a/tests/test_models/test_tft_v2.py +++ b/tests/test_models/test_tft_v2.py @@ -332,8 +332,6 @@ def test_model_with_datamodule_integration( - Model processes real dataloader batches correctly. - Output and target tensors from model and data module align in shape. - No NaNs in predictions. - - Note: The commented out tests are to test a bug in data_module """ dm = data_module_for_test model_metadata_from_dm = dm.metadata From 0cb7df62ff285dfb9f9dd2adfab2937562d0f792 Mon Sep 17 00:00:00 2001 From: PranavBhatP Date: Sun, 18 May 2025 12:40:40 +0530 Subject: [PATCH 057/139] add _prepare_metadata function to d2 class --- pytorch_forecasting/data/tslib_data_module.py | 146 ++++++++++++++---- 1 file changed, 116 insertions(+), 30 deletions(-) diff --git a/pytorch_forecasting/data/tslib_data_module.py b/pytorch_forecasting/data/tslib_data_module.py index 7037471ea..24fdc5b3d 100644 --- a/pytorch_forecasting/data/tslib_data_module.py +++ b/pytorch_forecasting/data/tslib_data_module.py @@ -37,33 +37,20 @@ class TslibDataModule(LightningDataModule): time_series_dataset: TimeSeries The time series dataset to be used for training and validation. This is the newly implemented D1 layer. - model_family: str = "transformer" - The model family to be used. Currently, only "transformer" is supported. Ensures - modularity and extensibility for future model families, while considering the - diversity of model architectures currently available in the `tslib` library. - context_length: int = 96 + context_length: int The length of the context window for the model. This is the number of time steps used as input to the model. - prediction_length: int = 24 + prediction_length: int The length of the prediction window for the model. This is the number of time steps to be predicted by the model. - freq: str = "h" + freq: str, default = "h" The frequency of the time series data. This is used to determine the time steps for the model. - add_time_features: bool = True - Whether to add frequency-based time features to the model. features: str = "MS" - features : str, default="MS" Feature combination mode: - "S": Single variable forecasting (target only) - "M": Multivariate forecasting, using all variables - "MS": Multivariate to single, using all variables to predict target - min_prediction_idx: Optional[int] = None - The minimum index for the prediction window. This is used to ensure that the - prediction window does not exceed the length of the time series. - allow_missing_timesteps: bool = False - Whether to allow missing timesteps in the time series. If True, the model will - be able to handle missing timesteps in the input data. add_relative_time_idx: bool = False Whether to allow the relative time index to be used with the model. add_target_scales: bool = False @@ -74,6 +61,8 @@ class TslibDataModule(LightningDataModule): Normalizer for the target variable. If "auto", uses `RobustScaler`. scalers : Optional[Dict[str, Union[StandardScaler, RobustScaler, TorchNormalizer]]], default=None #noqa: E501 Dictionary of feature scalers. + shuffle : bool, default=True + Whether to shuffle the data at every epoch. batch_size : int, default=32 Batch size for dataloader. num_workers : int, default=0 @@ -85,14 +74,10 @@ class TslibDataModule(LightningDataModule): def __init__( self, time_series_dataset: TimeSeries, - model_family: str = "transformer", - context_length: int = 96, - prediction_length: int = 24, + context_length: int, + prediction_length: int, freq: str = "h", - add_time_features: bool = True, features: str = "MS", - min_prediction_idx: Optional[int] = None, - allow_missing_timesteps: bool = False, add_relative_time_idx: bool = False, add_target_scales: bool = False, target_normalizer: Union[ @@ -101,26 +86,27 @@ def __init__( scalers: Optional[ Dict[str, Union[StandardScaler, RobustScaler, TorchNormalizer]] ] = None, # noqa: E501 + shuffle: bool = True, batch_size: int = 32, num_workers: int = 0, train_val_test_split: Tuple[float, float, float] = (0.7, 0.15, 0.15), + collate_fn: Optional[callable] = None, + **kwargs, ) -> None: super().__init__() self.time_series_dataset = time_series_dataset - self.model_family = model_family.lower() self.context_length = context_length self.prediction_length = prediction_length - self.min_prediction_idx = min_prediction_idx self.freq = freq - self.add_time_features = add_time_features self.features = features - self.allow_missing_timesteps = allow_missing_timesteps self.add_relative_time_idx = add_relative_time_idx self.add_target_scales = add_target_scales self.batch_size = batch_size self.num_workers = num_workers self.train_val_test_split = train_val_test_split + self.collate_fn = collate_fn + self.kwargs = kwargs warnings.warn( "TslibDataModule is experimental and subject to change. " @@ -134,8 +120,108 @@ def __init__( self._target_normalizer = target_normalizer self.scalers = scalers or {} - self._train_indices = None - self._val_indices = None - self._test_indices = None - self._metadata = None + self.shuffle = shuffle + + self.train_dataset = None + self.val_dataset = None + self.test_dataset = None + self.time_series_metadata = time_series_dataset.get_metadata() + + def _prepare_metadata(self) -> None: + """ + Prepare metadata for `tslib` time series data module. + + Returns + ------- + dict containing the following as keys: + - feature_names: Dict[str, List[str]] + Dictionary of feature names for each feature type. + - feature_indices: Dict[str, List[int]] + Dictionary of feature indices for each feature type. + - n_features: Dict[str, int] + Dictionary of number of features for each feature type. + - context_length: int + Length of the context window for the model, as set in the data module. + - prediction_length: int + Length of the prediction window for the model, as set in the data + module. + - freq: str or None + - features: str + Feature combination mode. + """ + # TODO: include handling for datasets without get_metadata() + ds_metadata = self.time_series_metadata.get_metadata() + + feature_names = { + "categorical": [], + "continuous": [], + "static": [], + "known": [], + "unknown": [], + "target": [], + "all": [], + } + + feature_indices = { + "categorical": [], + "continuous": [], + "static": [], + "known": [], + "unknown": [], + "target": [], + "all": [], + } + + cols = ds_metadata.get("cols", {}) + col_type = ds_metadata.get("col_type", {}) + col_known = ds_metadata.get("col_known", {}) + + all_features = cols.get("x", []) + static_features = cols.get("st", []) + target_features = cols.get("y", []) + feature_names["all"] = list(all_features) + feature_names["static"] = list(static_features) + feature_names["target"] = list(target_features) + + for idx, col in enumerate(all_features): + if col_type.get(col, "F") == "C": + feature_names["categorical"].append(col) + feature_indices["categorical"].append(idx) + else: + feature_names["continuous"].append(col) + feature_indices["continuous"].append(idx) + + if col_known.get(col, "U") == "K": + feature_names["known"].append(col) + feature_indices["known"].append(idx) + else: + feature_names["unknown"].append(col) + feature_indices["unknown"].append(idx) + + static_cat_names, static_cont_names = [], [] + for col in static_features: + if col_type.get(col, "F") == "C": + static_cat_names.append(col) + else: + static_cont_names.append(col) + + feature_names["static_categorical"] = static_cat_names + feature_names["static_continuous"] = static_cont_names + + for idx, col in enumerate(target_features): + feature_indices["target"].append(idx) + + n_features = {k: len(v) for k, v in feature_names.items()} + + metadata = dict( + feature_names=feature_names, + feature_indices=feature_indices, + n_features=n_features, + context_length=self.context_length, + prediction_length=self.prediction_length, + freq=self.freq, + features=self.features, + ) + + return metadata From b32ed0e30ef6b0ec1849944ce66108bf595d22a6 Mon Sep 17 00:00:00 2001 From: PranavBhatP Date: Sun, 18 May 2025 22:51:14 +0530 Subject: [PATCH 058/139] add data processing and window mechanism for data module --- pytorch_forecasting/data/tslib_data_module.py | 114 ++++++++++++++++++ 1 file changed, 114 insertions(+) diff --git a/pytorch_forecasting/data/tslib_data_module.py b/pytorch_forecasting/data/tslib_data_module.py index 24fdc5b3d..6cb284bec 100644 --- a/pytorch_forecasting/data/tslib_data_module.py +++ b/pytorch_forecasting/data/tslib_data_module.py @@ -22,6 +22,8 @@ NORMALIZER = Union[TorchNormalizer, EncoderNormalizer, NaNLabelEncoder] +# class _TsLibDataset(Dataset): + class TslibDataModule(LightningDataModule): """ @@ -69,6 +71,8 @@ class TslibDataModule(LightningDataModule): Number of workers for dataloader. train_val_test_split : tuple, default=(0.7, 0.15, 0.15) Proportions for train, validation, and test dataset splits. + collate_fn : Optional[callable], default=None + Custom collate function for the dataloader. """ # noqa: E501 def __init__( @@ -119,6 +123,17 @@ def __init__( else: self._target_normalizer = target_normalizer + self._metadata = None + + self.categorical_indices = [] + self.continuous_indices = [] + + for idx, col in enumerate(self.time_series_dataset["cols"]["x"]): + if self.time_series_metadata["col_type"].get(col) == "C": + self.categorical_indices.append(idx) + else: + self.continuous_indices.append(idx) + self.scalers = scalers or {} self.shuffle = shuffle @@ -225,3 +240,102 @@ def _prepare_metadata(self) -> None: ) return metadata + + @property + def metadata(self) -> Dict[str, Any]: + """ + Compute the metadata via the `_prepare_metadata` method. + This method is called when the `metadata` property is accessed for the first. + Returns + ------- + dict + Metadata for the data module. Refer to the `_prepare_metadata` method for + the keys and values in the metadata dictionary. + """ + if not hasattr(self, "_metadata"): + self._metadata = self._prepare_metadata() + return self._metadata + + def _process_data(self, idx: torch.Tensor) -> List[Dict[str, Any]]: + """ + Process the the time series data at the given index, before feeding it + to the `_TsLibDataset` class. + + Parameters + ---------- + idx : torch.Tensor + The index of the time series data to be processed. + + Returns + ------- + Dict[str, torch.Tensor] + A dictionary containing the processed data. + + Notes + ----- + - The target data `y` and features `x` are converted to torch.float32 tensors. + - The timepoints before the cutoff time are masked off. + - Splits data into categorical and continous features, which are grouped based on the indices. + """ # noqa: E501 + + series = self.time_series_dataset[idx] + if series is None: + raise ValueError(f"series at index {idx} is None. Check the dataset.") + target = series["y"] + features = series["x"] + timestep = series["t"] + cutoff_time = series["cutoff_time"] + + mask_timestep = torch.Tensor(timestep <= cutoff_time, dtype=torch.bool) + + if isinstance(torch, torch.Tensor): + target = target.float() + else: + target = torch.tensor(target, dtype=torch.float32) + + if isinstance(features, torch.Tensor): + features = features.float() + else: + features = torch.tensor(features, dtype=torch.float32) + + categorical_features = ( + features[:, self.categorical_indices] + if self.categorical_indices + else torch.zeros((series.shape[0], 0)) + ) + + continuous_features = ( + features[:, self.continuous_indices] + if self.continuous_indices + else torch.zeros((series.shape[0], 0)) + ) + + return { + "features": { + "categorical": categorical_features, + "continuous": continuous_features, + }, + "target": target, + "static": series["st"], + "group": series.get("group", torch.tensor([0])), + "length": len(series), + "time_mask": mask_timestep, + "cutoff_time": cutoff_time, + "timestep": timestep, + } + + def _create_windows(self, indices: torch.Tensor) -> List[Tuple[int, int, int, int]]: + """ + Create windows for the data in the given indices, for training, testing + and validation. + + Parameters + ---------- + indices : torch.Tensor + The indices of the time series data to be processed. + + Returns + ------- + List[Tuple[int, int, int, int]] + A list of tuples containing the start and end indices for the windows. + """ From cb555780474f46abc0e5802f1241373bb179f4b5 Mon Sep 17 00:00:00 2001 From: PranavBhatP Date: Mon, 19 May 2025 01:19:54 +0530 Subject: [PATCH 059/139] implement scaling, normalization in preprocessing, and windowing --- pytorch_forecasting/data/tslib_data_module.py | 105 +++++++++++++++++- 1 file changed, 99 insertions(+), 6 deletions(-) diff --git a/pytorch_forecasting/data/tslib_data_module.py b/pytorch_forecasting/data/tslib_data_module.py index 6cb284bec..a9e58e3d8 100644 --- a/pytorch_forecasting/data/tslib_data_module.py +++ b/pytorch_forecasting/data/tslib_data_module.py @@ -65,6 +65,9 @@ class TslibDataModule(LightningDataModule): Dictionary of feature scalers. shuffle : bool, default=True Whether to shuffle the data at every epoch. + window_stride : int, default=1 + The stride for the sliding window. This is used to create overlapping windows + for the data. batch_size : int, default=32 Batch size for dataloader. num_workers : int, default=0 @@ -88,9 +91,13 @@ def __init__( NORMALIZER, str, List[NORMALIZER], Tuple[NORMALIZER], None ] = "auto", # noqa: E501 scalers: Optional[ - Dict[str, Union[StandardScaler, RobustScaler, TorchNormalizer]] + Dict[ + str, + Union[StandardScaler, RobustScaler, TorchNormalizer, EncoderNormalizer], + ] ] = None, # noqa: E501 shuffle: bool = True, + window_stride: int = 1, batch_size: int = 32, num_workers: int = 0, train_val_test_split: Tuple[float, float, float] = (0.7, 0.15, 0.15), @@ -286,7 +293,7 @@ def _process_data(self, idx: torch.Tensor) -> List[Dict[str, Any]]: timestep = series["t"] cutoff_time = series["cutoff_time"] - mask_timestep = torch.Tensor(timestep <= cutoff_time, dtype=torch.bool) + mask_timestep = torch.tensor(timestep <= cutoff_time, dtype=torch.bool) if isinstance(torch, torch.Tensor): target = target.float() @@ -298,19 +305,59 @@ def _process_data(self, idx: torch.Tensor) -> List[Dict[str, Any]]: else: features = torch.tensor(features, dtype=torch.float32) + # scaling and normlization + target_scale = {} + + if self._target_normalizer is not None: + if self.add_target_scales: + if hasattr(self._target_normalizer, "scale_"): + target_scale["scale"] = torch.tensor(self._target_normalizer.scale_) + if hasattr(self._target_normalizer, "center_"): + target_scale["center"] = torch.tensor( + self._target_normalizer.center_ + ) # noqa: E501 + + if isinstance(self._target_normalizer, TorchNormalizer): + target = self._target_normalizer.transform(target) + else: + # extra case for handling non-native normalizers + # apart from those in NORMALIZER. + target_np = target.reshape(-1, 1).numpy() + target = torch.tensor( + self._target_normalizer.transform(target_np), + dtype=torch.float32, + ).reshape(target.shape) + + if self.scalers: + feature_indices = self.metadat["feature_indices"] + feature_names = self.metadata["feature_names"] + + for i, idx in enumerate(feature_indices.get("continuous", [])): + feature_name = feature_names["continuous"][i] + if feature_name in self.scalers: + scaler = self.scalers[feature_name] + if isinstance(scaler, TorchNormalizer): + features[..., idx] = scaler.transform(features[..., idx]) + else: + feature_np = features[..., idx].reshape(-1, 1).numpy() + features[..., idx] = torch.tensor( + scaler.transform(feature_np), + dtype=torch.float32, + ).reshape(features[..., idx].shape) + categorical_features = ( features[:, self.categorical_indices] if self.categorical_indices - else torch.zeros((series.shape[0], 0)) + else torch.zeros((features.shape[0], 0)) ) continuous_features = ( features[:, self.continuous_indices] if self.continuous_indices - else torch.zeros((series.shape[0], 0)) + else torch.zeros((features.shape[0], 0)) ) - return { + res = { "features": { "categorical": categorical_features, "continuous": continuous_features, @@ -324,6 +371,11 @@ def _process_data(self, idx: torch.Tensor) -> List[Dict[str, Any]]: "timestep": timestep, } + if target_scale: + res["target_scale"] = target_scale + + return res + def _create_windows(self, indices: torch.Tensor) -> List[Tuple[int, int, int, int]]: """ Create windows for the data in the given indices, for training, testing @@ -337,5 +389,46 @@ def _create_windows(self, indices: torch.Tensor) -> List[Tuple[int, int, int, in Returns ------- List[Tuple[int, int, int, int]] - A list of tuples containing the start and end indices for the windows. + A list of tuples where each tuple contains: + - series_idx: Index of time series in the dataset + - start_idx: Start index of the window + - context_length: Length of the context/encoder window + - prediction_length: Length of the prediction/decoder window """ + + windows = [] + + min_seq_length = self.context_length + self.prediction_length + + for idx in indices: + series_idx = idx.item() if isinstance(idx, torch.Tensor) else idx + sample = self.time_series_dataset[series_idx] + sequence_length = len(sample["t"]) + + if sequence_length < min_seq_length: + continue + + cutoff_time = sample.get("cutoff_time", None) + + max_start = sequence_length - min_seq_length + 1 + + stride = self.window_stride + + for start_idx in range(0, max_start, stride): + window_end = start_idx + min_seq_length - 1 # 0-indexed + + if cutoff_time is not None: # skip window if exceed cutoff time. + end_time = sample["t"][window_end].item() + if end_time > cutoff_time: + continue + + windows.append( + ( + series_idx, + start_idx, + self.context_length, + self.prediction_length, + ) + ) + + return windows From 22183360d25e13e3e05362d5f79549e828251d44 Mon Sep 17 00:00:00 2001 From: Pranav Bhat Date: Tue, 20 May 2025 11:11:52 +0530 Subject: [PATCH 060/139] create new tslib dataset, dataloader setup and collate_fn --- pytorch_forecasting/data/tslib_data_module.py | 339 +++++++++++++++++- 1 file changed, 336 insertions(+), 3 deletions(-) diff --git a/pytorch_forecasting/data/tslib_data_module.py b/pytorch_forecasting/data/tslib_data_module.py index a9e58e3d8..2a345dff2 100644 --- a/pytorch_forecasting/data/tslib_data_module.py +++ b/pytorch_forecasting/data/tslib_data_module.py @@ -22,7 +22,151 @@ NORMALIZER = Union[TorchNormalizer, EncoderNormalizer, NaNLabelEncoder] -# class _TsLibDataset(Dataset): + +class _TslibDataset(Dataset): + """ + Dataset class for `tslib` time series dataset. + + Parameters + ---------- + dataset : TimeSeries + The time series dataset to be used for training and validation. + data_module : TslibDataModule + The data module that contains the metadata and other configurations for the + dataset. + windows: List[Tuple[int, int, int, int]] + A list of tuples where each tuple contains: + - series_idx: Index of time series in the dataset + - start_idx: Start index of the window + - context_length: Length of the context/encoder window + - prediction_length: Length of the prediction/decoder window + add_relative_time_idx: bool + Whether to add relative time index to the dataset. + """ + + def __init__( + self, + dataset: TimeSeries, + data_module: "TslibDataModule", + windows: List[Tuple[int, int, int, int]], + add_relative_time_idx: bool = False, + ): + self.dataset = dataset + self.data_module = data_module + self.windows = windows + self.add_relative_time_idx = add_relative_time_idx + + def __len__(self) -> int: + return len(self.windows) + + def __getitem__(self, idx: int) -> Dict[str, Any]: + """ + Get the processed dataset item at the given index. + + Parameters + ---------- + idx : int + The index of the dataset item to be retrieved. + + Returns + ------- + x: Dict[str, torch.Tensor] + A dictionary containing the processed data. + y: torch.Tensor + The target variable. + """ + + series_idx, start_idx, context_length, prediction_length = self.windows[idx] + + processed_data = self.data_module._preprocess_data(series_idx) + + end_idx = start_idx + context_length + prediction_length + history_indices = slice(start_idx, start_idx + context_length) + future_indices = slice(start_idx + context_length, end_idx) + + feature_indices = self.data_module.metadata["feature_indices"] + + history_cont = processed_data["features"]["continuous"][history_indices] + history_cat = processed_data["features"]["categorical"][history_indices] + + future_cont = processed_data["features"]["continuous"][future_indices] + future_cat = processed_data["features"]["categorical"][future_indices] + + known_indices = set(feature_indices["known"]) + + # use masking to filter out known and unknow features. + cont_indices = feature_indices["continuous"] + cont_known_mask = torch.tensor( + [i in known_indices for i in cont_indices], dtype=torch.bool + ) + + cat_indices = feature_indices["categorical"] + cat_known_mask = torch.tensor( + [i in known_indices for i in cat_indices], dtype=torch.bool + ) + + future_cont = ( + future_cont[:, cont_known_mask] + if len(cont_known_mask) > 0 + else torch.zeros((future_cont.shape[0], 0)) + ) # noqa: E501 + future_cat = ( + future_cat[:, cat_known_mask] + if len(cat_known_mask) > 0 + else torch.zeros((future_cat.shape[0], 0)) + ) # noqa: E501 + + history_mask = ( + processed_data["time_mask"][history_indices] + if "time_mask" in processed_data + else torch.ones(context_length, dtype=torch.bool) + ) + + future_mask = ( + processed_data["time_mask"][future_indices] + if "time_mask" in processed_data + else torch.ones(prediction_length, dtype=torch.bool) + ) + + history_target = processed_data["target"][history_indices] + future_target = processed_data["target"][future_indices] + + history_time_idx = processed_data["timestep"][history_indices] + future_time_idx = processed_data["timestep"][future_indices] + + x = { + "history_cont": history_cont, + "history_cat": history_cat, + "future_cont": future_cont, + "future_cat": future_cat, + "history_length": torch.tensor(context_length), + "future_length": torch.tensor(prediction_length), + "history_mask": history_mask, + "future_mask": future_mask, + "groups": processed_data["group"], + "history_time_idx": torch.tensor(history_time_idx), + "future_time_idx": torch.tensor(future_time_idx), + "history_target": history_target, + "future_target": future_target, + "future_target_len": torch.tensor(prediction_length), + } + + if self.add_relative_time_idx: + x["history_relative_time_idx"] = torch.arange(-context_length, 0) + x["future_relative_time_idx"] = torch.arange(0, prediction_length) + + if processed_data["static"] is not None: + x["static_categorical_features"] = processed_data["static"].unsqueeze(0) + x["static_continuous_features"] = processed_data["static"].unsqueeze(0) + + if "target_scale" in processed_data: + x["target_scale"] = processed_data["target_scale"] + + y = processed_data["target"][future_indices] + if y.ndim() == 1: + y = y.unsqueeze(0) + + return x, y class TslibDataModule(LightningDataModule): @@ -263,10 +407,10 @@ def metadata(self) -> Dict[str, Any]: self._metadata = self._prepare_metadata() return self._metadata - def _process_data(self, idx: torch.Tensor) -> List[Dict[str, Any]]: + def _preprocess_data(self, idx: torch.Tensor) -> List[Dict[str, Any]]: """ Process the the time series data at the given index, before feeding it - to the `_TsLibDataset` class. + to the `_TslibDataset` class. Parameters ---------- @@ -432,3 +576,192 @@ def _create_windows(self, indices: torch.Tensor) -> List[Tuple[int, int, int, in ) return windows + + def setup(self, stage: Optional[str] = None) -> None: + """ + Setup the data module by preparing the datasets for training, + testing and validation. + + Parameters + ---------- + stage: Optional[str] + The stage of the data module. This can be "fit", "test" or "predict". + If None, the data module will be setup for training. + """ + + total_series = len(self.time_series_dataset) + + self._indices = ( + torch.randperm(total_series) if self.shuffle else torch.arange(total_series) + ) + + self._train_size = int(self.train_val_test_split[0] * total_series) + self._val_size = int(self.train_val_test_split[1] * total_series) + + self._train_indices = self._indices[: self._train_size] + self._val_indices = self._indices[ + self._train_size : self._train_size + self._val_size + ] + + self._test_indices = self._indices[ + self._train_size + self._val_size : total_series + ] + + if stage == "fit" or stage is None: + if not hasattr(self, "_train_dataset") or not hasattr(self, "_val_dataset"): + self._train_windows = self._create_windows(self._train_indices) + self._val_windows = self._create_windows(self._val_indices) + + self.train_dataset = _TslibDataset( + dataset=self.time_series_dataset, + data_module=self, + windows=self._train_windows, + add_relative_time_idx=self.add_relative_time_idx, + ) + + self.val_dataset = _TslibDataset( + dataset=self.time_series_dataset, + data_module=self, + windows=self._val_windows, + add_relative_time_idx=self.add_relative_time_idx, + ) + elif stage == "test": + if not hasattr(self, "_test_dataset"): + self._test_windows = self._create_windows(self._test_indices) + + self.test_dataset = _TslibDataset( + dataset=self.time_series_dataset, + data_module=self, + windows=self._test_windows, + add_relative_time_idx=self.add_relative_time_idx, + ) + + elif stage == "predict": + predict_indices = torch.arange(len(self.time_series_dataset)) + self._predict_windows = self._create_windows(predict_indices) + + self.predict_dataset = _TslibDataset( + dataset=self.time_series_dataset, + data_module=self, + windows=self._predict_windows, + add_relative_time_idx=self.add_relative_time_idx, + ) + + def train_dataloader(self) -> DataLoader: + """ + Create the train dataloader. + + Returns + ------- + DataLoader + The train dataloader. + """ + return DataLoader( + self.train_dataset, + batch_size=self.batch_size, + shuffle=self.shuffle, + num_workers=self.num_workers, + collate_fn=self.collate_fn, + ) + + def val_dataloader(self) -> DataLoader: + """ + Create the validation dataloader. + Returns + ------- + DataLoader + The validation dataloader. + """ + return DataLoader( + self.val_dataset, + batch_size=self.batch_size, + num_workers=self.num_workers, + collate_fn=self.collate_fn, + ) + + def test_dataloader(self) -> DataLoader: + """ + Create the test dataloader. + + Returns + ------- + DataLoader + The test dataloader. + """ + return DataLoader( + self.test_dataset, + batch_size=self.batch_size, + num_workers=self.num_workers, + collate_fn=self.collate_fn, + ) + + def predict_dataloader(self) -> DataLoader: + """ + Create the prediction dataloader. + + Returns + ------- + DataLoader + The prediction dataloader. + """ + return DataLoader( + self.predict_dataset, + batch_size=self.batch_size, + num_workers=self.num_workers, + collate_fn=self.collate_fn, + ) + + @staticmethod + def collate_fn(batch): + """ + Custom collate function for the dataloader. + + Parameters + ---------- + batch: List[TupleDict[str, Any]] + The batch of data to be collated. + + Returns + ------- + Tuple[Dict[str, torch.Tensor], torch.Tensor] + A tuple containing the collated data and the target variable. + """ + + x_batch = { + "history_cont": torch.stack([x["history_cont"] for x, _ in batch]), + "history_cat": torch.stack([x["history_cat"] for x, _ in batch]), + "future_cont": torch.stack([x["future_cont"] for x, _ in batch]), + "future_cat": torch.stack([x["future_cat"] for x, _ in batch]), + "history_length": torch.stack([x["history_length"] for x, _ in batch]), + "future_length": torch.stack([x["future_length"] for x, _ in batch]), + "history_mask": torch.stack([x["history_mask"] for x, _ in batch]), + "future_mask": torch.stack([x["future_mask"] for x, _ in batch]), + "groups": torch.stack([x["groups"] for x, _ in batch]), + "history_time_idx": torch.stack([x["history_time_idx"] for x, _ in batch]), + "future_time_idx": torch.stack([x["future_time_idx"] for x, _ in batch]), + "history_target": torch.stack([x["history_target"] for x, _ in batch]), + "future_target": torch.stack([x["future_target"] for x, _ in batch]), + "future_target_len": torch.stack( + [x["future_target_len"] for x, _ in batch] + ), + "target_scale": torch.stack([x["target_scale"] for x, _ in batch]), + } + + if "history_relative_time_idx" in batch[0][0]: + x_batch["history_relative_time_idx"] = torch.stack( + [x["history_relative_time_idx"] for x, _ in batch] + ) + x_batch["future_relative_time_idx"] = torch.stack( + [x["future_relative_time_idx"] for x, _ in batch] + ) + + if "static_categorical_features" in batch[0][0]: + x_batch["static_categorical_features"] = torch.stack( + [x["static_categorical_features"] for x, _ in batch] + ) + x_batch["static_continuous_features"] = torch.stack( + [x["static_continuous_features"] for x, _ in batch] + ) + + y_batch = torch.stack([y for _, y in batch]) + return x_batch, y_batch From 30b541b2910c461e3e488e19137c1242c0b0627b Mon Sep 17 00:00:00 2001 From: Aryan Saini Date: Wed, 21 May 2025 00:52:29 +0530 Subject: [PATCH 061/139] make the modules private --- .../{base_model_refactor.py => _base_model_v2.py} | 13 +++++++++++++ .../{tft_version_two.py => _tft_v2.py} | 2 +- .../test_models/{test_tft_v2.py => _test_tft_v2.py} | 2 +- 3 files changed, 15 insertions(+), 2 deletions(-) rename pytorch_forecasting/models/base/{base_model_refactor.py => _base_model_v2.py} (93%) rename pytorch_forecasting/models/temporal_fusion_transformer/{tft_version_two.py => _tft_v2.py} (99%) rename tests/test_models/{test_tft_v2.py => _test_tft_v2.py} (99%) diff --git a/pytorch_forecasting/models/base/base_model_refactor.py b/pytorch_forecasting/models/base/_base_model_v2.py similarity index 93% rename from pytorch_forecasting/models/base/base_model_refactor.py rename to pytorch_forecasting/models/base/_base_model_v2.py index ccd2c2600..ddefc29fb 100644 --- a/pytorch_forecasting/models/base/base_model_refactor.py +++ b/pytorch_forecasting/models/base/_base_model_v2.py @@ -6,6 +6,7 @@ from typing import Dict, List, Optional, Tuple, Union +from warnings import warn from lightning.pytorch import LightningModule from lightning.pytorch.utilities.types import STEP_OUTPUT @@ -53,6 +54,18 @@ def __init__( self.lr_scheduler_params = ( lr_scheduler_params if lr_scheduler_params is not None else {} ) + self.model_name = self.__class__.__name__ + warn( + f"The Model '{self.model_name}' is part of an experimental rework" + "of the pytorch-forecasting model layer, scheduled for release with v2.0.0." + " The API is not stable and may change without prior warning. " + "This class is intended for beta testing and as a basic skeleton, " + "but not for stable production use. " + "Feedback and suggestions are very welcome in " + "pytorch-forecasting issue 1736, " + "https://github.com/sktime/pytorch-forecasting/issues/1736", + UserWarning, + ) def forward(self, x: Dict[str, torch.Tensor]) -> Dict[str, torch.Tensor]: """ diff --git a/pytorch_forecasting/models/temporal_fusion_transformer/tft_version_two.py b/pytorch_forecasting/models/temporal_fusion_transformer/_tft_v2.py similarity index 99% rename from pytorch_forecasting/models/temporal_fusion_transformer/tft_version_two.py rename to pytorch_forecasting/models/temporal_fusion_transformer/_tft_v2.py index 1a1634356..a0cf7d39e 100644 --- a/pytorch_forecasting/models/temporal_fusion_transformer/tft_version_two.py +++ b/pytorch_forecasting/models/temporal_fusion_transformer/_tft_v2.py @@ -9,7 +9,7 @@ import torch.nn as nn from torch.optim import Optimizer -from pytorch_forecasting.models.base.base_model_refactor import BaseModel +from pytorch_forecasting.models.base._base_model_v2 import BaseModel class TFT(BaseModel): diff --git a/tests/test_models/test_tft_v2.py b/tests/test_models/_test_tft_v2.py similarity index 99% rename from tests/test_models/test_tft_v2.py rename to tests/test_models/_test_tft_v2.py index 791ea10ef..13d92d5db 100644 --- a/tests/test_models/test_tft_v2.py +++ b/tests/test_models/_test_tft_v2.py @@ -6,7 +6,7 @@ from pytorch_forecasting.data.data_module import EncoderDecoderTimeSeriesDataModule from pytorch_forecasting.data.timeseries import TimeSeries -from pytorch_forecasting.models.temporal_fusion_transformer.tft_version_two import TFT +from pytorch_forecasting.models.temporal_fusion_transformer._tft_v2 import TFT BATCH_SIZE_TEST = 2 MAX_ENCODER_LENGTH_TEST = 10 From 5cc3ff1dd8be8f4b325d383abea14c4c06ace280 Mon Sep 17 00:00:00 2001 From: Aryan Saini Date: Wed, 21 May 2025 01:05:44 +0530 Subject: [PATCH 062/139] initial commit --- .../_tft_v2_metadata.py | 0 .../tests/test_all_estimators_v2.py | 49 +++++++++++++++++++ 2 files changed, 49 insertions(+) create mode 100644 pytorch_forecasting/models/temporal_fusion_transformer/_tft_v2_metadata.py create mode 100644 pytorch_forecasting/tests/test_all_estimators_v2.py diff --git a/pytorch_forecasting/models/temporal_fusion_transformer/_tft_v2_metadata.py b/pytorch_forecasting/models/temporal_fusion_transformer/_tft_v2_metadata.py new file mode 100644 index 000000000..e69de29bb diff --git a/pytorch_forecasting/tests/test_all_estimators_v2.py b/pytorch_forecasting/tests/test_all_estimators_v2.py new file mode 100644 index 000000000..1dc7859ab --- /dev/null +++ b/pytorch_forecasting/tests/test_all_estimators_v2.py @@ -0,0 +1,49 @@ +"""Automated tests based on the skbase test suite template.""" + +from inspect import isclass +import shutil + +import lightning.pytorch as pl +from lightning.pytorch.callbacks import EarlyStopping +from lightning.pytorch.loggers import TensorBoardLogger + +from pytorch_forecasting.tests._conftest import make_dataloaders +from pytorch_forecasting.tests.test_all_estimators import ( + BaseFixtureGenerator, + PackageConfig, +) + +# whether to test only estimators from modules that are changed w.r.t. main +# default is False, can be set to True by pytest --only_changed_modules True flag +ONLY_CHANGED_MODULES = False + + +def _integration( + estimator_cls, + data_with_covariates, + tmp_path, + cell_type="LSTM", + data_loader_kwargs={}, + clip_target: bool = False, + trainer_kwargs=None, + **kwargs, +): + pass + + +class TestAllPtForecastersV2(PackageConfig, BaseFixtureGenerator): + """Generic tests for all objects in the mini package.""" + + def test_doctest_examples(self, object_class): + """Runs doctests for estimator class.""" + import doctest + + doctest.run_docstring_examples(object_class, globals()) + + def test_integration( + self, + object_metadata, + trainer_kwargs, + tmp_path, + ): + pass From f18e09d183f40608d9d92e5cb7ef50d02cbf4644 Mon Sep 17 00:00:00 2001 From: Aryan Saini Date: Wed, 21 May 2025 01:16:44 +0530 Subject: [PATCH 063/139] add TFTMetadata class --- .../_tft_v2_metadata.py | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/pytorch_forecasting/models/temporal_fusion_transformer/_tft_v2_metadata.py b/pytorch_forecasting/models/temporal_fusion_transformer/_tft_v2_metadata.py index e69de29bb..5ea87c24b 100644 --- a/pytorch_forecasting/models/temporal_fusion_transformer/_tft_v2_metadata.py +++ b/pytorch_forecasting/models/temporal_fusion_transformer/_tft_v2_metadata.py @@ -0,0 +1,24 @@ +"""TFT metadata container.""" + +from pytorch_forecasting.models.base._base_object import _BasePtForecaster + + +class TFTMetadata(_BasePtForecaster): + """TFT metadata container.""" + + _tags = { + "info:name": "TFT", + "info:compute": 3, + "authors": ["jdb78"], + "capability:exogenous": True, + "capability:multivariate": True, + "capability:pred_int": True, + "capability:flexible_history_length": False, + } + + @classmethod + def get_model_cls(cls): + """Get model class.""" + from pytorch_forecasting.models.temporal_fusion_transformer._tft_v2 import TFT + + return TFT From e1e360eb7e626ce590e8c8a1bf6a3467220cdbb2 Mon Sep 17 00:00:00 2001 From: Aryan Saini Date: Wed, 21 May 2025 01:16:58 +0530 Subject: [PATCH 064/139] add TFTMetadata class --- .../models/temporal_fusion_transformer/_tft_v2_metadata.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pytorch_forecasting/models/temporal_fusion_transformer/_tft_v2_metadata.py b/pytorch_forecasting/models/temporal_fusion_transformer/_tft_v2_metadata.py index 5ea87c24b..8f11d78f3 100644 --- a/pytorch_forecasting/models/temporal_fusion_transformer/_tft_v2_metadata.py +++ b/pytorch_forecasting/models/temporal_fusion_transformer/_tft_v2_metadata.py @@ -8,7 +8,6 @@ class TFTMetadata(_BasePtForecaster): _tags = { "info:name": "TFT", - "info:compute": 3, "authors": ["jdb78"], "capability:exogenous": True, "capability:multivariate": True, From b25ced9c50970ba119d42d4284fe9198026811c1 Mon Sep 17 00:00:00 2001 From: Pranav Bhat Date: Thu, 22 May 2025 22:33:19 +0530 Subject: [PATCH 065/139] implement base model for tslib this model inherits from @phoeenniixx's implementation of BaseModel --- .../models/base/_base_model_v2.py | 173 ++++++++++++++++++ .../__init__.py} | 0 .../models/timexer/_timexer.py | 0 3 files changed, 173 insertions(+) rename pytorch_forecasting/models/{base/_tslib_base_model.py => timexer/__init__.py} (100%) create mode 100644 pytorch_forecasting/models/timexer/_timexer.py diff --git a/pytorch_forecasting/models/base/_base_model_v2.py b/pytorch_forecasting/models/base/_base_model_v2.py index ddefc29fb..63a537c17 100644 --- a/pytorch_forecasting/models/base/_base_model_v2.py +++ b/pytorch_forecasting/models/base/_base_model_v2.py @@ -294,3 +294,176 @@ def log_metrics( prog_bar=True, logger=True, ) + + +""" +Experimental implementation of a base class for `tslib` models. +NOTE: This PR is stacked on the PR #1836(PranavBhatP) and PR #1812(phoeenniixx). +""" + + +class TslibBaseModel(BaseModel): + """ + Base class for `tslib` models. + + Parameters + ---------- + loss : nn.Module + Loss function to use for training. + logging_metrics : Optional[List[nn.Module]], optional + List of metrics to log during training, validation, and testing. + optimizer : Optional[Union[Optimizer, str]], optional + Optimizer to use for training. + optimizer_params : Optional[Dict], optional + Parameters for the optimizer. + lr_scheduler : Optional[str], optional + Learning rate scheduler to use. + lr_scheduler_params : Optional[Dict], optional + Parameters for the learning rate scheduler. + metadata : Optional[Dict], default=None + Metadata for the model from TslibDataModule. + """ + + def __init__( + self, + loss: nn.Module, + logging_metrics: Optional[List[nn.Module]] = None, + optimizer: Optional[Union[Optimizer, str]] = "adam", + optimizer_params: Optional[Dict] = None, + lr_scheduler: Optional[str] = None, + lr_scheduler_params: Optional[Dict] = None, + metadata: Optional[Dict] = None, + ): + super().__init__( + loss=loss, + logging_metrics=logging_metrics, + optimizer=optimizer, + optimizer_params=optimizer_params, + lr_scheduler=lr_scheduler, + lr_scheduler_params=lr_scheduler_params, + ) + self.save_hyperparameters(ignore=["loss", "logging_metrics", "metadata"]) + self.metadata = metadata or {} + self.model_name = self.__class__.__name__ + + self.context_length = self.metadata.get("context_length", 0) + self.prediction_length = self.metadata.get("prediction_length", 0) + + feature_indices = metadata.get("feature_indices", {}) + self.cont_indices = feature_indices.get("continuous", []) + self.cat_indices = feature_indices.get("categorical", []) + self.known_indices = feature_indices.get("known", []) + self.unknown_indices = feature_indices.get("unknown", []) + + feature_dims = metadata.get("n_features", {}) + self.cont_dim = feature_dims.get("continuous", 0) + self.cat_dim = feature_dims.get("categorical", 0) + self.static_cat_dim = feature_dims.get("static_categorical", 0) + self.static_cont_dim = feature_dims.get("static_continuous", 0) + self.target_dim = feature_dims.get("target", 1) + + self.feature_names = metadata.get("feature_names", {}) + + self._init_network() + + def _init_network(self): + """ + Initialize the network architecture. + This method should be implemented in subclasses to define the specific layers + and sub_modules of the model. + """ + raise NotImplementedError("Subclasses must implement _init_network method.") + + def forward(self, x: Dict[str, torch.Tensor]) -> Dict[str, torch.Tensor]: + """ + Forward pass of the model. + + Parameters + ---------- + x: Dict[str, torch.Tensor] + Dictionary containing input tensors. + + Returns + ------- + Dict[str, torch.Tensor] + Dictionary containing output tensors. These can include + - predictions: + Prediction_output of shape (batch_size, prediction_length, target_dim) + - attention_weights: Optionally, output attention weights + """ + + raise NotImplementedError("Subclasses must implement forward method.") + + def predict_step( + self, + batch: Tuple[Dict[str, torch.Tensor]], + batch_idx: int, + dataloader_idx: int = 0, + ) -> torch.Tensor: + """ + Prediction step for the model. + + Parameters + ---------- + batch : Tuple[Dict[str, torch.Tensor]] + Batch of data containing input tensors. + batch_idx : int + Index of the batch. + dataloader_idx : int + Index of the dataloader. + + Returns + ------- + torch.Tensor + Predicted output tensor. + """ + x, _ = batch + y_hat = self(x) + + if "target" in x: + y_hat["target"] = x["target"] + + return y_hat + + def transform_output( + self, + y_hat: Union[ + torch.Tensor, List[torch.Tensor] + ], # evidenced from TimeXer implementation - in PR #1797 # noqa: E501 + target_scale: Optional[Dict[str, torch.Tensor]], + ) -> Union[torch.Tensor, List[torch.Tensor]]: + """ + Transform the output of the model to the original scale. + + Parameters + ---------- + y_hat : Union[torch.Tensor, List[torch.Tensor]] + Dictionary containing the model output. + target_scale : Optional[Dict[str, torch.Tensor]] + Dictionary containing the target scale for inverse transformation. + + Returns + ------- + Union[torch.Tensor, List[torch.Tensor]] + Dictionary containing the transformed output. + + Notes + ----- + WARNING! : This is a temporary implementation and is meant to be replaced with + a more robust scaling and normalization module for v2 of PTF. + """ + + scale = None + center = None + + if "scale" in target_scale and "center" in target_scale: + scale = target_scale["scale"] + center = target_scale["center"] + else: + raise ValueError("Cannot transform output without scale and center.") + + while scale.dim() < y_hat.dim(): + scale = scale.unsqueeze(0) + center = center.unsqueeze(0) + + return y_hat * scale + center diff --git a/pytorch_forecasting/models/base/_tslib_base_model.py b/pytorch_forecasting/models/timexer/__init__.py similarity index 100% rename from pytorch_forecasting/models/base/_tslib_base_model.py rename to pytorch_forecasting/models/timexer/__init__.py diff --git a/pytorch_forecasting/models/timexer/_timexer.py b/pytorch_forecasting/models/timexer/_timexer.py new file mode 100644 index 000000000..e69de29bb From f4afe90cd70946ea80214186461d18de1ffcb737 Mon Sep 17 00:00:00 2001 From: Pranav Bhat Date: Thu, 22 May 2025 22:43:27 +0530 Subject: [PATCH 066/139] add layer directory to store different architecture layer implementations --- pytorch_forecasting/layers/__init__.py | 0 pytorch_forecasting/layers/attention.py | 0 pytorch_forecasting/layers/embeddings.py | 0 pytorch_forecasting/layers/encoders.py | 0 pytorch_forecasting/layers/heads.py | 0 5 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 pytorch_forecasting/layers/__init__.py create mode 100644 pytorch_forecasting/layers/attention.py create mode 100644 pytorch_forecasting/layers/embeddings.py create mode 100644 pytorch_forecasting/layers/encoders.py create mode 100644 pytorch_forecasting/layers/heads.py diff --git a/pytorch_forecasting/layers/__init__.py b/pytorch_forecasting/layers/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/pytorch_forecasting/layers/attention.py b/pytorch_forecasting/layers/attention.py new file mode 100644 index 000000000..e69de29bb diff --git a/pytorch_forecasting/layers/embeddings.py b/pytorch_forecasting/layers/embeddings.py new file mode 100644 index 000000000..e69de29bb diff --git a/pytorch_forecasting/layers/encoders.py b/pytorch_forecasting/layers/encoders.py new file mode 100644 index 000000000..e69de29bb diff --git a/pytorch_forecasting/layers/heads.py b/pytorch_forecasting/layers/heads.py new file mode 100644 index 000000000..e69de29bb From 04e1a45961b04c57a92c7dc3dd12ff40282ed7eb Mon Sep 17 00:00:00 2001 From: Pranav Bhat Date: Fri, 23 May 2025 01:32:55 +0530 Subject: [PATCH 067/139] add model layers and timexer sample model code this code has not yet been tested and verified for functionality --- pytorch_forecasting/data/tslib_data_module.py | 10 +- pytorch_forecasting/layers/__init__.py | 33 +++ pytorch_forecasting/layers/attention.py | 119 ++++++++++ pytorch_forecasting/layers/embeddings.py | 102 +++++++++ pytorch_forecasting/layers/encoders.py | 102 +++++++++ pytorch_forecasting/layers/heads.py | 0 pytorch_forecasting/layers/output_layers.py | 45 ++++ .../models/base/_base_model_v2.py | 1 + .../models/timexer/_timexer.py | 205 ++++++++++++++++++ 9 files changed, 614 insertions(+), 3 deletions(-) delete mode 100644 pytorch_forecasting/layers/heads.py create mode 100644 pytorch_forecasting/layers/output_layers.py diff --git a/pytorch_forecasting/data/tslib_data_module.py b/pytorch_forecasting/data/tslib_data_module.py index 2a345dff2..0764bac05 100644 --- a/pytorch_forecasting/data/tslib_data_module.py +++ b/pytorch_forecasting/data/tslib_data_module.py @@ -375,8 +375,12 @@ def _prepare_metadata(self) -> None: feature_names["static_categorical"] = static_cat_names feature_names["static_continuous"] = static_cont_names - for idx, col in enumerate(target_features): - feature_indices["target"].append(idx) + for col in target_features: + if col in all_features: + actual_idx = all_features.index(col) + feature_indices["target"].append(actual_idx) + else: + warnings.warn(f"Target feature {col} not found!") n_features = {k: len(v) for k, v in feature_names.items()} @@ -394,7 +398,7 @@ def _prepare_metadata(self) -> None: @property def metadata(self) -> Dict[str, Any]: - """ + """ " Compute the metadata via the `_prepare_metadata` method. This method is called when the `metadata` property is accessed for the first. Returns diff --git a/pytorch_forecasting/layers/__init__.py b/pytorch_forecasting/layers/__init__.py index e69de29bb..f9839595d 100644 --- a/pytorch_forecasting/layers/__init__.py +++ b/pytorch_forecasting/layers/__init__.py @@ -0,0 +1,33 @@ +""" +Architectural deep learning layers from `nn.Module`. +""" + +from pytorch_forecasting.layers.attention import ( + AttentionLayer, + FullAttention, + TriangularCausalMask, +) +from pytorch_forecasting.layers.embeddings import ( + DataEmbedding_inverted, + EnEmbedding, + PositionalEmbedding, +) +from pytorch_forecasting.layers.encoders import ( + Encoder, + EncoderLayer, +) +from pytorch_forecasting.layers.output_layers import ( + FlattenHead, +) + +__all__ = [ + "FullAttention", + "TriangularCausalMask", + "AttentionLayer", + "DataEmbedding_inverted", + "EnEmbedding", + "PositionalEmbedding", + "Encoder", + "EncoderLayer", + "FlattenHead", +] diff --git a/pytorch_forecasting/layers/attention.py b/pytorch_forecasting/layers/attention.py index e69de29bb..97d4d13e9 100644 --- a/pytorch_forecasting/layers/attention.py +++ b/pytorch_forecasting/layers/attention.py @@ -0,0 +1,119 @@ +""" +Implementation of attention layers from `nn.Module`. +""" + +from math import sqrt + +import numpy as np +import torch +import torch.nn as nn +import torch.nn.functional as F + + +class TriangularCausalMask: + """ + Triangular causal mask for attention mechanism. + """ + + def __init__(self, B, L, device="cpu"): + mask_shape = [B, 1, L, L] + with torch.no_grad(): + self._mask = torch.triu( + torch.ones(mask_shape, dtype=torch.bool), diagonal=1 + ).to(device) + + @property + def mask(self): + return self._mask + + +class FullAttention(nn.Module): + """ + Full attention mechanism with optional masking and dropout. + Args: + mask_flag (bool): Whether to apply masking. + factor (int): Factor for scaling the attention scores. + scale (float): Scaling factor for attention scores. + attention_dropout (float): Dropout rate for attention scores. + output_attention (bool): Whether to output attention weights.""" + + def __init__( + self, + mask_flag=True, + factor=5, + scale=None, + attention_dropout=0.1, + output_attention=False, + ): + super(FullAttention, self).__init__() + self.scale = scale + self.mask_flag = mask_flag + self.output_attention = output_attention + self.dropout = nn.Dropout(attention_dropout) + + def forward(self, queries, keys, values, attn_mask, tau=None, delta=None): + B, L, H, E = queries.shape + _, S, _, D = values.shape + scale = self.scale or 1.0 / sqrt(E) + + scores = torch.einsum("blhe,bshe->bhls", queries, keys) + + if self.mask_flag: + if attn_mask is None: + attn_mask = TriangularCausalMask(B, L, device=queries.device) + scores.masked_fill_(attn_mask.mask, -np.abs) + A = self.dropout(torch.softmax(scale * scores, dim=-1)) + V = torch.einsum("bhls,bshd->blhd", A, values) + + if self.output_attention: + return V.contiguous(), A + else: + return V.contiguous(), None + + +class AttentionLayer(nn.Module): + """ + Attention layer that combines query, key, and value projections with an attention + mechanism. + Args: + attention (nn.Module): Attention mechanism to use. + d_model (int): Dimension of the model. + n_heads (int): Number of attention heads. + d_keys (int, optional): Dimension of the keys. Defaults to d_model // n_heads. + d_values (int, optional): + Dimension of the values. Defaults to d_model // n_heads. + """ + + def __init__(self, attention, d_model, n_heads, d_keys=None, d_values=None): + super(AttentionLayer, self).__init__() + + d_keys = d_keys or (d_model // n_heads) + d_values = d_values or (d_model // n_heads) + + self.inner_attention = attention + self.query_projection = nn.Linear(d_model, d_keys * n_heads) + self.key_projection = nn.Linear(d_model, d_keys * n_heads) + self.value_projection = nn.Linear(d_model, d_values * n_heads) + self.out_projection = nn.Linear(d_values * n_heads, d_model) + self.n_heads = n_heads + + def forward(self, queries, keys, values, attn_mask, tau=None, delta=None): + B, L, _ = queries.shape + _, S, _ = keys.shape + H = self.n_heads + + if S == 0: + # skip the cross attention process since there is no exogenous variables + queries = self.query_projection(queries) + return self.out_projection(queries), None + + queries = self.query_projection(queries).view(B, L, H, -1) + keys = self.key_projection(keys).view(B, S, H, -1) + values = self.value_projection(values).view(B, S, H, -1) + + out, attn = self.inner_attention( + queries, keys, values, attn_mask, tau=tau, delta=delta + ) + out = out.view(B, L, -1) + + return self.out_projection(out), attn diff --git a/pytorch_forecasting/layers/embeddings.py b/pytorch_forecasting/layers/embeddings.py index e69de29bb..8805b8817 100644 --- a/pytorch_forecasting/layers/embeddings.py +++ b/pytorch_forecasting/layers/embeddings.py @@ -0,0 +1,102 @@ +""" +Implementation of embedding layers from `nn.Module`. +""" + +import math +from math import sqrt + +import numpy as np +import torch +import torch.nn as nn +import torch.nn.functional as F + + +class DataEmbedding_inverted(nn.Module): + """ + Data embedding module for time series data. + Args: + c_in (int): Number of input features. + d_model (int): Dimension of the model. + embed_type (str): Type of embedding to use. Defaults to "fixed". + freq (str): Frequency of the time series data. Defaults to "h". + dropout (float): Dropout rate. Defaults to 0.1. + """ + + def __init__(self, c_in, d_model, dropout=0.1): + super(DataEmbedding_inverted, self).__init__() + self.value_embedding = nn.Linear(c_in, d_model) + self.dropout = nn.Dropout(p=dropout) + + def forward(self, x, x_mark): + x = x.permute(0, 2, 1) + # x: [Batch Variate Time] + if x_mark is None: + x = self.value_embedding(x) + else: + x = self.value_embedding(torch.cat([x, x_mark.permute(0, 2, 1)], 1)) + # x: [Batch Variate d_model] + return self.dropout(x) + + +class PositionalEmbedding(nn.Module): + """ + Positional embedding module for time series data. + Args: + d_model (int): Dimension of the model. + max_len (int): Maximum length of the input sequence. Defaults to 5000.""" + + def __init__(self, d_model, max_len=5000): + super(PositionalEmbedding, self).__init__() + # Compute the positional encodings once in log space. + pe = torch.zeros(max_len, d_model).float() + pe.require_grad = False + + position = torch.arange(0, max_len).float().unsqueeze(1) + div_term = ( + torch.arange(0, d_model, 2).float() * -(math.log(10000.0) / d_model) + ).exp() + + pe[:, 0::2] = torch.sin(position * div_term) + pe[:, 1::2] = torch.cos(position * div_term) + + pe = pe.unsqueeze(0) + self.register_buffer("pe", pe) + + def forward(self, x): + return self.pe[:, : x.size(1)] + + +class EnEmbedding(nn.Module): + """ + Encoder embedding module for time series data. Handles endogenous feature + embeddings in this case. + Args: + n_vars (int): Number of input features. + d_model (int): Dimension of the model. + patch_len (int): Length of the patches. + dropout (float): Dropout rate. Defaults to 0.1. + """ + + def __init__(self, n_vars, d_model, patch_len, dropout): + super(EnEmbedding, self).__init__() + + self.patch_len = patch_len + + self.value_embedding = nn.Linear(patch_len, d_model, bias=False) + self.glb_token = nn.Parameter(torch.randn(1, n_vars, 1, d_model)) + self.position_embedding = PositionalEmbedding(d_model) + + self.dropout = nn.Dropout(dropout) + + def forward(self, x): + n_vars = x.shape[1] + glb = self.glb_token.repeat((x.shape[0], 1, 1, 1)) + + x = x.unfold(dimension=-1, size=self.patch_len, step=self.patch_len) + x = torch.reshape(x, (x.shape[0] * x.shape[1], x.shape[2], x.shape[3])) + # Input encoding + x = self.value_embedding(x) + self.position_embedding(x) + x = torch.reshape(x, (-1, n_vars, x.shape[-2], x.shape[-1])) + x = torch.cat([x, glb], dim=2) + x = torch.reshape(x, (x.shape[0] * x.shape[1], x.shape[2], x.shape[3])) + return self.dropout(x), n_vars diff --git a/pytorch_forecasting/layers/encoders.py b/pytorch_forecasting/layers/encoders.py index e69de29bb..26657ebf7 100644 --- a/pytorch_forecasting/layers/encoders.py +++ b/pytorch_forecasting/layers/encoders.py @@ -0,0 +1,102 @@ +""" +Implementation of encoder layers from `nn.Module`. +""" + +import math +from math import sqrt + +import numpy as np +import torch +import torch.nn as nn +import torch.nn.functional as F + + +class Encoder(nn.Module): + """ + Encoder module for the TimeXer model. + Args: + layers (list): List of encoder layers. + norm_layer (nn.Module, optional): Normalization layer. Defaults to None. + projection (nn.Module, optional): Projection layer. Defaults to None. + """ + + def __init__(self, layers, norm_layer=None, projection=None): + super(Encoder, self).__init__() + self.layers = nn.ModuleList(layers) + self.norm = norm_layer + self.projection = projection + + def forward(self, x, cross, x_mask=None, cross_mask=None, tau=None, delta=None): + for layer in self.layers: + x = layer( + x, cross, x_mask=x_mask, cross_mask=cross_mask, tau=tau, delta=delta + ) + + if self.norm is not None: + x = self.norm(x) + + if self.projection is not None: + x = self.projection(x) + return x + + +class EncoderLayer(nn.Module): + """ + Encoder layer for the TimeXer model. + Args: + self_attention (nn.Module): Self-attention mechanism. + cross_attention (nn.Module): Cross-attention mechanism. + d_model (int): Dimension of the model. + d_ff (int, optional): + Dimension of the feedforward layer. Defaults to 4 * d_model. + dropout (float): Dropout rate. Defaults to 0.1. + activation (str): Activation function. Defaults to "relu". + """ + + def __init__( + self, + self_attention, + cross_attention, + d_model, + d_ff=None, + dropout=0.1, + activation="relu", + ): + super(EncoderLayer, self).__init__() + d_ff = d_ff or 4 * d_model + self.self_attention = self_attention + self.cross_attention = cross_attention + self.conv1 = nn.Conv1d(in_channels=d_model, out_channels=d_ff, kernel_size=1) + self.conv2 = nn.Conv1d(in_channels=d_ff, out_channels=d_model, kernel_size=1) + self.norm1 = nn.LayerNorm(d_model) + self.norm2 = nn.LayerNorm(d_model) + self.norm3 = nn.LayerNorm(d_model) + self.dropout = nn.Dropout(dropout) + self.activation = F.relu if activation == "relu" else F.gelu + + def forward(self, x, cross, x_mask=None, cross_mask=None, tau=None, delta=None): + B, L, D = cross.shape + x = x + self.dropout( + self.self_attention(x, x, x, attn_mask=x_mask, tau=tau, delta=None)[0] + ) + x = self.norm1(x) + + x_glb_ori = x[:, -1, :].unsqueeze(1) + x_glb = torch.reshape(x_glb_ori, (B, -1, D)) + x_glb_attn = self.dropout( + self.cross_attention( + x_glb, cross, cross, attn_mask=cross_mask, tau=tau, delta=delta + )[0] + ) + x_glb_attn = torch.reshape( + x_glb_attn, (x_glb_attn.shape[0] * x_glb_attn.shape[1], x_glb_attn.shape[2]) + ).unsqueeze(1) + x_glb = x_glb_ori + x_glb_attn + x_glb = self.norm2(x_glb) + + y = x = torch.cat([x[:, :-1, :], x_glb], dim=1) + + y = self.dropout(self.activation(self.conv1(y.transpose(-1, 1)))) + y = self.dropout(self.conv2(y).transpose(-1, 1)) + + return self.norm3(x + y) diff --git a/pytorch_forecasting/layers/heads.py b/pytorch_forecasting/layers/heads.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/pytorch_forecasting/layers/output_layers.py b/pytorch_forecasting/layers/output_layers.py new file mode 100644 index 000000000..786d3d152 --- /dev/null +++ b/pytorch_forecasting/layers/output_layers.py @@ -0,0 +1,45 @@ +""" +Implementation of output layers from `nn.Module`. +""" + +import math +from math import sqrt + +import numpy as np +import torch +import torch.nn as nn +import torch.nn.functional as F + + +class FlattenHead(nn.Module): + """ + Flatten head for the output of the model. + Args: + n_vars (int): Number of input features. + nf (int): Number of features in the last layer. + target_window (int): Target window size. + head_dropout (float): Dropout rate for the head. Defaults to 0. + n_quantiles (int, optional): Number of quantiles. Defaults to None.""" + + def __init__(self, n_vars, nf, target_window, head_dropout=0, n_quantiles=None): + super().__init__() + self.n_vars = n_vars + self.flatten = nn.Flatten(start_dim=-2) + self.linear = nn.Linear(nf, target_window) + self.n_quantiles = n_quantiles + + if self.n_quantiles is not None: + self.linear = nn.Linear(nf, target_window * n_quantiles) + else: + self.linear = nn.Linear(nf, target_window) + self.dropout = nn.Dropout(head_dropout) + + def forward(self, x): + x = self.flatten(x) + x = self.linear(x) + x = self.dropout(x) + + if self.n_quantiles is not None: + batch_size, n_vars = x.shape[0], x.shape[1] + x = x.reshape(batch_size, n_vars, -1, self.n_quantiles) + return x diff --git a/pytorch_forecasting/models/base/_base_model_v2.py b/pytorch_forecasting/models/base/_base_model_v2.py index 63a537c17..b3eb34fe4 100644 --- a/pytorch_forecasting/models/base/_base_model_v2.py +++ b/pytorch_forecasting/models/base/_base_model_v2.py @@ -354,6 +354,7 @@ def __init__( self.cat_indices = feature_indices.get("categorical", []) self.known_indices = feature_indices.get("known", []) self.unknown_indices = feature_indices.get("unknown", []) + self.target_indices = feature_indices.get("target", []) feature_dims = metadata.get("n_features", {}) self.cont_dim = feature_dims.get("continuous", 0) diff --git a/pytorch_forecasting/models/timexer/_timexer.py b/pytorch_forecasting/models/timexer/_timexer.py index e69de29bb..190dda919 100644 --- a/pytorch_forecasting/models/timexer/_timexer.py +++ b/pytorch_forecasting/models/timexer/_timexer.py @@ -0,0 +1,205 @@ +""" +Time Series Transformer with eXogenous variables (TimeXer) +---------------------------------------------------------- +""" + +################################################################ +# NOTE: This implementation of TimeXer derives from PR #1797. # +# It is experimental and seeks to clarify design decisions. # +################################################################ + + +from typing import Any, Dict, List, Optional, Tuple, Union + +import lightning.pytorch as pl +from lightning.pytorch import LightningModule, Trainer +import numpy as np +import pandas as pd +import torch +import torch.nn as nn +import torch.nn.functional as F +from torch.optim import Optimizer + +from pytorch_forecasting.data import TimeSeries +from pytorch_forecasting.metrics import MAE, MAPE, MultiHorizonMetric, QuantileLoss +from pytorch_forecasting.metrics.base_metrics import MultiLoss +from pytorch_forecasting.models.base._base_model_v2 import TslibBaseModel + + +class TimeXer(TslibBaseModel): + def __init__( + self, + loss: nn.Module, + context_length: int, + prediction_length: int, + features: str = "MS", + enc_in: int = None, + d_model: int = 512, + n_heads: int = 8, + e_layers: int = 2, + d_ff: int = 2048, + dropout: float = 0.1, + patch_length: int = 24, + factor: int = 5, + logging_metrics: Optional[List[nn.Module]] = None, + optimizer: Optional[Union[Optimizer, str]] = "adam", + optimizer_params: Optional[Dict] = None, + lr_scheduler: Optional[str] = None, + lr_scheduler_params: Optional[Dict] = None, + metadata: Optional[Dict] = None, + **kwargs: Any, + ): + super().__init__( + loss=loss, + logging_metrics=logging_metrics, + optimizer=optimizer, + optimizer_params=optimizer_params, + lr_scheduler=lr_scheduler, + lr_scheduler_params=lr_scheduler_params, + metadata=metadata, + ) + + self.context_length = context_length + self.prediction_length = prediction_length + self.save_hyperparameters(ignore=["loss", "logging_metrics", "metadata"]) + + def _init_network(self): + """ + Initialize the network for TimeXer's architecture. + """ + + from pytorch_forecasting.layers.attention import ( + AttentionLayer, + FullAttention, + ) + from pytorch_forecasting.layers.embeddings import ( + DataEmbedding_inverted, + EnEmbedding, + PositionalEmbedding, + ) + from pytorch_forecasting.layers.encoders import Encoder, EncoderLayer + from pytorch_forecasting.layers.output_layers import FlattenHead + + self.patch_num = max(1, int(self.context_length // self.patch_length)) + + if self.target_dim > 1 and self.features == "M": + self.n_target_vars = self.target_dim + else: + self.n_target_vars = 1 + + self.enc_in = self.enc_in or self.cont_dim + + self.n_quantiles = None + + if hasattr(self, "quantiles"): + self.n_quantiles = len(self.loss.quantiles) + + self.en_embedding = EnEmbedding( + self.n_target_vars, self.d_model, self.patch_length, self.dropout + ) + + self.ex_embedding = DataEmbedding_inverted( + self.context_length, self.d_model, self.dropout + ) + + encoder_layers = [] + + encoder_layers = [] + for _ in range(self.e_layers): + encoder_layers.append( + EncoderLayer( + AttentionLayer( + FullAttention( + False, + self.factor, + attention_dropout=self.dropout, + output_attention=False, + ), + self.d_model, + self.n_heads, + ), + AttentionLayer( + FullAttention( + False, + self.factor, + attention_dropout=self.dropout, + output_attention=False, + ), + self.d_model, + self.n_heads, + ), + self.d_model, + self.d_ff, + dropout=self.dropout, + activation=self.activation, + ) + ) + + self.encoder = Encoder( + encoder_layers, norm_layer=torch.nn.LayerNorm(self.d_model) + ) + + # Initialize output head + self.head_nf = self.d_model * (self.patch_num + 1) + self.head = FlattenHead( + self.enc_in, + self.head_nf, + self.prediction_length, + head_dropout=self.dropout, + n_quantiles=self.n_quantiles, + ) + + # def _get_target_positions(self) -> torch.Tensor: + # """ + # Get the target positions from the dataset. + # Returns: + # torch.Tensor: Target positions. + # """ + # return torch.tensor(self.target_indices, device=self.device) + + def _forecast(self, x: Dict[str, torch.Tensor]) -> Dict[str, torch.Tensor]: + """ + Forward pass of the TimeXer model. + Args: + x (Dict[str, torch.Tensor]): Input data. + Returns: + Dict[str, torch.Tensor]: Model predictions. + """ + batch_size = x["history_cont"].shape[0] + history_cont = x["history_cont"] + history_time_idx = x.get("history_time_idx", None) + + history_target = x.get( + "history_target", + torch.zeros(batch_size, self.context_length, 0, device=self.device), + ) + + en_embed, n_vars = self.en_embedding(history_target.permute(0, 2, 1)) + ex_embed = self.ex_embedding(history_cont, history_time_idx) + + enc_out = self.encoder(en_embed, ex_embed) + + enc_out = torch.reshape( + enc_out, (-1, n_vars, enc_out.shape[-2], enc_out.shape[-1]) + ) + + enc_out = enc_out.permute(0, 1, 3, 2) + + dec_out = self.head(enc_out) + + if self.n_quantiles is not None: + dec_out = dec_out.permute(0, 2, 1, 3) + else: + dec_out = dec_out.permute(0, 2, 1) + + return dec_out + + def forward(self, x: Dict[str, torch.Tensor]) -> Dict[str, torch.Tensor]: + """ + Forward pass of the TimeXer model. + Args: + x (Dict[str, torch.Tensor]): Input data. + Returns: + Dict[str, torch.Tensor]: Model predictions. + """ + pass From a27905d10644b52261388f678578bfa75f40c5c6 Mon Sep 17 00:00:00 2001 From: Pranav Bhat Date: Fri, 23 May 2025 01:33:46 +0530 Subject: [PATCH 068/139] format timexer file with proper linting --- pytorch_forecasting/models/timexer/_timexer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pytorch_forecasting/models/timexer/_timexer.py b/pytorch_forecasting/models/timexer/_timexer.py index 190dda919..062655471 100644 --- a/pytorch_forecasting/models/timexer/_timexer.py +++ b/pytorch_forecasting/models/timexer/_timexer.py @@ -172,7 +172,7 @@ def _forecast(self, x: Dict[str, torch.Tensor]) -> Dict[str, torch.Tensor]: history_target = x.get( "history_target", torch.zeros(batch_size, self.context_length, 0, device=self.device), - ) + ) # noqa: E501 en_embed, n_vars = self.en_embedding(history_target.permute(0, 2, 1)) ex_embed = self.ex_embedding(history_cont, history_time_idx) From e87c25b544b756702ebcac719503fcac45a7ba1e Mon Sep 17 00:00:00 2001 From: Pranav Bhat Date: Fri, 23 May 2025 10:43:05 +0530 Subject: [PATCH 069/139] complete the pipeline for tslib data module --- pytorch_forecasting/layers/embeddings.py | 1 + .../models/base/_base_model_v2.py | 8 +- .../models/timexer/_timexer.py | 106 +++++++++++++++++- 3 files changed, 107 insertions(+), 8 deletions(-) diff --git a/pytorch_forecasting/layers/embeddings.py b/pytorch_forecasting/layers/embeddings.py index 8805b8817..e5b98c45d 100644 --- a/pytorch_forecasting/layers/embeddings.py +++ b/pytorch_forecasting/layers/embeddings.py @@ -89,6 +89,7 @@ def __init__(self, n_vars, d_model, patch_len, dropout): self.dropout = nn.Dropout(dropout) def forward(self, x): + x = x.permute(0, 2, 1) n_vars = x.shape[1] glb = self.glb_token.repeat((x.shape[0], 1, 1, 1)) diff --git a/pytorch_forecasting/models/base/_base_model_v2.py b/pytorch_forecasting/models/base/_base_model_v2.py index b3eb34fe4..c89f7bb10 100644 --- a/pytorch_forecasting/models/base/_base_model_v2.py +++ b/pytorch_forecasting/models/base/_base_model_v2.py @@ -14,11 +14,13 @@ import torch.nn as nn from torch.optim import Optimizer +from pytorch_forecasting.metrics import Metric + class BaseModel(LightningModule): def __init__( self, - loss: nn.Module, + loss: Metric, logging_metrics: Optional[List[nn.Module]] = None, optimizer: Optional[Union[Optimizer, str]] = "adam", optimizer_params: Optional[Dict] = None, @@ -308,7 +310,7 @@ class TslibBaseModel(BaseModel): Parameters ---------- - loss : nn.Module + loss : Metric Loss function to use for training. logging_metrics : Optional[List[nn.Module]], optional List of metrics to log during training, validation, and testing. @@ -326,7 +328,7 @@ class TslibBaseModel(BaseModel): def __init__( self, - loss: nn.Module, + loss: Metric, logging_metrics: Optional[List[nn.Module]] = None, optimizer: Optional[Union[Optimizer, str]] = "adam", optimizer_params: Optional[Dict] = None, diff --git a/pytorch_forecasting/models/timexer/_timexer.py b/pytorch_forecasting/models/timexer/_timexer.py index 062655471..e570608fa 100644 --- a/pytorch_forecasting/models/timexer/_timexer.py +++ b/pytorch_forecasting/models/timexer/_timexer.py @@ -21,7 +21,7 @@ from torch.optim import Optimizer from pytorch_forecasting.data import TimeSeries -from pytorch_forecasting.metrics import MAE, MAPE, MultiHorizonMetric, QuantileLoss +from pytorch_forecasting.metrics import MAE, MAPE, Metric, QuantileLoss from pytorch_forecasting.metrics.base_metrics import MultiLoss from pytorch_forecasting.models.base._base_model_v2 import TslibBaseModel @@ -29,7 +29,7 @@ class TimeXer(TslibBaseModel): def __init__( self, - loss: nn.Module, + loss: Metric, context_length: int, prediction_length: int, features: str = "MS", @@ -41,6 +41,8 @@ def __init__( dropout: float = 0.1, patch_length: int = 24, factor: int = 5, + endogenous_vars: Optional[List[str]] = None, + exogenous_vars: Optional[List[str]] = None, logging_metrics: Optional[List[nn.Module]] = None, optimizer: Optional[Union[Optimizer, str]] = "adam", optimizer_params: Optional[Dict] = None, @@ -61,6 +63,8 @@ def __init__( self.context_length = context_length self.prediction_length = prediction_length + self.endogenous_vars = endogenous_vars + self.exogenous_vars = exogenous_vars self.save_hyperparameters(ignore=["loss", "logging_metrics", "metadata"]) def _init_network(self): @@ -173,9 +177,75 @@ def _forecast(self, x: Dict[str, torch.Tensor]) -> Dict[str, torch.Tensor]: "history_target", torch.zeros(batch_size, self.context_length, 0, device=self.device), ) # noqa: E501 + if self.endogenous_vars: + endogenous_indices = [ + self.cont_names.index(var) for var in self.endogenous_vars + ] + endogenous_cont = history_cont[..., endogenous_indices] + else: + endogenous_cont = history_target + + if self.exogenous_vars: + exogenous_indices = [ + self.cont_names.index(var) for var in self.exogenous_vars + ] + exogenous_cont = history_cont[..., exogenous_indices] + else: + exogenous_cont = history_cont - en_embed, n_vars = self.en_embedding(history_target.permute(0, 2, 1)) - ex_embed = self.ex_embedding(history_cont, history_time_idx) + en_embed, n_vars = self.en_embedding(endogenous_cont) + ex_embed = self.ex_embedding(exogenous_cont, history_time_idx) + + enc_out = self.encoder(en_embed, ex_embed) + + enc_out = torch.reshape( + enc_out, (-1, n_vars, enc_out.shape[-2], enc_out.shape[-1]) + ) + + enc_out = enc_out.permute(0, 1, 3, 2) + + dec_out = self.head(enc_out) + + if self.n_quantiles is not None: + dec_out = dec_out.permute(0, 2, 1, 3) + else: + dec_out = dec_out.permute(0, 2, 1) + + return dec_out + + def _forecast_multi(self, x: Dict[str, torch.Tensor]) -> Dict[str, torch.Tensor]: + """ + Forecast for multivariate with multiple time series. + + Args: + x (Dict[str, torch.Tensor]): Input data. + Returns: + Dict[str, torch.Tensor]: Model predictions. + """ + + history_cont = x["history_cont"] + history_time_idx = x.get("history_time_idx", None) + history_target = x["history_target"] + + if self.endogenous_vars: + endogenous_indices = [ + self.cont_names.index(var) for var in self.endogenous_vars + ] + endogenous_cont = history_cont[..., endogenous_indices] + else: + endogenous_cont = history_target + + if self.exogenous_vars: + exogenous_indices = [ + self.cont_names.index(var) for var in self.exogenous_vars + ] + exogenous_cont = history_cont[..., exogenous_indices] + else: + exogenous_cont = history_cont + + en_embed, n_vars = self.en_embedding(endogenous_cont) + + ex_embed = self.ex_embedding(exogenous_cont, history_time_idx) enc_out = self.encoder(en_embed, ex_embed) @@ -202,4 +272,30 @@ def forward(self, x: Dict[str, torch.Tensor]) -> Dict[str, torch.Tensor]: Returns: Dict[str, torch.Tensor]: Model predictions. """ - pass + if self.features == "MS": + out = self._forecast(x) + else: + out = self._forecast_multi(x) + + prediction = out[:, : self.prediction_length, :] + + # check to see if the output shape is equal to number of targets + if prediction.size(2) != self.target_dim: + prediction = prediction[:, :, : self.target_dim] + + target_indices = range(len(self.target_dim)) + if self.n_quantiles is not None: + if self.target_dim > 1: + prediction = [prediction[..., i, :] for i in target_indices] + else: + prediction = prediction[..., 0] + else: + if len(target_indices) == 1: + prediction = prediction[..., 0] + else: + prediction = [prediction[..., i] for i in target_indices] + + if "target_scale" in x: + prediction = self.transform_output(prediction, x["target_scale"]) + + return {"prediction": prediction} From 92c12bf1da172f9bf86b9e40667d6cd120fa3958 Mon Sep 17 00:00:00 2001 From: Aryan Saini Date: Mon, 26 May 2025 01:32:35 +0530 Subject: [PATCH 070/139] add TFT tests --- .../models/base/_base_model_v2.py | 44 +- .../temporal_fusion_transformer/_tft_v2.py | 3 +- .../_tft_v2_metadata.py | 23 - .../temporal_fusion_transformer/_tft_ver2.py | 1130 +++++++++++++++++ .../tft_v2_metadata.py | 90 ++ pytorch_forecasting/tests/_conftest.py | 232 ++++ pytorch_forecasting/tests/_data_scenarios.py | 231 ++++ .../tests/test_all_estimators_v2.py | 81 +- 8 files changed, 1805 insertions(+), 29 deletions(-) delete mode 100644 pytorch_forecasting/models/temporal_fusion_transformer/_tft_v2_metadata.py create mode 100644 pytorch_forecasting/models/temporal_fusion_transformer/_tft_ver2.py create mode 100644 pytorch_forecasting/models/temporal_fusion_transformer/tft_v2_metadata.py diff --git a/pytorch_forecasting/models/base/_base_model_v2.py b/pytorch_forecasting/models/base/_base_model_v2.py index ddefc29fb..15e9cb9d8 100644 --- a/pytorch_forecasting/models/base/_base_model_v2.py +++ b/pytorch_forecasting/models/base/_base_model_v2.py @@ -14,11 +14,23 @@ import torch.nn as nn from torch.optim import Optimizer +from pytorch_forecasting.metrics import ( + MAE, + MASE, + SMAPE, + DistributionLoss, + Metric, + MultiHorizonMetric, + MultiLoss, + QuantileLoss, + convert_torchmetric_to_pytorch_forecasting_metric, +) + class BaseModel(LightningModule): def __init__( self, - loss: nn.Module, + loss: Metric = SMAPE(), logging_metrics: Optional[List[nn.Module]] = None, optimizer: Optional[Union[Optimizer, str]] = "adam", optimizer_params: Optional[Dict] = None, @@ -104,6 +116,7 @@ def training_step( x, y = batch y_hat_dict = self(x) y_hat = y_hat_dict["prediction"] + y_hat, y = self._align_prediction_target_shapes(y_hat, y) loss = self.loss(y_hat, y) self.log( "train_loss", loss, on_step=True, on_epoch=True, prog_bar=True, logger=True @@ -132,6 +145,7 @@ def validation_step( x, y = batch y_hat_dict = self(x) y_hat = y_hat_dict["prediction"] + y_hat, y = self._align_prediction_target_shapes(y_hat, y) loss = self.loss(y_hat, y) self.log( "val_loss", loss, on_step=False, on_epoch=True, prog_bar=True, logger=True @@ -160,6 +174,7 @@ def test_step( x, y = batch y_hat_dict = self(x) y_hat = y_hat_dict["prediction"] + y_hat, y = self._align_prediction_target_shapes(y_hat, y) loss = self.loss(y_hat, y) self.log( "test_loss", loss, on_step=False, on_epoch=True, prog_bar=True, logger=True @@ -294,3 +309,30 @@ def log_metrics( prog_bar=True, logger=True, ) + + def _align_prediction_target_shapes( + self, y_hat: torch.Tensor, y: torch.Tensor + ) -> Tuple[torch.Tensor, torch.Tensor]: + """ + Align prediction and target tensor shapes for loss/metric calculation. + + Returns + ------- + Tuple of aligned prediction and target tensors + """ + if y.dim() == 3 and y.shape[-1] == 1: + y = y.squeeze(-1) + if y_hat.dim() < y.dim(): + y_hat = y_hat.unsqueeze(-1) + elif y_hat.dim() > y.dim(): + if y_hat.shape[-1] == 1: + y_hat = y_hat.squeeze(-1) + if y_hat.shape != y.shape: + if y_hat.numel() == y.numel(): + y_hat = y_hat.view(y.shape) + else: + raise ValueError( + f"Cannot align shapes: y_hat {y_hat.shape} vs y {y.shape}" + ) + + return y_hat, y diff --git a/pytorch_forecasting/models/temporal_fusion_transformer/_tft_v2.py b/pytorch_forecasting/models/temporal_fusion_transformer/_tft_v2.py index a0cf7d39e..fd41fe2a1 100644 --- a/pytorch_forecasting/models/temporal_fusion_transformer/_tft_v2.py +++ b/pytorch_forecasting/models/temporal_fusion_transformer/_tft_v2.py @@ -9,13 +9,14 @@ import torch.nn as nn from torch.optim import Optimizer +from pytorch_forecasting.metrics import Metric from pytorch_forecasting.models.base._base_model_v2 import BaseModel class TFT(BaseModel): def __init__( self, - loss: nn.Module, + loss: Metric, logging_metrics: Optional[List[nn.Module]] = None, optimizer: Optional[Union[Optimizer, str]] = "adam", optimizer_params: Optional[Dict] = None, diff --git a/pytorch_forecasting/models/temporal_fusion_transformer/_tft_v2_metadata.py b/pytorch_forecasting/models/temporal_fusion_transformer/_tft_v2_metadata.py deleted file mode 100644 index 8f11d78f3..000000000 --- a/pytorch_forecasting/models/temporal_fusion_transformer/_tft_v2_metadata.py +++ /dev/null @@ -1,23 +0,0 @@ -"""TFT metadata container.""" - -from pytorch_forecasting.models.base._base_object import _BasePtForecaster - - -class TFTMetadata(_BasePtForecaster): - """TFT metadata container.""" - - _tags = { - "info:name": "TFT", - "authors": ["jdb78"], - "capability:exogenous": True, - "capability:multivariate": True, - "capability:pred_int": True, - "capability:flexible_history_length": False, - } - - @classmethod - def get_model_cls(cls): - """Get model class.""" - from pytorch_forecasting.models.temporal_fusion_transformer._tft_v2 import TFT - - return TFT diff --git a/pytorch_forecasting/models/temporal_fusion_transformer/_tft_ver2.py b/pytorch_forecasting/models/temporal_fusion_transformer/_tft_ver2.py new file mode 100644 index 000000000..cff63385c --- /dev/null +++ b/pytorch_forecasting/models/temporal_fusion_transformer/_tft_ver2.py @@ -0,0 +1,1130 @@ +""" +The temporal fusion transformer is a powerful predictive model for forecasting timeseries +""" # noqa: E501 + +from copy import copy +from typing import Any, Dict, List, Optional, Tuple, Union + +import numpy as np +import torch +from torch import nn +from torchmetrics import Metric as LightningMetric + +from pytorch_forecasting.data import TimeSeriesDataSet +from pytorch_forecasting.metrics import ( + MAE, + MAPE, + RMSE, + SMAPE, + MultiHorizonMetric, + QuantileLoss, +) +from pytorch_forecasting.models.base import BaseModelWithCovariates +from pytorch_forecasting.models.nn import LSTM, MultiEmbedding +from pytorch_forecasting.models.temporal_fusion_transformer.sub_modules import ( + AddNorm, + GateAddNorm, + GatedLinearUnit, + GatedResidualNetwork, + InterpretableMultiHeadAttention, + VariableSelectionNetwork, +) +from pytorch_forecasting.utils import ( + create_mask, + detach, + integer_histogram, + masked_op, + padded_stack, + to_list, +) +from pytorch_forecasting.utils._dependencies import _check_matplotlib + + +class TemporalFusionTransformer(BaseModelWithCovariates): + def __init__( + self, + metadata: Dict[str, Any], + hidden_size: int = 16, + lstm_layers: int = 1, + dropout: float = 0.1, + output_size: Union[int, List[int]] = None, + loss: MultiHorizonMetric = None, + attention_head_size: int = 4, + categorical_groups: Optional[Union[Dict, List[str]]] = None, + hidden_continuous_size: int = 8, + hidden_continuous_sizes: Optional[Dict[str, int]] = None, + embedding_sizes: Optional[Dict[str, Tuple[int, int]]] = None, + embedding_paddings: Optional[List[str]] = None, + embedding_labels: Optional[Dict[str, np.ndarray]] = None, + learning_rate: float = 1e-3, + log_interval: Union[int, float] = -1, + log_val_interval: Union[int, float] = None, + log_gradient_flow: bool = False, + reduce_on_plateau_patience: int = 1000, + monotone_constaints: Optional[Dict[str, int]] = None, + share_single_variable_networks: bool = False, + causal_attention: bool = True, + logging_metrics: nn.ModuleList = None, + **kwargs, + ): + """ + Temporal Fusion Transformer for forecasting timeseries - use its :py:meth:`~from_dataset` method if possible. + + Implementation of the article + `Temporal Fusion Transformers for Interpretable Multi-horizon Time Series + Forecasting `_. The network outperforms DeepAR by Amazon by 36-69% + in benchmarks. + + Enhancements compared to the original implementation (apart from capabilities added through base model + such as monotone constraints): + + * static variables can be continuous + * multiple categorical variables can be summarized with an EmbeddingBag + * variable encoder and decoder length by sample + * categorical embeddings are not transformed by variable selection network (because it is a redundant operation) + * variable dimension in variable selection network are scaled up via linear interpolation to reduce + number of parameters + * non-linear variable processing in variable selection network can be shared among decoder and encoder + (not shared by default) + + Tune its hyperparameters with + :py:func:`~pytorch_forecasting.models.temporal_fusion_transformer.tuning.optimize_hyperparameters`. + + Args: + + hidden_size: hidden size of network which is its main hyperparameter and can range from 8 to 512 + lstm_layers: number of LSTM layers (2 is mostly optimal) + dropout: dropout rate + output_size: number of outputs (e.g. number of quantiles for QuantileLoss and one target or list + of output sizes). + loss: loss function taking prediction and targets + attention_head_size: number of attention heads (4 is a good default) + max_encoder_length: length to encode (can be far longer than the decoder length but does not have to be) + static_categoricals: names of static categorical variables + static_reals: names of static continuous variables + time_varying_categoricals_encoder: names of categorical variables for encoder + time_varying_categoricals_decoder: names of categorical variables for decoder + time_varying_reals_encoder: names of continuous variables for encoder + time_varying_reals_decoder: names of continuous variables for decoder + categorical_groups: dictionary where values + are list of categorical variables that are forming together a new categorical + variable which is the key in the dictionary + x_reals: order of continuous variables in tensor passed to forward function + x_categoricals: order of categorical variables in tensor passed to forward function + hidden_continuous_size: default for hidden size for processing continous variables (similar to categorical + embedding size) + hidden_continuous_sizes: dictionary mapping continuous input indices to sizes for variable selection + (fallback to hidden_continuous_size if index is not in dictionary) + embedding_sizes: dictionary mapping (string) indices to tuple of number of categorical classes and + embedding size + embedding_paddings: list of indices for embeddings which transform the zero's embedding to a zero vector + embedding_labels: dictionary mapping (string) indices to list of categorical labels + learning_rate: learning rate + log_interval: log predictions every x batches, do not log if 0 or less, log interpretation if > 0. If < 1.0 + , will log multiple entries per batch. Defaults to -1. + log_val_interval: frequency with which to log validation set metrics, defaults to log_interval + log_gradient_flow: if to log gradient flow, this takes time and should be only done to diagnose training + failures + reduce_on_plateau_patience (int): patience after which learning rate is reduced by a factor of 10 + monotone_constaints (Dict[str, int]): dictionary of monotonicity constraints for continuous decoder + variables mapping + position (e.g. ``"0"`` for first position) to constraint (``-1`` for negative and ``+1`` for positive, + larger numbers add more weight to the constraint vs. the loss but are usually not necessary). + This constraint significantly slows down training. Defaults to {}. + share_single_variable_networks (bool): if to share the single variable networks between the encoder and + decoder. Defaults to False. + causal_attention (bool): If to attend only at previous timesteps in the decoder or also include future + predictions. Defaults to True. + logging_metrics (nn.ModuleList[LightningMetric]): list of metrics that are logged during training. + Defaults to nn.ModuleList([SMAPE(), MAE(), RMSE(), MAPE()]). + **kwargs: additional arguments to :py:class:`~BaseModel`. + """ # noqa: E501 + + max_encoder_length = metadata["max_encoder_length"] + if output_size is None: + output_size = metadata["target"] + static_categoricals = [ + f"static_cat_{i}" + for i in range(metadata.get("static_categorical_features", 0)) + ] + static_reals = [ + f"static_real_{i}" + for i in range(metadata.get("static_continuous_features", 0)) + ] + time_varying_categoricals_encoder = [ + f"encoder_cat_{i}" for i in range(metadata["encoder_cat"]) + ] + time_varying_reals_encoder = [ + f"encoder_real_{i}" for i in range(metadata["encoder_cont"]) + ] + time_varying_categoricals_decoder = [ + f"decoder_cat_{i}" for i in range(metadata["decoder_cat"]) + ] + time_varying_reals_decoder = [ + f"decoder_real_{i}" for i in range(metadata["decoder_cont"]) + ] + x_categoricals = ( + static_categoricals + + time_varying_categoricals_encoder + + time_varying_categoricals_decoder + ) + x_reals = static_reals + time_varying_reals_encoder + time_varying_reals_decoder + + if monotone_constaints is None: + monotone_constaints = {} + if embedding_labels is None: + embedding_labels = {} + if embedding_paddings is None: + embedding_paddings = [] + if embedding_sizes is None: + embedding_sizes = {} + if hidden_continuous_sizes is None: + hidden_continuous_sizes = {} + if categorical_groups is None: + categorical_groups = {} + if logging_metrics is None: + logging_metrics = nn.ModuleList([SMAPE(), MAE(), RMSE(), MAPE()]) + if loss is None: + loss = QuantileLoss() + self.save_hyperparameters(ignore=["metadata"]) + + self.hparams.max_encoder_length = max_encoder_length + self.hparams.output_size = output_size + self.hparams.static_categoricals = static_categoricals + self.hparams.static_reals = static_reals + self.hparams.time_varying_categoricals_encoder = ( + time_varying_categoricals_encoder + ) + self.hparams.time_varying_categoricals_decoder = ( + time_varying_categoricals_decoder + ) + self.hparams.time_varying_reals_encoder = time_varying_reals_encoder + self.hparams.time_varying_reals_decoder = time_varying_reals_decoder + self.hparams.x_categoricals = x_categoricals + self.hparams.x_reals = x_reals + # store loss function separately as it is a module + assert isinstance( + loss, LightningMetric + ), "Loss has to be a PyTorch Lightning `Metric`" + super().__init__(loss=loss, logging_metrics=logging_metrics, **kwargs) + + # processing inputs + # embeddings + self.input_embeddings = MultiEmbedding( + embedding_sizes=self.hparams.embedding_sizes, + categorical_groups=self.hparams.categorical_groups, + embedding_paddings=self.hparams.embedding_paddings, + x_categoricals=self.hparams.x_categoricals, + max_embedding_size=self.hparams.hidden_size, + ) + + # continuous variable processing + self.prescalers = nn.ModuleDict( + { + name: nn.Linear( + 1, + self.hparams.hidden_continuous_sizes.get( + name, self.hparams.hidden_continuous_size + ), + ) + for name in self.reals + } + ) + + # variable selection + # variable selection for static variables + static_input_sizes = { + name: self.input_embeddings.output_size[name] + for name in self.hparams.static_categoricals + } + static_input_sizes.update( + { + name: self.hparams.hidden_continuous_sizes.get( + name, self.hparams.hidden_continuous_size + ) + for name in self.hparams.static_reals + } + ) + self.static_variable_selection = VariableSelectionNetwork( + input_sizes=static_input_sizes, + hidden_size=self.hparams.hidden_size, + input_embedding_flags={ + name: True for name in self.hparams.static_categoricals + }, + dropout=self.hparams.dropout, + prescalers=self.prescalers, + ) + + # variable selection for encoder and decoder + encoder_input_sizes = { + name: self.input_embeddings.output_size[name] + for name in self.hparams.time_varying_categoricals_encoder + } + encoder_input_sizes.update( + { + name: self.hparams.hidden_continuous_sizes.get( + name, self.hparams.hidden_continuous_size + ) + for name in self.hparams.time_varying_reals_encoder + } + ) + + decoder_input_sizes = { + name: self.input_embeddings.output_size[name] + for name in self.hparams.time_varying_categoricals_decoder + } + decoder_input_sizes.update( + { + name: self.hparams.hidden_continuous_sizes.get( + name, self.hparams.hidden_continuous_size + ) + for name in self.hparams.time_varying_reals_decoder + } + ) + + # create single variable grns that are shared across decoder and encoder + if self.hparams.share_single_variable_networks: + self.shared_single_variable_grns = nn.ModuleDict() + for name, input_size in encoder_input_sizes.items(): + self.shared_single_variable_grns[name] = GatedResidualNetwork( + input_size, + min(input_size, self.hparams.hidden_size), + self.hparams.hidden_size, + self.hparams.dropout, + ) + for name, input_size in decoder_input_sizes.items(): + if name not in self.shared_single_variable_grns: + self.shared_single_variable_grns[name] = GatedResidualNetwork( + input_size, + min(input_size, self.hparams.hidden_size), + self.hparams.hidden_size, + self.hparams.dropout, + ) + + self.encoder_variable_selection = VariableSelectionNetwork( + input_sizes=encoder_input_sizes, + hidden_size=self.hparams.hidden_size, + input_embedding_flags={ + name: True for name in self.hparams.time_varying_categoricals_encoder + }, + dropout=self.hparams.dropout, + context_size=self.hparams.hidden_size, + prescalers=self.prescalers, + single_variable_grns=( + {} + if not self.hparams.share_single_variable_networks + else self.shared_single_variable_grns + ), + ) + + self.decoder_variable_selection = VariableSelectionNetwork( + input_sizes=decoder_input_sizes, + hidden_size=self.hparams.hidden_size, + input_embedding_flags={ + name: True for name in self.hparams.time_varying_categoricals_decoder + }, + dropout=self.hparams.dropout, + context_size=self.hparams.hidden_size, + prescalers=self.prescalers, + single_variable_grns=( + {} + if not self.hparams.share_single_variable_networks + else self.shared_single_variable_grns + ), + ) + + # static encoders + # for variable selection + self.static_context_variable_selection = GatedResidualNetwork( + input_size=self.hparams.hidden_size, + hidden_size=self.hparams.hidden_size, + output_size=self.hparams.hidden_size, + dropout=self.hparams.dropout, + ) + + # for hidden state of the lstm + self.static_context_initial_hidden_lstm = GatedResidualNetwork( + input_size=self.hparams.hidden_size, + hidden_size=self.hparams.hidden_size, + output_size=self.hparams.hidden_size, + dropout=self.hparams.dropout, + ) + + # for cell state of the lstm + self.static_context_initial_cell_lstm = GatedResidualNetwork( + input_size=self.hparams.hidden_size, + hidden_size=self.hparams.hidden_size, + output_size=self.hparams.hidden_size, + dropout=self.hparams.dropout, + ) + + # for post lstm static enrichment + self.static_context_enrichment = GatedResidualNetwork( + self.hparams.hidden_size, + self.hparams.hidden_size, + self.hparams.hidden_size, + self.hparams.dropout, + ) + + # lstm encoder (history) and decoder (future) for local processing + self.lstm_encoder = LSTM( + input_size=self.hparams.hidden_size, + hidden_size=self.hparams.hidden_size, + num_layers=self.hparams.lstm_layers, + dropout=self.hparams.dropout if self.hparams.lstm_layers > 1 else 0, + batch_first=True, + ) + + self.lstm_decoder = LSTM( + input_size=self.hparams.hidden_size, + hidden_size=self.hparams.hidden_size, + num_layers=self.hparams.lstm_layers, + dropout=self.hparams.dropout if self.hparams.lstm_layers > 1 else 0, + batch_first=True, + ) + + # skip connection for lstm + self.post_lstm_gate_encoder = GatedLinearUnit( + self.hparams.hidden_size, dropout=self.hparams.dropout + ) + self.post_lstm_gate_decoder = self.post_lstm_gate_encoder + # self.post_lstm_gate_decoder = GatedLinearUnit( + # self.hparams.hidden_size, dropout=self.hparams.dropout) + self.post_lstm_add_norm_encoder = AddNorm( + self.hparams.hidden_size, trainable_add=False + ) + # self.post_lstm_add_norm_decoder = AddNorm( + # self.hparams.hidden_size, trainable_add=True) + self.post_lstm_add_norm_decoder = self.post_lstm_add_norm_encoder + + # static enrichment and processing past LSTM + self.static_enrichment = GatedResidualNetwork( + input_size=self.hparams.hidden_size, + hidden_size=self.hparams.hidden_size, + output_size=self.hparams.hidden_size, + dropout=self.hparams.dropout, + context_size=self.hparams.hidden_size, + ) + + # attention for long-range processing + self.multihead_attn = InterpretableMultiHeadAttention( + d_model=self.hparams.hidden_size, + n_head=self.hparams.attention_head_size, + dropout=self.hparams.dropout, + ) + self.post_attn_gate_norm = GateAddNorm( + self.hparams.hidden_size, dropout=self.hparams.dropout, trainable_add=False + ) + self.pos_wise_ff = GatedResidualNetwork( + self.hparams.hidden_size, + self.hparams.hidden_size, + self.hparams.hidden_size, + dropout=self.hparams.dropout, + ) + + # output processing -> no dropout at this late stage + self.pre_output_gate_norm = GateAddNorm( + self.hparams.hidden_size, dropout=None, trainable_add=False + ) + + if self.n_targets > 1: # if to run with multiple targets + self.output_layer = nn.ModuleList( + [ + nn.Linear(self.hparams.hidden_size, output_size) + for output_size in self.hparams.output_size + ] + ) + else: + self.output_layer = nn.Linear( + self.hparams.hidden_size, self.hparams.output_size + ) + + @classmethod + def from_dataset( + cls, + dataset: TimeSeriesDataSet, + allowed_encoder_known_variable_names: List[str] = None, + **kwargs, + ): + """ + Create model from dataset. + + Args: + dataset: timeseries dataset + allowed_encoder_known_variable_names: List of known variables that are allowed in encoder, defaults to all + **kwargs: additional arguments such as hyperparameters for model (see ``__init__()``) + + Returns: + TemporalFusionTransformer + """ # noqa: E501 + # add maximum encoder length + # update defaults + new_kwargs = copy(kwargs) + new_kwargs["max_encoder_length"] = dataset.max_encoder_length + new_kwargs.update( + cls.deduce_default_output_parameters(dataset, kwargs, QuantileLoss()) + ) + + # create class and return + return super().from_dataset( + dataset, + allowed_encoder_known_variable_names=allowed_encoder_known_variable_names, + **new_kwargs, + ) + + def expand_static_context(self, context, timesteps): + """ + add time dimension to static context + """ + return context[:, None].expand(-1, timesteps, -1) + + def get_attention_mask( + self, encoder_lengths: torch.LongTensor, decoder_lengths: torch.LongTensor + ): + """ + Returns causal mask to apply for self-attention layer. + """ + decoder_length = decoder_lengths.max() + if self.hparams.causal_attention: + # indices to which is attended + attend_step = torch.arange(decoder_length, device=self.device) + # indices for which is predicted + predict_step = torch.arange(0, decoder_length, device=self.device)[:, None] + # do not attend to steps to self or after prediction + decoder_mask = ( + (attend_step >= predict_step) + .unsqueeze(0) + .expand(encoder_lengths.size(0), -1, -1) + ) + else: + # there is value in attending to future forecasts if + # they are made with knowledge currently available + # one possibility is here to use a second attention layer + # for future attention + # (assuming different effects matter in the future than the past) + # or alternatively using the same layer but + # allowing forward attention - i.e. only + # masking out non-available data and self + decoder_mask = ( + create_mask(decoder_length, decoder_lengths) + .unsqueeze(1) + .expand(-1, decoder_length, -1) + ) + # do not attend to steps where data is padded + encoder_mask = ( + create_mask(encoder_lengths.max(), encoder_lengths) + .unsqueeze(1) + .expand(-1, decoder_length, -1) + ) + # combine masks along attended time - first encoder and then decoder + mask = torch.cat( + ( + encoder_mask, + decoder_mask, + ), + dim=2, + ) + return mask + + def forward(self, x: Dict[str, torch.Tensor]) -> Dict[str, torch.Tensor]: + """ + input dimensions: n_samples x time x variables + """ + encoder_lengths = x["encoder_lengths"] + decoder_lengths = x["decoder_lengths"] + x_cat = torch.cat( + [x["encoder_cat"], x["decoder_cat"]], dim=1 + ) # concatenate in time dimension + + ############different from _tft########################### + encoder_cont = x["encoder_cont"] + decoder_cont = x["decoder_cont"] + if encoder_cont.shape[-1] != decoder_cont.shape[-1]: + max_features = max(encoder_cont.shape[-1], decoder_cont.shape[-1]) + + if encoder_cont.shape[-1] < max_features: + encoder_padding = torch.zeros( + encoder_cont.shape[0], + encoder_cont.shape[1], + max_features - encoder_cont.shape[-1], + ).to(encoder_cont.device) + encoder_cont = torch.cat([encoder_cont, encoder_padding], dim=-1) + + if decoder_cont.shape[-1] < max_features: + decoder_padding = torch.zeros( + decoder_cont.shape[0], + decoder_cont.shape[1], + max_features - decoder_cont.shape[-1], + ).to(decoder_cont.device) + decoder_cont = torch.cat([decoder_cont, decoder_padding], dim=-1) + x_cont = torch.cat( + [encoder_cont, decoder_cont], dim=1 + ) # concatenate in time dimension + ########## + + timesteps = x_cont.size(1) # encode + decode length + max_encoder_length = int(encoder_lengths.max()) + input_vectors = self.input_embeddings(x_cat) + + ############different from _tft########################### + available_reals = [name for name in self.hparams.x_reals if name in self.reals] + max_features = x_cont.shape[-1] + + input_vectors.update( + { + name: x_cont[..., idx].unsqueeze(-1) + for idx, name in enumerate(available_reals) + if idx < max_features + } + ) + + all_expected_vars = set(self.encoder_variables + self.decoder_variables) + real_vars_in_input = set(input_vectors.keys()) + missing_vars = all_expected_vars - real_vars_in_input + + for var_name in missing_vars: + if var_name.startswith(("encoder_real_", "decoder_real_")): + # Create zero tensor with same shape as other real variables + zero_tensor = torch.zeros_like(x_cont[..., 0].unsqueeze(-1)) + input_vectors[var_name] = zero_tensor + ############ + + # Embedding and variable selection + if len(self.static_variables) > 0: + # static embeddings will be constant over entire batch + static_embedding = { + name: input_vectors[name][:, 0] for name in self.static_variables + } + static_embedding, static_variable_selection = ( + self.static_variable_selection(static_embedding) + ) + else: + static_embedding = torch.zeros( + (x_cont.size(0), self.hparams.hidden_size), + dtype=self.dtype, + device=self.device, + ) + static_variable_selection = torch.zeros( + (x_cont.size(0), 0), dtype=self.dtype, device=self.device + ) + + static_context_variable_selection = self.expand_static_context( + self.static_context_variable_selection(static_embedding), timesteps + ) + + embeddings_varying_encoder = { + name: input_vectors[name][:, :max_encoder_length] + for name in self.encoder_variables + } + embeddings_varying_encoder, encoder_sparse_weights = ( + self.encoder_variable_selection( + embeddings_varying_encoder, + static_context_variable_selection[:, :max_encoder_length], + ) + ) + + embeddings_varying_decoder = { + name: input_vectors[name][:, max_encoder_length:] + for name in self.decoder_variables # select decoder + } + embeddings_varying_decoder, decoder_sparse_weights = ( + self.decoder_variable_selection( + embeddings_varying_decoder, + static_context_variable_selection[:, max_encoder_length:], + ) + ) + + # LSTM + # calculate initial state + input_hidden = self.static_context_initial_hidden_lstm(static_embedding).expand( + self.hparams.lstm_layers, -1, -1 + ) + input_cell = self.static_context_initial_cell_lstm(static_embedding).expand( + self.hparams.lstm_layers, -1, -1 + ) + + # run local encoder + encoder_output, (hidden, cell) = self.lstm_encoder( + embeddings_varying_encoder, + (input_hidden, input_cell), + lengths=encoder_lengths, + enforce_sorted=False, + ) + + # run local decoder + decoder_output, _ = self.lstm_decoder( + embeddings_varying_decoder, + (hidden, cell), + lengths=decoder_lengths, + enforce_sorted=False, + ) + + # skip connection over lstm + lstm_output_encoder = self.post_lstm_gate_encoder(encoder_output) + lstm_output_encoder = self.post_lstm_add_norm_encoder( + lstm_output_encoder, embeddings_varying_encoder + ) + + lstm_output_decoder = self.post_lstm_gate_decoder(decoder_output) + lstm_output_decoder = self.post_lstm_add_norm_decoder( + lstm_output_decoder, embeddings_varying_decoder + ) + + lstm_output = torch.cat([lstm_output_encoder, lstm_output_decoder], dim=1) + + # static enrichment + static_context_enrichment = self.static_context_enrichment(static_embedding) + attn_input = self.static_enrichment( + lstm_output, + self.expand_static_context(static_context_enrichment, timesteps), + ) + + # Attention + attn_output, attn_output_weights = self.multihead_attn( + q=attn_input[:, max_encoder_length:], # query only for predictions + k=attn_input, + v=attn_input, + mask=self.get_attention_mask( + encoder_lengths=encoder_lengths, decoder_lengths=decoder_lengths + ), + ) + + # skip connection over attention + attn_output = self.post_attn_gate_norm( + attn_output, attn_input[:, max_encoder_length:] + ) + + output = self.pos_wise_ff(attn_output) + + # skip connection over temporal fusion decoder (not LSTM decoder + # despite the LSTM output contains + # a skip from the variable selection network) + output = self.pre_output_gate_norm(output, lstm_output[:, max_encoder_length:]) + if self.n_targets > 1: # if to use multi-target architecture + output = [output_layer(output) for output_layer in self.output_layer] + else: + output = self.output_layer(output) + + return self.to_network_output( + prediction=self.transform_output(output, target_scale=x["target_scale"]), + encoder_attention=attn_output_weights[..., :max_encoder_length], + decoder_attention=attn_output_weights[..., max_encoder_length:], + static_variables=static_variable_selection, + encoder_variables=encoder_sparse_weights, + decoder_variables=decoder_sparse_weights, + decoder_lengths=decoder_lengths, + encoder_lengths=encoder_lengths, + ) + + def on_fit_end(self): + if self.log_interval > 0: + self.log_embeddings() + + def create_log(self, x, y, out, batch_idx, **kwargs): + log = super().create_log(x, y, out, batch_idx, **kwargs) + if self.log_interval > 0: + log["interpretation"] = self._log_interpretation(out) + return log + + def _log_interpretation(self, out): + # calculate interpretations etc for latter logging + interpretation = self.interpret_output( + detach(out), + reduction="sum", + attention_prediction_horizon=0, # attention only for first prediction horizon # noqa: E501 + ) + return interpretation + + def on_epoch_end(self, outputs): + """ + run at epoch end for training or validation + """ + if self.log_interval > 0 and not self.training: + self.log_interpretation(outputs) + + def interpret_output( + self, + out: Dict[str, torch.Tensor], + reduction: str = "none", + attention_prediction_horizon: int = 0, + ) -> Dict[str, torch.Tensor]: + """ + interpret output of model + + Args: + out: output as produced by ``forward()`` + reduction: "none" for no averaging over batches, "sum" for summing attentions, "mean" for + normalizing by encode lengths + attention_prediction_horizon: which prediction horizon to use for attention + + Returns: + interpretations that can be plotted with ``plot_interpretation()`` + """ # noqa: E501 + # take attention and concatenate if a list to proper attention object + batch_size = len(out["decoder_attention"]) + if isinstance(out["decoder_attention"], (list, tuple)): + # start with decoder attention + # assume issue is in last dimension, we need to find max + max_last_dimension = max(x.size(-1) for x in out["decoder_attention"]) + first_elm = out["decoder_attention"][0] + # create new attention tensor into which we will scatter + decoder_attention = torch.full( + (batch_size, *first_elm.shape[:-1], max_last_dimension), + float("nan"), + dtype=first_elm.dtype, + device=first_elm.device, + ) + # scatter into tensor + for idx, x in enumerate(out["decoder_attention"]): + decoder_length = out["decoder_lengths"][idx] + decoder_attention[idx, :, :, :decoder_length] = x[..., :decoder_length] + else: + decoder_attention = out["decoder_attention"].clone() + decoder_mask = create_mask( + out["decoder_attention"].size(1), out["decoder_lengths"] + ) + decoder_attention[ + decoder_mask[..., None, None].expand_as(decoder_attention) + ] = float("nan") + + if isinstance(out["encoder_attention"], (list, tuple)): + # same game for encoder attention + # create new attention tensor into which we will scatter + first_elm = out["encoder_attention"][0] + encoder_attention = torch.full( + (batch_size, *first_elm.shape[:-1], self.hparams.max_encoder_length), + float("nan"), + dtype=first_elm.dtype, + device=first_elm.device, + ) + # scatter into tensor + for idx, x in enumerate(out["encoder_attention"]): + encoder_length = out["encoder_lengths"][idx] + encoder_attention[ + idx, :, :, self.hparams.max_encoder_length - encoder_length : + ] = x[..., :encoder_length] + else: + # roll encoder attention (so start last encoder value is on the right) + encoder_attention = out["encoder_attention"].clone() + shifts = encoder_attention.size(3) - out["encoder_lengths"] + new_index = ( + torch.arange( + encoder_attention.size(3), device=encoder_attention.device + )[None, None, None].expand_as(encoder_attention) + - shifts[:, None, None, None] + ) % encoder_attention.size(3) + encoder_attention = torch.gather(encoder_attention, dim=3, index=new_index) + # expand encoder_attention to full size + if encoder_attention.size(-1) < self.hparams.max_encoder_length: + encoder_attention = torch.concat( + [ + torch.full( + ( + *encoder_attention.shape[:-1], + self.hparams.max_encoder_length + - out["encoder_lengths"].max(), + ), + float("nan"), + dtype=encoder_attention.dtype, + device=encoder_attention.device, + ), + encoder_attention, + ], + dim=-1, + ) + + # combine attention vector + attention = torch.concat([encoder_attention, decoder_attention], dim=-1) + attention[attention < 1e-5] = float("nan") + + # histogram of decode and encode lengths + encoder_length_histogram = integer_histogram( + out["encoder_lengths"], min=0, max=self.hparams.max_encoder_length + ) + decoder_length_histogram = integer_histogram( + out["decoder_lengths"], min=1, max=out["decoder_variables"].size(1) + ) + + # mask where decoder and encoder where not applied + # when averaging variable selection weights + encoder_variables = out["encoder_variables"].squeeze(-2).clone() + encode_mask = create_mask(encoder_variables.size(1), out["encoder_lengths"]) + encoder_variables = encoder_variables.masked_fill( + encode_mask.unsqueeze(-1), 0.0 + ).sum(dim=1) + encoder_variables /= ( + out["encoder_lengths"] + .where(out["encoder_lengths"] > 0, torch.ones_like(out["encoder_lengths"])) + .unsqueeze(-1) + ) + + decoder_variables = out["decoder_variables"].squeeze(-2).clone() + decode_mask = create_mask(decoder_variables.size(1), out["decoder_lengths"]) + decoder_variables = decoder_variables.masked_fill( + decode_mask.unsqueeze(-1), 0.0 + ).sum(dim=1) + decoder_variables /= out["decoder_lengths"].unsqueeze(-1) + + # static variables need no masking + static_variables = out["static_variables"].squeeze(1) + # attention is batch x time x heads x time_to_attend + # average over heads + only keep prediction attention and + # attention on observed timesteps + attention = masked_op( + attention[ + :, + attention_prediction_horizon, + :, + : self.hparams.max_encoder_length + attention_prediction_horizon, + ], + op="mean", + dim=1, + ) + + if reduction != "none": # if to average over batches + static_variables = static_variables.sum(dim=0) + encoder_variables = encoder_variables.sum(dim=0) + decoder_variables = decoder_variables.sum(dim=0) + + attention = masked_op(attention, dim=0, op=reduction) + else: + attention = attention / masked_op(attention, dim=1, op="sum").unsqueeze( + -1 + ) # renormalize + + interpretation = dict( + attention=attention.masked_fill(torch.isnan(attention), 0.0), + static_variables=static_variables, + encoder_variables=encoder_variables, + decoder_variables=decoder_variables, + encoder_length_histogram=encoder_length_histogram, + decoder_length_histogram=decoder_length_histogram, + ) + return interpretation + + def plot_prediction( + self, + x: Dict[str, torch.Tensor], + out: Dict[str, torch.Tensor], + idx: int, + plot_attention: bool = True, + add_loss_to_title: bool = False, + show_future_observed: bool = True, + ax=None, + **kwargs, + ): + """ + Plot actuals vs prediction and attention + + Args: + x (Dict[str, torch.Tensor]): network input + out (Dict[str, torch.Tensor]): network output + idx (int): sample index + plot_attention: if to plot attention on secondary axis + add_loss_to_title: if to add loss to title. Default to False. + show_future_observed: if to show actuals for future. Defaults to True. + ax: matplotlib axes to plot on + + Returns: + plt.Figure: matplotlib figure + """ + # plot prediction as normal + fig = super().plot_prediction( + x, + out, + idx=idx, + add_loss_to_title=add_loss_to_title, + show_future_observed=show_future_observed, + ax=ax, + **kwargs, + ) + + # add attention on secondary axis + if plot_attention: + interpretation = self.interpret_output(out.iget(slice(idx, idx + 1))) + for f in to_list(fig): + ax = f.axes[0] + ax2 = ax.twinx() + ax2.set_ylabel("Attention") + encoder_length = x["encoder_lengths"][0] + ax2.plot( + torch.arange(-encoder_length, 0), + interpretation["attention"][0, -encoder_length:].detach().cpu(), + alpha=0.2, + color="k", + ) + f.tight_layout() + return fig + + def plot_interpretation(self, interpretation: Dict[str, torch.Tensor]): + """ + Make figures that interpret model. + + * Attention + * Variable selection weights / importances + + Args: + interpretation: as obtained from ``interpret_output()`` + + Returns: + dictionary of matplotlib figures + """ + _check_matplotlib("plot_interpretation") + + import matplotlib.pyplot as plt + + figs = {} + + # attention + fig, ax = plt.subplots() + attention = interpretation["attention"].detach().cpu() + attention = attention / attention.sum(-1).unsqueeze(-1) + ax.plot( + np.arange( + -self.hparams.max_encoder_length, + attention.size(0) - self.hparams.max_encoder_length, + ), + attention, + ) + ax.set_xlabel("Time index") + ax.set_ylabel("Attention") + ax.set_title("Attention") + figs["attention"] = fig + + # variable selection + def make_selection_plot(title, values, labels): + fig, ax = plt.subplots(figsize=(7, len(values) * 0.25 + 2)) + order = np.argsort(values) + values = values / values.sum(-1).unsqueeze(-1) + ax.barh( + np.arange(len(values)), + values[order] * 100, + tick_label=np.asarray(labels)[order], + ) + ax.set_title(title) + ax.set_xlabel("Importance in %") + plt.tight_layout() + return fig + + figs["static_variables"] = make_selection_plot( + "Static variables importance", + interpretation["static_variables"].detach().cpu(), + self.static_variables, + ) + figs["encoder_variables"] = make_selection_plot( + "Encoder variables importance", + interpretation["encoder_variables"].detach().cpu(), + self.encoder_variables, + ) + figs["decoder_variables"] = make_selection_plot( + "Decoder variables importance", + interpretation["decoder_variables"].detach().cpu(), + self.decoder_variables, + ) + + return figs + + def log_interpretation(self, outputs): + """ + Log interpretation metrics to tensorboard. + """ + # extract interpretations + interpretation = { + # use padded_stack because decoder + # length histogram can be of different length + name: padded_stack( + [x["interpretation"][name].detach() for x in outputs], + side="right", + value=0, + ).sum(0) + for name in outputs[0]["interpretation"].keys() + } + # normalize attention with length histogram squared to account for: + # 1. zeros in attention and + # 2. higher attention due to less values + attention_occurances = ( + interpretation["encoder_length_histogram"][1:].flip(0).float().cumsum(0) + ) + attention_occurances = attention_occurances / attention_occurances.max() + attention_occurances = torch.cat( + [ + attention_occurances, + torch.ones( + interpretation["attention"].size(0) - attention_occurances.size(0), + dtype=attention_occurances.dtype, + device=attention_occurances.device, + ), + ], + dim=0, + ) + interpretation["attention"] = interpretation[ + "attention" + ] / attention_occurances.pow(2).clamp(1.0) + interpretation["attention"] = ( + interpretation["attention"] / interpretation["attention"].sum() + ) + + mpl_available = _check_matplotlib("log_interpretation", raise_error=False) + + # Don't log figures if matplotlib or add_figure is not available + if not mpl_available or not self._logger_supports("add_figure"): + return None + + import matplotlib.pyplot as plt + + figs = self.plot_interpretation(interpretation) # make interpretation figures + label = self.current_stage + # log to tensorboard + for name, fig in figs.items(): + self.logger.experiment.add_figure( + f"{label.capitalize()} {name} importance", + fig, + global_step=self.global_step, + ) + + # log lengths of encoder/decoder + for type in ["encoder", "decoder"]: + fig, ax = plt.subplots() + lengths = ( + padded_stack( + [ + out["interpretation"][f"{type}_length_histogram"] + for out in outputs + ] + ) + .sum(0) + .detach() + .cpu() + ) + if type == "decoder": + start = 1 + else: + start = 0 + ax.plot(torch.arange(start, start + len(lengths)), lengths) + ax.set_xlabel(f"{type.capitalize()} length") + ax.set_ylabel("Number of samples") + ax.set_title(f"{type.capitalize()} length distribution in {label} epoch") + + self.logger.experiment.add_figure( + f"{label.capitalize()} {type} length distribution", + fig, + global_step=self.global_step, + ) + + def log_embeddings(self): + """ + Log embeddings to tensorboard + """ + + # Don't log embeddings if add_embedding is not available + if not self._logger_supports("add_embedding"): + return None + + for name, emb in self.input_embeddings.items(): + labels = self.hparams.embedding_labels[name] + self.logger.experiment.add_embedding( + emb.weight.data.detach().cpu(), + metadata=labels, + tag=name, + global_step=self.global_step, + ) diff --git a/pytorch_forecasting/models/temporal_fusion_transformer/tft_v2_metadata.py b/pytorch_forecasting/models/temporal_fusion_transformer/tft_v2_metadata.py new file mode 100644 index 000000000..5ebea75b1 --- /dev/null +++ b/pytorch_forecasting/models/temporal_fusion_transformer/tft_v2_metadata.py @@ -0,0 +1,90 @@ +"""TFT metadata container.""" + +from pytorch_forecasting.models.base._base_object import _BasePtForecaster + + +class TFTMetadata(_BasePtForecaster): + """TFT metadata container.""" + + _tags = { + "info:name": "TFT", + "object_type": "ptf-v2", + "authors": ["phoeenniixx"], + "capability:exogenous": True, + "capability:multivariate": True, + "capability:pred_int": True, + "capability:flexible_history_length": False, + } + + @classmethod + def get_model_cls(cls): + """Get model class.""" + from pytorch_forecasting.models.temporal_fusion_transformer._tft_v2 import TFT + + return TFT + + @classmethod + def get_test_train_params(cls): + """Return testing parameter settings for the trainer. + + Returns + ------- + params : dict or list of dict, default = {} + Parameters to create testing instances of the class + Each dict are parameters to construct an "interesting" test instance, i.e., + `MyClass(**params)` or `MyClass(**params[i])` creates a valid test instance. + `create_test_instance` uses the first (or only) dictionary in `params` + """ + return [ + {}, + dict( + hidden_size=25, + attention_head_size=5, + ), + ] + + +class TemporalFusionTransformerMetadata(_BasePtForecaster): + """TFT metadata container.""" + + _tags = { + "info:name": "TemporalFusionTransformerM", + "object_type": "ptf-v2", + "authors": ["jdb78"], + "capability:exogenous": True, + "capability:multivariate": True, + "capability:pred_int": True, + "capability:flexible_history_length": False, + } + + @classmethod + def get_model_cls(cls): + """Get model class.""" + from pytorch_forecasting.models.temporal_fusion_transformer._tft_ver2 import ( + TemporalFusionTransformer, + ) + + return TemporalFusionTransformer + + @classmethod + def get_test_train_params(cls): + """Return testing parameter settings for the trainer. + + Returns + ------- + params : dict or list of dict, default = {} + Parameters to create testing instances of the class + Each dict are parameters to construct an "interesting" test instance, i.e., + `MyClass(**params)` or `MyClass(**params[i])` creates a valid test instance. + `create_test_instance` uses the first (or only) dictionary in `params` + """ + return [ + {}, + dict( + hidden_size=25, + attention_head_size=5, + data_loader_kwargs={ + "add_relative_time_idx": False, + }, + ), + ] diff --git a/pytorch_forecasting/tests/_conftest.py b/pytorch_forecasting/tests/_conftest.py index e276446a6..76d280f61 100644 --- a/pytorch_forecasting/tests/_conftest.py +++ b/pytorch_forecasting/tests/_conftest.py @@ -1,10 +1,15 @@ +from datetime import datetime, timedelta + import numpy as np +import pandas as pd import pytest import torch from pytorch_forecasting import TimeSeriesDataSet from pytorch_forecasting.data import EncoderNormalizer, GroupNormalizer, NaNLabelEncoder +from pytorch_forecasting.data.data_module import EncoderDecoderTimeSeriesDataModule from pytorch_forecasting.data.examples import generate_ar_data, get_stallion_data +from pytorch_forecasting.data.timeseries import TimeSeries torch.manual_seed(23) @@ -88,6 +93,233 @@ def make_dataloaders(data_with_covariates, **kwargs): return dict(train=train_dataloader, val=val_dataloader, test=test_dataloader) +@pytest.fixture(scope="session") +def data_with_covariates_v2(): + """Create synthetic time series data with all numerical features.""" + + start_date = datetime(2015, 1, 1) + end_date = datetime(2017, 12, 31) + dates = pd.date_range(start_date, end_date, freq="M") + + agencies = [0, 1] + skus = [0, 1] + data_list = [] + + for agency in agencies: + for sku in skus: + for date in dates: + time_idx = (date.year - 2015) * 12 + date.month - 1 + + volume = ( + np.random.exponential(2) + + 0.1 * time_idx + + 0.5 * np.sin(date.month * np.pi / 6) + ) + volume = max(0.001, volume) + month = date.month + year = date.year + quarter = (date.month - 1) // 3 + 1 + + seasonal_1 = np.sin(2 * np.pi * date.month / 12) + seasonal_2 = np.cos(2 * np.pi * date.month / 12) + + agency_feature_1 = agency * 10 + np.random.normal(0, 0.1) + agency_feature_2 = agency * 5 + np.random.normal(0, 0.1) + + sku_feature_1 = sku * 8 + np.random.normal(0, 0.1) + sku_feature_2 = sku * 3 + np.random.normal(0, 0.1) + + trend = time_idx * 0.1 + noise = np.random.normal(0, 0.1) + + special_event_1 = 1 if date.month in [12, 1] else 0 + special_event_2 = 1 if date.month in [6, 7, 8] else 0 + + data_list.append( + { + "date": date, + "time_idx": time_idx, + "agency_encoded": agency, + "sku_encoded": sku, + "volume": volume, + "target": volume, + "weight": 1.0 + np.sqrt(volume), + "month": month, + "year": year, + "quarter": quarter, + "seasonal_1": seasonal_1, + "seasonal_2": seasonal_2, + "agency_feature_1": agency_feature_1, + "agency_feature_2": agency_feature_2, + "sku_feature_1": sku_feature_1, + "sku_feature_2": sku_feature_2, + "trend": trend, + "noise": noise, + "special_event_1": special_event_1, + "special_event_2": special_event_2, + "log_volume": np.log1p(volume), + } + ) + + data = pd.DataFrame(data_list) + + numeric_cols = [col for col in data.columns if col != "date"] + for col in numeric_cols: + data[col] = pd.to_numeric(data[col], errors="coerce") + data[numeric_cols] = data[numeric_cols].fillna(0) + + return data + + +def make_dataloaders_v2(data_with_covariates, **kwargs): + """Create dataloaders with consistent encoder/decoder features.""" + + training_cutoff = "2016-09-01" + max_encoder_length = 4 + max_prediction_length = 3 + + target_col = kwargs.get("target", "target") + group_cols = kwargs.get("group_ids", ["agency_encoded", "sku_encoded"]) + add_relative_time_idx = kwargs.get("add_relative_time_idx", True) + + known_features = [ + "month", + "year", + "quarter", + "seasonal_1", + "seasonal_2", + "special_event_1", + "special_event_2", + "trend", + ] + unknown_features = [ + "agency_feature_1", + "agency_feature_2", + "sku_feature_1", + "sku_feature_2", + "noise", + "log_volume", + ] + + numerical_features = known_features + unknown_features + categorical_features = [] + static_features = group_cols + + for col in numerical_features + categorical_features + group_cols + [target_col]: + if col in data_with_covariates.columns: + data_with_covariates[col] = pd.to_numeric( + data_with_covariates[col], errors="coerce" + ).fillna(0) + + for col in categorical_features + group_cols: + if col in data_with_covariates.columns: + data_with_covariates[col] = data_with_covariates[col].astype("int64") + + if "weight" in data_with_covariates.columns: + data_with_covariates["weight"] = pd.to_numeric( + data_with_covariates["weight"], errors="coerce" + ).fillna(1.0) + + training_data = data_with_covariates[ + data_with_covariates.date < training_cutoff + ].copy() + validation_data = data_with_covariates.copy() + + required_columns = ( + ["time_idx", target_col, "weight", "date"] + + group_cols + + numerical_features + + categorical_features + ) + + available_columns = [ + col for col in required_columns if col in data_with_covariates.columns + ] + + training_data_clean = training_data[available_columns].copy() + validation_data_clean = validation_data[available_columns].copy() + + if "date" in training_data_clean.columns: + training_data_clean = training_data_clean.drop("date", axis=1) + if "date" in validation_data_clean.columns: + validation_data_clean = validation_data_clean.drop("date", axis=1) + + training_dataset = TimeSeries( + data=training_data_clean, + time="time_idx", + target=[target_col], + group=group_cols, + weight="weight", + num=numerical_features, + cat=categorical_features if categorical_features else None, + known=known_features, + unknown=unknown_features, + static=static_features, + ) + + validation_dataset = TimeSeries( + data=validation_data_clean, + time="time_idx", + target=[target_col], + group=group_cols, + weight="weight", + num=numerical_features, + cat=categorical_features if categorical_features else None, + known=known_features, + unknown=unknown_features, + static=static_features, + ) + + training_max_time_idx = training_data["time_idx"].max() + 1 + + train_datamodule = EncoderDecoderTimeSeriesDataModule( + time_series_dataset=training_dataset, + max_encoder_length=max_encoder_length, + max_prediction_length=max_prediction_length, + add_relative_time_idx=add_relative_time_idx, + batch_size=2, + num_workers=0, + train_val_test_split=(0.8, 0.2, 0.0), + ) + + val_datamodule = EncoderDecoderTimeSeriesDataModule( + time_series_dataset=validation_dataset, + max_encoder_length=max_encoder_length, + max_prediction_length=max_prediction_length, + min_prediction_idx=training_max_time_idx, + add_relative_time_idx=add_relative_time_idx, + batch_size=2, + num_workers=0, + train_val_test_split=(0.0, 1.0, 0.0), + ) + + test_datamodule = EncoderDecoderTimeSeriesDataModule( + time_series_dataset=validation_dataset, + max_encoder_length=max_encoder_length, + max_prediction_length=max_prediction_length, + min_prediction_idx=training_max_time_idx, + add_relative_time_idx=add_relative_time_idx, + batch_size=1, + num_workers=0, + train_val_test_split=(0.0, 0.0, 1.0), + ) + + train_datamodule.setup("fit") + val_datamodule.setup("fit") + test_datamodule.setup("test") + + train_dataloader = train_datamodule.train_dataloader() + val_dataloader = val_datamodule.val_dataloader() + test_dataloader = test_datamodule.test_dataloader() + + return { + "train": train_dataloader, + "val": val_dataloader, + "test": test_dataloader, + "data_module": train_datamodule, + } + + @pytest.fixture( params=[ dict(), diff --git a/pytorch_forecasting/tests/_data_scenarios.py b/pytorch_forecasting/tests/_data_scenarios.py index 062db97dd..b3037b3f6 100644 --- a/pytorch_forecasting/tests/_data_scenarios.py +++ b/pytorch_forecasting/tests/_data_scenarios.py @@ -1,10 +1,15 @@ +from datetime import datetime, timedelta + import numpy as np +import pandas as pd import pytest import torch from pytorch_forecasting import TimeSeriesDataSet from pytorch_forecasting.data import EncoderNormalizer, GroupNormalizer, NaNLabelEncoder +from pytorch_forecasting.data.data_module import EncoderDecoderTimeSeriesDataModule from pytorch_forecasting.data.examples import generate_ar_data, get_stallion_data +from pytorch_forecasting.data.timeseries import TimeSeries torch.manual_seed(23) @@ -87,6 +92,232 @@ def make_dataloaders(data_with_covariates, **kwargs): return dict(train=train_dataloader, val=val_dataloader, test=test_dataloader) +def data_with_covariates_v2(): + """Create synthetic time series data with all numerical features.""" + + start_date = datetime(2015, 1, 1) + end_date = datetime(2017, 12, 31) + dates = pd.date_range(start_date, end_date, freq="M") + + agencies = [0, 1] + skus = [0, 1] + data_list = [] + + for agency in agencies: + for sku in skus: + for date in dates: + time_idx = (date.year - 2015) * 12 + date.month - 1 + + volume = ( + np.random.exponential(2) + + 0.1 * time_idx + + 0.5 * np.sin(date.month * np.pi / 6) + ) + volume = max(0.001, volume) + month = date.month + year = date.year + quarter = (date.month - 1) // 3 + 1 + + seasonal_1 = np.sin(2 * np.pi * date.month / 12) + seasonal_2 = np.cos(2 * np.pi * date.month / 12) + + agency_feature_1 = agency * 10 + np.random.normal(0, 0.1) + agency_feature_2 = agency * 5 + np.random.normal(0, 0.1) + + sku_feature_1 = sku * 8 + np.random.normal(0, 0.1) + sku_feature_2 = sku * 3 + np.random.normal(0, 0.1) + + trend = time_idx * 0.1 + noise = np.random.normal(0, 0.1) + + special_event_1 = 1 if date.month in [12, 1] else 0 + special_event_2 = 1 if date.month in [6, 7, 8] else 0 + + data_list.append( + { + "date": date, + "time_idx": time_idx, + "agency_encoded": agency, + "sku_encoded": sku, + "volume": volume, + "target": volume, + "weight": 1.0 + np.sqrt(volume), + "month": month, + "year": year, + "quarter": quarter, + "seasonal_1": seasonal_1, + "seasonal_2": seasonal_2, + "agency_feature_1": agency_feature_1, + "agency_feature_2": agency_feature_2, + "sku_feature_1": sku_feature_1, + "sku_feature_2": sku_feature_2, + "trend": trend, + "noise": noise, + "special_event_1": special_event_1, + "special_event_2": special_event_2, + "log_volume": np.log1p(volume), + } + ) + + data = pd.DataFrame(data_list) + + numeric_cols = [col for col in data.columns if col != "date"] + for col in numeric_cols: + data[col] = pd.to_numeric(data[col], errors="coerce") + data[numeric_cols] = data[numeric_cols].fillna(0) + + return data + + +def make_dataloaders_v2(data_with_covariates, **kwargs): + """Create dataloaders with consistent encoder/decoder features.""" + + training_cutoff = "2016-09-01" + max_encoder_length = 4 + max_prediction_length = 3 + + target_col = kwargs.get("target", "target") + group_cols = kwargs.get("group_ids", ["agency_encoded", "sku_encoded"]) + add_relative_time_idx = kwargs.get("add_relative_time_idx", True) + + known_features = [ + "month", + "year", + "quarter", + "seasonal_1", + "seasonal_2", + "special_event_1", + "special_event_2", + "trend", + ] + unknown_features = [ + "agency_feature_1", + "agency_feature_2", + "sku_feature_1", + "sku_feature_2", + "noise", + "log_volume", + ] + + numerical_features = known_features + unknown_features + categorical_features = [] + static_features = group_cols + + for col in numerical_features + categorical_features + group_cols + [target_col]: + if col in data_with_covariates.columns: + data_with_covariates[col] = pd.to_numeric( + data_with_covariates[col], errors="coerce" + ).fillna(0) + + for col in categorical_features + group_cols: + if col in data_with_covariates.columns: + data_with_covariates[col] = data_with_covariates[col].astype("int64") + + if "weight" in data_with_covariates.columns: + data_with_covariates["weight"] = pd.to_numeric( + data_with_covariates["weight"], errors="coerce" + ).fillna(1.0) + + training_data = data_with_covariates[ + data_with_covariates.date < training_cutoff + ].copy() + validation_data = data_with_covariates.copy() + + required_columns = ( + ["time_idx", target_col, "weight", "date"] + + group_cols + + numerical_features + + categorical_features + ) + + available_columns = [ + col for col in required_columns if col in data_with_covariates.columns + ] + + training_data_clean = training_data[available_columns].copy() + validation_data_clean = validation_data[available_columns].copy() + + if "date" in training_data_clean.columns: + training_data_clean = training_data_clean.drop("date", axis=1) + if "date" in validation_data_clean.columns: + validation_data_clean = validation_data_clean.drop("date", axis=1) + + training_dataset = TimeSeries( + data=training_data_clean, + time="time_idx", + target=[target_col], + group=group_cols, + weight="weight", + num=numerical_features, + cat=categorical_features if categorical_features else None, + known=known_features, + unknown=unknown_features, + static=static_features, + ) + + validation_dataset = TimeSeries( + data=validation_data_clean, + time="time_idx", + target=[target_col], + group=group_cols, + weight="weight", + num=numerical_features, + cat=categorical_features if categorical_features else None, + known=known_features, + unknown=unknown_features, + static=static_features, + ) + + training_max_time_idx = training_data["time_idx"].max() + 1 + + train_datamodule = EncoderDecoderTimeSeriesDataModule( + time_series_dataset=training_dataset, + max_encoder_length=max_encoder_length, + max_prediction_length=max_prediction_length, + add_relative_time_idx=add_relative_time_idx, + batch_size=2, + num_workers=0, + train_val_test_split=(0.8, 0.2, 0.0), + ) + + val_datamodule = EncoderDecoderTimeSeriesDataModule( + time_series_dataset=validation_dataset, + max_encoder_length=max_encoder_length, + max_prediction_length=max_prediction_length, + min_prediction_idx=training_max_time_idx, + add_relative_time_idx=add_relative_time_idx, + batch_size=2, + num_workers=0, + train_val_test_split=(0.0, 1.0, 0.0), + ) + + test_datamodule = EncoderDecoderTimeSeriesDataModule( + time_series_dataset=validation_dataset, + max_encoder_length=max_encoder_length, + max_prediction_length=max_prediction_length, + min_prediction_idx=training_max_time_idx, + add_relative_time_idx=add_relative_time_idx, + batch_size=1, + num_workers=0, + train_val_test_split=(0.0, 0.0, 1.0), + ) + + train_datamodule.setup("fit") + val_datamodule.setup("fit") + test_datamodule.setup("test") + + train_dataloader = train_datamodule.train_dataloader() + val_dataloader = val_datamodule.val_dataloader() + test_dataloader = test_datamodule.test_dataloader() + + return { + "train": train_dataloader, + "val": val_dataloader, + "test": test_dataloader, + "data_module": train_datamodule, + } + + @pytest.fixture( params=[ dict(), diff --git a/pytorch_forecasting/tests/test_all_estimators_v2.py b/pytorch_forecasting/tests/test_all_estimators_v2.py index 1dc7859ab..efbe170f0 100644 --- a/pytorch_forecasting/tests/test_all_estimators_v2.py +++ b/pytorch_forecasting/tests/test_all_estimators_v2.py @@ -7,7 +7,8 @@ from lightning.pytorch.callbacks import EarlyStopping from lightning.pytorch.loggers import TensorBoardLogger -from pytorch_forecasting.tests._conftest import make_dataloaders +from pytorch_forecasting.metrics import SMAPE +from pytorch_forecasting.tests._conftest import make_dataloaders_v2 as make_dataloaders from pytorch_forecasting.tests.test_all_estimators import ( BaseFixtureGenerator, PackageConfig, @@ -22,18 +23,86 @@ def _integration( estimator_cls, data_with_covariates, tmp_path, - cell_type="LSTM", data_loader_kwargs={}, clip_target: bool = False, trainer_kwargs=None, **kwargs, ): - pass + data_with_covariates = data_with_covariates.copy() + if clip_target: + data_with_covariates["target"] = data_with_covariates["volume"].clip(1e-3, 1.0) + else: + data_with_covariates["target"] = data_with_covariates["volume"] + + data_loader_default_kwargs = dict( + target="target", + group_ids=["agency_encoded", "sku_encoded"], + add_relative_time_idx=True, + ) + data_loader_default_kwargs.update(data_loader_kwargs) + + dataloaders_with_covariates = make_dataloaders( + data_with_covariates, **data_loader_default_kwargs + ) + + train_dataloader = dataloaders_with_covariates["train"] + val_dataloader = dataloaders_with_covariates["val"] + test_dataloader = dataloaders_with_covariates["test"] + + early_stop_callback = EarlyStopping( + monitor="val_loss", min_delta=1e-4, patience=1, verbose=False, mode="min" + ) + + logger = TensorBoardLogger(tmp_path) + if trainer_kwargs is None: + trainer_kwargs = {} + trainer = pl.Trainer( + max_epochs=3, + gradient_clip_val=0.1, + callbacks=[early_stop_callback], + enable_checkpointing=True, + default_root_dir=tmp_path, + limit_train_batches=2, + limit_val_batches=2, + limit_test_batches=2, + logger=logger, + **trainer_kwargs, + ) + training_data_module = dataloaders_with_covariates["data_module"] + metadata = training_data_module.metadata + + net = estimator_cls( + metadata=metadata, + loss=SMAPE(), + **kwargs, + ) + + try: + trainer.fit( + net, + train_dataloaders=train_dataloader, + val_dataloaders=val_dataloader, + ) + test_outputs = trainer.test(net, dataloaders=test_dataloader) + assert len(test_outputs) > 0 + + # check loading + # net = estimator_cls.load_from_checkpoint( + # trainer.checkpoint_callback.best_model_path + # ) + # net.predict(val_dataloader) + + finally: + shutil.rmtree(tmp_path, ignore_errors=True) + + # net.predict(val_dataloader) class TestAllPtForecastersV2(PackageConfig, BaseFixtureGenerator): """Generic tests for all objects in the mini package.""" + object_type_filter = "ptf-v2" + def test_doctest_examples(self, object_class): """Runs doctests for estimator class.""" import doctest @@ -46,4 +115,8 @@ def test_integration( trainer_kwargs, tmp_path, ): - pass + from pytorch_forecasting.tests._data_scenarios import data_with_covariates_v2 + + data_with_covariates = data_with_covariates_v2() + object_class = object_metadata.get_model_cls() + _integration(object_class, data_with_covariates, tmp_path, **trainer_kwargs) From 1831bcb5ae7a87498fd5f10166b15de92a5ae5cc Mon Sep 17 00:00:00 2001 From: Pranav Bhat Date: Mon, 26 May 2025 01:51:57 +0530 Subject: [PATCH 071/139] add example notebook and fix buy in _timexer.py this completes the d2 pipeline for timexer, prediction has not been tested, also revert the loss dtype to nn.Module for now. --- examples/tslib_v2_example.ipynb | 19151 ++++++++++++++++ pytorch_forecasting/data/tslib_data_module.py | 92 +- pytorch_forecasting/models/__init__.py | 2 + .../models/base/_base_model_v2.py | 8 +- .../models/timexer/__init__.py | 5 + .../models/timexer/_timexer.py | 44 +- 6 files changed, 19217 insertions(+), 85 deletions(-) create mode 100644 examples/tslib_v2_example.ipynb diff --git a/examples/tslib_v2_example.ipynb b/examples/tslib_v2_example.ipynb new file mode 100644 index 000000000..493b13ee8 --- /dev/null +++ b/examples/tslib_v2_example.ipynb @@ -0,0 +1,19151 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "b5d44943", + "metadata": {}, + "source": [ + "# TSLib v2 - Example notebook for full pipeline" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "bcf03bf8", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Requirement already satisfied: pytorch-forecasting in c:\\users\\prana\\desktop\\code\\pytorch-forecasting\\.venv\\lib\\site-packages (1.3.0)\n", + "Requirement already satisfied: numpy<=3.0.0 in c:\\users\\prana\\desktop\\code\\pytorch-forecasting\\.venv\\lib\\site-packages (from pytorch-forecasting) (2.2.6)\n", + "Requirement already satisfied: torch!=2.0.1,<3.0.0,>=2.0.0 in c:\\users\\prana\\desktop\\code\\pytorch-forecasting\\.venv\\lib\\site-packages (from pytorch-forecasting) (2.7.0)\n", + "Requirement already satisfied: lightning<3.0.0,>=2.0.0 in c:\\users\\prana\\desktop\\code\\pytorch-forecasting\\.venv\\lib\\site-packages (from pytorch-forecasting) (2.5.1.post0)\n", + "Requirement already satisfied: scipy<2.0,>=1.8 in c:\\users\\prana\\desktop\\code\\pytorch-forecasting\\.venv\\lib\\site-packages (from pytorch-forecasting) (1.15.3)\n", + "Requirement already satisfied: pandas<3.0.0,>=1.3.0 in c:\\users\\prana\\desktop\\code\\pytorch-forecasting\\.venv\\lib\\site-packages (from pytorch-forecasting) (2.2.3)\n", + "Requirement already satisfied: scikit-learn<2.0,>=1.2 in c:\\users\\prana\\desktop\\code\\pytorch-forecasting\\.venv\\lib\\site-packages (from pytorch-forecasting) (1.6.1)\n", + "Requirement already satisfied: PyYAML<8.0,>=5.4 in c:\\users\\prana\\desktop\\code\\pytorch-forecasting\\.venv\\lib\\site-packages (from lightning<3.0.0,>=2.0.0->pytorch-forecasting) (6.0.2)\n", + "Requirement already satisfied: fsspec<2026.0,>=2022.5.0 in c:\\users\\prana\\desktop\\code\\pytorch-forecasting\\.venv\\lib\\site-packages (from fsspec[http]<2026.0,>=2022.5.0->lightning<3.0.0,>=2.0.0->pytorch-forecasting) (2025.5.1)\n", + "Requirement already satisfied: lightning-utilities<2.0,>=0.10.0 in c:\\users\\prana\\desktop\\code\\pytorch-forecasting\\.venv\\lib\\site-packages (from lightning<3.0.0,>=2.0.0->pytorch-forecasting) (0.14.3)\n", + "Requirement already satisfied: packaging<25.0,>=20.0 in c:\\users\\prana\\desktop\\code\\pytorch-forecasting\\.venv\\lib\\site-packages (from lightning<3.0.0,>=2.0.0->pytorch-forecasting) (24.2)\n", + "Requirement already satisfied: torchmetrics<3.0,>=0.7.0 in c:\\users\\prana\\desktop\\code\\pytorch-forecasting\\.venv\\lib\\site-packages (from lightning<3.0.0,>=2.0.0->pytorch-forecasting) (1.7.1)\n", + "Requirement already satisfied: tqdm<6.0,>=4.57.0 in c:\\users\\prana\\desktop\\code\\pytorch-forecasting\\.venv\\lib\\site-packages (from lightning<3.0.0,>=2.0.0->pytorch-forecasting) (4.67.1)\n", + "Requirement already satisfied: typing-extensions<6.0,>=4.4.0 in c:\\users\\prana\\desktop\\code\\pytorch-forecasting\\.venv\\lib\\site-packages (from lightning<3.0.0,>=2.0.0->pytorch-forecasting) (4.13.2)\n", + "Requirement already satisfied: pytorch-lightning in c:\\users\\prana\\desktop\\code\\pytorch-forecasting\\.venv\\lib\\site-packages (from lightning<3.0.0,>=2.0.0->pytorch-forecasting) (2.5.1.post0)\n", + "Requirement already satisfied: python-dateutil>=2.8.2 in c:\\users\\prana\\desktop\\code\\pytorch-forecasting\\.venv\\lib\\site-packages (from pandas<3.0.0,>=1.3.0->pytorch-forecasting) (2.9.0.post0)\n", + "Requirement already satisfied: pytz>=2020.1 in c:\\users\\prana\\desktop\\code\\pytorch-forecasting\\.venv\\lib\\site-packages (from pandas<3.0.0,>=1.3.0->pytorch-forecasting) (2025.2)\n", + "Requirement already satisfied: tzdata>=2022.7 in c:\\users\\prana\\desktop\\code\\pytorch-forecasting\\.venv\\lib\\site-packages (from pandas<3.0.0,>=1.3.0->pytorch-forecasting) (2025.2)\n", + "Requirement already satisfied: joblib>=1.2.0 in c:\\users\\prana\\desktop\\code\\pytorch-forecasting\\.venv\\lib\\site-packages (from scikit-learn<2.0,>=1.2->pytorch-forecasting) (1.5.1)\n", + "Requirement already satisfied: threadpoolctl>=3.1.0 in c:\\users\\prana\\desktop\\code\\pytorch-forecasting\\.venv\\lib\\site-packages (from scikit-learn<2.0,>=1.2->pytorch-forecasting) (3.6.0)\n", + "Requirement already satisfied: filelock in c:\\users\\prana\\desktop\\code\\pytorch-forecasting\\.venv\\lib\\site-packages (from torch!=2.0.1,<3.0.0,>=2.0.0->pytorch-forecasting) (3.18.0)\n", + "Requirement already satisfied: sympy>=1.13.3 in c:\\users\\prana\\desktop\\code\\pytorch-forecasting\\.venv\\lib\\site-packages (from torch!=2.0.1,<3.0.0,>=2.0.0->pytorch-forecasting) (1.14.0)\n", + "Requirement already satisfied: networkx in c:\\users\\prana\\desktop\\code\\pytorch-forecasting\\.venv\\lib\\site-packages (from torch!=2.0.1,<3.0.0,>=2.0.0->pytorch-forecasting) (3.4.2)\n", + "Requirement already satisfied: jinja2 in c:\\users\\prana\\desktop\\code\\pytorch-forecasting\\.venv\\lib\\site-packages (from torch!=2.0.1,<3.0.0,>=2.0.0->pytorch-forecasting) (3.1.6)\n", + "Requirement already satisfied: setuptools in c:\\users\\prana\\desktop\\code\\pytorch-forecasting\\.venv\\lib\\site-packages (from torch!=2.0.1,<3.0.0,>=2.0.0->pytorch-forecasting) (80.8.0)\n", + "Requirement already satisfied: aiohttp!=4.0.0a0,!=4.0.0a1 in c:\\users\\prana\\desktop\\code\\pytorch-forecasting\\.venv\\lib\\site-packages (from fsspec[http]<2026.0,>=2022.5.0->lightning<3.0.0,>=2.0.0->pytorch-forecasting) (3.12.0)\n", + "Requirement already satisfied: six>=1.5 in c:\\users\\prana\\desktop\\code\\pytorch-forecasting\\.venv\\lib\\site-packages (from python-dateutil>=2.8.2->pandas<3.0.0,>=1.3.0->pytorch-forecasting) (1.17.0)\n", + "Requirement already satisfied: mpmath<1.4,>=1.1.0 in c:\\users\\prana\\desktop\\code\\pytorch-forecasting\\.venv\\lib\\site-packages (from sympy>=1.13.3->torch!=2.0.1,<3.0.0,>=2.0.0->pytorch-forecasting) (1.3.0)\n", + "Requirement already satisfied: colorama in c:\\users\\prana\\desktop\\code\\pytorch-forecasting\\.venv\\lib\\site-packages (from tqdm<6.0,>=4.57.0->lightning<3.0.0,>=2.0.0->pytorch-forecasting) (0.4.6)\n", + "Requirement already satisfied: MarkupSafe>=2.0 in c:\\users\\prana\\desktop\\code\\pytorch-forecasting\\.venv\\lib\\site-packages (from jinja2->torch!=2.0.1,<3.0.0,>=2.0.0->pytorch-forecasting) (3.0.2)\n", + "Requirement already satisfied: aiohappyeyeballs>=2.5.0 in c:\\users\\prana\\desktop\\code\\pytorch-forecasting\\.venv\\lib\\site-packages (from aiohttp!=4.0.0a0,!=4.0.0a1->fsspec[http]<2026.0,>=2022.5.0->lightning<3.0.0,>=2.0.0->pytorch-forecasting) (2.6.1)\n", + "Requirement already satisfied: aiosignal>=1.1.2 in c:\\users\\prana\\desktop\\code\\pytorch-forecasting\\.venv\\lib\\site-packages (from aiohttp!=4.0.0a0,!=4.0.0a1->fsspec[http]<2026.0,>=2022.5.0->lightning<3.0.0,>=2.0.0->pytorch-forecasting) (1.3.2)\n", + "Requirement already satisfied: attrs>=17.3.0 in c:\\users\\prana\\desktop\\code\\pytorch-forecasting\\.venv\\lib\\site-packages (from aiohttp!=4.0.0a0,!=4.0.0a1->fsspec[http]<2026.0,>=2022.5.0->lightning<3.0.0,>=2.0.0->pytorch-forecasting) (25.3.0)\n", + "Requirement already satisfied: frozenlist>=1.1.1 in c:\\users\\prana\\desktop\\code\\pytorch-forecasting\\.venv\\lib\\site-packages (from aiohttp!=4.0.0a0,!=4.0.0a1->fsspec[http]<2026.0,>=2022.5.0->lightning<3.0.0,>=2.0.0->pytorch-forecasting) (1.6.0)\n", + "Requirement already satisfied: multidict<7.0,>=4.5 in c:\\users\\prana\\desktop\\code\\pytorch-forecasting\\.venv\\lib\\site-packages (from aiohttp!=4.0.0a0,!=4.0.0a1->fsspec[http]<2026.0,>=2022.5.0->lightning<3.0.0,>=2.0.0->pytorch-forecasting) (6.4.4)\n", + "Requirement already satisfied: propcache>=0.2.0 in c:\\users\\prana\\desktop\\code\\pytorch-forecasting\\.venv\\lib\\site-packages (from aiohttp!=4.0.0a0,!=4.0.0a1->fsspec[http]<2026.0,>=2022.5.0->lightning<3.0.0,>=2.0.0->pytorch-forecasting) (0.3.1)\n", + "Requirement already satisfied: yarl<2.0,>=1.17.0 in c:\\users\\prana\\desktop\\code\\pytorch-forecasting\\.venv\\lib\\site-packages (from aiohttp!=4.0.0a0,!=4.0.0a1->fsspec[http]<2026.0,>=2022.5.0->lightning<3.0.0,>=2.0.0->pytorch-forecasting) (1.20.0)\n", + "Requirement already satisfied: idna>=2.0 in c:\\users\\prana\\desktop\\code\\pytorch-forecasting\\.venv\\lib\\site-packages (from yarl<2.0,>=1.17.0->aiohttp!=4.0.0a0,!=4.0.0a1->fsspec[http]<2026.0,>=2022.5.0->lightning<3.0.0,>=2.0.0->pytorch-forecasting) (3.10)\n", + "Note: you may need to restart the kernel to use updated packages.\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "\n", + "[notice] A new release of pip is available: 24.2 -> 25.1.1\n", + "[notice] To update, run: python.exe -m pip install --upgrade pip\n" + ] + } + ], + "source": [ + "%pip install pytorch-forecasting" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "550a3fbf", + "metadata": {}, + "outputs": [], + "source": [ + "from typing import Any, Dict, List, Optional, Tuple, Union\n", + "\n", + "import numpy as np\n", + "import pandas as pd\n", + "from sklearn.preprocessing import RobustScaler, StandardScaler\n", + "import torch\n", + "from torch.optim import Optimizer\n", + "from torch.utils.data import Dataset\n", + "\n", + "from pytorch_forecasting.data.encoders import (\n", + " EncoderNormalizer,\n", + " NaNLabelEncoder,\n", + " TorchNormalizer,\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3a9cd738", + "metadata": {}, + "outputs": [], + "source": [ + "from pytorch_forecasting.data.timeseries import TimeSeries\n", + "from pytorch_forecasting.data.tslib_data_module import TslibDataModule\n", + "from pytorch_forecasting.models.timexer import TimeXer" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "a0058487", + "metadata": {}, + "outputs": [ + { + "data": { + "application/vnd.microsoft.datawrangler.viewer.v0+json": { + "columns": [ + { + "name": "index", + "rawType": "int64", + "type": "integer" + }, + { + "name": "series_id", + "rawType": "int64", + "type": "integer" + }, + { + "name": "time_idx", + "rawType": "int64", + "type": "integer" + }, + { + "name": "x", + "rawType": "float64", + "type": "float" + }, + { + "name": "y", + "rawType": "float64", + "type": "float" + }, + { + "name": "category", + "rawType": "int64", + "type": "integer" + }, + { + "name": "future_known_feature", + "rawType": "float64", + "type": "float" + }, + { + "name": "static_feature", + "rawType": "float64", + "type": "float" + }, + { + "name": "static_feature_cat", + "rawType": "int64", + "type": "integer" + } + ], + "ref": "11a96733-836f-490e-9788-80cd540ed87f", + "rows": [ + [ + "0", + "0", + "0", + "-0.20094783251489232", + "0.2831472670549394", + "0", + "1.0", + "0.8418973682486485", + "0" + ], + [ + "1", + "0", + "1", + "0.2831472670549394", + "0.5287947236215006", + "0", + "0.9950041652780258", + "0.8418973682486485", + "0" + ], + [ + "2", + "0", + "2", + "0.5287947236215006", + "0.6869941888949886", + "0", + "0.9800665778412416", + "0.8418973682486485", + "0" + ], + [ + "3", + "0", + "3", + "0.6869941888949886", + "0.7114514740783304", + "0", + "0.955336489125606", + "0.8418973682486485", + "0" + ], + [ + "4", + "0", + "4", + "0.7114514740783304", + "0.8367522080227738", + "0", + "0.9210609940028851", + "0.8418973682486485", + "0" + ] + ], + "shape": { + "columns": 8, + "rows": 5 + } + }, + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
series_idtime_idxxycategoryfuture_known_featurestatic_featurestatic_feature_cat
000-0.2009480.28314701.0000000.8418970
1010.2831470.52879500.9950040.8418970
2020.5287950.68699400.9800670.8418970
3030.6869940.71145100.9553360.8418970
4040.7114510.83675200.9210610.8418970
\n", + "
" + ], + "text/plain": [ + " series_id time_idx x y category future_known_feature \\\n", + "0 0 0 -0.200948 0.283147 0 1.000000 \n", + "1 0 1 0.283147 0.528795 0 0.995004 \n", + "2 0 2 0.528795 0.686994 0 0.980067 \n", + "3 0 3 0.686994 0.711451 0 0.955336 \n", + "4 0 4 0.711451 0.836752 0 0.921061 \n", + "\n", + " static_feature static_feature_cat \n", + "0 0.841897 0 \n", + "1 0.841897 0 \n", + "2 0.841897 0 \n", + "3 0.841897 0 \n", + "4 0.841897 0 " + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "num_series = 100\n", + "seq_length = 50\n", + "data_list = []\n", + "for i in range(num_series):\n", + " x = np.arange(seq_length)\n", + " y = np.sin(x / 5.0) + np.random.normal(scale=0.1, size=seq_length)\n", + " category = i % 5\n", + " static_value = np.random.rand()\n", + " for t in range(seq_length - 1):\n", + " data_list.append(\n", + " {\n", + " \"series_id\": i,\n", + " \"time_idx\": t,\n", + " \"x\": y[t],\n", + " \"y\": y[t + 1],\n", + " \"category\": category,\n", + " \"future_known_feature\": np.cos(t / 10),\n", + " \"static_feature\": static_value,\n", + " \"static_feature_cat\": i % 3,\n", + " }\n", + " )\n", + "data_df = pd.DataFrame(data_list)\n", + "data_df.head()" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "89a5adbe", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "C:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\pytorch_forecasting\\data\\timeseries\\_timeseries_v2.py:106: UserWarning: TimeSeries is part of an experimental rework of the pytorch-forecasting data layer, scheduled for release with v2.0.0. The API is not stable and may change without prior warning. For beta testing, but not for stable production use. Feedback and suggestions are very welcome in pytorch-forecasting issue 1736, https://github.com/sktime/pytorch-forecasting/issues/1736\n", + " warn(\n" + ] + } + ], + "source": [ + "dataset = TimeSeries(\n", + " data=data_df,\n", + " time=\"time_idx\",\n", + " target=\"y\",\n", + " group=[\"series_id\"],\n", + " num=[\"x\", \"future_know_feature\", \"static_feature\"],\n", + " cat=[\"category\", \"static_feature_cat\"],\n", + " known=[\"future_known_feature\"],\n", + " unknown=[\"x\", \"category\"],\n", + " static=[\"static_feature\", \"static_feature_cat\"],\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "5eae9035", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "C:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\pytorch_forecasting\\data\\tslib_data_module.py:266: UserWarning: TslibDataModule is experimental and subject to change. The API is not stable and may change without prior warning.\n", + " warnings.warn(\n" + ] + } + ], + "source": [ + "data_module = TslibDataModule(\n", + " time_series_dataset=dataset,\n", + " context_length=30,\n", + " prediction_length=1,\n", + " add_relative_time_idx=True,\n", + " target_normalizer=TorchNormalizer(),\n", + " categorical_encoders={\n", + " \"category\": NaNLabelEncoder(add_nan=True),\n", + " \"static_feature_cat\": NaNLabelEncoder(add_nan=True),\n", + " },\n", + " scalers={\n", + " \"x\": StandardScaler(),\n", + " \"future_known_feature\": StandardScaler(),\n", + " \"static_feature\": StandardScaler(),\n", + " },\n", + " batch_size=32,\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "76ebffc1", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "dict" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "type(data_module.metadata)" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "b1843233", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'feature_names': {'categorical': ['category', 'static_feature_cat'],\n", + " 'continuous': ['x', 'future_known_feature', 'static_feature'],\n", + " 'static': ['static_feature', 'static_feature_cat'],\n", + " 'known': ['future_known_feature'],\n", + " 'unknown': ['x', 'category', 'static_feature', 'static_feature_cat'],\n", + " 'target': ['y'],\n", + " 'all': ['x',\n", + " 'category',\n", + " 'future_known_feature',\n", + " 'static_feature',\n", + " 'static_feature_cat'],\n", + " 'static_categorical': ['static_feature_cat'],\n", + " 'static_continuous': ['static_feature']},\n", + " 'feature_indices': {'categorical': [1, 4],\n", + " 'continuous': [0, 2, 3],\n", + " 'static': [],\n", + " 'known': [2],\n", + " 'unknown': [0, 1, 3, 4],\n", + " 'target': [0],\n", + " 'all': []},\n", + " 'n_features': {'categorical': 2,\n", + " 'continuous': 3,\n", + " 'static': 2,\n", + " 'known': 1,\n", + " 'unknown': 4,\n", + " 'target': 1,\n", + " 'all': 5,\n", + " 'static_categorical': 1,\n", + " 'static_continuous': 1},\n", + " 'context_length': 30,\n", + " 'prediction_length': 1,\n", + " 'freq': 'h',\n", + " 'features': 'MS'}" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "data_module.metadata" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "12036e70", + "metadata": {}, + "outputs": [], + "source": [ + "import torch.nn as nn\n", + "\n", + "from pytorch_forecasting.metrics import MAE, SMAPE" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "429b5f15", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "C:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\pytorch_forecasting\\models\\base\\_base_model_v2.py:60: UserWarning: The Model 'TimeXer' is part of an experimental reworkof the pytorch-forecasting model layer, scheduled for release with v2.0.0. The API is not stable and may change without prior warning. This class is intended for beta testing and as a basic skeleton, but not for stable production use. Feedback and suggestions are very welcome in pytorch-forecasting issue 1736, https://github.com/sktime/pytorch-forecasting/issues/1736\n", + " warn(\n" + ] + } + ], + "source": [ + "model = TimeXer(\n", + " loss=nn.MSELoss(),\n", + " features=\"MS\",\n", + " d_model=64,\n", + " nhead=4,\n", + " e_layers=2,\n", + " d_ff=256,\n", + " dropout=0.1,\n", + " patch_length=4,\n", + " logging_metrics=[MAE(), SMAPE()],\n", + " optimizer=\"adam\",\n", + " optimizer_params={\"lr\": 1e-3},\n", + " lr_scheduler=\"reduce_lr_on_plateau\",\n", + " lr_scheduler_params={\n", + " \"mode\": \"min\",\n", + " \"factor\": 0.5,\n", + " \"patience\": 5,\n", + " },\n", + " metadata=data_module.metadata,\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "02605f9b", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Using default `ModelCheckpoint`. Consider installing `litmodels` package to enable `LitModelCheckpoint` for automatic upload to the Lightning model registry.\n", + "GPU available: False, used: False\n", + "TPU available: False, using: 0 TPU cores\n", + "HPU available: False, using: 0 HPUs\n" + ] + } + ], + "source": [ + "from lightning.pytorch import Trainer\n", + "\n", + "trainer = Trainer(\n", + " max_epochs=5,\n", + " accelerator=\"auto\",\n", + " devices=1,\n", + " enable_progress_bar=True,\n", + " enable_model_summary=True,\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "6e9117d2", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "\n", + " | Name | Type | Params | Mode \n", + "----------------------------------------------------------------\n", + "0 | loss | MSELoss | 0 | train\n", + "1 | en_embedding | EnEmbedding | 320 | train\n", + "2 | ex_embedding | DataEmbedding_inverted | 2.0 K | train\n", + "3 | encoder | Encoder | 133 K | train\n", + "4 | head | FlattenHead | 513 | train\n", + "----------------------------------------------------------------\n", + "136 K Trainable params\n", + "0 Non-trainable params\n", + "136 K Total params\n", + "0.546 Total estimated model params size (MB)\n", + "57 Modules in train mode\n", + "0 Modules in eval mode\n" + ] + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "93e5dd9e1fab49c4ab47276eb6f6421d", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "Sanity Checking: | | 0/? [00:00 Dict[str, Any]: x["target_scale"] = processed_data["target_scale"] y = processed_data["target"][future_indices] - if y.ndim() == 1: + if y.ndim == 1: y = y.unsqueeze(0) return x, y @@ -276,25 +276,27 @@ def __init__( self._metadata = None - self.categorical_indices = [] - self.continuous_indices = [] - - for idx, col in enumerate(self.time_series_dataset["cols"]["x"]): - if self.time_series_metadata["col_type"].get(col) == "C": - self.categorical_indices.append(idx) - else: - self.continuous_indices.append(idx) - self.scalers = scalers or {} self.shuffle = shuffle + self.continuous_indices = [] + self.categorical_indices = [] + self.train_dataset = None self.val_dataset = None self.test_dataset = None + self.window_stride = window_stride + self.time_series_metadata = time_series_dataset.get_metadata() - def _prepare_metadata(self) -> None: + for idx, col in enumerate(self.time_series_metadata["cols"]["x"]): + if self.time_series_metadata["col_type"].get(col) == "C": + self.categorical_indices.append(idx) + else: + self.continuous_indices.append(idx) + + def _prepare_metadata(self) -> Dict[str, Any]: """ Prepare metadata for `tslib` time series data module. @@ -317,7 +319,7 @@ def _prepare_metadata(self) -> None: Feature combination mode. """ # TODO: include handling for datasets without get_metadata() - ds_metadata = self.time_series_metadata.get_metadata() + ds_metadata = self.time_series_metadata feature_names = { "categorical": [], @@ -372,27 +374,22 @@ def _prepare_metadata(self) -> None: else: static_cont_names.append(col) + feature_indices["target"] = list(range(len(target_features))) + feature_names["static_categorical"] = static_cat_names feature_names["static_continuous"] = static_cont_names - for col in target_features: - if col in all_features: - actual_idx = all_features.index(col) - feature_indices["target"].append(actual_idx) - else: - warnings.warn(f"Target feature {col} not found!") - n_features = {k: len(v) for k, v in feature_names.items()} - metadata = dict( - feature_names=feature_names, - feature_indices=feature_indices, - n_features=n_features, - context_length=self.context_length, - prediction_length=self.prediction_length, - freq=self.freq, - features=self.features, - ) + metadata = { + "feature_names": feature_names, + "feature_indices": feature_indices, + "n_features": n_features, + "context_length": self.context_length, + "prediction_length": self.prediction_length, + "freq": self.freq, + "features": self.features, + } return metadata @@ -407,7 +404,7 @@ def metadata(self) -> Dict[str, Any]: Metadata for the data module. Refer to the `_prepare_metadata` method for the keys and values in the metadata dictionary. """ - if not hasattr(self, "_metadata"): + if self._metadata is None: self._metadata = self._prepare_metadata() return self._metadata @@ -456,43 +453,6 @@ def _preprocess_data(self, idx: torch.Tensor) -> List[Dict[str, Any]]: # scaling and normlization target_scale = {} - if self._target_normalizer is not None: - if self.add_target_scales: - if hasattr(self._target_normalizer, "scale_"): - target_scale["scale"] = torch.tensor(self._target_normalizer.scale_) - if hasattr(self._target_normalizer, "center_"): - target_scale["center"] = torch.tensor( - self._target_normalizer.center_ - ) # noqa: E501 - - if isinstance(self._target_normalizer, TorchNormalizer): - target = self._target_normalizer.transform(target) - else: - # extra case for handling non-native normalizers - # apart from those in NORMALIZER. - target_np = target.reshape(-1, 1).numpy() - target = torch.tensor( - self._target_normalizer.transform(target_np), - dtype=torch.float32, - ).reshape(target.shape) - - if self.scalers: - feature_indices = self.metadat["feature_indices"] - feature_names = self.metadata["feature_names"] - - for i, idx in enumerate(feature_indices.get("continuous", [])): - feature_name = feature_names["continuous"][i] - if feature_name in self.scalers: - scaler = self.scalers[feature_name] - if isinstance(scaler, TorchNormalizer): - features[..., idx] = scaler.transform(features[..., idx]) - else: - feature_np = features[..., idx].reshape(-1, 1).numpy() - features[..., idx] = torch.tensor( - scaler.transform(feature_np), - dtype=torch.float32, - ).reshape(features[..., idx].shape) - categorical_features = ( features[:, self.categorical_indices] if self.categorical_indices diff --git a/pytorch_forecasting/models/__init__.py b/pytorch_forecasting/models/__init__.py index 29aeb24f5..0a9d600f8 100644 --- a/pytorch_forecasting/models/__init__.py +++ b/pytorch_forecasting/models/__init__.py @@ -19,6 +19,7 @@ TemporalFusionTransformer, ) from pytorch_forecasting.models.tide import TiDEModel +from pytorch_forecasting.models.timexer import TimeXer __all__ = [ "NBeats", @@ -37,4 +38,5 @@ "MultiEmbedding", "DecoderMLP", "TiDEModel", + "TimeXer", ] diff --git a/pytorch_forecasting/models/base/_base_model_v2.py b/pytorch_forecasting/models/base/_base_model_v2.py index c89f7bb10..eb77bea95 100644 --- a/pytorch_forecasting/models/base/_base_model_v2.py +++ b/pytorch_forecasting/models/base/_base_model_v2.py @@ -20,7 +20,7 @@ class BaseModel(LightningModule): def __init__( self, - loss: Metric, + loss: nn.Module, logging_metrics: Optional[List[nn.Module]] = None, optimizer: Optional[Union[Optimizer, str]] = "adam", optimizer_params: Optional[Dict] = None, @@ -310,7 +310,7 @@ class TslibBaseModel(BaseModel): Parameters ---------- - loss : Metric + loss : nn.Module Loss function to use for training. logging_metrics : Optional[List[nn.Module]], optional List of metrics to log during training, validation, and testing. @@ -328,7 +328,7 @@ class TslibBaseModel(BaseModel): def __init__( self, - loss: Metric, + loss: nn.Module, logging_metrics: Optional[List[nn.Module]] = None, optimizer: Optional[Union[Optimizer, str]] = "adam", optimizer_params: Optional[Dict] = None, @@ -367,8 +367,6 @@ def __init__( self.feature_names = metadata.get("feature_names", {}) - self._init_network() - def _init_network(self): """ Initialize the network architecture. diff --git a/pytorch_forecasting/models/timexer/__init__.py b/pytorch_forecasting/models/timexer/__init__.py index e69de29bb..a1df74dce 100644 --- a/pytorch_forecasting/models/timexer/__init__.py +++ b/pytorch_forecasting/models/timexer/__init__.py @@ -0,0 +1,5 @@ +"""TimeXer model.""" + +from pytorch_forecasting.models.timexer._timexer import TimeXer + +__all__ = ["TimeXer"] diff --git a/pytorch_forecasting/models/timexer/_timexer.py b/pytorch_forecasting/models/timexer/_timexer.py index e570608fa..fc9cbcaaf 100644 --- a/pytorch_forecasting/models/timexer/_timexer.py +++ b/pytorch_forecasting/models/timexer/_timexer.py @@ -6,6 +6,7 @@ ################################################################ # NOTE: This implementation of TimeXer derives from PR #1797. # # It is experimental and seeks to clarify design decisions. # +# IT IS STRICTLY A PART OF THE v2 design of PTF. # ################################################################ @@ -20,8 +21,7 @@ import torch.nn.functional as F from torch.optim import Optimizer -from pytorch_forecasting.data import TimeSeries -from pytorch_forecasting.metrics import MAE, MAPE, Metric, QuantileLoss +from pytorch_forecasting.metrics import MAE, MAPE, MultiHorizonMetric, QuantileLoss from pytorch_forecasting.metrics.base_metrics import MultiLoss from pytorch_forecasting.models.base._base_model_v2 import TslibBaseModel @@ -29,9 +29,7 @@ class TimeXer(TslibBaseModel): def __init__( self, - loss: Metric, - context_length: int, - prediction_length: int, + loss: nn.Module, features: str = "MS", enc_in: int = None, d_model: int = 512, @@ -41,6 +39,7 @@ def __init__( dropout: float = 0.1, patch_length: int = 24, factor: int = 5, + activation: str = "relu", endogenous_vars: Optional[List[str]] = None, exogenous_vars: Optional[List[str]] = None, logging_metrics: Optional[List[nn.Module]] = None, @@ -61,12 +60,22 @@ def __init__( metadata=metadata, ) - self.context_length = context_length - self.prediction_length = prediction_length + self.features = features + self.enc_in = enc_in + self.d_model = d_model + self.n_heads = n_heads + self.e_layers = e_layers + self.d_ff = d_ff + self.dropout = dropout + self.patch_length = patch_length + self.activation = activation + self.factor = factor self.endogenous_vars = endogenous_vars self.exogenous_vars = exogenous_vars self.save_hyperparameters(ignore=["loss", "logging_metrics", "metadata"]) + self._init_network() + def _init_network(self): """ Initialize the network for TimeXer's architecture. @@ -91,11 +100,15 @@ def _init_network(self): else: self.n_target_vars = 1 + # currently enc_in is set only to cont_dim since + # the data module doesn't fully support categorical + # variables in the context length and modele expects + # float values. self.enc_in = self.enc_in or self.cont_dim self.n_quantiles = None - if hasattr(self, "quantiles"): + if hasattr(self.loss, "quantiles"): self.n_quantiles = len(self.loss.quantiles) self.en_embedding = EnEmbedding( @@ -108,7 +121,6 @@ def _init_network(self): encoder_layers = [] - encoder_layers = [] for _ in range(self.e_layers): encoder_layers.append( EncoderLayer( @@ -177,21 +189,25 @@ def _forecast(self, x: Dict[str, torch.Tensor]) -> Dict[str, torch.Tensor]: "history_target", torch.zeros(batch_size, self.context_length, 0, device=self.device), ) # noqa: E501 + + if history_time_idx is not None and history_time_idx.dim() == 2: + # change [batch_size, time_steps] to [batch_size, time_steps, features] + history_time_idx = history_time_idx.unsqueeze(-1) + + # explicitly set endogenous and exogenous variables + endogenous_cont = history_target if self.endogenous_vars: endogenous_indices = [ self.cont_names.index(var) for var in self.endogenous_vars ] endogenous_cont = history_cont[..., endogenous_indices] - else: - endogenous_cont = history_target + exogenous_cont = history_cont if self.exogenous_vars: exogenous_indices = [ self.cont_names.index(var) for var in self.exogenous_vars ] exogenous_cont = history_cont[..., exogenous_indices] - else: - exogenous_cont = history_cont en_embed, n_vars = self.en_embedding(endogenous_cont) ex_embed = self.ex_embedding(exogenous_cont, history_time_idx) @@ -283,7 +299,7 @@ def forward(self, x: Dict[str, torch.Tensor]) -> Dict[str, torch.Tensor]: if prediction.size(2) != self.target_dim: prediction = prediction[:, :, : self.target_dim] - target_indices = range(len(self.target_dim)) + target_indices = range(self.target_dim) if self.n_quantiles is not None: if self.target_dim > 1: prediction = [prediction[..., i, :] for i in target_indices] From a896b3f697fa51f76941263a3f832992a3261bdf Mon Sep 17 00:00:00 2001 From: Pranav Bhat Date: Mon, 26 May 2025 01:53:45 +0530 Subject: [PATCH 072/139] clear cell outputs on trainer.fit() --- examples/tslib_v2_example.ipynb | 18524 +----------------------------- 1 file changed, 2 insertions(+), 18522 deletions(-) diff --git a/examples/tslib_v2_example.ipynb b/examples/tslib_v2_example.ipynb index 493b13ee8..570c07cb9 100644 --- a/examples/tslib_v2_example.ipynb +++ b/examples/tslib_v2_example.ipynb @@ -582,18530 +582,10 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": null, "id": "6e9117d2", "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "\n", - " | Name | Type | Params | Mode \n", - "----------------------------------------------------------------\n", - "0 | loss | MSELoss | 0 | train\n", - "1 | en_embedding | EnEmbedding | 320 | train\n", - "2 | ex_embedding | DataEmbedding_inverted | 2.0 K | train\n", - "3 | encoder | Encoder | 133 K | train\n", - "4 | head | FlattenHead | 513 | train\n", - "----------------------------------------------------------------\n", - "136 K Trainable params\n", - "0 Non-trainable params\n", - "136 K Total params\n", - "0.546 Total estimated model params size (MB)\n", - "57 Modules in train mode\n", - "0 Modules in eval mode\n" - ] - }, - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "93e5dd9e1fab49c4ab47276eb6f6421d", - "version_major": 2, - "version_minor": 0 - }, - "text/plain": [ - "Sanity Checking: | | 0/? [00:00 Date: Mon, 26 May 2025 11:46:42 +0530 Subject: [PATCH 073/139] remove unnecessary squeeze method and add prediction demo in example notebook --- examples/tslib_v2_example.ipynb | 983 ++++++++++++++++-- pytorch_forecasting/data/tslib_data_module.py | 8 +- 2 files changed, 926 insertions(+), 65 deletions(-) diff --git a/examples/tslib_v2_example.ipynb b/examples/tslib_v2_example.ipynb index 570c07cb9..3b7326c4b 100644 --- a/examples/tslib_v2_example.ipynb +++ b/examples/tslib_v2_example.ipynb @@ -98,7 +98,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 3, "id": "3a9cd738", "metadata": {}, "outputs": [], @@ -110,7 +110,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 4, "id": "a0058487", "metadata": {}, "outputs": [ @@ -164,61 +164,61 @@ "type": "integer" } ], - "ref": "11a96733-836f-490e-9788-80cd540ed87f", + "ref": "950ad36a-7d6e-448c-b398-040695124321", "rows": [ [ "0", "0", "0", - "-0.20094783251489232", - "0.2831472670549394", + "-0.09566033622807475", + "0.17294468110110825", "0", "1.0", - "0.8418973682486485", + "0.4623136444068322", "0" ], [ "1", "0", "1", - "0.2831472670549394", - "0.5287947236215006", + "0.17294468110110825", + "0.4857756236975668", "0", "0.9950041652780258", - "0.8418973682486485", + "0.4623136444068322", "0" ], [ "2", "0", "2", - "0.5287947236215006", - "0.6869941888949886", + "0.4857756236975668", + "0.6302539390886626", "0", "0.9800665778412416", - "0.8418973682486485", + "0.4623136444068322", "0" ], [ "3", "0", "3", - "0.6869941888949886", - "0.7114514740783304", + "0.6302539390886626", + "0.907009118627536", "0", "0.955336489125606", - "0.8418973682486485", + "0.4623136444068322", "0" ], [ "4", "0", "4", - "0.7114514740783304", - "0.8367522080227738", + "0.907009118627536", + "0.8142656823454242", "0", "0.9210609940028851", - "0.8418973682486485", + "0.4623136444068322", "0" ] ], @@ -261,55 +261,55 @@ " 0\n", " 0\n", " 0\n", - " -0.200948\n", - " 0.283147\n", + " -0.095660\n", + " 0.172945\n", " 0\n", " 1.000000\n", - " 0.841897\n", + " 0.462314\n", " 0\n", " \n", " \n", " 1\n", " 0\n", " 1\n", - " 0.283147\n", - " 0.528795\n", + " 0.172945\n", + " 0.485776\n", " 0\n", " 0.995004\n", - " 0.841897\n", + " 0.462314\n", " 0\n", " \n", " \n", " 2\n", " 0\n", " 2\n", - " 0.528795\n", - " 0.686994\n", + " 0.485776\n", + " 0.630254\n", " 0\n", " 0.980067\n", - " 0.841897\n", + " 0.462314\n", " 0\n", " \n", " \n", " 3\n", " 0\n", " 3\n", - " 0.686994\n", - " 0.711451\n", + " 0.630254\n", + " 0.907009\n", " 0\n", " 0.955336\n", - " 0.841897\n", + " 0.462314\n", " 0\n", " \n", " \n", " 4\n", " 0\n", " 4\n", - " 0.711451\n", - " 0.836752\n", + " 0.907009\n", + " 0.814266\n", " 0\n", " 0.921061\n", - " 0.841897\n", + " 0.462314\n", " 0\n", " \n", " \n", @@ -318,21 +318,21 @@ ], "text/plain": [ " series_id time_idx x y category future_known_feature \\\n", - "0 0 0 -0.200948 0.283147 0 1.000000 \n", - "1 0 1 0.283147 0.528795 0 0.995004 \n", - "2 0 2 0.528795 0.686994 0 0.980067 \n", - "3 0 3 0.686994 0.711451 0 0.955336 \n", - "4 0 4 0.711451 0.836752 0 0.921061 \n", + "0 0 0 -0.095660 0.172945 0 1.000000 \n", + "1 0 1 0.172945 0.485776 0 0.995004 \n", + "2 0 2 0.485776 0.630254 0 0.980067 \n", + "3 0 3 0.630254 0.907009 0 0.955336 \n", + "4 0 4 0.907009 0.814266 0 0.921061 \n", "\n", " static_feature static_feature_cat \n", - "0 0.841897 0 \n", - "1 0.841897 0 \n", - "2 0.841897 0 \n", - "3 0.841897 0 \n", - "4 0.841897 0 " + "0 0.462314 0 \n", + "1 0.462314 0 \n", + "2 0.462314 0 \n", + "3 0.462314 0 \n", + "4 0.462314 0 " ] }, - "execution_count": 5, + "execution_count": 4, "metadata": {}, "output_type": "execute_result" } @@ -365,7 +365,7 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 5, "id": "89a5adbe", "metadata": {}, "outputs": [ @@ -394,7 +394,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 6, "id": "5eae9035", "metadata": {}, "outputs": [ @@ -402,7 +402,7 @@ "name": "stderr", "output_type": "stream", "text": [ - "C:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\pytorch_forecasting\\data\\tslib_data_module.py:266: UserWarning: TslibDataModule is experimental and subject to change. The API is not stable and may change without prior warning.\n", + "C:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\pytorch_forecasting\\data\\tslib_data_module.py:264: UserWarning: TslibDataModule is experimental and subject to change. The API is not stable and may change without prior warning.\n", " warnings.warn(\n" ] } @@ -429,7 +429,7 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 7, "id": "76ebffc1", "metadata": {}, "outputs": [ @@ -439,7 +439,7 @@ "dict" ] }, - "execution_count": 8, + "execution_count": 7, "metadata": {}, "output_type": "execute_result" } @@ -450,7 +450,7 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 8, "id": "b1843233", "metadata": {}, "outputs": [ @@ -492,7 +492,7 @@ " 'features': 'MS'}" ] }, - "execution_count": 9, + "execution_count": 8, "metadata": {}, "output_type": "execute_result" } @@ -503,7 +503,7 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 9, "id": "12036e70", "metadata": {}, "outputs": [], @@ -515,7 +515,7 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 10, "id": "429b5f15", "metadata": {}, "outputs": [ @@ -553,7 +553,7 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 11, "id": "02605f9b", "metadata": {}, "outputs": [ @@ -582,27 +582,890 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 12, "id": "6e9117d2", "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "\n", + " | Name | Type | Params | Mode \n", + "----------------------------------------------------------------\n", + "0 | loss | MSELoss | 0 | train\n", + "1 | en_embedding | EnEmbedding | 320 | train\n", + "2 | ex_embedding | DataEmbedding_inverted | 2.0 K | train\n", + "3 | encoder | Encoder | 133 K | train\n", + "4 | head | FlattenHead | 513 | train\n", + "----------------------------------------------------------------\n", + "136 K Trainable params\n", + "0 Non-trainable params\n", + "136 K Total params\n", + "0.546 Total estimated model params size (MB)\n", + "57 Modules in train mode\n", + "0 Modules in eval mode\n" + ] + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "d3960728820d491b8705df3797c85c65", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "Sanity Checking: | | 0/? [00:00 Dict[str, Any]: x["target_scale"] = processed_data["target_scale"] y = processed_data["target"][future_indices] - if y.ndim == 1: - y = y.unsqueeze(0) return x, y @@ -440,13 +438,13 @@ def _preprocess_data(self, idx: torch.Tensor) -> List[Dict[str, Any]]: mask_timestep = torch.tensor(timestep <= cutoff_time, dtype=torch.bool) - if isinstance(torch, torch.Tensor): - target = target.float() + if isinstance(target, torch.Tensor): + target = target.detach().clone().float() else: target = torch.tensor(target, dtype=torch.float32) if isinstance(features, torch.Tensor): - features = features.float() + features = features.detach().clone().float() else: features = torch.tensor(features, dtype=torch.float32) From 3b072630dc3cdc6815a66d893102779eb1d1ac31 Mon Sep 17 00:00:00 2001 From: Pranav Bhat Date: Tue, 27 May 2025 14:57:12 +0530 Subject: [PATCH 074/139] restructure layers directory this change introduces sub-directories to provide a single module for a group of layers rather than dumping them in a single file with the same as the sub-directory --- pytorch_forecasting/layers/__init__.py | 8 +- .../layers/attention/__init__.py | 8 ++ .../attention_layer.py} | 61 ----------- .../layers/attention/full_attention.py | 71 ++++++++++++ pytorch_forecasting/layers/embeddings.py | 103 ------------------ .../layers/embeddings/__init__.py | 11 ++ .../layers/embeddings/data_embedding.py | 38 +++++++ .../layers/embeddings/en_embedding.py | 50 +++++++++ .../layers/embeddings/positional_embedding.py | 39 +++++++ .../layers/encoders/__init__.py | 8 ++ .../layers/encoders/encoder.py | 40 +++++++ .../encoder_layer.py} | 31 +----- pytorch_forecasting/layers/output/__init__.py | 7 ++ .../flatten_head.py} | 2 +- .../models/timexer/_timexer.py | 5 +- 15 files changed, 278 insertions(+), 204 deletions(-) create mode 100644 pytorch_forecasting/layers/attention/__init__.py rename pytorch_forecasting/layers/{attention.py => attention/attention_layer.py} (51%) create mode 100644 pytorch_forecasting/layers/attention/full_attention.py delete mode 100644 pytorch_forecasting/layers/embeddings.py create mode 100644 pytorch_forecasting/layers/embeddings/__init__.py create mode 100644 pytorch_forecasting/layers/embeddings/data_embedding.py create mode 100644 pytorch_forecasting/layers/embeddings/en_embedding.py create mode 100644 pytorch_forecasting/layers/embeddings/positional_embedding.py create mode 100644 pytorch_forecasting/layers/encoders/__init__.py create mode 100644 pytorch_forecasting/layers/encoders/encoder.py rename pytorch_forecasting/layers/{encoders.py => encoders/encoder_layer.py} (70%) create mode 100644 pytorch_forecasting/layers/output/__init__.py rename pytorch_forecasting/layers/{output_layers.py => output/flatten_head.py} (95%) diff --git a/pytorch_forecasting/layers/__init__.py b/pytorch_forecasting/layers/__init__.py index f9839595d..b430f8772 100644 --- a/pytorch_forecasting/layers/__init__.py +++ b/pytorch_forecasting/layers/__init__.py @@ -2,11 +2,7 @@ Architectural deep learning layers from `nn.Module`. """ -from pytorch_forecasting.layers.attention import ( - AttentionLayer, - FullAttention, - TriangularCausalMask, -) +from pytorch_forecasting.layers.attention import AttentionLayer, FullAttention from pytorch_forecasting.layers.embeddings import ( DataEmbedding_inverted, EnEmbedding, @@ -16,7 +12,7 @@ Encoder, EncoderLayer, ) -from pytorch_forecasting.layers.output_layers import ( +from pytorch_forecasting.layers.output.flatten_head import ( FlattenHead, ) diff --git a/pytorch_forecasting/layers/attention/__init__.py b/pytorch_forecasting/layers/attention/__init__.py new file mode 100644 index 000000000..94e9b8cc0 --- /dev/null +++ b/pytorch_forecasting/layers/attention/__init__.py @@ -0,0 +1,8 @@ +""" +Attention Layers for pytorch-forecasting models. +""" + +from pytorch_forecasting.layers.attention.attention_layer import AttentionLayer +from pytorch_forecasting.layers.attention.full_attention import FullAttention + +__all__ = ["AttentionLayer", "FullAttention"] diff --git a/pytorch_forecasting/layers/attention.py b/pytorch_forecasting/layers/attention/attention_layer.py similarity index 51% rename from pytorch_forecasting/layers/attention.py rename to pytorch_forecasting/layers/attention/attention_layer.py index 97d4d13e9..55b9851d1 100644 --- a/pytorch_forecasting/layers/attention.py +++ b/pytorch_forecasting/layers/attention/attention_layer.py @@ -10,67 +10,6 @@ import torch.nn.functional as F -class TriangularCausalMask: - """ - Triangular causal mask for attention mechanism. - """ - - def __init__(self, B, L, device="cpu"): - mask_shape = [B, 1, L, L] - with torch.no_grad(): - self._mask = torch.triu( - torch.ones(mask_shape, dtype=torch.bool), diagonal=1 - ).to(device) - - @property - def mask(self): - return self._mask - - -class FullAttention(nn.Module): - """ - Full attention mechanism with optional masking and dropout. - Args: - mask_flag (bool): Whether to apply masking. - factor (int): Factor for scaling the attention scores. - scale (float): Scaling factor for attention scores. - attention_dropout (float): Dropout rate for attention scores. - output_attention (bool): Whether to output attention weights.""" - - def __init__( - self, - mask_flag=True, - factor=5, - scale=None, - attention_dropout=0.1, - output_attention=False, - ): - super(FullAttention, self).__init__() - self.scale = scale - self.mask_flag = mask_flag - self.output_attention = output_attention - self.dropout = nn.Dropout(attention_dropout) - - def forward(self, queries, keys, values, attn_mask, tau=None, delta=None): - B, L, H, E = queries.shape - _, S, _, D = values.shape - scale = self.scale or 1.0 / sqrt(E) - - scores = torch.einsum("blhe,bshe->bhls", queries, keys) - - if self.mask_flag: - if attn_mask is None: - attn_mask = TriangularCausalMask(B, L, device=queries.device) - scores.masked_fill_(attn_mask.mask, -np.abs) - A = self.dropout(torch.softmax(scale * scores, dim=-1)) - V = torch.einsum("bhls,bshd->blhd", A, values) - - if self.output_attention: - return V.contiguous(), A - else: - return V.contiguous(), None - - class AttentionLayer(nn.Module): """ Attention layer that combines query, key, and value projections with an attention diff --git a/pytorch_forecasting/layers/attention/full_attention.py b/pytorch_forecasting/layers/attention/full_attention.py new file mode 100644 index 000000000..88a062330 --- /dev/null +++ b/pytorch_forecasting/layers/attention/full_attention.py @@ -0,0 +1,71 @@ +""" +Full Attention Layer. +""" + +from math import sqrt + +import numpy as np +import torch +import torch.nn as nn +import torch.nn.functional as F + + +class TriangularCausalMask: + """ + Triangular causal mask for attention mechanism. + """ + + def __init__(self, B, L, device="cpu"): + mask_shape = [B, 1, L, L] + with torch.no_grad(): + self._mask = torch.triu( + torch.ones(mask_shape, dtype=torch.bool), diagonal=1 + ).to(device) + + @property + def mask(self): + return self._mask + + +class FullAttention(nn.Module): + """ + Full attention mechanism with optional masking and dropout. + Args: + mask_flag (bool): Whether to apply masking. + factor (int): Factor for scaling the attention scores. + scale (float): Scaling factor for attention scores. + attention_dropout (float): Dropout rate for attention scores. + output_attention (bool): Whether to output attention weights.""" + + def __init__( + self, + mask_flag=True, + factor=5, + scale=None, + attention_dropout=0.1, + output_attention=False, + ): + super(FullAttention, self).__init__() + self.scale = scale + self.mask_flag = mask_flag + self.output_attention = output_attention + self.dropout = nn.Dropout(attention_dropout) + + def forward(self, queries, keys, values, attn_mask, tau=None, delta=None): + B, L, H, E = queries.shape + _, S, _, D = values.shape + scale = self.scale or 1.0 / sqrt(E) + + scores = torch.einsum("blhe,bshe->bhls", queries, keys) + + if self.mask_flag: + if attn_mask is None: + attn_mask = TriangularCausalMask(B, L, device=queries.device) + scores.masked_fill_(attn_mask.mask, -np.abs) + A = self.dropout(torch.softmax(scale * scores, dim=-1)) + V = torch.einsum("bhls,bshd->blhd", A, values) + + if self.output_attention: + return V.contiguous(), A + else: + return V.contiguous(), None diff --git a/pytorch_forecasting/layers/embeddings.py b/pytorch_forecasting/layers/embeddings.py deleted file mode 100644 index e5b98c45d..000000000 --- a/pytorch_forecasting/layers/embeddings.py +++ /dev/null @@ -1,103 +0,0 @@ -""" -Implementation of embedding layers from `nn.Module`. -""" - -import math -from math import sqrt - -import numpy as np -import torch -import torch.nn as nn -import torch.nn.functional as F - - -class DataEmbedding_inverted(nn.Module): - """ - Data embedding module for time series data. - Args: - c_in (int): Number of input features. - d_model (int): Dimension of the model. - embed_type (str): Type of embedding to use. Defaults to "fixed". - freq (str): Frequency of the time series data. Defaults to "h". - dropout (float): Dropout rate. Defaults to 0.1. - """ - - def __init__(self, c_in, d_model, dropout=0.1): - super(DataEmbedding_inverted, self).__init__() - self.value_embedding = nn.Linear(c_in, d_model) - self.dropout = nn.Dropout(p=dropout) - - def forward(self, x, x_mark): - x = x.permute(0, 2, 1) - # x: [Batch Variate Time] - if x_mark is None: - x = self.value_embedding(x) - else: - x = self.value_embedding(torch.cat([x, x_mark.permute(0, 2, 1)], 1)) - # x: [Batch Variate d_model] - return self.dropout(x) - - -class PositionalEmbedding(nn.Module): - """ - Positional embedding module for time series data. - Args: - d_model (int): Dimension of the model. - max_len (int): Maximum length of the input sequence. Defaults to 5000.""" - - def __init__(self, d_model, max_len=5000): - super(PositionalEmbedding, self).__init__() - # Compute the positional encodings once in log space. - pe = torch.zeros(max_len, d_model).float() - pe.require_grad = False - - position = torch.arange(0, max_len).float().unsqueeze(1) - div_term = ( - torch.arange(0, d_model, 2).float() * -(math.log(10000.0) / d_model) - ).exp() - - pe[:, 0::2] = torch.sin(position * div_term) - pe[:, 1::2] = torch.cos(position * div_term) - - pe = pe.unsqueeze(0) - self.register_buffer("pe", pe) - - def forward(self, x): - return self.pe[:, : x.size(1)] - - -class EnEmbedding(nn.Module): - """ - Encoder embedding module for time series data. Handles endogenous feature - embeddings in this case. - Args: - n_vars (int): Number of input features. - d_model (int): Dimension of the model. - patch_len (int): Length of the patches. - dropout (float): Dropout rate. Defaults to 0.1. - """ - - def __init__(self, n_vars, d_model, patch_len, dropout): - super(EnEmbedding, self).__init__() - - self.patch_len = patch_len - - self.value_embedding = nn.Linear(patch_len, d_model, bias=False) - self.glb_token = nn.Parameter(torch.randn(1, n_vars, 1, d_model)) - self.position_embedding = PositionalEmbedding(d_model) - - self.dropout = nn.Dropout(dropout) - - def forward(self, x): - x = x.permute(0, 2, 1) - n_vars = x.shape[1] - glb = self.glb_token.repeat((x.shape[0], 1, 1, 1)) - - x = x.unfold(dimension=-1, size=self.patch_len, step=self.patch_len) - x = torch.reshape(x, (x.shape[0] * x.shape[1], x.shape[2], x.shape[3])) - # Input encoding - x = self.value_embedding(x) + self.position_embedding(x) - x = torch.reshape(x, (-1, n_vars, x.shape[-2], x.shape[-1])) - x = torch.cat([x, glb], dim=2) - x = torch.reshape(x, (x.shape[0] * x.shape[1], x.shape[2], x.shape[3])) - return self.dropout(x), n_vars diff --git a/pytorch_forecasting/layers/embeddings/__init__.py b/pytorch_forecasting/layers/embeddings/__init__.py new file mode 100644 index 000000000..d5001fcf0 --- /dev/null +++ b/pytorch_forecasting/layers/embeddings/__init__.py @@ -0,0 +1,11 @@ +""" +Implementation of embedding layers for PTF models imported from `nn.Modules` +""" + +from pytorch_forecasting.layers.embeddings.data_embedding import DataEmbedding_inverted +from pytorch_forecasting.layers.embeddings.en_embedding import EnEmbedding +from pytorch_forecasting.layers.embeddings.positional_embedding import ( + PositionalEmbedding, +) + +__all__ = ["PositionalEmbedding", "DataEmbedding_inverted", "EnEmbedding"] diff --git a/pytorch_forecasting/layers/embeddings/data_embedding.py b/pytorch_forecasting/layers/embeddings/data_embedding.py new file mode 100644 index 000000000..0f590a4c4 --- /dev/null +++ b/pytorch_forecasting/layers/embeddings/data_embedding.py @@ -0,0 +1,38 @@ +""" +Data embedding layer for exogenous variables. +""" + +import math +from math import sqrt + +import numpy as np +import torch +import torch.nn as nn +import torch.nn.functional as F + + +class DataEmbedding_inverted(nn.Module): + """ + Data embedding module for time series data. + Args: + c_in (int): Number of input features. + d_model (int): Dimension of the model. + embed_type (str): Type of embedding to use. Defaults to "fixed". + freq (str): Frequency of the time series data. Defaults to "h". + dropout (float): Dropout rate. Defaults to 0.1. + """ + + def __init__(self, c_in, d_model, dropout=0.1): + super(DataEmbedding_inverted, self).__init__() + self.value_embedding = nn.Linear(c_in, d_model) + self.dropout = nn.Dropout(p=dropout) + + def forward(self, x, x_mark): + x = x.permute(0, 2, 1) + # x: [Batch Variate Time] + if x_mark is None: + x = self.value_embedding(x) + else: + x = self.value_embedding(torch.cat([x, x_mark.permute(0, 2, 1)], 1)) + # x: [Batch Variate d_model] + return self.dropout(x) diff --git a/pytorch_forecasting/layers/embeddings/en_embedding.py b/pytorch_forecasting/layers/embeddings/en_embedding.py new file mode 100644 index 000000000..82a5194d1 --- /dev/null +++ b/pytorch_forecasting/layers/embeddings/en_embedding.py @@ -0,0 +1,50 @@ +""" +Implementation of endogenous embedding layers from `nn.Module`. +""" + +import math +from math import sqrt + +import numpy as np +import torch +import torch.nn as nn +import torch.nn.functional as F + +from pytorch_forecasting.layers.embeddings import PositionalEmbedding + + +class EnEmbedding(nn.Module): + """ + Encoder embedding module for time series data. Handles endogenous feature + embeddings in this case. + Args: + n_vars (int): Number of input features. + d_model (int): Dimension of the model. + patch_len (int): Length of the patches. + dropout (float): Dropout rate. Defaults to 0.1. + """ + + def __init__(self, n_vars, d_model, patch_len, dropout): + super(EnEmbedding, self).__init__() + + self.patch_len = patch_len + + self.value_embedding = nn.Linear(patch_len, d_model, bias=False) + self.glb_token = nn.Parameter(torch.randn(1, n_vars, 1, d_model)) + self.position_embedding = PositionalEmbedding(d_model) + + self.dropout = nn.Dropout(dropout) + + def forward(self, x): + x = x.permute(0, 2, 1) + n_vars = x.shape[1] + glb = self.glb_token.repeat((x.shape[0], 1, 1, 1)) + + x = x.unfold(dimension=-1, size=self.patch_len, step=self.patch_len) + x = torch.reshape(x, (x.shape[0] * x.shape[1], x.shape[2], x.shape[3])) + # Input encoding + x = self.value_embedding(x) + self.position_embedding(x) + x = torch.reshape(x, (-1, n_vars, x.shape[-2], x.shape[-1])) + x = torch.cat([x, glb], dim=2) + x = torch.reshape(x, (x.shape[0] * x.shape[1], x.shape[2], x.shape[3])) + return self.dropout(x), n_vars diff --git a/pytorch_forecasting/layers/embeddings/positional_embedding.py b/pytorch_forecasting/layers/embeddings/positional_embedding.py new file mode 100644 index 000000000..75c348bf6 --- /dev/null +++ b/pytorch_forecasting/layers/embeddings/positional_embedding.py @@ -0,0 +1,39 @@ +""" +Positional Embedding Layer for PTF. +""" + +import math +from math import sqrt + +import numpy as np +import torch +import torch.nn as nn +import torch.nn.functional as F + + +class PositionalEmbedding(nn.Module): + """ + Positional embedding module for time series data. + Args: + d_model (int): Dimension of the model. + max_len (int): Maximum length of the input sequence. Defaults to 5000.""" + + def __init__(self, d_model, max_len=5000): + super(PositionalEmbedding, self).__init__() + # Compute the positional encodings once in log space. + pe = torch.zeros(max_len, d_model).float() + pe.require_grad = False + + position = torch.arange(0, max_len).float().unsqueeze(1) + div_term = ( + torch.arange(0, d_model, 2).float() * -(math.log(10000.0) / d_model) + ).exp() + + pe[:, 0::2] = torch.sin(position * div_term) + pe[:, 1::2] = torch.cos(position * div_term) + + pe = pe.unsqueeze(0) + self.register_buffer("pe", pe) + + def forward(self, x): + return self.pe[:, : x.size(1)] diff --git a/pytorch_forecasting/layers/encoders/__init__.py b/pytorch_forecasting/layers/encoders/__init__.py new file mode 100644 index 000000000..6fc81b3da --- /dev/null +++ b/pytorch_forecasting/layers/encoders/__init__.py @@ -0,0 +1,8 @@ +""" +Encoder layers for neural network models. +""" + +from .encoder import Encoder +from .encoder_layer import EncoderLayer + +__all__ = ["Encoder", "EncoderLayer"] diff --git a/pytorch_forecasting/layers/encoders/encoder.py b/pytorch_forecasting/layers/encoders/encoder.py new file mode 100644 index 000000000..5b53d933b --- /dev/null +++ b/pytorch_forecasting/layers/encoders/encoder.py @@ -0,0 +1,40 @@ +""" +Implementation of encoder layers from `nn.Module`. +""" + +import math +from math import sqrt + +import numpy as np +import torch +import torch.nn as nn +import torch.nn.functional as F + + +class Encoder(nn.Module): + """ + Encoder module for the TimeXer model. + Args: + layers (list): List of encoder layers. + norm_layer (nn.Module, optional): Normalization layer. Defaults to None. + projection (nn.Module, optional): Projection layer. Defaults to None. + """ + + def __init__(self, layers, norm_layer=None, projection=None): + super(Encoder, self).__init__() + self.layers = nn.ModuleList(layers) + self.norm = norm_layer + self.projection = projection + + def forward(self, x, cross, x_mask=None, cross_mask=None, tau=None, delta=None): + for layer in self.layers: + x = layer( + x, cross, x_mask=x_mask, cross_mask=cross_mask, tau=tau, delta=delta + ) + + if self.norm is not None: + x = self.norm(x) + + if self.projection is not None: + x = self.projection(x) + return x diff --git a/pytorch_forecasting/layers/encoders.py b/pytorch_forecasting/layers/encoders/encoder_layer.py similarity index 70% rename from pytorch_forecasting/layers/encoders.py rename to pytorch_forecasting/layers/encoders/encoder_layer.py index 26657ebf7..ed3a95df2 100644 --- a/pytorch_forecasting/layers/encoders.py +++ b/pytorch_forecasting/layers/encoders/encoder_layer.py @@ -1,5 +1,5 @@ """ -Implementation of encoder layers from `nn.Module`. +Implementation of EncoderLayer for encoder-decoder architectures from `nn.Module`. """ import math @@ -11,35 +11,6 @@ import torch.nn.functional as F -class Encoder(nn.Module): - """ - Encoder module for the TimeXer model. - Args: - layers (list): List of encoder layers. - norm_layer (nn.Module, optional): Normalization layer. Defaults to None. - projection (nn.Module, optional): Projection layer. Defaults to None. - """ - - def __init__(self, layers, norm_layer=None, projection=None): - super(Encoder, self).__init__() - self.layers = nn.ModuleList(layers) - self.norm = norm_layer - self.projection = projection - - def forward(self, x, cross, x_mask=None, cross_mask=None, tau=None, delta=None): - for layer in self.layers: - x = layer( - x, cross, x_mask=x_mask, cross_mask=cross_mask, tau=tau, delta=delta - ) - - if self.norm is not None: - x = self.norm(x) - - if self.projection is not None: - x = self.projection(x) - return x - - class EncoderLayer(nn.Module): """ Encoder layer for the TimeXer model. diff --git a/pytorch_forecasting/layers/output/__init__.py b/pytorch_forecasting/layers/output/__init__.py new file mode 100644 index 000000000..019cc93fb --- /dev/null +++ b/pytorch_forecasting/layers/output/__init__.py @@ -0,0 +1,7 @@ +""" +Implementation of output layers for PyTorch Forecasting. +""" + +from .flatten_head import FlattenHead + +__all__ = ["FlattenHead"] diff --git a/pytorch_forecasting/layers/output_layers.py b/pytorch_forecasting/layers/output/flatten_head.py similarity index 95% rename from pytorch_forecasting/layers/output_layers.py rename to pytorch_forecasting/layers/output/flatten_head.py index 786d3d152..71823b162 100644 --- a/pytorch_forecasting/layers/output_layers.py +++ b/pytorch_forecasting/layers/output/flatten_head.py @@ -1,5 +1,5 @@ """ -Implementation of output layers from `nn.Module`. +Implementation of output layers from `nn.Module` for TimeXer model. """ import math diff --git a/pytorch_forecasting/models/timexer/_timexer.py b/pytorch_forecasting/models/timexer/_timexer.py index fc9cbcaaf..99f72c633 100644 --- a/pytorch_forecasting/models/timexer/_timexer.py +++ b/pytorch_forecasting/models/timexer/_timexer.py @@ -81,17 +81,16 @@ def _init_network(self): Initialize the network for TimeXer's architecture. """ - from pytorch_forecasting.layers.attention import ( + from pytorch_forecasting.layers.attention.attention_layer import ( AttentionLayer, FullAttention, ) from pytorch_forecasting.layers.embeddings import ( DataEmbedding_inverted, EnEmbedding, - PositionalEmbedding, ) from pytorch_forecasting.layers.encoders import Encoder, EncoderLayer - from pytorch_forecasting.layers.output_layers import FlattenHead + from pytorch_forecasting.layers.output.flatten_head import FlattenHead self.patch_num = max(1, int(self.context_length // self.patch_length)) From 010298e6e626e80821ac678c566dcce3baf0bf0e Mon Sep 17 00:00:00 2001 From: Pranav Bhat Date: Tue, 27 May 2025 21:58:00 +0530 Subject: [PATCH 075/139] delete empty modules.py --- pytorch_forecasting/models/modules.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 pytorch_forecasting/models/modules.py diff --git a/pytorch_forecasting/models/modules.py b/pytorch_forecasting/models/modules.py deleted file mode 100644 index e69de29bb..000000000 From d0aa444ef49d60f7fc6dc25e2d460c6f9c953462 Mon Sep 17 00:00:00 2001 From: Pranav Bhat Date: Tue, 27 May 2025 21:59:41 +0530 Subject: [PATCH 076/139] add warning and move tslib base model to a new file --- .../models/base/_base_model_v2.py | 172 ---------------- .../models/base/_tslib_base_model_v2.py | 188 ++++++++++++++++++ 2 files changed, 188 insertions(+), 172 deletions(-) create mode 100644 pytorch_forecasting/models/base/_tslib_base_model_v2.py diff --git a/pytorch_forecasting/models/base/_base_model_v2.py b/pytorch_forecasting/models/base/_base_model_v2.py index eb77bea95..542bbfae5 100644 --- a/pytorch_forecasting/models/base/_base_model_v2.py +++ b/pytorch_forecasting/models/base/_base_model_v2.py @@ -296,175 +296,3 @@ def log_metrics( prog_bar=True, logger=True, ) - - -""" -Experimental implementation of a base class for `tslib` models. -NOTE: This PR is stacked on the PR #1836(PranavBhatP) and PR #1812(phoeenniixx). -""" - - -class TslibBaseModel(BaseModel): - """ - Base class for `tslib` models. - - Parameters - ---------- - loss : nn.Module - Loss function to use for training. - logging_metrics : Optional[List[nn.Module]], optional - List of metrics to log during training, validation, and testing. - optimizer : Optional[Union[Optimizer, str]], optional - Optimizer to use for training. - optimizer_params : Optional[Dict], optional - Parameters for the optimizer. - lr_scheduler : Optional[str], optional - Learning rate scheduler to use. - lr_scheduler_params : Optional[Dict], optional - Parameters for the learning rate scheduler. - metadata : Optional[Dict], default=None - Metadata for the model from TslibDataModule. - """ - - def __init__( - self, - loss: nn.Module, - logging_metrics: Optional[List[nn.Module]] = None, - optimizer: Optional[Union[Optimizer, str]] = "adam", - optimizer_params: Optional[Dict] = None, - lr_scheduler: Optional[str] = None, - lr_scheduler_params: Optional[Dict] = None, - metadata: Optional[Dict] = None, - ): - super().__init__( - loss=loss, - logging_metrics=logging_metrics, - optimizer=optimizer, - optimizer_params=optimizer_params, - lr_scheduler=lr_scheduler, - lr_scheduler_params=lr_scheduler_params, - ) - self.save_hyperparameters(ignore=["loss", "logging_metrics", "metadata"]) - self.metadata = metadata or {} - self.model_name = self.__class__.__name__ - - self.context_length = self.metadata.get("context_length", 0) - self.prediction_length = self.metadata.get("prediction_length", 0) - - feature_indices = metadata.get("feature_indices", {}) - self.cont_indices = feature_indices.get("continuous", []) - self.cat_indices = feature_indices.get("categorical", []) - self.known_indices = feature_indices.get("known", []) - self.unknown_indices = feature_indices.get("unknown", []) - self.target_indices = feature_indices.get("target", []) - - feature_dims = metadata.get("n_features", {}) - self.cont_dim = feature_dims.get("continuous", 0) - self.cat_dim = feature_dims.get("categorical", 0) - self.static_cat_dim = feature_dims.get("static_categorical", 0) - self.static_cont_dim = feature_dims.get("static_continuous", 0) - self.target_dim = feature_dims.get("target", 1) - - self.feature_names = metadata.get("feature_names", {}) - - def _init_network(self): - """ - Initialize the network architecture. - This method should be implemented in subclasses to define the specific layers - and sub_modules of the model. - """ - raise NotImplementedError("Subclasses must implement _init_network method.") - - def forward(self, x: Dict[str, torch.Tensor]) -> Dict[str, torch.Tensor]: - """ - Forward pass of the model. - - Parameters - ---------- - x: Dict[str, torch.Tensor] - Dictionary containing input tensors. - - Returns - ------- - Dict[str, torch.Tensor] - Dictionary containing output tensors. These can include - - predictions: - Prediction_output of shape (batch_size, prediction_length, target_dim) - - attention_weights: Optionally, output attention weights - """ - - raise NotImplementedError("Subclasses must implement forward method.") - - def predict_step( - self, - batch: Tuple[Dict[str, torch.Tensor]], - batch_idx: int, - dataloader_idx: int = 0, - ) -> torch.Tensor: - """ - Prediction step for the model. - - Parameters - ---------- - batch : Tuple[Dict[str, torch.Tensor]] - Batch of data containing input tensors. - batch_idx : int - Index of the batch. - dataloader_idx : int - Index of the dataloader. - - Returns - ------- - torch.Tensor - Predicted output tensor. - """ - x, _ = batch - y_hat = self(x) - - if "target" in x: - y_hat["target"] = x["target"] - - return y_hat - - def transform_output( - self, - y_hat: Union[ - torch.Tensor, List[torch.Tensor] - ], # evidenced from TimeXer implementation - in PR #1797 # noqa: E501 - target_scale: Optional[Dict[str, torch.Tensor]], - ) -> Union[torch.Tensor, List[torch.Tensor]]: - """ - Transform the output of the model to the original scale. - - Parameters - ---------- - y_hat : Union[torch.Tensor, List[torch.Tensor]] - Dictionary containing the model output. - target_scale : Optional[Dict[str, torch.Tensor]] - Dictionary containing the target scale for inverse transformation. - - Returns - ------- - Union[torch.Tensor, List[torch.Tensor]] - Dictionary containing the transformed output. - - Notes - ----- - WARNING! : This is a temporary implementation and is meant to be replaced with - a more robust scaling and normalization module for v2 of PTF. - """ - - scale = None - center = None - - if "scale" in target_scale and "center" in target_scale: - scale = target_scale["scale"] - center = target_scale["center"] - else: - raise ValueError("Cannot transform output without scale and center.") - - while scale.dim() < y_hat.dim(): - scale = scale.unsqueeze(0) - center = center.unsqueeze(0) - - return y_hat * scale + center diff --git a/pytorch_forecasting/models/base/_tslib_base_model_v2.py b/pytorch_forecasting/models/base/_tslib_base_model_v2.py new file mode 100644 index 000000000..002cd5c47 --- /dev/null +++ b/pytorch_forecasting/models/base/_tslib_base_model_v2.py @@ -0,0 +1,188 @@ +""" +Experimental implementation of a base class for `tslib` models. +NOTE: This PR is stacked on the PR #1812(phoeenniixx). +""" + +from typing import Dict, List, Optional, Tuple, Union +from warnings import warn + +import torch +import torch.nn as nn +from torch.optim import Optimizer + +from pytorch_forecasting.models.base import BaseModel + + +class TslibBaseModel(BaseModel): + """ + Base class for `tslib` models. + + Parameters + ---------- + loss : nn.Module + Loss function to use for training. + logging_metrics : Optional[List[nn.Module]], optional + List of metrics to log during training, validation, and testing. + optimizer : Optional[Union[Optimizer, str]], optional + Optimizer to use for training. + optimizer_params : Optional[Dict], optional + Parameters for the optimizer. + lr_scheduler : Optional[str], optional + Learning rate scheduler to use. + lr_scheduler_params : Optional[Dict], optional + Parameters for the learning rate scheduler. + metadata : Optional[Dict], default=None + Metadata for the model from TslibDataModule. + """ + + def __init__( + self, + loss: nn.Module, + logging_metrics: Optional[List[nn.Module]] = None, + optimizer: Optional[Union[Optimizer, str]] = "adam", + optimizer_params: Optional[Dict] = None, + lr_scheduler: Optional[str] = None, + lr_scheduler_params: Optional[Dict] = None, + metadata: Optional[Dict] = None, + ): + super().__init__( + loss=loss, + logging_metrics=logging_metrics, + optimizer=optimizer, + optimizer_params=optimizer_params, + lr_scheduler=lr_scheduler, + lr_scheduler_params=lr_scheduler_params, + ) + self.save_hyperparameters(ignore=["loss", "logging_metrics", "metadata"]) + self.metadata = metadata or {} + self.model_name = self.__class__.__name__ + + warn( + f"The Model '{self.model_name}' is part of an experimental implementation" + "of the pytorch-forecasting model layer for Time Series Library, scheduled" + "for release with v2.0.0. The API is not stable" + "and may change without prior warning. This class is intended for beta" + "testing, not for stable production use.", + UserWarning, + ) + + self.context_length = self.metadata.get("context_length", 0) + self.prediction_length = self.metadata.get("prediction_length", 0) + + feature_indices = metadata.get("feature_indices", {}) + self.cont_indices = feature_indices.get("continuous", []) + self.cat_indices = feature_indices.get("categorical", []) + self.known_indices = feature_indices.get("known", []) + self.unknown_indices = feature_indices.get("unknown", []) + self.target_indices = feature_indices.get("target", []) + + feature_dims = metadata.get("n_features", {}) + self.cont_dim = feature_dims.get("continuous", 0) + self.cat_dim = feature_dims.get("categorical", 0) + self.static_cat_dim = feature_dims.get("static_categorical", 0) + self.static_cont_dim = feature_dims.get("static_continuous", 0) + self.target_dim = feature_dims.get("target", 1) + + self.feature_names = metadata.get("feature_names", {}) + + def _init_network(self): + """ + Initialize the network architecture. + This method should be implemented in subclasses to define the specific layers + and sub_modules of the model. + """ + raise NotImplementedError("Subclasses must implement _init_network method.") + + def forward(self, x: Dict[str, torch.Tensor]) -> Dict[str, torch.Tensor]: + """ + Forward pass of the model. + + Parameters + ---------- + x: Dict[str, torch.Tensor] + Dictionary containing input tensors. + + Returns + ------- + Dict[str, torch.Tensor] + Dictionary containing output tensors. These can include + - predictions: + Prediction_output of shape (batch_size, prediction_length, target_dim) + - attention_weights: Optionally, output attention weights + """ + + raise NotImplementedError("Subclasses must implement forward method.") + + def predict_step( + self, + batch: Tuple[Dict[str, torch.Tensor]], + batch_idx: int, + dataloader_idx: int = 0, + ) -> torch.Tensor: + """ + Prediction step for the model. + + Parameters + ---------- + batch : Tuple[Dict[str, torch.Tensor]] + Batch of data containing input tensors. + batch_idx : int + Index of the batch. + dataloader_idx : int + Index of the dataloader. + + Returns + ------- + torch.Tensor + Predicted output tensor. + """ + x, _ = batch + y_hat = self(x) + + if "target" in x: + y_hat["target"] = x["target"] + + return y_hat + + def transform_output( + self, + y_hat: Union[ + torch.Tensor, List[torch.Tensor] + ], # evidenced from TimeXer implementation - in PR #1797 # noqa: E501 + target_scale: Optional[Dict[str, torch.Tensor]], + ) -> Union[torch.Tensor, List[torch.Tensor]]: + """ + Transform the output of the model to the original scale. + + Parameters + ---------- + y_hat : Union[torch.Tensor, List[torch.Tensor]] + Dictionary containing the model output. + target_scale : Optional[Dict[str, torch.Tensor]] + Dictionary containing the target scale for inverse transformation. + + Returns + ------- + Union[torch.Tensor, List[torch.Tensor]] + Dictionary containing the transformed output. + + Notes + ----- + WARNING! : This is a temporary implementation and is meant to be replaced with + a more robust scaling and normalization module for v2 of PTF. + """ + + scale = None + center = None + + if "scale" in target_scale and "center" in target_scale: + scale = target_scale["scale"] + center = target_scale["center"] + else: + raise ValueError("Cannot transform output without scale and center.") + + while scale.dim() < y_hat.dim(): + scale = scale.unsqueeze(0) + center = center.unsqueeze(0) + + return y_hat * scale + center From fef4113c9e495ac8c186a9f2c41ee2190205c0a4 Mon Sep 17 00:00:00 2001 From: Pranav Bhat Date: Tue, 27 May 2025 22:09:43 +0530 Subject: [PATCH 077/139] fix wrong import statement in _timexer.py --- pytorch_forecasting/models/timexer/_timexer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pytorch_forecasting/models/timexer/_timexer.py b/pytorch_forecasting/models/timexer/_timexer.py index 99f72c633..a26f9b855 100644 --- a/pytorch_forecasting/models/timexer/_timexer.py +++ b/pytorch_forecasting/models/timexer/_timexer.py @@ -23,7 +23,7 @@ from pytorch_forecasting.metrics import MAE, MAPE, MultiHorizonMetric, QuantileLoss from pytorch_forecasting.metrics.base_metrics import MultiLoss -from pytorch_forecasting.models.base._base_model_v2 import TslibBaseModel +from pytorch_forecasting.models.base._tslib_base_model_v2 import TslibBaseModel class TimeXer(TslibBaseModel): From 1d478d5b13e68efe1b75f2e6bc17b2500bceaacb Mon Sep 17 00:00:00 2001 From: Aryan Saini Date: Wed, 28 May 2025 01:05:19 +0530 Subject: [PATCH 078/139] remove refactored TFT --- .../temporal_fusion_transformer/_tft_ver2.py | 1130 ----------------- .../tft_v2_metadata.py | 46 - .../tests/test_all_estimators.py | 15 + 3 files changed, 15 insertions(+), 1176 deletions(-) delete mode 100644 pytorch_forecasting/models/temporal_fusion_transformer/_tft_ver2.py diff --git a/pytorch_forecasting/models/temporal_fusion_transformer/_tft_ver2.py b/pytorch_forecasting/models/temporal_fusion_transformer/_tft_ver2.py deleted file mode 100644 index cff63385c..000000000 --- a/pytorch_forecasting/models/temporal_fusion_transformer/_tft_ver2.py +++ /dev/null @@ -1,1130 +0,0 @@ -""" -The temporal fusion transformer is a powerful predictive model for forecasting timeseries -""" # noqa: E501 - -from copy import copy -from typing import Any, Dict, List, Optional, Tuple, Union - -import numpy as np -import torch -from torch import nn -from torchmetrics import Metric as LightningMetric - -from pytorch_forecasting.data import TimeSeriesDataSet -from pytorch_forecasting.metrics import ( - MAE, - MAPE, - RMSE, - SMAPE, - MultiHorizonMetric, - QuantileLoss, -) -from pytorch_forecasting.models.base import BaseModelWithCovariates -from pytorch_forecasting.models.nn import LSTM, MultiEmbedding -from pytorch_forecasting.models.temporal_fusion_transformer.sub_modules import ( - AddNorm, - GateAddNorm, - GatedLinearUnit, - GatedResidualNetwork, - InterpretableMultiHeadAttention, - VariableSelectionNetwork, -) -from pytorch_forecasting.utils import ( - create_mask, - detach, - integer_histogram, - masked_op, - padded_stack, - to_list, -) -from pytorch_forecasting.utils._dependencies import _check_matplotlib - - -class TemporalFusionTransformer(BaseModelWithCovariates): - def __init__( - self, - metadata: Dict[str, Any], - hidden_size: int = 16, - lstm_layers: int = 1, - dropout: float = 0.1, - output_size: Union[int, List[int]] = None, - loss: MultiHorizonMetric = None, - attention_head_size: int = 4, - categorical_groups: Optional[Union[Dict, List[str]]] = None, - hidden_continuous_size: int = 8, - hidden_continuous_sizes: Optional[Dict[str, int]] = None, - embedding_sizes: Optional[Dict[str, Tuple[int, int]]] = None, - embedding_paddings: Optional[List[str]] = None, - embedding_labels: Optional[Dict[str, np.ndarray]] = None, - learning_rate: float = 1e-3, - log_interval: Union[int, float] = -1, - log_val_interval: Union[int, float] = None, - log_gradient_flow: bool = False, - reduce_on_plateau_patience: int = 1000, - monotone_constaints: Optional[Dict[str, int]] = None, - share_single_variable_networks: bool = False, - causal_attention: bool = True, - logging_metrics: nn.ModuleList = None, - **kwargs, - ): - """ - Temporal Fusion Transformer for forecasting timeseries - use its :py:meth:`~from_dataset` method if possible. - - Implementation of the article - `Temporal Fusion Transformers for Interpretable Multi-horizon Time Series - Forecasting `_. The network outperforms DeepAR by Amazon by 36-69% - in benchmarks. - - Enhancements compared to the original implementation (apart from capabilities added through base model - such as monotone constraints): - - * static variables can be continuous - * multiple categorical variables can be summarized with an EmbeddingBag - * variable encoder and decoder length by sample - * categorical embeddings are not transformed by variable selection network (because it is a redundant operation) - * variable dimension in variable selection network are scaled up via linear interpolation to reduce - number of parameters - * non-linear variable processing in variable selection network can be shared among decoder and encoder - (not shared by default) - - Tune its hyperparameters with - :py:func:`~pytorch_forecasting.models.temporal_fusion_transformer.tuning.optimize_hyperparameters`. - - Args: - - hidden_size: hidden size of network which is its main hyperparameter and can range from 8 to 512 - lstm_layers: number of LSTM layers (2 is mostly optimal) - dropout: dropout rate - output_size: number of outputs (e.g. number of quantiles for QuantileLoss and one target or list - of output sizes). - loss: loss function taking prediction and targets - attention_head_size: number of attention heads (4 is a good default) - max_encoder_length: length to encode (can be far longer than the decoder length but does not have to be) - static_categoricals: names of static categorical variables - static_reals: names of static continuous variables - time_varying_categoricals_encoder: names of categorical variables for encoder - time_varying_categoricals_decoder: names of categorical variables for decoder - time_varying_reals_encoder: names of continuous variables for encoder - time_varying_reals_decoder: names of continuous variables for decoder - categorical_groups: dictionary where values - are list of categorical variables that are forming together a new categorical - variable which is the key in the dictionary - x_reals: order of continuous variables in tensor passed to forward function - x_categoricals: order of categorical variables in tensor passed to forward function - hidden_continuous_size: default for hidden size for processing continous variables (similar to categorical - embedding size) - hidden_continuous_sizes: dictionary mapping continuous input indices to sizes for variable selection - (fallback to hidden_continuous_size if index is not in dictionary) - embedding_sizes: dictionary mapping (string) indices to tuple of number of categorical classes and - embedding size - embedding_paddings: list of indices for embeddings which transform the zero's embedding to a zero vector - embedding_labels: dictionary mapping (string) indices to list of categorical labels - learning_rate: learning rate - log_interval: log predictions every x batches, do not log if 0 or less, log interpretation if > 0. If < 1.0 - , will log multiple entries per batch. Defaults to -1. - log_val_interval: frequency with which to log validation set metrics, defaults to log_interval - log_gradient_flow: if to log gradient flow, this takes time and should be only done to diagnose training - failures - reduce_on_plateau_patience (int): patience after which learning rate is reduced by a factor of 10 - monotone_constaints (Dict[str, int]): dictionary of monotonicity constraints for continuous decoder - variables mapping - position (e.g. ``"0"`` for first position) to constraint (``-1`` for negative and ``+1`` for positive, - larger numbers add more weight to the constraint vs. the loss but are usually not necessary). - This constraint significantly slows down training. Defaults to {}. - share_single_variable_networks (bool): if to share the single variable networks between the encoder and - decoder. Defaults to False. - causal_attention (bool): If to attend only at previous timesteps in the decoder or also include future - predictions. Defaults to True. - logging_metrics (nn.ModuleList[LightningMetric]): list of metrics that are logged during training. - Defaults to nn.ModuleList([SMAPE(), MAE(), RMSE(), MAPE()]). - **kwargs: additional arguments to :py:class:`~BaseModel`. - """ # noqa: E501 - - max_encoder_length = metadata["max_encoder_length"] - if output_size is None: - output_size = metadata["target"] - static_categoricals = [ - f"static_cat_{i}" - for i in range(metadata.get("static_categorical_features", 0)) - ] - static_reals = [ - f"static_real_{i}" - for i in range(metadata.get("static_continuous_features", 0)) - ] - time_varying_categoricals_encoder = [ - f"encoder_cat_{i}" for i in range(metadata["encoder_cat"]) - ] - time_varying_reals_encoder = [ - f"encoder_real_{i}" for i in range(metadata["encoder_cont"]) - ] - time_varying_categoricals_decoder = [ - f"decoder_cat_{i}" for i in range(metadata["decoder_cat"]) - ] - time_varying_reals_decoder = [ - f"decoder_real_{i}" for i in range(metadata["decoder_cont"]) - ] - x_categoricals = ( - static_categoricals - + time_varying_categoricals_encoder - + time_varying_categoricals_decoder - ) - x_reals = static_reals + time_varying_reals_encoder + time_varying_reals_decoder - - if monotone_constaints is None: - monotone_constaints = {} - if embedding_labels is None: - embedding_labels = {} - if embedding_paddings is None: - embedding_paddings = [] - if embedding_sizes is None: - embedding_sizes = {} - if hidden_continuous_sizes is None: - hidden_continuous_sizes = {} - if categorical_groups is None: - categorical_groups = {} - if logging_metrics is None: - logging_metrics = nn.ModuleList([SMAPE(), MAE(), RMSE(), MAPE()]) - if loss is None: - loss = QuantileLoss() - self.save_hyperparameters(ignore=["metadata"]) - - self.hparams.max_encoder_length = max_encoder_length - self.hparams.output_size = output_size - self.hparams.static_categoricals = static_categoricals - self.hparams.static_reals = static_reals - self.hparams.time_varying_categoricals_encoder = ( - time_varying_categoricals_encoder - ) - self.hparams.time_varying_categoricals_decoder = ( - time_varying_categoricals_decoder - ) - self.hparams.time_varying_reals_encoder = time_varying_reals_encoder - self.hparams.time_varying_reals_decoder = time_varying_reals_decoder - self.hparams.x_categoricals = x_categoricals - self.hparams.x_reals = x_reals - # store loss function separately as it is a module - assert isinstance( - loss, LightningMetric - ), "Loss has to be a PyTorch Lightning `Metric`" - super().__init__(loss=loss, logging_metrics=logging_metrics, **kwargs) - - # processing inputs - # embeddings - self.input_embeddings = MultiEmbedding( - embedding_sizes=self.hparams.embedding_sizes, - categorical_groups=self.hparams.categorical_groups, - embedding_paddings=self.hparams.embedding_paddings, - x_categoricals=self.hparams.x_categoricals, - max_embedding_size=self.hparams.hidden_size, - ) - - # continuous variable processing - self.prescalers = nn.ModuleDict( - { - name: nn.Linear( - 1, - self.hparams.hidden_continuous_sizes.get( - name, self.hparams.hidden_continuous_size - ), - ) - for name in self.reals - } - ) - - # variable selection - # variable selection for static variables - static_input_sizes = { - name: self.input_embeddings.output_size[name] - for name in self.hparams.static_categoricals - } - static_input_sizes.update( - { - name: self.hparams.hidden_continuous_sizes.get( - name, self.hparams.hidden_continuous_size - ) - for name in self.hparams.static_reals - } - ) - self.static_variable_selection = VariableSelectionNetwork( - input_sizes=static_input_sizes, - hidden_size=self.hparams.hidden_size, - input_embedding_flags={ - name: True for name in self.hparams.static_categoricals - }, - dropout=self.hparams.dropout, - prescalers=self.prescalers, - ) - - # variable selection for encoder and decoder - encoder_input_sizes = { - name: self.input_embeddings.output_size[name] - for name in self.hparams.time_varying_categoricals_encoder - } - encoder_input_sizes.update( - { - name: self.hparams.hidden_continuous_sizes.get( - name, self.hparams.hidden_continuous_size - ) - for name in self.hparams.time_varying_reals_encoder - } - ) - - decoder_input_sizes = { - name: self.input_embeddings.output_size[name] - for name in self.hparams.time_varying_categoricals_decoder - } - decoder_input_sizes.update( - { - name: self.hparams.hidden_continuous_sizes.get( - name, self.hparams.hidden_continuous_size - ) - for name in self.hparams.time_varying_reals_decoder - } - ) - - # create single variable grns that are shared across decoder and encoder - if self.hparams.share_single_variable_networks: - self.shared_single_variable_grns = nn.ModuleDict() - for name, input_size in encoder_input_sizes.items(): - self.shared_single_variable_grns[name] = GatedResidualNetwork( - input_size, - min(input_size, self.hparams.hidden_size), - self.hparams.hidden_size, - self.hparams.dropout, - ) - for name, input_size in decoder_input_sizes.items(): - if name not in self.shared_single_variable_grns: - self.shared_single_variable_grns[name] = GatedResidualNetwork( - input_size, - min(input_size, self.hparams.hidden_size), - self.hparams.hidden_size, - self.hparams.dropout, - ) - - self.encoder_variable_selection = VariableSelectionNetwork( - input_sizes=encoder_input_sizes, - hidden_size=self.hparams.hidden_size, - input_embedding_flags={ - name: True for name in self.hparams.time_varying_categoricals_encoder - }, - dropout=self.hparams.dropout, - context_size=self.hparams.hidden_size, - prescalers=self.prescalers, - single_variable_grns=( - {} - if not self.hparams.share_single_variable_networks - else self.shared_single_variable_grns - ), - ) - - self.decoder_variable_selection = VariableSelectionNetwork( - input_sizes=decoder_input_sizes, - hidden_size=self.hparams.hidden_size, - input_embedding_flags={ - name: True for name in self.hparams.time_varying_categoricals_decoder - }, - dropout=self.hparams.dropout, - context_size=self.hparams.hidden_size, - prescalers=self.prescalers, - single_variable_grns=( - {} - if not self.hparams.share_single_variable_networks - else self.shared_single_variable_grns - ), - ) - - # static encoders - # for variable selection - self.static_context_variable_selection = GatedResidualNetwork( - input_size=self.hparams.hidden_size, - hidden_size=self.hparams.hidden_size, - output_size=self.hparams.hidden_size, - dropout=self.hparams.dropout, - ) - - # for hidden state of the lstm - self.static_context_initial_hidden_lstm = GatedResidualNetwork( - input_size=self.hparams.hidden_size, - hidden_size=self.hparams.hidden_size, - output_size=self.hparams.hidden_size, - dropout=self.hparams.dropout, - ) - - # for cell state of the lstm - self.static_context_initial_cell_lstm = GatedResidualNetwork( - input_size=self.hparams.hidden_size, - hidden_size=self.hparams.hidden_size, - output_size=self.hparams.hidden_size, - dropout=self.hparams.dropout, - ) - - # for post lstm static enrichment - self.static_context_enrichment = GatedResidualNetwork( - self.hparams.hidden_size, - self.hparams.hidden_size, - self.hparams.hidden_size, - self.hparams.dropout, - ) - - # lstm encoder (history) and decoder (future) for local processing - self.lstm_encoder = LSTM( - input_size=self.hparams.hidden_size, - hidden_size=self.hparams.hidden_size, - num_layers=self.hparams.lstm_layers, - dropout=self.hparams.dropout if self.hparams.lstm_layers > 1 else 0, - batch_first=True, - ) - - self.lstm_decoder = LSTM( - input_size=self.hparams.hidden_size, - hidden_size=self.hparams.hidden_size, - num_layers=self.hparams.lstm_layers, - dropout=self.hparams.dropout if self.hparams.lstm_layers > 1 else 0, - batch_first=True, - ) - - # skip connection for lstm - self.post_lstm_gate_encoder = GatedLinearUnit( - self.hparams.hidden_size, dropout=self.hparams.dropout - ) - self.post_lstm_gate_decoder = self.post_lstm_gate_encoder - # self.post_lstm_gate_decoder = GatedLinearUnit( - # self.hparams.hidden_size, dropout=self.hparams.dropout) - self.post_lstm_add_norm_encoder = AddNorm( - self.hparams.hidden_size, trainable_add=False - ) - # self.post_lstm_add_norm_decoder = AddNorm( - # self.hparams.hidden_size, trainable_add=True) - self.post_lstm_add_norm_decoder = self.post_lstm_add_norm_encoder - - # static enrichment and processing past LSTM - self.static_enrichment = GatedResidualNetwork( - input_size=self.hparams.hidden_size, - hidden_size=self.hparams.hidden_size, - output_size=self.hparams.hidden_size, - dropout=self.hparams.dropout, - context_size=self.hparams.hidden_size, - ) - - # attention for long-range processing - self.multihead_attn = InterpretableMultiHeadAttention( - d_model=self.hparams.hidden_size, - n_head=self.hparams.attention_head_size, - dropout=self.hparams.dropout, - ) - self.post_attn_gate_norm = GateAddNorm( - self.hparams.hidden_size, dropout=self.hparams.dropout, trainable_add=False - ) - self.pos_wise_ff = GatedResidualNetwork( - self.hparams.hidden_size, - self.hparams.hidden_size, - self.hparams.hidden_size, - dropout=self.hparams.dropout, - ) - - # output processing -> no dropout at this late stage - self.pre_output_gate_norm = GateAddNorm( - self.hparams.hidden_size, dropout=None, trainable_add=False - ) - - if self.n_targets > 1: # if to run with multiple targets - self.output_layer = nn.ModuleList( - [ - nn.Linear(self.hparams.hidden_size, output_size) - for output_size in self.hparams.output_size - ] - ) - else: - self.output_layer = nn.Linear( - self.hparams.hidden_size, self.hparams.output_size - ) - - @classmethod - def from_dataset( - cls, - dataset: TimeSeriesDataSet, - allowed_encoder_known_variable_names: List[str] = None, - **kwargs, - ): - """ - Create model from dataset. - - Args: - dataset: timeseries dataset - allowed_encoder_known_variable_names: List of known variables that are allowed in encoder, defaults to all - **kwargs: additional arguments such as hyperparameters for model (see ``__init__()``) - - Returns: - TemporalFusionTransformer - """ # noqa: E501 - # add maximum encoder length - # update defaults - new_kwargs = copy(kwargs) - new_kwargs["max_encoder_length"] = dataset.max_encoder_length - new_kwargs.update( - cls.deduce_default_output_parameters(dataset, kwargs, QuantileLoss()) - ) - - # create class and return - return super().from_dataset( - dataset, - allowed_encoder_known_variable_names=allowed_encoder_known_variable_names, - **new_kwargs, - ) - - def expand_static_context(self, context, timesteps): - """ - add time dimension to static context - """ - return context[:, None].expand(-1, timesteps, -1) - - def get_attention_mask( - self, encoder_lengths: torch.LongTensor, decoder_lengths: torch.LongTensor - ): - """ - Returns causal mask to apply for self-attention layer. - """ - decoder_length = decoder_lengths.max() - if self.hparams.causal_attention: - # indices to which is attended - attend_step = torch.arange(decoder_length, device=self.device) - # indices for which is predicted - predict_step = torch.arange(0, decoder_length, device=self.device)[:, None] - # do not attend to steps to self or after prediction - decoder_mask = ( - (attend_step >= predict_step) - .unsqueeze(0) - .expand(encoder_lengths.size(0), -1, -1) - ) - else: - # there is value in attending to future forecasts if - # they are made with knowledge currently available - # one possibility is here to use a second attention layer - # for future attention - # (assuming different effects matter in the future than the past) - # or alternatively using the same layer but - # allowing forward attention - i.e. only - # masking out non-available data and self - decoder_mask = ( - create_mask(decoder_length, decoder_lengths) - .unsqueeze(1) - .expand(-1, decoder_length, -1) - ) - # do not attend to steps where data is padded - encoder_mask = ( - create_mask(encoder_lengths.max(), encoder_lengths) - .unsqueeze(1) - .expand(-1, decoder_length, -1) - ) - # combine masks along attended time - first encoder and then decoder - mask = torch.cat( - ( - encoder_mask, - decoder_mask, - ), - dim=2, - ) - return mask - - def forward(self, x: Dict[str, torch.Tensor]) -> Dict[str, torch.Tensor]: - """ - input dimensions: n_samples x time x variables - """ - encoder_lengths = x["encoder_lengths"] - decoder_lengths = x["decoder_lengths"] - x_cat = torch.cat( - [x["encoder_cat"], x["decoder_cat"]], dim=1 - ) # concatenate in time dimension - - ############different from _tft########################### - encoder_cont = x["encoder_cont"] - decoder_cont = x["decoder_cont"] - if encoder_cont.shape[-1] != decoder_cont.shape[-1]: - max_features = max(encoder_cont.shape[-1], decoder_cont.shape[-1]) - - if encoder_cont.shape[-1] < max_features: - encoder_padding = torch.zeros( - encoder_cont.shape[0], - encoder_cont.shape[1], - max_features - encoder_cont.shape[-1], - ).to(encoder_cont.device) - encoder_cont = torch.cat([encoder_cont, encoder_padding], dim=-1) - - if decoder_cont.shape[-1] < max_features: - decoder_padding = torch.zeros( - decoder_cont.shape[0], - decoder_cont.shape[1], - max_features - decoder_cont.shape[-1], - ).to(decoder_cont.device) - decoder_cont = torch.cat([decoder_cont, decoder_padding], dim=-1) - x_cont = torch.cat( - [encoder_cont, decoder_cont], dim=1 - ) # concatenate in time dimension - ########## - - timesteps = x_cont.size(1) # encode + decode length - max_encoder_length = int(encoder_lengths.max()) - input_vectors = self.input_embeddings(x_cat) - - ############different from _tft########################### - available_reals = [name for name in self.hparams.x_reals if name in self.reals] - max_features = x_cont.shape[-1] - - input_vectors.update( - { - name: x_cont[..., idx].unsqueeze(-1) - for idx, name in enumerate(available_reals) - if idx < max_features - } - ) - - all_expected_vars = set(self.encoder_variables + self.decoder_variables) - real_vars_in_input = set(input_vectors.keys()) - missing_vars = all_expected_vars - real_vars_in_input - - for var_name in missing_vars: - if var_name.startswith(("encoder_real_", "decoder_real_")): - # Create zero tensor with same shape as other real variables - zero_tensor = torch.zeros_like(x_cont[..., 0].unsqueeze(-1)) - input_vectors[var_name] = zero_tensor - ############ - - # Embedding and variable selection - if len(self.static_variables) > 0: - # static embeddings will be constant over entire batch - static_embedding = { - name: input_vectors[name][:, 0] for name in self.static_variables - } - static_embedding, static_variable_selection = ( - self.static_variable_selection(static_embedding) - ) - else: - static_embedding = torch.zeros( - (x_cont.size(0), self.hparams.hidden_size), - dtype=self.dtype, - device=self.device, - ) - static_variable_selection = torch.zeros( - (x_cont.size(0), 0), dtype=self.dtype, device=self.device - ) - - static_context_variable_selection = self.expand_static_context( - self.static_context_variable_selection(static_embedding), timesteps - ) - - embeddings_varying_encoder = { - name: input_vectors[name][:, :max_encoder_length] - for name in self.encoder_variables - } - embeddings_varying_encoder, encoder_sparse_weights = ( - self.encoder_variable_selection( - embeddings_varying_encoder, - static_context_variable_selection[:, :max_encoder_length], - ) - ) - - embeddings_varying_decoder = { - name: input_vectors[name][:, max_encoder_length:] - for name in self.decoder_variables # select decoder - } - embeddings_varying_decoder, decoder_sparse_weights = ( - self.decoder_variable_selection( - embeddings_varying_decoder, - static_context_variable_selection[:, max_encoder_length:], - ) - ) - - # LSTM - # calculate initial state - input_hidden = self.static_context_initial_hidden_lstm(static_embedding).expand( - self.hparams.lstm_layers, -1, -1 - ) - input_cell = self.static_context_initial_cell_lstm(static_embedding).expand( - self.hparams.lstm_layers, -1, -1 - ) - - # run local encoder - encoder_output, (hidden, cell) = self.lstm_encoder( - embeddings_varying_encoder, - (input_hidden, input_cell), - lengths=encoder_lengths, - enforce_sorted=False, - ) - - # run local decoder - decoder_output, _ = self.lstm_decoder( - embeddings_varying_decoder, - (hidden, cell), - lengths=decoder_lengths, - enforce_sorted=False, - ) - - # skip connection over lstm - lstm_output_encoder = self.post_lstm_gate_encoder(encoder_output) - lstm_output_encoder = self.post_lstm_add_norm_encoder( - lstm_output_encoder, embeddings_varying_encoder - ) - - lstm_output_decoder = self.post_lstm_gate_decoder(decoder_output) - lstm_output_decoder = self.post_lstm_add_norm_decoder( - lstm_output_decoder, embeddings_varying_decoder - ) - - lstm_output = torch.cat([lstm_output_encoder, lstm_output_decoder], dim=1) - - # static enrichment - static_context_enrichment = self.static_context_enrichment(static_embedding) - attn_input = self.static_enrichment( - lstm_output, - self.expand_static_context(static_context_enrichment, timesteps), - ) - - # Attention - attn_output, attn_output_weights = self.multihead_attn( - q=attn_input[:, max_encoder_length:], # query only for predictions - k=attn_input, - v=attn_input, - mask=self.get_attention_mask( - encoder_lengths=encoder_lengths, decoder_lengths=decoder_lengths - ), - ) - - # skip connection over attention - attn_output = self.post_attn_gate_norm( - attn_output, attn_input[:, max_encoder_length:] - ) - - output = self.pos_wise_ff(attn_output) - - # skip connection over temporal fusion decoder (not LSTM decoder - # despite the LSTM output contains - # a skip from the variable selection network) - output = self.pre_output_gate_norm(output, lstm_output[:, max_encoder_length:]) - if self.n_targets > 1: # if to use multi-target architecture - output = [output_layer(output) for output_layer in self.output_layer] - else: - output = self.output_layer(output) - - return self.to_network_output( - prediction=self.transform_output(output, target_scale=x["target_scale"]), - encoder_attention=attn_output_weights[..., :max_encoder_length], - decoder_attention=attn_output_weights[..., max_encoder_length:], - static_variables=static_variable_selection, - encoder_variables=encoder_sparse_weights, - decoder_variables=decoder_sparse_weights, - decoder_lengths=decoder_lengths, - encoder_lengths=encoder_lengths, - ) - - def on_fit_end(self): - if self.log_interval > 0: - self.log_embeddings() - - def create_log(self, x, y, out, batch_idx, **kwargs): - log = super().create_log(x, y, out, batch_idx, **kwargs) - if self.log_interval > 0: - log["interpretation"] = self._log_interpretation(out) - return log - - def _log_interpretation(self, out): - # calculate interpretations etc for latter logging - interpretation = self.interpret_output( - detach(out), - reduction="sum", - attention_prediction_horizon=0, # attention only for first prediction horizon # noqa: E501 - ) - return interpretation - - def on_epoch_end(self, outputs): - """ - run at epoch end for training or validation - """ - if self.log_interval > 0 and not self.training: - self.log_interpretation(outputs) - - def interpret_output( - self, - out: Dict[str, torch.Tensor], - reduction: str = "none", - attention_prediction_horizon: int = 0, - ) -> Dict[str, torch.Tensor]: - """ - interpret output of model - - Args: - out: output as produced by ``forward()`` - reduction: "none" for no averaging over batches, "sum" for summing attentions, "mean" for - normalizing by encode lengths - attention_prediction_horizon: which prediction horizon to use for attention - - Returns: - interpretations that can be plotted with ``plot_interpretation()`` - """ # noqa: E501 - # take attention and concatenate if a list to proper attention object - batch_size = len(out["decoder_attention"]) - if isinstance(out["decoder_attention"], (list, tuple)): - # start with decoder attention - # assume issue is in last dimension, we need to find max - max_last_dimension = max(x.size(-1) for x in out["decoder_attention"]) - first_elm = out["decoder_attention"][0] - # create new attention tensor into which we will scatter - decoder_attention = torch.full( - (batch_size, *first_elm.shape[:-1], max_last_dimension), - float("nan"), - dtype=first_elm.dtype, - device=first_elm.device, - ) - # scatter into tensor - for idx, x in enumerate(out["decoder_attention"]): - decoder_length = out["decoder_lengths"][idx] - decoder_attention[idx, :, :, :decoder_length] = x[..., :decoder_length] - else: - decoder_attention = out["decoder_attention"].clone() - decoder_mask = create_mask( - out["decoder_attention"].size(1), out["decoder_lengths"] - ) - decoder_attention[ - decoder_mask[..., None, None].expand_as(decoder_attention) - ] = float("nan") - - if isinstance(out["encoder_attention"], (list, tuple)): - # same game for encoder attention - # create new attention tensor into which we will scatter - first_elm = out["encoder_attention"][0] - encoder_attention = torch.full( - (batch_size, *first_elm.shape[:-1], self.hparams.max_encoder_length), - float("nan"), - dtype=first_elm.dtype, - device=first_elm.device, - ) - # scatter into tensor - for idx, x in enumerate(out["encoder_attention"]): - encoder_length = out["encoder_lengths"][idx] - encoder_attention[ - idx, :, :, self.hparams.max_encoder_length - encoder_length : - ] = x[..., :encoder_length] - else: - # roll encoder attention (so start last encoder value is on the right) - encoder_attention = out["encoder_attention"].clone() - shifts = encoder_attention.size(3) - out["encoder_lengths"] - new_index = ( - torch.arange( - encoder_attention.size(3), device=encoder_attention.device - )[None, None, None].expand_as(encoder_attention) - - shifts[:, None, None, None] - ) % encoder_attention.size(3) - encoder_attention = torch.gather(encoder_attention, dim=3, index=new_index) - # expand encoder_attention to full size - if encoder_attention.size(-1) < self.hparams.max_encoder_length: - encoder_attention = torch.concat( - [ - torch.full( - ( - *encoder_attention.shape[:-1], - self.hparams.max_encoder_length - - out["encoder_lengths"].max(), - ), - float("nan"), - dtype=encoder_attention.dtype, - device=encoder_attention.device, - ), - encoder_attention, - ], - dim=-1, - ) - - # combine attention vector - attention = torch.concat([encoder_attention, decoder_attention], dim=-1) - attention[attention < 1e-5] = float("nan") - - # histogram of decode and encode lengths - encoder_length_histogram = integer_histogram( - out["encoder_lengths"], min=0, max=self.hparams.max_encoder_length - ) - decoder_length_histogram = integer_histogram( - out["decoder_lengths"], min=1, max=out["decoder_variables"].size(1) - ) - - # mask where decoder and encoder where not applied - # when averaging variable selection weights - encoder_variables = out["encoder_variables"].squeeze(-2).clone() - encode_mask = create_mask(encoder_variables.size(1), out["encoder_lengths"]) - encoder_variables = encoder_variables.masked_fill( - encode_mask.unsqueeze(-1), 0.0 - ).sum(dim=1) - encoder_variables /= ( - out["encoder_lengths"] - .where(out["encoder_lengths"] > 0, torch.ones_like(out["encoder_lengths"])) - .unsqueeze(-1) - ) - - decoder_variables = out["decoder_variables"].squeeze(-2).clone() - decode_mask = create_mask(decoder_variables.size(1), out["decoder_lengths"]) - decoder_variables = decoder_variables.masked_fill( - decode_mask.unsqueeze(-1), 0.0 - ).sum(dim=1) - decoder_variables /= out["decoder_lengths"].unsqueeze(-1) - - # static variables need no masking - static_variables = out["static_variables"].squeeze(1) - # attention is batch x time x heads x time_to_attend - # average over heads + only keep prediction attention and - # attention on observed timesteps - attention = masked_op( - attention[ - :, - attention_prediction_horizon, - :, - : self.hparams.max_encoder_length + attention_prediction_horizon, - ], - op="mean", - dim=1, - ) - - if reduction != "none": # if to average over batches - static_variables = static_variables.sum(dim=0) - encoder_variables = encoder_variables.sum(dim=0) - decoder_variables = decoder_variables.sum(dim=0) - - attention = masked_op(attention, dim=0, op=reduction) - else: - attention = attention / masked_op(attention, dim=1, op="sum").unsqueeze( - -1 - ) # renormalize - - interpretation = dict( - attention=attention.masked_fill(torch.isnan(attention), 0.0), - static_variables=static_variables, - encoder_variables=encoder_variables, - decoder_variables=decoder_variables, - encoder_length_histogram=encoder_length_histogram, - decoder_length_histogram=decoder_length_histogram, - ) - return interpretation - - def plot_prediction( - self, - x: Dict[str, torch.Tensor], - out: Dict[str, torch.Tensor], - idx: int, - plot_attention: bool = True, - add_loss_to_title: bool = False, - show_future_observed: bool = True, - ax=None, - **kwargs, - ): - """ - Plot actuals vs prediction and attention - - Args: - x (Dict[str, torch.Tensor]): network input - out (Dict[str, torch.Tensor]): network output - idx (int): sample index - plot_attention: if to plot attention on secondary axis - add_loss_to_title: if to add loss to title. Default to False. - show_future_observed: if to show actuals for future. Defaults to True. - ax: matplotlib axes to plot on - - Returns: - plt.Figure: matplotlib figure - """ - # plot prediction as normal - fig = super().plot_prediction( - x, - out, - idx=idx, - add_loss_to_title=add_loss_to_title, - show_future_observed=show_future_observed, - ax=ax, - **kwargs, - ) - - # add attention on secondary axis - if plot_attention: - interpretation = self.interpret_output(out.iget(slice(idx, idx + 1))) - for f in to_list(fig): - ax = f.axes[0] - ax2 = ax.twinx() - ax2.set_ylabel("Attention") - encoder_length = x["encoder_lengths"][0] - ax2.plot( - torch.arange(-encoder_length, 0), - interpretation["attention"][0, -encoder_length:].detach().cpu(), - alpha=0.2, - color="k", - ) - f.tight_layout() - return fig - - def plot_interpretation(self, interpretation: Dict[str, torch.Tensor]): - """ - Make figures that interpret model. - - * Attention - * Variable selection weights / importances - - Args: - interpretation: as obtained from ``interpret_output()`` - - Returns: - dictionary of matplotlib figures - """ - _check_matplotlib("plot_interpretation") - - import matplotlib.pyplot as plt - - figs = {} - - # attention - fig, ax = plt.subplots() - attention = interpretation["attention"].detach().cpu() - attention = attention / attention.sum(-1).unsqueeze(-1) - ax.plot( - np.arange( - -self.hparams.max_encoder_length, - attention.size(0) - self.hparams.max_encoder_length, - ), - attention, - ) - ax.set_xlabel("Time index") - ax.set_ylabel("Attention") - ax.set_title("Attention") - figs["attention"] = fig - - # variable selection - def make_selection_plot(title, values, labels): - fig, ax = plt.subplots(figsize=(7, len(values) * 0.25 + 2)) - order = np.argsort(values) - values = values / values.sum(-1).unsqueeze(-1) - ax.barh( - np.arange(len(values)), - values[order] * 100, - tick_label=np.asarray(labels)[order], - ) - ax.set_title(title) - ax.set_xlabel("Importance in %") - plt.tight_layout() - return fig - - figs["static_variables"] = make_selection_plot( - "Static variables importance", - interpretation["static_variables"].detach().cpu(), - self.static_variables, - ) - figs["encoder_variables"] = make_selection_plot( - "Encoder variables importance", - interpretation["encoder_variables"].detach().cpu(), - self.encoder_variables, - ) - figs["decoder_variables"] = make_selection_plot( - "Decoder variables importance", - interpretation["decoder_variables"].detach().cpu(), - self.decoder_variables, - ) - - return figs - - def log_interpretation(self, outputs): - """ - Log interpretation metrics to tensorboard. - """ - # extract interpretations - interpretation = { - # use padded_stack because decoder - # length histogram can be of different length - name: padded_stack( - [x["interpretation"][name].detach() for x in outputs], - side="right", - value=0, - ).sum(0) - for name in outputs[0]["interpretation"].keys() - } - # normalize attention with length histogram squared to account for: - # 1. zeros in attention and - # 2. higher attention due to less values - attention_occurances = ( - interpretation["encoder_length_histogram"][1:].flip(0).float().cumsum(0) - ) - attention_occurances = attention_occurances / attention_occurances.max() - attention_occurances = torch.cat( - [ - attention_occurances, - torch.ones( - interpretation["attention"].size(0) - attention_occurances.size(0), - dtype=attention_occurances.dtype, - device=attention_occurances.device, - ), - ], - dim=0, - ) - interpretation["attention"] = interpretation[ - "attention" - ] / attention_occurances.pow(2).clamp(1.0) - interpretation["attention"] = ( - interpretation["attention"] / interpretation["attention"].sum() - ) - - mpl_available = _check_matplotlib("log_interpretation", raise_error=False) - - # Don't log figures if matplotlib or add_figure is not available - if not mpl_available or not self._logger_supports("add_figure"): - return None - - import matplotlib.pyplot as plt - - figs = self.plot_interpretation(interpretation) # make interpretation figures - label = self.current_stage - # log to tensorboard - for name, fig in figs.items(): - self.logger.experiment.add_figure( - f"{label.capitalize()} {name} importance", - fig, - global_step=self.global_step, - ) - - # log lengths of encoder/decoder - for type in ["encoder", "decoder"]: - fig, ax = plt.subplots() - lengths = ( - padded_stack( - [ - out["interpretation"][f"{type}_length_histogram"] - for out in outputs - ] - ) - .sum(0) - .detach() - .cpu() - ) - if type == "decoder": - start = 1 - else: - start = 0 - ax.plot(torch.arange(start, start + len(lengths)), lengths) - ax.set_xlabel(f"{type.capitalize()} length") - ax.set_ylabel("Number of samples") - ax.set_title(f"{type.capitalize()} length distribution in {label} epoch") - - self.logger.experiment.add_figure( - f"{label.capitalize()} {type} length distribution", - fig, - global_step=self.global_step, - ) - - def log_embeddings(self): - """ - Log embeddings to tensorboard - """ - - # Don't log embeddings if add_embedding is not available - if not self._logger_supports("add_embedding"): - return None - - for name, emb in self.input_embeddings.items(): - labels = self.hparams.embedding_labels[name] - self.logger.experiment.add_embedding( - emb.weight.data.detach().cpu(), - metadata=labels, - tag=name, - global_step=self.global_step, - ) diff --git a/pytorch_forecasting/models/temporal_fusion_transformer/tft_v2_metadata.py b/pytorch_forecasting/models/temporal_fusion_transformer/tft_v2_metadata.py index 5ebea75b1..2a98767c2 100644 --- a/pytorch_forecasting/models/temporal_fusion_transformer/tft_v2_metadata.py +++ b/pytorch_forecasting/models/temporal_fusion_transformer/tft_v2_metadata.py @@ -42,49 +42,3 @@ def get_test_train_params(cls): attention_head_size=5, ), ] - - -class TemporalFusionTransformerMetadata(_BasePtForecaster): - """TFT metadata container.""" - - _tags = { - "info:name": "TemporalFusionTransformerM", - "object_type": "ptf-v2", - "authors": ["jdb78"], - "capability:exogenous": True, - "capability:multivariate": True, - "capability:pred_int": True, - "capability:flexible_history_length": False, - } - - @classmethod - def get_model_cls(cls): - """Get model class.""" - from pytorch_forecasting.models.temporal_fusion_transformer._tft_ver2 import ( - TemporalFusionTransformer, - ) - - return TemporalFusionTransformer - - @classmethod - def get_test_train_params(cls): - """Return testing parameter settings for the trainer. - - Returns - ------- - params : dict or list of dict, default = {} - Parameters to create testing instances of the class - Each dict are parameters to construct an "interesting" test instance, i.e., - `MyClass(**params)` or `MyClass(**params[i])` creates a valid test instance. - `create_test_instance` uses the first (or only) dictionary in `params` - """ - return [ - {}, - dict( - hidden_size=25, - attention_head_size=5, - data_loader_kwargs={ - "add_relative_time_idx": False, - }, - ), - ] diff --git a/pytorch_forecasting/tests/test_all_estimators.py b/pytorch_forecasting/tests/test_all_estimators.py index b8a21cc6a..18199214b 100644 --- a/pytorch_forecasting/tests/test_all_estimators.py +++ b/pytorch_forecasting/tests/test_all_estimators.py @@ -265,6 +265,21 @@ def _integration( class TestAllPtForecasters(PackageConfig, BaseFixtureGenerator): """Generic tests for all objects in the mini package.""" + def _all_objects(self): + """Retrieve list of all object classes, excluding ptf-v2 objects.""" + obj_list = super()._all_objects() + + filtered_obj_list = [] + for obj in obj_list: + if hasattr(obj, "get_class_tag"): + object_type = obj.get_class_tag("object_type", None) + if object_type != "ptf-v2": + filtered_obj_list.append(obj) + else: + filtered_obj_list.append(obj) + + return filtered_obj_list + def test_doctest_examples(self, object_class): """Runs doctests for estimator class.""" import doctest From d0490192b62ddaf8575318f55dcaf3582e0e8eef Mon Sep 17 00:00:00 2001 From: Aryan Saini Date: Wed, 28 May 2025 16:56:23 +0530 Subject: [PATCH 079/139] update test_all_estimators --- pytorch_forecasting/tests/_conftest.py | 234 +++++++++++++++++- pytorch_forecasting/tests/_data_scenarios.py | 233 ++++++++++++++++- .../tests/test_all_estimators.py | 17 +- .../tests/test_all_estimators_v2.py | 4 +- 4 files changed, 483 insertions(+), 5 deletions(-) diff --git a/pytorch_forecasting/tests/_conftest.py b/pytorch_forecasting/tests/_conftest.py index 36691e850..caa0f5600 100644 --- a/pytorch_forecasting/tests/_conftest.py +++ b/pytorch_forecasting/tests/_conftest.py @@ -1,10 +1,15 @@ +from datetime import datetime + import numpy as np +import pandas as pd import pytest import torch from pytorch_forecasting import TimeSeriesDataSet from pytorch_forecasting.data import EncoderNormalizer, GroupNormalizer, NaNLabelEncoder +from pytorch_forecasting.data.data_module import EncoderDecoderTimeSeriesDataModule from pytorch_forecasting.data.examples import generate_ar_data, get_stallion_data +from pytorch_forecasting.data.timeseries import TimeSeries torch.manual_seed(23) @@ -155,6 +160,233 @@ def multiple_dataloaders_with_covariates(data_with_covariates, request): return make_dataloaders(data_with_covariates, **request.param) +@pytest.fixture(scope="session") +def data_with_covariates_v2(): + """Create synthetic time series data with all numerical features.""" + + start_date = datetime(2015, 1, 1) + end_date = datetime(2017, 12, 31) + dates = pd.date_range(start_date, end_date, freq="M") + + agencies = [0, 1] + skus = [0, 1] + data_list = [] + + for agency in agencies: + for sku in skus: + for date in dates: + time_idx = (date.year - 2015) * 12 + date.month - 1 + + volume = ( + np.random.exponential(2) + + 0.1 * time_idx + + 0.5 * np.sin(date.month * np.pi / 6) + ) + volume = max(0.001, volume) + month = date.month + year = date.year + quarter = (date.month - 1) // 3 + 1 + + seasonal_1 = np.sin(2 * np.pi * date.month / 12) + seasonal_2 = np.cos(2 * np.pi * date.month / 12) + + agency_feature_1 = agency * 10 + np.random.normal(0, 0.1) + agency_feature_2 = agency * 5 + np.random.normal(0, 0.1) + + sku_feature_1 = sku * 8 + np.random.normal(0, 0.1) + sku_feature_2 = sku * 3 + np.random.normal(0, 0.1) + + trend = time_idx * 0.1 + noise = np.random.normal(0, 0.1) + + special_event_1 = 1 if date.month in [12, 1] else 0 + special_event_2 = 1 if date.month in [6, 7, 8] else 0 + + data_list.append( + { + "date": date, + "time_idx": time_idx, + "agency_encoded": agency, + "sku_encoded": sku, + "volume": volume, + "target": volume, + "weight": 1.0 + np.sqrt(volume), + "month": month, + "year": year, + "quarter": quarter, + "seasonal_1": seasonal_1, + "seasonal_2": seasonal_2, + "agency_feature_1": agency_feature_1, + "agency_feature_2": agency_feature_2, + "sku_feature_1": sku_feature_1, + "sku_feature_2": sku_feature_2, + "trend": trend, + "noise": noise, + "special_event_1": special_event_1, + "special_event_2": special_event_2, + "log_volume": np.log1p(volume), + } + ) + + data = pd.DataFrame(data_list) + + numeric_cols = [col for col in data.columns if col != "date"] + for col in numeric_cols: + data[col] = pd.to_numeric(data[col], errors="coerce") + data[numeric_cols] = data[numeric_cols].fillna(0) + + return data + + +def make_dataloaders_v2(data_with_covariates, **kwargs): + """Create dataloaders with consistent encoder/decoder features.""" + + training_cutoff = "2016-09-01" + max_encoder_length = 4 + max_prediction_length = 3 + + target_col = kwargs.get("target", "target") + group_cols = kwargs.get("group_ids", ["agency_encoded", "sku_encoded"]) + add_relative_time_idx = kwargs.get("add_relative_time_idx", True) + + known_features = [ + "month", + "year", + "quarter", + "seasonal_1", + "seasonal_2", + "special_event_1", + "special_event_2", + "trend", + ] + unknown_features = [ + "agency_feature_1", + "agency_feature_2", + "sku_feature_1", + "sku_feature_2", + "noise", + "log_volume", + ] + + numerical_features = known_features + unknown_features + categorical_features = [] + static_features = group_cols + + for col in numerical_features + categorical_features + group_cols + [target_col]: + if col in data_with_covariates.columns: + data_with_covariates[col] = pd.to_numeric( + data_with_covariates[col], errors="coerce" + ).fillna(0) + + for col in categorical_features + group_cols: + if col in data_with_covariates.columns: + data_with_covariates[col] = data_with_covariates[col].astype("int64") + + if "weight" in data_with_covariates.columns: + data_with_covariates["weight"] = pd.to_numeric( + data_with_covariates["weight"], errors="coerce" + ).fillna(1.0) + + training_data = data_with_covariates[ + data_with_covariates.date < training_cutoff + ].copy() + validation_data = data_with_covariates.copy() + + required_columns = ( + ["time_idx", target_col, "weight", "date"] + + group_cols + + numerical_features + + categorical_features + ) + + available_columns = [ + col for col in required_columns if col in data_with_covariates.columns + ] + + training_data_clean = training_data[available_columns].copy() + validation_data_clean = validation_data[available_columns].copy() + + if "date" in training_data_clean.columns: + training_data_clean = training_data_clean.drop("date", axis=1) + if "date" in validation_data_clean.columns: + validation_data_clean = validation_data_clean.drop("date", axis=1) + + training_dataset = TimeSeries( + data=training_data_clean, + time="time_idx", + target=[target_col], + group=group_cols, + weight="weight", + num=numerical_features, + cat=categorical_features if categorical_features else None, + known=known_features, + unknown=unknown_features, + static=static_features, + ) + + validation_dataset = TimeSeries( + data=validation_data_clean, + time="time_idx", + target=[target_col], + group=group_cols, + weight="weight", + num=numerical_features, + cat=categorical_features if categorical_features else None, + known=known_features, + unknown=unknown_features, + static=static_features, + ) + + training_max_time_idx = training_data["time_idx"].max() + 1 + + train_datamodule = EncoderDecoderTimeSeriesDataModule( + time_series_dataset=training_dataset, + max_encoder_length=max_encoder_length, + max_prediction_length=max_prediction_length, + add_relative_time_idx=add_relative_time_idx, + batch_size=2, + num_workers=0, + train_val_test_split=(0.8, 0.2, 0.0), + ) + + val_datamodule = EncoderDecoderTimeSeriesDataModule( + time_series_dataset=validation_dataset, + max_encoder_length=max_encoder_length, + max_prediction_length=max_prediction_length, + min_prediction_idx=training_max_time_idx, + add_relative_time_idx=add_relative_time_idx, + batch_size=2, + num_workers=0, + train_val_test_split=(0.0, 1.0, 0.0), + ) + + test_datamodule = EncoderDecoderTimeSeriesDataModule( + time_series_dataset=validation_dataset, + max_encoder_length=max_encoder_length, + max_prediction_length=max_prediction_length, + min_prediction_idx=training_max_time_idx, + add_relative_time_idx=add_relative_time_idx, + batch_size=1, + num_workers=0, + train_val_test_split=(0.0, 0.0, 1.0), + ) + + train_datamodule.setup("fit") + val_datamodule.setup("fit") + test_datamodule.setup("test") + + train_dataloader = train_datamodule.train_dataloader() + val_dataloader = val_datamodule.val_dataloader() + test_dataloader = test_datamodule.test_dataloader() + + return { + "train": train_dataloader, + "val": val_dataloader, + "test": test_dataloader, + "data_module": train_datamodule, + } + + @pytest.fixture(scope="session") def dataloaders_with_different_encoder_decoder_length(data_with_covariates): return make_dataloaders( @@ -259,4 +491,4 @@ def dataloaders_fixed_window_without_covariates(): train=False, batch_size=batch_size, num_workers=0 ) - return dict(train=train_dataloader, val=val_dataloader, test=test_dataloader) \ No newline at end of file + return dict(train=train_dataloader, val=val_dataloader, test=test_dataloader) diff --git a/pytorch_forecasting/tests/_data_scenarios.py b/pytorch_forecasting/tests/_data_scenarios.py index fdf8e5e6d..d39f6d988 100644 --- a/pytorch_forecasting/tests/_data_scenarios.py +++ b/pytorch_forecasting/tests/_data_scenarios.py @@ -1,10 +1,15 @@ +from datetime import datetime + import numpy as np +import pandas as pd import pytest import torch from pytorch_forecasting import TimeSeriesDataSet from pytorch_forecasting.data import EncoderNormalizer, GroupNormalizer, NaNLabelEncoder +from pytorch_forecasting.data.data_module import EncoderDecoderTimeSeriesDataModule from pytorch_forecasting.data.examples import generate_ar_data, get_stallion_data +from pytorch_forecasting.data.timeseries import TimeSeries torch.manual_seed(23) @@ -87,6 +92,232 @@ def make_dataloaders(data_with_covariates, **kwargs): return dict(train=train_dataloader, val=val_dataloader, test=test_dataloader) +def data_with_covariates_v2(): + """Create synthetic time series data with all numerical features.""" + + start_date = datetime(2015, 1, 1) + end_date = datetime(2017, 12, 31) + dates = pd.date_range(start_date, end_date, freq="M") + + agencies = [0, 1] + skus = [0, 1] + data_list = [] + + for agency in agencies: + for sku in skus: + for date in dates: + time_idx = (date.year - 2015) * 12 + date.month - 1 + + volume = ( + np.random.exponential(2) + + 0.1 * time_idx + + 0.5 * np.sin(date.month * np.pi / 6) + ) + volume = max(0.001, volume) + month = date.month + year = date.year + quarter = (date.month - 1) // 3 + 1 + + seasonal_1 = np.sin(2 * np.pi * date.month / 12) + seasonal_2 = np.cos(2 * np.pi * date.month / 12) + + agency_feature_1 = agency * 10 + np.random.normal(0, 0.1) + agency_feature_2 = agency * 5 + np.random.normal(0, 0.1) + + sku_feature_1 = sku * 8 + np.random.normal(0, 0.1) + sku_feature_2 = sku * 3 + np.random.normal(0, 0.1) + + trend = time_idx * 0.1 + noise = np.random.normal(0, 0.1) + + special_event_1 = 1 if date.month in [12, 1] else 0 + special_event_2 = 1 if date.month in [6, 7, 8] else 0 + + data_list.append( + { + "date": date, + "time_idx": time_idx, + "agency_encoded": agency, + "sku_encoded": sku, + "volume": volume, + "target": volume, + "weight": 1.0 + np.sqrt(volume), + "month": month, + "year": year, + "quarter": quarter, + "seasonal_1": seasonal_1, + "seasonal_2": seasonal_2, + "agency_feature_1": agency_feature_1, + "agency_feature_2": agency_feature_2, + "sku_feature_1": sku_feature_1, + "sku_feature_2": sku_feature_2, + "trend": trend, + "noise": noise, + "special_event_1": special_event_1, + "special_event_2": special_event_2, + "log_volume": np.log1p(volume), + } + ) + + data = pd.DataFrame(data_list) + + numeric_cols = [col for col in data.columns if col != "date"] + for col in numeric_cols: + data[col] = pd.to_numeric(data[col], errors="coerce") + data[numeric_cols] = data[numeric_cols].fillna(0) + + return data + + +def make_dataloaders_v2(data_with_covariates, **kwargs): + """Create dataloaders with consistent encoder/decoder features.""" + + training_cutoff = "2016-09-01" + max_encoder_length = 4 + max_prediction_length = 3 + + target_col = kwargs.get("target", "target") + group_cols = kwargs.get("group_ids", ["agency_encoded", "sku_encoded"]) + add_relative_time_idx = kwargs.get("add_relative_time_idx", True) + + known_features = [ + "month", + "year", + "quarter", + "seasonal_1", + "seasonal_2", + "special_event_1", + "special_event_2", + "trend", + ] + unknown_features = [ + "agency_feature_1", + "agency_feature_2", + "sku_feature_1", + "sku_feature_2", + "noise", + "log_volume", + ] + + numerical_features = known_features + unknown_features + categorical_features = [] + static_features = group_cols + + for col in numerical_features + categorical_features + group_cols + [target_col]: + if col in data_with_covariates.columns: + data_with_covariates[col] = pd.to_numeric( + data_with_covariates[col], errors="coerce" + ).fillna(0) + + for col in categorical_features + group_cols: + if col in data_with_covariates.columns: + data_with_covariates[col] = data_with_covariates[col].astype("int64") + + if "weight" in data_with_covariates.columns: + data_with_covariates["weight"] = pd.to_numeric( + data_with_covariates["weight"], errors="coerce" + ).fillna(1.0) + + training_data = data_with_covariates[ + data_with_covariates.date < training_cutoff + ].copy() + validation_data = data_with_covariates.copy() + + required_columns = ( + ["time_idx", target_col, "weight", "date"] + + group_cols + + numerical_features + + categorical_features + ) + + available_columns = [ + col for col in required_columns if col in data_with_covariates.columns + ] + + training_data_clean = training_data[available_columns].copy() + validation_data_clean = validation_data[available_columns].copy() + + if "date" in training_data_clean.columns: + training_data_clean = training_data_clean.drop("date", axis=1) + if "date" in validation_data_clean.columns: + validation_data_clean = validation_data_clean.drop("date", axis=1) + + training_dataset = TimeSeries( + data=training_data_clean, + time="time_idx", + target=[target_col], + group=group_cols, + weight="weight", + num=numerical_features, + cat=categorical_features if categorical_features else None, + known=known_features, + unknown=unknown_features, + static=static_features, + ) + + validation_dataset = TimeSeries( + data=validation_data_clean, + time="time_idx", + target=[target_col], + group=group_cols, + weight="weight", + num=numerical_features, + cat=categorical_features if categorical_features else None, + known=known_features, + unknown=unknown_features, + static=static_features, + ) + + training_max_time_idx = training_data["time_idx"].max() + 1 + + train_datamodule = EncoderDecoderTimeSeriesDataModule( + time_series_dataset=training_dataset, + max_encoder_length=max_encoder_length, + max_prediction_length=max_prediction_length, + add_relative_time_idx=add_relative_time_idx, + batch_size=2, + num_workers=0, + train_val_test_split=(0.8, 0.2, 0.0), + ) + + val_datamodule = EncoderDecoderTimeSeriesDataModule( + time_series_dataset=validation_dataset, + max_encoder_length=max_encoder_length, + max_prediction_length=max_prediction_length, + min_prediction_idx=training_max_time_idx, + add_relative_time_idx=add_relative_time_idx, + batch_size=2, + num_workers=0, + train_val_test_split=(0.0, 1.0, 0.0), + ) + + test_datamodule = EncoderDecoderTimeSeriesDataModule( + time_series_dataset=validation_dataset, + max_encoder_length=max_encoder_length, + max_prediction_length=max_prediction_length, + min_prediction_idx=training_max_time_idx, + add_relative_time_idx=add_relative_time_idx, + batch_size=1, + num_workers=0, + train_val_test_split=(0.0, 0.0, 1.0), + ) + + train_datamodule.setup("fit") + val_datamodule.setup("fit") + test_datamodule.setup("test") + + train_dataloader = train_datamodule.train_dataloader() + val_dataloader = val_datamodule.val_dataloader() + test_dataloader = test_datamodule.test_dataloader() + + return { + "train": train_dataloader, + "val": val_dataloader, + "test": test_dataloader, + "data_module": train_datamodule, + } + + @pytest.fixture( params=[ dict(), @@ -258,4 +489,4 @@ def dataloaders_fixed_window_without_covariates(): train=False, batch_size=batch_size, num_workers=0 ) - return dict(train=train_dataloader, val=val_dataloader, test=test_dataloader) \ No newline at end of file + return dict(train=train_dataloader, val=val_dataloader, test=test_dataloader) diff --git a/pytorch_forecasting/tests/test_all_estimators.py b/pytorch_forecasting/tests/test_all_estimators.py index 8b273d449..dca0f30c3 100644 --- a/pytorch_forecasting/tests/test_all_estimators.py +++ b/pytorch_forecasting/tests/test_all_estimators.py @@ -262,6 +262,21 @@ def _integration( class TestAllPtForecasters(PackageConfig, BaseFixtureGenerator): """Generic tests for all objects in the mini package.""" + def _all_objects(self): + """Retrieve list of all object classes, excluding ptf-v2 objects.""" + obj_list = super()._all_objects() + + filtered_obj_list = [] + for obj in obj_list: + if hasattr(obj, "get_class_tag"): + object_type = obj.get_class_tag("object_type", None) + if object_type != "ptf-v2": + filtered_obj_list.append(obj) + else: + filtered_obj_list.append(obj) + + return filtered_obj_list + def test_doctest_examples(self, object_class): """Runs doctests for estimator class.""" from skbase.utils.doctest_run import run_doctest @@ -288,4 +303,4 @@ def test_integration( data_with_covariates = data_with_covariates.assign( volume=lambda x: x.volume.round() ) - _integration(object_class, data_with_covariates, tmp_path, **trainer_kwargs) \ No newline at end of file + _integration(object_class, data_with_covariates, tmp_path, **trainer_kwargs) diff --git a/pytorch_forecasting/tests/test_all_estimators_v2.py b/pytorch_forecasting/tests/test_all_estimators_v2.py index efbe170f0..8a68d8e17 100644 --- a/pytorch_forecasting/tests/test_all_estimators_v2.py +++ b/pytorch_forecasting/tests/test_all_estimators_v2.py @@ -105,9 +105,9 @@ class TestAllPtForecastersV2(PackageConfig, BaseFixtureGenerator): def test_doctest_examples(self, object_class): """Runs doctests for estimator class.""" - import doctest + from skbase.utils.doctest_run import run_doctest - doctest.run_docstring_examples(object_class, globals()) + run_doctest(object_class, name=f"class {object_class.__name__}") def test_integration( self, From e72486b79e4b5984400822e8e81ceec665f75148 Mon Sep 17 00:00:00 2001 From: Aryan Saini Date: Wed, 28 May 2025 16:59:50 +0530 Subject: [PATCH 080/139] linting --- pytorch_forecasting/models/deepar/_deepar_metadata.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pytorch_forecasting/models/deepar/_deepar_metadata.py b/pytorch_forecasting/models/deepar/_deepar_metadata.py index 59df96441..a9eb46a04 100644 --- a/pytorch_forecasting/models/deepar/_deepar_metadata.py +++ b/pytorch_forecasting/models/deepar/_deepar_metadata.py @@ -122,4 +122,4 @@ def get_test_train_params(cls): n_plotting_samples=100, trainer_kwargs=dict(accelerator="cpu"), ), - ] \ No newline at end of file + ] From 5142d52043da0a5f25689c66d3345972588ad7b8 Mon Sep 17 00:00:00 2001 From: Pranav Bhat Date: Wed, 28 May 2025 18:19:30 +0530 Subject: [PATCH 081/139] fix circular dependency error in en_embedding.py --- pytorch_forecasting/layers/embeddings/en_embedding.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pytorch_forecasting/layers/embeddings/en_embedding.py b/pytorch_forecasting/layers/embeddings/en_embedding.py index 82a5194d1..803b7f0e1 100644 --- a/pytorch_forecasting/layers/embeddings/en_embedding.py +++ b/pytorch_forecasting/layers/embeddings/en_embedding.py @@ -10,7 +10,9 @@ import torch.nn as nn import torch.nn.functional as F -from pytorch_forecasting.layers.embeddings import PositionalEmbedding +from pytorch_forecasting.layers.embeddings.positional_embedding import ( + PositionalEmbedding, +) class EnEmbedding(nn.Module): From a734f265970cccc4c7e2e944916ac2f2607afb0f Mon Sep 17 00:00:00 2001 From: Aryan Saini Date: Thu, 29 May 2025 22:58:11 +0530 Subject: [PATCH 082/139] refactor --- .../models/base/_base_model_v2.py | 42 ------ .../temporal_fusion_transformer/_tft_v2.py | 1 - pytorch_forecasting/tests/_conftest.py | 134 +++++++++--------- .../tests/test_all_estimators_v2.py | 4 +- 4 files changed, 69 insertions(+), 112 deletions(-) diff --git a/pytorch_forecasting/models/base/_base_model_v2.py b/pytorch_forecasting/models/base/_base_model_v2.py index a74f926b9..aceec0869 100644 --- a/pytorch_forecasting/models/base/_base_model_v2.py +++ b/pytorch_forecasting/models/base/_base_model_v2.py @@ -14,18 +14,6 @@ import torch.nn as nn from torch.optim import Optimizer -from pytorch_forecasting.metrics import ( - MAE, - MASE, - SMAPE, - DistributionLoss, - Metric, - MultiHorizonMetric, - MultiLoss, - QuantileLoss, - convert_torchmetric_to_pytorch_forecasting_metric, -) - class BaseModel(LightningModule): def __init__( @@ -116,7 +104,6 @@ def training_step( x, y = batch y_hat_dict = self(x) y_hat = y_hat_dict["prediction"] - y_hat, y = self._align_prediction_target_shapes(y_hat, y) loss = self.loss(y_hat, y) self.log( "train_loss", loss, on_step=True, on_epoch=True, prog_bar=True, logger=True @@ -145,7 +132,6 @@ def validation_step( x, y = batch y_hat_dict = self(x) y_hat = y_hat_dict["prediction"] - y_hat, y = self._align_prediction_target_shapes(y_hat, y) loss = self.loss(y_hat, y) self.log( "val_loss", loss, on_step=False, on_epoch=True, prog_bar=True, logger=True @@ -174,7 +160,6 @@ def test_step( x, y = batch y_hat_dict = self(x) y_hat = y_hat_dict["prediction"] - y_hat, y = self._align_prediction_target_shapes(y_hat, y) loss = self.loss(y_hat, y) self.log( "test_loss", loss, on_step=False, on_epoch=True, prog_bar=True, logger=True @@ -309,30 +294,3 @@ def log_metrics( prog_bar=True, logger=True, ) - - def _align_prediction_target_shapes( - self, y_hat: torch.Tensor, y: torch.Tensor - ) -> Tuple[torch.Tensor, torch.Tensor]: - """ - Align prediction and target tensor shapes for loss/metric calculation. - - Returns - ------- - Tuple of aligned prediction and target tensors - """ - if y.dim() == 3 and y.shape[-1] == 1: - y = y.squeeze(-1) - if y_hat.dim() < y.dim(): - y_hat = y_hat.unsqueeze(-1) - elif y_hat.dim() > y.dim(): - if y_hat.shape[-1] == 1: - y_hat = y_hat.squeeze(-1) - if y_hat.shape != y.shape: - if y_hat.numel() == y.numel(): - y_hat = y_hat.view(y.shape) - else: - raise ValueError( - f"Cannot align shapes: y_hat {y_hat.shape} vs y {y.shape}" - ) - - return y_hat, y diff --git a/pytorch_forecasting/models/temporal_fusion_transformer/_tft_v2.py b/pytorch_forecasting/models/temporal_fusion_transformer/_tft_v2.py index fa4f8f6c0..e74f7cf32 100644 --- a/pytorch_forecasting/models/temporal_fusion_transformer/_tft_v2.py +++ b/pytorch_forecasting/models/temporal_fusion_transformer/_tft_v2.py @@ -9,7 +9,6 @@ import torch.nn as nn from torch.optim import Optimizer -from pytorch_forecasting.metrics import Metric from pytorch_forecasting.models.base._base_model_v2 import BaseModel diff --git a/pytorch_forecasting/tests/_conftest.py b/pytorch_forecasting/tests/_conftest.py index caa0f5600..d175db0c6 100644 --- a/pytorch_forecasting/tests/_conftest.py +++ b/pytorch_forecasting/tests/_conftest.py @@ -93,73 +93,6 @@ def make_dataloaders(data_with_covariates, **kwargs): return dict(train=train_dataloader, val=val_dataloader, test=test_dataloader) -@pytest.fixture( - params=[ - dict(), - dict( - static_categoricals=["agency", "sku"], - static_reals=["avg_population_2017", "avg_yearly_household_income_2017"], - time_varying_known_categoricals=["special_days", "month"], - variable_groups=dict( - special_days=[ - "easter_day", - "good_friday", - "new_year", - "christmas", - "labor_day", - "independence_day", - "revolution_day_memorial", - "regional_games", - "fifa_u_17_world_cup", - "football_gold_cup", - "beer_capital", - "music_fest", - ] - ), - time_varying_known_reals=[ - "time_idx", - "price_regular", - "price_actual", - "discount", - "discount_in_percent", - ], - time_varying_unknown_categoricals=[], - time_varying_unknown_reals=[ - "volume", - "log_volume", - "industry_volume", - "soda_volume", - "avg_max_temp", - ], - constant_fill_strategy={"volume": 0}, - categorical_encoders={"sku": NaNLabelEncoder(add_nan=True)}, - ), - dict(static_categoricals=["agency", "sku"]), - dict(randomize_length=True, min_encoder_length=2), - dict(target_normalizer=EncoderNormalizer(), min_encoder_length=2), - dict(target_normalizer=GroupNormalizer(transformation="log1p")), - dict( - target_normalizer=GroupNormalizer( - groups=["agency", "sku"], transformation="softplus", center=False - ) - ), - dict(target="agency"), - # test multiple targets - dict(target=["industry_volume", "volume"]), - dict(target=["agency", "volume"]), - dict( - target=["agency", "volume"], min_encoder_length=1, min_prediction_length=1 - ), - dict(target=["agency", "volume"], weight="volume"), - # test weights - dict(target="volume", weight="volume"), - ], - scope="session", -) -def multiple_dataloaders_with_covariates(data_with_covariates, request): - return make_dataloaders(data_with_covariates, **request.param) - - @pytest.fixture(scope="session") def data_with_covariates_v2(): """Create synthetic time series data with all numerical features.""" @@ -387,6 +320,73 @@ def make_dataloaders_v2(data_with_covariates, **kwargs): } +@pytest.fixture( + params=[ + dict(), + dict( + static_categoricals=["agency", "sku"], + static_reals=["avg_population_2017", "avg_yearly_household_income_2017"], + time_varying_known_categoricals=["special_days", "month"], + variable_groups=dict( + special_days=[ + "easter_day", + "good_friday", + "new_year", + "christmas", + "labor_day", + "independence_day", + "revolution_day_memorial", + "regional_games", + "fifa_u_17_world_cup", + "football_gold_cup", + "beer_capital", + "music_fest", + ] + ), + time_varying_known_reals=[ + "time_idx", + "price_regular", + "price_actual", + "discount", + "discount_in_percent", + ], + time_varying_unknown_categoricals=[], + time_varying_unknown_reals=[ + "volume", + "log_volume", + "industry_volume", + "soda_volume", + "avg_max_temp", + ], + constant_fill_strategy={"volume": 0}, + categorical_encoders={"sku": NaNLabelEncoder(add_nan=True)}, + ), + dict(static_categoricals=["agency", "sku"]), + dict(randomize_length=True, min_encoder_length=2), + dict(target_normalizer=EncoderNormalizer(), min_encoder_length=2), + dict(target_normalizer=GroupNormalizer(transformation="log1p")), + dict( + target_normalizer=GroupNormalizer( + groups=["agency", "sku"], transformation="softplus", center=False + ) + ), + dict(target="agency"), + # test multiple targets + dict(target=["industry_volume", "volume"]), + dict(target=["agency", "volume"]), + dict( + target=["agency", "volume"], min_encoder_length=1, min_prediction_length=1 + ), + dict(target=["agency", "volume"], weight="volume"), + # test weights + dict(target="volume", weight="volume"), + ], + scope="session", +) +def multiple_dataloaders_with_covariates(data_with_covariates, request): + return make_dataloaders(data_with_covariates, **request.param) + + @pytest.fixture(scope="session") def dataloaders_with_different_encoder_decoder_length(data_with_covariates): return make_dataloaders( diff --git a/pytorch_forecasting/tests/test_all_estimators_v2.py b/pytorch_forecasting/tests/test_all_estimators_v2.py index 8a68d8e17..29e6ab22a 100644 --- a/pytorch_forecasting/tests/test_all_estimators_v2.py +++ b/pytorch_forecasting/tests/test_all_estimators_v2.py @@ -6,8 +6,8 @@ import lightning.pytorch as pl from lightning.pytorch.callbacks import EarlyStopping from lightning.pytorch.loggers import TensorBoardLogger +import torch.nn as nn -from pytorch_forecasting.metrics import SMAPE from pytorch_forecasting.tests._conftest import make_dataloaders_v2 as make_dataloaders from pytorch_forecasting.tests.test_all_estimators import ( BaseFixtureGenerator, @@ -73,7 +73,7 @@ def _integration( net = estimator_cls( metadata=metadata, - loss=SMAPE(), + loss=nn.MSELoss(), **kwargs, ) From 7f466b29a742d6af8fc5360ee02d1e39bab0a3c0 Mon Sep 17 00:00:00 2001 From: Aryan Saini Date: Fri, 30 May 2025 00:08:56 +0530 Subject: [PATCH 083/139] Add more test_params --- .../tft_v2_metadata.py | 17 +++++++++++++++++ pytorch_forecasting/tests/_conftest.py | 4 ++-- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/pytorch_forecasting/models/temporal_fusion_transformer/tft_v2_metadata.py b/pytorch_forecasting/models/temporal_fusion_transformer/tft_v2_metadata.py index 2a98767c2..91e2440ed 100644 --- a/pytorch_forecasting/models/temporal_fusion_transformer/tft_v2_metadata.py +++ b/pytorch_forecasting/models/temporal_fusion_transformer/tft_v2_metadata.py @@ -41,4 +41,21 @@ def get_test_train_params(cls): hidden_size=25, attention_head_size=5, ), + dict( + data_loader_kwargs=dict(max_encoder_length=5, max_prediction_length=3) + ), + dict( + hidden_size=24, + attention_head_size=8, + data_loader_kwargs=dict( + max_encoder_length=5, + max_prediction_length=3, + add_relative_time_idx=False, + ), + ), + dict( + hidden_size=12, + data_loader_kwargs=dict(max_encoder_length=7, max_prediction_length=10), + ), + dict(attention_head_size=2), ] diff --git a/pytorch_forecasting/tests/_conftest.py b/pytorch_forecasting/tests/_conftest.py index d175db0c6..7bc19920a 100644 --- a/pytorch_forecasting/tests/_conftest.py +++ b/pytorch_forecasting/tests/_conftest.py @@ -175,8 +175,8 @@ def make_dataloaders_v2(data_with_covariates, **kwargs): """Create dataloaders with consistent encoder/decoder features.""" training_cutoff = "2016-09-01" - max_encoder_length = 4 - max_prediction_length = 3 + max_encoder_length = kwargs.get("max_encoder_length", 4) + max_prediction_length = kwargs.get("max_prediction_length", 3) target_col = kwargs.get("target", "target") group_cols = kwargs.get("group_ids", ["agency_encoded", "sku_encoded"]) From 8a680dfc8dcf6031c5d3e154568dbb7bf5855402 Mon Sep 17 00:00:00 2001 From: Pranav Bhat Date: Fri, 30 May 2025 00:10:43 +0530 Subject: [PATCH 084/139] add prelimnary tests for tslib d2 --- pytorch_forecasting/data/tslib_data_module.py | 1 - tests/test_data/test_tslib_data_module.py | 256 ++++++++++++++++++ 2 files changed, 256 insertions(+), 1 deletion(-) create mode 100644 tests/test_data/test_tslib_data_module.py diff --git a/pytorch_forecasting/data/tslib_data_module.py b/pytorch_forecasting/data/tslib_data_module.py index 2a58f051b..267caba98 100644 --- a/pytorch_forecasting/data/tslib_data_module.py +++ b/pytorch_forecasting/data/tslib_data_module.py @@ -336,7 +336,6 @@ def _prepare_metadata(self) -> Dict[str, Any]: "known": [], "unknown": [], "target": [], - "all": [], } cols = ds_metadata.get("cols", {}) diff --git a/tests/test_data/test_tslib_data_module.py b/tests/test_data/test_tslib_data_module.py new file mode 100644 index 000000000..6a6496307 --- /dev/null +++ b/tests/test_data/test_tslib_data_module.py @@ -0,0 +1,256 @@ +import numpy as np +import pandas as pd +import pytest +import torch + +from pytorch_forecasting.data.examples import get_stallion_data +from pytorch_forecasting.data.timeseries import TimeSeries +from pytorch_forecasting.data.tslib_data_module import TslibDataModule + + +@pytest.fixture(scope="session") +def sample_timeseries_data(): + """Fixture to generate a sample TimeSeries.""" + + np.random.seed(42) + n_series = 5 + n_timesteps = 50 + + data = [] + + for series_id in range(n_series): + for time_idx in range(n_timesteps): + + # create a realistic time series with trend, seasonality, and noise + target = ( + 10 + + 0.1 * time_idx + + np.sin(2 * np.pi * time_idx / 12) + + np.random.randn() * 0.5 + ) # noqa: E501 + + cat_a = np.random.choice([0, 1, 2]) + + feature_1 = np.random.randn() + time_idx * 0.01 + feature_2 = target * 0.8 + np.random.randn() * 0.2 + feature_3 = np.sin(time_idx / 5) + np.random.randn() * 0.1 + + static_feature = series_id * 2.5 + + data.append( + { + "series_id": series_id, + "time_idx": time_idx, + "target": target, + "cat_a": cat_a, + "feature_1": feature_1, + "feature_2": feature_2, + "feature_3": feature_3, + "static_feature": static_feature, + } + ) + + df = pd.DataFrame(data) + + time_series = TimeSeries( + data=df, + time="time_idx", + target="target", + group=["series_id"], + num=["feature_1", "feature_2", "feature_3"], + cat=["cat_a"], + unknown=["feature_2", "target"], + static=["static_feature"], + known=["feature_1", "feature_3"], + ) + return time_series + + +@pytest.fixture +def tslib_data_module(sample_timeseries_data): + """Fixture for TSLibDataModule.""" + return TslibDataModule( + time_series_dataset=sample_timeseries_data, + context_length=8, + prediction_length=4, + batch_size=2, # Smaller batch size for faster testing + num_workers=0, # Avoid multiprocessing issues in tests + ) + + +def test_init(sample_timeseries_data): + """Test the initialization of the data module.""" + + tslib_dm = TslibDataModule( + time_series_dataset=sample_timeseries_data, + context_length=32, + prediction_length=16, + batch_size=8, + ) + + assert tslib_dm.time_series_dataset == sample_timeseries_data + assert tslib_dm.context_length == 32 + assert tslib_dm.prediction_length == 16 + assert tslib_dm.batch_size == 8 + assert tslib_dm.train_val_test_split == (0.7, 0.15, 0.15) + + assert isinstance(tslib_dm.time_series_metadata, dict) + assert "cols" in tslib_dm.time_series_metadata + + +def test_prepare_metadata(tslib_data_module): + """Test the metadata preparation to ensure correct metadata extraction + and structure.""" + + metadata = tslib_data_module.metadata + + assert isinstance(metadata, dict) + + assert "feature_names" in metadata + assert "feature_indices" in metadata + assert "n_features" in metadata + assert "context_length" in metadata + assert "prediction_length" in metadata + assert "freq" in metadata + assert "features" in metadata + + assert "categorical" in metadata["feature_names"] + assert "continuous" in metadata["feature_names"] + assert "static" in metadata["feature_names"] + assert "known" in metadata["feature_names"] + assert "unknown" in metadata["feature_names"] + assert "target" in metadata["feature_names"] + assert "all" in metadata["feature_names"] + assert "static_categorical" in metadata["feature_names"] + assert "static_continuous" in metadata["feature_names"] + + assert "categorical" in metadata["feature_indices"] + assert "continuous" in metadata["feature_indices"] + assert "static" in metadata["feature_indices"] + assert "known" in metadata["feature_indices"] + assert "unknown" in metadata["feature_indices"] + assert "target" in metadata["feature_indices"] + + for k in metadata["n_features"]: + assert k in metadata["n_features"] + assert metadata["n_features"][k] == len(metadata["feature_names"][k]) + + assert metadata["context_length"] == tslib_data_module.context_length + assert metadata["prediction_length"] == tslib_data_module.prediction_length + assert metadata["freq"] == tslib_data_module.time_series_dataset.freq + assert metadata["features"] == tslib_data_module.time_series_dataset.features + + +def test_setup(tslib_data_module): + """Test the setup method to ensure datamodule is setup for training, + testing, and validation.""" + + tslib_data_module.setup(stage="fit") + assert hasattr(tslib_data_module, "train_dataset") + assert hasattr(tslib_data_module, "val_dataset") + assert len(tslib_data_module._train_windows) > 0 + assert len(tslib_data_module._val_windows) > 0 + + tslib_data_module.setup(stage="test") + assert hasattr(tslib_data_module, "test_dataset") + assert len(tslib_data_module._test_windows) > 0 + + tslib_data_module.setup(stage="predict") + assert hasattr(tslib_data_module, "predict_dataset") + assert len(tslib_data_module._predict_windows) > 0 + + +def test_train_dataloader(tslib_data_module): + """Test the train dataloader to ensure it returns the batches of the data, + and all hyperparameters are correctly set.""" + + tslib_data_module.setup(stage="fit") + train_data_loader = tslib_data_module.train_dataloader() + + assert hasattr(train_data_loader, "batch_size") + assert train_data_loader.batch_size == tslib_data_module.batch_size + assert train_data_loader.num_workers == tslib_data_module.num_workers + + val_data_loader = tslib_data_module.val_dataloader() + assert hasattr(val_data_loader, "batch_size") + + +def test_test_dataloader(tslib_data_module): + """Test the test dataloader to ensure it returns the batches of the data, + and all hyperparameters are correctly set.""" + + tslib_data_module.setup(stage="test") + test_data_loader = tslib_data_module.test_dataloader() + + assert hasattr(test_data_loader, "batch_size") + assert test_data_loader.batch_size == tslib_data_module.batch_size + assert test_data_loader.num_workers == tslib_data_module.num_workers + + +def test_predict_dataloader(tslib_data_module): + """Test the predict dataloader to ensure it returns the batches of the data, + and all hyperparameters are correctly set.""" + + tslib_data_module.setup(stage="predict") + predict_data_loader = tslib_data_module.predict_dataloader() + + assert hasattr(predict_data_loader, "batch_size") + assert predict_data_loader.batch_size == tslib_data_module.batch_size + assert predict_data_loader.num_workers == tslib_data_module.num_workers + + +def test_tslib_dataset(tslib_data_module): + """Test the _TslibDataset to ensure it is correctly initialized + and ensure correct outputs from __getitem__.""" + + tslib_data_module.setup(stage="fit") + assert hasattr(tslib_data_module, "train_dataset") + train_dataset = tslib_data_module.train_dataset + + assert len(train_dataset) > 0, "The train dataset is empty!" + + sample_x, sample_y = train_dataset[0] + + assert isinstance(sample_x, dict), "Sample x should be a dictionary." + assert isinstance(sample_y, torch.Tensor), "Sample y should be a PyTorch tensor." + + expected_keys = [ + "history_cont", + "history_cat", + "future_cont", + "future_cat", + "history_length", + "future_length", + "history_mask", + "future_mask", + "groups", + "history_time_idx", + "future_time_idx", + "future_target", + "future_target_len", + ] + + for key in expected_keys: + assert key in sample_x, f"Key '{key}' not found in sample_x." + + context_length = tslib_data_module.context_length + prediction_length = tslib_data_module.prediction_length + metadata = tslib_data_module.metadata + + assert sample_x["history_cont"].shape[0] == context_length + assert sample_x["history_cat"].shape[0] == context_length + assert sample_x["future_cont"].shape[0] == prediction_length + assert sample_x["future_cat"].shape[0] == prediction_length + assert sample_x["history_target"].shape[0] == context_length + assert sample_x["future_target"].shape[0] == prediction_length + + assert sample_x["future_cont"].shape[1] == len(metadata["feature_names"]["known"]) + assert sample_x["future_cat"].shape[1] == len(metadata[""]) + + assert sample_y.shape[0] == prediction_length + + assert sample_x["history_cont"].dtype == torch.float32 + assert sample_x["future_cont"].dtype == torch.float32 + assert sample_x["history_target"].dtype == torch.float32 + + assert sample_y.dtype == torch.float32 From 0ccb078f37ceeabb3a3bdc57a87346c632fb3fe6 Mon Sep 17 00:00:00 2001 From: Pranav Bhat Date: Fri, 30 May 2025 01:06:35 +0530 Subject: [PATCH 085/139] add collate, setup and dataset tests tests still failing --- tests/test_data/test_tslib_data_module.py | 62 +++++++++++++++++++++-- 1 file changed, 58 insertions(+), 4 deletions(-) diff --git a/tests/test_data/test_tslib_data_module.py b/tests/test_data/test_tslib_data_module.py index 6a6496307..aea3bcd46 100644 --- a/tests/test_data/test_tslib_data_module.py +++ b/tests/test_data/test_tslib_data_module.py @@ -137,8 +137,6 @@ def test_prepare_metadata(tslib_data_module): assert metadata["context_length"] == tslib_data_module.context_length assert metadata["prediction_length"] == tslib_data_module.prediction_length - assert metadata["freq"] == tslib_data_module.time_series_dataset.freq - assert metadata["features"] == tslib_data_module.time_series_dataset.features def test_setup(tslib_data_module): @@ -244,8 +242,26 @@ def test_tslib_dataset(tslib_data_module): assert sample_x["history_target"].shape[0] == context_length assert sample_x["future_target"].shape[0] == prediction_length - assert sample_x["future_cont"].shape[1] == len(metadata["feature_names"]["known"]) - assert sample_x["future_cat"].shape[1] == len(metadata[""]) + known_cat_count = len( + [ + col + for col in metadata["cols"]["x"] + if metadata["col_type"].get(col) == "C" + and metadata["col_known"].get(col) == "K" + ] + ) + + known_cont_count = len( + [ + col + for col in metadata["cols"]["x"] + if metadata["col_type"].get(col) == "F" + and metadata["col_known"].get(col) == "K" + ] + ) + + assert sample_x["future_cont"].shape[1] == known_cont_count + assert sample_x["future_cat"].shape[1] == known_cat_count assert sample_y.shape[0] == prediction_length @@ -254,3 +270,41 @@ def test_tslib_dataset(tslib_data_module): assert sample_x["history_target"].dtype == torch.float32 assert sample_y.dtype == torch.float32 + + +def test_collate_fn(tslib_data_module): + """Test the collate function in the TslibDataModule to ensure it correctly + collates the data into batches and properly handles stacking of batches.""" + + batch_size = 5 + + batches = [tslib_data_module.train_dataset[i] for i in range(batch_size)] + + x_batch, y_batch = tslib_data_module.collate_fn(batches) + + for key in x_batch: + assert x_batch[key].shape[0] == batch_size + + metadata = tslib_data_module.time_series_metadata + known_cat_count = len( + [ + col + for col in metadata["cols"]["x"] + if metadata["col_type"].get(col) == "C" + and metadata["col_known"].get(col) == "K" + ] + ) + + known_cont_count = len( + [ + col + for col in metadata["cols"]["x"] + if metadata["col_type"].get(col) == "F" + and metadata["col_known"].get(col) == "K" + ] + ) + + assert x_batch["future_cat"].shape[1] == known_cat_count + assert x_batch["future_cont"].shape[1] == known_cont_count + assert y_batch.shape[0] == batch_size + assert y_batch.shape[1] == tslib_data_module.prediction_length From 826ac31aaa12140d5ab85d581af90b84585f8eb0 Mon Sep 17 00:00:00 2001 From: Pranav Bhat Date: Fri, 30 May 2025 11:02:49 +0530 Subject: [PATCH 086/139] fix failing setup and tslib_dataset tests --- pytorch_forecasting/data/tslib_data_module.py | 4 +- tests/test_data/test_tslib_data_module.py | 60 ++++++++++++++++++- 2 files changed, 60 insertions(+), 4 deletions(-) diff --git a/pytorch_forecasting/data/tslib_data_module.py b/pytorch_forecasting/data/tslib_data_module.py index 267caba98..e7daba480 100644 --- a/pytorch_forecasting/data/tslib_data_module.py +++ b/pytorch_forecasting/data/tslib_data_module.py @@ -258,7 +258,9 @@ def __init__( self.batch_size = batch_size self.num_workers = num_workers self.train_val_test_split = train_val_test_split - self.collate_fn = collate_fn + self.collate_fn = ( + collate_fn if collate_fn is not None else self.__class__.collate_fn + ) # noqa: E501 self.kwargs = kwargs warnings.warn( diff --git a/tests/test_data/test_tslib_data_module.py b/tests/test_data/test_tslib_data_module.py index aea3bcd46..2124fde28 100644 --- a/tests/test_data/test_tslib_data_module.py +++ b/tests/test_data/test_tslib_data_module.py @@ -13,7 +13,7 @@ def sample_timeseries_data(): """Fixture to generate a sample TimeSeries.""" np.random.seed(42) - n_series = 5 + n_series = 20 n_timesteps = 50 data = [] @@ -233,7 +233,7 @@ def test_tslib_dataset(tslib_data_module): context_length = tslib_data_module.context_length prediction_length = tslib_data_module.prediction_length - metadata = tslib_data_module.metadata + metadata = tslib_data_module.time_series_metadata assert sample_x["history_cont"].shape[0] == context_length assert sample_x["history_cat"].shape[0] == context_length @@ -276,7 +276,8 @@ def test_collate_fn(tslib_data_module): """Test the collate function in the TslibDataModule to ensure it correctly collates the data into batches and properly handles stacking of batches.""" - batch_size = 5 + tslib_data_module.setup(stage="fit") + batch_size = 2 batches = [tslib_data_module.train_dataset[i] for i in range(batch_size)] @@ -308,3 +309,56 @@ def test_collate_fn(tslib_data_module): assert x_batch["future_cont"].shape[1] == known_cont_count assert y_batch.shape[0] == batch_size assert y_batch.shape[1] == tslib_data_module.prediction_length + + +def test_create_windows(tslib_data_module): + """Test the _create_windows method to ensures correct creation + of windows for training, validation and testing.""" + + tslib_data_module.setup(stage="fit") + train_indices = tslib_data_module._train_indices + train_windows = tslib_data_module._create_windows(train_indices) + + assert len(train_windows) > 0, "No training windows created!" + + for windows in train_windows: + assert isinstance(windows, tuple), "Windows should be a tuple." + + assert len(windows) == 4, "Each window should have 4 elements." + + series_idx, start_idx, context_length, prediction_length = windows + + assert isinstance(series_idx, int), "series_idx should be an integer." + + assert isinstance(start_idx, int), "start_idx should be an integer." + + assert ( + context_length == tslib_data_module.context_length + ), "context_length should match the datamodule's context_length." + + assert ( + prediction_length == tslib_data_module.prediction_length + ), "prediction_length should match the datamodule's prediction_length." + + assert ( + 0 <= series_idx < len(tslib_data_module.time_series_dataset) + ), "series_idx should be within the range of the dataset length." + + min_required_length = context_length + prediction_length + + time_series_dataset = tslib_data_module.time_series_dataset + series_length = len(time_series_dataset[series_idx]) + + assert ( + start_idx + min_required_length <= series_length + ), "Window extended beyond series length." + + all_indices = torch.arange(len(tslib_data_module.time_series_dataset)) + all_windows = tslib_data_module._create_windows(all_indices) + assert ( + len(all_windows) >= train_windows + ), "Should have more windows than all indices." + + empty_windows = tslib_data_module._create_windows(torch.tensor([])) + + assert len(empty_windows) == 0, "Should return empty list for empty index." From d70b07cb5048950d4dc6d38490bd8bbb382835da Mon Sep 17 00:00:00 2001 From: Pranav Bhat Date: Fri, 30 May 2025 12:52:23 +0530 Subject: [PATCH 087/139] fix incorrect metadata handling in tslib dataset and fix tests for collate and window creation --- pytorch_forecasting/data/tslib_data_module.py | 29 +++++---- tests/test_data/test_tslib_data_module.py | 59 ++++++++++--------- 2 files changed, 50 insertions(+), 38 deletions(-) diff --git a/pytorch_forecasting/data/tslib_data_module.py b/pytorch_forecasting/data/tslib_data_module.py index e7daba480..e0cf818ac 100644 --- a/pytorch_forecasting/data/tslib_data_module.py +++ b/pytorch_forecasting/data/tslib_data_module.py @@ -80,29 +80,34 @@ def __getitem__(self, idx: int) -> Dict[str, Any]: processed_data = self.data_module._preprocess_data(series_idx) + continous_features = processed_data["features"]["continuous"] + categorical_features = processed_data["features"]["categorical"] + end_idx = start_idx + context_length + prediction_length history_indices = slice(start_idx, start_idx + context_length) future_indices = slice(start_idx + context_length, end_idx) - feature_indices = self.data_module.metadata["feature_indices"] + metadata = self.data_module.metadata - history_cont = processed_data["features"]["continuous"][history_indices] - history_cat = processed_data["features"]["categorical"][history_indices] + history_cont = continous_features[history_indices] + history_cat = categorical_features[history_indices] - future_cont = processed_data["features"]["continuous"][future_indices] - future_cat = processed_data["features"]["categorical"][future_indices] + future_cont = continous_features[future_indices] + future_cat = categorical_features[future_indices] - known_indices = set(feature_indices["known"]) + known_features = set(metadata["feature_names"]["known"]) + continuous_feature_names = metadata["feature_names"]["continuous"] + categorical_feature_names = metadata["feature_names"]["categorical"] # use masking to filter out known and unknow features. - cont_indices = feature_indices["continuous"] cont_known_mask = torch.tensor( - [i in known_indices for i in cont_indices], dtype=torch.bool + [feat in known_features for feat in continuous_feature_names], + dtype=torch.bool, ) - cat_indices = feature_indices["categorical"] cat_known_mask = torch.tensor( - [i in known_indices for i in cat_indices], dtype=torch.bool + [feat in known_features for feat in categorical_feature_names], + dtype=torch.bool, ) future_cont = ( @@ -707,9 +712,11 @@ def collate_fn(batch): "future_target_len": torch.stack( [x["future_target_len"] for x, _ in batch] ), - "target_scale": torch.stack([x["target_scale"] for x, _ in batch]), } + if "target_scale" in batch[0][0]: + x_batch["target_scale"] = torch.stack([x["target_scale"] for x, _ in batch]) + if "history_relative_time_idx" in batch[0][0]: x_batch["history_relative_time_idx"] = torch.stack( [x["history_relative_time_idx"] for x, _ in batch] diff --git a/tests/test_data/test_tslib_data_module.py b/tests/test_data/test_tslib_data_module.py index 2124fde28..4e2e73c43 100644 --- a/tests/test_data/test_tslib_data_module.py +++ b/tests/test_data/test_tslib_data_module.py @@ -59,7 +59,7 @@ def sample_timeseries_data(): group=["series_id"], num=["feature_1", "feature_2", "feature_3"], cat=["cat_a"], - unknown=["feature_2", "target"], + unknown=["feature_2", "target", "cat_a"], static=["static_feature"], known=["feature_1", "feature_3"], ) @@ -233,7 +233,7 @@ def test_tslib_dataset(tslib_data_module): context_length = tslib_data_module.context_length prediction_length = tslib_data_module.prediction_length - metadata = tslib_data_module.time_series_metadata + metadata = tslib_data_module.metadata assert sample_x["history_cont"].shape[0] == context_length assert sample_x["history_cat"].shape[0] == context_length @@ -244,22 +244,21 @@ def test_tslib_dataset(tslib_data_module): known_cat_count = len( [ - col - for col in metadata["cols"]["x"] - if metadata["col_type"].get(col) == "C" - and metadata["col_known"].get(col) == "K" + name + for name in metadata["feature_names"]["known"] + if name in metadata["feature_names"]["categorical"] ] ) - known_cont_count = len( [ - col - for col in metadata["cols"]["x"] - if metadata["col_type"].get(col) == "F" - and metadata["col_known"].get(col) == "K" + name + for name in metadata["feature_names"]["known"] + if name in metadata["feature_names"]["continuous"] ] ) + print(sample_x["future_cont"].shape) + assert sample_x["future_cont"].shape[1] == known_cont_count assert sample_x["future_cat"].shape[1] == known_cat_count @@ -286,27 +285,26 @@ def test_collate_fn(tslib_data_module): for key in x_batch: assert x_batch[key].shape[0] == batch_size - metadata = tslib_data_module.time_series_metadata + metadata = tslib_data_module.metadata + known_cat_count = len( [ - col - for col in metadata["cols"]["x"] - if metadata["col_type"].get(col) == "C" - and metadata["col_known"].get(col) == "K" + name + for name in metadata["feature_names"]["known"] + if name in metadata["feature_names"]["categorical"] ] ) - known_cont_count = len( [ - col - for col in metadata["cols"]["x"] - if metadata["col_type"].get(col) == "F" - and metadata["col_known"].get(col) == "K" + name + for name in metadata["feature_names"]["known"] + if name in metadata["feature_names"]["continuous"] ] ) - assert x_batch["future_cat"].shape[1] == known_cat_count - assert x_batch["future_cont"].shape[1] == known_cont_count + assert x_batch["future_cont"].shape[2] == known_cont_count + assert x_batch["future_cat"].shape[2] == known_cat_count + # print(x_batch["future_cont"].shape) assert y_batch.shape[0] == batch_size assert y_batch.shape[1] == tslib_data_module.prediction_length @@ -347,16 +345,23 @@ def test_create_windows(tslib_data_module): min_required_length = context_length + prediction_length time_series_dataset = tslib_data_module.time_series_dataset - series_length = len(time_series_dataset[series_idx]) - + # print(type(time_series_dataset[series_idx])) + sample = time_series_dataset[series_idx] + + if "t" in sample: + series_length = len(sample["t"]) + elif "y" in sample: + series_length = len(sample["y"]) + else: + series_length = len(sample) assert ( start_idx + min_required_length <= series_length ), "Window extended beyond series length." all_indices = torch.arange(len(tslib_data_module.time_series_dataset)) all_windows = tslib_data_module._create_windows(all_indices) - assert ( - len(all_windows) >= train_windows + assert len(all_windows) >= len( + train_windows ), "Should have more windows than all indices." empty_windows = tslib_data_module._create_windows(torch.tensor([])) From 7b4114007b250339138ce5587d87a6f3a2ee8677 Mon Sep 17 00:00:00 2001 From: Pranav Bhat Date: Fri, 30 May 2025 13:33:02 +0530 Subject: [PATCH 088/139] fix code to comply with new linting syntax rules --- examples/tslib_v2_example.ipynb | 4 +- pytorch_forecasting/data/tslib_data_module.py | 42 +++++++++---------- .../layers/attention/attention_layer.py | 2 +- .../layers/attention/full_attention.py | 2 +- .../layers/embeddings/data_embedding.py | 2 +- .../layers/embeddings/en_embedding.py | 2 +- .../layers/embeddings/positional_embedding.py | 2 +- .../layers/encoders/encoder.py | 2 +- .../layers/encoders/encoder_layer.py | 2 +- .../models/base/_tslib_base_model_v2.py | 42 +++++++++---------- .../models/timexer/_timexer.py | 32 +++++++------- 11 files changed, 67 insertions(+), 67 deletions(-) diff --git a/examples/tslib_v2_example.ipynb b/examples/tslib_v2_example.ipynb index 3b7326c4b..bebd35619 100644 --- a/examples/tslib_v2_example.ipynb +++ b/examples/tslib_v2_example.ipynb @@ -75,12 +75,12 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": null, "id": "550a3fbf", "metadata": {}, "outputs": [], "source": [ - "from typing import Any, Dict, List, Optional, Tuple, Union\n", + "from typing import Any, Optional, Union\n", "\n", "import numpy as np\n", "import pandas as pd\n", diff --git a/pytorch_forecasting/data/tslib_data_module.py b/pytorch_forecasting/data/tslib_data_module.py index e0cf818ac..747b45387 100644 --- a/pytorch_forecasting/data/tslib_data_module.py +++ b/pytorch_forecasting/data/tslib_data_module.py @@ -2,7 +2,7 @@ Experimmental data module for integrating `tslib` time series deep learning library. """ -from typing import Any, Dict, List, Optional, Tuple, Union +from typing import Any, Optional, Union import warnings from lightning.pytorch import LightningDataModule @@ -34,7 +34,7 @@ class _TslibDataset(Dataset): data_module : TslibDataModule The data module that contains the metadata and other configurations for the dataset. - windows: List[Tuple[int, int, int, int]] + windows: list[tuple[int, int, int, int]] A list of tuples where each tuple contains: - series_idx: Index of time series in the dataset - start_idx: Start index of the window @@ -48,7 +48,7 @@ def __init__( self, dataset: TimeSeries, data_module: "TslibDataModule", - windows: List[Tuple[int, int, int, int]], + windows: list[tuple[int, int, int, int]], add_relative_time_idx: bool = False, ): self.dataset = dataset @@ -59,7 +59,7 @@ def __init__( def __len__(self) -> int: return len(self.windows) - def __getitem__(self, idx: int) -> Dict[str, Any]: + def __getitem__(self, idx: int) -> dict[str, Any]: """ Get the processed dataset item at the given index. @@ -70,7 +70,7 @@ def __getitem__(self, idx: int) -> Dict[str, Any]: Returns ------- - x: Dict[str, torch.Tensor] + x: dict[str, torch.Tensor] A dictionary containing the processed data. y: torch.Tensor The target variable. @@ -205,10 +205,10 @@ class TslibDataModule(LightningDataModule): add_target_scales: bool = False Whether to add target scaling info. target_normalizer : - Union[NORMALIZER, str, List[NORMALIZER], Tuple[NORMALIZER], None], + Union[NORMALIZER, str, list[NORMALIZER], tuple[NORMALIZER], None], default="auto" Normalizer for the target variable. If "auto", uses `RobustScaler`. - scalers : Optional[Dict[str, Union[StandardScaler, RobustScaler, TorchNormalizer]]], default=None #noqa: E501 + scalers : Optional[dict[str, Union[StandardScaler, RobustScaler, TorchNormalizer]]], default=None #noqa: E501 Dictionary of feature scalers. shuffle : bool, default=True Whether to shuffle the data at every epoch. @@ -235,10 +235,10 @@ def __init__( add_relative_time_idx: bool = False, add_target_scales: bool = False, target_normalizer: Union[ - NORMALIZER, str, List[NORMALIZER], Tuple[NORMALIZER], None + NORMALIZER, str, list[NORMALIZER], tuple[NORMALIZER], None ] = "auto", # noqa: E501 scalers: Optional[ - Dict[ + dict[ str, Union[StandardScaler, RobustScaler, TorchNormalizer, EncoderNormalizer], ] @@ -247,7 +247,7 @@ def __init__( window_stride: int = 1, batch_size: int = 32, num_workers: int = 0, - train_val_test_split: Tuple[float, float, float] = (0.7, 0.15, 0.15), + train_val_test_split: tuple[float, float, float] = (0.7, 0.15, 0.15), collate_fn: Optional[callable] = None, **kwargs, ) -> None: @@ -301,18 +301,18 @@ def __init__( else: self.continuous_indices.append(idx) - def _prepare_metadata(self) -> Dict[str, Any]: + def _prepare_metadata(self) -> dict[str, Any]: """ Prepare metadata for `tslib` time series data module. Returns ------- dict containing the following as keys: - - feature_names: Dict[str, List[str]] + - feature_names: dict[str, list[str]] Dictionary of feature names for each feature type. - - feature_indices: Dict[str, List[int]] + - feature_indices: dict[str, list[int]] Dictionary of feature indices for each feature type. - - n_features: Dict[str, int] + - n_features: dict[str, int] Dictionary of number of features for each feature type. - context_length: int Length of the context window for the model, as set in the data module. @@ -398,7 +398,7 @@ def _prepare_metadata(self) -> Dict[str, Any]: return metadata @property - def metadata(self) -> Dict[str, Any]: + def metadata(self) -> dict[str, Any]: """ " Compute the metadata via the `_prepare_metadata` method. This method is called when the `metadata` property is accessed for the first. @@ -412,7 +412,7 @@ def metadata(self) -> Dict[str, Any]: self._metadata = self._prepare_metadata() return self._metadata - def _preprocess_data(self, idx: torch.Tensor) -> List[Dict[str, Any]]: + def _preprocess_data(self, idx: torch.Tensor) -> list[dict[str, Any]]: """ Process the the time series data at the given index, before feeding it to the `_TslibDataset` class. @@ -424,7 +424,7 @@ def _preprocess_data(self, idx: torch.Tensor) -> List[Dict[str, Any]]: Returns ------- - Dict[str, torch.Tensor] + dict[str, torch.Tensor] A dictionary containing the processed data. Notes @@ -488,7 +488,7 @@ def _preprocess_data(self, idx: torch.Tensor) -> List[Dict[str, Any]]: return res - def _create_windows(self, indices: torch.Tensor) -> List[Tuple[int, int, int, int]]: + def _create_windows(self, indices: torch.Tensor) -> list[tuple[int, int, int, int]]: """ Create windows for the data in the given indices, for training, testing and validation. @@ -500,7 +500,7 @@ def _create_windows(self, indices: torch.Tensor) -> List[Tuple[int, int, int, in Returns ------- - List[Tuple[int, int, int, int]] + list[tuple[int, int, int, int]] A list of tuples where each tuple contains: - series_idx: Index of time series in the dataset - start_idx: Start index of the window @@ -686,12 +686,12 @@ def collate_fn(batch): Parameters ---------- - batch: List[TupleDict[str, Any]] + batch: list[tuple[dict[str, Any]]] The batch of data to be collated. Returns ------- - Tuple[Dict[str, torch.Tensor], torch.Tensor] + tuple[dict[str, torch.Tensor], torch.Tensor] A tuple containing the collated data and the target variable. """ diff --git a/pytorch_forecasting/layers/attention/attention_layer.py b/pytorch_forecasting/layers/attention/attention_layer.py index 55b9851d1..3f6072a93 100644 --- a/pytorch_forecasting/layers/attention/attention_layer.py +++ b/pytorch_forecasting/layers/attention/attention_layer.py @@ -24,7 +24,7 @@ class AttentionLayer(nn.Module): """ def __init__(self, attention, d_model, n_heads, d_keys=None, d_values=None): - super(AttentionLayer, self).__init__() + super().__init__() d_keys = d_keys or (d_model // n_heads) d_values = d_values or (d_model // n_heads) diff --git a/pytorch_forecasting/layers/attention/full_attention.py b/pytorch_forecasting/layers/attention/full_attention.py index 88a062330..def9b5214 100644 --- a/pytorch_forecasting/layers/attention/full_attention.py +++ b/pytorch_forecasting/layers/attention/full_attention.py @@ -45,7 +45,7 @@ def __init__( attention_dropout=0.1, output_attention=False, ): - super(FullAttention, self).__init__() + super().__init__() self.scale = scale self.mask_flag = mask_flag self.output_attention = output_attention diff --git a/pytorch_forecasting/layers/embeddings/data_embedding.py b/pytorch_forecasting/layers/embeddings/data_embedding.py index 0f590a4c4..9e33e65f7 100644 --- a/pytorch_forecasting/layers/embeddings/data_embedding.py +++ b/pytorch_forecasting/layers/embeddings/data_embedding.py @@ -23,7 +23,7 @@ class DataEmbedding_inverted(nn.Module): """ def __init__(self, c_in, d_model, dropout=0.1): - super(DataEmbedding_inverted, self).__init__() + super().__init__() self.value_embedding = nn.Linear(c_in, d_model) self.dropout = nn.Dropout(p=dropout) diff --git a/pytorch_forecasting/layers/embeddings/en_embedding.py b/pytorch_forecasting/layers/embeddings/en_embedding.py index 803b7f0e1..b49fd6741 100644 --- a/pytorch_forecasting/layers/embeddings/en_embedding.py +++ b/pytorch_forecasting/layers/embeddings/en_embedding.py @@ -27,7 +27,7 @@ class EnEmbedding(nn.Module): """ def __init__(self, n_vars, d_model, patch_len, dropout): - super(EnEmbedding, self).__init__() + super().__init__() self.patch_len = patch_len diff --git a/pytorch_forecasting/layers/embeddings/positional_embedding.py b/pytorch_forecasting/layers/embeddings/positional_embedding.py index 75c348bf6..82b107315 100644 --- a/pytorch_forecasting/layers/embeddings/positional_embedding.py +++ b/pytorch_forecasting/layers/embeddings/positional_embedding.py @@ -19,7 +19,7 @@ class PositionalEmbedding(nn.Module): max_len (int): Maximum length of the input sequence. Defaults to 5000.""" def __init__(self, d_model, max_len=5000): - super(PositionalEmbedding, self).__init__() + super().__init__() # Compute the positional encodings once in log space. pe = torch.zeros(max_len, d_model).float() pe.require_grad = False diff --git a/pytorch_forecasting/layers/encoders/encoder.py b/pytorch_forecasting/layers/encoders/encoder.py index 5b53d933b..3b54a0838 100644 --- a/pytorch_forecasting/layers/encoders/encoder.py +++ b/pytorch_forecasting/layers/encoders/encoder.py @@ -21,7 +21,7 @@ class Encoder(nn.Module): """ def __init__(self, layers, norm_layer=None, projection=None): - super(Encoder, self).__init__() + super().__init__() self.layers = nn.ModuleList(layers) self.norm = norm_layer self.projection = projection diff --git a/pytorch_forecasting/layers/encoders/encoder_layer.py b/pytorch_forecasting/layers/encoders/encoder_layer.py index ed3a95df2..a246edc91 100644 --- a/pytorch_forecasting/layers/encoders/encoder_layer.py +++ b/pytorch_forecasting/layers/encoders/encoder_layer.py @@ -33,7 +33,7 @@ def __init__( dropout=0.1, activation="relu", ): - super(EncoderLayer, self).__init__() + super().__init__() d_ff = d_ff or 4 * d_model self.self_attention = self_attention self.cross_attention = cross_attention diff --git a/pytorch_forecasting/models/base/_tslib_base_model_v2.py b/pytorch_forecasting/models/base/_tslib_base_model_v2.py index 002cd5c47..b7ef2a51b 100644 --- a/pytorch_forecasting/models/base/_tslib_base_model_v2.py +++ b/pytorch_forecasting/models/base/_tslib_base_model_v2.py @@ -3,7 +3,7 @@ NOTE: This PR is stacked on the PR #1812(phoeenniixx). """ -from typing import Dict, List, Optional, Tuple, Union +from typing import Optional, Union from warnings import warn import torch @@ -21,29 +21,29 @@ class TslibBaseModel(BaseModel): ---------- loss : nn.Module Loss function to use for training. - logging_metrics : Optional[List[nn.Module]], optional - List of metrics to log during training, validation, and testing. + logging_metrics : Optional[list[nn.Module]], optional + list of metrics to log during training, validation, and testing. optimizer : Optional[Union[Optimizer, str]], optional Optimizer to use for training. - optimizer_params : Optional[Dict], optional + optimizer_params : Optional[dict], optional Parameters for the optimizer. lr_scheduler : Optional[str], optional Learning rate scheduler to use. - lr_scheduler_params : Optional[Dict], optional + lr_scheduler_params : Optional[dict], optional Parameters for the learning rate scheduler. - metadata : Optional[Dict], default=None + metadata : Optional[dict], default=None Metadata for the model from TslibDataModule. """ def __init__( self, loss: nn.Module, - logging_metrics: Optional[List[nn.Module]] = None, + logging_metrics: Optional[list[nn.Module]] = None, optimizer: Optional[Union[Optimizer, str]] = "adam", - optimizer_params: Optional[Dict] = None, + optimizer_params: Optional[dict] = None, lr_scheduler: Optional[str] = None, - lr_scheduler_params: Optional[Dict] = None, - metadata: Optional[Dict] = None, + lr_scheduler_params: Optional[dict] = None, + metadata: Optional[dict] = None, ): super().__init__( loss=loss, @@ -93,18 +93,18 @@ def _init_network(self): """ raise NotImplementedError("Subclasses must implement _init_network method.") - def forward(self, x: Dict[str, torch.Tensor]) -> Dict[str, torch.Tensor]: + def forward(self, x: dict[str, torch.Tensor]) -> dict[str, torch.Tensor]: """ Forward pass of the model. Parameters ---------- - x: Dict[str, torch.Tensor] + x: dict[str, torch.Tensor] Dictionary containing input tensors. Returns ------- - Dict[str, torch.Tensor] + dict[str, torch.Tensor] Dictionary containing output tensors. These can include - predictions: Prediction_output of shape (batch_size, prediction_length, target_dim) @@ -115,7 +115,7 @@ def forward(self, x: Dict[str, torch.Tensor]) -> Dict[str, torch.Tensor]: def predict_step( self, - batch: Tuple[Dict[str, torch.Tensor]], + batch: tuple[dict[str, torch.Tensor]], batch_idx: int, dataloader_idx: int = 0, ) -> torch.Tensor: @@ -124,7 +124,7 @@ def predict_step( Parameters ---------- - batch : Tuple[Dict[str, torch.Tensor]] + batch : tuple[dict[str, torch.Tensor]] Batch of data containing input tensors. batch_idx : int Index of the batch. @@ -147,23 +147,23 @@ def predict_step( def transform_output( self, y_hat: Union[ - torch.Tensor, List[torch.Tensor] + torch.Tensor, list[torch.Tensor] ], # evidenced from TimeXer implementation - in PR #1797 # noqa: E501 - target_scale: Optional[Dict[str, torch.Tensor]], - ) -> Union[torch.Tensor, List[torch.Tensor]]: + target_scale: Optional[dict[str, torch.Tensor]], + ) -> Union[torch.Tensor, list[torch.Tensor]]: """ Transform the output of the model to the original scale. Parameters ---------- - y_hat : Union[torch.Tensor, List[torch.Tensor]] + y_hat : Union[torch.Tensor, list[torch.Tensor]] Dictionary containing the model output. - target_scale : Optional[Dict[str, torch.Tensor]] + target_scale : Optional[dict[str, torch.Tensor]] Dictionary containing the target scale for inverse transformation. Returns ------- - Union[torch.Tensor, List[torch.Tensor]] + Union[torch.Tensor, list[torch.Tensor]] Dictionary containing the transformed output. Notes diff --git a/pytorch_forecasting/models/timexer/_timexer.py b/pytorch_forecasting/models/timexer/_timexer.py index a26f9b855..9f731811b 100644 --- a/pytorch_forecasting/models/timexer/_timexer.py +++ b/pytorch_forecasting/models/timexer/_timexer.py @@ -10,7 +10,7 @@ ################################################################ -from typing import Any, Dict, List, Optional, Tuple, Union +from typing import Any, Optional, Union import lightning.pytorch as pl from lightning.pytorch import LightningModule, Trainer @@ -40,14 +40,14 @@ def __init__( patch_length: int = 24, factor: int = 5, activation: str = "relu", - endogenous_vars: Optional[List[str]] = None, - exogenous_vars: Optional[List[str]] = None, - logging_metrics: Optional[List[nn.Module]] = None, + endogenous_vars: Optional[list[str]] = None, + exogenous_vars: Optional[list[str]] = None, + logging_metrics: Optional[list[nn.Module]] = None, optimizer: Optional[Union[Optimizer, str]] = "adam", - optimizer_params: Optional[Dict] = None, + optimizer_params: Optional[dict] = None, lr_scheduler: Optional[str] = None, - lr_scheduler_params: Optional[Dict] = None, - metadata: Optional[Dict] = None, + lr_scheduler_params: Optional[dict] = None, + metadata: Optional[dict] = None, **kwargs: Any, ): super().__init__( @@ -172,13 +172,13 @@ def _init_network(self): # """ # return torch.tensor(self.target_indices, device=self.device) - def _forecast(self, x: Dict[str, torch.Tensor]) -> Dict[str, torch.Tensor]: + def _forecast(self, x: dict[str, torch.Tensor]) -> dict[str, torch.Tensor]: """ Forward pass of the TimeXer model. Args: - x (Dict[str, torch.Tensor]): Input data. + x (dict[str, torch.Tensor]): Input data. Returns: - Dict[str, torch.Tensor]: Model predictions. + dict[str, torch.Tensor]: Model predictions. """ batch_size = x["history_cont"].shape[0] history_cont = x["history_cont"] @@ -228,14 +228,14 @@ def _forecast(self, x: Dict[str, torch.Tensor]) -> Dict[str, torch.Tensor]: return dec_out - def _forecast_multi(self, x: Dict[str, torch.Tensor]) -> Dict[str, torch.Tensor]: + def _forecast_multi(self, x: dict[str, torch.Tensor]) -> dict[str, torch.Tensor]: """ Forecast for multivariate with multiple time series. Args: - x (Dict[str, torch.Tensor]): Input data. + x (dict[str, torch.Tensor]): Input data. Returns: - Dict[str, torch.Tensor]: Model predictions. + dict[str, torch.Tensor]: Model predictions. """ history_cont = x["history_cont"] @@ -279,13 +279,13 @@ def _forecast_multi(self, x: Dict[str, torch.Tensor]) -> Dict[str, torch.Tensor] return dec_out - def forward(self, x: Dict[str, torch.Tensor]) -> Dict[str, torch.Tensor]: + def forward(self, x: dict[str, torch.Tensor]) -> dict[str, torch.Tensor]: """ Forward pass of the TimeXer model. Args: - x (Dict[str, torch.Tensor]): Input data. + x (dict[str, torch.Tensor]): Input data. Returns: - Dict[str, torch.Tensor]: Model predictions. + dict[str, torch.Tensor]: Model predictions. """ if self.features == "MS": out = self._forecast(x) From e3e5bb8096bfe5beea25ed1dbadf9b930f4b98f6 Mon Sep 17 00:00:00 2001 From: Pranav Bhat Date: Fri, 30 May 2025 16:47:05 +0530 Subject: [PATCH 089/139] add tests for checking custom train test split --- tests/test_data/test_tslib_data_module.py | 77 +++++++++++++++++++++++ 1 file changed, 77 insertions(+) diff --git a/tests/test_data/test_tslib_data_module.py b/tests/test_data/test_tslib_data_module.py index 4e2e73c43..ae18999fa 100644 --- a/tests/test_data/test_tslib_data_module.py +++ b/tests/test_data/test_tslib_data_module.py @@ -367,3 +367,80 @@ def test_create_windows(tslib_data_module): empty_windows = tslib_data_module._create_windows(torch.tensor([])) assert len(empty_windows) == 0, "Should return empty list for empty index." + + +def test_dataloader_pipeline(tslib_data_module): + """Test for a single iteration of the dataloader pipeline to + perform batch retrival and ensure correct data shapes and types.""" + + tslib_data_module.setup(stage="fit") + train_dataloader = tslib_data_module.train_dataloader() + + x_batch, y_batch = next(iter(train_dataloader)) + + assert isinstance(x_batch, dict), "x_batch should be a dictionary." + assert isinstance(y_batch, torch.Tensor), "y_batch should be a PyTorch tensor." + + assert x_batch["history_cont"].shape[0] == tslib_data_module.context_length + assert x_batch["history_cat"].shape[0] == tslib_data_module.context_length + + metadata = tslib_data_module.metadata + + known_cat_count = len( + [ + name + for name in metadata["feature_names"]["known"] + if name in metadata["feature_names"]["categorical"] + ] + ) + + known_cont_count = len( + [ + name + for name in metadata["feature_names"]["known"] + if name in metadata["feature_names"]["continuous"] + ] + ) + + assert x_batch["future_cont"].shape[0] == tslib_data_module.batch_size + assert x_batch["future_cat"].shape[1] == known_cat_count + assert x_batch["future_cont"].shape[2] == known_cont_count + assert x_batch["future_cont"].shape[0] == tslib_data_module.batch_size + + assert y_batch.shape[0] == tslib_data_module.batch_size + assert y_batch.shape[1] == tslib_data_module.max_prediction_length + + +def test_different_split_ratios(sample_timeseries_data): + """Test the TslibDataModule with different train/val/test split ratios.""" + + custom_split = (0.6, 0.2, 0.2) + dm_custom = TslibDataModule( + time_series_dataset=sample_timeseries_data, + context_length=8, + prediction_length=4, + batch_size=2, + train_val_test_split=custom_split, + ) + + dm_custom.setup(stage="fit") + + total_series = len(sample_timeseries_data) + expected_train = int(total_series * 0.6) + expected_val = int(total_series * 0.2) + expected_test = total_series - expected_train - expected_val + + assert len(dm_custom._train_indices) == expected_train + assert len(dm_custom._val_indices) == expected_val + assert len(dm_custom._test_indices) == expected_test + + assert dm_custom.train_val_test_split == custom_split + + total_split = ( + len(dm_custom._train_indices) + + len(dm_custom._val_indices) + + len(dm_custom._test_indices) + ) + assert ( + total_split == total_series + ), "Total split indices should match the dataset length." From d67ccaee1c54720fb85592fdd53e74bed5cd0301 Mon Sep 17 00:00:00 2001 From: Pranav Bhat Date: Fri, 30 May 2025 20:41:11 +0530 Subject: [PATCH 090/139] add tests for multitarget dataset and fix handling of incosistent dtypes between end_time and cutoff_time in _create_windows --- pytorch_forecasting/data/tslib_data_module.py | 31 ++++-- tests/test_data/test_tslib_data_module.py | 96 ++++++++++++++++++- 2 files changed, 117 insertions(+), 10 deletions(-) diff --git a/pytorch_forecasting/data/tslib_data_module.py b/pytorch_forecasting/data/tslib_data_module.py index 747b45387..a3d663a0f 100644 --- a/pytorch_forecasting/data/tslib_data_module.py +++ b/pytorch_forecasting/data/tslib_data_module.py @@ -136,8 +136,8 @@ def __getitem__(self, idx: int) -> dict[str, Any]: history_target = processed_data["target"][history_indices] future_target = processed_data["target"][future_indices] - history_time_idx = processed_data["timestep"][history_indices] - future_time_idx = processed_data["timestep"][future_indices] + # history_time_idx = processed_data["timestep"][history_indices] + # future_time_idx = processed_data["timestep"][future_indices] x = { "history_cont": history_cont, @@ -149,8 +149,10 @@ def __getitem__(self, idx: int) -> dict[str, Any]: "history_mask": history_mask, "future_mask": future_mask, "groups": processed_data["group"], - "history_time_idx": torch.tensor(history_time_idx), - "future_time_idx": torch.tensor(future_time_idx), + "history_time_idx": torch.arange(context_length), + "future_time_idx": torch.arange( + context_length, context_length + prediction_length + ), "history_target": history_target, "future_target": future_target, "future_target_len": torch.tensor(prediction_length), @@ -530,8 +532,25 @@ def _create_windows(self, indices: torch.Tensor) -> list[tuple[int, int, int, in window_end = start_idx + min_seq_length - 1 # 0-indexed if cutoff_time is not None: # skip window if exceed cutoff time. - end_time = sample["t"][window_end].item() - if end_time > cutoff_time: + end_time = sample["t"][window_end] + + if isinstance(end_time, torch.Tensor): + end_time = end_time.item() + + # Convert both to pandas Timestamp for consistent comparison + try: + if not isinstance(end_time, pd.Timestamp): + end_time = pd.Timestamp(end_time) + if not isinstance(cutoff_time, pd.Timestamp): + cutoff_time = pd.Timestamp(cutoff_time) + + if end_time > cutoff_time: + continue + except (ValueError, TypeError) as e: + # If conversion fails, skip this window + warnings.warn( + f"Could not convert time values for comparison: {e}" + ) continue windows.append( diff --git a/tests/test_data/test_tslib_data_module.py b/tests/test_data/test_tslib_data_module.py index ae18999fa..ba140a5b4 100644 --- a/tests/test_data/test_tslib_data_module.py +++ b/tests/test_data/test_tslib_data_module.py @@ -381,8 +381,8 @@ def test_dataloader_pipeline(tslib_data_module): assert isinstance(x_batch, dict), "x_batch should be a dictionary." assert isinstance(y_batch, torch.Tensor), "y_batch should be a PyTorch tensor." - assert x_batch["history_cont"].shape[0] == tslib_data_module.context_length - assert x_batch["history_cat"].shape[0] == tslib_data_module.context_length + assert x_batch["history_cont"].shape[1] == tslib_data_module.context_length + assert x_batch["history_cat"].shape[1] == tslib_data_module.context_length metadata = tslib_data_module.metadata @@ -403,12 +403,12 @@ def test_dataloader_pipeline(tslib_data_module): ) assert x_batch["future_cont"].shape[0] == tslib_data_module.batch_size - assert x_batch["future_cat"].shape[1] == known_cat_count + assert x_batch["future_cat"].shape[2] == known_cat_count assert x_batch["future_cont"].shape[2] == known_cont_count assert x_batch["future_cont"].shape[0] == tslib_data_module.batch_size assert y_batch.shape[0] == tslib_data_module.batch_size - assert y_batch.shape[1] == tslib_data_module.max_prediction_length + assert y_batch.shape[1] == tslib_data_module.prediction_length def test_different_split_ratios(sample_timeseries_data): @@ -444,3 +444,91 @@ def test_different_split_ratios(sample_timeseries_data): assert ( total_split == total_series ), "Total split indices should match the dataset length." + + +def test_preprocess_data(tslib_data_module, sample_timeseries_data): + """Test the preprocess_data method. + Ensures alignment and presence of all required features.""" + + if not hasattr(tslib_data_module, "_indices"): + tslib_data_module.setup() + + sample_series_idx = tslib_data_module._train_indices[0] + processed = tslib_data_module._preprocess_data(sample_series_idx) + + assert "features" in processed + assert "target" in processed + assert "static" in processed + assert "group" in processed + assert "time_mask" in processed + assert "continuous" in processed["features"] + assert "categorical" in processed["features"] + assert "length" in processed + assert "timestep" in processed + + original_sample = sample_timeseries_data[sample_series_idx] + + expected_length = len(original_sample["t"]) + + assert processed["features"]["categorical"].shape[0] == expected_length + assert processed["features"]["continuous"].shape[0] == expected_length + assert processed["target"].shape[0] == expected_length + + +def test_static_features(tslib_data_module): + """Test with static features included. + + Validates the static feature support in the TslibDataModule.""" + + tslib_data_module.setup(stage="fit") + + metadata = tslib_data_module.metadata + + assert metadata["n_features"]["static_continuous"] == 1 + + x, y = tslib_data_module.train_dataset[0] + + assert "static_continuous_features" in x + assert ( + x["static_continuous_features"].shape[1] + == metadata["n_features"]["static_continuous"] + ) + + +def test_multivariate_target(): + """Test with multivariate target (multiple target columns). + + Verifies correct handling of multivariate targets in data pipeline.""" + df = pd.DataFrame( + { + "group": np.repeat([0, 1], 50), + "time": np.tile(pd.date_range("2020-01-01", periods=50), 2), + "target1": np.random.normal(0, 1, 100), + "target2": np.random.normal(5, 2, 100), + "feature1": np.random.normal(0, 1, 100), + "feature2": np.random.normal(0, 1, 100), + } + ) + + ts = TimeSeries( + data=df, + time="time", + target=["target1", "target2"], + group=["group"], + num=["feature1", "feature2"], + ) + + dm = TslibDataModule( + time_series_dataset=ts, + context_length=8, + prediction_length=4, + batch_size=2, + ) + + dm.setup(stage="fit") + + x, y = dm.train_dataset[0] + + assert ( + y.shape[-1] == 2 + ), "Target should have two dimensions for n_features for multivariate target." From 0968452c7beaca969aeaf40728f61a8d4b165534 Mon Sep 17 00:00:00 2001 From: Aryan Saini Date: Sun, 1 Jun 2025 00:07:15 +0530 Subject: [PATCH 091/139] Add metadata tests --- .../tests/test_all_estimators_v2.py | 41 +++++++++++++++---- 1 file changed, 32 insertions(+), 9 deletions(-) diff --git a/pytorch_forecasting/tests/test_all_estimators_v2.py b/pytorch_forecasting/tests/test_all_estimators_v2.py index 29e6ab22a..857ea1f76 100644 --- a/pytorch_forecasting/tests/test_all_estimators_v2.py +++ b/pytorch_forecasting/tests/test_all_estimators_v2.py @@ -71,6 +71,38 @@ def _integration( training_data_module = dataloaders_with_covariates["data_module"] metadata = training_data_module.metadata + assert metadata["encoder_cont"] == 14 # 14 features (8 known + 6 unknown) + assert metadata["encoder_cat"] == 0 + assert metadata["decoder_cont"] == 8 # 8 (only known features) + assert metadata["decoder_cat"] == 0 + assert metadata["static_categorical_features"] == 0 + assert ( + metadata["static_continuous_features"] == 2 + ) # 2 (agency_encoded, sku_encoded) + assert metadata["target"] == 1 + + batch_x, batch_y = next(iter(train_dataloader)) + + assert batch_x["encoder_cont"].shape[2] == metadata["encoder_cont"] + assert batch_x["encoder_cat"].shape[2] == metadata["encoder_cat"] + + assert batch_x["decoder_cont"].shape[2] == metadata["decoder_cont"] + assert batch_x["decoder_cat"].shape[2] == metadata["decoder_cat"] + + if "static_categorical_features" in batch_x: + assert ( + batch_x["static_categorical_features"].shape[2] + == metadata["static_categorical_features"] + ) + + if "static_continuous_features" in batch_x: + assert ( + batch_x["static_continuous_features"].shape[2] + == metadata["static_continuous_features"] + ) + + assert batch_y.shape[2] == metadata["target"] + net = estimator_cls( metadata=metadata, loss=nn.MSELoss(), @@ -85,18 +117,9 @@ def _integration( ) test_outputs = trainer.test(net, dataloaders=test_dataloader) assert len(test_outputs) > 0 - - # check loading - # net = estimator_cls.load_from_checkpoint( - # trainer.checkpoint_callback.best_model_path - # ) - # net.predict(val_dataloader) - finally: shutil.rmtree(tmp_path, ignore_errors=True) - # net.predict(val_dataloader) - class TestAllPtForecastersV2(PackageConfig, BaseFixtureGenerator): """Generic tests for all objects in the mini package.""" From 4e8f86343a888e3bf820c132fa1713948ea5838f Mon Sep 17 00:00:00 2001 From: Aryan Saini Date: Sun, 1 Jun 2025 19:14:17 +0530 Subject: [PATCH 092/139] add object-filter to ptf-v1 --- .../models/deepar/_deepar_metadata.py | 1 + .../models/nbeats/_nbeats_metadata.py | 1 + pytorch_forecasting/models/tide/_tide_metadata.py | 1 + pytorch_forecasting/tests/test_all_estimators.py | 15 +-------------- 4 files changed, 4 insertions(+), 14 deletions(-) diff --git a/pytorch_forecasting/models/deepar/_deepar_metadata.py b/pytorch_forecasting/models/deepar/_deepar_metadata.py index ad0e210a5..28754298a 100644 --- a/pytorch_forecasting/models/deepar/_deepar_metadata.py +++ b/pytorch_forecasting/models/deepar/_deepar_metadata.py @@ -9,6 +9,7 @@ class DeepARMetadata(_BasePtForecaster): _tags = { "info:name": "DeepAR", "info:compute": 3, + "object_type": "ptf-v1", "authors": ["jdb78"], "capability:exogenous": True, "capability:multivariate": True, diff --git a/pytorch_forecasting/models/nbeats/_nbeats_metadata.py b/pytorch_forecasting/models/nbeats/_nbeats_metadata.py index 9910a0ba1..f644b378a 100644 --- a/pytorch_forecasting/models/nbeats/_nbeats_metadata.py +++ b/pytorch_forecasting/models/nbeats/_nbeats_metadata.py @@ -9,6 +9,7 @@ class NBeatsMetadata(_BasePtForecaster): _tags = { "info:name": "NBeats", "info:compute": 1, + "object_type": "ptf-v1", "authors": ["jdb78"], "capability:exogenous": False, "capability:multivariate": False, diff --git a/pytorch_forecasting/models/tide/_tide_metadata.py b/pytorch_forecasting/models/tide/_tide_metadata.py index 502229b71..49a2acc67 100644 --- a/pytorch_forecasting/models/tide/_tide_metadata.py +++ b/pytorch_forecasting/models/tide/_tide_metadata.py @@ -9,6 +9,7 @@ class TiDEModelMetadata(_BasePtForecaster): _tags = { "info:name": "TiDEModel", "info:compute": 3, + "object_type": "ptf-v1", "authors": ["Sohaib-Ahmed21"], "capability:exogenous": True, "capability:multivariate": True, diff --git a/pytorch_forecasting/tests/test_all_estimators.py b/pytorch_forecasting/tests/test_all_estimators.py index efc944937..add3ef7ba 100644 --- a/pytorch_forecasting/tests/test_all_estimators.py +++ b/pytorch_forecasting/tests/test_all_estimators.py @@ -245,20 +245,7 @@ def _integration( class TestAllPtForecasters(PackageConfig, BaseFixtureGenerator): """Generic tests for all objects in the mini package.""" - def _all_objects(self): - """Retrieve list of all object classes, excluding ptf-v2 objects.""" - obj_list = super()._all_objects() - - filtered_obj_list = [] - for obj in obj_list: - if hasattr(obj, "get_class_tag"): - object_type = obj.get_class_tag("object_type", None) - if object_type != "ptf-v2": - filtered_obj_list.append(obj) - else: - filtered_obj_list.append(obj) - - return filtered_obj_list + object_type_filter = "ptf-v1" def test_doctest_examples(self, object_class): """Runs doctests for estimator class.""" From 5f79e257665864d4b8a9e809a35f11974b4e7f71 Mon Sep 17 00:00:00 2001 From: Pranav Bhat Date: Mon, 2 Jun 2025 16:36:28 +0530 Subject: [PATCH 093/139] add warnings for hyperparams and refactor d_model param to hidden_size for clarity --- examples/tslib_v2_example.ipynb | 4 +- .../models/timexer/_timexer.py | 39 ++++++++++++++----- 2 files changed, 32 insertions(+), 11 deletions(-) diff --git a/examples/tslib_v2_example.ipynb b/examples/tslib_v2_example.ipynb index bebd35619..d1caa11dd 100644 --- a/examples/tslib_v2_example.ipynb +++ b/examples/tslib_v2_example.ipynb @@ -515,7 +515,7 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": null, "id": "429b5f15", "metadata": {}, "outputs": [ @@ -532,7 +532,7 @@ "model = TimeXer(\n", " loss=nn.MSELoss(),\n", " features=\"MS\",\n", - " d_model=64,\n", + " hidden_size=64,\n", " nhead=4,\n", " e_layers=2,\n", " d_ff=256,\n", diff --git a/pytorch_forecasting/models/timexer/_timexer.py b/pytorch_forecasting/models/timexer/_timexer.py index 9f731811b..fd69e4c8d 100644 --- a/pytorch_forecasting/models/timexer/_timexer.py +++ b/pytorch_forecasting/models/timexer/_timexer.py @@ -11,6 +11,7 @@ from typing import Any, Optional, Union +import warnings as warn import lightning.pytorch as pl from lightning.pytorch import LightningModule, Trainer @@ -32,7 +33,7 @@ def __init__( loss: nn.Module, features: str = "MS", enc_in: int = None, - d_model: int = 512, + hidden_size: int = 512, n_heads: int = 8, e_layers: int = 2, d_ff: int = 2048, @@ -62,7 +63,7 @@ def __init__( self.features = features self.enc_in = enc_in - self.d_model = d_model + self.hidden_size = hidden_size self.n_heads = n_heads self.e_layers = e_layers self.d_ff = d_ff @@ -92,6 +93,20 @@ def _init_network(self): from pytorch_forecasting.layers.encoders import Encoder, EncoderLayer from pytorch_forecasting.layers.output.flatten_head import FlattenHead + if self.context_length <= self.patch_length: + raise ValueError( + f"Context length ({self.context_length}) must be greater than patch" + "length. Patches of ({self.patch_length}) will end up being longer than" + "the sequence length." + ) + + if self.context_length % self.patch_length != 0: + warn.warn( + f"Context length ({self.context_length}) is not divisible by" + " patch length. This may lead to unexpected behavior, as some" + "time steps will not be used in the model." + ) + self.patch_num = max(1, int(self.context_length // self.patch_length)) if self.target_dim > 1 and self.features == "M": @@ -110,12 +125,18 @@ def _init_network(self): if hasattr(self.loss, "quantiles"): self.n_quantiles = len(self.loss.quantiles) + if self.hidden_size % self.n_heads != 0: + raise ValueError( + f"hidden_size ({self.hidden_size}) must be divisible by n_heads ({self.n_heads}) " # noqa: E501 + f"for the multi-head attention mechanism to work properly." + ) + self.en_embedding = EnEmbedding( - self.n_target_vars, self.d_model, self.patch_length, self.dropout + self.n_target_vars, self.hidden_size, self.patch_length, self.dropout ) self.ex_embedding = DataEmbedding_inverted( - self.context_length, self.d_model, self.dropout + self.context_length, self.hidden_size, self.dropout ) encoder_layers = [] @@ -130,7 +151,7 @@ def _init_network(self): attention_dropout=self.dropout, output_attention=False, ), - self.d_model, + self.hidden_size, self.n_heads, ), AttentionLayer( @@ -140,10 +161,10 @@ def _init_network(self): attention_dropout=self.dropout, output_attention=False, ), - self.d_model, + self.hidden_size, self.n_heads, ), - self.d_model, + self.hidden_size, self.d_ff, dropout=self.dropout, activation=self.activation, @@ -151,11 +172,11 @@ def _init_network(self): ) self.encoder = Encoder( - encoder_layers, norm_layer=torch.nn.LayerNorm(self.d_model) + encoder_layers, norm_layer=torch.nn.LayerNorm(self.hidden_size) ) # Initialize output head - self.head_nf = self.d_model * (self.patch_num + 1) + self.head_nf = self.hidden_size * (self.patch_num + 1) self.head = FlattenHead( self.enc_in, self.head_nf, From 4bfec1b05f22d61a73035d48a918f709c3490231 Mon Sep 17 00:00:00 2001 From: Pranav Bhat Date: Mon, 2 Jun 2025 17:53:43 +0530 Subject: [PATCH 094/139] dummy commit to trigger code-quality checks --- pytorch_forecasting/models/timexer/_timexer.py | 3 +-- tests/test_data/test_tslib_data_module.py | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/pytorch_forecasting/models/timexer/_timexer.py b/pytorch_forecasting/models/timexer/_timexer.py index fd69e4c8d..95dd37697 100644 --- a/pytorch_forecasting/models/timexer/_timexer.py +++ b/pytorch_forecasting/models/timexer/_timexer.py @@ -9,7 +9,6 @@ # IT IS STRICTLY A PART OF THE v2 design of PTF. # ################################################################ - from typing import Any, Optional, Union import warnings as warn @@ -128,7 +127,7 @@ def _init_network(self): if self.hidden_size % self.n_heads != 0: raise ValueError( f"hidden_size ({self.hidden_size}) must be divisible by n_heads ({self.n_heads}) " # noqa: E501 - f"for the multi-head attention mechanism to work properly." + f"for multi-head attention mechanism to work properly." ) self.en_embedding = EnEmbedding( diff --git a/tests/test_data/test_tslib_data_module.py b/tests/test_data/test_tslib_data_module.py index ba140a5b4..4dd3e260c 100644 --- a/tests/test_data/test_tslib_data_module.py +++ b/tests/test_data/test_tslib_data_module.py @@ -20,8 +20,7 @@ def sample_timeseries_data(): for series_id in range(n_series): for time_idx in range(n_timesteps): - - # create a realistic time series with trend, seasonality, and noise + # Generate a target variable with some noise target = ( 10 + 0.1 * time_idx From 7510509095a6ad548fab769d7b326b0149e6c5b1 Mon Sep 17 00:00:00 2001 From: Pranav Bhat Date: Wed, 4 Jun 2025 09:49:46 +0530 Subject: [PATCH 095/139] refactor logic for handling shuffling at the series level --- pytorch_forecasting/data/tslib_data_module.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/pytorch_forecasting/data/tslib_data_module.py b/pytorch_forecasting/data/tslib_data_module.py index a3d663a0f..3901726e1 100644 --- a/pytorch_forecasting/data/tslib_data_module.py +++ b/pytorch_forecasting/data/tslib_data_module.py @@ -578,9 +578,7 @@ def setup(self, stage: Optional[str] = None) -> None: total_series = len(self.time_series_dataset) - self._indices = ( - torch.randperm(total_series) if self.shuffle else torch.arange(total_series) - ) + self._indices = torch.randperm(total_series) self._train_size = int(self.train_val_test_split[0] * total_series) self._val_size = int(self.train_val_test_split[1] * total_series) From 1a52579b14c00d3bdbc4626b158d5fe283cfd657 Mon Sep 17 00:00:00 2001 From: Pranav Bhat Date: Wed, 4 Jun 2025 10:39:51 +0530 Subject: [PATCH 096/139] add assert statement for validation of time series indices --- pytorch_forecasting/data/tslib_data_module.py | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/pytorch_forecasting/data/tslib_data_module.py b/pytorch_forecasting/data/tslib_data_module.py index 3901726e1..951a9ad74 100644 --- a/pytorch_forecasting/data/tslib_data_module.py +++ b/pytorch_forecasting/data/tslib_data_module.py @@ -576,8 +576,18 @@ def setup(self, stage: Optional[str] = None) -> None: If None, the data module will be setup for training. """ + # TODO: Add support for temporal/random/group splits. + # Currently, it only supports random splits. + # Handle the case where the dataset is empty. + total_series = len(self.time_series_dataset) + if total_series == 0: + raise ValueError( + "The time series dataset is empty. " + "Please provide a non-empty dataset." + ) + self._indices = torch.randperm(total_series) self._train_size = int(self.train_val_test_split[0] * total_series) @@ -592,6 +602,16 @@ def setup(self, stage: Optional[str] = None) -> None: self._train_size + self._val_size : total_series ] + assert ( + len(self._train_indices) > 0 + ), "Training dataset must contain at least one time series" + assert ( + len(self._val_indices) > 0 + ), "Validation dataset must contain at least one time series" + assert ( + len(self._test_indices) > 0 + ), "Test dataset must contain at least one time series" + if stage == "fit" or stage is None: if not hasattr(self, "_train_dataset") or not hasattr(self, "_val_dataset"): self._train_windows = self._create_windows(self._train_indices) From 693fbd286a6f684fa82acc04ee2b891b6bd54528 Mon Sep 17 00:00:00 2001 From: Pranav Bhat Date: Wed, 4 Jun 2025 10:51:06 +0530 Subject: [PATCH 097/139] add handling for time series datasets of small size. --- pytorch_forecasting/data/tslib_data_module.py | 30 ++++++++++++------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/pytorch_forecasting/data/tslib_data_module.py b/pytorch_forecasting/data/tslib_data_module.py index 951a9ad74..4da3e0ae0 100644 --- a/pytorch_forecasting/data/tslib_data_module.py +++ b/pytorch_forecasting/data/tslib_data_module.py @@ -588,19 +588,29 @@ def setup(self, stage: Optional[str] = None) -> None: "Please provide a non-empty dataset." ) + # this is a very rudimentary way to handle the splits when + # the dataset is of size equal to 1 or 2. self._indices = torch.randperm(total_series) + if total_series == 1: + self._train_indices = self._indices + self._val_indices = self._indices + self._test_indices = self._indices + elif total_series == 2: + self._train_indices = self._indices[0:1] + self._val_indices = self._indices[1:2] + self._test_indices = self._indices[1:2] + else: + self._train_size = max(1, int(self.train_val_test_split[0] * total_series)) + self._val_size = max(1, int(self.train_val_test_split[1] * total_series)) - self._train_size = int(self.train_val_test_split[0] * total_series) - self._val_size = int(self.train_val_test_split[1] * total_series) - - self._train_indices = self._indices[: self._train_size] - self._val_indices = self._indices[ - self._train_size : self._train_size + self._val_size - ] + self._train_indices = self._indices[: self._train_size] + self._val_indices = self._indices[ + self._train_size : self._train_size + self._val_size + ] - self._test_indices = self._indices[ - self._train_size + self._val_size : total_series - ] + self._test_indices = self._indices[ + self._train_size + self._val_size : total_series + ] assert ( len(self._train_indices) > 0 From 10b1e4adcfe7fdaf327c619f9043bbd8912459f1 Mon Sep 17 00:00:00 2001 From: Pranav Bhat Date: Wed, 4 Jun 2025 11:02:48 +0530 Subject: [PATCH 098/139] change layers module files to private --- pytorch_forecasting/layers/__init__.py | 2 +- pytorch_forecasting/layers/attention/__init__.py | 4 ++-- .../attention/{attention_layer.py => _attention_layer.py} | 0 .../attention/{full_attention.py => _full_attention.py} | 0 pytorch_forecasting/layers/embeddings/__init__.py | 6 +++--- .../embeddings/{data_embedding.py => _data_embedding.py} | 0 .../layers/embeddings/{en_embedding.py => _en_embedding.py} | 2 +- .../{positional_embedding.py => _positional_embedding.py} | 0 pytorch_forecasting/layers/encoders/__init__.py | 4 ++-- .../layers/encoders/{encoder.py => _encoder.py} | 0 .../layers/encoders/{encoder_layer.py => _encoder_layer.py} | 0 pytorch_forecasting/layers/output/__init__.py | 2 +- .../layers/output/{flatten_head.py => _flatten_head.py} | 0 pytorch_forecasting/models/timexer/_timexer.py | 4 ++-- 14 files changed, 12 insertions(+), 12 deletions(-) rename pytorch_forecasting/layers/attention/{attention_layer.py => _attention_layer.py} (100%) rename pytorch_forecasting/layers/attention/{full_attention.py => _full_attention.py} (100%) rename pytorch_forecasting/layers/embeddings/{data_embedding.py => _data_embedding.py} (100%) rename pytorch_forecasting/layers/embeddings/{en_embedding.py => _en_embedding.py} (95%) rename pytorch_forecasting/layers/embeddings/{positional_embedding.py => _positional_embedding.py} (100%) rename pytorch_forecasting/layers/encoders/{encoder.py => _encoder.py} (100%) rename pytorch_forecasting/layers/encoders/{encoder_layer.py => _encoder_layer.py} (100%) rename pytorch_forecasting/layers/output/{flatten_head.py => _flatten_head.py} (100%) diff --git a/pytorch_forecasting/layers/__init__.py b/pytorch_forecasting/layers/__init__.py index b430f8772..406470189 100644 --- a/pytorch_forecasting/layers/__init__.py +++ b/pytorch_forecasting/layers/__init__.py @@ -12,7 +12,7 @@ Encoder, EncoderLayer, ) -from pytorch_forecasting.layers.output.flatten_head import ( +from pytorch_forecasting.layers.output._flatten_head import ( FlattenHead, ) diff --git a/pytorch_forecasting/layers/attention/__init__.py b/pytorch_forecasting/layers/attention/__init__.py index 94e9b8cc0..23724a67b 100644 --- a/pytorch_forecasting/layers/attention/__init__.py +++ b/pytorch_forecasting/layers/attention/__init__.py @@ -2,7 +2,7 @@ Attention Layers for pytorch-forecasting models. """ -from pytorch_forecasting.layers.attention.attention_layer import AttentionLayer -from pytorch_forecasting.layers.attention.full_attention import FullAttention +from pytorch_forecasting.layers.attention._attention_layer import AttentionLayer +from pytorch_forecasting.layers.attention._full_attention import FullAttention __all__ = ["AttentionLayer", "FullAttention"] diff --git a/pytorch_forecasting/layers/attention/attention_layer.py b/pytorch_forecasting/layers/attention/_attention_layer.py similarity index 100% rename from pytorch_forecasting/layers/attention/attention_layer.py rename to pytorch_forecasting/layers/attention/_attention_layer.py diff --git a/pytorch_forecasting/layers/attention/full_attention.py b/pytorch_forecasting/layers/attention/_full_attention.py similarity index 100% rename from pytorch_forecasting/layers/attention/full_attention.py rename to pytorch_forecasting/layers/attention/_full_attention.py diff --git a/pytorch_forecasting/layers/embeddings/__init__.py b/pytorch_forecasting/layers/embeddings/__init__.py index d5001fcf0..1d0a681d9 100644 --- a/pytorch_forecasting/layers/embeddings/__init__.py +++ b/pytorch_forecasting/layers/embeddings/__init__.py @@ -2,9 +2,9 @@ Implementation of embedding layers for PTF models imported from `nn.Modules` """ -from pytorch_forecasting.layers.embeddings.data_embedding import DataEmbedding_inverted -from pytorch_forecasting.layers.embeddings.en_embedding import EnEmbedding -from pytorch_forecasting.layers.embeddings.positional_embedding import ( +from pytorch_forecasting.layers.embeddings._data_embedding import DataEmbedding_inverted +from pytorch_forecasting.layers.embeddings._en_embedding import EnEmbedding +from pytorch_forecasting.layers.embeddings._positional_embedding import ( PositionalEmbedding, ) diff --git a/pytorch_forecasting/layers/embeddings/data_embedding.py b/pytorch_forecasting/layers/embeddings/_data_embedding.py similarity index 100% rename from pytorch_forecasting/layers/embeddings/data_embedding.py rename to pytorch_forecasting/layers/embeddings/_data_embedding.py diff --git a/pytorch_forecasting/layers/embeddings/en_embedding.py b/pytorch_forecasting/layers/embeddings/_en_embedding.py similarity index 95% rename from pytorch_forecasting/layers/embeddings/en_embedding.py rename to pytorch_forecasting/layers/embeddings/_en_embedding.py index b49fd6741..efa611809 100644 --- a/pytorch_forecasting/layers/embeddings/en_embedding.py +++ b/pytorch_forecasting/layers/embeddings/_en_embedding.py @@ -10,7 +10,7 @@ import torch.nn as nn import torch.nn.functional as F -from pytorch_forecasting.layers.embeddings.positional_embedding import ( +from pytorch_forecasting.layers.embeddings._positional_embedding import ( PositionalEmbedding, ) diff --git a/pytorch_forecasting/layers/embeddings/positional_embedding.py b/pytorch_forecasting/layers/embeddings/_positional_embedding.py similarity index 100% rename from pytorch_forecasting/layers/embeddings/positional_embedding.py rename to pytorch_forecasting/layers/embeddings/_positional_embedding.py diff --git a/pytorch_forecasting/layers/encoders/__init__.py b/pytorch_forecasting/layers/encoders/__init__.py index 6fc81b3da..517d36748 100644 --- a/pytorch_forecasting/layers/encoders/__init__.py +++ b/pytorch_forecasting/layers/encoders/__init__.py @@ -2,7 +2,7 @@ Encoder layers for neural network models. """ -from .encoder import Encoder -from .encoder_layer import EncoderLayer +from ._encoder import Encoder +from ._encoder_layer import EncoderLayer __all__ = ["Encoder", "EncoderLayer"] diff --git a/pytorch_forecasting/layers/encoders/encoder.py b/pytorch_forecasting/layers/encoders/_encoder.py similarity index 100% rename from pytorch_forecasting/layers/encoders/encoder.py rename to pytorch_forecasting/layers/encoders/_encoder.py diff --git a/pytorch_forecasting/layers/encoders/encoder_layer.py b/pytorch_forecasting/layers/encoders/_encoder_layer.py similarity index 100% rename from pytorch_forecasting/layers/encoders/encoder_layer.py rename to pytorch_forecasting/layers/encoders/_encoder_layer.py diff --git a/pytorch_forecasting/layers/output/__init__.py b/pytorch_forecasting/layers/output/__init__.py index 019cc93fb..867f5b0d9 100644 --- a/pytorch_forecasting/layers/output/__init__.py +++ b/pytorch_forecasting/layers/output/__init__.py @@ -2,6 +2,6 @@ Implementation of output layers for PyTorch Forecasting. """ -from .flatten_head import FlattenHead +from ._flatten_head import FlattenHead __all__ = ["FlattenHead"] diff --git a/pytorch_forecasting/layers/output/flatten_head.py b/pytorch_forecasting/layers/output/_flatten_head.py similarity index 100% rename from pytorch_forecasting/layers/output/flatten_head.py rename to pytorch_forecasting/layers/output/_flatten_head.py diff --git a/pytorch_forecasting/models/timexer/_timexer.py b/pytorch_forecasting/models/timexer/_timexer.py index 95dd37697..656638487 100644 --- a/pytorch_forecasting/models/timexer/_timexer.py +++ b/pytorch_forecasting/models/timexer/_timexer.py @@ -81,7 +81,7 @@ def _init_network(self): Initialize the network for TimeXer's architecture. """ - from pytorch_forecasting.layers.attention.attention_layer import ( + from pytorch_forecasting.layers.attention._attention_layer import ( AttentionLayer, FullAttention, ) @@ -90,7 +90,7 @@ def _init_network(self): EnEmbedding, ) from pytorch_forecasting.layers.encoders import Encoder, EncoderLayer - from pytorch_forecasting.layers.output.flatten_head import FlattenHead + from pytorch_forecasting.layers.output._flatten_head import FlattenHead if self.context_length <= self.patch_length: raise ValueError( From 0fab57a05a398790039e806837a6531e04a34bbc Mon Sep 17 00:00:00 2001 From: Pranav Bhat Date: Wed, 4 Jun 2025 11:23:55 +0530 Subject: [PATCH 099/139] refactor notebook and change _timexer.py to fix broken import blocks --- examples/tslib_v2_example.ipynb | 871 +++++++++--------- .../models/base/_tslib_base_model_v2.py | 2 +- .../models/timexer/_timexer.py | 2 +- 3 files changed, 430 insertions(+), 445 deletions(-) diff --git a/examples/tslib_v2_example.ipynb b/examples/tslib_v2_example.ipynb index d1caa11dd..a948ad888 100644 --- a/examples/tslib_v2_example.ipynb +++ b/examples/tslib_v2_example.ipynb @@ -5,77 +5,24 @@ "id": "b5d44943", "metadata": {}, "source": [ - "# TSLib v2 - Example notebook for full pipeline" + "# TSLib for v2 - Example notebook for full pipeline" ] }, { - "cell_type": "code", - "execution_count": 1, - "id": "bcf03bf8", + "cell_type": "markdown", + "id": "b7d27b55", "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Requirement already satisfied: pytorch-forecasting in c:\\users\\prana\\desktop\\code\\pytorch-forecasting\\.venv\\lib\\site-packages (1.3.0)\n", - "Requirement already satisfied: numpy<=3.0.0 in c:\\users\\prana\\desktop\\code\\pytorch-forecasting\\.venv\\lib\\site-packages (from pytorch-forecasting) (2.2.6)\n", - "Requirement already satisfied: torch!=2.0.1,<3.0.0,>=2.0.0 in c:\\users\\prana\\desktop\\code\\pytorch-forecasting\\.venv\\lib\\site-packages (from pytorch-forecasting) (2.7.0)\n", - "Requirement already satisfied: lightning<3.0.0,>=2.0.0 in c:\\users\\prana\\desktop\\code\\pytorch-forecasting\\.venv\\lib\\site-packages (from pytorch-forecasting) (2.5.1.post0)\n", - "Requirement already satisfied: scipy<2.0,>=1.8 in c:\\users\\prana\\desktop\\code\\pytorch-forecasting\\.venv\\lib\\site-packages (from pytorch-forecasting) (1.15.3)\n", - "Requirement already satisfied: pandas<3.0.0,>=1.3.0 in c:\\users\\prana\\desktop\\code\\pytorch-forecasting\\.venv\\lib\\site-packages (from pytorch-forecasting) (2.2.3)\n", - "Requirement already satisfied: scikit-learn<2.0,>=1.2 in c:\\users\\prana\\desktop\\code\\pytorch-forecasting\\.venv\\lib\\site-packages (from pytorch-forecasting) (1.6.1)\n", - "Requirement already satisfied: PyYAML<8.0,>=5.4 in c:\\users\\prana\\desktop\\code\\pytorch-forecasting\\.venv\\lib\\site-packages (from lightning<3.0.0,>=2.0.0->pytorch-forecasting) (6.0.2)\n", - "Requirement already satisfied: fsspec<2026.0,>=2022.5.0 in c:\\users\\prana\\desktop\\code\\pytorch-forecasting\\.venv\\lib\\site-packages (from fsspec[http]<2026.0,>=2022.5.0->lightning<3.0.0,>=2.0.0->pytorch-forecasting) (2025.5.1)\n", - "Requirement already satisfied: lightning-utilities<2.0,>=0.10.0 in c:\\users\\prana\\desktop\\code\\pytorch-forecasting\\.venv\\lib\\site-packages (from lightning<3.0.0,>=2.0.0->pytorch-forecasting) (0.14.3)\n", - "Requirement already satisfied: packaging<25.0,>=20.0 in c:\\users\\prana\\desktop\\code\\pytorch-forecasting\\.venv\\lib\\site-packages (from lightning<3.0.0,>=2.0.0->pytorch-forecasting) (24.2)\n", - "Requirement already satisfied: torchmetrics<3.0,>=0.7.0 in c:\\users\\prana\\desktop\\code\\pytorch-forecasting\\.venv\\lib\\site-packages (from lightning<3.0.0,>=2.0.0->pytorch-forecasting) (1.7.1)\n", - "Requirement already satisfied: tqdm<6.0,>=4.57.0 in c:\\users\\prana\\desktop\\code\\pytorch-forecasting\\.venv\\lib\\site-packages (from lightning<3.0.0,>=2.0.0->pytorch-forecasting) (4.67.1)\n", - "Requirement already satisfied: typing-extensions<6.0,>=4.4.0 in c:\\users\\prana\\desktop\\code\\pytorch-forecasting\\.venv\\lib\\site-packages (from lightning<3.0.0,>=2.0.0->pytorch-forecasting) (4.13.2)\n", - "Requirement already satisfied: pytorch-lightning in c:\\users\\prana\\desktop\\code\\pytorch-forecasting\\.venv\\lib\\site-packages (from lightning<3.0.0,>=2.0.0->pytorch-forecasting) (2.5.1.post0)\n", - "Requirement already satisfied: python-dateutil>=2.8.2 in c:\\users\\prana\\desktop\\code\\pytorch-forecasting\\.venv\\lib\\site-packages (from pandas<3.0.0,>=1.3.0->pytorch-forecasting) (2.9.0.post0)\n", - "Requirement already satisfied: pytz>=2020.1 in c:\\users\\prana\\desktop\\code\\pytorch-forecasting\\.venv\\lib\\site-packages (from pandas<3.0.0,>=1.3.0->pytorch-forecasting) (2025.2)\n", - "Requirement already satisfied: tzdata>=2022.7 in c:\\users\\prana\\desktop\\code\\pytorch-forecasting\\.venv\\lib\\site-packages (from pandas<3.0.0,>=1.3.0->pytorch-forecasting) (2025.2)\n", - "Requirement already satisfied: joblib>=1.2.0 in c:\\users\\prana\\desktop\\code\\pytorch-forecasting\\.venv\\lib\\site-packages (from scikit-learn<2.0,>=1.2->pytorch-forecasting) (1.5.1)\n", - "Requirement already satisfied: threadpoolctl>=3.1.0 in c:\\users\\prana\\desktop\\code\\pytorch-forecasting\\.venv\\lib\\site-packages (from scikit-learn<2.0,>=1.2->pytorch-forecasting) (3.6.0)\n", - "Requirement already satisfied: filelock in c:\\users\\prana\\desktop\\code\\pytorch-forecasting\\.venv\\lib\\site-packages (from torch!=2.0.1,<3.0.0,>=2.0.0->pytorch-forecasting) (3.18.0)\n", - "Requirement already satisfied: sympy>=1.13.3 in c:\\users\\prana\\desktop\\code\\pytorch-forecasting\\.venv\\lib\\site-packages (from torch!=2.0.1,<3.0.0,>=2.0.0->pytorch-forecasting) (1.14.0)\n", - "Requirement already satisfied: networkx in c:\\users\\prana\\desktop\\code\\pytorch-forecasting\\.venv\\lib\\site-packages (from torch!=2.0.1,<3.0.0,>=2.0.0->pytorch-forecasting) (3.4.2)\n", - "Requirement already satisfied: jinja2 in c:\\users\\prana\\desktop\\code\\pytorch-forecasting\\.venv\\lib\\site-packages (from torch!=2.0.1,<3.0.0,>=2.0.0->pytorch-forecasting) (3.1.6)\n", - "Requirement already satisfied: setuptools in c:\\users\\prana\\desktop\\code\\pytorch-forecasting\\.venv\\lib\\site-packages (from torch!=2.0.1,<3.0.0,>=2.0.0->pytorch-forecasting) (80.8.0)\n", - "Requirement already satisfied: aiohttp!=4.0.0a0,!=4.0.0a1 in c:\\users\\prana\\desktop\\code\\pytorch-forecasting\\.venv\\lib\\site-packages (from fsspec[http]<2026.0,>=2022.5.0->lightning<3.0.0,>=2.0.0->pytorch-forecasting) (3.12.0)\n", - "Requirement already satisfied: six>=1.5 in c:\\users\\prana\\desktop\\code\\pytorch-forecasting\\.venv\\lib\\site-packages (from python-dateutil>=2.8.2->pandas<3.0.0,>=1.3.0->pytorch-forecasting) (1.17.0)\n", - "Requirement already satisfied: mpmath<1.4,>=1.1.0 in c:\\users\\prana\\desktop\\code\\pytorch-forecasting\\.venv\\lib\\site-packages (from sympy>=1.13.3->torch!=2.0.1,<3.0.0,>=2.0.0->pytorch-forecasting) (1.3.0)\n", - "Requirement already satisfied: colorama in c:\\users\\prana\\desktop\\code\\pytorch-forecasting\\.venv\\lib\\site-packages (from tqdm<6.0,>=4.57.0->lightning<3.0.0,>=2.0.0->pytorch-forecasting) (0.4.6)\n", - "Requirement already satisfied: MarkupSafe>=2.0 in c:\\users\\prana\\desktop\\code\\pytorch-forecasting\\.venv\\lib\\site-packages (from jinja2->torch!=2.0.1,<3.0.0,>=2.0.0->pytorch-forecasting) (3.0.2)\n", - "Requirement already satisfied: aiohappyeyeballs>=2.5.0 in c:\\users\\prana\\desktop\\code\\pytorch-forecasting\\.venv\\lib\\site-packages (from aiohttp!=4.0.0a0,!=4.0.0a1->fsspec[http]<2026.0,>=2022.5.0->lightning<3.0.0,>=2.0.0->pytorch-forecasting) (2.6.1)\n", - "Requirement already satisfied: aiosignal>=1.1.2 in c:\\users\\prana\\desktop\\code\\pytorch-forecasting\\.venv\\lib\\site-packages (from aiohttp!=4.0.0a0,!=4.0.0a1->fsspec[http]<2026.0,>=2022.5.0->lightning<3.0.0,>=2.0.0->pytorch-forecasting) (1.3.2)\n", - "Requirement already satisfied: attrs>=17.3.0 in c:\\users\\prana\\desktop\\code\\pytorch-forecasting\\.venv\\lib\\site-packages (from aiohttp!=4.0.0a0,!=4.0.0a1->fsspec[http]<2026.0,>=2022.5.0->lightning<3.0.0,>=2.0.0->pytorch-forecasting) (25.3.0)\n", - "Requirement already satisfied: frozenlist>=1.1.1 in c:\\users\\prana\\desktop\\code\\pytorch-forecasting\\.venv\\lib\\site-packages (from aiohttp!=4.0.0a0,!=4.0.0a1->fsspec[http]<2026.0,>=2022.5.0->lightning<3.0.0,>=2.0.0->pytorch-forecasting) (1.6.0)\n", - "Requirement already satisfied: multidict<7.0,>=4.5 in c:\\users\\prana\\desktop\\code\\pytorch-forecasting\\.venv\\lib\\site-packages (from aiohttp!=4.0.0a0,!=4.0.0a1->fsspec[http]<2026.0,>=2022.5.0->lightning<3.0.0,>=2.0.0->pytorch-forecasting) (6.4.4)\n", - "Requirement already satisfied: propcache>=0.2.0 in c:\\users\\prana\\desktop\\code\\pytorch-forecasting\\.venv\\lib\\site-packages (from aiohttp!=4.0.0a0,!=4.0.0a1->fsspec[http]<2026.0,>=2022.5.0->lightning<3.0.0,>=2.0.0->pytorch-forecasting) (0.3.1)\n", - "Requirement already satisfied: yarl<2.0,>=1.17.0 in c:\\users\\prana\\desktop\\code\\pytorch-forecasting\\.venv\\lib\\site-packages (from aiohttp!=4.0.0a0,!=4.0.0a1->fsspec[http]<2026.0,>=2022.5.0->lightning<3.0.0,>=2.0.0->pytorch-forecasting) (1.20.0)\n", - "Requirement already satisfied: idna>=2.0 in c:\\users\\prana\\desktop\\code\\pytorch-forecasting\\.venv\\lib\\site-packages (from yarl<2.0,>=1.17.0->aiohttp!=4.0.0a0,!=4.0.0a1->fsspec[http]<2026.0,>=2022.5.0->lightning<3.0.0,>=2.0.0->pytorch-forecasting) (3.10)\n", - "Note: you may need to restart the kernel to use updated packages.\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "\n", - "[notice] A new release of pip is available: 24.2 -> 25.1.1\n", - "[notice] To update, run: python.exe -m pip install --upgrade pip\n" - ] - } - ], "source": [ - "%pip install pytorch-forecasting" + "## Basic imports for getting started\n", + "\n", + "This notebook is a basic vignette for the usage of the `tslib` data module on the `TimeXer` model for the v2 of PyTorch Forecasting. This is an experimental version and is an unstable version of the API.\n", + "\n", + "Feedback and suggestions on this pipeline - PR [#1836](https://github.com/sktime/pytorch-forecasting/pull/1836)" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1, "id": "550a3fbf", "metadata": {}, "outputs": [], @@ -98,7 +45,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 2, "id": "3a9cd738", "metadata": {}, "outputs": [], @@ -108,9 +55,17 @@ "from pytorch_forecasting.models.timexer import TimeXer" ] }, + { + "cell_type": "markdown", + "id": "2625ed3d", + "metadata": {}, + "source": [ + "## Construct a time series dataset" + ] + }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 3, "id": "a0058487", "metadata": {}, "outputs": [ @@ -164,61 +119,61 @@ "type": "integer" } ], - "ref": "950ad36a-7d6e-448c-b398-040695124321", + "ref": "13ecf12d-1f5b-4a9e-819d-d9f4bf58e846", "rows": [ [ "0", "0", "0", - "-0.09566033622807475", - "0.17294468110110825", + "-0.07483923449414111", + "0.20199381946268397", "0", "1.0", - "0.4623136444068322", + "0.17985441841447924", "0" ], [ "1", "0", "1", - "0.17294468110110825", - "0.4857756236975668", + "0.20199381946268397", + "0.40552523283985864", "0", "0.9950041652780258", - "0.4623136444068322", + "0.17985441841447924", "0" ], [ "2", "0", "2", - "0.4857756236975668", - "0.6302539390886626", + "0.40552523283985864", + "0.5088540116605776", "0", "0.9800665778412416", - "0.4623136444068322", + "0.17985441841447924", "0" ], [ "3", "0", "3", - "0.6302539390886626", - "0.907009118627536", + "0.5088540116605776", + "0.7524218082168045", "0", "0.955336489125606", - "0.4623136444068322", + "0.17985441841447924", "0" ], [ "4", "0", "4", - "0.907009118627536", - "0.8142656823454242", + "0.7524218082168045", + "0.7817964315409408", "0", "0.9210609940028851", - "0.4623136444068322", + "0.17985441841447924", "0" ] ], @@ -261,55 +216,55 @@ " 0\n", " 0\n", " 0\n", - " -0.095660\n", - " 0.172945\n", + " -0.074839\n", + " 0.201994\n", " 0\n", " 1.000000\n", - " 0.462314\n", + " 0.179854\n", " 0\n", " \n", " \n", " 1\n", " 0\n", " 1\n", - " 0.172945\n", - " 0.485776\n", + " 0.201994\n", + " 0.405525\n", " 0\n", " 0.995004\n", - " 0.462314\n", + " 0.179854\n", " 0\n", " \n", " \n", " 2\n", " 0\n", " 2\n", - " 0.485776\n", - " 0.630254\n", + " 0.405525\n", + " 0.508854\n", " 0\n", " 0.980067\n", - " 0.462314\n", + " 0.179854\n", " 0\n", " \n", " \n", " 3\n", " 0\n", " 3\n", - " 0.630254\n", - " 0.907009\n", + " 0.508854\n", + " 0.752422\n", " 0\n", " 0.955336\n", - " 0.462314\n", + " 0.179854\n", " 0\n", " \n", " \n", " 4\n", " 0\n", " 4\n", - " 0.907009\n", - " 0.814266\n", + " 0.752422\n", + " 0.781796\n", " 0\n", " 0.921061\n", - " 0.462314\n", + " 0.179854\n", " 0\n", " \n", " \n", @@ -318,21 +273,21 @@ ], "text/plain": [ " series_id time_idx x y category future_known_feature \\\n", - "0 0 0 -0.095660 0.172945 0 1.000000 \n", - "1 0 1 0.172945 0.485776 0 0.995004 \n", - "2 0 2 0.485776 0.630254 0 0.980067 \n", - "3 0 3 0.630254 0.907009 0 0.955336 \n", - "4 0 4 0.907009 0.814266 0 0.921061 \n", + "0 0 0 -0.074839 0.201994 0 1.000000 \n", + "1 0 1 0.201994 0.405525 0 0.995004 \n", + "2 0 2 0.405525 0.508854 0 0.980067 \n", + "3 0 3 0.508854 0.752422 0 0.955336 \n", + "4 0 4 0.752422 0.781796 0 0.921061 \n", "\n", " static_feature static_feature_cat \n", - "0 0.462314 0 \n", - "1 0.462314 0 \n", - "2 0.462314 0 \n", - "3 0.462314 0 \n", - "4 0.462314 0 " + "0 0.179854 0 \n", + "1 0.179854 0 \n", + "2 0.179854 0 \n", + "3 0.179854 0 \n", + "4 0.179854 0 " ] }, - "execution_count": 4, + "execution_count": 3, "metadata": {}, "output_type": "execute_result" } @@ -365,7 +320,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 4, "id": "89a5adbe", "metadata": {}, "outputs": [ @@ -373,7 +328,7 @@ "name": "stderr", "output_type": "stream", "text": [ - "C:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\pytorch_forecasting\\data\\timeseries\\_timeseries_v2.py:106: UserWarning: TimeSeries is part of an experimental rework of the pytorch-forecasting data layer, scheduled for release with v2.0.0. The API is not stable and may change without prior warning. For beta testing, but not for stable production use. Feedback and suggestions are very welcome in pytorch-forecasting issue 1736, https://github.com/sktime/pytorch-forecasting/issues/1736\n", + "C:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\pytorch_forecasting\\data\\timeseries\\_timeseries_v2.py:105: UserWarning: TimeSeries is part of an experimental rework of the pytorch-forecasting data layer, scheduled for release with v2.0.0. The API is not stable and may change without prior warning. For beta testing, but not for stable production use. Feedback and suggestions are very welcome in pytorch-forecasting issue 1736, https://github.com/sktime/pytorch-forecasting/issues/1736\n", " warn(\n" ] } @@ -392,9 +347,17 @@ ")" ] }, + { + "cell_type": "markdown", + "id": "f8753a6a", + "metadata": {}, + "source": [ + "## Initialise the `TslibDataModule` using the dataset" + ] + }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 5, "id": "5eae9035", "metadata": {}, "outputs": [ @@ -402,7 +365,7 @@ "name": "stderr", "output_type": "stream", "text": [ - "C:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\pytorch_forecasting\\data\\tslib_data_module.py:264: UserWarning: TslibDataModule is experimental and subject to change. The API is not stable and may change without prior warning.\n", + "C:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\pytorch_forecasting\\data\\tslib_data_module.py:273: UserWarning: TslibDataModule is experimental and subject to change. The API is not stable and may change without prior warning.\n", " warnings.warn(\n" ] } @@ -429,7 +392,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 6, "id": "76ebffc1", "metadata": {}, "outputs": [ @@ -439,7 +402,7 @@ "dict" ] }, - "execution_count": 7, + "execution_count": 6, "metadata": {}, "output_type": "execute_result" } @@ -450,7 +413,7 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 7, "id": "b1843233", "metadata": {}, "outputs": [ @@ -475,8 +438,7 @@ " 'static': [],\n", " 'known': [2],\n", " 'unknown': [0, 1, 3, 4],\n", - " 'target': [0],\n", - " 'all': []},\n", + " 'target': [0]},\n", " 'n_features': {'categorical': 2,\n", " 'continuous': 3,\n", " 'static': 2,\n", @@ -492,7 +454,7 @@ " 'features': 'MS'}" ] }, - "execution_count": 8, + "execution_count": 7, "metadata": {}, "output_type": "execute_result" } @@ -503,7 +465,7 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 8, "id": "12036e70", "metadata": {}, "outputs": [], @@ -513,9 +475,17 @@ "from pytorch_forecasting.metrics import MAE, SMAPE" ] }, + { + "cell_type": "markdown", + "id": "dd9451ee", + "metadata": {}, + "source": [ + "## Initialise the model" + ] + }, { "cell_type": "code", - "execution_count": null, + "execution_count": 9, "id": "429b5f15", "metadata": {}, "outputs": [ @@ -524,7 +494,11 @@ "output_type": "stream", "text": [ "C:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\pytorch_forecasting\\models\\base\\_base_model_v2.py:60: UserWarning: The Model 'TimeXer' is part of an experimental reworkof the pytorch-forecasting model layer, scheduled for release with v2.0.0. The API is not stable and may change without prior warning. This class is intended for beta testing and as a basic skeleton, but not for stable production use. Feedback and suggestions are very welcome in pytorch-forecasting issue 1736, https://github.com/sktime/pytorch-forecasting/issues/1736\n", - " warn(\n" + " warn(\n", + "C:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\pytorch_forecasting\\models\\base\\_tslib_base_model_v2.py:60: UserWarning: The Model 'TimeXer' is part of an experimental implementationof the pytorch-forecasting model layer for Time Series Library, scheduledfor release with v2.0.0. The API is not stableand may change without prior warning. This class is intended for betatesting, not for stable production use.\n", + " warn(\n", + "C:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\pytorch_forecasting\\models\\timexer\\_timexer.py:103: UserWarning: Context length (30) is not divisible by patch length. This may lead to unexpected behavior, as sometime steps will not be used in the model.\n", + " warn.warn(\n" ] } ], @@ -553,7 +527,7 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 10, "id": "02605f9b", "metadata": {}, "outputs": [ @@ -562,7 +536,7 @@ "output_type": "stream", "text": [ "Using default `ModelCheckpoint`. Consider installing `litmodels` package to enable `LitModelCheckpoint` for automatic upload to the Lightning model registry.\n", - "GPU available: False, used: False\n", + "GPU available: True (cuda), used: True\n", "TPU available: False, using: 0 TPU cores\n", "HPU available: False, using: 0 HPUs\n" ] @@ -580,9 +554,17 @@ ")" ] }, + { + "cell_type": "markdown", + "id": "e22756b2", + "metadata": {}, + "source": [ + "## Fit the trainer on the model and feed data using the data module" + ] + }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 11, "id": "6e9117d2", "metadata": {}, "outputs": [ @@ -590,6 +572,8 @@ "name": "stderr", "output_type": "stream", "text": [ + "You are using a CUDA device ('NVIDIA GeForce RTX 3050 6GB Laptop GPU') that has Tensor Cores. To properly utilize them, you should set `torch.set_float32_matmul_precision('medium' | 'high')` which will trade-off precision for performance. For more details, read https://pytorch.org/docs/stable/generated/torch.set_float32_matmul_precision.html#torch.set_float32_matmul_precision\n", + "LOCAL_RANK: 0 - CUDA_VISIBLE_DEVICES: [0]\n", "\n", " | Name | Type | Params | Mode \n", "----------------------------------------------------------------\n", @@ -610,7 +594,7 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "d3960728820d491b8705df3797c85c65", + "model_id": "24d5067729cd4bf6a5643930eeb292dd", "version_major": 2, "version_minor": 0 }, @@ -626,9 +610,9 @@ "output_type": "stream", "text": [ "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\lightning\\pytorch\\trainer\\connectors\\data_connector.py:425: The 'val_dataloader' does not have many workers which may be a bottleneck. Consider increasing the value of the `num_workers` argument` to `num_workers=15` in the `DataLoader` to improve performance.\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\lightning\\pytorch\\trainer\\connectors\\data_connector.py:425: The 'train_dataloader' does not have many workers which may be a bottleneck. Consider increasing the value of the `num_workers` argument` to `num_workers=15` in the `DataLoader` to improve performance.\n", "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\lightning\\pytorch\\loops\\fit_loop.py:310: The number of training batches (42) is smaller than the logging interval Trainer(log_every_n_steps=50). Set a lower value for log_every_n_steps if you want to see logs for the training epoch.\n" @@ -637,7 +621,7 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "19847f2d252543779a6fadc4c0694733", + "model_id": "e07c7950c550457487b2061bae5e0315", "version_major": 2, "version_minor": 0 }, @@ -652,96 +636,96 @@ "name": "stderr", "output_type": "stream", "text": [ - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([18, 1, 1])) that is different to the input size (torch.Size([18, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([18, 1, 1])) that is different to the input size (torch.Size([18, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n" ] }, { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "328d918532ae425bb01cd7f26ebdcf95", + "model_id": "f069022fd4894c05bd5a463f7ac703a4", "version_major": 2, "version_minor": 0 }, @@ -756,114 +740,114 @@ "name": "stderr", "output_type": "stream", "text": [ - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([29, 1, 1])) that is different to the input size (torch.Size([29, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([29, 1, 1])) that is different to the input size (torch.Size([29, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([18, 1, 1])) that is different to the input size (torch.Size([18, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([18, 1, 1])) that is different to the input size (torch.Size([18, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n" ] }, { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "84108218c0e44320a85e168bcf7353c8", + "model_id": "4f2b98f770f7407bbceb69026858dc13", "version_major": 2, "version_minor": 0 }, @@ -878,114 +862,114 @@ "name": "stderr", "output_type": "stream", "text": [ - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([29, 1, 1])) that is different to the input size (torch.Size([29, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([29, 1, 1])) that is different to the input size (torch.Size([29, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([18, 1, 1])) that is different to the input size (torch.Size([18, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([18, 1, 1])) that is different to the input size (torch.Size([18, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n" ] }, { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "0869a7aeb5c14741baad858e84c32cdb", + "model_id": "403034168b554544ae718ae235756388", "version_major": 2, "version_minor": 0 }, @@ -1000,114 +984,114 @@ "name": "stderr", "output_type": "stream", "text": [ - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([29, 1, 1])) that is different to the input size (torch.Size([29, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([29, 1, 1])) that is different to the input size (torch.Size([29, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([18, 1, 1])) that is different to the input size (torch.Size([18, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([18, 1, 1])) that is different to the input size (torch.Size([18, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n" ] }, { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "cb72ae23b5e744b599f7038ce1acc834", + "model_id": "f07af2441e414094b0d823dc119354a2", "version_major": 2, "version_minor": 0 }, @@ -1122,114 +1106,114 @@ "name": "stderr", "output_type": "stream", "text": [ - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([29, 1, 1])) that is different to the input size (torch.Size([29, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([29, 1, 1])) that is different to the input size (torch.Size([29, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([18, 1, 1])) that is different to the input size (torch.Size([18, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([18, 1, 1])) that is different to the input size (torch.Size([18, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n" ] }, { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "4a625e7e55b04a0581f5ffa0f33f31be", + "model_id": "e5e3791b226e4782b42e7377d1412807", "version_major": 2, "version_minor": 0 }, @@ -1244,23 +1228,23 @@ "name": "stderr", "output_type": "stream", "text": [ - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([29, 1, 1])) that is different to the input size (torch.Size([29, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([29, 1, 1])) that is different to the input size (torch.Size([29, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", "`Trainer.fit` stopped: `max_epochs=5` reached.\n" ] @@ -1270,9 +1254,17 @@ "trainer.fit(model, data_module)" ] }, + { + "cell_type": "markdown", + "id": "16e2d445", + "metadata": {}, + "source": [ + "## Test the model" + ] + }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 12, "id": "dbf1ace6", "metadata": {}, "outputs": [ @@ -1280,13 +1272,14 @@ "name": "stderr", "output_type": "stream", "text": [ + "LOCAL_RANK: 0 - CUDA_VISIBLE_DEVICES: [0]\n", "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\lightning\\pytorch\\trainer\\connectors\\data_connector.py:425: The 'test_dataloader' does not have many workers which may be a bottleneck. Consider increasing the value of the `num_workers` argument` to `num_workers=15` in the `DataLoader` to improve performance.\n" ] }, { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "b2a93991b3ef442693bd0c38a4db2e76", + "model_id": "1be83989d9b841388aa5e69299a93298", "version_major": 2, "version_minor": 0 }, @@ -1301,23 +1294,23 @@ "name": "stderr", "output_type": "stream", "text": [ - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:610: UserWarning: Using a target size (torch.Size([29, 1, 1])) that is different to the input size (torch.Size([29, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([29, 1, 1])) that is different to the input size (torch.Size([29, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", " return F.mse_loss(input, target, reduction=self.reduction)\n" ] }, @@ -1328,9 +1321,9 @@ "────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────\n", " Test metric DataLoader 0\n", "────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────\n", - " test_MAE 0.37911707162857056\n", - " test_SMAPE 0.8660855889320374\n", - " test_loss 0.1897258311510086\n", + " test_MAE 0.3953440487384796\n", + " test_SMAPE 0.9527823328971863\n", + " test_loss 0.205839142203331\n", "────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────\n" ] } @@ -1341,7 +1334,7 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 13, "id": "250b128a", "metadata": {}, "outputs": [ @@ -1398,7 +1391,7 @@ ")" ] }, - "execution_count": 14, + "execution_count": 13, "metadata": {}, "output_type": "execute_result" } @@ -1409,7 +1402,7 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": 14, "id": "f730b49a", "metadata": {}, "outputs": [ @@ -1417,38 +1410,38 @@ "name": "stdout", "output_type": "stream", "text": [ - "Prediction: tensor([[0.4459],\n", - " [0.4574],\n", - " [0.4523],\n", - " [0.4688],\n", - " [0.4577],\n", - " [0.4392],\n", - " [0.4170],\n", - " [0.4361],\n", - " [0.4367],\n", - " [0.4475],\n", - " [0.4552],\n", - " [0.4350],\n", - " [0.4449],\n", - " [0.4299],\n", - " [0.4112],\n", - " [0.4330],\n", - " [0.4180],\n", - " [0.4473],\n", - " [0.4113],\n", - " [0.4923],\n", - " [0.4416],\n", - " [0.4613],\n", - " [0.4616],\n", - " [0.4454],\n", - " [0.4567],\n", - " [0.4397],\n", - " [0.4370],\n", - " [0.4406],\n", - " [0.4291],\n", - " [0.4336],\n", - " [0.4317],\n", - " [0.4308]])\n" + "Prediction: tensor([[0.3538],\n", + " [0.3561],\n", + " [0.3795],\n", + " [0.3685],\n", + " [0.3574],\n", + " [0.3608],\n", + " [0.3478],\n", + " [0.3433],\n", + " [0.3494],\n", + " [0.3422],\n", + " [0.3275],\n", + " [0.3395],\n", + " [0.3370],\n", + " [0.3410],\n", + " [0.3316],\n", + " [0.3429],\n", + " [0.3147],\n", + " [0.3117],\n", + " [0.3006],\n", + " [0.3454],\n", + " [0.3531],\n", + " [0.3538],\n", + " [0.3559],\n", + " [0.3683],\n", + " [0.3617],\n", + " [0.3578],\n", + " [0.3609],\n", + " [0.3358],\n", + " [0.3451],\n", + " [0.3471],\n", + " [0.3465],\n", + " [0.3267]])\n" ] } ], @@ -1460,14 +1453,6 @@ "\n", " print(\"Prediction:\", y_pred[\"prediction\"])" ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "1956ed7e", - "metadata": {}, - "outputs": [], - "source": [] } ], "metadata": { diff --git a/pytorch_forecasting/models/base/_tslib_base_model_v2.py b/pytorch_forecasting/models/base/_tslib_base_model_v2.py index b7ef2a51b..02026c671 100644 --- a/pytorch_forecasting/models/base/_tslib_base_model_v2.py +++ b/pytorch_forecasting/models/base/_tslib_base_model_v2.py @@ -10,7 +10,7 @@ import torch.nn as nn from torch.optim import Optimizer -from pytorch_forecasting.models.base import BaseModel +from pytorch_forecasting.models.base._base_model_v2 import BaseModel class TslibBaseModel(BaseModel): diff --git a/pytorch_forecasting/models/timexer/_timexer.py b/pytorch_forecasting/models/timexer/_timexer.py index 656638487..c0946146b 100644 --- a/pytorch_forecasting/models/timexer/_timexer.py +++ b/pytorch_forecasting/models/timexer/_timexer.py @@ -81,7 +81,7 @@ def _init_network(self): Initialize the network for TimeXer's architecture. """ - from pytorch_forecasting.layers.attention._attention_layer import ( + from pytorch_forecasting.layers.attention import ( AttentionLayer, FullAttention, ) From 668c9017ac23b0c4619fdc743282409ac44e653a Mon Sep 17 00:00:00 2001 From: Pranav Bhat Date: Wed, 4 Jun 2025 20:39:15 +0530 Subject: [PATCH 100/139] remove features parameter and precompute the feature mode (S/MS/M) inside the data module and pass to TslibBaseModel --- pytorch_forecasting/data/tslib_data_module.py | 29 ++++++- .../models/base/_tslib_base_model_v2.py | 3 + .../models/timexer/_timexer.py | 81 ++++++++++++++++++- 3 files changed, 109 insertions(+), 4 deletions(-) diff --git a/pytorch_forecasting/data/tslib_data_module.py b/pytorch_forecasting/data/tslib_data_module.py index 4da3e0ae0..2d7538922 100644 --- a/pytorch_forecasting/data/tslib_data_module.py +++ b/pytorch_forecasting/data/tslib_data_module.py @@ -233,7 +233,6 @@ def __init__( context_length: int, prediction_length: int, freq: str = "h", - features: str = "MS", add_relative_time_idx: bool = False, add_target_scales: bool = False, target_normalizer: Union[ @@ -259,7 +258,6 @@ def __init__( self.context_length = context_length self.prediction_length = prediction_length self.freq = freq - self.features = features self.add_relative_time_idx = add_relative_time_idx self.add_target_scales = add_target_scales self.batch_size = batch_size @@ -354,6 +352,18 @@ def _prepare_metadata(self) -> dict[str, Any]: all_features = cols.get("x", []) static_features = cols.get("st", []) target_features = cols.get("y", []) + + if len(target_features) == 0: + raise ValueError( + "The time series dataset must have at least one target variable. " + "Please provide a dataset with a target variable." + ) + if len(all_features) == 0: + raise ValueError( + "The time series dataset must have at least one feature. " + "Please provide a dataset with features." + ) + feature_names["all"] = list(all_features) feature_names["static"] = list(static_features) feature_names["target"] = list(target_features) @@ -387,6 +397,21 @@ def _prepare_metadata(self) -> dict[str, Any]: n_features = {k: len(v) for k, v in feature_names.items()} + # detect the feature mode - S/MS/M + + n_targets = n_features["target"] + n_cont = n_features["continuous"] + n_cat = n_features["categorical"] + + if n_targets == 1 and (n_cont + n_cat) == 1: + self.features = "S" + elif n_targets == 1 and (n_cont + n_cat) > 1: + self.features = "MS" + elif n_targets > 1 and (n_cont + n_cat) > 0: + self.features = "M" + else: + self.features = "MS" + metadata = { "feature_names": feature_names, "feature_indices": feature_indices, diff --git a/pytorch_forecasting/models/base/_tslib_base_model_v2.py b/pytorch_forecasting/models/base/_tslib_base_model_v2.py index 02026c671..9bf705b08 100644 --- a/pytorch_forecasting/models/base/_tslib_base_model_v2.py +++ b/pytorch_forecasting/models/base/_tslib_base_model_v2.py @@ -85,6 +85,9 @@ def __init__( self.feature_names = metadata.get("feature_names", {}) + # feature-mode + self.features = metadata.get("features", "MS") + def _init_network(self): """ Initialize the network architecture. diff --git a/pytorch_forecasting/models/timexer/_timexer.py b/pytorch_forecasting/models/timexer/_timexer.py index c0946146b..7830f9eff 100644 --- a/pytorch_forecasting/models/timexer/_timexer.py +++ b/pytorch_forecasting/models/timexer/_timexer.py @@ -27,10 +27,79 @@ class TimeXer(TslibBaseModel): + """ + An implementation of TimeXer model for v2 of pytorch-forecasting. + + TimeXer empowers the canonical transformer with the ability to reconcile + endogenous and exogenous information without any architectural modifications + and achieves consistent state-of-the-art performance across twelve real-world + forecasting benchmarks. + + TimeXer employs patch-level and variate-level representations respectively for + endogenous and exogenous variables, with an endogenous global token as a bridge + in-between. With this design, TimeXer can jointly capture intra-endogenous + temporal dependencies and exogenous-to-endogenous correlations. + + Parameters + ---------- + loss: nn.Module + Loss function to use for training. + enc_in: int, optional + Number of input features for the encoder. If not provided, it will be set to + the number of continuous features in the dataset. + hidden_size: int, default=512 + Dimension of the model embeddings and hidden representations of features. + n_heads: int, default=8 + Number of attention heads in the multi-head attention mechanism.\ + e_layers: int, default=2 + Number of encoder layers in the transformer architecture. + d_ff: int, default=2048 + Dimension of the feed-forward network in the transformer architecture. + dropout: float, default=0.1 + Dropout rate for regularization. This is used throughout the model to prevent overfitting. + patch_length: int, default=24 + Length of each non-overlapping patch for endogenous variable tokenization. + factor: int, default=5 + Factor for the attention mechanism, controlling the number of keys and values. + activation: str, default='relu' + Activation function to use in the feed-forward network. Common choices are 'relu', 'gelu', etc. + endogenous_vars: Optional[list[str]], default=None + List of endogenous variable names to be used in the model. If None, all historical values + for the target variable are used. + exogenous_vars: Optional[list[str]], default=None + List of exogenous variable names to be used in the model. If None, all historical values + for continous variables are used. + logging_metrics: Optional[list[nn.Module]], default=None + List of metrics to log during training, validation, and testing. + optimizer: Optional[Union[Optimizer, str]], default='adam' + Optimizer to use for training. Can be a string name or an instance of an optimizer. + optimizer_params: Optional[dict], default=None + Parameters for the optimizer. If None, default parameters for the optimizer will be used. + lr_scheduler: Optional[str], default=None + Learning rate scheduler to use. If None, no scheduler is used. + lr_scheduler_params: Optional[dict], default=None + Parameters for the learning rate scheduler. If None, default parameters for the scheduler will be used. + metadata: Optional[dict], default=None + Metadata for the model from TslibDataModule. This can include information about the dataset, + such as the number of time steps, number of features, etc. It is used to initialize the model + and ensure it is compatible with the data being used. + + References + ---------- + [1] https://arxiv.org/abs/2402.19072 + [2] https://github.com/thuml/TimeXer + + Notes + ----- + [1] This implementation handles only continous variables in the context length. Categorical variables + support will be added in the future. + [2] The `TimeXer` model obtains many of its attributes from the `TslibBaseModel` class, which is a base class + where a lot of the boiler plate code for metadata handling and model initialization is implemented. + """ # noqa: E501 + def __init__( self, loss: nn.Module, - features: str = "MS", enc_in: int = None, hidden_size: int = 512, n_heads: int = 8, @@ -60,7 +129,14 @@ def __init__( metadata=metadata, ) - self.features = features + warn.warn( + "TimeXer is an experimental model implemented on TslibBaseModelV2. " + "It is an unstable version and maybe subject to unannouced changes." + "Please use with caution. Feedback on the design and implementation is" + "" + "welcome. On the issue #1833 - https://github.com/sktime/pytorch-forecasting/issues/1833", + ) + self.enc_in = enc_in self.hidden_size = hidden_size self.n_heads = n_heads @@ -307,6 +383,7 @@ def forward(self, x: dict[str, torch.Tensor]) -> dict[str, torch.Tensor]: Returns: dict[str, torch.Tensor]: Model predictions. """ + # this is a feature mode, pre-computed using TslibBaseModel. if self.features == "MS": out = self._forecast(x) else: From 913c4189b6cef32ac04c510f2a184c49b79c17ef Mon Sep 17 00:00:00 2001 From: Pranav Bhat Date: Fri, 6 Jun 2025 12:21:55 +0530 Subject: [PATCH 101/139] remove unused import in _base_model_v2 --- pytorch_forecasting/models/base/_base_model_v2.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/pytorch_forecasting/models/base/_base_model_v2.py b/pytorch_forecasting/models/base/_base_model_v2.py index 5a461e251..aceec0869 100644 --- a/pytorch_forecasting/models/base/_base_model_v2.py +++ b/pytorch_forecasting/models/base/_base_model_v2.py @@ -14,8 +14,6 @@ import torch.nn as nn from torch.optim import Optimizer -from pytorch_forecasting.metrics import Metric - class BaseModel(LightningModule): def __init__( From 8990e8b60f7873336dd2204babb31a4aef3782d5 Mon Sep 17 00:00:00 2001 From: Pranav Bhat Date: Fri, 6 Jun 2025 15:15:34 +0530 Subject: [PATCH 102/139] add function to validate presence of continous and categorical indices --- pytorch_forecasting/data/tslib_data_module.py | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/pytorch_forecasting/data/tslib_data_module.py b/pytorch_forecasting/data/tslib_data_module.py index 2d7538922..4ab27cc2b 100644 --- a/pytorch_forecasting/data/tslib_data_module.py +++ b/pytorch_forecasting/data/tslib_data_module.py @@ -301,6 +301,40 @@ def __init__( else: self.continuous_indices.append(idx) + self._validate_indices() + + def _validate_indices(self): + """ + Validate that we have meaningful features for training. + Raises warnings for missing features or indices. + """ + + has_continuous = self.continuous_indices and len(self.continuous_indices) > 0 + has_categorical = self.categorical_indices and len(self.categorical_indices) > 0 + + if not has_continuous and not has_categorical: + raise ValueError( + "No categorical or continous features found in the dataset." + "Cannot proceed with model training. Please ensure that your" + "dataset has at least one column with continous or categorical data." + ) + + if not has_continuous: + warnings.warn( + "No continuous features found in the dataset. " + "Some models (TimeXer) requires continous features. " + "Consider adding continous featuresinto the dataset.", + UserWarning, + ) + + if not has_categorical: + warnings.warn( + "No categorical features found in the dataset. " + "This may limit the model capabilities and and restrict " + "the usage to continuous features only.", + UserWarning, + ) + def _prepare_metadata(self) -> dict[str, Any]: """ Prepare metadata for `tslib` time series data module. From 2c518ee554afee1e45d5a6dfa69782c35e75498b Mon Sep 17 00:00:00 2001 From: Aryan Saini Date: Sat, 7 Jun 2025 01:08:53 +0530 Subject: [PATCH 103/139] add new base classes --- .../models/base/_base_object.py | 20 +++++++++++++++---- .../models/deepar/_deepar_metadata.py | 5 ++--- .../models/mlp/_decodermlp_metadata.py | 4 ++-- .../models/nbeats/_nbeats_metadata.py | 5 ++--- .../tft_v2_metadata.py | 5 ++--- .../models/tide/_tide_metadata.py | 5 ++--- .../tests/test_all_estimators.py | 2 +- .../tests/test_all_estimators_v2.py | 2 +- 8 files changed, 28 insertions(+), 20 deletions(-) diff --git a/pytorch_forecasting/models/base/_base_object.py b/pytorch_forecasting/models/base/_base_object.py index 0106b7afa..a7cccfae5 100644 --- a/pytorch_forecasting/models/base/_base_object.py +++ b/pytorch_forecasting/models/base/_base_object.py @@ -17,10 +17,6 @@ class _BasePtForecaster(_BaseObject): This class points to model objects and contains metadata as tags. """ - _tags = { - "object_type": "forecaster_pytorch", - } - @classmethod def get_model_cls(cls): """Get model class.""" @@ -112,3 +108,19 @@ def create_test_instances_and_names(cls, parameter_set="default"): names = [cls.__name__] return objs, names + + +class _BasePtForecasterV1(_BasePtForecaster): + """Base class for PyTorch Forecasting v1 forecasters.""" + + _tags = { + "object_type": "forecaster_pytorch_v1", + } + + +class _BasePtForecasterV2(_BasePtForecaster): + """Base class for PyTorch Forecasting v2 forecasters.""" + + _tags = { + "object_type": "forecaster_pytorch_v2", + } diff --git a/pytorch_forecasting/models/deepar/_deepar_metadata.py b/pytorch_forecasting/models/deepar/_deepar_metadata.py index 28754298a..27e5af168 100644 --- a/pytorch_forecasting/models/deepar/_deepar_metadata.py +++ b/pytorch_forecasting/models/deepar/_deepar_metadata.py @@ -1,15 +1,14 @@ """DeepAR metadata container.""" -from pytorch_forecasting.models.base._base_object import _BasePtForecaster +from pytorch_forecasting.models.base._base_object import _BasePtForecasterV1 -class DeepARMetadata(_BasePtForecaster): +class DeepARMetadata(_BasePtForecasterV1): """DeepAR metadata container.""" _tags = { "info:name": "DeepAR", "info:compute": 3, - "object_type": "ptf-v1", "authors": ["jdb78"], "capability:exogenous": True, "capability:multivariate": True, diff --git a/pytorch_forecasting/models/mlp/_decodermlp_metadata.py b/pytorch_forecasting/models/mlp/_decodermlp_metadata.py index c7abead33..c3fd71dce 100644 --- a/pytorch_forecasting/models/mlp/_decodermlp_metadata.py +++ b/pytorch_forecasting/models/mlp/_decodermlp_metadata.py @@ -1,9 +1,9 @@ """DecoderMLP metadata container.""" -from pytorch_forecasting.models.base._base_object import _BasePtForecaster +from pytorch_forecasting.models.base._base_object import _BasePtForecasterV1 -class DecoderMLPMetadata(_BasePtForecaster): +class DecoderMLPMetadata(_BasePtForecasterV1): """DecoderMLP metadata container.""" _tags = { diff --git a/pytorch_forecasting/models/nbeats/_nbeats_metadata.py b/pytorch_forecasting/models/nbeats/_nbeats_metadata.py index f644b378a..9fd3132f1 100644 --- a/pytorch_forecasting/models/nbeats/_nbeats_metadata.py +++ b/pytorch_forecasting/models/nbeats/_nbeats_metadata.py @@ -1,15 +1,14 @@ """NBeats metadata container.""" -from pytorch_forecasting.models.base._base_object import _BasePtForecaster +from pytorch_forecasting.models.base._base_object import _BasePtForecasterV1 -class NBeatsMetadata(_BasePtForecaster): +class NBeatsMetadata(_BasePtForecasterV1): """NBeats metadata container.""" _tags = { "info:name": "NBeats", "info:compute": 1, - "object_type": "ptf-v1", "authors": ["jdb78"], "capability:exogenous": False, "capability:multivariate": False, diff --git a/pytorch_forecasting/models/temporal_fusion_transformer/tft_v2_metadata.py b/pytorch_forecasting/models/temporal_fusion_transformer/tft_v2_metadata.py index 91e2440ed..41d2df27b 100644 --- a/pytorch_forecasting/models/temporal_fusion_transformer/tft_v2_metadata.py +++ b/pytorch_forecasting/models/temporal_fusion_transformer/tft_v2_metadata.py @@ -1,14 +1,13 @@ """TFT metadata container.""" -from pytorch_forecasting.models.base._base_object import _BasePtForecaster +from pytorch_forecasting.models.base._base_object import _BasePtForecasterV2 -class TFTMetadata(_BasePtForecaster): +class TFTMetadata(_BasePtForecasterV2): """TFT metadata container.""" _tags = { "info:name": "TFT", - "object_type": "ptf-v2", "authors": ["phoeenniixx"], "capability:exogenous": True, "capability:multivariate": True, diff --git a/pytorch_forecasting/models/tide/_tide_metadata.py b/pytorch_forecasting/models/tide/_tide_metadata.py index 49a2acc67..e8866b38e 100644 --- a/pytorch_forecasting/models/tide/_tide_metadata.py +++ b/pytorch_forecasting/models/tide/_tide_metadata.py @@ -1,15 +1,14 @@ """TiDE metadata container.""" -from pytorch_forecasting.models.base._base_object import _BasePtForecaster +from pytorch_forecasting.models.base._base_object import _BasePtForecasterV1 -class TiDEModelMetadata(_BasePtForecaster): +class TiDEModelMetadata(_BasePtForecasterV1): """Metadata container for TiDE Model.""" _tags = { "info:name": "TiDEModel", "info:compute": 3, - "object_type": "ptf-v1", "authors": ["Sohaib-Ahmed21"], "capability:exogenous": True, "capability:multivariate": True, diff --git a/pytorch_forecasting/tests/test_all_estimators.py b/pytorch_forecasting/tests/test_all_estimators.py index add3ef7ba..c690a5460 100644 --- a/pytorch_forecasting/tests/test_all_estimators.py +++ b/pytorch_forecasting/tests/test_all_estimators.py @@ -245,7 +245,7 @@ def _integration( class TestAllPtForecasters(PackageConfig, BaseFixtureGenerator): """Generic tests for all objects in the mini package.""" - object_type_filter = "ptf-v1" + object_type_filter = "forecaster_pytorch_v1" def test_doctest_examples(self, object_class): """Runs doctests for estimator class.""" diff --git a/pytorch_forecasting/tests/test_all_estimators_v2.py b/pytorch_forecasting/tests/test_all_estimators_v2.py index 857ea1f76..85ebe1cd0 100644 --- a/pytorch_forecasting/tests/test_all_estimators_v2.py +++ b/pytorch_forecasting/tests/test_all_estimators_v2.py @@ -124,7 +124,7 @@ def _integration( class TestAllPtForecastersV2(PackageConfig, BaseFixtureGenerator): """Generic tests for all objects in the mini package.""" - object_type_filter = "ptf-v2" + object_type_filter = "forecaster_pytorch_v2" def test_doctest_examples(self, object_class): """Runs doctests for estimator class.""" From 7a5c58fd37bccff3eeb8c065dca252c7c4a872c5 Mon Sep 17 00:00:00 2001 From: Aryan Saini Date: Mon, 9 Jun 2025 02:07:31 +0530 Subject: [PATCH 104/139] remove try block --- .../tests/test_all_estimators_v2.py | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/pytorch_forecasting/tests/test_all_estimators_v2.py b/pytorch_forecasting/tests/test_all_estimators_v2.py index 85ebe1cd0..7e5720a4b 100644 --- a/pytorch_forecasting/tests/test_all_estimators_v2.py +++ b/pytorch_forecasting/tests/test_all_estimators_v2.py @@ -109,16 +109,15 @@ def _integration( **kwargs, ) - try: - trainer.fit( - net, - train_dataloaders=train_dataloader, - val_dataloaders=val_dataloader, - ) - test_outputs = trainer.test(net, dataloaders=test_dataloader) - assert len(test_outputs) > 0 - finally: - shutil.rmtree(tmp_path, ignore_errors=True) + trainer.fit( + net, + train_dataloaders=train_dataloader, + val_dataloaders=val_dataloader, + ) + test_outputs = trainer.test(net, dataloaders=test_dataloader) + assert len(test_outputs) > 0 + + shutil.rmtree(tmp_path, ignore_errors=True) class TestAllPtForecastersV2(PackageConfig, BaseFixtureGenerator): From d0009ffb3107d259daf9e528f694df4b45c0cf36 Mon Sep 17 00:00:00 2001 From: Pranav Bhat Date: Mon, 9 Jun 2025 10:58:42 +0530 Subject: [PATCH 105/139] fix bug in quantile predictions for timexer --- pytorch_forecasting/models/timexer/_timexer.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/pytorch_forecasting/models/timexer/_timexer.py b/pytorch_forecasting/models/timexer/_timexer.py index 7830f9eff..8c00a01cf 100644 --- a/pytorch_forecasting/models/timexer/_timexer.py +++ b/pytorch_forecasting/models/timexer/_timexer.py @@ -6,7 +6,8 @@ ################################################################ # NOTE: This implementation of TimeXer derives from PR #1797. # # It is experimental and seeks to clarify design decisions. # -# IT IS STRICTLY A PART OF THE v2 design of PTF. # +# IT IS STRICTLY A PART OF THE v2 design of PTF. It overrides # +# the v1 version introduced in v1 by PR #1797 # ################################################################ from typing import Any, Optional, Union @@ -398,10 +399,11 @@ def forward(self, x: dict[str, torch.Tensor]) -> dict[str, torch.Tensor]: target_indices = range(self.target_dim) if self.n_quantiles is not None: if self.target_dim > 1: + # for multiple targets, we return a list of tensors of shape + # [batch, timesteps, quantiles] prediction = [prediction[..., i, :] for i in target_indices] - else: - prediction = prediction[..., 0] else: + # point prediction if len(target_indices) == 1: prediction = prediction[..., 0] else: From c9d3c26fb9b18c9afdb52da0763562919ecab14d Mon Sep 17 00:00:00 2001 From: Pranav Bhat Date: Mon, 9 Jun 2025 11:52:36 +0530 Subject: [PATCH 106/139] refactor tensor handling to return pure tensor outputs with timexer and notebook This change refactors the earlier version of the code, where a list of tensor is returned for multi target datasets. With the v2, a pure tensor is returned for all types of predictions and it moves away from the older, more error prone methof returning list of tensors --- examples/tslib_v2_example.ipynb | 1229 ++++++++--------- .../models/timexer/_timexer.py | 13 - 2 files changed, 578 insertions(+), 664 deletions(-) diff --git a/examples/tslib_v2_example.ipynb b/examples/tslib_v2_example.ipynb index a948ad888..f659e3d44 100644 --- a/examples/tslib_v2_example.ipynb +++ b/examples/tslib_v2_example.ipynb @@ -119,61 +119,61 @@ "type": "integer" } ], - "ref": "13ecf12d-1f5b-4a9e-819d-d9f4bf58e846", + "ref": "c9857045-a68d-4ffa-88c5-5a89d998296b", "rows": [ [ "0", "0", "0", - "-0.07483923449414111", - "0.20199381946268397", + "-0.053473564791053224", + "0.07936543423104955", "0", "1.0", - "0.17985441841447924", + "0.07624037564165775", "0" ], [ "1", "0", "1", - "0.20199381946268397", - "0.40552523283985864", + "0.07936543423104955", + "0.47510107923326006", "0", "0.9950041652780258", - "0.17985441841447924", + "0.07624037564165775", "0" ], [ "2", "0", "2", - "0.40552523283985864", - "0.5088540116605776", + "0.47510107923326006", + "0.5532736977674955", "0", "0.9800665778412416", - "0.17985441841447924", + "0.07624037564165775", "0" ], [ "3", "0", "3", - "0.5088540116605776", - "0.7524218082168045", + "0.5532736977674955", + "0.5934601539777484", "0", "0.955336489125606", - "0.17985441841447924", + "0.07624037564165775", "0" ], [ "4", "0", "4", - "0.7524218082168045", - "0.7817964315409408", + "0.5934601539777484", + "0.999893228642273", "0", "0.9210609940028851", - "0.17985441841447924", + "0.07624037564165775", "0" ] ], @@ -216,55 +216,55 @@ " 0\n", " 0\n", " 0\n", - " -0.074839\n", - " 0.201994\n", + " -0.053474\n", + " 0.079365\n", " 0\n", " 1.000000\n", - " 0.179854\n", + " 0.07624\n", " 0\n", " \n", " \n", " 1\n", " 0\n", " 1\n", - " 0.201994\n", - " 0.405525\n", + " 0.079365\n", + " 0.475101\n", " 0\n", " 0.995004\n", - " 0.179854\n", + " 0.07624\n", " 0\n", " \n", " \n", " 2\n", " 0\n", " 2\n", - " 0.405525\n", - " 0.508854\n", + " 0.475101\n", + " 0.553274\n", " 0\n", " 0.980067\n", - " 0.179854\n", + " 0.07624\n", " 0\n", " \n", " \n", " 3\n", " 0\n", " 3\n", - " 0.508854\n", - " 0.752422\n", + " 0.553274\n", + " 0.593460\n", " 0\n", " 0.955336\n", - " 0.179854\n", + " 0.07624\n", " 0\n", " \n", " \n", " 4\n", " 0\n", " 4\n", - " 0.752422\n", - " 0.781796\n", + " 0.593460\n", + " 0.999893\n", " 0\n", " 0.921061\n", - " 0.179854\n", + " 0.07624\n", " 0\n", " \n", " \n", @@ -273,18 +273,18 @@ ], "text/plain": [ " series_id time_idx x y category future_known_feature \\\n", - "0 0 0 -0.074839 0.201994 0 1.000000 \n", - "1 0 1 0.201994 0.405525 0 0.995004 \n", - "2 0 2 0.405525 0.508854 0 0.980067 \n", - "3 0 3 0.508854 0.752422 0 0.955336 \n", - "4 0 4 0.752422 0.781796 0 0.921061 \n", + "0 0 0 -0.053474 0.079365 0 1.000000 \n", + "1 0 1 0.079365 0.475101 0 0.995004 \n", + "2 0 2 0.475101 0.553274 0 0.980067 \n", + "3 0 3 0.553274 0.593460 0 0.955336 \n", + "4 0 4 0.593460 0.999893 0 0.921061 \n", "\n", " static_feature static_feature_cat \n", - "0 0.179854 0 \n", - "1 0.179854 0 \n", - "2 0.179854 0 \n", - "3 0.179854 0 \n", - "4 0.179854 0 " + "0 0.07624 0 \n", + "1 0.07624 0 \n", + "2 0.07624 0 \n", + "3 0.07624 0 \n", + "4 0.07624 0 " ] }, "execution_count": 3, @@ -365,7 +365,7 @@ "name": "stderr", "output_type": "stream", "text": [ - "C:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\pytorch_forecasting\\data\\tslib_data_module.py:273: UserWarning: TslibDataModule is experimental and subject to change. The API is not stable and may change without prior warning.\n", + "C:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\pytorch_forecasting\\data\\tslib_data_module.py:271: UserWarning: TslibDataModule is experimental and subject to change. The API is not stable and may change without prior warning.\n", " warnings.warn(\n" ] } @@ -472,7 +472,7 @@ "source": [ "import torch.nn as nn\n", "\n", - "from pytorch_forecasting.metrics import MAE, SMAPE" + "from pytorch_forecasting.metrics import MAE, SMAPE, QuantileLoss" ] }, { @@ -480,7 +480,9 @@ "id": "dd9451ee", "metadata": {}, "source": [ - "## Initialise the model" + "## Initialise the model\n", + "\n", + "We shall try out two versions of this model, one using `MAE()` and one with `QuantileLoss()`." ] }, { @@ -493,19 +495,20 @@ "name": "stderr", "output_type": "stream", "text": [ - "C:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\pytorch_forecasting\\models\\base\\_base_model_v2.py:60: UserWarning: The Model 'TimeXer' is part of an experimental reworkof the pytorch-forecasting model layer, scheduled for release with v2.0.0. The API is not stable and may change without prior warning. This class is intended for beta testing and as a basic skeleton, but not for stable production use. Feedback and suggestions are very welcome in pytorch-forecasting issue 1736, https://github.com/sktime/pytorch-forecasting/issues/1736\n", + "C:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\pytorch_forecasting\\models\\base\\_base_model_v2.py:58: UserWarning: The Model 'TimeXer' is part of an experimental reworkof the pytorch-forecasting model layer, scheduled for release with v2.0.0. The API is not stable and may change without prior warning. This class is intended for beta testing and as a basic skeleton, but not for stable production use. Feedback and suggestions are very welcome in pytorch-forecasting issue 1736, https://github.com/sktime/pytorch-forecasting/issues/1736\n", " warn(\n", "C:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\pytorch_forecasting\\models\\base\\_tslib_base_model_v2.py:60: UserWarning: The Model 'TimeXer' is part of an experimental implementationof the pytorch-forecasting model layer for Time Series Library, scheduledfor release with v2.0.0. The API is not stableand may change without prior warning. This class is intended for betatesting, not for stable production use.\n", " warn(\n", - "C:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\pytorch_forecasting\\models\\timexer\\_timexer.py:103: UserWarning: Context length (30) is not divisible by patch length. This may lead to unexpected behavior, as sometime steps will not be used in the model.\n", + "C:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\pytorch_forecasting\\models\\timexer\\_timexer.py:133: UserWarning: TimeXer is an experimental model implemented on TslibBaseModelV2. It is an unstable version and maybe subject to unannouced changes.Please use with caution. Feedback on the design and implementation iswelcome. On the issue #1833 - https://github.com/sktime/pytorch-forecasting/issues/1833\n", + " warn.warn(\n", + "C:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\pytorch_forecasting\\models\\timexer\\_timexer.py:180: UserWarning: Context length (30) is not divisible by patch length. This may lead to unexpected behavior, as sometime steps will not be used in the model.\n", " warn.warn(\n" ] } ], "source": [ - "model = TimeXer(\n", + "model1 = TimeXer(\n", " loss=nn.MSELoss(),\n", - " features=\"MS\",\n", " hidden_size=64,\n", " nhead=4,\n", " e_layers=2,\n", @@ -528,6 +531,34 @@ { "cell_type": "code", "execution_count": 10, + "id": "0aa21f48", + "metadata": {}, + "outputs": [], + "source": [ + "model2 = TimeXer(\n", + " loss=QuantileLoss(quantiles=[0.1, 0.5, 0.9]), # quantiles of 0.1, 0.5 and 0.9 used.\n", + " hidden_size=64,\n", + " nhead=4,\n", + " e_layers=2,\n", + " d_ff=256,\n", + " dropout=0.1,\n", + " patch_length=4,\n", + " logging_metrics=[MAE(), SMAPE()],\n", + " optimizer=\"adam\",\n", + " optimizer_params={\"lr\": 1e-3},\n", + " lr_scheduler=\"reduce_lr_on_plateau\",\n", + " lr_scheduler_params={\n", + " \"mode\": \"min\",\n", + " \"factor\": 0.5,\n", + " \"patience\": 5,\n", + " },\n", + " metadata=data_module.metadata,\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 11, "id": "02605f9b", "metadata": {}, "outputs": [ @@ -535,6 +566,10 @@ "name": "stderr", "output_type": "stream", "text": [ + "Using default `ModelCheckpoint`. Consider installing `litmodels` package to enable `LitModelCheckpoint` for automatic upload to the Lightning model registry.\n", + "GPU available: True (cuda), used: True\n", + "TPU available: False, using: 0 TPU cores\n", + "HPU available: False, using: 0 HPUs\n", "Using default `ModelCheckpoint`. Consider installing `litmodels` package to enable `LitModelCheckpoint` for automatic upload to the Lightning model registry.\n", "GPU available: True (cuda), used: True\n", "TPU available: False, using: 0 TPU cores\n", @@ -545,12 +580,20 @@ "source": [ "from lightning.pytorch import Trainer\n", "\n", - "trainer = Trainer(\n", + "trainer1 = Trainer(\n", " max_epochs=5,\n", " accelerator=\"auto\",\n", " devices=1,\n", " enable_progress_bar=True,\n", " enable_model_summary=True,\n", + ")\n", + "\n", + "trainer2 = Trainer(\n", + " max_epochs=4,\n", + " accelerator=\"gpu\",\n", + " devices=1,\n", + " enable_progress_bar=True,\n", + " enable_model_summary=True,\n", ")" ] }, @@ -564,7 +607,7 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 12, "id": "6e9117d2", "metadata": {}, "outputs": [ @@ -594,7 +637,7 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "24d5067729cd4bf6a5643930eeb292dd", + "model_id": "1ecfe067c98048e1bd2b47f4299d7fc9", "version_major": 2, "version_minor": 0 }, @@ -610,10 +653,6 @@ "output_type": "stream", "text": [ "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\lightning\\pytorch\\trainer\\connectors\\data_connector.py:425: The 'val_dataloader' does not have many workers which may be a bottleneck. Consider increasing the value of the `num_workers` argument` to `num_workers=15` in the `DataLoader` to improve performance.\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", - " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", - " return F.mse_loss(input, target, reduction=self.reduction)\n", "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\lightning\\pytorch\\trainer\\connectors\\data_connector.py:425: The 'train_dataloader' does not have many workers which may be a bottleneck. Consider increasing the value of the `num_workers` argument` to `num_workers=15` in the `DataLoader` to improve performance.\n", "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\lightning\\pytorch\\loops\\fit_loop.py:310: The number of training batches (42) is smaller than the logging interval Trainer(log_every_n_steps=50). Set a lower value for log_every_n_steps if you want to see logs for the training epoch.\n" ] @@ -621,7 +660,7 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "e07c7950c550457487b2061bae5e0315", + "model_id": "5e3a2094bf4545428632f0968fb342c7", "version_major": 2, "version_minor": 0 }, @@ -633,99 +672,23 @@ "output_type": "display_data" }, { - "name": "stderr", - "output_type": "stream", - "text": [ - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", - " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", - " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", - " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", - " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", - " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", - " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", - " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", - " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", - " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", - " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", - " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", - " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", - " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", - " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", - " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", - " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", - " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", - " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", - " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", - " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", - " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", - " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", - " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", - " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", - " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", - " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", - " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", - " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", - " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", - " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", - " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", - " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", - " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", - " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", - " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", - " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", - " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", - " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", - " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", - " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([32, 1, 1])) that is different to the input size (torch.Size([32, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", - " return F.mse_loss(input, target, reduction=self.reduction)\n", - "c:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\.venv\\Lib\\site-packages\\torch\\nn\\modules\\loss.py:608: UserWarning: Using a target size (torch.Size([18, 1, 1])) that is different to the input size (torch.Size([18, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", - " return F.mse_loss(input, target, reduction=self.reduction)\n" - ] + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "2b4d1aad5db94de0bd39efd7f8175525", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "Validation: | | 0/? [00:00 dict[str, torch.Tensor]: if prediction.size(2) != self.target_dim: prediction = prediction[:, :, : self.target_dim] - target_indices = range(self.target_dim) - if self.n_quantiles is not None: - if self.target_dim > 1: - # for multiple targets, we return a list of tensors of shape - # [batch, timesteps, quantiles] - prediction = [prediction[..., i, :] for i in target_indices] - else: - # point prediction - if len(target_indices) == 1: - prediction = prediction[..., 0] - else: - prediction = [prediction[..., i] for i in target_indices] - if "target_scale" in x: prediction = self.transform_output(prediction, x["target_scale"]) From 1829de58a9d8c743e7830da251f1f117f0556082 Mon Sep 17 00:00:00 2001 From: Pranav Bhat Date: Mon, 9 Jun 2025 15:53:15 +0530 Subject: [PATCH 107/139] restore v1 version of _timexer --- .../models/timexer/__init__.py | 30 +- .../models/timexer/_timexer.py | 652 ++++++++++-------- .../models/timexer/_timexer_metadata.py | 148 ++++ .../models/timexer/_timexer_v2.py | 402 +++++++++++ .../models/timexer/sub_modules.py | 335 +++++++++ tests/test_models/test_timexer.py | 473 +++++++++++++ 6 files changed, 1755 insertions(+), 285 deletions(-) create mode 100644 pytorch_forecasting/models/timexer/_timexer_metadata.py create mode 100644 pytorch_forecasting/models/timexer/_timexer_v2.py create mode 100644 pytorch_forecasting/models/timexer/sub_modules.py create mode 100644 tests/test_models/test_timexer.py diff --git a/pytorch_forecasting/models/timexer/__init__.py b/pytorch_forecasting/models/timexer/__init__.py index a1df74dce..5c01a3a27 100644 --- a/pytorch_forecasting/models/timexer/__init__.py +++ b/pytorch_forecasting/models/timexer/__init__.py @@ -1,5 +1,31 @@ -"""TimeXer model.""" +""" +TimeXer model for forecasting time series. +""" from pytorch_forecasting.models.timexer._timexer import TimeXer +from pytorch_forecasting.models.timexer._timexer_metadata import TimeXerMetadata +from pytorch_forecasting.models.timexer.sub_modules import ( + AttentionLayer, + DataEmbedding_inverted, + Encoder, + EncoderLayer, + EnEmbedding, + FlattenHead, + FullAttention, + PositionalEmbedding, + TriangularCausalMask, +) -__all__ = ["TimeXer"] +__all__ = [ + "TimeXer", + "TriangularCausalMask", + "FullAttention", + "AttentionLayer", + "DataEmbedding_inverted", + "PositionalEmbedding", + "FlattenHead", + "EnEmbedding", + "Encoder", + "EncoderLayer", + "TimeXerMetadata", +] diff --git a/pytorch_forecasting/models/timexer/_timexer.py b/pytorch_forecasting/models/timexer/_timexer.py index 668b611c2..cc6518764 100644 --- a/pytorch_forecasting/models/timexer/_timexer.py +++ b/pytorch_forecasting/models/timexer/_timexer.py @@ -1,315 +1,342 @@ """ Time Series Transformer with eXogenous variables (TimeXer) ----------------------------------------------------------- +--------------------------------------------------------- """ -################################################################ -# NOTE: This implementation of TimeXer derives from PR #1797. # -# It is experimental and seeks to clarify design decisions. # -# IT IS STRICTLY A PART OF THE v2 design of PTF. It overrides # -# the v1 version introduced in v1 by PR #1797 # -################################################################ - -from typing import Any, Optional, Union +from copy import copy +from typing import Optional, Union import warnings as warn import lightning.pytorch as pl from lightning.pytorch import LightningModule, Trainer import numpy as np -import pandas as pd import torch import torch.nn as nn import torch.nn.functional as F -from torch.optim import Optimizer -from pytorch_forecasting.metrics import MAE, MAPE, MultiHorizonMetric, QuantileLoss +from pytorch_forecasting.data import TimeSeriesDataSet +from pytorch_forecasting.metrics import ( + MAE, + MAPE, + MASE, + RMSE, + SMAPE, + MultiHorizonMetric, + QuantileLoss, +) from pytorch_forecasting.metrics.base_metrics import MultiLoss -from pytorch_forecasting.models.base._tslib_base_model_v2 import TslibBaseModel - - -class TimeXer(TslibBaseModel): - """ - An implementation of TimeXer model for v2 of pytorch-forecasting. - - TimeXer empowers the canonical transformer with the ability to reconcile - endogenous and exogenous information without any architectural modifications - and achieves consistent state-of-the-art performance across twelve real-world - forecasting benchmarks. - - TimeXer employs patch-level and variate-level representations respectively for - endogenous and exogenous variables, with an endogenous global token as a bridge - in-between. With this design, TimeXer can jointly capture intra-endogenous - temporal dependencies and exogenous-to-endogenous correlations. - - Parameters - ---------- - loss: nn.Module - Loss function to use for training. - enc_in: int, optional - Number of input features for the encoder. If not provided, it will be set to - the number of continuous features in the dataset. - hidden_size: int, default=512 - Dimension of the model embeddings and hidden representations of features. - n_heads: int, default=8 - Number of attention heads in the multi-head attention mechanism.\ - e_layers: int, default=2 - Number of encoder layers in the transformer architecture. - d_ff: int, default=2048 - Dimension of the feed-forward network in the transformer architecture. - dropout: float, default=0.1 - Dropout rate for regularization. This is used throughout the model to prevent overfitting. - patch_length: int, default=24 - Length of each non-overlapping patch for endogenous variable tokenization. - factor: int, default=5 - Factor for the attention mechanism, controlling the number of keys and values. - activation: str, default='relu' - Activation function to use in the feed-forward network. Common choices are 'relu', 'gelu', etc. - endogenous_vars: Optional[list[str]], default=None - List of endogenous variable names to be used in the model. If None, all historical values - for the target variable are used. - exogenous_vars: Optional[list[str]], default=None - List of exogenous variable names to be used in the model. If None, all historical values - for continous variables are used. - logging_metrics: Optional[list[nn.Module]], default=None - List of metrics to log during training, validation, and testing. - optimizer: Optional[Union[Optimizer, str]], default='adam' - Optimizer to use for training. Can be a string name or an instance of an optimizer. - optimizer_params: Optional[dict], default=None - Parameters for the optimizer. If None, default parameters for the optimizer will be used. - lr_scheduler: Optional[str], default=None - Learning rate scheduler to use. If None, no scheduler is used. - lr_scheduler_params: Optional[dict], default=None - Parameters for the learning rate scheduler. If None, default parameters for the scheduler will be used. - metadata: Optional[dict], default=None - Metadata for the model from TslibDataModule. This can include information about the dataset, - such as the number of time steps, number of features, etc. It is used to initialize the model - and ensure it is compatible with the data being used. - - References - ---------- - [1] https://arxiv.org/abs/2402.19072 - [2] https://github.com/thuml/TimeXer - - Notes - ----- - [1] This implementation handles only continous variables in the context length. Categorical variables - support will be added in the future. - [2] The `TimeXer` model obtains many of its attributes from the `TslibBaseModel` class, which is a base class - where a lot of the boiler plate code for metadata handling and model initialization is implemented. - """ # noqa: E501 - +from pytorch_forecasting.models.base import BaseModelWithCovariates +from pytorch_forecasting.models.timexer.sub_modules import ( + AttentionLayer, + DataEmbedding_inverted, + Encoder, + EncoderLayer, + EnEmbedding, + FlattenHead, + FullAttention, +) + + +class TimeXer(BaseModelWithCovariates): def __init__( self, - loss: nn.Module, + context_length: int, + prediction_length: int, + task_name: str = "long_term_forecast", + features: str = "MS", enc_in: int = None, - hidden_size: int = 512, - n_heads: int = 8, + hidden_size: int = 256, + n_heads: int = 4, e_layers: int = 2, - d_ff: int = 2048, - dropout: float = 0.1, - patch_length: int = 24, - factor: int = 5, + d_ff: int = 1024, + dropout: float = 0.2, activation: str = "relu", - endogenous_vars: Optional[list[str]] = None, - exogenous_vars: Optional[list[str]] = None, - logging_metrics: Optional[list[nn.Module]] = None, - optimizer: Optional[Union[Optimizer, str]] = "adam", - optimizer_params: Optional[dict] = None, - lr_scheduler: Optional[str] = None, - lr_scheduler_params: Optional[dict] = None, - metadata: Optional[dict] = None, - **kwargs: Any, + patch_length: int = 16, + factor: int = 5, + embed_type: str = "fixed", + freq: str = "h", + output_size: Union[int, list[int]] = 1, + loss: MultiHorizonMetric = None, + learning_rate: float = 1e-3, + static_categoricals: Optional[list[str]] = None, + static_reals: Optional[list[str]] = None, + time_varying_categoricals_encoder: Optional[list[str]] = None, + time_varying_categoricals_decoder: Optional[list[str]] = None, + time_varying_reals_encoder: Optional[list[str]] = None, + time_varying_reals_decoder: Optional[list[str]] = None, + x_reals: Optional[list[str]] = None, + x_categoricals: Optional[list[str]] = None, + embedding_sizes: Optional[dict[str, tuple[int, int]]] = None, + embedding_labels: Optional[list[str]] = None, + embedding_paddings: Optional[list[str]] = None, + categorical_groups: Optional[dict[str, list[str]]] = None, + logging_metrics: nn.ModuleList = None, + **kwargs, ): - super().__init__( - loss=loss, - logging_metrics=logging_metrics, - optimizer=optimizer, - optimizer_params=optimizer_params, - lr_scheduler=lr_scheduler, - lr_scheduler_params=lr_scheduler_params, - metadata=metadata, - ) + """An implementation of the TimeXer model. - warn.warn( - "TimeXer is an experimental model implemented on TslibBaseModelV2. " - "It is an unstable version and maybe subject to unannouced changes." - "Please use with caution. Feedback on the design and implementation is" - "" - "welcome. On the issue #1833 - https://github.com/sktime/pytorch-forecasting/issues/1833", - ) + TimeXer empowers the canonical transformer with the ability to reconcile + endogenous and exogenous information without any architectural modifications + and achieves consistent state-of-the-art performance across twelve real-world + forecasting benchmarks. - self.enc_in = enc_in - self.hidden_size = hidden_size - self.n_heads = n_heads - self.e_layers = e_layers - self.d_ff = d_ff - self.dropout = dropout - self.patch_length = patch_length - self.activation = activation - self.factor = factor - self.endogenous_vars = endogenous_vars - self.exogenous_vars = exogenous_vars - self.save_hyperparameters(ignore=["loss", "logging_metrics", "metadata"]) - - self._init_network() - - def _init_network(self): - """ - Initialize the network for TimeXer's architecture. - """ + TimeXer employs patch-level and variate-level representations respectively for + endogenous and exogenous variables, with an endogenous global token as a bridge + in-between. With this design, TimeXer can jointly capture intra-endogenous + temporal dependencies and exogenous-to-endogenous correlations. - from pytorch_forecasting.layers.attention import ( - AttentionLayer, - FullAttention, - ) - from pytorch_forecasting.layers.embeddings import ( - DataEmbedding_inverted, - EnEmbedding, - ) - from pytorch_forecasting.layers.encoders import Encoder, EncoderLayer - from pytorch_forecasting.layers.output._flatten_head import FlattenHead + TimeXer model for time series forecasting with exogenous variables. - if self.context_length <= self.patch_length: + Args: + + context_length (int): Length of input sequence used for making predictions. + prediction_length (int): Number of future time steps to predict. + task_name (str, optional): Type of forecasting task, either + 'long_term_forecast' or 'short_term_forecast', which corresponds to + forecasting scenarios implied by the task names. + features (str, optional): Type of features used in the model ('MS' for + multivariate forecating with single target, 'M' for multivariate + forecasting with multiple targets and 'S' for univariate forecasting). + enc_in (int, optional): Number of input variables for encoder. + hidden_size (int, optional): Dimension of model embeddings and hidden + representations. + n_heads (int, optional): Number of attention heads in multi-head attention + layers. + e_layers (int, optional): Number of encoder layers with dual attention + mechanism. + d_ff (int, optional): Dimension of feedforward network in transformer layers + dropout (float, optional): Dropout rate applied throughout the network for + regularization. + activation (str, optional): Activation function used in feedforward networks + ('relu' or 'gelu'). + patch_length (int, optional): Length of each non-overlapping patch for + endogenous variable tokenization. + use_norm (bool, optional): Whether to apply normalization to input data. + Do not change, as it a setting controlled by the pytorch-forecasting API + factor: Scaling factor for attention scores. + embed_type: Type of time feature embedding ('timeF' for time-based features) + freq: Frequency of the time series data('h' for hourly,'d' for daily, etc.). + static_categoricals (list[str]): names of static categorical variables + static_reals (list[str]): names of static continuous variables + time_varying_categoricals_encoder (list[str]): names of categorical + variables for encoder + time_varying_categoricals_decoder (list[str]): names of categorical + variables for decoder + time_varying_reals_encoder (list[str]): names of continuous variables for + encoder + time_varying_reals_decoder (list[str]): names of continuous variables for + decoder + x_reals (list[str]): order of continuous variables in tensor passed to + forward function + x_categoricals (list[str]): order of categorical variables in tensor passed + to forward function + embedding_sizes (dict[str, tuple[int, int]]): dictionary mapping categorical + variables to tuple of integers where the first integer denotes the + number of categorical classes and the second the embedding size + embedding_labels (dict[str, list[str]]): dictionary mapping (string) indices + to list of categorical labels + embedding_paddings (list[str]): names of categorical variables for which + label 0 is always mapped to an embedding vector filled with zeros + categorical_groups (dict[str, list[str]]): dictionary of categorical + variables that are grouped together and can also take multiple values + simultaneously (e.g. holiday during octoberfest). They should be + implemented as bag of embeddings. + logging_metrics (nn.ModuleList[LightningMetric]): list of metrics that are + logged during training. Defaults to + nn.ModuleList([SMAPE(), MAE(), RMSE(), MAPE()]). + **kwargs: additional arguments to :py:class:`~BaseModel`. + """ + + if static_categoricals is None: + static_categoricals = [] + if static_reals is None: + static_reals = [] + if time_varying_categoricals_encoder is None: + time_varying_categoricals_encoder = [] + if time_varying_categoricals_decoder is None: + time_varying_categoricals_decoder = [] + if categorical_groups is None: + categorical_groups = {} + if time_varying_reals_encoder is None: + time_varying_reals_encoder = [] + if time_varying_reals_decoder is None: + time_varying_reals_decoder = [] + if embedding_sizes is None: + embedding_sizes = {} + if embedding_paddings is None: + embedding_paddings = [] + if embedding_labels is None: + embedding_labels = {} + if x_reals is None: + x_reals = [] + if x_categoricals is None: + x_categoricals = [] + if logging_metrics is None: + logging_metrics = nn.ModuleList([SMAPE(), MAE(), RMSE(), MAPE()]) + if loss is None: + if features == "M": + loss = MultiLoss([MAE()] * len(self.target_positions)) + else: + loss = MAE() + self.save_hyperparameters(ignore=["loss", "logging_metrics"]) + # loss is a standalone module and is stored separately. + super().__init__(loss=loss, logging_metrics=logging_metrics, **kwargs) + + if self.hparams.context_length < self.hparams.patch_length: raise ValueError( - f"Context length ({self.context_length}) must be greater than patch" - "length. Patches of ({self.patch_length}) will end up being longer than" - "the sequence length." + f"context_length ({context_length}) must be greater than or equal to" + f" patch_length ({patch_length}). Model cannot create patches larger" + " than the sequence length." ) - if self.context_length % self.patch_length != 0: + if self.hparams.context_length % self.hparams.patch_length != 0: warn.warn( - f"Context length ({self.context_length}) is not divisible by" - " patch length. This may lead to unexpected behavior, as some" - "time steps will not be used in the model." + f"In the input sequence, the context_length ({context_length}) is not a" + f" multiple of the patch_length ({patch_length}). This may lead to some" + "patches being ignored during training." ) - self.patch_num = max(1, int(self.context_length // self.patch_length)) - - if self.target_dim > 1 and self.features == "M": - self.n_target_vars = self.target_dim - else: - self.n_target_vars = 1 + self.patch_num = max( + 1, int(self.hparams.context_length // self.hparams.patch_length) + ) + self.n_target_vars = len(self.target_positions) - # currently enc_in is set only to cont_dim since - # the data module doesn't fully support categorical - # variables in the context length and modele expects - # float values. - self.enc_in = self.enc_in or self.cont_dim + self.enc_in = enc_in + if enc_in is None: + self.enc_in = len(self.reals) self.n_quantiles = None - if hasattr(self.loss, "quantiles"): - self.n_quantiles = len(self.loss.quantiles) + if isinstance(loss, QuantileLoss): + self.n_quantiles = len(loss.quantiles) - if self.hidden_size % self.n_heads != 0: + if hidden_size % n_heads != 0: raise ValueError( - f"hidden_size ({self.hidden_size}) must be divisible by n_heads ({self.n_heads}) " # noqa: E501 - f"for multi-head attention mechanism to work properly." + f"hidden_size ({hidden_size}) must be divisible by n_heads ({n_heads}) " + f"for the multi-head attention mechanism to work properly." ) self.en_embedding = EnEmbedding( - self.n_target_vars, self.hidden_size, self.patch_length, self.dropout + self.n_target_vars, + self.hparams.hidden_size, + self.hparams.patch_length, + self.hparams.dropout, ) self.ex_embedding = DataEmbedding_inverted( - self.context_length, self.hidden_size, self.dropout + self.hparams.context_length, + self.hparams.hidden_size, + self.hparams.embed_type, + self.hparams.freq, + self.hparams.dropout, ) - encoder_layers = [] + if e_layers <= 0: + raise ValueError(f"e_layers ({e_layers}) must be positive.") + elif e_layers > 12: + warn.warn( + f"e_layers ({e_layers}) is quite high. This might lead to overfitting " + f"and high computational cost. Consider using 2-6 layers.", + UserWarning, + ) - for _ in range(self.e_layers): - encoder_layers.append( + self.encoder = Encoder( + [ EncoderLayer( AttentionLayer( FullAttention( False, - self.factor, - attention_dropout=self.dropout, + self.hparams.factor, + attention_dropout=self.hparams.dropout, output_attention=False, ), - self.hidden_size, - self.n_heads, + self.hparams.hidden_size, + self.hparams.n_heads, ), AttentionLayer( FullAttention( False, - self.factor, - attention_dropout=self.dropout, + self.hparams.factor, + attention_dropout=self.hparams.dropout, output_attention=False, ), - self.hidden_size, - self.n_heads, + self.hparams.hidden_size, + self.hparams.n_heads, ), - self.hidden_size, - self.d_ff, - dropout=self.dropout, - activation=self.activation, + self.hparams.hidden_size, + self.hparams.d_ff, + dropout=self.hparams.dropout, + activation=self.hparams.activation, ) - ) - - self.encoder = Encoder( - encoder_layers, norm_layer=torch.nn.LayerNorm(self.hidden_size) + for l in range(self.hparams.e_layers) + ], + norm_layer=torch.nn.LayerNorm(self.hparams.hidden_size), ) - - # Initialize output head - self.head_nf = self.hidden_size * (self.patch_num + 1) + self.head_nf = self.hparams.hidden_size * (self.patch_num + 1) self.head = FlattenHead( self.enc_in, self.head_nf, - self.prediction_length, - head_dropout=self.dropout, + self.hparams.prediction_length, + head_dropout=self.hparams.dropout, n_quantiles=self.n_quantiles, ) - # def _get_target_positions(self) -> torch.Tensor: - # """ - # Get the target positions from the dataset. - # Returns: - # torch.Tensor: Target positions. - # """ - # return torch.tensor(self.target_indices, device=self.device) + @classmethod + def from_dataset( + cls, + dataset: TimeSeriesDataSet, + allowed_encoder_known_variable_names: list[str] = None, + **kwargs, + ): + """ + Create model from dataset and set parameters related to covariates. + + Args: + dataset: timeseries dataset + allowed_encoder_known_variable_names: list of known variables that are allowed in encoder, defaults to all + **kwargs: additional arguments such as hyperparameters for model (see ``__init__()``) + + Returns: + TimeXer + """ # noqa: E501 + new_kwargs = copy(kwargs) + new_kwargs.update( + { + "context_length": dataset.max_encoder_length, + "prediction_length": dataset.max_prediction_length, + } + ) + + new_kwargs.update(cls.deduce_default_output_parameters(dataset, kwargs, MAE())) + + return super().from_dataset( + dataset, + allowed_encoder_known_variable_names=allowed_encoder_known_variable_names, + **new_kwargs, + ) def _forecast(self, x: dict[str, torch.Tensor]) -> dict[str, torch.Tensor]: """ - Forward pass of the TimeXer model. + Forecast for univariate or multivariate with single target (MS) case. + Args: - x (dict[str, torch.Tensor]): Input data. - Returns: - dict[str, torch.Tensor]: Model predictions. + x: Dictionary containing entries for encoder_cat, encoder_cont """ - batch_size = x["history_cont"].shape[0] - history_cont = x["history_cont"] - history_time_idx = x.get("history_time_idx", None) - - history_target = x.get( - "history_target", - torch.zeros(batch_size, self.context_length, 0, device=self.device), - ) # noqa: E501 - - if history_time_idx is not None and history_time_idx.dim() == 2: - # change [batch_size, time_steps] to [batch_size, time_steps, features] - history_time_idx = history_time_idx.unsqueeze(-1) - - # explicitly set endogenous and exogenous variables - endogenous_cont = history_target - if self.endogenous_vars: - endogenous_indices = [ - self.cont_names.index(var) for var in self.endogenous_vars - ] - endogenous_cont = history_cont[..., endogenous_indices] - - exogenous_cont = history_cont - if self.exogenous_vars: - exogenous_indices = [ - self.cont_names.index(var) for var in self.exogenous_vars - ] - exogenous_cont = history_cont[..., exogenous_indices] - - en_embed, n_vars = self.en_embedding(endogenous_cont) - ex_embed = self.ex_embedding(exogenous_cont, history_time_idx) + encoder_cont = x["encoder_cont"] + encoder_time_idx = x.get("encoder_time_idx", None) + target_pos = self.target_positions - enc_out = self.encoder(en_embed, ex_embed) + # masking to ignore the target variable + mask = torch.ones(encoder_cont.shape[-1], dtype=torch.bool) + mask[target_pos] = False + exog_data = encoder_cont[..., mask] + en_embed, n_vars = self.en_embedding( + encoder_cont[:, :, target_pos[-1]].unsqueeze(-1).permute(0, 2, 1) + ) + ex_embed = self.ex_embedding(exog_data, encoder_time_idx) + + enc_out = self.encoder(en_embed, ex_embed) enc_out = torch.reshape( enc_out, (-1, n_vars, enc_out.shape[-2], enc_out.shape[-1]) ) @@ -317,7 +344,6 @@ def _forecast(self, x: dict[str, torch.Tensor]) -> dict[str, torch.Tensor]: enc_out = enc_out.permute(0, 1, 3, 2) dec_out = self.head(enc_out) - if self.n_quantiles is not None: dec_out = dec_out.permute(0, 2, 1, 3) else: @@ -327,48 +353,39 @@ def _forecast(self, x: dict[str, torch.Tensor]) -> dict[str, torch.Tensor]: def _forecast_multi(self, x: dict[str, torch.Tensor]) -> dict[str, torch.Tensor]: """ - Forecast for multivariate with multiple time series. + Forecast for multivariate with multiple targets (M) case. Args: - x (dict[str, torch.Tensor]): Input data. + x: Dictionary containing entries for encoder_cat, encoder_cont Returns: - dict[str, torch.Tensor]: Model predictions. + Dictionary with predictions """ - history_cont = x["history_cont"] - history_time_idx = x.get("history_time_idx", None) - history_target = x["history_target"] - - if self.endogenous_vars: - endogenous_indices = [ - self.cont_names.index(var) for var in self.endogenous_vars - ] - endogenous_cont = history_cont[..., endogenous_indices] - else: - endogenous_cont = history_target - - if self.exogenous_vars: - exogenous_indices = [ - self.cont_names.index(var) for var in self.exogenous_vars - ] - exogenous_cont = history_cont[..., exogenous_indices] - else: - exogenous_cont = history_cont + encoder_cont = x["encoder_cont"] + encoder_time_idx = x.get("encoder_time_idx", None) + target_pos = self.target_positions + encoder_target = encoder_cont[..., target_pos] - en_embed, n_vars = self.en_embedding(endogenous_cont) + en_embed, n_vars = self.en_embedding(encoder_target.permute(0, 2, 1)) - ex_embed = self.ex_embedding(exogenous_cont, history_time_idx) + # use masking to ignore the target variable in encoder_cont under ex_embed. + mask = torch.ones( + encoder_cont.shape[-1], dtype=torch.bool, device=encoder_cont.device + ) + mask[target_pos] = False + exog_data = encoder_cont[..., mask] + ex_embed = self.ex_embedding(exog_data, encoder_time_idx) + # batch_size x sequence_length x hidden_size enc_out = self.encoder(en_embed, ex_embed) enc_out = torch.reshape( enc_out, (-1, n_vars, enc_out.shape[-2], enc_out.shape[-1]) - ) + ) # batch_size x n_vars x sequence_length x hidden_size enc_out = enc_out.permute(0, 1, 3, 2) dec_out = self.head(enc_out) - if self.n_quantiles is not None: dec_out = dec_out.permute(0, 2, 1, 3) else: @@ -376,27 +393,96 @@ def _forecast_multi(self, x: dict[str, torch.Tensor]) -> dict[str, torch.Tensor] return dec_out - def forward(self, x: dict[str, torch.Tensor]) -> dict[str, torch.Tensor]: + @property + def decoder_covariate_size(self) -> int: + """Decoder covariates size. + + Returns: + int: size of time-dependent covariates used by the decoder """ - Forward pass of the TimeXer model. - Args: - x (dict[str, torch.Tensor]): Input data. + return len( + set(self.hparams.time_varying_reals_decoder) - set(self.target_names) + ) + sum( + self.embeddings.output_size[name] + for name in self.hparams.time_varying_categoricals_decoder + ) + + @property + def encoder_covariate_size(self) -> int: + """Encoder covariate size. + Returns: - dict[str, torch.Tensor]: Model predictions. + int: size of time-dependent covariates used by the encoder """ - # this is a feature mode, pre-computed using TslibBaseModel. - if self.features == "MS": - out = self._forecast(x) - else: - out = self._forecast_multi(x) + return len( + set(self.hparams.time_varying_reals_encoder) - set(self.target_names) + ) + sum( + self.embeddings.output_size[name] + for name in self.hparams.time_varying_categoricals_encoder + ) - prediction = out[:, : self.prediction_length, :] + @property + def static_size(self) -> int: + """Static covariate size. - # check to see if the output shape is equal to number of targets - if prediction.size(2) != self.target_dim: - prediction = prediction[:, :, : self.target_dim] + Returns: + int: size of static covariates + """ + return len(self.hparams.static_reals) + sum( + self.embeddings.output_size[name] + for name in self.hparams.static_categoricals + ) - if "target_scale" in x: - prediction = self.transform_output(prediction, x["target_scale"]) + def forward(self, x: dict[str, torch.Tensor]) -> dict[str, torch.Tensor]: + """ + Forward pass of the model. + + Args: + x: Dictionary containing model inputs - return {"prediction": prediction} + Returns: + Dictionary with model outputs + """ + if ( + self.hparams.task_name == "long_term_forecast" + or self.hparams.task_name == "short_term_forecast" + ): # noqa: E501 + if self.hparams.features == "M": + out = self._forecast_multi(x) + else: + out = self._forecast(x) + prediction = out[:, : self.hparams.prediction_length, :] + + target_positions = self.target_positions + + # note: prediction.size(2) is the number of target variables i.e n_targets + target_indices = range(prediction.size(2)) + + if prediction.size(2) != len(target_positions): + prediction = prediction[:, :, : len(target_positions)] + + # In the case of a single target, the result will be a torch.Tensor + # with shape (batch_size, prediction_length) + # In the case of multiple targets, the result will be a list of "n_targets" + # tensors with shape (batch_size, prediction_length) + # If quantile predictions are used, the result will have an additional + # dimension for quantiles, resulting in a shape of + # (batch_size, prediction_length, n_quantiles) + if self.n_quantiles is not None: + # quantile predictions. + if len(target_indices) == 1: + prediction = prediction[..., 0, :] + else: + prediction = [prediction[..., i, :] for i in target_indices] + else: + # point predictions. + if len(target_indices) == 1: + prediction = prediction[..., 0] + else: + prediction = [prediction[..., i] for i in target_indices] + prediction = self.transform_output( + prediction=prediction, target_scale=x["target_scale"] + ) + return self.to_network_output(prediction=prediction) + else: + return None diff --git a/pytorch_forecasting/models/timexer/_timexer_metadata.py b/pytorch_forecasting/models/timexer/_timexer_metadata.py new file mode 100644 index 000000000..3f9917ccc --- /dev/null +++ b/pytorch_forecasting/models/timexer/_timexer_metadata.py @@ -0,0 +1,148 @@ +"""TimeXer metadata container.""" + +from pytorch_forecasting.models.base._base_object import _BasePtForecaster + + +class TimeXerMetadata(_BasePtForecaster): + """TimeXer metdata container.""" + + _tags = { + "info:name": "TimeXer", + "info:compute": 3, + "authors": ["PranavBhatP"], + "capability:exogenous": True, + "capability:multivariate": True, + "capability:pred_int": True, + "capability:flexible_history_length": True, + "capability:cold_start": False, + } + + @classmethod + def get_model_cls(cls): + """Get model class.""" + from pytorch_forecasting.models import TimeXer + + return TimeXer + + @classmethod + def get_test_train_params(cls): + """ + Return testing parameter settings for the trainer. + + Returns + ------- + params : dict or list of dict, default = {} + Parameters to create testing instances of the class + """ + + from pytorch_forecasting.data.encoders import GroupNormalizer, MultiNormalizer + from pytorch_forecasting.metrics import SMAPE, QuantileLoss + + return [ + { + # Basic test params + "hidden_size": 16, + "patch_length": 1, + "n_heads": 2, + "e_layers": 1, + "d_ff": 32, + "dropout": 0.1, + }, + { + "hidden_size": 32, + "n_heads": 4, + "e_layers": 2, + "d_ff": 64, + "patch_length": 4, + "dropout": 0.2, + "activation": "gelu", + }, + { + "hidden_size": 16, + "n_heads": 2, + "e_layers": 1, + "d_ff": 32, + "patch_length": 2, + "dropout": 0.1, + "loss": QuantileLoss(quantiles=[0.1, 0.5, 0.9]), + }, + { + "hidden_size": 24, + "n_heads": 3, + "e_layers": 1, + "d_ff": 48, + "patch_length": 3, + "dropout": 0.15, + "loss": SMAPE(), + "data_loader_kwargs": dict( + target_normalizer=GroupNormalizer( + groups=["agency", "sku"], transformation="softplus" + ), + ), + }, + { + "hidden_size": 16, + "n_heads": 2, + "e_layers": 1, + "d_ff": 32, + "patch_length": 2, + "dropout": 0.1, + "features": "M", + "data_loader_kwargs": dict( + target=["volume", "price_regular"], + time_varying_unknown_reals=["volume", "price_regular"], + target_normalizer=MultiNormalizer( + [ + GroupNormalizer(groups=["agency", "sku"]), + GroupNormalizer(groups=["agency", "sku"]), + ] + ), + ), + }, + ] + + @classmethod + def _get_test_dataloaders_from(cls, params): + """ + Get dataloaders from parameters. + + Parameters + ---------- + params: dict + Parameters to create dataloaders. + One of the elements in the list returned by ``get_test_train_params``. + + Returns + ------- + dataloaders: Dict[str, DataLoader] + Dict of dataloaders created from the parameters. + Train, validation, and test dataloaders created from the parameters. + """ + loss = params.get("loss", None) + clip_target = params.get("clip_target", False) + data_loader_kwargs = params.get("data_loader_kwargs", {}) + + from pytorch_forecasting.metrics import NegativeBinomialDistributionLoss + from pytorch_forecasting.tests._conftest import make_dataloaders + from pytorch_forecasting.tests._data_scenarios import data_with_covariates + + dwc = data_with_covariates() + + if isinstance(loss, NegativeBinomialDistributionLoss): + dwc = dwc.assign(volume=lambda x: x.volume.round()) + + dwc = dwc.copy() + if clip_target: + dwc["target"] = dwc["volume"].clip(1e-3, 1.0) + else: + dwc["target"] = dwc["volume"] + data_loader_default_kwargs = dict( + target="target", + time_varying_known_reals=["price_actual"], + time_varying_unknown_reals=["target"], + static_categoricals=["agency"], + add_relative_time_idx=True, + ) + data_loader_default_kwargs.update(data_loader_kwargs) + dataloaders_w_covariates = make_dataloaders(dwc, **data_loader_default_kwargs) + return dataloaders_w_covariates diff --git a/pytorch_forecasting/models/timexer/_timexer_v2.py b/pytorch_forecasting/models/timexer/_timexer_v2.py new file mode 100644 index 000000000..3ee82d3e3 --- /dev/null +++ b/pytorch_forecasting/models/timexer/_timexer_v2.py @@ -0,0 +1,402 @@ +""" +Time Series Transformer with eXogenous variables (TimeXer) +---------------------------------------------------------- +""" + +################################################################ +# NOTE: This implementation of TimeXer derives from PR #1797. # +# It is experimental and seeks to clarify design decisions. # +# IT IS STRICTLY A PART OF THE v2 design of PTF. It overrides # +# the v1 version introduced in PTF by PR #1797 # +################################################################ + +from typing import Any, Optional, Union +import warnings as warn + +import lightning.pytorch as pl +from lightning.pytorch import LightningModule, Trainer +import numpy as np +import pandas as pd +import torch +import torch.nn as nn +import torch.nn.functional as F +from torch.optim import Optimizer + +from pytorch_forecasting.metrics import MAE, MAPE, MultiHorizonMetric, QuantileLoss +from pytorch_forecasting.metrics.base_metrics import MultiLoss +from pytorch_forecasting.models.base._tslib_base_model_v2 import TslibBaseModel + + +class TimeXer(TslibBaseModel): + """ + An implementation of TimeXer model for v2 of pytorch-forecasting. + + TimeXer empowers the canonical transformer with the ability to reconcile + endogenous and exogenous information without any architectural modifications + and achieves consistent state-of-the-art performance across twelve real-world + forecasting benchmarks. + + TimeXer employs patch-level and variate-level representations respectively for + endogenous and exogenous variables, with an endogenous global token as a bridge + in-between. With this design, TimeXer can jointly capture intra-endogenous + temporal dependencies and exogenous-to-endogenous correlations. + + Parameters + ---------- + loss: nn.Module + Loss function to use for training. + enc_in: int, optional + Number of input features for the encoder. If not provided, it will be set to + the number of continuous features in the dataset. + hidden_size: int, default=512 + Dimension of the model embeddings and hidden representations of features. + n_heads: int, default=8 + Number of attention heads in the multi-head attention mechanism.\ + e_layers: int, default=2 + Number of encoder layers in the transformer architecture. + d_ff: int, default=2048 + Dimension of the feed-forward network in the transformer architecture. + dropout: float, default=0.1 + Dropout rate for regularization. This is used throughout the model to prevent overfitting. + patch_length: int, default=24 + Length of each non-overlapping patch for endogenous variable tokenization. + factor: int, default=5 + Factor for the attention mechanism, controlling the number of keys and values. + activation: str, default='relu' + Activation function to use in the feed-forward network. Common choices are 'relu', 'gelu', etc. + endogenous_vars: Optional[list[str]], default=None + List of endogenous variable names to be used in the model. If None, all historical values + for the target variable are used. + exogenous_vars: Optional[list[str]], default=None + List of exogenous variable names to be used in the model. If None, all historical values + for continous variables are used. + logging_metrics: Optional[list[nn.Module]], default=None + List of metrics to log during training, validation, and testing. + optimizer: Optional[Union[Optimizer, str]], default='adam' + Optimizer to use for training. Can be a string name or an instance of an optimizer. + optimizer_params: Optional[dict], default=None + Parameters for the optimizer. If None, default parameters for the optimizer will be used. + lr_scheduler: Optional[str], default=None + Learning rate scheduler to use. If None, no scheduler is used. + lr_scheduler_params: Optional[dict], default=None + Parameters for the learning rate scheduler. If None, default parameters for the scheduler will be used. + metadata: Optional[dict], default=None + Metadata for the model from TslibDataModule. This can include information about the dataset, + such as the number of time steps, number of features, etc. It is used to initialize the model + and ensure it is compatible with the data being used. + + References + ---------- + [1] https://arxiv.org/abs/2402.19072 + [2] https://github.com/thuml/TimeXer + + Notes + ----- + [1] This implementation handles only continous variables in the context length. Categorical variables + support will be added in the future. + [2] The `TimeXer` model obtains many of its attributes from the `TslibBaseModel` class, which is a base class + where a lot of the boiler plate code for metadata handling and model initialization is implemented. + """ # noqa: E501 + + def __init__( + self, + loss: nn.Module, + enc_in: int = None, + hidden_size: int = 512, + n_heads: int = 8, + e_layers: int = 2, + d_ff: int = 2048, + dropout: float = 0.1, + patch_length: int = 24, + factor: int = 5, + activation: str = "relu", + endogenous_vars: Optional[list[str]] = None, + exogenous_vars: Optional[list[str]] = None, + logging_metrics: Optional[list[nn.Module]] = None, + optimizer: Optional[Union[Optimizer, str]] = "adam", + optimizer_params: Optional[dict] = None, + lr_scheduler: Optional[str] = None, + lr_scheduler_params: Optional[dict] = None, + metadata: Optional[dict] = None, + **kwargs: Any, + ): + super().__init__( + loss=loss, + logging_metrics=logging_metrics, + optimizer=optimizer, + optimizer_params=optimizer_params, + lr_scheduler=lr_scheduler, + lr_scheduler_params=lr_scheduler_params, + metadata=metadata, + ) + + warn.warn( + "TimeXer is an experimental model implemented on TslibBaseModelV2. " + "It is an unstable version and maybe subject to unannouced changes." + "Please use with caution. Feedback on the design and implementation is" + "" + "welcome. On the issue #1833 - https://github.com/sktime/pytorch-forecasting/issues/1833", + ) + + self.enc_in = enc_in + self.hidden_size = hidden_size + self.n_heads = n_heads + self.e_layers = e_layers + self.d_ff = d_ff + self.dropout = dropout + self.patch_length = patch_length + self.activation = activation + self.factor = factor + self.endogenous_vars = endogenous_vars + self.exogenous_vars = exogenous_vars + self.save_hyperparameters(ignore=["loss", "logging_metrics", "metadata"]) + + self._init_network() + + def _init_network(self): + """ + Initialize the network for TimeXer's architecture. + """ + + from pytorch_forecasting.layers.attention import ( + AttentionLayer, + FullAttention, + ) + from pytorch_forecasting.layers.embeddings import ( + DataEmbedding_inverted, + EnEmbedding, + ) + from pytorch_forecasting.layers.encoders import Encoder, EncoderLayer + from pytorch_forecasting.layers.output._flatten_head import FlattenHead + + if self.context_length <= self.patch_length: + raise ValueError( + f"Context length ({self.context_length}) must be greater than patch" + "length. Patches of ({self.patch_length}) will end up being longer than" + "the sequence length." + ) + + if self.context_length % self.patch_length != 0: + warn.warn( + f"Context length ({self.context_length}) is not divisible by" + " patch length. This may lead to unexpected behavior, as some" + "time steps will not be used in the model." + ) + + self.patch_num = max(1, int(self.context_length // self.patch_length)) + + if self.target_dim > 1 and self.features == "M": + self.n_target_vars = self.target_dim + else: + self.n_target_vars = 1 + + # currently enc_in is set only to cont_dim since + # the data module doesn't fully support categorical + # variables in the context length and modele expects + # float values. + self.enc_in = self.enc_in or self.cont_dim + + self.n_quantiles = None + + if hasattr(self.loss, "quantiles"): + self.n_quantiles = len(self.loss.quantiles) + + if self.hidden_size % self.n_heads != 0: + raise ValueError( + f"hidden_size ({self.hidden_size}) must be divisible by n_heads ({self.n_heads}) " # noqa: E501 + f"for multi-head attention mechanism to work properly." + ) + + self.en_embedding = EnEmbedding( + self.n_target_vars, self.hidden_size, self.patch_length, self.dropout + ) + + self.ex_embedding = DataEmbedding_inverted( + self.context_length, self.hidden_size, self.dropout + ) + + encoder_layers = [] + + for _ in range(self.e_layers): + encoder_layers.append( + EncoderLayer( + AttentionLayer( + FullAttention( + False, + self.factor, + attention_dropout=self.dropout, + output_attention=False, + ), + self.hidden_size, + self.n_heads, + ), + AttentionLayer( + FullAttention( + False, + self.factor, + attention_dropout=self.dropout, + output_attention=False, + ), + self.hidden_size, + self.n_heads, + ), + self.hidden_size, + self.d_ff, + dropout=self.dropout, + activation=self.activation, + ) + ) + + self.encoder = Encoder( + encoder_layers, norm_layer=torch.nn.LayerNorm(self.hidden_size) + ) + + # Initialize output head + self.head_nf = self.hidden_size * (self.patch_num + 1) + self.head = FlattenHead( + self.enc_in, + self.head_nf, + self.prediction_length, + head_dropout=self.dropout, + n_quantiles=self.n_quantiles, + ) + + # def _get_target_positions(self) -> torch.Tensor: + # """ + # Get the target positions from the dataset. + # Returns: + # torch.Tensor: Target positions. + # """ + # return torch.tensor(self.target_indices, device=self.device) + + def _forecast(self, x: dict[str, torch.Tensor]) -> dict[str, torch.Tensor]: + """ + Forward pass of the TimeXer model. + Args: + x (dict[str, torch.Tensor]): Input data. + Returns: + dict[str, torch.Tensor]: Model predictions. + """ + batch_size = x["history_cont"].shape[0] + history_cont = x["history_cont"] + history_time_idx = x.get("history_time_idx", None) + + history_target = x.get( + "history_target", + torch.zeros(batch_size, self.context_length, 0, device=self.device), + ) # noqa: E501 + + if history_time_idx is not None and history_time_idx.dim() == 2: + # change [batch_size, time_steps] to [batch_size, time_steps, features] + history_time_idx = history_time_idx.unsqueeze(-1) + + # explicitly set endogenous and exogenous variables + endogenous_cont = history_target + if self.endogenous_vars: + endogenous_indices = [ + self.cont_names.index(var) for var in self.endogenous_vars + ] + endogenous_cont = history_cont[..., endogenous_indices] + + exogenous_cont = history_cont + if self.exogenous_vars: + exogenous_indices = [ + self.cont_names.index(var) for var in self.exogenous_vars + ] + exogenous_cont = history_cont[..., exogenous_indices] + + en_embed, n_vars = self.en_embedding(endogenous_cont) + ex_embed = self.ex_embedding(exogenous_cont, history_time_idx) + + enc_out = self.encoder(en_embed, ex_embed) + + enc_out = torch.reshape( + enc_out, (-1, n_vars, enc_out.shape[-2], enc_out.shape[-1]) + ) + + enc_out = enc_out.permute(0, 1, 3, 2) + + dec_out = self.head(enc_out) + + if self.n_quantiles is not None: + dec_out = dec_out.permute(0, 2, 1, 3) + else: + dec_out = dec_out.permute(0, 2, 1) + + return dec_out + + def _forecast_multi(self, x: dict[str, torch.Tensor]) -> dict[str, torch.Tensor]: + """ + Forecast for multivariate with multiple time series. + + Args: + x (dict[str, torch.Tensor]): Input data. + Returns: + dict[str, torch.Tensor]: Model predictions. + """ + + history_cont = x["history_cont"] + history_time_idx = x.get("history_time_idx", None) + history_target = x["history_target"] + + if self.endogenous_vars: + endogenous_indices = [ + self.cont_names.index(var) for var in self.endogenous_vars + ] + endogenous_cont = history_cont[..., endogenous_indices] + else: + endogenous_cont = history_target + + if self.exogenous_vars: + exogenous_indices = [ + self.cont_names.index(var) for var in self.exogenous_vars + ] + exogenous_cont = history_cont[..., exogenous_indices] + else: + exogenous_cont = history_cont + + en_embed, n_vars = self.en_embedding(endogenous_cont) + + ex_embed = self.ex_embedding(exogenous_cont, history_time_idx) + + enc_out = self.encoder(en_embed, ex_embed) + + enc_out = torch.reshape( + enc_out, (-1, n_vars, enc_out.shape[-2], enc_out.shape[-1]) + ) + + enc_out = enc_out.permute(0, 1, 3, 2) + + dec_out = self.head(enc_out) + + if self.n_quantiles is not None: + dec_out = dec_out.permute(0, 2, 1, 3) + else: + dec_out = dec_out.permute(0, 2, 1) + + return dec_out + + def forward(self, x: dict[str, torch.Tensor]) -> dict[str, torch.Tensor]: + """ + Forward pass of the TimeXer model. + Args: + x (dict[str, torch.Tensor]): Input data. + Returns: + dict[str, torch.Tensor]: Model predictions. + """ + # this is a feature mode, pre-computed using TslibBaseModel. + if self.features == "MS": + out = self._forecast(x) + else: + out = self._forecast_multi(x) + + prediction = out[:, : self.prediction_length, :] + + # check to see if the output shape is equal to number of targets + if prediction.size(2) != self.target_dim: + prediction = prediction[:, :, : self.target_dim] + + if "target_scale" in x: + prediction = self.transform_output(prediction, x["target_scale"]) + + return {"prediction": prediction} diff --git a/pytorch_forecasting/models/timexer/sub_modules.py b/pytorch_forecasting/models/timexer/sub_modules.py new file mode 100644 index 000000000..b0ba3b089 --- /dev/null +++ b/pytorch_forecasting/models/timexer/sub_modules.py @@ -0,0 +1,335 @@ +""" +Implementation of `nn.Modules` for TimeXer model. +""" + +import math +from math import sqrt + +import numpy as np +import torch +import torch.nn as nn +import torch.nn.functional as F + + +class TriangularCausalMask: + """ + Triangular causal mask for attention mechanism. + """ + + def __init__(self, B, L, device="cpu"): + mask_shape = [B, 1, L, L] + with torch.no_grad(): + self._mask = torch.triu( + torch.ones(mask_shape, dtype=torch.bool), diagonal=1 + ).to(device) + + @property + def mask(self): + return self._mask + + +class FullAttention(nn.Module): + """ + Full attention mechanism with optional masking and dropout. + Args: + mask_flag (bool): Whether to apply masking. + factor (int): Factor for scaling the attention scores. + scale (float): Scaling factor for attention scores. + attention_dropout (float): Dropout rate for attention scores. + output_attention (bool): Whether to output attention weights.""" + + def __init__( + self, + mask_flag=True, + factor=5, + scale=None, + attention_dropout=0.1, + output_attention=False, + ): + super().__init__() + self.scale = scale + self.mask_flag = mask_flag + self.output_attention = output_attention + self.dropout = nn.Dropout(attention_dropout) + + def forward(self, queries, keys, values, attn_mask, tau=None, delta=None): + B, L, H, E = queries.shape + _, S, _, D = values.shape + scale = self.scale or 1.0 / sqrt(E) + + scores = torch.einsum("blhe,bshe->bhls", queries, keys) + + if self.mask_flag: + if attn_mask is None: + attn_mask = TriangularCausalMask(B, L, device=queries.device) + scores.masked_fill_(attn_mask.mask, -np.abs) + A = self.dropout(torch.softmax(scale * scores, dim=-1)) + V = torch.einsum("bhls,bshd->blhd", A, values) + + if self.output_attention: + return V.contiguous(), A + else: + return V.contiguous(), None + + +class AttentionLayer(nn.Module): + """ + Attention layer that combines query, key, and value projections with an attention + mechanism. + Args: + attention (nn.Module): Attention mechanism to use. + d_model (int): Dimension of the model. + n_heads (int): Number of attention heads. + d_keys (int, optional): Dimension of the keys. Defaults to d_model // n_heads. + d_values (int, optional): + Dimension of the values. Defaults to d_model // n_heads. + """ + + def __init__(self, attention, d_model, n_heads, d_keys=None, d_values=None): + super().__init__() + + d_keys = d_keys or (d_model // n_heads) + d_values = d_values or (d_model // n_heads) + + self.inner_attention = attention + self.query_projection = nn.Linear(d_model, d_keys * n_heads) + self.key_projection = nn.Linear(d_model, d_keys * n_heads) + self.value_projection = nn.Linear(d_model, d_values * n_heads) + self.out_projection = nn.Linear(d_values * n_heads, d_model) + self.n_heads = n_heads + + def forward(self, queries, keys, values, attn_mask, tau=None, delta=None): + B, L, _ = queries.shape + _, S, _ = keys.shape + H = self.n_heads + + if S == 0: + # skip the cross attention process since there is no exogenous variables + queries = self.query_projection(queries) + return self.out_projection(queries), None + + queries = self.query_projection(queries).view(B, L, H, -1) + keys = self.key_projection(keys).view(B, S, H, -1) + values = self.value_projection(values).view(B, S, H, -1) + + out, attn = self.inner_attention( + queries, keys, values, attn_mask, tau=tau, delta=delta + ) + out = out.view(B, L, -1) + + return self.out_projection(out), attn + + +class DataEmbedding_inverted(nn.Module): + """ + Data embedding module for time series data. + Args: + c_in (int): Number of input features. + d_model (int): Dimension of the model. + embed_type (str): Type of embedding to use. Defaults to "fixed". + freq (str): Frequency of the time series data. Defaults to "h". + dropout (float): Dropout rate. Defaults to 0.1. + """ + + def __init__(self, c_in, d_model, embed_type="fixed", freq="h", dropout=0.1): + super().__init__() + self.value_embedding = nn.Linear(c_in, d_model) + self.dropout = nn.Dropout(p=dropout) + + def forward(self, x, x_mark): + x = x.permute(0, 2, 1) + # x: [Batch Variate Time] + if x_mark is None: + x = self.value_embedding(x) + else: + x = self.value_embedding(torch.cat([x, x_mark.permute(0, 2, 1)], 1)) + # x: [Batch Variate d_model] + return self.dropout(x) + + +class PositionalEmbedding(nn.Module): + """ + Positional embedding module for time series data. + Args: + d_model (int): Dimension of the model. + max_len (int): Maximum length of the input sequence. Defaults to 5000.""" + + def __init__(self, d_model, max_len=5000): + super().__init__() + # Compute the positional encodings once in log space. + pe = torch.zeros(max_len, d_model).float() + pe.require_grad = False + + position = torch.arange(0, max_len).float().unsqueeze(1) + div_term = ( + torch.arange(0, d_model, 2).float() * -(math.log(10000.0) / d_model) + ).exp() + + pe[:, 0::2] = torch.sin(position * div_term) + pe[:, 1::2] = torch.cos(position * div_term) + + pe = pe.unsqueeze(0) + self.register_buffer("pe", pe) + + def forward(self, x): + return self.pe[:, : x.size(1)] + + +class FlattenHead(nn.Module): + """ + Flatten head for the output of the model. + Args: + n_vars (int): Number of input features. + nf (int): Number of features in the last layer. + target_window (int): Target window size. + head_dropout (float): Dropout rate for the head. Defaults to 0. + n_quantiles (int, optional): Number of quantiles. Defaults to None.""" + + def __init__(self, n_vars, nf, target_window, head_dropout=0, n_quantiles=None): + super().__init__() + self.n_vars = n_vars + self.flatten = nn.Flatten(start_dim=-2) + self.linear = nn.Linear(nf, target_window) + self.n_quantiles = n_quantiles + + if self.n_quantiles is not None: + self.linear = nn.Linear(nf, target_window * n_quantiles) + else: + self.linear = nn.Linear(nf, target_window) + self.dropout = nn.Dropout(head_dropout) + + def forward(self, x): + x = self.flatten(x) + x = self.linear(x) + x = self.dropout(x) + + if self.n_quantiles is not None: + batch_size, n_vars = x.shape[0], x.shape[1] + x = x.reshape(batch_size, n_vars, -1, self.n_quantiles) + return x + + +class EnEmbedding(nn.Module): + """ + Encoder embedding module for time series data. Handles endogenous feature + embeddings in this case. + Args: + n_vars (int): Number of input features. + d_model (int): Dimension of the model. + patch_len (int): Length of the patches. + dropout (float): Dropout rate. Defaults to 0.1.""" + + def __init__(self, n_vars, d_model, patch_len, dropout): + super().__init__() + + self.patch_len = patch_len + + self.value_embedding = nn.Linear(patch_len, d_model, bias=False) + self.glb_token = nn.Parameter(torch.randn(1, n_vars, 1, d_model)) + self.position_embedding = PositionalEmbedding(d_model) + + self.dropout = nn.Dropout(dropout) + + def forward(self, x): + n_vars = x.shape[1] + glb = self.glb_token.repeat((x.shape[0], 1, 1, 1)) + + x = x.unfold(dimension=-1, size=self.patch_len, step=self.patch_len) + x = torch.reshape(x, (x.shape[0] * x.shape[1], x.shape[2], x.shape[3])) + # Input encoding + x = self.value_embedding(x) + self.position_embedding(x) + x = torch.reshape(x, (-1, n_vars, x.shape[-2], x.shape[-1])) + x = torch.cat([x, glb], dim=2) + x = torch.reshape(x, (x.shape[0] * x.shape[1], x.shape[2], x.shape[3])) + return self.dropout(x), n_vars + + +class Encoder(nn.Module): + """ + Encoder module for the TimeXer model. + Args: + layers (list): List of encoder layers. + norm_layer (nn.Module, optional): Normalization layer. Defaults to None. + projection (nn.Module, optional): Projection layer. Defaults to None. + """ + + def __init__(self, layers, norm_layer=None, projection=None): + super().__init__() + self.layers = nn.ModuleList(layers) + self.norm = norm_layer + self.projection = projection + + def forward(self, x, cross, x_mask=None, cross_mask=None, tau=None, delta=None): + for layer in self.layers: + x = layer( + x, cross, x_mask=x_mask, cross_mask=cross_mask, tau=tau, delta=delta + ) + + if self.norm is not None: + x = self.norm(x) + + if self.projection is not None: + x = self.projection(x) + return x + + +class EncoderLayer(nn.Module): + """ + Encoder layer for the TimeXer model. + Args: + self_attention (nn.Module): Self-attention mechanism. + cross_attention (nn.Module): Cross-attention mechanism. + d_model (int): Dimension of the model. + d_ff (int, optional): + Dimension of the feedforward layer. Defaults to 4 * d_model. + dropout (float): Dropout rate. Defaults to 0.1. + activation (str): Activation function. Defaults to "relu". + """ + + def __init__( + self, + self_attention, + cross_attention, + d_model, + d_ff=None, + dropout=0.1, + activation="relu", + ): + super().__init__() + d_ff = d_ff or 4 * d_model + self.self_attention = self_attention + self.cross_attention = cross_attention + self.conv1 = nn.Conv1d(in_channels=d_model, out_channels=d_ff, kernel_size=1) + self.conv2 = nn.Conv1d(in_channels=d_ff, out_channels=d_model, kernel_size=1) + self.norm1 = nn.LayerNorm(d_model) + self.norm2 = nn.LayerNorm(d_model) + self.norm3 = nn.LayerNorm(d_model) + self.dropout = nn.Dropout(dropout) + self.activation = F.relu if activation == "relu" else F.gelu + + def forward(self, x, cross, x_mask=None, cross_mask=None, tau=None, delta=None): + B, L, D = cross.shape + x = x + self.dropout( + self.self_attention(x, x, x, attn_mask=x_mask, tau=tau, delta=None)[0] + ) + x = self.norm1(x) + + x_glb_ori = x[:, -1, :].unsqueeze(1) + x_glb = torch.reshape(x_glb_ori, (B, -1, D)) + x_glb_attn = self.dropout( + self.cross_attention( + x_glb, cross, cross, attn_mask=cross_mask, tau=tau, delta=delta + )[0] + ) + x_glb_attn = torch.reshape( + x_glb_attn, (x_glb_attn.shape[0] * x_glb_attn.shape[1], x_glb_attn.shape[2]) + ).unsqueeze(1) + x_glb = x_glb_ori + x_glb_attn + x_glb = self.norm2(x_glb) + + y = x = torch.cat([x[:, :-1, :], x_glb], dim=1) + + y = self.dropout(self.activation(self.conv1(y.transpose(-1, 1)))) + y = self.dropout(self.conv2(y).transpose(-1, 1)) + + return self.norm3(x + y) diff --git a/tests/test_models/test_timexer.py b/tests/test_models/test_timexer.py new file mode 100644 index 000000000..2f5518026 --- /dev/null +++ b/tests/test_models/test_timexer.py @@ -0,0 +1,473 @@ +import shutil +import sys + +import lightning.pytorch as pl +from lightning.pytorch.callbacks import EarlyStopping +from lightning.pytorch.loggers import TensorBoardLogger +import numpy as np +import pandas as pd +import pytest +from test_models.conftest import make_dataloaders +import torch + +from pytorch_forecasting import TimeSeriesDataSet +from pytorch_forecasting.data import NaNLabelEncoder +from pytorch_forecasting.data.encoders import GroupNormalizer, MultiNormalizer +from pytorch_forecasting.metrics import MAE, MAPE, RMSE, SMAPE, MultiLoss, QuantileLoss +from pytorch_forecasting.models import TimeXer + + +def _integration(dataloader, tmp_path, loss=None, trainer_kwargs=None, **kwargs): + """ + Integration test for the TimeXer model. + Args: + dataloader: The dataloader to use for training and validation. + tmp_path: The temporary path to save the model. + loss: The loss function to use. If None, a default loss function is used. + trainer_kwargs: Additional arguments for the trainer. + **kwargs: Additional arguments for the TimeXer model. + """ + + train_dataloader = dataloader["train"] + val_dataloader = dataloader["val"] + test_dataloader = dataloader["test"] + + early_stop_callback = EarlyStopping( + monitor="val_loss", + min_delta=1e-4, + patience=5, + verbose=False, + mode="min", + ) + + logger = TensorBoardLogger(tmp_path) + + if trainer_kwargs is None: + trainer_kwargs = {} + + trainer = pl.Trainer( + max_epochs=2, + gradient_clip_val=0.1, + callbacks=[early_stop_callback], + logger=logger, + enable_checkpointing=True, + limit_train_batches=2, + limit_val_batches=2, + limit_test_batches=2, + **trainer_kwargs, + ) + + kwargs.setdefault("learning_rate", 0.01) + + # n_targets = len(train_dataloader.dataset.target_positions) + + # resolve the loss function if the loss is not provided explicitly + if loss is not None: + pass # do nothing' + elif isinstance(train_dataloader.dataset.target_normalizer, MultiNormalizer): + n_targets = len(train_dataloader.dataset.target_normalizer.normalizers) + loss = MultiLoss([MAE()] * n_targets) + else: + loss = MAE() + + net = TimeXer.from_dataset( + train_dataloader.dataset, + hidden_size=kwargs.get("hidden_size", 16), + n_heads=2, + e_layers=1, + d_ff=32, + patch_length=2, + dropout=0.1, + loss=loss, + **kwargs, + ) + + try: + trainer.fit( + net, + train_dataloaders=train_dataloader, + val_dataloaders=val_dataloader, + ) + + test_outputs = trainer.test(net, dataloaders=test_dataloader) + assert len(test_outputs) > 0 + + # test the checkpointing feature + net = TimeXer.load_from_checkpoint( + trainer.checkpoint_callback.best_model_path, + ) + + predictions = net.predict( + val_dataloader, + return_index=True, + return_x=True, + return_y=True, + fast_dev_run=True, + trainer_kwargs=trainer_kwargs, + ) + + if isinstance(predictions.output, torch.Tensor): + assert predictions.output.ndim == 2, ( + f"shapes of the output should be [batch_size, n_targets], " + f"but got {predictions.output.shape}" + ) + else: + assert all(p.ndim for p in predictions.output), ( + f"shapes of the output should be [batch_size, n_targets], " + f"but got {predictions.output.shape}" + ) + + # raw prediction if debugging the model + + net.predict( + val_dataloader, + return_index=True, + return_x=True, + fast_dev_run=True, + mode="raw", + trainer_kwargs=trainer_kwargs, + ) + + finally: + # remove the temporary directory created for the test + shutil.rmtree(tmp_path, ignore_errors=True) + + +def test_integration(data_with_covariates, tmp_path): + """ + Test simple integration of the TimeXer model with a dataloader. + Args: + tmp_path: The temporary path to save the model. + dataloaders: The dataloaders to use for training and validation. + """ + + dataloaders = make_dataloaders( + data_with_covariates, + target="volume", + time_varying_known_reals=["price_actual"], + time_varying_unknown_reals=["volume"], + static_categoricals=["agency"], + add_relative_time_idx=True, + target_normalizer=GroupNormalizer(groups=["agency", "sku"], center=False), + ) + _integration( + dataloaders, + tmp_path, + trainer_kwargs={"accelerator": "cpu"}, + ) + + +def test_quantile_loss(data_with_covariates, tmp_path): + """ + Test the TimeXer model with quantile loss. + Args: + data_with_covariates: The data to use for training and validation. + tmp_path: The temporary path to save the model. + """ + + dataloaders_with_covariates = make_dataloaders( + data_with_covariates, + target="volume", + time_varying_known_reals=["price_actual"], + time_varying_unknown_reals=["volume"], + static_categoricals=["agency"], + add_relative_time_idx=True, + target_normalizer=GroupNormalizer(groups=["agency", "sku"], center=False), + ) + + _integration( + dataloaders_with_covariates, + tmp_path, + loss=QuantileLoss(quantiles=[0.1, 0.5, 0.9]), + trainer_kwargs=dict(accelerator="cpu"), + ) + + +def test_multiple_targets(data_with_covariates, tmp_path): + """ + Test TimeXer with multiple target variables. + Args: + data_with_covariates: The data to use for training and validation. + tmp_path: The temporary path to save the model. + """ + data = data_with_covariates.copy() + + dataloaders = make_dataloaders( + data, + target=["volume", "industry_volume"], + time_varying_known_reals=["price_actual"], + time_varying_unknown_reals=["volume", "industry_volume"], + static_categoricals=["agency"], + add_relative_time_idx=True, + target_normalizer=MultiNormalizer( + [ + GroupNormalizer(groups=["agency", "sku"]), + GroupNormalizer(groups=["agency", "sku"]), + ] + ), + ) + + _integration( + dataloaders, + tmp_path, + features="M", + trainer_kwargs=dict(accelerator="cpu"), + ) + + +@pytest.fixture +def model(dataloaders_with_covariates): + """Create a TimeXer model for testing.""" + + dataset = dataloaders_with_covariates["train"].dataset + net = TimeXer.from_dataset( + dataset, + learning_rate=0.01, + hidden_size=16, + n_heads=2, + e_layers=1, + d_ff=32, + patch_length=2, + dropout=0.1, + loss=MAE(), + ) + return net + + +def test_model_init(dataloaders_with_covariates): + """Test model intialization from a dataset with different params.""" + dataset = dataloaders_with_covariates["train"].dataset + + context_length = dataset.max_encoder_length + # obtains the patch length from the context length, to ensure that the + # model can handle large patch lengths + patch_length_from_context = min(context_length, 2) + + model1 = TimeXer.from_dataset(dataset, patch_length=patch_length_from_context) + assert isinstance(model1, TimeXer) + + model2 = TimeXer.from_dataset( + dataset, + hidden_size=32, + n_heads=4, + e_layers=2, + d_ff=64, + patch_length=2, + dropout=0.2, + ) + # Testing correctness of core params + assert isinstance(model2, TimeXer) + assert model2.hparams.hidden_size == 32 + assert model2.hparams.n_heads == 4 + assert model2.hparams.e_layers == 2 + assert model2.hparams.d_ff == 64 + assert model2.hparams.patch_length == 2 + + +@pytest.mark.parametrize( + "kwargs", + [ + dict(mode="raw"), + dict(return_index=True), + dict(return_x=True), + dict(return_y=True), + ], +) +def test_prediction_with_dataloader(model, dataloaders_with_covariates, kwargs): + """Test prediction with dataloader and various options.""" + val_dataloader = dataloaders_with_covariates["val"] + model.predict(val_dataloader, fast_dev_run=True, **kwargs) + + +def test_prediction_with_dataset(model, dataloaders_with_covariates): + """Test prediction with dataset directly.""" + val_dataloader = dataloaders_with_covariates["val"] + model.predict(val_dataloader.dataset, fast_dev_run=True) + + +def test_prediction_with_dataframe(model, data_with_covariates): + """Test prediction with dataframe directly.""" + model.predict(data_with_covariates, fast_dev_run=True) + + +def check_embedding_shapes(model): + """Test that embedding components are initialized correctly.""" + # Check en_embedding + assert hasattr(model, "en_embedding") + assert model.en_embedding.hidden_size == model.hparams.hidden_size + assert model.en_embedding.patch_len == model.hparams.patch_length + + assert hasattr(model, "ex_embedding") + assert model.ex_embedding.hidden_size == model.hparams.hidden_size + assert model.ex_embedding.embed_type == model.hparams.embed_type + + assert hasattr(model, "encoder") + assert len(model.encoder.encoders) == model.hparams.e_layers + + assert hasattr(model, "head") + assert model.head.n_targets == model.enc_in + assert model.head.pred_len == model.hparams.prediction_length + + +def test_no_exogenous_variables(): + """Test model with no exogenous variables.""" + data = pd.DataFrame( + { + "target": np.ones(1600), + "group_id": np.repeat(np.arange(16), 100), + "time_idx": np.tile(np.arange(100), 16), + } + ) + training_dataset = TimeSeriesDataSet( + data=data, + time_idx="time_idx", + target="target", + group_ids=["group_id"], + max_encoder_length=10, + max_prediction_length=5, + time_varying_unknown_reals=["target"], + time_varying_known_reals=[], + ) + validation_dataset = TimeSeriesDataSet.from_dataset( + training_dataset, data, stop_randomization=True, predict=True + ) + training_data_loader = training_dataset.to_dataloader( + train=True, batch_size=8, num_workers=0 + ) + validation_data_loader = validation_dataset.to_dataloader( + train=False, batch_size=8, num_workers=0 + ) + + forecaster = TimeXer.from_dataset( + training_dataset, + hidden_size=16, + n_heads=2, + e_layers=1, + patch_length=2, + ) + + trainer = pl.Trainer( + max_epochs=2, + limit_train_batches=8, + limit_val_batches=8, + ) + + trainer.fit( + forecaster, + train_dataloaders=training_data_loader, + val_dataloaders=validation_data_loader, + ) + + # Make predictions + predictions = forecaster.predict( + validation_data_loader, + return_x=True, + return_y=True, + ) + + assert isinstance(predictions.output, torch.Tensor) + assert predictions.output.ndim == 2 + + +def test_with_exogenous_variables(tmp_path): + data = pd.DataFrame( + { + "target": np.sin(np.arange(500)) + np.random.normal(0, 0.1, 500), + "exog": np.cos(np.arange(500)), + "group_id": np.repeat(np.arange(5), 100), + "time_idx": np.tile(np.arange(100), 5), + } + ) + + max_encoder_length = 20 + max_prediction_length = 10 + training_cutoff = 80 + + training = TimeSeriesDataSet( + data[lambda x: x.time_idx <= training_cutoff], + time_idx="time_idx", + target="target", + group_ids=["group_id"], + max_encoder_length=max_encoder_length, + max_prediction_length=max_prediction_length, + min_encoder_length=max_encoder_length, + min_prediction_length=max_prediction_length, + time_varying_known_reals=["exog"], + time_varying_unknown_reals=["target"], + target_normalizer=GroupNormalizer(groups=["group_id"]), + ) + + validation = TimeSeriesDataSet.from_dataset( + training, data, min_prediction_idx=training_cutoff + 1, stop_randomization=True + ) + + batch_size = 5 # Exactly matches the number of groups + train_dataloader = training.to_dataloader( + train=True, batch_size=batch_size, num_workers=0, shuffle=False + ) + val_dataloader = validation.to_dataloader( + train=False, batch_size=batch_size, num_workers=0, shuffle=False + ) + + model = TimeXer.from_dataset( + training, + hidden_size=16, + n_heads=2, + e_layers=1, + patch_length=5, + dropout=0.1, + ) + + trainer = pl.Trainer( + max_epochs=2, + accelerator="cpu", + ) + + try: + trainer.fit( + model, + train_dataloaders=train_dataloader, + val_dataloaders=val_dataloader, + ) + + # Test direct model forward pass + batch = next(iter(val_dataloader)) + x, y = batch + + # The purpose of the below code is to ensure that the model + # treats exogenous variables correctly. The approach here uses + # masking to nullify exogenous and to confirm that the output is + # different from the original output (with exogenous variables). + with torch.no_grad(): + normal_output = model(x) + x_no_exog = x.copy() + target_pos = model.target_positions[0] + mask = torch.ones( + x_no_exog["encoder_cont"].shape[-1], + dtype=torch.bool, + device=x_no_exog["encoder_cont"].device, + ) + mask[target_pos] = False + + for i in range(x_no_exog["encoder_cont"].shape[-1]): + if i != target_pos: + x_no_exog["encoder_cont"][:, :, i] = 0.0 + + no_exog_output = model(x_no_exog) + + assert not torch.allclose( + normal_output["prediction"], no_exog_output["prediction"], atol=1e-2 + ) + + # Test predict API + predictions = model.predict( + val_dataloader, + return_x=True, + return_y=True, + ) + + assert isinstance(predictions.output, torch.Tensor) + assert predictions.output.shape[1] == max_prediction_length + + finally: + shutil.rmtree(tmp_path, ignore_errors=True) From 8fc1865fbf21d4f91a5bc047d2a8facb13564906 Mon Sep 17 00:00:00 2001 From: Pranav Bhat Date: Mon, 9 Jun 2025 15:54:20 +0530 Subject: [PATCH 108/139] change import statement for new location of timexer v2 in notebook --- examples/tslib_v2_example.ipynb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/tslib_v2_example.ipynb b/examples/tslib_v2_example.ipynb index f659e3d44..9a9e0356f 100644 --- a/examples/tslib_v2_example.ipynb +++ b/examples/tslib_v2_example.ipynb @@ -45,14 +45,14 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": null, "id": "3a9cd738", "metadata": {}, "outputs": [], "source": [ "from pytorch_forecasting.data.timeseries import TimeSeries\n", "from pytorch_forecasting.data.tslib_data_module import TslibDataModule\n", - "from pytorch_forecasting.models.timexer import TimeXer" + "from pytorch_forecasting.models.timexer._timexer_v2 import TimeXer" ] }, { From ebe8d227f53c2dbdea552cb151fe0b5f495168b7 Mon Sep 17 00:00:00 2001 From: Pranav Bhat Date: Mon, 9 Jun 2025 15:58:11 +0530 Subject: [PATCH 109/139] changed tslib_data_module to private --- .../data/{tslib_data_module.py => _tslib_data_module.py} | 0 tests/test_data/test_tslib_data_module.py | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename pytorch_forecasting/data/{tslib_data_module.py => _tslib_data_module.py} (100%) diff --git a/pytorch_forecasting/data/tslib_data_module.py b/pytorch_forecasting/data/_tslib_data_module.py similarity index 100% rename from pytorch_forecasting/data/tslib_data_module.py rename to pytorch_forecasting/data/_tslib_data_module.py diff --git a/tests/test_data/test_tslib_data_module.py b/tests/test_data/test_tslib_data_module.py index 4dd3e260c..e087181b2 100644 --- a/tests/test_data/test_tslib_data_module.py +++ b/tests/test_data/test_tslib_data_module.py @@ -3,9 +3,9 @@ import pytest import torch +from pytorch_forecasting.data._tslib_data_module import TslibDataModule from pytorch_forecasting.data.examples import get_stallion_data from pytorch_forecasting.data.timeseries import TimeSeries -from pytorch_forecasting.data.tslib_data_module import TslibDataModule @pytest.fixture(scope="session") From 01b2d78f66509b1c5819f94962f678869bec092e Mon Sep 17 00:00:00 2001 From: Pranav Bhat Date: Mon, 9 Jun 2025 16:05:35 +0530 Subject: [PATCH 110/139] change import statement for new location of tslib data module in notebook --- examples/tslib_v2_example.ipynb | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/examples/tslib_v2_example.ipynb b/examples/tslib_v2_example.ipynb index 9a9e0356f..53f5c876f 100644 --- a/examples/tslib_v2_example.ipynb +++ b/examples/tslib_v2_example.ipynb @@ -22,7 +22,7 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": null, "id": "550a3fbf", "metadata": {}, "outputs": [], @@ -36,22 +36,13 @@ "from torch.optim import Optimizer\n", "from torch.utils.data import Dataset\n", "\n", + "from pytorch_forecasting.data._tslib_data_module import TslibDataModule\n", "from pytorch_forecasting.data.encoders import (\n", " EncoderNormalizer,\n", " NaNLabelEncoder,\n", " TorchNormalizer,\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "3a9cd738", - "metadata": {}, - "outputs": [], - "source": [ + ")\n", "from pytorch_forecasting.data.timeseries import TimeSeries\n", - "from pytorch_forecasting.data.tslib_data_module import TslibDataModule\n", "from pytorch_forecasting.models.timexer._timexer_v2 import TimeXer" ] }, From 3b9de6d9f2be70b7f5833e60555d21b369a73030 Mon Sep 17 00:00:00 2001 From: Aryan Saini Date: Tue, 10 Jun 2025 01:17:55 +0530 Subject: [PATCH 111/139] add support for multiple datamodules --- .../tft_v2_metadata.py | 77 +++++++++++++++++++ pytorch_forecasting/tests/_conftest.py | 56 ++------------ pytorch_forecasting/tests/_data_scenarios.py | 56 ++------------ .../tests/test_all_estimators_v2.py | 69 +++-------------- 4 files changed, 98 insertions(+), 160 deletions(-) diff --git a/pytorch_forecasting/models/temporal_fusion_transformer/tft_v2_metadata.py b/pytorch_forecasting/models/temporal_fusion_transformer/tft_v2_metadata.py index 41d2df27b..c1277fcb4 100644 --- a/pytorch_forecasting/models/temporal_fusion_transformer/tft_v2_metadata.py +++ b/pytorch_forecasting/models/temporal_fusion_transformer/tft_v2_metadata.py @@ -22,6 +22,83 @@ def get_model_cls(cls): return TFT + @classmethod + def _get_test_dataloaders_from(cls, trainer_kwargs): + """Create test dataloaders from trainer_kwargs - following v1 pattern.""" + from pytorch_forecasting.data.data_module import ( + EncoderDecoderTimeSeriesDataModule, + ) + from pytorch_forecasting.tests._conftest import make_datasets_v2 + from pytorch_forecasting.tests._data_scenarios import data_with_covariates_v2 + + data_with_covariates = data_with_covariates_v2() + + data_loader_default_kwargs = dict( + target="target", + group_ids=["agency_encoded", "sku_encoded"], + add_relative_time_idx=True, + ) + + data_loader_kwargs = trainer_kwargs.get("data_loader_kwargs", {}) + data_loader_default_kwargs.update(data_loader_kwargs) + + datasets_info = make_datasets_v2( + data_with_covariates, **data_loader_default_kwargs + ) + + training_dataset = datasets_info["training_dataset"] + validation_dataset = datasets_info["validation_dataset"] + training_max_time_idx = datasets_info["training_max_time_idx"] + + max_encoder_length = data_loader_kwargs.get("max_encoder_length", 4) + max_prediction_length = data_loader_kwargs.get("max_prediction_length", 3) + add_relative_time_idx = data_loader_kwargs.get("add_relative_time_idx", True) + batch_size = data_loader_kwargs.get("batch_size", 2) + + train_datamodule = EncoderDecoderTimeSeriesDataModule( + time_series_dataset=training_dataset, + max_encoder_length=max_encoder_length, + max_prediction_length=max_prediction_length, + add_relative_time_idx=add_relative_time_idx, + batch_size=batch_size, + train_val_test_split=(0.8, 0.2, 0.0), + ) + + val_datamodule = EncoderDecoderTimeSeriesDataModule( + time_series_dataset=validation_dataset, + max_encoder_length=max_encoder_length, + max_prediction_length=max_prediction_length, + min_prediction_idx=training_max_time_idx, + add_relative_time_idx=add_relative_time_idx, + batch_size=batch_size, + train_val_test_split=(0.0, 1.0, 0.0), + ) + + test_datamodule = EncoderDecoderTimeSeriesDataModule( + time_series_dataset=validation_dataset, + max_encoder_length=max_encoder_length, + max_prediction_length=max_prediction_length, + min_prediction_idx=training_max_time_idx, + add_relative_time_idx=add_relative_time_idx, + batch_size=1, + train_val_test_split=(0.0, 0.0, 1.0), + ) + + train_datamodule.setup("fit") + val_datamodule.setup("fit") + test_datamodule.setup("test") + + train_dataloader = train_datamodule.train_dataloader() + val_dataloader = val_datamodule.val_dataloader() + test_dataloader = test_datamodule.test_dataloader() + + return { + "train": train_dataloader, + "val": val_dataloader, + "test": test_dataloader, + "data_module": train_datamodule, + } + @classmethod def get_test_train_params(cls): """Return testing parameter settings for the trainer. diff --git a/pytorch_forecasting/tests/_conftest.py b/pytorch_forecasting/tests/_conftest.py index a3b2bba5d..9f2806cfb 100644 --- a/pytorch_forecasting/tests/_conftest.py +++ b/pytorch_forecasting/tests/_conftest.py @@ -7,7 +7,6 @@ from pytorch_forecasting import TimeSeriesDataSet from pytorch_forecasting.data import EncoderNormalizer, GroupNormalizer, NaNLabelEncoder -from pytorch_forecasting.data.data_module import EncoderDecoderTimeSeriesDataModule from pytorch_forecasting.data.examples import generate_ar_data, get_stallion_data from pytorch_forecasting.data.timeseries import TimeSeries @@ -175,16 +174,12 @@ def data_with_covariates_v2(): return data -def make_dataloaders_v2(data_with_covariates, **kwargs): - """Create dataloaders with consistent encoder/decoder features.""" +def make_datasets_v2(data_with_covariates, **kwargs): + """Create datasets with consistent encoder/decoder features.""" training_cutoff = "2016-09-01" - max_encoder_length = kwargs.get("max_encoder_length", 4) - max_prediction_length = kwargs.get("max_prediction_length", 3) - target_col = kwargs.get("target", "target") group_cols = kwargs.get("group_ids", ["agency_encoded", "sku_encoded"]) - add_relative_time_idx = kwargs.get("add_relative_time_idx", True) known_features = [ "month", @@ -276,51 +271,10 @@ def make_dataloaders_v2(data_with_covariates, **kwargs): training_max_time_idx = training_data["time_idx"].max() + 1 - train_datamodule = EncoderDecoderTimeSeriesDataModule( - time_series_dataset=training_dataset, - max_encoder_length=max_encoder_length, - max_prediction_length=max_prediction_length, - add_relative_time_idx=add_relative_time_idx, - batch_size=2, - num_workers=0, - train_val_test_split=(0.8, 0.2, 0.0), - ) - - val_datamodule = EncoderDecoderTimeSeriesDataModule( - time_series_dataset=validation_dataset, - max_encoder_length=max_encoder_length, - max_prediction_length=max_prediction_length, - min_prediction_idx=training_max_time_idx, - add_relative_time_idx=add_relative_time_idx, - batch_size=2, - num_workers=0, - train_val_test_split=(0.0, 1.0, 0.0), - ) - - test_datamodule = EncoderDecoderTimeSeriesDataModule( - time_series_dataset=validation_dataset, - max_encoder_length=max_encoder_length, - max_prediction_length=max_prediction_length, - min_prediction_idx=training_max_time_idx, - add_relative_time_idx=add_relative_time_idx, - batch_size=1, - num_workers=0, - train_val_test_split=(0.0, 0.0, 1.0), - ) - - train_datamodule.setup("fit") - val_datamodule.setup("fit") - test_datamodule.setup("test") - - train_dataloader = train_datamodule.train_dataloader() - val_dataloader = val_datamodule.val_dataloader() - test_dataloader = test_datamodule.test_dataloader() - return { - "train": train_dataloader, - "val": val_dataloader, - "test": test_dataloader, - "data_module": train_datamodule, + "training_dataset": training_dataset, + "validation_dataset": validation_dataset, + "training_max_time_idx": training_max_time_idx, } diff --git a/pytorch_forecasting/tests/_data_scenarios.py b/pytorch_forecasting/tests/_data_scenarios.py index d39f6d988..40c6fa9d9 100644 --- a/pytorch_forecasting/tests/_data_scenarios.py +++ b/pytorch_forecasting/tests/_data_scenarios.py @@ -7,7 +7,6 @@ from pytorch_forecasting import TimeSeriesDataSet from pytorch_forecasting.data import EncoderNormalizer, GroupNormalizer, NaNLabelEncoder -from pytorch_forecasting.data.data_module import EncoderDecoderTimeSeriesDataModule from pytorch_forecasting.data.examples import generate_ar_data, get_stallion_data from pytorch_forecasting.data.timeseries import TimeSeries @@ -169,16 +168,12 @@ def data_with_covariates_v2(): return data -def make_dataloaders_v2(data_with_covariates, **kwargs): - """Create dataloaders with consistent encoder/decoder features.""" +def make_datasets_v2(data_with_covariates, **kwargs): + """Create datasets with consistent encoder/decoder features.""" training_cutoff = "2016-09-01" - max_encoder_length = 4 - max_prediction_length = 3 - target_col = kwargs.get("target", "target") group_cols = kwargs.get("group_ids", ["agency_encoded", "sku_encoded"]) - add_relative_time_idx = kwargs.get("add_relative_time_idx", True) known_features = [ "month", @@ -270,51 +265,10 @@ def make_dataloaders_v2(data_with_covariates, **kwargs): training_max_time_idx = training_data["time_idx"].max() + 1 - train_datamodule = EncoderDecoderTimeSeriesDataModule( - time_series_dataset=training_dataset, - max_encoder_length=max_encoder_length, - max_prediction_length=max_prediction_length, - add_relative_time_idx=add_relative_time_idx, - batch_size=2, - num_workers=0, - train_val_test_split=(0.8, 0.2, 0.0), - ) - - val_datamodule = EncoderDecoderTimeSeriesDataModule( - time_series_dataset=validation_dataset, - max_encoder_length=max_encoder_length, - max_prediction_length=max_prediction_length, - min_prediction_idx=training_max_time_idx, - add_relative_time_idx=add_relative_time_idx, - batch_size=2, - num_workers=0, - train_val_test_split=(0.0, 1.0, 0.0), - ) - - test_datamodule = EncoderDecoderTimeSeriesDataModule( - time_series_dataset=validation_dataset, - max_encoder_length=max_encoder_length, - max_prediction_length=max_prediction_length, - min_prediction_idx=training_max_time_idx, - add_relative_time_idx=add_relative_time_idx, - batch_size=1, - num_workers=0, - train_val_test_split=(0.0, 0.0, 1.0), - ) - - train_datamodule.setup("fit") - val_datamodule.setup("fit") - test_datamodule.setup("test") - - train_dataloader = train_datamodule.train_dataloader() - val_dataloader = val_datamodule.val_dataloader() - test_dataloader = test_datamodule.test_dataloader() - return { - "train": train_dataloader, - "val": val_dataloader, - "test": test_dataloader, - "data_module": train_datamodule, + "training_dataset": training_dataset, + "validation_dataset": validation_dataset, + "training_max_time_idx": training_max_time_idx, } diff --git a/pytorch_forecasting/tests/test_all_estimators_v2.py b/pytorch_forecasting/tests/test_all_estimators_v2.py index 7e5720a4b..7cb283454 100644 --- a/pytorch_forecasting/tests/test_all_estimators_v2.py +++ b/pytorch_forecasting/tests/test_all_estimators_v2.py @@ -8,7 +8,6 @@ from lightning.pytorch.loggers import TensorBoardLogger import torch.nn as nn -from pytorch_forecasting.tests._conftest import make_dataloaders_v2 as make_dataloaders from pytorch_forecasting.tests.test_all_estimators import ( BaseFixtureGenerator, PackageConfig, @@ -21,33 +20,16 @@ def _integration( estimator_cls, - data_with_covariates, + dataloaders, tmp_path, data_loader_kwargs={}, clip_target: bool = False, trainer_kwargs=None, **kwargs, ): - data_with_covariates = data_with_covariates.copy() - if clip_target: - data_with_covariates["target"] = data_with_covariates["volume"].clip(1e-3, 1.0) - else: - data_with_covariates["target"] = data_with_covariates["volume"] - - data_loader_default_kwargs = dict( - target="target", - group_ids=["agency_encoded", "sku_encoded"], - add_relative_time_idx=True, - ) - data_loader_default_kwargs.update(data_loader_kwargs) - - dataloaders_with_covariates = make_dataloaders( - data_with_covariates, **data_loader_default_kwargs - ) - - train_dataloader = dataloaders_with_covariates["train"] - val_dataloader = dataloaders_with_covariates["val"] - test_dataloader = dataloaders_with_covariates["test"] + train_dataloader = dataloaders["train"] + val_dataloader = dataloaders["val"] + test_dataloader = dataloaders["test"] early_stop_callback = EarlyStopping( monitor="val_loss", min_delta=1e-4, patience=1, verbose=False, mode="min" @@ -68,40 +50,12 @@ def _integration( logger=logger, **trainer_kwargs, ) - training_data_module = dataloaders_with_covariates["data_module"] + training_data_module = dataloaders.get("data_module") metadata = training_data_module.metadata - assert metadata["encoder_cont"] == 14 # 14 features (8 known + 6 unknown) - assert metadata["encoder_cat"] == 0 - assert metadata["decoder_cont"] == 8 # 8 (only known features) - assert metadata["decoder_cat"] == 0 - assert metadata["static_categorical_features"] == 0 - assert ( - metadata["static_continuous_features"] == 2 - ) # 2 (agency_encoded, sku_encoded) - assert metadata["target"] == 1 - - batch_x, batch_y = next(iter(train_dataloader)) - - assert batch_x["encoder_cont"].shape[2] == metadata["encoder_cont"] - assert batch_x["encoder_cat"].shape[2] == metadata["encoder_cat"] - - assert batch_x["decoder_cont"].shape[2] == metadata["decoder_cont"] - assert batch_x["decoder_cat"].shape[2] == metadata["decoder_cat"] - - if "static_categorical_features" in batch_x: - assert ( - batch_x["static_categorical_features"].shape[2] - == metadata["static_categorical_features"] - ) - - if "static_continuous_features" in batch_x: - assert ( - batch_x["static_continuous_features"].shape[2] - == metadata["static_continuous_features"] - ) - - assert batch_y.shape[2] == metadata["target"] + assert isinstance( + metadata, dict + ), f"Expected metadata to be dict, got {type(metadata)}" net = estimator_cls( metadata=metadata, @@ -137,8 +91,7 @@ def test_integration( trainer_kwargs, tmp_path, ): - from pytorch_forecasting.tests._data_scenarios import data_with_covariates_v2 - - data_with_covariates = data_with_covariates_v2() object_class = object_metadata.get_model_cls() - _integration(object_class, data_with_covariates, tmp_path, **trainer_kwargs) + dataloaders = object_metadata._get_test_dataloaders_from(trainer_kwargs) + + _integration(object_class, dataloaders, tmp_path, **trainer_kwargs) From 032a7b0a202b995cf0d522ba6c3206a05f0726a3 Mon Sep 17 00:00:00 2001 From: Aryan Saini Date: Tue, 10 Jun 2025 01:23:30 +0530 Subject: [PATCH 112/139] typo --- .../models/temporal_fusion_transformer/tft_v2_metadata.py | 2 +- pytorch_forecasting/tests/test_all_estimators_v2.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pytorch_forecasting/models/temporal_fusion_transformer/tft_v2_metadata.py b/pytorch_forecasting/models/temporal_fusion_transformer/tft_v2_metadata.py index c1277fcb4..f17f514f2 100644 --- a/pytorch_forecasting/models/temporal_fusion_transformer/tft_v2_metadata.py +++ b/pytorch_forecasting/models/temporal_fusion_transformer/tft_v2_metadata.py @@ -23,7 +23,7 @@ def get_model_cls(cls): return TFT @classmethod - def _get_test_dataloaders_from(cls, trainer_kwargs): + def _get_test_datamodule_from(cls, trainer_kwargs): """Create test dataloaders from trainer_kwargs - following v1 pattern.""" from pytorch_forecasting.data.data_module import ( EncoderDecoderTimeSeriesDataModule, diff --git a/pytorch_forecasting/tests/test_all_estimators_v2.py b/pytorch_forecasting/tests/test_all_estimators_v2.py index 7cb283454..2bde90505 100644 --- a/pytorch_forecasting/tests/test_all_estimators_v2.py +++ b/pytorch_forecasting/tests/test_all_estimators_v2.py @@ -92,6 +92,6 @@ def test_integration( tmp_path, ): object_class = object_metadata.get_model_cls() - dataloaders = object_metadata._get_test_dataloaders_from(trainer_kwargs) + dataloaders = object_metadata._get_test_datamodule_from(trainer_kwargs) _integration(object_class, dataloaders, tmp_path, **trainer_kwargs) From 1749cd2de955fd149acd47d8326dd529f8cececd Mon Sep 17 00:00:00 2001 From: Pranav Bhat Date: Wed, 11 Jun 2025 16:26:25 +0530 Subject: [PATCH 113/139] fix feature mode handling in tslib data module and add error handling to timexer --- pytorch_forecasting/data/_tslib_data_module.py | 6 ++---- pytorch_forecasting/models/timexer/_timexer_v2.py | 8 +++++++- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/pytorch_forecasting/data/_tslib_data_module.py b/pytorch_forecasting/data/_tslib_data_module.py index 4ab27cc2b..13eb12d1f 100644 --- a/pytorch_forecasting/data/_tslib_data_module.py +++ b/pytorch_forecasting/data/_tslib_data_module.py @@ -437,14 +437,12 @@ def _prepare_metadata(self) -> dict[str, Any]: n_cont = n_features["continuous"] n_cat = n_features["categorical"] - if n_targets == 1 and (n_cont + n_cat) == 1: + if n_targets == 1 and (n_cont + n_cat) == 0: self.features = "S" - elif n_targets == 1 and (n_cont + n_cat) > 1: + elif n_targets == 1 and (n_cont + n_cat) >= 1: self.features = "MS" elif n_targets > 1 and (n_cont + n_cat) > 0: self.features = "M" - else: - self.features = "MS" metadata = { "feature_names": feature_names, diff --git a/pytorch_forecasting/models/timexer/_timexer_v2.py b/pytorch_forecasting/models/timexer/_timexer_v2.py index 3ee82d3e3..f7de461c8 100644 --- a/pytorch_forecasting/models/timexer/_timexer_v2.py +++ b/pytorch_forecasting/models/timexer/_timexer_v2.py @@ -387,8 +387,14 @@ def forward(self, x: dict[str, torch.Tensor]) -> dict[str, torch.Tensor]: # this is a feature mode, pre-computed using TslibBaseModel. if self.features == "MS": out = self._forecast(x) - else: + elif self.features == "M": out = self._forecast_multi(x) + else: + raise ValueError( + f"Unsupported features mode: {self.features}. " + "Supported modes are 'MS' for multivariate single series and 'M' for" + "multivariate multiple series." + ) prediction = out[:, : self.prediction_length, :] From 7f8fca8223425f83f3466eb77818fb9801f41928 Mon Sep 17 00:00:00 2001 From: Pranav Bhat Date: Wed, 11 Jun 2025 18:28:48 +0530 Subject: [PATCH 114/139] fix validation of empty cont and cat indices to allow univariate forecasting --- .../data/_tslib_data_module.py | 25 +++++++++++-------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/pytorch_forecasting/data/_tslib_data_module.py b/pytorch_forecasting/data/_tslib_data_module.py index 13eb12d1f..55aee4c77 100644 --- a/pytorch_forecasting/data/_tslib_data_module.py +++ b/pytorch_forecasting/data/_tslib_data_module.py @@ -311,13 +311,21 @@ def _validate_indices(self): has_continuous = self.continuous_indices and len(self.continuous_indices) > 0 has_categorical = self.categorical_indices and len(self.categorical_indices) > 0 - - if not has_continuous and not has_categorical: + has_targets = len(self.time_series_metadata.get("cols", {}).get("y", [])) > 0 + if not has_targets: raise ValueError( - "No categorical or continous features found in the dataset." - "Cannot proceed with model training. Please ensure that your" - "dataset has at least one column with continous or categorical data." + "No target variables found in the dataset. " + "Cannot proceed with model training." + ) + + if not has_continuous and not has_categorical and has_targets: + warnings.warn( + "No continuous or categorical features found. " + "Proceeding with pure univariate forecasting " + "using target history only.", + UserWarning, ) + return if not has_continuous: warnings.warn( @@ -392,11 +400,6 @@ def _prepare_metadata(self) -> dict[str, Any]: "The time series dataset must have at least one target variable. " "Please provide a dataset with a target variable." ) - if len(all_features) == 0: - raise ValueError( - "The time series dataset must have at least one feature. " - "Please provide a dataset with features." - ) feature_names["all"] = list(all_features) feature_names["static"] = list(static_features) @@ -443,6 +446,8 @@ def _prepare_metadata(self) -> dict[str, Any]: self.features = "MS" elif n_targets > 1 and (n_cont + n_cat) > 0: self.features = "M" + else: + self.features = "M" metadata = { "feature_names": feature_names, From 8e5864cbbae14f5aaf0c88f2837b57b106a02484 Mon Sep 17 00:00:00 2001 From: Pranav Bhat Date: Wed, 11 Jun 2025 19:43:38 +0530 Subject: [PATCH 115/139] add metadata class for timexer but tests not running --- .../models/timexer/_timexer_metadata.py | 4 +- .../models/timexer/_timexer_v2_metadata.py | 160 ++++++++++++++++++ 2 files changed, 162 insertions(+), 2 deletions(-) create mode 100644 pytorch_forecasting/models/timexer/_timexer_v2_metadata.py diff --git a/pytorch_forecasting/models/timexer/_timexer_metadata.py b/pytorch_forecasting/models/timexer/_timexer_metadata.py index 3f9917ccc..b8196b7b7 100644 --- a/pytorch_forecasting/models/timexer/_timexer_metadata.py +++ b/pytorch_forecasting/models/timexer/_timexer_metadata.py @@ -1,9 +1,9 @@ """TimeXer metadata container.""" -from pytorch_forecasting.models.base._base_object import _BasePtForecaster +from pytorch_forecasting.models.base._base_object import _BasePtForecasterV1 -class TimeXerMetadata(_BasePtForecaster): +class TimeXerMetadata(_BasePtForecasterV1): """TimeXer metdata container.""" _tags = { diff --git a/pytorch_forecasting/models/timexer/_timexer_v2_metadata.py b/pytorch_forecasting/models/timexer/_timexer_v2_metadata.py new file mode 100644 index 000000000..495a8f913 --- /dev/null +++ b/pytorch_forecasting/models/timexer/_timexer_v2_metadata.py @@ -0,0 +1,160 @@ +""" +Metadata container for TimeXer v2. +""" + +from pytorch_forecasting.models.base._base_object import _BasePtForecasterV2 + + +class TimeXerMetadata(_BasePtForecasterV2): + """TimeXer metadata container.""" + + _tags = { + "info:name": "TimeXer", + "authors": ["PranavBhatP"], + "capability:exogenous": True, + "capability:multivariate": True, + "capability:pred_int": True, + "capability:flexible_history_length": False, + } + + @classmethod + def get_model_cls(cls): + """Get model class.""" + from pytorch_forecasting.models.timexer._timexer_v2 import TimeXer + + return TimeXer + + @classmethod + def _get_test_datamodule_from(cls, trainer_kwargs): + """Create test dataloaders from trainer_kwargs - following v1 pattern.""" + from pytorch_forecasting.data._tslib_data_module import TslibDataModule + from pytorch_forecasting.tests._conftest import make_datasets_v2 + from pytorch_forecasting.tests._data_scenarios import data_with_covariates_v2 + + data_with_covariates = data_with_covariates_v2() + + data_loader_default_kwargs = dict( + target="target", + group_ids=["agency_encoded", "sku_encoded"], + add_relative_time_idx=True, + ) + + data_loader_kwargs = trainer_kwargs.get("data_loader_kwargs", {}) + data_loader_default_kwargs.update(data_loader_kwargs) + + datasets_info = make_datasets_v2( + data_with_covariates, **data_loader_default_kwargs + ) + + training_dataset = datasets_info["training_dataset"] + validation_dataset = datasets_info["validation_dataset"] + + context_length = data_loader_kwargs.get("context_length", 24) + prediction_length = data_loader_kwargs.get("prediction_length", 4) + batch_size = data_loader_kwargs.get("batch_size", 2) + + train_datamodule = TslibDataModule( + time_series_dataset=training_dataset, + context_length=context_length, + prediction_length=prediction_length, + add_relative_time_idx=data_loader_kwargs.get("add_relative_time_idx", True), + batch_size=batch_size, + train_val_test_split=(0.8, 0.2, 0.0), + ) + + val_datamodule = TslibDataModule( + time_series_dataset=validation_dataset, + context_length=context_length, + prediction_length=prediction_length, + add_relative_time_idx=data_loader_kwargs.get("add_relative_time_idx", True), + batch_size=batch_size, + train_val_test_split=(0.0, 1.0, 0.0), + ) + + test_datamodule = TslibDataModule( + time_series_dataset=validation_dataset, + context_length=context_length, + prediction_length=prediction_length, + add_relative_time_idx=data_loader_kwargs.get("add_relative_time_idx", True), + batch_size=1, + train_val_test_split=(0.0, 0.0, 1.0), + ) + + train_datamodule.setup("fit") + val_datamodule.setup("fit") + test_datamodule.setup("test") + + train_dataloader = train_datamodule.train_dataloader() + val_dataloader = val_datamodule.val_dataloader() + test_dataloader = test_datamodule.test_dataloader() + + return { + "train": train_dataloader, + "val": val_dataloader, + "test": test_dataloader, + "data_module": train_datamodule, + } + + @classmethod + def get_test_train_params(cls): + """Return testing parameter settings for the trainer. + + Returns + ------- + params : dict or list of dict, default = {} + Parameters to create testing instances of the class + Each dict are parameters to construct an "interesting" test instance, i.e., + `MyClass(**params)` or `MyClass(**params[i])` creates a valid test instance. + `create_test_instance` uses the first (or only) dictionary in `params` + """ + return [ + {}, + dict( + hidden_size=64, + n_heads=4, + ), + dict(data_loader_kwargs=dict(context_length=48, prediction_length=12)), + dict( + hidden_size=32, + n_heads=2, + data_loader_kwargs=dict( + context_length=24, + prediction_length=6, + add_relative_time_idx=False, + ), + ), + dict( + hidden_size=128, + patch_length=12, + data_loader_kwargs=dict(context_length=96, prediction_length=24), + ), + dict( + n_heads=2, + e_layers=1, + patch_length=6, + ), + dict( + hidden_size=256, + n_heads=8, + e_layers=3, + d_ff=1024, + patch_length=8, + factor=3, + activation="gelu", + dropout=0.2, + ), + dict( + hidden_size=32, + n_heads=2, + e_layers=1, + d_ff=64, + patch_length=4, + factor=2, + activation="relu", + dropout=0.05, + data_loader_kwargs=dict( + context_length=32, + prediction_length=8, + ), + ), + ] From 327919cac7a01a007aa90eb0b5cec5518637343f Mon Sep 17 00:00:00 2001 From: Pranav Bhat Date: Wed, 11 Jun 2025 22:42:05 +0530 Subject: [PATCH 116/139] implement metadata container for v2 of timexer --- pytorch_forecasting/models/timexer/_timexer_v2.py | 2 +- ...mexer_v2_metadata.py => timexer_v2_metadata.py} | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) rename pytorch_forecasting/models/timexer/{_timexer_v2_metadata.py => timexer_v2_metadata.py} (93%) diff --git a/pytorch_forecasting/models/timexer/_timexer_v2.py b/pytorch_forecasting/models/timexer/_timexer_v2.py index f7de461c8..e811a2e7f 100644 --- a/pytorch_forecasting/models/timexer/_timexer_v2.py +++ b/pytorch_forecasting/models/timexer/_timexer_v2.py @@ -107,7 +107,7 @@ def __init__( e_layers: int = 2, d_ff: int = 2048, dropout: float = 0.1, - patch_length: int = 24, + patch_length: int = 4, factor: int = 5, activation: str = "relu", endogenous_vars: Optional[list[str]] = None, diff --git a/pytorch_forecasting/models/timexer/_timexer_v2_metadata.py b/pytorch_forecasting/models/timexer/timexer_v2_metadata.py similarity index 93% rename from pytorch_forecasting/models/timexer/_timexer_v2_metadata.py rename to pytorch_forecasting/models/timexer/timexer_v2_metadata.py index 495a8f913..5db49911d 100644 --- a/pytorch_forecasting/models/timexer/_timexer_v2_metadata.py +++ b/pytorch_forecasting/models/timexer/timexer_v2_metadata.py @@ -49,7 +49,7 @@ def _get_test_datamodule_from(cls, trainer_kwargs): training_dataset = datasets_info["training_dataset"] validation_dataset = datasets_info["validation_dataset"] - context_length = data_loader_kwargs.get("context_length", 24) + context_length = data_loader_kwargs.get("context_length", 12) prediction_length = data_loader_kwargs.get("prediction_length", 4) batch_size = data_loader_kwargs.get("batch_size", 2) @@ -113,20 +113,20 @@ def get_test_train_params(cls): hidden_size=64, n_heads=4, ), - dict(data_loader_kwargs=dict(context_length=48, prediction_length=12)), + dict(data_loader_kwargs=dict(context_length=12, prediction_length=3)), dict( hidden_size=32, n_heads=2, data_loader_kwargs=dict( - context_length=24, - prediction_length=6, + context_length=12, + prediction_length=3, add_relative_time_idx=False, ), ), dict( hidden_size=128, patch_length=12, - data_loader_kwargs=dict(context_length=96, prediction_length=24), + data_loader_kwargs=dict(context_length=16, prediction_length=4), ), dict( n_heads=2, @@ -153,8 +153,8 @@ def get_test_train_params(cls): activation="relu", dropout=0.05, data_loader_kwargs=dict( - context_length=32, - prediction_length=8, + context_length=16, + prediction_length=4, ), ), ] From 3809ad5fb563362b64438a2855f6b2757a8eec86 Mon Sep 17 00:00:00 2001 From: Pranav Bhat Date: Wed, 11 Jun 2025 22:43:50 +0530 Subject: [PATCH 117/139] fix minor bug by simplifying the window creation and removing redundant code --- .../data/_tslib_data_module.py | 64 ++++++------------- 1 file changed, 19 insertions(+), 45 deletions(-) diff --git a/pytorch_forecasting/data/_tslib_data_module.py b/pytorch_forecasting/data/_tslib_data_module.py index 55aee4c77..a33e1e709 100644 --- a/pytorch_forecasting/data/_tslib_data_module.py +++ b/pytorch_forecasting/data/_tslib_data_module.py @@ -584,45 +584,29 @@ def _create_windows(self, indices: torch.Tensor) -> list[tuple[int, int, int, in if sequence_length < min_seq_length: continue - cutoff_time = sample.get("cutoff_time", None) + effective_min_prediction_idx = self.context_length - max_start = sequence_length - min_seq_length + 1 + max_prediction_idx = sequence_length - self.prediction_length + 1 - stride = self.window_stride - - for start_idx in range(0, max_start, stride): - window_end = start_idx + min_seq_length - 1 # 0-indexed - - if cutoff_time is not None: # skip window if exceed cutoff time. - end_time = sample["t"][window_end] - - if isinstance(end_time, torch.Tensor): - end_time = end_time.item() + if max_prediction_idx <= effective_min_prediction_idx: + continue - # Convert both to pandas Timestamp for consistent comparison - try: - if not isinstance(end_time, pd.Timestamp): - end_time = pd.Timestamp(end_time) - if not isinstance(cutoff_time, pd.Timestamp): - cutoff_time = pd.Timestamp(cutoff_time) + stride = self.window_stride - if end_time > cutoff_time: - continue - except (ValueError, TypeError) as e: - # If conversion fails, skip this window - warnings.warn( - f"Could not convert time values for comparison: {e}" + for start_idx in range( + 0, max_prediction_idx - effective_min_prediction_idx, stride + ): # noqa: E501 + if start_idx + self.context_length + self.prediction_length <= ( + sequence_length + ): + windows.append( + ( + series_idx, + start_idx, + self.context_length, + self.prediction_length, ) - continue - - windows.append( - ( - series_idx, - start_idx, - self.context_length, - self.prediction_length, ) - ) return windows @@ -662,8 +646,8 @@ def setup(self, stage: Optional[str] = None) -> None: self._val_indices = self._indices[1:2] self._test_indices = self._indices[1:2] else: - self._train_size = max(1, int(self.train_val_test_split[0] * total_series)) - self._val_size = max(1, int(self.train_val_test_split[1] * total_series)) + self._train_size = int(self.train_val_test_split[0] * total_series) + self._val_size = int(self.train_val_test_split[1] * total_series) self._train_indices = self._indices[: self._train_size] self._val_indices = self._indices[ @@ -674,16 +658,6 @@ def setup(self, stage: Optional[str] = None) -> None: self._train_size + self._val_size : total_series ] - assert ( - len(self._train_indices) > 0 - ), "Training dataset must contain at least one time series" - assert ( - len(self._val_indices) > 0 - ), "Validation dataset must contain at least one time series" - assert ( - len(self._test_indices) > 0 - ), "Test dataset must contain at least one time series" - if stage == "fit" or stage is None: if not hasattr(self, "_train_dataset") or not hasattr(self, "_val_dataset"): self._train_windows = self._create_windows(self._train_indices) From 212e01d03eac25a1c1700cd78f26f64c8af3b24f Mon Sep 17 00:00:00 2001 From: Pranav Bhat Date: Wed, 11 Jun 2025 23:11:49 +0530 Subject: [PATCH 118/139] improve notebook content --- .../source/tutorials}/tslib_v2_example.ipynb | 406 ++++++++++-------- 1 file changed, 220 insertions(+), 186 deletions(-) rename {examples => docs/source/tutorials}/tslib_v2_example.ipynb (79%) diff --git a/examples/tslib_v2_example.ipynb b/docs/source/tutorials/tslib_v2_example.ipynb similarity index 79% rename from examples/tslib_v2_example.ipynb rename to docs/source/tutorials/tslib_v2_example.ipynb index 53f5c876f..6faff618d 100644 --- a/examples/tslib_v2_example.ipynb +++ b/docs/source/tutorials/tslib_v2_example.ipynb @@ -22,7 +22,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1, "id": "550a3fbf", "metadata": {}, "outputs": [], @@ -51,12 +51,14 @@ "id": "2625ed3d", "metadata": {}, "source": [ - "## Construct a time series dataset" + "## Construct a time series dataset\n", + "\n", + "This step requires us to build a `TimeSeries` object for creating a time series dataset, which identifies the features from a raw time series dataset. As you can see below, we are initialising a sample time series dataset." ] }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 2, "id": "a0058487", "metadata": {}, "outputs": [ @@ -110,61 +112,61 @@ "type": "integer" } ], - "ref": "c9857045-a68d-4ffa-88c5-5a89d998296b", + "ref": "9a040c8c-9b72-4d64-ad12-c5d4702ced35", "rows": [ [ "0", "0", "0", - "-0.053473564791053224", - "0.07936543423104955", + "-0.03319064433379144", + "0.22982012179859285", "0", "1.0", - "0.07624037564165775", + "0.4945926741169627", "0" ], [ "1", "0", "1", - "0.07936543423104955", - "0.47510107923326006", + "0.22982012179859285", + "0.4612874019620733", "0", "0.9950041652780258", - "0.07624037564165775", + "0.4945926741169627", "0" ], [ "2", "0", "2", - "0.47510107923326006", - "0.5532736977674955", + "0.4612874019620733", + "0.5387362265604877", "0", "0.9800665778412416", - "0.07624037564165775", + "0.4945926741169627", "0" ], [ "3", "0", "3", - "0.5532736977674955", - "0.5934601539777484", + "0.5387362265604877", + "0.8368343109148751", "0", "0.955336489125606", - "0.07624037564165775", + "0.4945926741169627", "0" ], [ "4", "0", "4", - "0.5934601539777484", - "0.999893228642273", + "0.8368343109148751", + "0.7705107068068119", "0", "0.9210609940028851", - "0.07624037564165775", + "0.4945926741169627", "0" ] ], @@ -207,55 +209,55 @@ " 0\n", " 0\n", " 0\n", - " -0.053474\n", - " 0.079365\n", + " -0.033191\n", + " 0.229820\n", " 0\n", " 1.000000\n", - " 0.07624\n", + " 0.494593\n", " 0\n", " \n", " \n", " 1\n", " 0\n", " 1\n", - " 0.079365\n", - " 0.475101\n", + " 0.229820\n", + " 0.461287\n", " 0\n", " 0.995004\n", - " 0.07624\n", + " 0.494593\n", " 0\n", " \n", " \n", " 2\n", " 0\n", " 2\n", - " 0.475101\n", - " 0.553274\n", + " 0.461287\n", + " 0.538736\n", " 0\n", " 0.980067\n", - " 0.07624\n", + " 0.494593\n", " 0\n", " \n", " \n", " 3\n", " 0\n", " 3\n", - " 0.553274\n", - " 0.593460\n", + " 0.538736\n", + " 0.836834\n", " 0\n", " 0.955336\n", - " 0.07624\n", + " 0.494593\n", " 0\n", " \n", " \n", " 4\n", " 0\n", " 4\n", - " 0.593460\n", - " 0.999893\n", + " 0.836834\n", + " 0.770511\n", " 0\n", " 0.921061\n", - " 0.07624\n", + " 0.494593\n", " 0\n", " \n", " \n", @@ -264,21 +266,21 @@ ], "text/plain": [ " series_id time_idx x y category future_known_feature \\\n", - "0 0 0 -0.053474 0.079365 0 1.000000 \n", - "1 0 1 0.079365 0.475101 0 0.995004 \n", - "2 0 2 0.475101 0.553274 0 0.980067 \n", - "3 0 3 0.553274 0.593460 0 0.955336 \n", - "4 0 4 0.593460 0.999893 0 0.921061 \n", + "0 0 0 -0.033191 0.229820 0 1.000000 \n", + "1 0 1 0.229820 0.461287 0 0.995004 \n", + "2 0 2 0.461287 0.538736 0 0.980067 \n", + "3 0 3 0.538736 0.836834 0 0.955336 \n", + "4 0 4 0.836834 0.770511 0 0.921061 \n", "\n", " static_feature static_feature_cat \n", - "0 0.07624 0 \n", - "1 0.07624 0 \n", - "2 0.07624 0 \n", - "3 0.07624 0 \n", - "4 0.07624 0 " + "0 0.494593 0 \n", + "1 0.494593 0 \n", + "2 0.494593 0 \n", + "3 0.494593 0 \n", + "4 0.494593 0 " ] }, - "execution_count": 3, + "execution_count": 2, "metadata": {}, "output_type": "execute_result" } @@ -309,9 +311,57 @@ "data_df.head()" ] }, + { + "cell_type": "markdown", + "id": "c7c04ff5", + "metadata": {}, + "source": [ + "## Feature Categories and Definitions\n", + "\n", + "### **`time_idx`**\n", + "- **Definition**: The temporal index column that orders observations chronologically\n", + "- **Example**: Sequential time steps (0, 1, 2, ...) or timestamps\n", + "- **Usage**: Identifies the temporal ordering of data points within each time series\n", + "\n", + "### **`target`** \n", + "- **Definition**: The variable you want to predict/forecast\n", + "- **Example**: Sales volume, stock price, temperature readings\n", + "- **Usage**: The dependent variable that the model learns to forecast\n", + "\n", + "### **`group`**\n", + "- **Definition**: Categorical variables that identify different time series entities\n", + "- **Example**: `series_id`, `store_id`, `product_id`, `customer_id`\n", + "- **Usage**: Distinguishes between multiple time series in the dataset\n", + "\n", + "### **`num`**\n", + "- **Definition**: Numerical/continuous features used as model inputs\n", + "- **Example**: Price, quantity, weather data, economic indicators \n", + "- **Usage**: Continuous variables that provide numerical context for predictions\n", + "\n", + "### **`cat`**\n", + "- **Definition**: Categorical features that represent discrete classes or labels\n", + "- **Example**: Product category, day of week, seasonal indicators, region\n", + "- **Usage**: Discrete variables that provide categorical context for predictions\n", + "\n", + "### **`known`**\n", + "- **Definition**: Future values that are known at prediction time (exogenous variables)\n", + "- **Example**: Holidays, planned promotions, scheduled events, calendar features\n", + "- **Usage**: Information available for both historical and future periods\n", + "\n", + "### **`unknown`**\n", + "- **Definition**: Variables only available during training/historical periods\n", + "- **Example**: Past weather conditions, historical prices, competitor actions\n", + "- **Usage**: Features that help with training but aren't available for future predictions\n", + "\n", + "### **`static`**\n", + "- **Definition**: Time-invariant features that remain constant for each time series\n", + "- **Example**: Store size, product attributes, geographic location, customer demographics\n", + "- **Usage**: Entity-specific characteristics that don't change over time" + ] + }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 20, "id": "89a5adbe", "metadata": {}, "outputs": [ @@ -343,12 +393,15 @@ "id": "f8753a6a", "metadata": {}, "source": [ - "## Initialise the `TslibDataModule` using the dataset" + "## Initialise the `TslibDataModule` using the dataset\n", + "\n", + "This steps initialises a basic data module built specially for `tslib` modules and provides all the metadata required to train and implement the `tslib` of your choice!\n", + "You can refer the implementation for `TslibDataModule` for more information." ] }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 4, "id": "5eae9035", "metadata": {}, "outputs": [ @@ -356,7 +409,7 @@ "name": "stderr", "output_type": "stream", "text": [ - "C:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\pytorch_forecasting\\data\\tslib_data_module.py:271: UserWarning: TslibDataModule is experimental and subject to change. The API is not stable and may change without prior warning.\n", + "C:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\pytorch_forecasting\\data\\_tslib_data_module.py:271: UserWarning: TslibDataModule is experimental and subject to change. The API is not stable and may change without prior warning.\n", " warnings.warn(\n" ] } @@ -383,28 +436,7 @@ }, { "cell_type": "code", - "execution_count": 6, - "id": "76ebffc1", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "dict" - ] - }, - "execution_count": 6, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "type(data_module.metadata)" - ] - }, - { - "cell_type": "code", - "execution_count": 7, + "execution_count": 5, "id": "b1843233", "metadata": {}, "outputs": [ @@ -445,7 +477,7 @@ " 'features': 'MS'}" ] }, - "execution_count": 7, + "execution_count": 5, "metadata": {}, "output_type": "execute_result" } @@ -455,30 +487,32 @@ ] }, { - "cell_type": "code", - "execution_count": 8, - "id": "12036e70", + "cell_type": "markdown", + "id": "dd9451ee", "metadata": {}, - "outputs": [], "source": [ - "import torch.nn as nn\n", + "## Initialise the model\n", "\n", - "from pytorch_forecasting.metrics import MAE, SMAPE, QuantileLoss" + "We shall try out two versions of this model, one using `MAE()` and one with `QuantileLoss()`.\n", + "\n", + "Let us quickly import the required packages for the next steps." ] }, { - "cell_type": "markdown", - "id": "dd9451ee", + "cell_type": "code", + "execution_count": 22, + "id": "f6b568a5", "metadata": {}, + "outputs": [], "source": [ - "## Initialise the model\n", + "import torch.nn as nn\n", "\n", - "We shall try out two versions of this model, one using `MAE()` and one with `QuantileLoss()`." + "from pytorch_forecasting.metrics import MAE, SMAPE, QuantileLoss" ] }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 23, "id": "429b5f15", "metadata": {}, "outputs": [ @@ -490,9 +524,9 @@ " warn(\n", "C:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\pytorch_forecasting\\models\\base\\_tslib_base_model_v2.py:60: UserWarning: The Model 'TimeXer' is part of an experimental implementationof the pytorch-forecasting model layer for Time Series Library, scheduledfor release with v2.0.0. The API is not stableand may change without prior warning. This class is intended for betatesting, not for stable production use.\n", " warn(\n", - "C:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\pytorch_forecasting\\models\\timexer\\_timexer.py:133: UserWarning: TimeXer is an experimental model implemented on TslibBaseModelV2. It is an unstable version and maybe subject to unannouced changes.Please use with caution. Feedback on the design and implementation iswelcome. On the issue #1833 - https://github.com/sktime/pytorch-forecasting/issues/1833\n", + "C:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\pytorch_forecasting\\models\\timexer\\_timexer_v2.py:133: UserWarning: TimeXer is an experimental model implemented on TslibBaseModelV2. It is an unstable version and maybe subject to unannouced changes.Please use with caution. Feedback on the design and implementation iswelcome. On the issue #1833 - https://github.com/sktime/pytorch-forecasting/issues/1833\n", " warn.warn(\n", - "C:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\pytorch_forecasting\\models\\timexer\\_timexer.py:180: UserWarning: Context length (30) is not divisible by patch length. This may lead to unexpected behavior, as sometime steps will not be used in the model.\n", + "C:\\Users\\prana\\Desktop\\code\\pytorch-forecasting\\pytorch_forecasting\\models\\timexer\\_timexer_v2.py:180: UserWarning: Context length (30) is not divisible by patch length. This may lead to unexpected behavior, as sometime steps will not be used in the model.\n", " warn.warn(\n" ] } @@ -521,7 +555,7 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 8, "id": "0aa21f48", "metadata": {}, "outputs": [], @@ -549,7 +583,7 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 24, "id": "02605f9b", "metadata": {}, "outputs": [ @@ -598,7 +632,7 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 10, "id": "6e9117d2", "metadata": {}, "outputs": [ @@ -628,7 +662,7 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "1ecfe067c98048e1bd2b47f4299d7fc9", + "model_id": "ee7a04f4538241e9b735e9a48752f106", "version_major": 2, "version_minor": 0 }, @@ -651,7 +685,7 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "5e3a2094bf4545428632f0968fb342c7", + "model_id": "319963d7730f4c0d8047009dbd9167ca", "version_major": 2, "version_minor": 0 }, @@ -665,7 +699,7 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "2b4d1aad5db94de0bd39efd7f8175525", + "model_id": "bf4329366a31411691efaf82f6ed16a5", "version_major": 2, "version_minor": 0 }, @@ -679,7 +713,7 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "0dd6ba99319d48b0b35692aaa4e95310", + "model_id": "7f264696d9c1404cb2ffa81f4f7a95b1", "version_major": 2, "version_minor": 0 }, @@ -693,7 +727,7 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "b4737a76040249f19e9623664ecf5ac9", + "model_id": "4bc9933c01884586be7e44e7c58a53a8", "version_major": 2, "version_minor": 0 }, @@ -707,7 +741,7 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "e69834733cbf448282737158fcd36e3b", + "model_id": "f3229660e28a41c4b0d884e08d99b565", "version_major": 2, "version_minor": 0 }, @@ -721,7 +755,7 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "c0ab8deef6f043aea43bad3b6e161b07", + "model_id": "2545a9ecf028495297a4d3dcd918d618", "version_major": 2, "version_minor": 0 }, @@ -754,7 +788,7 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 11, "id": "3c67d86f", "metadata": {}, "outputs": [ @@ -783,7 +817,7 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "36c37e2e45fe46e983bf8394a32d9b2e", + "model_id": "0c087d79f3584548ac84a2838066a731", "version_major": 2, "version_minor": 0 }, @@ -806,7 +840,7 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "e174aa2bcce24a27b7a18257de3fdcf5", + "model_id": "05fc57fe977b4cb0b8695857c5bdfb57", "version_major": 2, "version_minor": 0 }, @@ -820,7 +854,7 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "bc646a5affc046cbb17acd5374b30843", + "model_id": "ecaf412ba30f43019c3e154d42a1671d", "version_major": 2, "version_minor": 0 }, @@ -834,7 +868,7 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "c3341a2a66d2488c8f59ad2a5a7a85a7", + "model_id": "6536856597a34b79ae3bb65b2ee6a0d4", "version_major": 2, "version_minor": 0 }, @@ -848,7 +882,7 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "52729a846571428e8f397c9682524b5f", + "model_id": "89590ef6629c46449060c9bbc8747765", "version_major": 2, "version_minor": 0 }, @@ -862,7 +896,7 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "7a338ea94aa9490b8717c783331a2f51", + "model_id": "777c414a2b294d319aabc6500ff61566", "version_major": 2, "version_minor": 0 }, @@ -895,7 +929,7 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 12, "id": "dbf1ace6", "metadata": {}, "outputs": [ @@ -910,7 +944,7 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "62cb4dc5cc1048f68884de161d2e53c3", + "model_id": "31779b6cf5cc4049aaedced8d2a2e956", "version_major": 2, "version_minor": 0 }, @@ -928,9 +962,9 @@ "────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────\n", " Test metric DataLoader 0\n", "────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────\n", - " test_MAE 0.47346481680870056\n", - " test_SMAPE 1.0982568264007568\n", - " test_loss 0.01038370467722416\n", + " test_MAE 0.46785134077072144\n", + " test_SMAPE 1.0638009309768677\n", + " test_loss 0.014495044946670532\n", "────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────\n" ] } @@ -941,7 +975,7 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": 13, "id": "250b128a", "metadata": {}, "outputs": [ @@ -998,7 +1032,7 @@ ")" ] }, - "execution_count": 15, + "execution_count": 13, "metadata": {}, "output_type": "execute_result" } @@ -1009,7 +1043,7 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 14, "id": "f730b49a", "metadata": {}, "outputs": [ @@ -1017,69 +1051,69 @@ "name": "stdout", "output_type": "stream", "text": [ - "Prediction: tensor([[[-0.0154]],\n", + "Prediction: tensor([[[-3.8579e-02]],\n", "\n", - " [[ 0.1871]],\n", + " [[ 1.3515e-01]],\n", "\n", - " [[ 0.3392]],\n", + " [[ 2.7090e-01]],\n", "\n", - " [[ 0.4948]],\n", + " [[ 4.3945e-01]],\n", "\n", - " [[ 0.6630]],\n", + " [[ 5.7105e-01]],\n", "\n", - " [[ 0.7778]],\n", + " [[ 7.0694e-01]],\n", "\n", - " [[ 0.8391]],\n", + " [[ 8.1090e-01]],\n", "\n", - " [[ 0.9001]],\n", + " [[ 8.7570e-01]],\n", "\n", - " [[ 0.9442]],\n", + " [[ 9.0934e-01]],\n", "\n", - " [[ 0.9302]],\n", + " [[ 9.0872e-01]],\n", "\n", - " [[ 0.8536]],\n", + " [[ 8.6581e-01]],\n", "\n", - " [[ 0.7712]],\n", + " [[ 7.9358e-01]],\n", "\n", - " [[ 0.6658]],\n", + " [[ 6.9972e-01]],\n", "\n", - " [[ 0.5068]],\n", + " [[ 5.8747e-01]],\n", "\n", - " [[ 0.3144]],\n", + " [[ 4.4550e-01]],\n", "\n", - " [[ 0.1697]],\n", + " [[ 2.9315e-01]],\n", "\n", - " [[-0.0298]],\n", + " [[ 1.5351e-01]],\n", "\n", - " [[-0.1914]],\n", + " [[-5.8678e-04]],\n", "\n", - " [[-0.3421]],\n", + " [[-1.5129e-01]],\n", "\n", - " [[-0.0522]],\n", + " [[ 1.4533e-02]],\n", "\n", - " [[ 0.1340]],\n", + " [[ 1.7025e-01]],\n", "\n", - " [[ 0.3186]],\n", + " [[ 3.5256e-01]],\n", "\n", - " [[ 0.4486]],\n", + " [[ 5.0771e-01]],\n", "\n", - " [[ 0.5877]],\n", + " [[ 6.4501e-01]],\n", "\n", - " [[ 0.7270]],\n", + " [[ 7.4584e-01]],\n", "\n", - " [[ 0.8507]],\n", + " [[ 8.4855e-01]],\n", "\n", - " [[ 0.9346]],\n", + " [[ 8.7391e-01]],\n", "\n", - " [[ 0.9891]],\n", + " [[ 9.2469e-01]],\n", "\n", - " [[ 0.9665]],\n", + " [[ 8.8924e-01]],\n", "\n", - " [[ 0.9295]],\n", + " [[ 8.6606e-01]],\n", "\n", - " [[ 0.8394]],\n", + " [[ 7.7753e-01]],\n", "\n", - " [[ 0.6876]]])\n" + " [[ 6.8279e-01]]])\n" ] } ], @@ -1094,7 +1128,7 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": 15, "id": "e316c047", "metadata": {}, "outputs": [ @@ -1104,7 +1138,7 @@ "torch.Size([32, 1, 1])" ] }, - "execution_count": 17, + "execution_count": 15, "metadata": {}, "output_type": "execute_result" } @@ -1123,7 +1157,7 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": 16, "id": "22bd191f", "metadata": {}, "outputs": [ @@ -1138,7 +1172,7 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "988bff58f66d4cc6920ad764610c69c4", + "model_id": "69e76b8fe0c841ca8e40f2569373d0f1", "version_major": 2, "version_minor": 0 }, @@ -1156,9 +1190,9 @@ "────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────\n", " Test metric DataLoader 0\n", "────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────\n", - " test_MAE 14.938094139099121\n", - " test_SMAPE 32.958351135253906\n", - " test_loss 7.362493991851807\n", + " test_MAE 14.947474479675293\n", + " test_SMAPE 32.57101821899414\n", + " test_loss 5.774611473083496\n", "────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────\n" ] } @@ -1169,7 +1203,7 @@ }, { "cell_type": "code", - "execution_count": 20, + "execution_count": 17, "id": "a1d857db", "metadata": {}, "outputs": [ @@ -1226,7 +1260,7 @@ ")" ] }, - "execution_count": 20, + "execution_count": 17, "metadata": {}, "output_type": "execute_result" } @@ -1237,7 +1271,7 @@ }, { "cell_type": "code", - "execution_count": 21, + "execution_count": 18, "id": "52e2a36a", "metadata": {}, "outputs": [ @@ -1245,100 +1279,100 @@ "name": "stdout", "output_type": "stream", "text": [ - "Prediction: tensor([[[[-0.0882, -0.0966, 0.2563]]],\n", + "Prediction: tensor([[[[-0.1741, -0.0312, 0.2449]]],\n", "\n", "\n", - " [[[ 0.0720, 0.0759, 0.3883]]],\n", + " [[[-0.0194, 0.1198, 0.3921]]],\n", "\n", "\n", - " [[[ 0.1974, 0.2169, 0.5460]]],\n", + " [[[ 0.1472, 0.2544, 0.5401]]],\n", "\n", "\n", - " [[[ 0.3700, 0.3511, 0.6714]]],\n", + " [[[ 0.3183, 0.4101, 0.6707]]],\n", "\n", "\n", - " [[[ 0.5103, 0.4835, 0.8189]]],\n", + " [[[ 0.4626, 0.5497, 0.8223]]],\n", "\n", "\n", - " [[[ 0.6425, 0.6295, 0.9646]]],\n", + " [[[ 0.5880, 0.6819, 0.9794]]],\n", "\n", "\n", - " [[[ 0.7363, 0.7258, 1.0382]]],\n", + " [[[ 0.7212, 0.7909, 1.0700]]],\n", "\n", "\n", - " [[[ 0.8186, 0.8055, 1.1244]]],\n", + " [[[ 0.8104, 0.8627, 1.1342]]],\n", "\n", "\n", - " [[[ 0.8566, 0.8481, 1.1788]]],\n", + " [[[ 0.8615, 0.9050, 1.1836]]],\n", "\n", "\n", - " [[[ 0.8571, 0.8442, 1.1794]]],\n", + " [[[ 0.8919, 0.9103, 1.1939]]],\n", "\n", "\n", - " [[[ 0.8222, 0.7861, 1.1303]]],\n", + " [[[ 0.8414, 0.8754, 1.1404]]],\n", "\n", "\n", - " [[[ 0.7280, 0.6996, 1.0412]]],\n", + " [[[ 0.7774, 0.8125, 1.0497]]],\n", "\n", "\n", - " [[[ 0.6072, 0.5680, 0.9375]]],\n", + " [[[ 0.6535, 0.7326, 0.9382]]],\n", "\n", "\n", - " [[[ 0.5022, 0.4539, 0.7923]]],\n", + " [[[ 0.5000, 0.6076, 0.7917]]],\n", "\n", "\n", - " [[[ 0.3323, 0.3054, 0.6894]]],\n", + " [[[ 0.3172, 0.4677, 0.6275]]],\n", "\n", "\n", - " [[[ 0.1943, 0.1492, 0.5411]]],\n", + " [[[ 0.1383, 0.3008, 0.4571]]],\n", "\n", "\n", - " [[[ 0.0051, -0.0246, 0.3653]]],\n", + " [[[-0.0549, 0.1177, 0.2809]]],\n", "\n", "\n", - " [[[-0.1639, -0.1800, 0.1996]]],\n", + " [[[-0.2488, -0.0911, 0.0679]]],\n", "\n", "\n", - " [[[-0.3275, -0.3510, 0.0374]]],\n", + " [[[-0.4082, -0.2451, -0.0699]]],\n", "\n", "\n", - " [[[-0.1448, -0.1228, 0.2111]]],\n", + " [[[-0.2056, -0.0571, 0.2309]]],\n", "\n", "\n", - " [[[ 0.0111, 0.0275, 0.3488]]],\n", + " [[[-0.0128, 0.0945, 0.3519]]],\n", "\n", "\n", - " [[[ 0.1809, 0.1731, 0.4954]]],\n", + " [[[ 0.1674, 0.2839, 0.5486]]],\n", "\n", "\n", - " [[[ 0.3314, 0.3016, 0.6251]]],\n", + " [[[ 0.3257, 0.4233, 0.7065]]],\n", "\n", "\n", - " [[[ 0.4878, 0.4772, 0.7736]]],\n", + " [[[ 0.4488, 0.5480, 0.8359]]],\n", "\n", "\n", - " [[[ 0.6037, 0.6159, 0.9321]]],\n", + " [[[ 0.5644, 0.6576, 0.9174]]],\n", "\n", "\n", - " [[[ 0.7124, 0.7119, 1.0446]]],\n", + " [[[ 0.6968, 0.7615, 1.0404]]],\n", "\n", "\n", - " [[[ 0.7932, 0.7615, 1.1003]]],\n", + " [[[ 0.7988, 0.8487, 1.1348]]],\n", "\n", "\n", - " [[[ 0.8380, 0.8052, 1.1551]]],\n", + " [[[ 0.8528, 0.8676, 1.1572]]],\n", "\n", "\n", - " [[[ 0.8486, 0.8086, 1.1488]]],\n", + " [[[ 0.8239, 0.8639, 1.1581]]],\n", "\n", "\n", - " [[[ 0.8270, 0.7949, 1.1258]]],\n", + " [[[ 0.8162, 0.8525, 1.1129]]],\n", "\n", "\n", - " [[[ 0.7602, 0.7362, 1.0832]]],\n", + " [[[ 0.7303, 0.8025, 1.0176]]],\n", "\n", "\n", - " [[[ 0.6516, 0.6263, 0.9960]]]])\n" + " [[[ 0.6131, 0.7197, 0.9090]]]])\n" ] } ], @@ -1353,7 +1387,7 @@ }, { "cell_type": "code", - "execution_count": 22, + "execution_count": 19, "id": "a4e6e4b1", "metadata": {}, "outputs": [ @@ -1363,7 +1397,7 @@ "torch.Size([32, 1, 1, 3])" ] }, - "execution_count": 22, + "execution_count": 19, "metadata": {}, "output_type": "execute_result" } From 12a79a830ebc0681cbc94006f956b32a7a93d440 Mon Sep 17 00:00:00 2001 From: Pranav Bhat Date: Wed, 11 Jun 2025 23:42:50 +0530 Subject: [PATCH 119/139] remove 'gpu' param from trainer creation step --- docs/source/tutorials/tslib_v2_example.ipynb | 4 ++-- tests/test_models/test_timexer_v2.py | 0 2 files changed, 2 insertions(+), 2 deletions(-) create mode 100644 tests/test_models/test_timexer_v2.py diff --git a/docs/source/tutorials/tslib_v2_example.ipynb b/docs/source/tutorials/tslib_v2_example.ipynb index 6faff618d..8af751de9 100644 --- a/docs/source/tutorials/tslib_v2_example.ipynb +++ b/docs/source/tutorials/tslib_v2_example.ipynb @@ -583,7 +583,7 @@ }, { "cell_type": "code", - "execution_count": 24, + "execution_count": null, "id": "02605f9b", "metadata": {}, "outputs": [ @@ -615,7 +615,7 @@ "\n", "trainer2 = Trainer(\n", " max_epochs=4,\n", - " accelerator=\"gpu\",\n", + " accelerator=\"auto\",\n", " devices=1,\n", " enable_progress_bar=True,\n", " enable_model_summary=True,\n", diff --git a/tests/test_models/test_timexer_v2.py b/tests/test_models/test_timexer_v2.py new file mode 100644 index 000000000..e69de29bb From 75220be9a8d4250d44bd898dfab463d3d6255974 Mon Sep 17 00:00:00 2001 From: Pranav Bhat Date: Thu, 12 Jun 2025 19:55:27 +0530 Subject: [PATCH 120/139] add basic fixtures for timexer v2 tests --- tests/test_models/test_timexer_v2.py | 209 +++++++++++++++++++++++++++ 1 file changed, 209 insertions(+) diff --git a/tests/test_models/test_timexer_v2.py b/tests/test_models/test_timexer_v2.py index e69de29bb..ed9b266dc 100644 --- a/tests/test_models/test_timexer_v2.py +++ b/tests/test_models/test_timexer_v2.py @@ -0,0 +1,209 @@ +import numpy as np +import pandas as pd +import pytest +import torch +import torch.nn as nn + +from pytorch_forecasting.data import TimeSeries +from pytorch_forecasting.data._tslib_data_module import TslibDataModule +from pytorch_forecasting.metrics import MAE, SMAPE, QuantileLoss +from pytorch_forecasting.models.timexer._timexer_v2 import TimeXer + + +@pytest.fixture +def sample_multivariate_data(): + """Sample multivariate data for testing.""" + + np.random.seed(42) + + series_len = 30 + num_groups = 3 + data = [] + + for i in range(num_groups): + time_idx = np.arange(series_len, dtype=np.int64) + + trend = 100 + i * 20 + 0.5 * time_idx + seasonal = 10 * np.sin(2 * np.pi * time_idx / 12) + noise = np.random.normal(0, 5, series_len) + + target = trend + seasonal + noise + + temperature = ( + 20 + + 15 * np.sin(2 * np.pi * time_idx / 365) + + np.random.normal(0, 3, series_len) + ) # noqa: E501 + humidity = ( + 30 + + 20 * np.cos(2 * np.pi * time_idx / 7) + + np.random.normal(0, 5, series_len) + ) # noqa: E501 + pressure = ( + 1013 + + 10 * np.sin(2 * np.pi * time_idx / 30) + + np.random.normal(0, 2, series_len) + ) # noqa: E501 + + static_cont_val = np.float32(i * 10.0) + static_cat_code = np.float32(i % 2) + + df_group = pd.DataFrame( + { + "time_idx": time_idx, + "group_id": f"group_{i}", + "value": target.astype(np.float32), + "temperature": temperature.astype(np.float32), + "humidity": humidity.astype(np.float32), + "pressure": pressure.astype(np.float32), + "static_cont_feat": np.full( + series_len, static_cont_val, dtype=np.float32 + ), + "static_cat_feat": np.full( + series_len, static_cat_code, dtype=np.float32 + ), + } + ) + data.append(df_group) + + df = pd.concat(data, ignore_index=True) + df["group_id"] = df["group_id"].astype("category") + + return df + + +@pytest.fixture +def sample_multivariate_multi_series_data(): + """Create sample data for M mode (multiple series) testing.""" + np.random.seed(123) + + series_len = 30 + num_groups = 5 + data = [] + + for i in range(num_groups): + time_idx = np.arange(series_len, dtype=np.int64) + base_level = 50 + i * 15 + trend_slope = 0.2 + i * 0.1 + seasonal_amp = 5 + i * 2 + + # Target variables (multiple targets for M mode) + target1 = ( + base_level + + trend_slope * time_idx + + seasonal_amp * np.sin(2 * np.pi * time_idx / 7) + + np.random.normal(0, 1, series_len) + ) + target2 = ( + base_level * 0.8 + + trend_slope * 0.5 * time_idx + + seasonal_amp * 0.7 * np.cos(2 * np.pi * time_idx / 7) + + np.random.normal(0, 1.5, series_len) + ) # noqa: E501 + + # Exogenous variables + temperature = ( + 18 + + 12 * np.sin(2 * np.pi * time_idx / 365) + + np.random.normal(0, 2, series_len) + ) # noqa: E501 + humidity = ( + 45 + + 25 * np.cos(2 * np.pi * time_idx / 7 + i * np.pi / 4) + + np.random.normal(0, 4, series_len) + ) # noqa: E501 + pressure = ( + 1010 + + 8 * np.sin(2 * np.pi * time_idx / 30) + + np.random.normal(0, 1.5, series_len) + ) # noqa: E501 + wind_speed = ( + 5 + + 3 * np.sin(2 * np.pi * time_idx / 14) + + np.random.normal(0, 1, series_len) + ) # noqa: E501 + + df_group = pd.DataFrame( + { + "time_idx": time_idx, + "group_id": f"series_{i}", + "target1": target1.astype(np.float32), + "target2": target2.astype(np.float32), + "temperature": temperature.astype(np.float32), + "humidity": humidity.astype(np.float32), + "pressure": pressure.astype(np.float32), + "wind_speed": wind_speed.astype(np.float32), + } + ) + data.append(df_group) + + df = pd.concat(data, ignore_index=True) + df["group_id"] = df["group_id"].astype("category") + + return df + + +def basic_timeseries_dataset(sample_multivariate_data): + """Create a basic TimeSeries dataset for testing.""" + return TimeSeries( + data=sample_multivariate_data, + time="time_idx", + target="value", + group=["group_id"], + num=[ + "value", + "temperature", + "humidity", + "pressure", + "static_cont_feat", + "static_cat_feat", + ], + cat=[], + known=["temperature", "humidity", "pressure", "time_idx"], + static=["static_cont_feat", "static_cat_feat"], + ) + + +@pytest.fixture +def basic_tslib_data_module(basic_timeseries_dataset): + """Create a basic TslibDataModule for testing.""" + return TslibDataModule( + time_series_dataset=basic_timeseries_dataset, + batch_size=4, + context_length=12, + prediction_length=8, + train_val_test_split=(0.6, 0.2, 0.2), + ) + + +@pytest.fixture +def basic_metadata(basic_tslib_data_module): + """Basic metadata from data module for model initialization.""" + basic_tslib_data_module.setup() + + # Return the generated metadata + return basic_tslib_data_module.metadata + + +@pytest.fixture +def model(basic_metadata): + """Initialize a TimeXer model for testing.""" + return TimeXer( + loss=MAE(), + hidden_size=64, + n_heads=8, + e_layers=2, + d_ff=256, + droupout=0.1, + patch_length=4, + logging_metrics=[SMAPE()], + optimizer="adam", + optimizer_params={"lr": 1e-3}, + lr_scheduler="reduce_lr_on_plateau", + lr_scheduler_params={ + "mode": "min", + "factor": 0.5, + "patience": 5, + }, + metadata=basic_metadata, + ) From 8b0087eec5b713d4e7b414790f1d6fa8b6d7c135 Mon Sep 17 00:00:00 2001 From: Aryan Saini Date: Fri, 13 Jun 2025 02:38:40 +0530 Subject: [PATCH 121/139] linting --- pytorch_forecasting/tests/_data_scenarios.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pytorch_forecasting/tests/_data_scenarios.py b/pytorch_forecasting/tests/_data_scenarios.py index de20a2eae..c13ff0ae5 100644 --- a/pytorch_forecasting/tests/_data_scenarios.py +++ b/pytorch_forecasting/tests/_data_scenarios.py @@ -263,7 +263,6 @@ def make_datasets_v2(data_with_covariates, **kwargs): } - def dataloaders_with_different_encoder_decoder_length(): return make_dataloaders( data_with_covariates(), From 6e4e692a75b3001e779a494c1eb9b6b0e4562ffa Mon Sep 17 00:00:00 2001 From: Pranav Bhat Date: Fri, 13 Jun 2025 16:06:30 +0530 Subject: [PATCH 122/139] add tests for timexer in v2 --- tests/test_models/test_timexer_v2.py | 197 ++++++++++++++++++++++++++- 1 file changed, 194 insertions(+), 3 deletions(-) diff --git a/tests/test_models/test_timexer_v2.py b/tests/test_models/test_timexer_v2.py index ed9b266dc..3285bd0e7 100644 --- a/tests/test_models/test_timexer_v2.py +++ b/tests/test_models/test_timexer_v2.py @@ -1,3 +1,11 @@ +""" +Basic test frameowrk for TimeXer v2 model. +TODO: +- Add tests for testing the scaling of features, once that is implemented in the D1/D2 + level. +- Add tests for the M mode (multiple series) once that is implemented. +""" + import numpy as np import pandas as pd import pytest @@ -143,6 +151,7 @@ def sample_multivariate_multi_series_data(): return df +@pytest.fixture def basic_timeseries_dataset(sample_multivariate_data): """Create a basic TimeSeries dataset for testing.""" return TimeSeries( @@ -169,10 +178,10 @@ def basic_tslib_data_module(basic_timeseries_dataset): """Create a basic TslibDataModule for testing.""" return TslibDataModule( time_series_dataset=basic_timeseries_dataset, - batch_size=4, + batch_size=2, context_length=12, prediction_length=8, - train_val_test_split=(0.6, 0.2, 0.2), + train_val_test_split=(0.7, 0.15, 0.15), ) @@ -194,7 +203,7 @@ def model(basic_metadata): n_heads=8, e_layers=2, d_ff=256, - droupout=0.1, + dropout=0.1, patch_length=4, logging_metrics=[SMAPE()], optimizer="adam", @@ -207,3 +216,185 @@ def model(basic_metadata): }, metadata=basic_metadata, ) + + +def test_basic_model_initialization(model, basic_metadata): + """Test the basic model initialization.""" + + assert isinstance(model, TimeXer) + + assert model.hidden_size == 64 + assert model.n_heads == 8 + assert model.e_layers == 2 + assert model.d_ff == 256 + assert model.patch_length == 4 + assert model.dropout == 0.1 + + assert model.patch_num == 3 + assert model.n_target_vars == 1 + assert model.head_nf == 64 * (3 + 1) + + assert model.context_length == basic_metadata["context_length"] + assert model.prediction_length == basic_metadata["prediction_length"] + assert model.cont_dim == basic_metadata["n_features"]["continuous"] + assert model.cat_dim == basic_metadata["n_features"]["categorical"] + assert model.target_dim == basic_metadata["n_features"]["target"] + assert model.features == basic_metadata["features"] + + +def test_multivariate_single_series(model, basic_tslib_data_module): + basic_tslib_data_module.setup() + train_dataloader = basic_tslib_data_module.train_dataloader() + batch = next(iter(train_dataloader))[0] + + model.eval() + with torch.no_grad(): + output = model(batch) + + assert "prediction" in output + predictions = output["prediction"] + + batch_size = batch["history_cont"].shape[0] + assert predictions.shape == (batch_size, model.prediction_length, model.target_dim) + + assert not torch.isnan(predictions).any() + assert not torch.isinf(predictions).any() + + +def test_quantile_predictions(basic_metadata): + """Test quantile predictions with TimeXer model.""" + + quantiles = [0.1, 0.5, 0.9] + + model = TimeXer( + loss=QuantileLoss(quantiles=quantiles), + hidden_size=64, + n_heads=8, + e_layers=2, + d_ff=256, + dropout=0.1, + patch_length=4, + metadata=basic_metadata, + ) + + assert model.n_quantiles == 3 + + batch_size = 4 + + # sample input data as a substitute for x + sample_input_data = { + "history_cont": torch.randn( + batch_size, 12, basic_metadata["n_features"]["continuous"] + ), + "history_target": torch.randn( + batch_size, 12, basic_metadata["n_features"]["target"] + ), + "history_time_idx": torch.arange(12).unsqueeze(0).repeat(batch_size, 1), + } + + model.eval() + with torch.no_grad(): + output = model(sample_input_data) + + predictions = output["prediction"] + assert predictions.shape == (batch_size, 8, 1, 3) + + +def test_missing_history_target_handling(basic_metadata): + """Test handling of missing history_target in TimeXer model.""" + + model = TimeXer( + loss=MAE(), + hidden_size=64, + n_heads=8, + e_layers=2, + d_ff=256, + dropout=0.1, + patch_length=4, + metadata=basic_metadata, + ) + + batch_size = 4 + sample_input = { + "history_cont": torch.randn( + batch_size, 12, basic_metadata["n_features"]["continuous"] + ), # noqa: E501 + "history_time_idx": torch.arange(12).unsqueeze(0).repeat(batch_size, 1), + } + + model.eval() + with torch.no_grad(): + output = model(sample_input) + + predictions = output["prediction"] + assert predictions.shape == (batch_size, 8, basic_metadata["n_features"]["target"]) + assert not torch.isnan(predictions).any() + + +def test_endogenous_exogenous_variable_selection(basic_metadata): + """Test explicit endogenous and exogenous variable selection in TimeXer model.""" + + endo_names = basic_metadata["feature_names"]["continuous"][0] + exog_names = basic_metadata["feature_names"]["continuous"][1] + + model = TimeXer( + loss=MAE(), + hidden_size=64, + n_heads=8, + endogenous_vars=[endo_names], + exogenous_vars=[exog_names], + e_layers=2, + metadata=basic_metadata, + ) + + batch_size = 4 + sample_input = { + "history_cont": torch.randn( + batch_size, 12, basic_metadata["n_features"]["continuous"] + ), + "history_target": torch.randn( + batch_size, 12, basic_metadata["n_features"]["target"] + ), + "history_time_idx": torch.arange(12).unsqueeze(0).repeat(batch_size, 1), + } + + model.eval() + with torch.no_grad(): + output = model(sample_input) + + predictions = output["prediction"] + assert predictions.shape == (batch_size, 8, 1) + assert not torch.isnan(predictions).any() + + +def test_integration_with_datamodule(model, basic_tslib_data_module): + """Test integration of TimeXer model with TslibDataModule.""" + + basic_tslib_data_module.setup(stage="fit") + basic_tslib_data_module.setup(stage="test") + + train_loader = basic_tslib_data_module.train_dataloader() + test_loader = basic_tslib_data_module.test_dataloader() + val_loader = basic_tslib_data_module.val_dataloader() + + model.eval() + with torch.no_grad(): + train_batch = next(iter(train_loader))[0] + train_output = model(train_batch) + assert train_output["prediction"].shape[1] == model.prediction_length + + # Check if validation and test sets are not empty + # If they are empty, skip the validation and test checks + try: + val_batch = next(iter(val_loader))[0] + val_output = model(val_batch) + assert val_output["prediction"].shape[1] == model.prediction_length + except StopIteration: + print("Validation set is empty, skipping validation testing") + + try: + test_batch = next(iter(test_loader))[0] + test_output = model(test_batch) + assert test_output["prediction"].shape[1] == model.prediction_length + except StopIteration: + print("Test set is empty, skipping test testing") From 0003c549dce8c7162df465453fb600e54a3f9de6 Mon Sep 17 00:00:00 2001 From: Pranav Bhat Date: Fri, 13 Jun 2025 16:09:01 +0530 Subject: [PATCH 123/139] minor changes to forecast and forecast multi to deal with explicit endogenous or exogenous variables --- .../models/timexer/_timexer_v2.py | 27 ++++++++++++------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/pytorch_forecasting/models/timexer/_timexer_v2.py b/pytorch_forecasting/models/timexer/_timexer_v2.py index e811a2e7f..b8a484271 100644 --- a/pytorch_forecasting/models/timexer/_timexer_v2.py +++ b/pytorch_forecasting/models/timexer/_timexer_v2.py @@ -198,7 +198,7 @@ def _init_network(self): self.n_quantiles = None - if hasattr(self.loss, "quantiles"): + if hasattr(self.loss, "quantiles") and self.loss.quantiles is not None: self.n_quantiles = len(self.loss.quantiles) if self.hidden_size % self.n_heads != 0: @@ -283,7 +283,7 @@ def _forecast(self, x: dict[str, torch.Tensor]) -> dict[str, torch.Tensor]: history_target = x.get( "history_target", - torch.zeros(batch_size, self.context_length, 0, device=self.device), + torch.zeros(batch_size, self.context_length, 1, device=self.device), ) # noqa: E501 if history_time_idx is not None and history_time_idx.dim() == 2: @@ -294,14 +294,16 @@ def _forecast(self, x: dict[str, torch.Tensor]) -> dict[str, torch.Tensor]: endogenous_cont = history_target if self.endogenous_vars: endogenous_indices = [ - self.cont_names.index(var) for var in self.endogenous_vars + self.feature_names["continuous"].index(var) + for var in self.endogenous_vars # noqa: E501 ] endogenous_cont = history_cont[..., endogenous_indices] exogenous_cont = history_cont if self.exogenous_vars: exogenous_indices = [ - self.cont_names.index(var) for var in self.exogenous_vars + self.feature_names["continuous"].index(var) + for var in self.exogenous_vars # noqa: E501 ] exogenous_cont = history_cont[..., exogenous_indices] @@ -334,26 +336,31 @@ def _forecast_multi(self, x: dict[str, torch.Tensor]) -> dict[str, torch.Tensor] Returns: dict[str, torch.Tensor]: Model predictions. """ - + batch_size = x["history_cont"].shape[0] history_cont = x["history_cont"] history_time_idx = x.get("history_time_idx", None) - history_target = x["history_target"] + history_target = x.get( + "history_target", + torch.zeros(batch_size, self.context_length, 1, device=self.device), + ) # noqa: E501 + if history_time_idx is not None and history_time_idx.dim() == 2: + # change [batch_size, time_steps] to [batch_size, time_steps, features] + history_time_idx = history_time_idx.unsqueeze(-1) + + endogenous_cont = history_target if self.endogenous_vars: endogenous_indices = [ self.cont_names.index(var) for var in self.endogenous_vars ] endogenous_cont = history_cont[..., endogenous_indices] - else: - endogenous_cont = history_target + exogenous_cont = history_cont if self.exogenous_vars: exogenous_indices = [ self.cont_names.index(var) for var in self.exogenous_vars ] exogenous_cont = history_cont[..., exogenous_indices] - else: - exogenous_cont = history_cont en_embed, n_vars = self.en_embedding(endogenous_cont) From 254641024c1ecf8eabdafb9ed7a0ed748a79c582 Mon Sep 17 00:00:00 2001 From: Pranav Bhat Date: Fri, 13 Jun 2025 16:24:32 +0530 Subject: [PATCH 124/139] add endogenous and exogenous feature handling for forecast_multi --- pytorch_forecasting/models/timexer/_timexer_v2.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/pytorch_forecasting/models/timexer/_timexer_v2.py b/pytorch_forecasting/models/timexer/_timexer_v2.py index b8a484271..32e11994e 100644 --- a/pytorch_forecasting/models/timexer/_timexer_v2.py +++ b/pytorch_forecasting/models/timexer/_timexer_v2.py @@ -351,14 +351,16 @@ def _forecast_multi(self, x: dict[str, torch.Tensor]) -> dict[str, torch.Tensor] endogenous_cont = history_target if self.endogenous_vars: endogenous_indices = [ - self.cont_names.index(var) for var in self.endogenous_vars + self.feature_names["continuous"].index(var) + for var in self.endogenous_vars # noqa: E501 ] endogenous_cont = history_cont[..., endogenous_indices] exogenous_cont = history_cont if self.exogenous_vars: exogenous_indices = [ - self.cont_names.index(var) for var in self.exogenous_vars + self.feature_names["continuous"].index(var) + for var in self.exogenous_vars # noqa: E501 ] exogenous_cont = history_cont[..., exogenous_indices] From 7c6385c8ad00de8f85e64bf0b62d46a1a84db506 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Franz=20Kir=C3=A1ly?= Date: Mon, 16 Jun 2025 00:13:12 +0200 Subject: [PATCH 125/139] rename --- .../models/timexer/{timexer_v2_metadata.py => _timexer_pgk_v2.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename pytorch_forecasting/models/timexer/{timexer_v2_metadata.py => _timexer_pgk_v2.py} (100%) diff --git a/pytorch_forecasting/models/timexer/timexer_v2_metadata.py b/pytorch_forecasting/models/timexer/_timexer_pgk_v2.py similarity index 100% rename from pytorch_forecasting/models/timexer/timexer_v2_metadata.py rename to pytorch_forecasting/models/timexer/_timexer_pgk_v2.py From 66a006cea4ecd17e8202993a8b3d41961732f941 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Franz=20Kir=C3=A1ly?= Date: Mon, 16 Jun 2025 00:15:41 +0200 Subject: [PATCH 126/139] renames, exports --- pytorch_forecasting/models/timexer/__init__.py | 2 ++ .../timexer/{_timexer_pgk_v2.py => _timexer_pkg_v2.py} | 2 +- pytorch_forecasting/models/timexer/_timexer_v2.py | 7 ------- 3 files changed, 3 insertions(+), 8 deletions(-) rename pytorch_forecasting/models/timexer/{_timexer_pgk_v2.py => _timexer_pkg_v2.py} (99%) diff --git a/pytorch_forecasting/models/timexer/__init__.py b/pytorch_forecasting/models/timexer/__init__.py index 8ec9c7dd2..43703d33b 100644 --- a/pytorch_forecasting/models/timexer/__init__.py +++ b/pytorch_forecasting/models/timexer/__init__.py @@ -4,6 +4,7 @@ from pytorch_forecasting.models.timexer._timexer import TimeXer from pytorch_forecasting.models.timexer._timexer_pkg import TimeXer_pkg +from pytorch_forecasting.models.timexer._timexer_pkg_v2 import TimeXer_pkg_v2 from pytorch_forecasting.models.timexer.sub_modules import ( AttentionLayer, DataEmbedding_inverted, @@ -28,4 +29,5 @@ "Encoder", "EncoderLayer", "TimeXer_pkg", + "TimeXer_pkg_v2", ] diff --git a/pytorch_forecasting/models/timexer/_timexer_pgk_v2.py b/pytorch_forecasting/models/timexer/_timexer_pkg_v2.py similarity index 99% rename from pytorch_forecasting/models/timexer/_timexer_pgk_v2.py rename to pytorch_forecasting/models/timexer/_timexer_pkg_v2.py index 5db49911d..9e8859126 100644 --- a/pytorch_forecasting/models/timexer/_timexer_pgk_v2.py +++ b/pytorch_forecasting/models/timexer/_timexer_pkg_v2.py @@ -5,7 +5,7 @@ from pytorch_forecasting.models.base._base_object import _BasePtForecasterV2 -class TimeXerMetadata(_BasePtForecasterV2): +class TimeXer_pkg_v2(_BasePtForecasterV2): """TimeXer metadata container.""" _tags = { diff --git a/pytorch_forecasting/models/timexer/_timexer_v2.py b/pytorch_forecasting/models/timexer/_timexer_v2.py index 32e11994e..d6732a4af 100644 --- a/pytorch_forecasting/models/timexer/_timexer_v2.py +++ b/pytorch_forecasting/models/timexer/_timexer_v2.py @@ -13,17 +13,10 @@ from typing import Any, Optional, Union import warnings as warn -import lightning.pytorch as pl -from lightning.pytorch import LightningModule, Trainer -import numpy as np -import pandas as pd import torch import torch.nn as nn -import torch.nn.functional as F from torch.optim import Optimizer -from pytorch_forecasting.metrics import MAE, MAPE, MultiHorizonMetric, QuantileLoss -from pytorch_forecasting.metrics.base_metrics import MultiLoss from pytorch_forecasting.models.base._tslib_base_model_v2 import TslibBaseModel From eb0dfa5ff6d29b2a116ecedcb79bda31a20e30f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Franz=20Kir=C3=A1ly?= Date: Mon, 16 Jun 2025 00:18:27 +0200 Subject: [PATCH 127/139] reverts --- pytorch_forecasting/models/deepar/_deepar_pkg.py | 4 ++-- pytorch_forecasting/models/mlp/_decodermlp_pkg.py | 4 ++-- pytorch_forecasting/models/nbeats/_nbeats_pkg.py | 4 ++-- pytorch_forecasting/models/timexer/_timexer_pkg.py | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/pytorch_forecasting/models/deepar/_deepar_pkg.py b/pytorch_forecasting/models/deepar/_deepar_pkg.py index ffee0d328..eb30d639c 100644 --- a/pytorch_forecasting/models/deepar/_deepar_pkg.py +++ b/pytorch_forecasting/models/deepar/_deepar_pkg.py @@ -1,9 +1,9 @@ """DeepAR package container.""" -from pytorch_forecasting.models.base._base_object import _BasePtForecasterV1 +from pytorch_forecasting.models.base._base_object import _BasePtForecaster -class DeepAR_pkg(_BasePtForecasterV1): +class DeepAR_pkg(_BasePtForecaster): """DeepAR package container.""" _tags = { diff --git a/pytorch_forecasting/models/mlp/_decodermlp_pkg.py b/pytorch_forecasting/models/mlp/_decodermlp_pkg.py index df5060088..917d99fb2 100644 --- a/pytorch_forecasting/models/mlp/_decodermlp_pkg.py +++ b/pytorch_forecasting/models/mlp/_decodermlp_pkg.py @@ -1,9 +1,9 @@ """DecoderMLP package container.""" -from pytorch_forecasting.models.base._base_object import _BasePtForecasterV1 +from pytorch_forecasting.models.base._base_object import _BasePtForecaster -class DecoderMLP_pkg(_BasePtForecasterV1): +class DecoderMLP_pkg(_BasePtForecaster): """DecoderMLP package container.""" _tags = { diff --git a/pytorch_forecasting/models/nbeats/_nbeats_pkg.py b/pytorch_forecasting/models/nbeats/_nbeats_pkg.py index 837e709ce..7650749c2 100644 --- a/pytorch_forecasting/models/nbeats/_nbeats_pkg.py +++ b/pytorch_forecasting/models/nbeats/_nbeats_pkg.py @@ -1,9 +1,9 @@ """NBeats package container.""" -from pytorch_forecasting.models.base._base_object import _BasePtForecasterV1 +from pytorch_forecasting.models.base._base_object import _BasePtForecaster -class NBeats_pkg(_BasePtForecasterV1): +class NBeats_pkg(_BasePtForecaster): """NBeats package container.""" _tags = { diff --git a/pytorch_forecasting/models/timexer/_timexer_pkg.py b/pytorch_forecasting/models/timexer/_timexer_pkg.py index f22c2db0f..d1febcdb1 100644 --- a/pytorch_forecasting/models/timexer/_timexer_pkg.py +++ b/pytorch_forecasting/models/timexer/_timexer_pkg.py @@ -1,9 +1,9 @@ """TimeXer package container.""" -from pytorch_forecasting.models.base._base_object import _BasePtForecasterV1 +from pytorch_forecasting.models.base._base_object import _BasePtForecaster -class TimeXer_pkg(_BasePtForecasterV1): +class TimeXer_pkg(_BasePtForecaster): """TimeXer package container.""" _tags = { From 6fac5092265989ff54658029932a7111b8dc3159 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Franz=20Kir=C3=A1ly?= Date: Mon, 16 Jun 2025 00:19:05 +0200 Subject: [PATCH 128/139] revert --- pytorch_forecasting/models/tide/_tide_pkg.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pytorch_forecasting/models/tide/_tide_pkg.py b/pytorch_forecasting/models/tide/_tide_pkg.py index 373a95525..67fdcf154 100644 --- a/pytorch_forecasting/models/tide/_tide_pkg.py +++ b/pytorch_forecasting/models/tide/_tide_pkg.py @@ -1,9 +1,9 @@ """TiDE package container.""" -from pytorch_forecasting.models.base._base_object import _BasePtForecasterV1 +from pytorch_forecasting.models.base._base_object import _BasePtForecaster -class TiDEModel_pkg(_BasePtForecasterV1): +class TiDEModel_pkg(_BasePtForecaster): """Package container for TiDE Model.""" _tags = { From ee1edf55a6bef57470eded60ddb2d32615cf45cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Franz=20Kir=C3=A1ly?= Date: Mon, 16 Jun 2025 00:19:27 +0200 Subject: [PATCH 129/139] revert --- pytorch_forecasting/tests/_conftest.py | 186 ------------------------- 1 file changed, 186 deletions(-) diff --git a/pytorch_forecasting/tests/_conftest.py b/pytorch_forecasting/tests/_conftest.py index dd8d1d8d9..8def0bfe2 100644 --- a/pytorch_forecasting/tests/_conftest.py +++ b/pytorch_forecasting/tests/_conftest.py @@ -1,14 +1,10 @@ -from datetime import datetime - import numpy as np -import pandas as pd import pytest import torch from pytorch_forecasting import TimeSeriesDataSet from pytorch_forecasting.data import EncoderNormalizer, GroupNormalizer, NaNLabelEncoder from pytorch_forecasting.data.examples import generate_ar_data, get_stallion_data -from pytorch_forecasting.data.timeseries import TimeSeries torch.manual_seed(23) @@ -60,188 +56,6 @@ def make_dataloaders(data_with_covariates, **kwargs): return dict(train=train_dataloader, val=val_dataloader, test=test_dataloader) -@pytest.fixture(scope="session") -def data_with_covariates_v2(): - """Create synthetic time series data with all numerical features.""" - - start_date = datetime(2015, 1, 1) - end_date = datetime(2017, 12, 31) - dates = pd.date_range(start_date, end_date, freq="M") - - agencies = [0, 1] - skus = [0, 1] - data_list = [] - - for agency in agencies: - for sku in skus: - for date in dates: - time_idx = (date.year - 2015) * 12 + date.month - 1 - - volume = ( - np.random.exponential(2) - + 0.1 * time_idx - + 0.5 * np.sin(date.month * np.pi / 6) - ) - volume = max(0.001, volume) - month = date.month - year = date.year - quarter = (date.month - 1) // 3 + 1 - - seasonal_1 = np.sin(2 * np.pi * date.month / 12) - seasonal_2 = np.cos(2 * np.pi * date.month / 12) - - agency_feature_1 = agency * 10 + np.random.normal(0, 0.1) - agency_feature_2 = agency * 5 + np.random.normal(0, 0.1) - - sku_feature_1 = sku * 8 + np.random.normal(0, 0.1) - sku_feature_2 = sku * 3 + np.random.normal(0, 0.1) - - trend = time_idx * 0.1 - noise = np.random.normal(0, 0.1) - - special_event_1 = 1 if date.month in [12, 1] else 0 - special_event_2 = 1 if date.month in [6, 7, 8] else 0 - - data_list.append( - { - "date": date, - "time_idx": time_idx, - "agency_encoded": agency, - "sku_encoded": sku, - "volume": volume, - "target": volume, - "weight": 1.0 + np.sqrt(volume), - "month": month, - "year": year, - "quarter": quarter, - "seasonal_1": seasonal_1, - "seasonal_2": seasonal_2, - "agency_feature_1": agency_feature_1, - "agency_feature_2": agency_feature_2, - "sku_feature_1": sku_feature_1, - "sku_feature_2": sku_feature_2, - "trend": trend, - "noise": noise, - "special_event_1": special_event_1, - "special_event_2": special_event_2, - "log_volume": np.log1p(volume), - } - ) - - data = pd.DataFrame(data_list) - - numeric_cols = [col for col in data.columns if col != "date"] - for col in numeric_cols: - data[col] = pd.to_numeric(data[col], errors="coerce") - data[numeric_cols] = data[numeric_cols].fillna(0) - - return data - - -def make_datasets_v2(data_with_covariates, **kwargs): - """Create datasets with consistent encoder/decoder features.""" - - training_cutoff = "2016-09-01" - target_col = kwargs.get("target", "target") - group_cols = kwargs.get("group_ids", ["agency_encoded", "sku_encoded"]) - - known_features = [ - "month", - "year", - "quarter", - "seasonal_1", - "seasonal_2", - "special_event_1", - "special_event_2", - "trend", - ] - unknown_features = [ - "agency_feature_1", - "agency_feature_2", - "sku_feature_1", - "sku_feature_2", - "noise", - "log_volume", - ] - - numerical_features = known_features + unknown_features - categorical_features = [] - static_features = group_cols - - for col in numerical_features + categorical_features + group_cols + [target_col]: - if col in data_with_covariates.columns: - data_with_covariates[col] = pd.to_numeric( - data_with_covariates[col], errors="coerce" - ).fillna(0) - - for col in categorical_features + group_cols: - if col in data_with_covariates.columns: - data_with_covariates[col] = data_with_covariates[col].astype("int64") - - if "weight" in data_with_covariates.columns: - data_with_covariates["weight"] = pd.to_numeric( - data_with_covariates["weight"], errors="coerce" - ).fillna(1.0) - - training_data = data_with_covariates[ - data_with_covariates.date < training_cutoff - ].copy() - validation_data = data_with_covariates.copy() - - required_columns = ( - ["time_idx", target_col, "weight", "date"] - + group_cols - + numerical_features - + categorical_features - ) - - available_columns = [ - col for col in required_columns if col in data_with_covariates.columns - ] - - training_data_clean = training_data[available_columns].copy() - validation_data_clean = validation_data[available_columns].copy() - - if "date" in training_data_clean.columns: - training_data_clean = training_data_clean.drop("date", axis=1) - if "date" in validation_data_clean.columns: - validation_data_clean = validation_data_clean.drop("date", axis=1) - - training_dataset = TimeSeries( - data=training_data_clean, - time="time_idx", - target=[target_col], - group=group_cols, - weight="weight", - num=numerical_features, - cat=categorical_features if categorical_features else None, - known=known_features, - unknown=unknown_features, - static=static_features, - ) - - validation_dataset = TimeSeries( - data=validation_data_clean, - time="time_idx", - target=[target_col], - group=group_cols, - weight="weight", - num=numerical_features, - cat=categorical_features if categorical_features else None, - known=known_features, - unknown=unknown_features, - static=static_features, - ) - - training_max_time_idx = training_data["time_idx"].max() + 1 - - return { - "training_dataset": training_dataset, - "validation_dataset": validation_dataset, - "training_max_time_idx": training_max_time_idx, - } - - @pytest.fixture( params=[ dict(), From ff85e69f9042a8b0bfbfb9d3b087bab6156d56a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Franz=20Kir=C3=A1ly?= Date: Mon, 16 Jun 2025 00:21:26 +0200 Subject: [PATCH 130/139] move --- pytorch_forecasting/data/tests/__init__.py | 1 + .../data/tests}/test_tslib_data_module.py | 0 2 files changed, 1 insertion(+) create mode 100644 pytorch_forecasting/data/tests/__init__.py rename {tests/test_data => pytorch_forecasting/data/tests}/test_tslib_data_module.py (100%) diff --git a/pytorch_forecasting/data/tests/__init__.py b/pytorch_forecasting/data/tests/__init__.py new file mode 100644 index 000000000..162591895 --- /dev/null +++ b/pytorch_forecasting/data/tests/__init__.py @@ -0,0 +1 @@ +"""Tests for data modules and dataloaders in pytorch_forecasting.data package.""" diff --git a/tests/test_data/test_tslib_data_module.py b/pytorch_forecasting/data/tests/test_tslib_data_module.py similarity index 100% rename from tests/test_data/test_tslib_data_module.py rename to pytorch_forecasting/data/tests/test_tslib_data_module.py From 377f41617f1a476c3beefe633eb4a8108fb09a3e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Franz=20Kir=C3=A1ly?= Date: Mon, 16 Jun 2025 00:21:37 +0200 Subject: [PATCH 131/139] Update test_tslib_data_module.py --- pytorch_forecasting/data/tests/test_tslib_data_module.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pytorch_forecasting/data/tests/test_tslib_data_module.py b/pytorch_forecasting/data/tests/test_tslib_data_module.py index e087181b2..6822a9fa3 100644 --- a/pytorch_forecasting/data/tests/test_tslib_data_module.py +++ b/pytorch_forecasting/data/tests/test_tslib_data_module.py @@ -4,7 +4,6 @@ import torch from pytorch_forecasting.data._tslib_data_module import TslibDataModule -from pytorch_forecasting.data.examples import get_stallion_data from pytorch_forecasting.data.timeseries import TimeSeries From 14db3802695d7812825a721a42ce0ec588f0e54d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Franz=20Kir=C3=A1ly?= Date: Mon, 16 Jun 2025 00:23:06 +0200 Subject: [PATCH 132/139] make layer folders all private --- pytorch_forecasting/layers/__init__.py | 8 ++++---- .../layers/{attention => _attention}/__init__.py | 0 .../layers/{attention => _attention}/_attention_layer.py | 0 .../layers/{attention => _attention}/_full_attention.py | 0 .../layers/{embeddings => _embeddings}/__init__.py | 0 .../layers/{embeddings => _embeddings}/_data_embedding.py | 0 .../layers/{embeddings => _embeddings}/_en_embedding.py | 0 .../{embeddings => _embeddings}/_positional_embedding.py | 0 .../layers/{encoders => _encoders}/__init__.py | 0 .../layers/{encoders => _encoders}/_encoder.py | 0 .../layers/{encoders => _encoders}/_encoder_layer.py | 0 .../layers/{output => _output}/__init__.py | 0 .../layers/{output => _output}/_flatten_head.py | 0 13 files changed, 4 insertions(+), 4 deletions(-) rename pytorch_forecasting/layers/{attention => _attention}/__init__.py (100%) rename pytorch_forecasting/layers/{attention => _attention}/_attention_layer.py (100%) rename pytorch_forecasting/layers/{attention => _attention}/_full_attention.py (100%) rename pytorch_forecasting/layers/{embeddings => _embeddings}/__init__.py (100%) rename pytorch_forecasting/layers/{embeddings => _embeddings}/_data_embedding.py (100%) rename pytorch_forecasting/layers/{embeddings => _embeddings}/_en_embedding.py (100%) rename pytorch_forecasting/layers/{embeddings => _embeddings}/_positional_embedding.py (100%) rename pytorch_forecasting/layers/{encoders => _encoders}/__init__.py (100%) rename pytorch_forecasting/layers/{encoders => _encoders}/_encoder.py (100%) rename pytorch_forecasting/layers/{encoders => _encoders}/_encoder_layer.py (100%) rename pytorch_forecasting/layers/{output => _output}/__init__.py (100%) rename pytorch_forecasting/layers/{output => _output}/_flatten_head.py (100%) diff --git a/pytorch_forecasting/layers/__init__.py b/pytorch_forecasting/layers/__init__.py index 406470189..43a8db84c 100644 --- a/pytorch_forecasting/layers/__init__.py +++ b/pytorch_forecasting/layers/__init__.py @@ -2,17 +2,17 @@ Architectural deep learning layers from `nn.Module`. """ -from pytorch_forecasting.layers.attention import AttentionLayer, FullAttention -from pytorch_forecasting.layers.embeddings import ( +from pytorch_forecasting.layers._attention import AttentionLayer, FullAttention +from pytorch_forecasting.layers._embeddings import ( DataEmbedding_inverted, EnEmbedding, PositionalEmbedding, ) -from pytorch_forecasting.layers.encoders import ( +from pytorch_forecasting.layers._encoders import ( Encoder, EncoderLayer, ) -from pytorch_forecasting.layers.output._flatten_head import ( +from pytorch_forecasting.layers._output._flatten_head import ( FlattenHead, ) diff --git a/pytorch_forecasting/layers/attention/__init__.py b/pytorch_forecasting/layers/_attention/__init__.py similarity index 100% rename from pytorch_forecasting/layers/attention/__init__.py rename to pytorch_forecasting/layers/_attention/__init__.py diff --git a/pytorch_forecasting/layers/attention/_attention_layer.py b/pytorch_forecasting/layers/_attention/_attention_layer.py similarity index 100% rename from pytorch_forecasting/layers/attention/_attention_layer.py rename to pytorch_forecasting/layers/_attention/_attention_layer.py diff --git a/pytorch_forecasting/layers/attention/_full_attention.py b/pytorch_forecasting/layers/_attention/_full_attention.py similarity index 100% rename from pytorch_forecasting/layers/attention/_full_attention.py rename to pytorch_forecasting/layers/_attention/_full_attention.py diff --git a/pytorch_forecasting/layers/embeddings/__init__.py b/pytorch_forecasting/layers/_embeddings/__init__.py similarity index 100% rename from pytorch_forecasting/layers/embeddings/__init__.py rename to pytorch_forecasting/layers/_embeddings/__init__.py diff --git a/pytorch_forecasting/layers/embeddings/_data_embedding.py b/pytorch_forecasting/layers/_embeddings/_data_embedding.py similarity index 100% rename from pytorch_forecasting/layers/embeddings/_data_embedding.py rename to pytorch_forecasting/layers/_embeddings/_data_embedding.py diff --git a/pytorch_forecasting/layers/embeddings/_en_embedding.py b/pytorch_forecasting/layers/_embeddings/_en_embedding.py similarity index 100% rename from pytorch_forecasting/layers/embeddings/_en_embedding.py rename to pytorch_forecasting/layers/_embeddings/_en_embedding.py diff --git a/pytorch_forecasting/layers/embeddings/_positional_embedding.py b/pytorch_forecasting/layers/_embeddings/_positional_embedding.py similarity index 100% rename from pytorch_forecasting/layers/embeddings/_positional_embedding.py rename to pytorch_forecasting/layers/_embeddings/_positional_embedding.py diff --git a/pytorch_forecasting/layers/encoders/__init__.py b/pytorch_forecasting/layers/_encoders/__init__.py similarity index 100% rename from pytorch_forecasting/layers/encoders/__init__.py rename to pytorch_forecasting/layers/_encoders/__init__.py diff --git a/pytorch_forecasting/layers/encoders/_encoder.py b/pytorch_forecasting/layers/_encoders/_encoder.py similarity index 100% rename from pytorch_forecasting/layers/encoders/_encoder.py rename to pytorch_forecasting/layers/_encoders/_encoder.py diff --git a/pytorch_forecasting/layers/encoders/_encoder_layer.py b/pytorch_forecasting/layers/_encoders/_encoder_layer.py similarity index 100% rename from pytorch_forecasting/layers/encoders/_encoder_layer.py rename to pytorch_forecasting/layers/_encoders/_encoder_layer.py diff --git a/pytorch_forecasting/layers/output/__init__.py b/pytorch_forecasting/layers/_output/__init__.py similarity index 100% rename from pytorch_forecasting/layers/output/__init__.py rename to pytorch_forecasting/layers/_output/__init__.py diff --git a/pytorch_forecasting/layers/output/_flatten_head.py b/pytorch_forecasting/layers/_output/_flatten_head.py similarity index 100% rename from pytorch_forecasting/layers/output/_flatten_head.py rename to pytorch_forecasting/layers/_output/_flatten_head.py From 7eaee3809d01b33f4c9bdef97eb65371d8c44917 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Franz=20Kir=C3=A1ly?= Date: Mon, 16 Jun 2025 00:24:10 +0200 Subject: [PATCH 133/139] Update _timexer_v2.py --- pytorch_forecasting/models/timexer/_timexer_v2.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/pytorch_forecasting/models/timexer/_timexer_v2.py b/pytorch_forecasting/models/timexer/_timexer_v2.py index d6732a4af..01474a91a 100644 --- a/pytorch_forecasting/models/timexer/_timexer_v2.py +++ b/pytorch_forecasting/models/timexer/_timexer_v2.py @@ -151,16 +151,15 @@ def _init_network(self): Initialize the network for TimeXer's architecture. """ - from pytorch_forecasting.layers.attention import ( + from pytorch_forecasting.layers import ( AttentionLayer, - FullAttention, - ) - from pytorch_forecasting.layers.embeddings import ( DataEmbedding_inverted, + Encoder, + EncoderLayer, EnEmbedding, + FlattenHead, + FullAttention, ) - from pytorch_forecasting.layers.encoders import Encoder, EncoderLayer - from pytorch_forecasting.layers.output._flatten_head import FlattenHead if self.context_length <= self.patch_length: raise ValueError( From 06ae10252265a64a79381cdd60f8fafd3ebc8815 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Franz=20Kir=C3=A1ly?= Date: Mon, 16 Jun 2025 00:25:48 +0200 Subject: [PATCH 134/139] Update _tslib_base_model_v2.py --- pytorch_forecasting/models/base/_tslib_base_model_v2.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pytorch_forecasting/models/base/_tslib_base_model_v2.py b/pytorch_forecasting/models/base/_tslib_base_model_v2.py index 9bf705b08..b502c46bf 100644 --- a/pytorch_forecasting/models/base/_tslib_base_model_v2.py +++ b/pytorch_forecasting/models/base/_tslib_base_model_v2.py @@ -1,6 +1,5 @@ """ Experimental implementation of a base class for `tslib` models. -NOTE: This PR is stacked on the PR #1812(phoeenniixx). """ from typing import Optional, Union From faf86ce32db165c45cf39a609fdb7ebaf2906ced Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Franz=20Kir=C3=A1ly?= Date: Mon, 16 Jun 2025 00:30:42 +0200 Subject: [PATCH 135/139] imports --- pytorch_forecasting/layers/_attention/__init__.py | 4 ++-- pytorch_forecasting/layers/_embeddings/__init__.py | 6 +++--- pytorch_forecasting/layers/_embeddings/_en_embedding.py | 2 +- pytorch_forecasting/layers/_encoders/__init__.py | 4 ++-- pytorch_forecasting/layers/_output/__init__.py | 2 +- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/pytorch_forecasting/layers/_attention/__init__.py b/pytorch_forecasting/layers/_attention/__init__.py index 23724a67b..cdfc6c3e2 100644 --- a/pytorch_forecasting/layers/_attention/__init__.py +++ b/pytorch_forecasting/layers/_attention/__init__.py @@ -2,7 +2,7 @@ Attention Layers for pytorch-forecasting models. """ -from pytorch_forecasting.layers.attention._attention_layer import AttentionLayer -from pytorch_forecasting.layers.attention._full_attention import FullAttention +from pytorch_forecasting.layers._attention._attention_layer import AttentionLayer +from pytorch_forecasting.layers._attention._full_attention import FullAttention __all__ = ["AttentionLayer", "FullAttention"] diff --git a/pytorch_forecasting/layers/_embeddings/__init__.py b/pytorch_forecasting/layers/_embeddings/__init__.py index 1d0a681d9..37db28800 100644 --- a/pytorch_forecasting/layers/_embeddings/__init__.py +++ b/pytorch_forecasting/layers/_embeddings/__init__.py @@ -2,9 +2,9 @@ Implementation of embedding layers for PTF models imported from `nn.Modules` """ -from pytorch_forecasting.layers.embeddings._data_embedding import DataEmbedding_inverted -from pytorch_forecasting.layers.embeddings._en_embedding import EnEmbedding -from pytorch_forecasting.layers.embeddings._positional_embedding import ( +from pytorch_forecasting.layers._embeddings._data_embedding import DataEmbedding_inverted +from pytorch_forecasting.layers._embeddings._en_embedding import EnEmbedding +from pytorch_forecasting.layers._embeddings._positional_embedding import ( PositionalEmbedding, ) diff --git a/pytorch_forecasting/layers/_embeddings/_en_embedding.py b/pytorch_forecasting/layers/_embeddings/_en_embedding.py index efa611809..573f0571b 100644 --- a/pytorch_forecasting/layers/_embeddings/_en_embedding.py +++ b/pytorch_forecasting/layers/_embeddings/_en_embedding.py @@ -10,7 +10,7 @@ import torch.nn as nn import torch.nn.functional as F -from pytorch_forecasting.layers.embeddings._positional_embedding import ( +from pytorch_forecasting.layers._embeddings._positional_embedding import ( PositionalEmbedding, ) diff --git a/pytorch_forecasting/layers/_encoders/__init__.py b/pytorch_forecasting/layers/_encoders/__init__.py index 517d36748..da20c4f3f 100644 --- a/pytorch_forecasting/layers/_encoders/__init__.py +++ b/pytorch_forecasting/layers/_encoders/__init__.py @@ -2,7 +2,7 @@ Encoder layers for neural network models. """ -from ._encoder import Encoder -from ._encoder_layer import EncoderLayer +from pytorch_forecasting.layers._encoders._encoder import Encoder +from pytorch_forecasting.layers._encoders._encoder_layer import EncoderLayer __all__ = ["Encoder", "EncoderLayer"] diff --git a/pytorch_forecasting/layers/_output/__init__.py b/pytorch_forecasting/layers/_output/__init__.py index 867f5b0d9..eb3b686a3 100644 --- a/pytorch_forecasting/layers/_output/__init__.py +++ b/pytorch_forecasting/layers/_output/__init__.py @@ -2,6 +2,6 @@ Implementation of output layers for PyTorch Forecasting. """ -from ._flatten_head import FlattenHead +from pytorch_forecasting.layers._output._flatten_head import FlattenHead __all__ = ["FlattenHead"] From 65e171e613167ef49d065717291e63314ccc9ce2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Franz=20Kir=C3=A1ly?= Date: Mon, 16 Jun 2025 00:32:56 +0200 Subject: [PATCH 136/139] revert --- .../tft_v2_metadata.py | 137 ------------------ 1 file changed, 137 deletions(-) delete mode 100644 pytorch_forecasting/models/temporal_fusion_transformer/tft_v2_metadata.py diff --git a/pytorch_forecasting/models/temporal_fusion_transformer/tft_v2_metadata.py b/pytorch_forecasting/models/temporal_fusion_transformer/tft_v2_metadata.py deleted file mode 100644 index f17f514f2..000000000 --- a/pytorch_forecasting/models/temporal_fusion_transformer/tft_v2_metadata.py +++ /dev/null @@ -1,137 +0,0 @@ -"""TFT metadata container.""" - -from pytorch_forecasting.models.base._base_object import _BasePtForecasterV2 - - -class TFTMetadata(_BasePtForecasterV2): - """TFT metadata container.""" - - _tags = { - "info:name": "TFT", - "authors": ["phoeenniixx"], - "capability:exogenous": True, - "capability:multivariate": True, - "capability:pred_int": True, - "capability:flexible_history_length": False, - } - - @classmethod - def get_model_cls(cls): - """Get model class.""" - from pytorch_forecasting.models.temporal_fusion_transformer._tft_v2 import TFT - - return TFT - - @classmethod - def _get_test_datamodule_from(cls, trainer_kwargs): - """Create test dataloaders from trainer_kwargs - following v1 pattern.""" - from pytorch_forecasting.data.data_module import ( - EncoderDecoderTimeSeriesDataModule, - ) - from pytorch_forecasting.tests._conftest import make_datasets_v2 - from pytorch_forecasting.tests._data_scenarios import data_with_covariates_v2 - - data_with_covariates = data_with_covariates_v2() - - data_loader_default_kwargs = dict( - target="target", - group_ids=["agency_encoded", "sku_encoded"], - add_relative_time_idx=True, - ) - - data_loader_kwargs = trainer_kwargs.get("data_loader_kwargs", {}) - data_loader_default_kwargs.update(data_loader_kwargs) - - datasets_info = make_datasets_v2( - data_with_covariates, **data_loader_default_kwargs - ) - - training_dataset = datasets_info["training_dataset"] - validation_dataset = datasets_info["validation_dataset"] - training_max_time_idx = datasets_info["training_max_time_idx"] - - max_encoder_length = data_loader_kwargs.get("max_encoder_length", 4) - max_prediction_length = data_loader_kwargs.get("max_prediction_length", 3) - add_relative_time_idx = data_loader_kwargs.get("add_relative_time_idx", True) - batch_size = data_loader_kwargs.get("batch_size", 2) - - train_datamodule = EncoderDecoderTimeSeriesDataModule( - time_series_dataset=training_dataset, - max_encoder_length=max_encoder_length, - max_prediction_length=max_prediction_length, - add_relative_time_idx=add_relative_time_idx, - batch_size=batch_size, - train_val_test_split=(0.8, 0.2, 0.0), - ) - - val_datamodule = EncoderDecoderTimeSeriesDataModule( - time_series_dataset=validation_dataset, - max_encoder_length=max_encoder_length, - max_prediction_length=max_prediction_length, - min_prediction_idx=training_max_time_idx, - add_relative_time_idx=add_relative_time_idx, - batch_size=batch_size, - train_val_test_split=(0.0, 1.0, 0.0), - ) - - test_datamodule = EncoderDecoderTimeSeriesDataModule( - time_series_dataset=validation_dataset, - max_encoder_length=max_encoder_length, - max_prediction_length=max_prediction_length, - min_prediction_idx=training_max_time_idx, - add_relative_time_idx=add_relative_time_idx, - batch_size=1, - train_val_test_split=(0.0, 0.0, 1.0), - ) - - train_datamodule.setup("fit") - val_datamodule.setup("fit") - test_datamodule.setup("test") - - train_dataloader = train_datamodule.train_dataloader() - val_dataloader = val_datamodule.val_dataloader() - test_dataloader = test_datamodule.test_dataloader() - - return { - "train": train_dataloader, - "val": val_dataloader, - "test": test_dataloader, - "data_module": train_datamodule, - } - - @classmethod - def get_test_train_params(cls): - """Return testing parameter settings for the trainer. - - Returns - ------- - params : dict or list of dict, default = {} - Parameters to create testing instances of the class - Each dict are parameters to construct an "interesting" test instance, i.e., - `MyClass(**params)` or `MyClass(**params[i])` creates a valid test instance. - `create_test_instance` uses the first (or only) dictionary in `params` - """ - return [ - {}, - dict( - hidden_size=25, - attention_head_size=5, - ), - dict( - data_loader_kwargs=dict(max_encoder_length=5, max_prediction_length=3) - ), - dict( - hidden_size=24, - attention_head_size=8, - data_loader_kwargs=dict( - max_encoder_length=5, - max_prediction_length=3, - add_relative_time_idx=False, - ), - ), - dict( - hidden_size=12, - data_loader_kwargs=dict(max_encoder_length=7, max_prediction_length=10), - ), - dict(attention_head_size=2), - ] From 3786a4cd9388ded088164917764a7a9089ed4576 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Franz=20Kir=C3=A1ly?= Date: Mon, 16 Jun 2025 00:33:38 +0200 Subject: [PATCH 137/139] Update _timexer_pkg_v2.py --- pytorch_forecasting/models/timexer/_timexer_pkg_v2.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/pytorch_forecasting/models/timexer/_timexer_pkg_v2.py b/pytorch_forecasting/models/timexer/_timexer_pkg_v2.py index 9e8859126..ba23d3488 100644 --- a/pytorch_forecasting/models/timexer/_timexer_pkg_v2.py +++ b/pytorch_forecasting/models/timexer/_timexer_pkg_v2.py @@ -28,8 +28,10 @@ def get_model_cls(cls): def _get_test_datamodule_from(cls, trainer_kwargs): """Create test dataloaders from trainer_kwargs - following v1 pattern.""" from pytorch_forecasting.data._tslib_data_module import TslibDataModule - from pytorch_forecasting.tests._conftest import make_datasets_v2 - from pytorch_forecasting.tests._data_scenarios import data_with_covariates_v2 + from pytorch_forecasting.tests._data_scenarios import ( + data_with_covariates_v2, + make_datasets_v2 + ) data_with_covariates = data_with_covariates_v2() From d3e6ce2db4f907bea595563a43787c813136da95 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Franz=20Kir=C3=A1ly?= Date: Mon, 16 Jun 2025 00:35:50 +0200 Subject: [PATCH 138/139] imports --- pytorch_forecasting/layers/_embeddings/__init__.py | 4 +++- pytorch_forecasting/models/timexer/_timexer_pkg_v2.py | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/pytorch_forecasting/layers/_embeddings/__init__.py b/pytorch_forecasting/layers/_embeddings/__init__.py index 37db28800..e18bd88ce 100644 --- a/pytorch_forecasting/layers/_embeddings/__init__.py +++ b/pytorch_forecasting/layers/_embeddings/__init__.py @@ -2,7 +2,9 @@ Implementation of embedding layers for PTF models imported from `nn.Modules` """ -from pytorch_forecasting.layers._embeddings._data_embedding import DataEmbedding_inverted +from pytorch_forecasting.layers._embeddings._data_embedding import ( + DataEmbedding_inverted, +) from pytorch_forecasting.layers._embeddings._en_embedding import EnEmbedding from pytorch_forecasting.layers._embeddings._positional_embedding import ( PositionalEmbedding, diff --git a/pytorch_forecasting/models/timexer/_timexer_pkg_v2.py b/pytorch_forecasting/models/timexer/_timexer_pkg_v2.py index ba23d3488..22f7f83ba 100644 --- a/pytorch_forecasting/models/timexer/_timexer_pkg_v2.py +++ b/pytorch_forecasting/models/timexer/_timexer_pkg_v2.py @@ -30,7 +30,7 @@ def _get_test_datamodule_from(cls, trainer_kwargs): from pytorch_forecasting.data._tslib_data_module import TslibDataModule from pytorch_forecasting.tests._data_scenarios import ( data_with_covariates_v2, - make_datasets_v2 + make_datasets_v2, ) data_with_covariates = data_with_covariates_v2() From 87ceb1018d8cc8f8539181661fcd978636723dba Mon Sep 17 00:00:00 2001 From: Pranav Bhat Date: Mon, 16 Jun 2025 19:40:00 +0530 Subject: [PATCH 139/139] remove redundant code for _forecast_multi --- .../models/timexer/_timexer_v2.py | 78 +------------------ 1 file changed, 1 insertion(+), 77 deletions(-) diff --git a/pytorch_forecasting/models/timexer/_timexer_v2.py b/pytorch_forecasting/models/timexer/_timexer_v2.py index 01474a91a..671b2a383 100644 --- a/pytorch_forecasting/models/timexer/_timexer_v2.py +++ b/pytorch_forecasting/models/timexer/_timexer_v2.py @@ -253,14 +253,6 @@ def _init_network(self): n_quantiles=self.n_quantiles, ) - # def _get_target_positions(self) -> torch.Tensor: - # """ - # Get the target positions from the dataset. - # Returns: - # torch.Tensor: Target positions. - # """ - # return torch.tensor(self.target_indices, device=self.device) - def _forecast(self, x: dict[str, torch.Tensor]) -> dict[str, torch.Tensor]: """ Forward pass of the TimeXer model. @@ -319,64 +311,6 @@ def _forecast(self, x: dict[str, torch.Tensor]) -> dict[str, torch.Tensor]: return dec_out - def _forecast_multi(self, x: dict[str, torch.Tensor]) -> dict[str, torch.Tensor]: - """ - Forecast for multivariate with multiple time series. - - Args: - x (dict[str, torch.Tensor]): Input data. - Returns: - dict[str, torch.Tensor]: Model predictions. - """ - batch_size = x["history_cont"].shape[0] - history_cont = x["history_cont"] - history_time_idx = x.get("history_time_idx", None) - history_target = x.get( - "history_target", - torch.zeros(batch_size, self.context_length, 1, device=self.device), - ) # noqa: E501 - - if history_time_idx is not None and history_time_idx.dim() == 2: - # change [batch_size, time_steps] to [batch_size, time_steps, features] - history_time_idx = history_time_idx.unsqueeze(-1) - - endogenous_cont = history_target - if self.endogenous_vars: - endogenous_indices = [ - self.feature_names["continuous"].index(var) - for var in self.endogenous_vars # noqa: E501 - ] - endogenous_cont = history_cont[..., endogenous_indices] - - exogenous_cont = history_cont - if self.exogenous_vars: - exogenous_indices = [ - self.feature_names["continuous"].index(var) - for var in self.exogenous_vars # noqa: E501 - ] - exogenous_cont = history_cont[..., exogenous_indices] - - en_embed, n_vars = self.en_embedding(endogenous_cont) - - ex_embed = self.ex_embedding(exogenous_cont, history_time_idx) - - enc_out = self.encoder(en_embed, ex_embed) - - enc_out = torch.reshape( - enc_out, (-1, n_vars, enc_out.shape[-2], enc_out.shape[-1]) - ) - - enc_out = enc_out.permute(0, 1, 3, 2) - - dec_out = self.head(enc_out) - - if self.n_quantiles is not None: - dec_out = dec_out.permute(0, 2, 1, 3) - else: - dec_out = dec_out.permute(0, 2, 1) - - return dec_out - def forward(self, x: dict[str, torch.Tensor]) -> dict[str, torch.Tensor]: """ Forward pass of the TimeXer model. @@ -385,18 +319,8 @@ def forward(self, x: dict[str, torch.Tensor]) -> dict[str, torch.Tensor]: Returns: dict[str, torch.Tensor]: Model predictions. """ - # this is a feature mode, pre-computed using TslibBaseModel. - if self.features == "MS": - out = self._forecast(x) - elif self.features == "M": - out = self._forecast_multi(x) - else: - raise ValueError( - f"Unsupported features mode: {self.features}. " - "Supported modes are 'MS' for multivariate single series and 'M' for" - "multivariate multiple series." - ) + out = self._forecast(x) prediction = out[:, : self.prediction_length, :] # check to see if the output shape is equal to number of targets