From 563cb70f12b2c99358c3d8f19ddc231e3772e3ef Mon Sep 17 00:00:00 2001 From: pedroml Date: Fri, 5 Sep 2025 11:38:58 +0000 Subject: [PATCH] chronosx --- .github/ISSUE_TEMPLATE/bug_report.md | 33 - .github/ISSUE_TEMPLATE/config.yml | 8 - .github/workflows/ci.yml | 52 - .github/workflows/eval-model.yml | 37 - .github/workflows/publish-to-pypi.yml | 27 - CITATION.cff | 100 +- CODE_OF_CONDUCT.md | 2 +- CONTRIBUTING.md | 2 +- LICENSE | 2 +- NOTICE | 2 +- README.md | 126 +- ci/evaluate/backtest_config.yaml | 37 - figures/chronos-logo.png | Bin 120558 -> 0 bytes figures/main-figure.png | Bin 232294 -> 0 bytes figures/zero_shot-agg_scaled_score.svg | 4875 ----------------- ...loy-chronos-bolt-to-amazon-sagemaker.ipynb | 1003 ---- pyproject.toml | 34 +- scripts/.DS_Store | Bin 0 -> 6148 bytes scripts/README.md | 153 - scripts/evaluation/agg-relative-score.py | 60 - scripts/evaluation/configs/in-domain.yaml | 78 - scripts/evaluation/configs/zero-shot.yaml | 137 - scripts/evaluation/evaluate.py | 253 - .../chronos-bolt-base-agg-rel-scores.csv | 5 - .../results/chronos-bolt-base-in-domain.csv | 16 - .../results/chronos-bolt-base-zero-shot.csv | 28 - .../chronos-bolt-mini-agg-rel-scores.csv | 5 - .../results/chronos-bolt-mini-in-domain.csv | 16 - .../results/chronos-bolt-mini-zero-shot.csv | 28 - .../chronos-bolt-small-agg-rel-scores.csv | 5 - .../results/chronos-bolt-small-in-domain.csv | 16 - .../results/chronos-bolt-small-zero-shot.csv | 28 - .../chronos-bolt-tiny-agg-rel-scores.csv | 5 - .../results/chronos-bolt-tiny-in-domain.csv | 16 - .../results/chronos-bolt-tiny-zero-shot.csv | 28 - .../chronos-t5-base-agg-rel-scores.csv | 5 - .../results/chronos-t5-base-in-domain.csv | 16 - .../results/chronos-t5-base-zero-shot.csv | 28 - .../chronos-t5-large-agg-rel-scores.csv | 5 - .../results/chronos-t5-large-in-domain.csv | 16 - .../results/chronos-t5-large-zero-shot.csv | 28 - .../chronos-t5-mini-agg-rel-scores.csv | 5 - .../results/chronos-t5-mini-in-domain.csv | 16 - .../results/chronos-t5-mini-zero-shot.csv | 28 - .../chronos-t5-small-agg-rel-scores.csv | 5 - .../results/chronos-t5-small-in-domain.csv | 16 - .../results/chronos-t5-small-zero-shot.csv | 28 - .../chronos-t5-tiny-agg-rel-scores.csv | 5 - .../results/chronos-t5-tiny-in-domain.csv | 16 - .../results/chronos-t5-tiny-zero-shot.csv | 28 - .../results/seasonal-naive-in-domain.csv | 16 - .../results/seasonal-naive-zero-shot.csv | 28 - scripts/experiments/README.md | 8 + scripts/experiments/configs/datasets.yaml | 196 + .../configs/synthetic_datasets.yaml | 256 + scripts/experiments/example.py | 69 + .../experiment_with_real_datasets.py | 204 + .../experiments/synthetic_datasets/README.md | 6 + .../synthetic_datasets/constants.py | 222 + .../synthetic_datasets/covariates.py | 101 + .../experiment_with_synthetic_datasets.py | 213 + .../generate_synthetic_datasets.py | 62 + .../experiments/synthetic_datasets/target.py | 53 + scripts/kernel-synth.py | 200 - scripts/training/configs/chronos-gpt2.yaml | 35 - scripts/training/configs/chronos-t5-base.yaml | 35 - .../training/configs/chronos-t5-large.yaml | 35 - scripts/training/configs/chronos-t5-mini.yaml | 35 - .../training/configs/chronos-t5-small.yaml | 35 - scripts/training/configs/chronos-t5-tiny.yaml | 35 - scripts/training/train.py | 702 --- src/chronos/__init__.py | 24 - src/chronos/base.py | 164 - src/chronos/chronos.py | 582 -- src/chronos/chronos_bolt.py | 640 --- src/chronos/utils.py | 20 - src/chronosx/chronosx.py | 706 +++ src/chronosx/injection_blocks/__init__.py | 0 .../injection_blocks/basic_modules.py | 52 + .../injection_blocks/block_mapping.py | 8 + .../injection_blocks/input_injection_block.py | 42 + .../output_injection_block.py | 64 + src/chronosx/utils/__init__.py | 9 + src/chronosx/utils/chronos_dataset.py | 412 ++ src/chronosx/utils/epf.py | 56 + src/chronosx/utils/hf_data_loader.py | 92 + src/chronosx/utils/m5.py | 55 + src/chronosx/utils/prepare_covariates.py | 38 + src/chronosx/utils/transform.py | 83 + src/chronosx/utils/utils.py | 54 + test/__init__.py | 2 - test/dummy-chronos-bolt-model/config.json | 50 - .../model.safetensors | Bin 85408 -> 0 bytes test/dummy-chronos-model/config.json | 48 - .../generation_config.json | 7 - test/dummy-chronos-model/pytorch_model.bin | Bin 54975 -> 0 bytes test/test_chronos.py | 388 -- test/test_chronos_bolt.py | 301 - test/test_utils.py | 29 - test/util.py | 13 - 100 files changed, 3243 insertions(+), 10704 deletions(-) delete mode 100644 .github/ISSUE_TEMPLATE/bug_report.md delete mode 100644 .github/ISSUE_TEMPLATE/config.yml delete mode 100644 .github/workflows/ci.yml delete mode 100644 .github/workflows/eval-model.yml delete mode 100644 .github/workflows/publish-to-pypi.yml delete mode 100644 ci/evaluate/backtest_config.yaml delete mode 100644 figures/chronos-logo.png delete mode 100644 figures/main-figure.png delete mode 100644 figures/zero_shot-agg_scaled_score.svg delete mode 100644 notebooks/deploy-chronos-bolt-to-amazon-sagemaker.ipynb create mode 100644 scripts/.DS_Store delete mode 100644 scripts/README.md delete mode 100644 scripts/evaluation/agg-relative-score.py delete mode 100644 scripts/evaluation/configs/in-domain.yaml delete mode 100644 scripts/evaluation/configs/zero-shot.yaml delete mode 100644 scripts/evaluation/evaluate.py delete mode 100644 scripts/evaluation/results/chronos-bolt-base-agg-rel-scores.csv delete mode 100644 scripts/evaluation/results/chronos-bolt-base-in-domain.csv delete mode 100644 scripts/evaluation/results/chronos-bolt-base-zero-shot.csv delete mode 100644 scripts/evaluation/results/chronos-bolt-mini-agg-rel-scores.csv delete mode 100644 scripts/evaluation/results/chronos-bolt-mini-in-domain.csv delete mode 100644 scripts/evaluation/results/chronos-bolt-mini-zero-shot.csv delete mode 100644 scripts/evaluation/results/chronos-bolt-small-agg-rel-scores.csv delete mode 100644 scripts/evaluation/results/chronos-bolt-small-in-domain.csv delete mode 100644 scripts/evaluation/results/chronos-bolt-small-zero-shot.csv delete mode 100644 scripts/evaluation/results/chronos-bolt-tiny-agg-rel-scores.csv delete mode 100644 scripts/evaluation/results/chronos-bolt-tiny-in-domain.csv delete mode 100644 scripts/evaluation/results/chronos-bolt-tiny-zero-shot.csv delete mode 100644 scripts/evaluation/results/chronos-t5-base-agg-rel-scores.csv delete mode 100644 scripts/evaluation/results/chronos-t5-base-in-domain.csv delete mode 100644 scripts/evaluation/results/chronos-t5-base-zero-shot.csv delete mode 100644 scripts/evaluation/results/chronos-t5-large-agg-rel-scores.csv delete mode 100644 scripts/evaluation/results/chronos-t5-large-in-domain.csv delete mode 100644 scripts/evaluation/results/chronos-t5-large-zero-shot.csv delete mode 100644 scripts/evaluation/results/chronos-t5-mini-agg-rel-scores.csv delete mode 100644 scripts/evaluation/results/chronos-t5-mini-in-domain.csv delete mode 100644 scripts/evaluation/results/chronos-t5-mini-zero-shot.csv delete mode 100644 scripts/evaluation/results/chronos-t5-small-agg-rel-scores.csv delete mode 100644 scripts/evaluation/results/chronos-t5-small-in-domain.csv delete mode 100644 scripts/evaluation/results/chronos-t5-small-zero-shot.csv delete mode 100644 scripts/evaluation/results/chronos-t5-tiny-agg-rel-scores.csv delete mode 100644 scripts/evaluation/results/chronos-t5-tiny-in-domain.csv delete mode 100644 scripts/evaluation/results/chronos-t5-tiny-zero-shot.csv delete mode 100644 scripts/evaluation/results/seasonal-naive-in-domain.csv delete mode 100644 scripts/evaluation/results/seasonal-naive-zero-shot.csv create mode 100644 scripts/experiments/README.md create mode 100644 scripts/experiments/configs/datasets.yaml create mode 100644 scripts/experiments/configs/synthetic_datasets.yaml create mode 100644 scripts/experiments/example.py create mode 100644 scripts/experiments/experiment_with_real_datasets.py create mode 100644 scripts/experiments/synthetic_datasets/README.md create mode 100644 scripts/experiments/synthetic_datasets/constants.py create mode 100644 scripts/experiments/synthetic_datasets/covariates.py create mode 100644 scripts/experiments/synthetic_datasets/experiment_with_synthetic_datasets.py create mode 100644 scripts/experiments/synthetic_datasets/generate_synthetic_datasets.py create mode 100644 scripts/experiments/synthetic_datasets/target.py delete mode 100644 scripts/kernel-synth.py delete mode 100644 scripts/training/configs/chronos-gpt2.yaml delete mode 100644 scripts/training/configs/chronos-t5-base.yaml delete mode 100644 scripts/training/configs/chronos-t5-large.yaml delete mode 100644 scripts/training/configs/chronos-t5-mini.yaml delete mode 100644 scripts/training/configs/chronos-t5-small.yaml delete mode 100644 scripts/training/configs/chronos-t5-tiny.yaml delete mode 100644 scripts/training/train.py delete mode 100644 src/chronos/__init__.py delete mode 100644 src/chronos/base.py delete mode 100644 src/chronos/chronos.py delete mode 100644 src/chronos/chronos_bolt.py delete mode 100644 src/chronos/utils.py create mode 100644 src/chronosx/chronosx.py create mode 100644 src/chronosx/injection_blocks/__init__.py create mode 100644 src/chronosx/injection_blocks/basic_modules.py create mode 100644 src/chronosx/injection_blocks/block_mapping.py create mode 100644 src/chronosx/injection_blocks/input_injection_block.py create mode 100644 src/chronosx/injection_blocks/output_injection_block.py create mode 100644 src/chronosx/utils/__init__.py create mode 100644 src/chronosx/utils/chronos_dataset.py create mode 100644 src/chronosx/utils/epf.py create mode 100644 src/chronosx/utils/hf_data_loader.py create mode 100644 src/chronosx/utils/m5.py create mode 100644 src/chronosx/utils/prepare_covariates.py create mode 100644 src/chronosx/utils/transform.py create mode 100644 src/chronosx/utils/utils.py delete mode 100644 test/__init__.py delete mode 100644 test/dummy-chronos-bolt-model/config.json delete mode 100644 test/dummy-chronos-bolt-model/model.safetensors delete mode 100644 test/dummy-chronos-model/config.json delete mode 100644 test/dummy-chronos-model/generation_config.json delete mode 100644 test/dummy-chronos-model/pytorch_model.bin delete mode 100644 test/test_chronos.py delete mode 100644 test/test_chronos_bolt.py delete mode 100644 test/test_utils.py delete mode 100644 test/util.py diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md deleted file mode 100644 index f197c08..0000000 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ /dev/null @@ -1,33 +0,0 @@ ---- -name: Bug report -about: Create a report to help us reproduce and fix a bug -title: "[BUG]" -labels: ['bug'] -assignees: '' - ---- - -**Bug report checklist** - -- [ ] I provided code that demonstrates a minimal reproducible example. -- [ ] I confirmed bug exists on the latest mainline of Chronos via source install. - -**Describe the bug** - - -**Expected behavior** - - -**To reproduce** - - -**Environment description** -Operating system: -Python version: -CUDA version: -PyTorch version: -HuggingFace transformers version: -HuggingFace accelerate version: - diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml deleted file mode 100644 index d26a286..0000000 --- a/.github/ISSUE_TEMPLATE/config.yml +++ /dev/null @@ -1,8 +0,0 @@ -blank_issues_enabled: false -contact_links: - - name: Frequently asked questions - url: https://github.com/amazon-science/chronos-forecasting/issues?q=is%3Aissue+label%3AFAQ - about: Check the frequently asked questions before opening a new one - - name: Discussions - url: https://github.com/amazon-science/chronos-forecasting/discussions/new - about: Use this to ask questions and start discussions diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml deleted file mode 100644 index 4f5d42b..0000000 --- a/.github/workflows/ci.yml +++ /dev/null @@ -1,52 +0,0 @@ -name: CI - -on: - push: - branches: ["main"] # Run only on main branch - pull_request: - branches: ["**"] # Run on any branch - schedule: - - cron: "0 8 * * *" # Run at 8 AM UTC - -jobs: - type-check: - strategy: - max-parallel: 4 - fail-fast: false - matrix: - python-version: ["3.11"] - platform: [ubuntu-latest] - - runs-on: ${{ matrix.platform }} - - steps: - - uses: actions/checkout@v4 - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v5 - with: - python-version: ${{ matrix.python-version }} - - name: Install dependencies - run: pip install ".[typecheck]" -f https://download.pytorch.org/whl/cpu/torch_stable.html - - name: Type checks with mypy - run: mypy src test - - test: - strategy: - max-parallel: 4 - fail-fast: false - matrix: - python-version: ["3.9", "3.10", "3.11", "3.12"] - platform: [ubuntu-latest] - - runs-on: ${{ matrix.platform }} - - steps: - - uses: actions/checkout@v4 - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v5 - with: - python-version: ${{ matrix.python-version }} - - name: Install dependencies - run: pip install ".[test]" -f https://download.pytorch.org/whl/cpu/torch_stable.html - - name: Test with pytest - run: pytest diff --git a/.github/workflows/eval-model.yml b/.github/workflows/eval-model.yml deleted file mode 100644 index 991d186..0000000 --- a/.github/workflows/eval-model.yml +++ /dev/null @@ -1,37 +0,0 @@ -# Evaluates Chronos-Bolt (Small) model on selected datasets -name: Evaluate - -on: - # Runs only with read privilages for the GITHUB_TOKEN - pull_request: - branches: ["main"] # Run on PRs to main branch - types: - - opened # When a PR is created - - reopened # When a closed PR is reopened - - synchronize # When new commits are pushed to the PR - - labeled # When a label is added to the PR - -jobs: - evaluate-and-print: - if: contains(github.event.pull_request.labels.*.name, 'run-eval') # Only run if 'run-eval' label is added - runs-on: ubuntu-latest - env: - RESULTS_CSV: "eval-ci-metrics-${{ github.event.pull_request.number }}.csv" - - steps: - - name: Checkout Repository - uses: actions/checkout@v4 - - - name: Set up Python - uses: actions/setup-python@v5 - with: - python-version: '3.11' - - - name: Install Dependencies - run: pip install ".[evaluation]" -f https://download.pytorch.org/whl/cpu/torch_stable.html - - - name: Run Eval Script - run: python scripts/evaluation/evaluate.py ci/evaluate/backtest_config.yaml $RESULTS_CSV --chronos-model-id=amazon/chronos-bolt-small --device=cpu --torch-dtype=float32 - - - name: Print CSV - run: cat $RESULTS_CSV diff --git a/.github/workflows/publish-to-pypi.yml b/.github/workflows/publish-to-pypi.yml deleted file mode 100644 index b15306b..0000000 --- a/.github/workflows/publish-to-pypi.yml +++ /dev/null @@ -1,27 +0,0 @@ -name: Publish Python Package to PyPi - -on: - release: - types: [published] - -jobs: - deploy-to-pypi: - runs-on: ubuntu-latest - environment: release - permissions: - id-token: write - steps: - - uses: actions/checkout@v4 - - name: Set up Python - uses: actions/setup-python@v5 - with: - python-version: '3.11' - - name: Install dependencies - run: | - python -m pip install -U pip - python -m pip install setuptools wheel build - - name: Build package - run: | - python -m build - - name: Publish to PyPi - uses: pypa/gh-action-pypi-publish@release/v1 \ No newline at end of file diff --git a/CITATION.cff b/CITATION.cff index aaa56c3..bc9b39a 100644 --- a/CITATION.cff +++ b/CITATION.cff @@ -1,78 +1,62 @@ cff-version: 1.2.0 -title: "Chronos: Learning the Language of Time Series" +title: "ChronosX: Adapting Pretrained Time Series Models with Exogenous Variables" message: "If you find Chronos models useful for your research, please consider citing the associated paper." authors: + - family-names: Arango + given-names: Sebastian Pineda + - family-names: Mercado + given-names: Pedro + - family-names: Kapoor + given-names: Shubham - family-names: Ansari given-names: Abdul Fatir - family-names: Stella given-names: Lorenzo - - family-names: Turkmen - given-names: Caner - - family-names: Zhang - given-names: Xiyuan - - family-names: Mercado - given-names: Pedro - family-names: Shen given-names: Huibin + - family-names: Senetaire + given-names: Hugo + - family-names: Turkmen + given-names: Caner - family-names: Shchur given-names: Oleksandr - - family-names: Rangapuram - given-names: Syama Syndar - - family-names: Arango - given-names: Sebastian Pineda - - family-names: Kapoor - given-names: Shubham - - family-names: Zschiegner - given-names: Jasper - family-names: Maddix given-names: Danielle C. - - family-names: Mahoney - given-names: Michael W. - - family-names: Torkkola - given-names: Kari - - family-names: Wilson - given-names: Andrew Gordon - family-names: Bohlke-Schneider given-names: Michael - family-names: Wang given-names: Yuyang + - family-names: Rangapuram + given-names: Syama Syndar preferred-citation: type: article authors: - - family-names: Ansari - given-names: Abdul Fatir - - family-names: Stella - given-names: Lorenzo - - family-names: Turkmen - given-names: Caner - - family-names: Zhang - given-names: Xiyuan - - family-names: Mercado - given-names: Pedro - - family-names: Shen - given-names: Huibin - - family-names: Shchur - given-names: Oleksandr - - family-names: Rangapuram - given-names: Syama Syndar - - family-names: Arango - given-names: Sebastian Pineda - - family-names: Kapoor - given-names: Shubham - - family-names: Zschiegner - given-names: Jasper - - family-names: Maddix - given-names: Danielle C. - - family-names: Mahoney - given-names: Michael W. - - family-names: Torkkola - given-names: Kari - - family-names: Wilson - given-names: Andrew Gordon - - family-names: Bohlke-Schneider - given-names: Michael - - family-names: Wang - given-names: Yuyang - title: "Chronos: Learning the Language of Time Series" - journal: "arXiv preprint arXiv:2403.07815" - year: 2024 + - family-names: Arango + given-names: Sebastian Pineda + - family-names: Mercado + given-names: Pedro + - family-names: Kapoor + given-names: Shubham + - family-names: Ansari + given-names: Abdul Fatir + - family-names: Stella + given-names: Lorenzo + - family-names: Shen + given-names: Huibin + - family-names: Senetaire + given-names: Hugo + - family-names: Turkmen + given-names: Caner + - family-names: Shchur + given-names: Oleksandr + - family-names: Maddix + given-names: Danielle C. + - family-names: Bohlke-Schneider + given-names: Michael + - family-names: Wang + given-names: Yuyang + - family-names: Rangapuram + given-names: Syama Syndar + title: "ChronosX: Adapting Pretrained Time Series Models with Exogenous Variables" + journal: "arXiv preprint arXiv:2503.12107" + year: 2025 \ No newline at end of file diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index 5b627cf..a0ea08c 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -1,4 +1,4 @@ ## Code of Conduct This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact -opensource-codeofconduct@amazon.com with any additional questions or comments. +opensource-codeofconduct@amazon.com with any additional questions or comments. \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c4b6a1c..3a6fbda 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -56,4 +56,4 @@ If you discover a potential security issue in this project we ask that you notif ## Licensing -See the [LICENSE](LICENSE) file for our project's licensing. We will ask you to confirm the licensing of your contribution. +See the [LICENSE](LICENSE) file for our project's licensing. We will ask you to confirm the licensing of your contribution. \ No newline at end of file diff --git a/LICENSE b/LICENSE index 67db858..19dc35b 100644 --- a/LICENSE +++ b/LICENSE @@ -172,4 +172,4 @@ of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. + of your accepting any such warranty or additional liability. \ No newline at end of file diff --git a/NOTICE b/NOTICE index 616fc58..f48b352 100644 --- a/NOTICE +++ b/NOTICE @@ -1 +1 @@ -Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. \ No newline at end of file diff --git a/README.md b/README.md index 72b9d20..f95b662 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,124 @@ -
- # ChronosX: Adapting Pretrained Time Series Models with Exogenous Variables -## 🚀 Code soon to be released 🚀 -
+[![preprint](https://img.shields.io/static/v1?label=arXiv&message=2503.12107&color=B31B1B&logo=arXiv)](https://arxiv.org/abs/2503.12107) +[![License: MIT](https://img.shields.io/badge/License-Apache--2.0-green.svg)](https://opensource.org/licenses/Apache-2.0) + +This repository provides the forecasting model ChronosX introduced in the paper +[ChronosX: Adapting Pretrained Time Series Models with Exogenous Variables](https://arxiv.org/abs/2503.12107). + +## ✨ Introduction +ChronosX introduces a new method to incorporate covariates into pretrained time series forecasting models. ChronosX incorporates covariate information into pretrained forecasting models through modular blocks that inject past and future covariate information, without necessarily modifying the pretrained model in consideration. + +For further details on ChronosX please refer to the paper [ChronosX: Adapting Pretrained Time Series Models with Exogenous Variables](https://arxiv.org/abs/2503.12107). + +## 📈 Usage + +To perform inference with Chronos or Chronos-Bolt models, the easiest way is to install this package through `pip`: + +```sh +pip install git+https://github.com/amazon-science/chronos-forecasting.git@chronosx +``` + +### Forecasting + +A minimal example showing how to perform forecasting using ChronosX: + +```python +import numpy as np +import yaml + +from chronosx.chronosx import ChronosXPipeline +from chronosx.utils import ChronosDataset, load_and_split_dataset +from gluonts.ev.metrics import MASE, MeanWeightedSumQuantileLoss +from gluonts.model.evaluation import evaluate_forecasts +from pathlib import Path + + +config_path = "./scripts/experiments/configs/datasets.yaml" +output_dir = Path(f"./output/finetune") +output_dir.mkdir(exist_ok=True, parents=True) + +with open(config_path) as fp: + backtest_configs = yaml.safe_load(fp) + +dataset_config = backtest_configs[0] +prediction_length = dataset_config["prediction_length"] +num_covariates = 2 * len(dataset_config["covariates_fields"]) + + +# Load Chronos +pipeline = ChronosXPipeline( + pretrained_model_name_or_path="amazon/chronos-t5-small", + prediction_length=prediction_length, + num_covariates=num_covariates, +) + +# load Dataset +train_dataset, test_dataset = load_and_split_dataset(backtest_config=dataset_config) +quantized_train_dataset = ChronosDataset( + datasets=[train_dataset], + probabilities=[1.0], + tokenizer=pipeline.tokenizer, + prediction_length=prediction_length, + mode="training", +).shuffle() + +# fine tune model +_, save_model_path = pipeline.finetune( + output_dir, + quantized_train_dataset, + skip_pretrained_validation=True, +) + +# Evaluate fine tuned model +pipeline = ChronosXPipeline( + prediction_length=prediction_length, + num_covariates=num_covariates, + pretrained_model_name_or_path=output_dir / "final-checkpoint", +) + +pipeline.chronosx.eval() +forecasts = pipeline.generate_forecasts(test_dataset.input) + +metrics = ( + evaluate_forecasts( + forecasts, + test_data=test_dataset, + metrics=[ + MASE(), + MeanWeightedSumQuantileLoss(np.arange(0.05, 1, 0.05).round(2).tolist()), + ], + ) + .reset_index(drop=True) + .to_dict(orient="records") +) + +print(metrics) +``` + +### Experiments + +Scripts for experiments can be found in [this folder](./scripts/). + +## 📝 Citation + +If you find ChronosX useful for your research, please consider citing the associated [paper](https://arxiv.org/abs/2503.12107): + +``` +@inproceedings{ +arango2025chronosx, +title={ChronosX: Adapting Pretrained Time Series Models with Exogenous Variables}, +author={Sebastian Pineda Arango and Pedro Mercado and Shubham Kapoor and Abdul Fatir Ansari and Lorenzo Stella and Huibin Shen and Hugo Henri Joseph Senetaire and Ali Caner Turkmen and Oleksandr Shchur and Danielle C. Maddix and Bernie Wang and Michael Bohlke-Schneider and Syama Sundar Rangapuram}, +booktitle={The 28th International Conference on Artificial Intelligence and Statistics}, +year={2025}, +url={https://openreview.net/forum?id=f4nWNn0RjV} +} +``` + +## 🛡️ Security + +See [CONTRIBUTING](CONTRIBUTING.md#security-issue-notifications) for more information. + +## 📃 License + +This project is licensed under the Apache-2.0 License. \ No newline at end of file diff --git a/ci/evaluate/backtest_config.yaml b/ci/evaluate/backtest_config.yaml deleted file mode 100644 index 8843908..0000000 --- a/ci/evaluate/backtest_config.yaml +++ /dev/null @@ -1,37 +0,0 @@ -# From In-domain -- name: taxi_30min # 30 min - hf_repo: autogluon/chronos_datasets - offset: -48 - prediction_length: 48 - num_rolls: 1 -# From Zero-shot -- name: ETTh # Hourly - hf_repo: autogluon/chronos_datasets_extra - offset: -24 - prediction_length: 24 - num_rolls: 1 -- name: monash_covid_deaths # Daily - hf_repo: autogluon/chronos_datasets - offset: -30 - prediction_length: 30 - num_rolls: 1 -- name: monash_nn5_weekly # Weekly - hf_repo: autogluon/chronos_datasets - offset: -8 - prediction_length: 8 - num_rolls: 1 -- name: monash_fred_md # Monthly - hf_repo: autogluon/chronos_datasets - offset: -12 - prediction_length: 12 - num_rolls: 1 -- name: monash_m3_quarterly # Quarterly - hf_repo: autogluon/chronos_datasets - offset: -8 - prediction_length: 8 - num_rolls: 1 -- name: monash_tourism_yearly # Yearly - hf_repo: autogluon/chronos_datasets - offset: -4 - prediction_length: 4 - num_rolls: 1 \ No newline at end of file diff --git a/figures/chronos-logo.png b/figures/chronos-logo.png deleted file mode 100644 index fff5a42e83756a968fde3130b9221ef5766eb39b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 120558 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
    ZWb<(HR>)6@6rT$jJ$z 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 z�XojBm*KTER0)fw6)%FKCfVNWtAK>)l0Y8Ge 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%0D{i1pNKU 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%0Dwm&ScD%K-mSJe1?wbR^_DBSo1gV$-qs7%*R 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≶afi$@QhY50jyXA^CHdZnF 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 - - - - - - - 2024-11-26T17:40:11.488708 - image/svg+xml - - - Matplotlib v3.9.2, https://matplotlib.org/ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/notebooks/deploy-chronos-bolt-to-amazon-sagemaker.ipynb b/notebooks/deploy-chronos-bolt-to-amazon-sagemaker.ipynb deleted file mode 100644 index 486f0ae..0000000 --- a/notebooks/deploy-chronos-bolt-to-amazon-sagemaker.ipynb +++ /dev/null @@ -1,1003 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# SageMaker JumpStart - Deploy Chronos endpoints to AWS for production use" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "In this demo notebook, we will walk through the process of using the **SageMaker Python SDK** to deploy a **Chronos** model to a cloud endpoint on AWS. To simplify deployment, we will leverage **SageMaker JumpStart**.\n", - "\n", - "### Why Deploy to an Endpoint?\n", - "So far, we’ve seen how to run models locally, which is useful for experimentation. However, in a production setting, a forecasting model is typically just one component of a larger system. Running models locally doesn’t scale well and lacks the reliability needed for real-world applications.\n", - "\n", - "To address this, we deploy models as **endpoints** on AWS. An endpoint acts as a **hosted service**—we can send it requests (containing time series data), and it returns forecasts in response. This allows seamless integration into production workflows, ensuring scalability and real-time inference." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Deploy the model" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "First, update the SageMaker SDK to access the latest models:" - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "metadata": {}, - "outputs": [], - "source": [ - "!pip install -U -q sagemaker" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We create a `JumpStartModel` with the necessary configuration based on the model ID. The key parameters are:\n", - "- `model_id`: Specifies the model to use. Here, we choose the [Chronos-Bolt (Base)](https://huggingface.co/amazon/chronos-bolt-base) model. Currently, the following model IDs are supported:\n", - " - `autogluon-forecasting-chronos-bolt-base` - [Chronos-Bolt (Base)](https://huggingface.co/amazon/chronos-bolt-base).\n", - " - `autogluon-forecasting-chronos-bolt-small` - [Chronos-Bolt (Small)](https://huggingface.co/amazon/chronos-bolt-small).\n", - " - [Original Chronos models](https://huggingface.co/amazon/chronos-t5-small) in sizes `small`, `base` and `large` can be accessed, e.g., as `autogluon-forecasting-chronos-t5-small`. Note that these models require a GPU to run, are much slower and don't support covariates. Therefore, for most practical purposes we recommend using Chronos-Bolt models instead.\n", - "- `instance_type`: Defines the AWS instance for serving the endpoint. We use `ml.c5.2xlarge` to run the model on CPU. To use a GPU, select an instance like `ml.g5.2xlarge`, or choose other CPU options such as `ml.m5.xlarge` or `ml.m5.4xlarge`. You can check the pricing for different SageMaker instance types for real-time inference [here](https://aws.amazon.com/sagemaker-ai/pricing/).\n", - "\n", - "The `JumpStartModel` will automatically set the necessary attributes such as `image_uri` based on the chosen `model_id` and `instance_type`." - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "metadata": {}, - "outputs": [], - "source": [ - "from sagemaker.jumpstart.model import JumpStartModel\n", - "\n", - "model_id = \"autogluon-forecasting-chronos-bolt-base\"\n", - "\n", - "model = JumpStartModel(\n", - " model_id=model_id,\n", - " instance_type=\"ml.c5.2xlarge\",\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Next, we deploy the model and create an endpoint. Deployment typically takes a few minutes, as SageMaker provisions the instance, loads the model, and sets up the endpoint for inference.\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "predictor = model.deploy()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "> **Note:** Once the endpoint is deployed, it remains active and incurs charges on your AWS account until it is deleted. The cost depends on factors such as the instance type, the region where the endpoint is hosted, and the duration it remains running. To avoid unnecessary charges, make sure to delete the endpoint when it is no longer needed. For detailed pricing information, refer to the [SageMaker AI pricing page](https://aws.amazon.com/sagemaker-ai/pricing/).\n", - "\n", - "\n", - "If the previous step results in an error, you may need to update the model configuration. For example, specifying a `role` when creating the `JumpStartModel` ensures the necessary AWS resources are accessible." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# model = JumpStartModel(role=\"your-sagemaker-execution-role\", model_id=model_id, instance_type=\"ml.c5.2xlarge\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Alternatively, you can create a predictor for an existing endpoint." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# from sagemaker.predictor import retrieve_default\n", - "\n", - "# endpoint_name = \"NAME-OF-EXISTING-ENDPOINT\"\n", - "# predictor = retrieve_default(endpoint_name)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Querying the endpoint" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We can now invoke the endpoint to make a forecast. We send a **payload** to the endpoint, which includes historical time series values and configuration parameters, such as the prediction length. The endpoint processes this input and returns a **response** containing the forecasted values based on the provided data." - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": {}, - "outputs": [], - "source": [ - "# Define a utility function to print the response in a pretty format\n", - "from pprint import pformat\n", - "\n", - "\n", - "def nested_round(data, decimals=2):\n", - " \"\"\"Round numbers, including nested dicts and list.\"\"\"\n", - " if isinstance(data, float):\n", - " return round(data, decimals)\n", - " elif isinstance(data, list):\n", - " return [nested_round(item, decimals) for item in data]\n", - " elif isinstance(data, dict):\n", - " return {key: nested_round(value, decimals) for key, value in data.items()}\n", - " else:\n", - " return data\n", - "\n", - "\n", - "def pretty_format(data):\n", - " return pformat(nested_round(data), width=150, sort_dicts=False)" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "07605824", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "{'predictions': [{'mean': [-1.58, 0.52, 1.88, 1.39, -1.03, -3.34, -2.67, -0.64, 0.96, 1.59],\n", - " '0.1': [-4.17, -2.71, -1.7, -2.35, -4.79, -6.98, -6.59, -4.87, -3.45, -2.89],\n", - " '0.5': [-1.58, 0.52, 1.88, 1.39, -1.03, -3.34, -2.67, -0.64, 0.96, 1.59],\n", - " '0.9': [1.47, 4.47, 6.27, 5.98, 3.5, 1.11, 2.06, 4.47, 6.41, 7.17]}]}\n" - ] - } - ], - "source": [ - "payload = {\n", - " \"inputs\": [\n", - " {\"target\": [0.0, 4.0, 5.0, 1.5, -3.0, -5.0, -3.0, 1.5, 5.0, 4.0, 0.0, -4.0, -5.0, -1.5, 3.0, 5.0, 3.0, -1.5, -5.0, -4.0]},\n", - " ],\n", - " \"parameters\": {\n", - " \"prediction_length\": 10\n", - " }\n", - "}\n", - "response = predictor.predict(payload)\n", - "print(pretty_format(response))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "A payload may also contain **multiple time series**, potentially including `start` and `item_id` fields." - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "d476c397", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "{'predictions': [{'mean': [1.41, 1.5, 1.49, 1.45, 1.51],\n", - " '0.1': [0.12, -0.08, -0.25, -0.41, -0.45],\n", - " '0.5': [1.41, 1.5, 1.49, 1.45, 1.51],\n", - " '0.9': [3.29, 3.82, 4.09, 4.3, 4.56],\n", - " 'item_id': 'product_A',\n", - " 'start': '2024-01-01T10:00:00'},\n", - " {'mean': [-1.22, -1.3, -1.3, -1.14, -1.13],\n", - " '0.1': [-4.51, -5.48, -6.12, -6.5, -7.1],\n", - " '0.5': [-1.22, -1.3, -1.3, -1.14, -1.13],\n", - " '0.9': [2.84, 4.02, 4.92, 5.99, 6.79],\n", - " 'item_id': 'product_B',\n", - " 'start': '2024-02-02T10:00:00'}]}\n" - ] - } - ], - "source": [ - "payload = {\n", - " \"inputs\": [\n", - " {\n", - " \"target\": [1.0, 2.0, 3.0, 2.0, 0.5, 2.0, 3.0, 2.0, 1.0],\n", - " \"item_id\": \"product_A\",\n", - " \"start\": \"2024-01-01T01:00:00\",\n", - " },\n", - " {\n", - " \"target\": [5.4, 3.0, 3.0, 2.0, 1.5, 2.0, -1.0],\n", - " \"item_id\": \"product_B\",\n", - " \"start\": \"2024-02-02T03:00:00\",\n", - " },\n", - " ],\n", - " \"parameters\": {\n", - " \"prediction_length\": 5,\n", - " \"freq\": \"1h\",\n", - " \"quantile_levels\": [0.1, 0.5, 0.9],\n", - " \"batch_size\": 2,\n", - " },\n", - "}\n", - "response = predictor.predict(payload)\n", - "print(pretty_format(response))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Chronos-Bolt models also support forecasting with covariates (a.k.a. exogenous features or related time series). These can be provided using the `past_covariates` and `future_covariates` keys." - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "{'predictions': [{'mean': [1.41, 1.5, 1.49], '0.1': [0.12, -0.08, -0.25], '0.5': [1.41, 1.5, 1.49], '0.9': [3.29, 3.82, 4.09]},\n", - " {'mean': [-1.22, -1.3, -1.3], '0.1': [-4.51, -5.48, -6.12], '0.5': [-1.22, -1.3, -1.3], '0.9': [2.84, 4.02, 4.92]}]}\n" - ] - } - ], - "source": [ - "payload = {\n", - " \"inputs\": [\n", - " {\n", - " \"target\": [1.0, 2.0, 3.0, 2.0, 0.5, 2.0, 3.0, 2.0, 1.0],\n", - " # past_covariates must have the same length as \"target\"\n", - " \"past_covariates\": {\n", - " \"feat_1\": [3.0, 6.0, 9.0, 6.0, 1.5, 6.0, 9.0, 6.0, 3.0],\n", - " \"feat_2\": [\"A\", \"B\", \"B\", \"B\", \"A\", \"A\", \"A\", \"A\", \"B\"],\n", - " },\n", - " # future_covariates must have length equal to \"prediction_length\"\n", - " \"future_covariates\": {\n", - " \"feat_1\": [2.5, 2.2, 3.3],\n", - " \"feat_2\": [\"B\", \"A\", \"A\"],\n", - " },\n", - " },\n", - " {\n", - " \"target\": [5.4, 3.0, 3.0, 2.0, 1.5, 2.0, -1.0],\n", - " \"past_covariates\": {\n", - " \"feat_1\": [0.6, 1.2, 1.8, 1.2, 0.3, 1.2, 1.8],\n", - " \"feat_2\": [\"A\", \"B\", \"B\", \"B\", \"A\", \"A\", \"A\"],\n", - " },\n", - " \"future_covariates\": {\n", - " \"feat_1\": [1.2, 0.3, 4.4],\n", - " \"feat_2\": [\"A\", \"B\", \"A\"],\n", - " },\n", - " },\n", - " ],\n", - " \"parameters\": {\n", - " \"prediction_length\": 3,\n", - " \"quantile_levels\": [0.1, 0.5, 0.9],\n", - " },\n", - "}\n", - "response = predictor.predict(payload)\n", - "print(pretty_format(response))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Endpoint API\n", - "So far, we have explored several examples of querying the endpoint with different payload structures. Below is a comprehensive API specification detailing all supported parameters, their meanings, and how they affect the model’s predictions.\n", - "\n", - "* **inputs** (required): List with at most 1000 time series that need to be forecasted. Each time series is represented by a dictionary with the following keys:\n", - " * **target** (required): List of observed numeric time series values. \n", - " - It is recommended that each time series contains at least 30 observations.\n", - " - If any time series contains fewer than 5 observations, an error will be raised.\n", - " * **item_id**: String that uniquely identifies each time series. \n", - " - If provided, the ID must be unique for each time series.\n", - " - If provided, then the endpoint response will also include the **item_id** field for each forecast.\n", - " * **start**: Timestamp of the first time series observation in ISO format (`YYYY-MM-DD` or `YYYY-MM-DDThh:mm:ss`). \n", - " - If **start** field is provided, then **freq** must also be provided as part of **parameters**.\n", - " - If provided, then the endpoint response will also include the **start** field indicating the first timestamp of each forecast.\n", - " * **past_covariates**: Dictionary containing the past values of the covariates for this time series.\n", - " - If **past_covariates** field is provided, then **future_covariates** must be provided as well with the same keys.\n", - " - Each key in **past_covariates** correspond to the name of the covariate. Each value must be an array consisting of all-numeric or all-string values, with the length equal to the length of the **target**.\n", - " * **future_covariates**: Dictionary containing the future values of the covariates for this time series (values during the forecast horizon).\n", - " - If **future_covariates** field is provided, then **past_covariates** must be provided as well with the same keys.\n", - " - Each key in **future_covariates** correspond to the name of the covariate. Each value must be an array consisting of all-numeric or all-string values, with the length equal to **prediction_length**.\n", - " - If both **past_covariates** and **future_covariates** are provided, a regression model specified by **covariate_model** will be used to incorporate the covariate information into the forecast.\n", - "* **parameters**: Optional parameters to configure the model.\n", - " * **prediction_length**: Integer corresponding to the number of future time series values that need to be predicted. Defaults to `1`.\n", - " - Recommended to keep prediction_length <= 64 since larger values will result in inaccurate quantile forecasts. Values above 1000 will raise an error.\n", - " * **quantile_levels**: List of floats in range (0, 1) specifying which quantiles should should be included in the probabilistic forecast. Defaults to `[0.1, 0.5, 0.9]`. \n", - " - Note that Chronos-Bolt cannot produce quantiles outside the [0.1, 0.9] range (predictions outside the range will be clipped).\n", - " * **freq**: Frequency of the time series observations in [pandas-compatible format](https://pandas.pydata.org/pandas-docs/stable/user_guide/timeseries.html#offset-aliases). For example, `1h` for hourly data or `2W` for bi-weekly data. \n", - " - If **freq** is provided, then **start** must also be provided for each time series in **inputs**.\n", - " * **batch_size**: Number of time series processed in parallel by the model. Larger values speed up inference but may lead to out of memory errors. Defaults to `256`.\n", - " * **covariate_model**: Name of the tabular regression model applied to the covariates. Possible options: `GBM` (LightGBM), `LR` (linear regression), `RF` (random forest), `CAT` (CatBoost), `XGB` (XGBoost). Defaults to `GBM`.\n", - "\n", - "All keys not marked with (required) are optional.\n", - "\n", - "The endpoint response contains the probabilistic (quantile) forecast for each time series included in the request." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Working with long-format data frames" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The endpoint communicates using JSON format for both input and output. However, in practice, time series data is often stored in a **long-format data frame** (where each row represents a timestamp for a specific item).\n", - "\n", - "In the following example, we demonstrate how to:\n", - "\n", - "1. Convert a long-format data frame into the JSON payload format required by the endpoint.\n", - "2. Send the request and retrieve predictions.\n", - "3. Convert the response back into a long-format data frame for further analysis.\n", - "\n", - "First, we load an example dataset in long data frame format." - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "metadata": {}, - "outputs": [ - { - "data": { - "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", - "
    item_idtimestampscaled_pricepromotion_emailpromotion_homepageunit_sales
    01062_1012018-01-010.8791300.00.0636.0
    11062_1012018-01-080.9945170.00.0123.0
    21062_1012018-01-151.0055130.00.0391.0
    31062_1012018-01-221.0000000.00.0339.0
    41062_1012018-01-290.8833090.00.0661.0
    \n", - "
    " - ], - "text/plain": [ - " item_id timestamp scaled_price promotion_email promotion_homepage \\\n", - "0 1062_101 2018-01-01 0.879130 0.0 0.0 \n", - "1 1062_101 2018-01-08 0.994517 0.0 0.0 \n", - "2 1062_101 2018-01-15 1.005513 0.0 0.0 \n", - "3 1062_101 2018-01-22 1.000000 0.0 0.0 \n", - "4 1062_101 2018-01-29 0.883309 0.0 0.0 \n", - "\n", - " unit_sales \n", - "0 636.0 \n", - "1 123.0 \n", - "2 391.0 \n", - "3 339.0 \n", - "4 661.0 " - ] - }, - "execution_count": 6, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "import pandas as pd\n", - "\n", - "df = pd.read_csv(\n", - " \"https://autogluon.s3.amazonaws.com/datasets/timeseries/grocery_sales/test.csv\",\n", - " parse_dates=[\"timestamp\"],\n", - ")\n", - "df.head()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We split the data into two parts:\n", - "- Past data, including historic values of the target column and the covariates.\n", - "- Future data that contains the future values of the covariates during the forecast horizon." - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "metadata": {}, - "outputs": [], - "source": [ - "prediction_length = 8\n", - "target_col = \"unit_sales\"\n", - "freq = pd.infer_freq(df[df.item_id == df.item_id[0]][\"timestamp\"])\n", - "\n", - "past_df = df.groupby(\"item_id\").head(-prediction_length)\n", - "future_df = df.groupby(\"item_id\").tail(prediction_length).drop(columns=[target_col])" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "metadata": {}, - "outputs": [ - { - "data": { - "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", - "
    item_idtimestampscaled_pricepromotion_emailpromotion_homepageunit_sales
    01062_1012018-01-010.8791300.00.0636.0
    11062_1012018-01-080.9945170.00.0123.0
    21062_1012018-01-151.0055130.00.0391.0
    31062_1012018-01-221.0000000.00.0339.0
    41062_1012018-01-290.8833090.00.0661.0
    \n", - "
    " - ], - "text/plain": [ - " item_id timestamp scaled_price promotion_email promotion_homepage \\\n", - "0 1062_101 2018-01-01 0.879130 0.0 0.0 \n", - "1 1062_101 2018-01-08 0.994517 0.0 0.0 \n", - "2 1062_101 2018-01-15 1.005513 0.0 0.0 \n", - "3 1062_101 2018-01-22 1.000000 0.0 0.0 \n", - "4 1062_101 2018-01-29 0.883309 0.0 0.0 \n", - "\n", - " unit_sales \n", - "0 636.0 \n", - "1 123.0 \n", - "2 391.0 \n", - "3 339.0 \n", - "4 661.0 " - ] - }, - "execution_count": 8, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "past_df.head()" - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "metadata": {}, - "outputs": [ - { - "data": { - "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", - "
    item_idtimestampscaled_pricepromotion_emailpromotion_homepage
    231062_1012018-06-111.0054250.00.0
    241062_1012018-06-181.0054540.00.0
    251062_1012018-06-251.0000000.00.0
    261062_1012018-07-021.0055130.00.0
    271062_1012018-07-091.0000000.00.0
    \n", - "
    " - ], - "text/plain": [ - " item_id timestamp scaled_price promotion_email promotion_homepage\n", - "23 1062_101 2018-06-11 1.005425 0.0 0.0\n", - "24 1062_101 2018-06-18 1.005454 0.0 0.0\n", - "25 1062_101 2018-06-25 1.000000 0.0 0.0\n", - "26 1062_101 2018-07-02 1.005513 0.0 0.0\n", - "27 1062_101 2018-07-09 1.000000 0.0 0.0" - ] - }, - "execution_count": 9, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "future_df.head()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We can now convert this data into a JSON payload." - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "metadata": {}, - "outputs": [], - "source": [ - "def convert_df_to_payload(\n", - " past_df,\n", - " future_df=None,\n", - " prediction_length=1,\n", - " freq=\"D\",\n", - " target_col=\"target\",\n", - " id_col=\"item_id\",\n", - " timestamp_col=\"timestamp\",\n", - "):\n", - " \"\"\"\n", - " Converts past and future DataFrames into JSON payload format for the Chronos endpoint.\n", - "\n", - " Args:\n", - " past_df (pd.DataFrame): Historical data with `target_col`, `timestamp_col`, and `id_col`.\n", - " future_df (pd.DataFrame, optional): Future covariates with `timestamp_col` and `id_col`.\n", - " prediction_length (int): Number of future time steps to predict.\n", - " freq (str): Pandas-compatible frequency of the time series.\n", - " target_col (str): Column name for target values.\n", - " id_col (str): Column name for item IDs.\n", - " timestamp_col (str): Column name for timestamps.\n", - "\n", - " Returns:\n", - " dict: JSON payload formatted for the Chronos endpoint.\n", - " \"\"\"\n", - " past_df = past_df.sort_values([id_col, timestamp_col])\n", - " if future_df is not None:\n", - " future_df = future_df.sort_values([id_col, timestamp_col])\n", - "\n", - " covariate_cols = list(past_df.columns.drop([target_col, id_col, timestamp_col]))\n", - " if covariate_cols and (future_df is None or not set(covariate_cols).issubset(future_df.columns)):\n", - " raise ValueError(f\"If past_df contains covariates {covariate_cols}, they should also be present in future_df\")\n", - "\n", - " inputs = []\n", - " for item_id, past_group in past_df.groupby(id_col):\n", - " target_values = past_group[target_col].tolist()\n", - "\n", - " if len(target_values) < 5:\n", - " raise ValueError(f\"Time series '{item_id}' has fewer than 5 observations.\")\n", - "\n", - " series_dict = {\n", - " \"target\": target_values,\n", - " \"item_id\": str(item_id),\n", - " \"start\": past_group[timestamp_col].iloc[0].isoformat(),\n", - " }\n", - "\n", - " if covariate_cols:\n", - " series_dict[\"past_covariates\"] = past_group[covariate_cols].to_dict(orient=\"list\")\n", - " future_group = future_df[future_df[id_col] == item_id]\n", - " if len(future_group) != prediction_length:\n", - " raise ValueError(\n", - " f\"future_df must contain exactly {prediction_length=} values for each item_id from past_df \"\n", - " f\"(got {len(future_group)=}) for {item_id=}\"\n", - " )\n", - " series_dict[\"future_covariates\"] = future_group[covariate_cols].to_dict(orient=\"list\")\n", - "\n", - " inputs.append(series_dict)\n", - "\n", - "\n", - " return {\n", - " \"inputs\": inputs,\n", - " \"parameters\": {\"prediction_length\": prediction_length, \"freq\": freq},\n", - " }" - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "metadata": {}, - "outputs": [], - "source": [ - "payload = convert_df_to_payload(\n", - " past_df,\n", - " future_df,\n", - " prediction_length=prediction_length,\n", - " freq=freq,\n", - " target_col=\"unit_sales\",\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We can now send the payload to the endpoint." - ] - }, - { - "cell_type": "code", - "execution_count": 12, - "metadata": {}, - "outputs": [], - "source": [ - "response = predictor.predict(payload)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Note how Chronos-Bolt generated predictions for >300 time series in the dataset (with covariates!) in less than 2 seconds, even when running on a small CPU instance.\n", - "\n", - "Finally, we can convert the response back to a long-format data frame." - ] - }, - { - "cell_type": "code", - "execution_count": 13, - "metadata": {}, - "outputs": [], - "source": [ - "def convert_response_to_df(response, freq=\"D\"):\n", - " \"\"\"\n", - " Converts a JSON response from the Chronos endpoint into a long-format DataFrame.\n", - "\n", - " Args:\n", - " response (dict): JSON response containing forecasts.\n", - " freq (str): Pandas-compatible frequency of the time series.\n", - "\n", - " Returns:\n", - " pd.DataFrame: Long-format DataFrame with timestamps, item_id, and forecasted values.\n", - " \"\"\"\n", - " dfs = []\n", - " for forecast in response[\"predictions\"]:\n", - " forecast_df = pd.DataFrame(forecast).drop(columns=[\"start\"])\n", - " forecast_df[\"timestamp\"] = pd.date_range(forecast[\"start\"], freq=freq, periods=len(forecast_df))\n", - " dfs.append(forecast_df)\n", - " return pd.concat(dfs)" - ] - }, - { - "cell_type": "code", - "execution_count": 14, - "metadata": {}, - "outputs": [ - { - "data": { - "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", - "
    mean0.10.50.9item_idtimestamp
    0315.504037210.074945315.504037487.4844081062_1012018-06-11
    1315.364478200.272695315.364478508.1458501062_1012018-06-18
    2310.507265193.902630310.507265511.5597401062_1012018-06-25
    3317.322873200.051215317.322873525.0138301062_1012018-07-02
    4319.089405199.634549319.089405534.1025181062_1012018-07-09
    \n", - "
    " - ], - "text/plain": [ - " mean 0.1 0.5 0.9 item_id timestamp\n", - "0 315.504037 210.074945 315.504037 487.484408 1062_101 2018-06-11\n", - "1 315.364478 200.272695 315.364478 508.145850 1062_101 2018-06-18\n", - "2 310.507265 193.902630 310.507265 511.559740 1062_101 2018-06-25\n", - "3 317.322873 200.051215 317.322873 525.013830 1062_101 2018-07-02\n", - "4 319.089405 199.634549 319.089405 534.102518 1062_101 2018-07-09" - ] - }, - "execution_count": 14, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "forecast_df = convert_response_to_df(response, freq=freq)\n", - "forecast_df.head()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Clean up the endpoint\n", - "Don't forget to clean up resources when finished to avoid unnecessary charges." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "predictor.delete_predictor()" - ] - } - ], - "metadata": { - "instance_type": "ml.t3.medium", - "kernelspec": { - "display_name": "base", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.11.10" - } - }, - "nbformat": 4, - "nbformat_minor": 4 -} diff --git a/pyproject.toml b/pyproject.toml index c722699..042a162 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,19 +1,19 @@ [project] -name = "chronos-forecasting" -version = "1.5.0" +name = "chronosx" +version = "1.0.0" authors = [ - { name="Abdul Fatir Ansari", email="ansarnd@amazon.com" }, - { name="Lorenzo Stella", email="stellalo@amazon.com" }, - { name="Caner Turkmen", email="atturkm@amazon.com" }, + { name="Pedro Mercado", email="pedroml@amazon.com" } ] -description = "Chronos: Pretrained models for time series forecasting" +description = "ChronosX: Adapting Pretrained Time Series Models with Exogenous Variables" readme = "README.md" license = { file = "LICENSE" } -requires-python = ">=3.9" +requires-python = ">=3.10" dependencies = [ - "torch>=2.0,<3", # package was tested on 2.2 - "transformers>=4.48,<5", - "accelerate>=0.32,<1", + "chronos-forecasting[training]==1.5.0", + "transformers==4.47.1", + "accelerate==0.34.2", + "datasetsforecast==1.0.0", + "fev==0.5.0", ] classifiers = [ "Programming Language :: Python :: 3", @@ -27,18 +27,12 @@ requires = ["hatchling"] build-backend = "hatchling.build" [tool.hatch.build.targets.wheel] -packages = ["src/chronos"] - -[project.optional-dependencies] -test = ["pytest~=8.0", "numpy~=1.21"] -typecheck = ["mypy~=1.9"] -training = ["gluonts[pro]~=0.15", "numpy~=1.21", "datasets~=2.18", "typer", "typer-config", "joblib", "scikit-learn", "tensorboard"] -evaluation = ["gluonts[pro]~=0.15", "numpy~=1.21", "datasets~=2.18", "typer"] +packages = ["src/chronosx"] [project.urls] -Homepage = "https://github.com/amazon-science/chronos-forecasting" +Homepage = "https://www.amazon.science/publications/chronosx-adapting-pretrained-time-series-models-with-exogenous-variables" Issues = "https://github.com/amazon-science/chronos-forecasting/issues" -Paper = "https://arxiv.org/abs/2403.07815" +Paper = "https://arxiv.org/pdf/2503.12107" [tool.mypy] -ignore_missing_imports = true +ignore_missing_imports = true \ No newline at end of file diff --git a/scripts/.DS_Store b/scripts/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..4c60d183080ce40609f451a89357e7565bc4271d GIT binary patch literal 6148 zcmeHKJ5EDE3>-s>NNG}1?iIMfDhemy0tk{KQXq(cl=4-ai=#3A2qk(+gG7VIl0CbA zp10a5o}U5O;$w9S%mGa4j`;L2Hh=Cuva8A%kSU`4#o~0nzePE$Cn`z- zDR8R5b#CY0|L^D*=KoWYc2YnJ{3``)vb04xDE \ - --max-kernels - ``` - The generated time series will be saved in a [GluonTS](https://github.com/awslabs/gluonts)-comptabile arrow file `kernelsynth-data.arrow`. - -## Pretraining (and fine-tuning) Chronos models -- Install this package with with the `training` extra: - ``` - pip install "chronos-forecasting[training] @ git+https://github.com/amazon-science/chronos-forecasting.git" - ``` -- Convert your time series dataset into a GluonTS-compatible file dataset. We recommend using the arrow format. You may use the `convert_to_arrow` function from the following snippet for that. Optionally, you may use [synthetic data from KernelSynth](#generating-synthetic-time-series-kernelsynth) to follow along. - ```py - from pathlib import Path - from typing import List, Union - - import numpy as np - from gluonts.dataset.arrow import ArrowWriter - - - def convert_to_arrow( - path: Union[str, Path], - time_series: Union[List[np.ndarray], np.ndarray], - compression: str = "lz4", - ): - """ - Store a given set of series into Arrow format at the specified path. - - Input data can be either a list of 1D numpy arrays, or a single 2D - numpy array of shape (num_series, time_length). - """ - assert isinstance(time_series, list) or ( - isinstance(time_series, np.ndarray) and - time_series.ndim == 2 - ) - - # Set an arbitrary start time - start = np.datetime64("2000-01-01 00:00", "s") - - dataset = [ - {"start": start, "target": ts} for ts in time_series - ] - - ArrowWriter(compression=compression).write_to_file( - dataset, - path=path, - ) - - - if __name__ == "__main__": - # Generate 20 random time series of length 1024 - time_series = [np.random.randn(1024) for i in range(20)] - - # Convert to GluonTS arrow format - convert_to_arrow("./noise-data.arrow", time_series=time_series) - ``` -- Modify the [training configs](training/configs) to use your data. Let's use the KernelSynth data as an example. - ```yaml - # List of training data files - training_data_paths: - - "/path/to/kernelsynth-data.arrow" - # Mixing probability of each dataset file - probability: - - 1.0 - ``` - You may optionally change other parameters of the config file, as required. For instance, if you're interested in fine-tuning the model from a pretrained Chronos checkpoint, you should change the `model_id`, set `random_init: false`, and (optionally) change other parameters such as `max_steps` and `learning_rate`. -- Start the training (or fine-tuning) job: - ```sh - # On single GPU - CUDA_VISIBLE_DEVICES=0 python training/train.py --config /path/to/modified/config.yaml - - # On multiple GPUs (example with 8 GPUs) - torchrun --nproc-per-node=8 training/train.py --config /path/to/modified/config.yaml - - # Fine-tune `amazon/chronos-t5-small` for 1000 steps with initial learning rate of 1e-3 - CUDA_VISIBLE_DEVICES=0 python training/train.py --config /path/to/modified/config.yaml \ - --model-id amazon/chronos-t5-small \ - --no-random-init \ - --max-steps 1000 \ - --learning-rate 0.001 - ``` - The output and checkpoints will be saved in `output/run-{id}/`. -> [!TIP] -> If the initial training step is too slow, you might want to change the `shuffle_buffer_length` and/or set `torch_compile` to `false`. - -> [!IMPORTANT] -> When pretraining causal models (such as GPT2), the training script does [`LastValueImputation`](https://github.com/awslabs/gluonts/blob/f0f2266d520cb980f4c1ce18c28b003ad5cd2599/src/gluonts/transform/feature.py#L103) for missing values by default. If you pretrain causal models, please ensure that missing values are imputed similarly before passing the context tensor to `ChronosPipeline.predict()` for accurate results. -- (Optional) Once trained, you can easily push your fine-tuned model to HuggingFace🤗 Hub. Before that, do not forget to [create an access token](https://huggingface.co/settings/tokens) with **write permissions** and put it in `~/.cache/huggingface/token`. Here's a snippet that will push a fine-tuned model to HuggingFace🤗 Hub at `/chronos-t5-small-fine-tuned`. - ```py - from chronos import ChronosPipeline - - pipeline = ChronosPipeline.from_pretrained("/path/to/fine-tuned/model/ckpt/dir/") - pipeline.model.model.push_to_hub("chronos-t5-small-fine-tuned") - ``` - -## Evaluating Chronos models - -Follow these steps to compute the WQL and MASE values for the in-domain and zero-shot benchmarks in our paper. - -- Install this package with with the `evaluation` extra: - ``` - pip install "chronos-forecasting[evaluation] @ git+https://github.com/amazon-science/chronos-forecasting.git" - ``` -- Run the evaluation script: - ```sh - # In-domain evaluation - # Results will be saved in: evaluation/results/chronos-t5-small-in-domain.csv - python evaluation/evaluate.py evaluation/configs/in-domain.yaml evaluation/results/chronos-t5-small-in-domain.csv \ - --chronos-model-id "amazon/chronos-t5-small" \ - --batch-size=32 \ - --device=cuda:0 \ - --num-samples 20 - - # Zero-shot evaluation - # Results will be saved in: evaluation/results/chronos-t5-small-zero-shot.csv - python evaluation/evaluate.py evaluation/configs/zero-shot.yaml evaluation/results/chronos-t5-small-zero-shot.csv \ - --chronos-model-id "amazon/chronos-t5-small" \ - --batch-size=32 \ - --device=cuda:0 \ - --num-samples 20 - ``` -- Use the following snippet to compute the aggregated relative WQL and MASE scores: - ```py - import pandas as pd - from scipy.stats import gmean # requires: pip install scipy - - - def agg_relative_score(model_df: pd.DataFrame, baseline_df: pd.DataFrame): - relative_score = model_df.drop("model", axis="columns") / baseline_df.drop( - "model", axis="columns" - ) - return relative_score.agg(gmean) - - - result_df = pd.read_csv("evaluation/results/chronos-t5-small-in-domain.csv").set_index("dataset") - baseline_df = pd.read_csv("evaluation/results/seasonal-naive-in-domain.csv").set_index("dataset") - - agg_score_df = agg_relative_score(result_df, baseline_df) - ``` \ No newline at end of file diff --git a/scripts/evaluation/agg-relative-score.py b/scripts/evaluation/agg-relative-score.py deleted file mode 100644 index 0a308e7..0000000 --- a/scripts/evaluation/agg-relative-score.py +++ /dev/null @@ -1,60 +0,0 @@ -import pandas as pd -import typer -from scipy.stats import gmean -from pathlib import Path - -app = typer.Typer(pretty_exceptions_enable=False) -DEFAULT_RESULTS_DIR = Path(__file__).parent / "results" - - -def agg_relative_score(model_csv: Path, baseline_csv: Path): - model_df = pd.read_csv(model_csv).set_index("dataset") - baseline_df = pd.read_csv(baseline_csv).set_index("dataset") - relative_score = model_df.drop("model", axis="columns") / baseline_df.drop( - "model", axis="columns" - ) - return relative_score.agg(gmean) - - -@app.command() -def main( - model_name: str, - baseline_name: str = "seasonal-naive", - results_dir: Path = DEFAULT_RESULTS_DIR, -): - """ - Compute the aggregated relative score as reported in the Chronos paper. - Results will be saved to {results_dir}/{model_name}-agg-rel-scores.csv - - Parameters - ---------- - model_name : str - Name of the model used in the CSV files. The in-domain and zero-shot CSVs - are expected to be named {model_name}-in-domain.csv and {model_name}-zero-shot.csv. - results_dir : Path, optional, default = results/ - Directory where results CSVs generated by evaluate.py are stored - """ - - in_domain_agg_score_df = agg_relative_score( - results_dir / f"{model_name}-in-domain.csv", - results_dir / f"{baseline_name}-in-domain.csv", - ) - in_domain_agg_score_df.name = "value" - in_domain_agg_score_df.index.name = "metric" - - zero_shot_agg_score_df = agg_relative_score( - results_dir / f"{model_name}-zero-shot.csv", - results_dir / f"{baseline_name}-zero-shot.csv", - ) - zero_shot_agg_score_df.name = "value" - zero_shot_agg_score_df.index.name = "metric" - - agg_score_df = pd.concat( - {"in-domain": in_domain_agg_score_df, "zero-shot": zero_shot_agg_score_df}, - names=["benchmark"], - ) - agg_score_df.to_csv(f"{results_dir}/{model_name}-agg-rel-scores.csv") - - -if __name__ == "__main__": - app() diff --git a/scripts/evaluation/configs/in-domain.yaml b/scripts/evaluation/configs/in-domain.yaml deleted file mode 100644 index 119b22f..0000000 --- a/scripts/evaluation/configs/in-domain.yaml +++ /dev/null @@ -1,78 +0,0 @@ -# Backtest configs for the 15 "in-domain" datasets. -# The training portion of these datasets was part of the -# training corpus for Chronos models. -- name: electricity_15min - hf_repo: autogluon/chronos_datasets - offset: -5376 - prediction_length: 24 - num_rolls: 1 -- name: monash_electricity_hourly - hf_repo: autogluon/chronos_datasets - offset: -24 - prediction_length: 24 - num_rolls: 1 -- name: monash_electricity_weekly - hf_repo: autogluon/chronos_datasets - offset: -8 - prediction_length: 8 - num_rolls: 1 -- name: monash_kdd_cup_2018 - hf_repo: autogluon/chronos_datasets - offset: -48 - prediction_length: 48 - num_rolls: 1 -- name: m4_daily - hf_repo: autogluon/chronos_datasets - offset: -14 - prediction_length: 14 - num_rolls: 1 -- name: m4_hourly - hf_repo: autogluon/chronos_datasets - offset: -48 - prediction_length: 48 - num_rolls: 1 -- name: m4_monthly - hf_repo: autogluon/chronos_datasets - offset: -18 - prediction_length: 18 - num_rolls: 1 -- name: m4_weekly - hf_repo: autogluon/chronos_datasets - offset: -13 - prediction_length: 13 - num_rolls: 1 -- name: monash_pedestrian_counts - hf_repo: autogluon/chronos_datasets - offset: -48 - prediction_length: 48 - num_rolls: 1 -- name: taxi_30min - hf_repo: autogluon/chronos_datasets - offset: -48 - prediction_length: 48 - num_rolls: 1 -- name: uber_tlc_hourly - hf_repo: autogluon/chronos_datasets - offset: -24 - prediction_length: 24 - num_rolls: 1 -- name: uber_tlc_daily - hf_repo: autogluon/chronos_datasets - offset: -7 - prediction_length: 7 - num_rolls: 1 -- name: monash_rideshare - hf_repo: autogluon/chronos_datasets - offset: -24 - prediction_length: 24 - num_rolls: 1 -- name: monash_temperature_rain - hf_repo: autogluon/chronos_datasets - offset: -30 - prediction_length: 30 - num_rolls: 1 -- name: monash_london_smart_meters - hf_repo: autogluon/chronos_datasets - offset: -48 - prediction_length: 48 - num_rolls: 1 diff --git a/scripts/evaluation/configs/zero-shot.yaml b/scripts/evaluation/configs/zero-shot.yaml deleted file mode 100644 index e3cabd6..0000000 --- a/scripts/evaluation/configs/zero-shot.yaml +++ /dev/null @@ -1,137 +0,0 @@ -# Backtest configs for the 27 "zero-shot" datasets. -# These datasets were not seen by Chronos models during training. -- name: monash_traffic - hf_repo: autogluon/chronos_datasets - offset: -24 - prediction_length: 24 - num_rolls: 1 -- name: monash_australian_electricity - hf_repo: autogluon/chronos_datasets - offset: -48 - prediction_length: 48 - num_rolls: 1 -- name: ercot - hf_repo: autogluon/chronos_datasets - offset: -24 - prediction_length: 24 - num_rolls: 1 -- name: ETTm - hf_repo: autogluon/chronos_datasets_extra - offset: -96 - prediction_length: 24 - num_rolls: 1 -- name: ETTh - hf_repo: autogluon/chronos_datasets_extra - offset: -24 - prediction_length: 24 - num_rolls: 1 -- name: exchange_rate - hf_repo: autogluon/chronos_datasets - offset: -30 - prediction_length: 30 - num_rolls: 1 -- name: nn5 - hf_repo: autogluon/chronos_datasets - offset: -56 - prediction_length: 56 - num_rolls: 1 -- name: monash_nn5_weekly - hf_repo: autogluon/chronos_datasets - offset: -8 - prediction_length: 8 - num_rolls: 1 -- name: monash_weather - hf_repo: autogluon/chronos_datasets - offset: -30 - prediction_length: 30 - num_rolls: 1 -- name: monash_covid_deaths - hf_repo: autogluon/chronos_datasets - offset: -30 - prediction_length: 30 - num_rolls: 1 -- name: monash_fred_md - hf_repo: autogluon/chronos_datasets - offset: -12 - prediction_length: 12 - num_rolls: 1 -- name: m4_quarterly - hf_repo: autogluon/chronos_datasets - offset: -8 - prediction_length: 8 - num_rolls: 1 -- name: m4_yearly - hf_repo: autogluon/chronos_datasets - offset: -6 - prediction_length: 6 - num_rolls: 1 -- name: dominick - hf_repo: autogluon/chronos_datasets - offset: -8 - prediction_length: 8 - num_rolls: 1 -- name: m5 - hf_repo: autogluon/chronos_datasets - offset: -28 - prediction_length: 28 - num_rolls: 1 -- name: monash_tourism_monthly - hf_repo: autogluon/chronos_datasets - offset: -24 - prediction_length: 24 - num_rolls: 1 -- name: monash_tourism_quarterly - hf_repo: autogluon/chronos_datasets - offset: -8 - prediction_length: 8 - num_rolls: 1 -- name: monash_tourism_yearly - hf_repo: autogluon/chronos_datasets - offset: -4 - prediction_length: 4 - num_rolls: 1 -- name: monash_car_parts - hf_repo: autogluon/chronos_datasets - offset: -12 - prediction_length: 12 - num_rolls: 1 -- name: monash_hospital - hf_repo: autogluon/chronos_datasets - offset: -12 - prediction_length: 12 - num_rolls: 1 -- name: monash_cif_2016 - hf_repo: autogluon/chronos_datasets - offset: -12 - prediction_length: 12 - num_rolls: 1 -- name: monash_m1_yearly - hf_repo: autogluon/chronos_datasets - offset: -6 - prediction_length: 6 - num_rolls: 1 -- name: monash_m1_quarterly - hf_repo: autogluon/chronos_datasets - offset: -8 - prediction_length: 8 - num_rolls: 1 -- name: monash_m1_monthly - hf_repo: autogluon/chronos_datasets - offset: -18 - prediction_length: 18 - num_rolls: 1 -- name: monash_m3_monthly - hf_repo: autogluon/chronos_datasets - offset: -18 - prediction_length: 18 - num_rolls: 1 -- name: monash_m3_yearly - hf_repo: autogluon/chronos_datasets - offset: -6 - prediction_length: 6 - num_rolls: 1 -- name: monash_m3_quarterly - hf_repo: autogluon/chronos_datasets - offset: -8 - prediction_length: 8 - num_rolls: 1 \ No newline at end of file diff --git a/scripts/evaluation/evaluate.py b/scripts/evaluation/evaluate.py deleted file mode 100644 index 9c9acff..0000000 --- a/scripts/evaluation/evaluate.py +++ /dev/null @@ -1,253 +0,0 @@ -import logging -from pathlib import Path -from typing import Iterable, Optional - -import datasets -import numpy as np -import pandas as pd -import torch -import typer -import yaml -from gluonts.dataset.split import split -from gluonts.ev.metrics import MASE, MeanWeightedSumQuantileLoss -from gluonts.itertools import batcher -from gluonts.model.evaluation import evaluate_forecasts -from gluonts.model.forecast import QuantileForecast, SampleForecast -from tqdm.auto import tqdm - -from chronos import ( - BaseChronosPipeline, - ChronosBoltPipeline, - ChronosPipeline, - ForecastType, -) - -app = typer.Typer(pretty_exceptions_enable=False) - - -def to_gluonts_univariate(hf_dataset: datasets.Dataset): - series_fields = [ - col - for col in hf_dataset.features - if isinstance(hf_dataset.features[col], datasets.Sequence) - ] - series_fields.remove("timestamp") - dataset_length = hf_dataset.info.splits["train"].num_examples * len(series_fields) - - # Assumes that all time series in the dataset have the same frequency - dataset_freq = pd.DatetimeIndex(hf_dataset[0]["timestamp"]).to_period()[0].freqstr - - gts_dataset = [] - for hf_entry in hf_dataset: - for field in series_fields: - gts_dataset.append( - { - "start": pd.Period( - hf_entry["timestamp"][0], - freq=dataset_freq, - ), - "target": hf_entry[field], - } - ) - assert len(gts_dataset) == dataset_length - - return gts_dataset - - -def load_and_split_dataset(backtest_config: dict): - hf_repo = backtest_config["hf_repo"] - dataset_name = backtest_config["name"] - offset = backtest_config["offset"] - prediction_length = backtest_config["prediction_length"] - num_rolls = backtest_config["num_rolls"] - - # This is needed because the datasets in autogluon/chronos_datasets_extra cannot - # be distribued due to license restrictions and must be generated on the fly - trust_remote_code = True if hf_repo == "autogluon/chronos_datasets_extra" else False - - ds = datasets.load_dataset( - hf_repo, dataset_name, split="train", trust_remote_code=trust_remote_code - ) - ds.set_format("numpy") - - gts_dataset = to_gluonts_univariate(ds) - - # Split dataset for evaluation - _, test_template = split(gts_dataset, offset=offset) - test_data = test_template.generate_instances(prediction_length, windows=num_rolls) - - return test_data - - -def generate_forecasts( - test_data_input: Iterable, - pipeline: BaseChronosPipeline, - prediction_length: int, - batch_size: int, - **predict_kwargs, -): - # Generate forecasts - forecast_outputs = [] - for batch in tqdm(batcher(test_data_input, batch_size=batch_size)): - context = [torch.tensor(entry["target"]) for entry in batch] - forecast_outputs.append( - pipeline.predict( - context, - prediction_length=prediction_length, - **predict_kwargs, - ).numpy() - ) - forecast_outputs = np.concatenate(forecast_outputs) - - # Convert forecast samples into gluonts Forecast objects - forecasts = [] - for item, ts in zip(forecast_outputs, test_data_input): - forecast_start_date = ts["start"] + len(ts["target"]) - - if pipeline.forecast_type == ForecastType.SAMPLES: - forecasts.append( - SampleForecast(samples=item, start_date=forecast_start_date) - ) - elif pipeline.forecast_type == ForecastType.QUANTILES: - forecasts.append( - QuantileForecast( - forecast_arrays=item, - forecast_keys=list(map(str, pipeline.quantiles)), - start_date=forecast_start_date, - ) - ) - - return forecasts - - -@app.command() -def main( - config_path: Path, - metrics_path: Path, - chronos_model_id: str = "amazon/chronos-t5-small", - device: str = "cuda", - torch_dtype: str = "bfloat16", - batch_size: int = 32, - num_samples: int = 20, - temperature: Optional[float] = None, - top_k: Optional[int] = None, - top_p: Optional[float] = None, -): - """Evaluate Chronos models. - - Parameters - ---------- - config_path : Path - Path to the evaluation config. See ./configs/. - metrics_path : Path - Path to the CSV file where metrics will be saved. - chronos_model_id : str, optional, default = "amazon/chronos-t5-small" - HuggingFace ID of the Chronos model or local path - Available models on HuggingFace: - Chronos: - - amazon/chronos-t5-tiny - - amazon/chronos-t5-mini - - amazon/chronos-t5-small - - amazon/chronos-t5-base - - amazon/chronos-t5-large - Chronos-Bolt: - - amazon/chronos-bolt-tiny - - amazon/chronos-bolt-mini - - amazon/chronos-bolt-small - - amazon/chronos-bolt-base - device : str, optional, default = "cuda" - Device on which inference will be performed - torch_dtype : str, optional - Model's dtype, by default "bfloat16" - batch_size : int, optional, default = 32 - Batch size for inference. For Chronos-Bolt models, significantly larger - batch sizes can be used - num_samples : int, optional, default = 20 - Number of samples to draw when using the original Chronos models - temperature : Optional[float], optional, default = 1.0 - Softmax temperature to used for the original Chronos models - top_k : Optional[int], optional, default = 50 - Top-K sampling, by default None - top_p : Optional[float], optional, default = 1.0 - Top-p sampling, by default None - """ - if isinstance(torch_dtype, str): - torch_dtype = getattr(torch, torch_dtype) - assert isinstance(torch_dtype, torch.dtype) - - # Load Chronos - pipeline = BaseChronosPipeline.from_pretrained( - chronos_model_id, - device_map=device, - torch_dtype=torch_dtype, - ) - - if isinstance(pipeline, ChronosPipeline): - predict_kwargs = dict( - num_samples=num_samples, - temperature=temperature, - top_k=top_k, - top_p=top_p, - ) - elif isinstance(pipeline, ChronosBoltPipeline): - predict_kwargs = {} - - # Load backtest configs - with open(config_path) as fp: - backtest_configs = yaml.safe_load(fp) - - result_rows = [] - for config in backtest_configs: - dataset_name = config["name"] - prediction_length = config["prediction_length"] - - logger.info(f"Loading {dataset_name}") - test_data = load_and_split_dataset(backtest_config=config) - - logger.info( - f"Generating forecasts for {dataset_name} " - f"({len(test_data.input)} time series)" - ) - forecasts = generate_forecasts( - test_data.input, - pipeline=pipeline, - prediction_length=prediction_length, - batch_size=batch_size, - **predict_kwargs, - ) - - logger.info(f"Evaluating forecasts for {dataset_name}") - metrics = ( - evaluate_forecasts( - forecasts, - test_data=test_data, - metrics=[ - MASE(), - MeanWeightedSumQuantileLoss(np.arange(0.1, 1.0, 0.1)), - ], - batch_size=5000, - ) - .reset_index(drop=True) - .to_dict(orient="records") - ) - result_rows.append( - {"dataset": dataset_name, "model": chronos_model_id, **metrics[0]} - ) - - # Save results to a CSV file - results_df = ( - pd.DataFrame(result_rows) - .rename( - {"MASE[0.5]": "MASE", "mean_weighted_sum_quantile_loss": "WQL"}, - axis="columns", - ) - .sort_values(by="dataset") - ) - results_df.to_csv(metrics_path, index=False) - - -if __name__ == "__main__": - logging.basicConfig(format="%(asctime)s - %(name)s - %(levelname)s - %(message)s") - logger = logging.getLogger("Chronos Evaluation") - logger.setLevel(logging.INFO) - app() diff --git a/scripts/evaluation/results/chronos-bolt-base-agg-rel-scores.csv b/scripts/evaluation/results/chronos-bolt-base-agg-rel-scores.csv deleted file mode 100644 index 3912150..0000000 --- a/scripts/evaluation/results/chronos-bolt-base-agg-rel-scores.csv +++ /dev/null @@ -1,5 +0,0 @@ -benchmark,metric,value -in-domain,MASE,0.6800133628315155 -in-domain,WQL,0.5339263811489279 -zero-shot,MASE,0.7914551113353537 -zero-shot,WQL,0.6241424984163773 diff --git a/scripts/evaluation/results/chronos-bolt-base-in-domain.csv b/scripts/evaluation/results/chronos-bolt-base-in-domain.csv deleted file mode 100644 index abe3347..0000000 --- a/scripts/evaluation/results/chronos-bolt-base-in-domain.csv +++ /dev/null @@ -1,16 +0,0 @@ -dataset,model,MASE,WQL -electricity_15min,amazon/chronos-bolt-base,0.41069374835605243,0.0703533790998506 -m4_daily,amazon/chronos-bolt-base,3.205192517121196,0.02110308498174413 -m4_hourly,amazon/chronos-bolt-base,0.8350129849014075,0.025353803894164 -m4_monthly,amazon/chronos-bolt-base,0.9491758928362231,0.09382496106659234 -m4_weekly,amazon/chronos-bolt-base,2.0847827409162742,0.03816605075768161 -monash_electricity_hourly,amazon/chronos-bolt-base,1.254966217685461,0.09442192616975713 -monash_electricity_weekly,amazon/chronos-bolt-base,1.8391546050108039,0.06410971963960499 -monash_kdd_cup_2018,amazon/chronos-bolt-base,0.6405985809360102,0.2509172188706336 -monash_london_smart_meters,amazon/chronos-bolt-base,0.701398572604996,0.3218915088923906 -monash_pedestrian_counts,amazon/chronos-bolt-base,0.2646412642278343,0.18789459806066328 -monash_rideshare,amazon/chronos-bolt-base,0.7695376426829713,0.11637119433040358 -monash_temperature_rain,amazon/chronos-bolt-base,0.8983612698773724,0.6050555216496304 -taxi_30min,amazon/chronos-bolt-base,0.7688908266765317,0.2363178601205094 -uber_tlc_daily,amazon/chronos-bolt-base,0.8231767493519677,0.0926036406916842 -uber_tlc_hourly,amazon/chronos-bolt-base,0.6632193728217927,0.14987786887626975 diff --git a/scripts/evaluation/results/chronos-bolt-base-zero-shot.csv b/scripts/evaluation/results/chronos-bolt-base-zero-shot.csv deleted file mode 100644 index 833658a..0000000 --- a/scripts/evaluation/results/chronos-bolt-base-zero-shot.csv +++ /dev/null @@ -1,28 +0,0 @@ -dataset,model,MASE,WQL -ETTh,amazon/chronos-bolt-base,0.7479154031956647,0.07062173821055001 -ETTm,amazon/chronos-bolt-base,0.6334357237512225,0.052261607745858835 -dominick,amazon/chronos-bolt-base,0.8560272479913918,0.3453573743726445 -ercot,amazon/chronos-bolt-base,0.6933217425507392,0.02142183038021456 -exchange_rate,amazon/chronos-bolt-base,1.7095176257412634,0.01200682136751536 -m4_quarterly,amazon/chronos-bolt-base,1.2244670010522907,0.0771066518089854 -m4_yearly,amazon/chronos-bolt-base,3.513752058541554,0.12142798053483984 -m5,amazon/chronos-bolt-base,0.9152230096463854,0.561999688057527 -monash_australian_electricity,amazon/chronos-bolt-base,0.7403239930185613,0.03584034231329335 -monash_car_parts,amazon/chronos-bolt-base,0.8550263912438314,0.9945122291263591 -monash_cif_2016,amazon/chronos-bolt-base,0.9988541862779904,0.016456104842296485 -monash_covid_deaths,amazon/chronos-bolt-base,38.901749109066415,0.047410971217640714 -monash_fred_md,amazon/chronos-bolt-base,0.6468787708795645,0.04185083716355386 -monash_hospital,amazon/chronos-bolt-base,0.6883138394434054,0.057032869931903894 -monash_m1_monthly,amazon/chronos-bolt-base,1.0997677446267855,0.1392311148066238 -monash_m1_quarterly,amazon/chronos-bolt-base,1.7737851980875563,0.1007118219350403 -monash_m1_yearly,amazon/chronos-bolt-base,4.404672537832342,0.1504617654430952 -monash_m3_monthly,amazon/chronos-bolt-base,0.8510696834878182,0.09269673913736748 -monash_m3_quarterly,amazon/chronos-bolt-base,1.2890908822598466,0.07615133571216029 -monash_m3_yearly,amazon/chronos-bolt-base,2.9067097980770082,0.12934285625258413 -monash_nn5_weekly,amazon/chronos-bolt-base,0.9158766337957451,0.08352114810139548 -monash_tourism_monthly,amazon/chronos-bolt-base,1.5283388458731357,0.09026425492612797 -monash_tourism_quarterly,amazon/chronos-bolt-base,1.756127005530011,0.06448060953595125 -monash_tourism_yearly,amazon/chronos-bolt-base,3.691545772463519,0.16548820700844424 -monash_traffic,amazon/chronos-bolt-base,0.7843310867739336,0.23148632068725078 -monash_weather,amazon/chronos-bolt-base,0.8115247139672316,0.13350830777170594 -nn5,amazon/chronos-bolt-base,0.5764084996361287,0.1500519584148468 diff --git a/scripts/evaluation/results/chronos-bolt-mini-agg-rel-scores.csv b/scripts/evaluation/results/chronos-bolt-mini-agg-rel-scores.csv deleted file mode 100644 index 9fba001..0000000 --- a/scripts/evaluation/results/chronos-bolt-mini-agg-rel-scores.csv +++ /dev/null @@ -1,5 +0,0 @@ -benchmark,metric,value -in-domain,MASE,0.7268373301543752 -in-domain,WQL,0.565140251955324 -zero-shot,MASE,0.8221798917822493 -zero-shot,WQL,0.6441645845380903 diff --git a/scripts/evaluation/results/chronos-bolt-mini-in-domain.csv b/scripts/evaluation/results/chronos-bolt-mini-in-domain.csv deleted file mode 100644 index fac01bf..0000000 --- a/scripts/evaluation/results/chronos-bolt-mini-in-domain.csv +++ /dev/null @@ -1,16 +0,0 @@ -dataset,model,MASE,WQL -electricity_15min,amazon/chronos-bolt-mini,0.44185193304080733,0.0731477927531107 -m4_daily,amazon/chronos-bolt-mini,3.1342608828747456,0.0206872246743766 -m4_hourly,amazon/chronos-bolt-mini,0.9218285923038745,0.024383114886067574 -m4_monthly,amazon/chronos-bolt-mini,0.9628339921394529,0.09502498697494888 -m4_weekly,amazon/chronos-bolt-mini,2.2330452369879255,0.039393515325238534 -monash_electricity_hourly,amazon/chronos-bolt-mini,1.6195944363428718,0.11468972600782207 -monash_electricity_weekly,amazon/chronos-bolt-mini,1.866105365159433,0.06019900031840434 -monash_kdd_cup_2018,amazon/chronos-bolt-mini,0.74790954883436,0.3012661161484388 -monash_london_smart_meters,amazon/chronos-bolt-mini,0.7187830347765344,0.32984510693830227 -monash_pedestrian_counts,amazon/chronos-bolt-mini,0.308633944815819,0.23331301029432483 -monash_rideshare,amazon/chronos-bolt-mini,0.818948044410056,0.1297966960374544 -monash_temperature_rain,amazon/chronos-bolt-mini,0.9035244443682741,0.605031064086567 -taxi_30min,amazon/chronos-bolt-mini,0.812010120941363,0.25232294549917317 -uber_tlc_daily,amazon/chronos-bolt-mini,0.8507256206478295,0.10101757743084538 -uber_tlc_hourly,amazon/chronos-bolt-mini,0.6685484898085609,0.1515245941548974 diff --git a/scripts/evaluation/results/chronos-bolt-mini-zero-shot.csv b/scripts/evaluation/results/chronos-bolt-mini-zero-shot.csv deleted file mode 100644 index 2060004..0000000 --- a/scripts/evaluation/results/chronos-bolt-mini-zero-shot.csv +++ /dev/null @@ -1,28 +0,0 @@ -dataset,model,MASE,WQL -ETTh,amazon/chronos-bolt-mini,0.8057126710113404,0.07740387596411452 -ETTm,amazon/chronos-bolt-mini,0.6100793941108849,0.05129333450944573 -dominick,amazon/chronos-bolt-mini,0.8664152477208024,0.3499696999160997 -ercot,amazon/chronos-bolt-mini,0.6871250728215426,0.02448804863744021 -exchange_rate,amazon/chronos-bolt-mini,1.3520551553333662,0.00934663373172766 -m4_quarterly,amazon/chronos-bolt-mini,1.2569644266281508,0.07833787023275976 -m4_yearly,amazon/chronos-bolt-mini,3.7611003052413796,0.12931927951165456 -m5,amazon/chronos-bolt-mini,0.9188876472137485,0.5661303206519673 -monash_australian_electricity,amazon/chronos-bolt-mini,0.8823559450287066,0.04493688824488474 -monash_car_parts,amazon/chronos-bolt-mini,0.8604081423647779,1.0041876404811494 -monash_cif_2016,amazon/chronos-bolt-mini,1.0762361363763873,0.017641893717784202 -monash_covid_deaths,amazon/chronos-bolt-mini,38.83915011538576,0.06098317835750057 -monash_fred_md,amazon/chronos-bolt-mini,0.6169859211923081,0.03256236965040934 -monash_hospital,amazon/chronos-bolt-mini,0.6924431064606051,0.05766349075348645 -monash_m1_monthly,amazon/chronos-bolt-mini,1.147893030263777,0.13270222658510553 -monash_m1_quarterly,amazon/chronos-bolt-mini,1.8662100001165818,0.09846363409254102 -monash_m1_yearly,amazon/chronos-bolt-mini,5.319154632748303,0.16167328827180308 -monash_m3_monthly,amazon/chronos-bolt-mini,0.8758452776118432,0.09493431248614057 -monash_m3_quarterly,amazon/chronos-bolt-mini,1.3555175243802005,0.07808062465932723 -monash_m3_yearly,amazon/chronos-bolt-mini,3.605769430055575,0.15711010456482008 -monash_nn5_weekly,amazon/chronos-bolt-mini,0.9347141924977239,0.08522899825844342 -monash_tourism_monthly,amazon/chronos-bolt-mini,1.649587479665881,0.0979648261309891 -monash_tourism_quarterly,amazon/chronos-bolt-mini,1.8471553663088986,0.06501077791766902 -monash_tourism_yearly,amazon/chronos-bolt-mini,3.9932920493826245,0.1743539122097316 -monash_traffic,amazon/chronos-bolt-mini,0.8355442361271347,0.24351051123330386 -monash_weather,amazon/chronos-bolt-mini,0.800013628350165,0.13041050756802045 -nn5,amazon/chronos-bolt-mini,0.611917632501032,0.1570111102680171 diff --git a/scripts/evaluation/results/chronos-bolt-small-agg-rel-scores.csv b/scripts/evaluation/results/chronos-bolt-small-agg-rel-scores.csv deleted file mode 100644 index 0652750..0000000 --- a/scripts/evaluation/results/chronos-bolt-small-agg-rel-scores.csv +++ /dev/null @@ -1,5 +0,0 @@ -benchmark,metric,value -in-domain,MASE,0.7030801652116672 -in-domain,WQL,0.5443547623341555 -zero-shot,MASE,0.8192127745093378 -zero-shot,WQL,0.6356097843099521 diff --git a/scripts/evaluation/results/chronos-bolt-small-in-domain.csv b/scripts/evaluation/results/chronos-bolt-small-in-domain.csv deleted file mode 100644 index 7c17064..0000000 --- a/scripts/evaluation/results/chronos-bolt-small-in-domain.csv +++ /dev/null @@ -1,16 +0,0 @@ -dataset,model,MASE,WQL -electricity_15min,amazon/chronos-bolt-small,0.44920089250026723,0.08115291306964295 -m4_daily,amazon/chronos-bolt-small,3.201966619014735,0.02143368277732494 -m4_hourly,amazon/chronos-bolt-small,0.8686298207618999,0.020368729287465817 -m4_monthly,amazon/chronos-bolt-small,0.9537717737278778,0.0939247807527992 -m4_weekly,amazon/chronos-bolt-small,2.1236755094789177,0.03785184715517262 -monash_electricity_hourly,amazon/chronos-bolt-small,1.3728906161330452,0.09452411472431674 -monash_electricity_weekly,amazon/chronos-bolt-small,1.8703239487242378,0.06648479071326366 -monash_kdd_cup_2018,amazon/chronos-bolt-small,0.6458631909979771,0.25148489931571666 -monash_london_smart_meters,amazon/chronos-bolt-small,0.7126939688565166,0.326874529903459 -monash_pedestrian_counts,amazon/chronos-bolt-small,0.3015070035798365,0.2285590441093863 -monash_rideshare,amazon/chronos-bolt-small,0.823726965741684,0.12409769473500927 -monash_temperature_rain,amazon/chronos-bolt-small,0.8980348827836525,0.5984819599873311 -taxi_30min,amazon/chronos-bolt-small,0.7597818149895785,0.2348569752311862 -uber_tlc_daily,amazon/chronos-bolt-small,0.8460854328036702,0.09666483354735897 -uber_tlc_hourly,amazon/chronos-bolt-small,0.6662547495017634,0.1524256346268063 diff --git a/scripts/evaluation/results/chronos-bolt-small-zero-shot.csv b/scripts/evaluation/results/chronos-bolt-small-zero-shot.csv deleted file mode 100644 index e9f4134..0000000 --- a/scripts/evaluation/results/chronos-bolt-small-zero-shot.csv +++ /dev/null @@ -1,28 +0,0 @@ -dataset,model,MASE,WQL -ETTh,amazon/chronos-bolt-small,0.792521748651108,0.07590654063011319 -ETTm,amazon/chronos-bolt-small,0.6209623928936988,0.05056189722606397 -dominick,amazon/chronos-bolt-small,0.8706134610400587,0.34811141409475416 -ercot,amazon/chronos-bolt-small,0.7562857616685997,0.02596064260343696 -exchange_rate,amazon/chronos-bolt-small,1.774835301692689,0.011363548847621512 -m4_quarterly,amazon/chronos-bolt-small,1.2478142413437487,0.07808795122806232 -m4_yearly,amazon/chronos-bolt-small,3.6925595655002574,0.12772564181388502 -m5,amazon/chronos-bolt-small,0.9195435643571084,0.5668430814831332 -monash_australian_electricity,amazon/chronos-bolt-small,0.8128424798841111,0.041509852162861564 -monash_car_parts,amazon/chronos-bolt-small,0.8584574663781737,1.0074689402521324 -monash_cif_2016,amazon/chronos-bolt-small,1.0182471909074982,0.01581964877692293 -monash_covid_deaths,amazon/chronos-bolt-small,36.467595559655145,0.0427382859406882 -monash_fred_md,amazon/chronos-bolt-small,0.6132863794635253,0.03730410577241995 -monash_hospital,amazon/chronos-bolt-small,0.6954489513780618,0.058119864671526154 -monash_m1_monthly,amazon/chronos-bolt-small,1.1277621848099244,0.1335656174632902 -monash_m1_quarterly,amazon/chronos-bolt-small,1.8356144904231688,0.09363028483838018 -monash_m1_yearly,amazon/chronos-bolt-small,5.098146069746402,0.15669928873371905 -monash_m3_monthly,amazon/chronos-bolt-small,0.8685125121306435,0.09396568468255145 -monash_m3_quarterly,amazon/chronos-bolt-small,1.3269103591066727,0.07691022995374203 -monash_m3_yearly,amazon/chronos-bolt-small,3.40993282700627,0.1547639821304127 -monash_nn5_weekly,amazon/chronos-bolt-small,0.9266513350636507,0.08452821221908001 -monash_tourism_monthly,amazon/chronos-bolt-small,1.6106732721197876,0.09362336754317802 -monash_tourism_quarterly,amazon/chronos-bolt-small,1.8357819365308639,0.06734337535269994 -monash_tourism_yearly,amazon/chronos-bolt-small,3.8963100495394194,0.16766064312072784 -monash_traffic,amazon/chronos-bolt-small,0.8598507749866499,0.25173786112983054 -monash_weather,amazon/chronos-bolt-small,0.8020408743877911,0.13258563963844888 -nn5,amazon/chronos-bolt-small,0.5833047644729239,0.15066847836762787 diff --git a/scripts/evaluation/results/chronos-bolt-tiny-agg-rel-scores.csv b/scripts/evaluation/results/chronos-bolt-tiny-agg-rel-scores.csv deleted file mode 100644 index 72e98ec..0000000 --- a/scripts/evaluation/results/chronos-bolt-tiny-agg-rel-scores.csv +++ /dev/null @@ -1,5 +0,0 @@ -benchmark,metric,value -in-domain,MASE,0.7403252781013574 -in-domain,WQL,0.5733728165523524 -zero-shot,MASE,0.8445407343705457 -zero-shot,WQL,0.6678781905023173 diff --git a/scripts/evaluation/results/chronos-bolt-tiny-in-domain.csv b/scripts/evaluation/results/chronos-bolt-tiny-in-domain.csv deleted file mode 100644 index bf2ffb5..0000000 --- a/scripts/evaluation/results/chronos-bolt-tiny-in-domain.csv +++ /dev/null @@ -1,16 +0,0 @@ -dataset,model,MASE,WQL -electricity_15min,amazon/chronos-bolt-tiny,0.4676384089765091,0.0861229808117837 -m4_daily,amazon/chronos-bolt-tiny,3.1789994761356795,0.020961883512815756 -m4_hourly,amazon/chronos-bolt-tiny,0.9348005698736752,0.021087527284114574 -m4_monthly,amazon/chronos-bolt-tiny,0.965298729632761,0.0950380483243082 -m4_weekly,amazon/chronos-bolt-tiny,2.261575511029903,0.04093653263178429 -monash_electricity_hourly,amazon/chronos-bolt-tiny,1.5739346351263623,0.10808418398945202 -monash_electricity_weekly,amazon/chronos-bolt-tiny,1.8628689103722829,0.05773335283584782 -monash_kdd_cup_2018,amazon/chronos-bolt-tiny,0.6869549985391232,0.28012801092758166 -monash_london_smart_meters,amazon/chronos-bolt-tiny,0.7284234905933779,0.33496438244693033 -monash_pedestrian_counts,amazon/chronos-bolt-tiny,0.32338947321773864,0.2530637833749087 -monash_rideshare,amazon/chronos-bolt-tiny,0.8562780835002918,0.1304317657933891 -monash_temperature_rain,amazon/chronos-bolt-tiny,0.9030707620825977,0.6064087080755548 -taxi_30min,amazon/chronos-bolt-tiny,0.9122159603256838,0.28002194370731626 -uber_tlc_daily,amazon/chronos-bolt-tiny,0.9087055420190513,0.11193388685815164 -uber_tlc_hourly,amazon/chronos-bolt-tiny,0.6716569179590032,0.15310845458208555 diff --git a/scripts/evaluation/results/chronos-bolt-tiny-zero-shot.csv b/scripts/evaluation/results/chronos-bolt-tiny-zero-shot.csv deleted file mode 100644 index 4ec181e..0000000 --- a/scripts/evaluation/results/chronos-bolt-tiny-zero-shot.csv +++ /dev/null @@ -1,28 +0,0 @@ -dataset,model,MASE,WQL -ETTh,amazon/chronos-bolt-tiny,0.7941225847155844,0.07480860969990633 -ETTm,amazon/chronos-bolt-tiny,0.6508270995240056,0.05440068825429993 -dominick,amazon/chronos-bolt-tiny,0.876060127216559,0.35175949052933253 -ercot,amazon/chronos-bolt-tiny,0.7309134980173839,0.02468604544464515 -exchange_rate,amazon/chronos-bolt-tiny,1.6857262567077134,0.011477224264784112 -m4_quarterly,amazon/chronos-bolt-tiny,1.2605908919338378,0.0789049420017836 -m4_yearly,amazon/chronos-bolt-tiny,3.7118394116161757,0.1286932555969197 -m5,amazon/chronos-bolt-tiny,0.9195469670062033,0.5634881835998845 -monash_australian_electricity,amazon/chronos-bolt-tiny,0.8419304693259403,0.042040993880313904 -monash_car_parts,amazon/chronos-bolt-tiny,0.8625579150452282,1.0009987800801836 -monash_cif_2016,amazon/chronos-bolt-tiny,1.095219642027011,0.017550336784241796 -monash_covid_deaths,amazon/chronos-bolt-tiny,40.674057986280744,0.06723714516685976 -monash_fred_md,amazon/chronos-bolt-tiny,0.6127387450520702,0.04747523852271518 -monash_hospital,amazon/chronos-bolt-tiny,0.6980246281225624,0.05864223243167421 -monash_m1_monthly,amazon/chronos-bolt-tiny,1.1625495971731141,0.13142237467151166 -monash_m1_quarterly,amazon/chronos-bolt-tiny,1.8941765599193754,0.09972207844232561 -monash_m1_yearly,amazon/chronos-bolt-tiny,5.136332694531757,0.160331813128038 -monash_m3_monthly,amazon/chronos-bolt-tiny,0.8744553726704598,0.09435519378597752 -monash_m3_quarterly,amazon/chronos-bolt-tiny,1.364563776692303,0.07875066385737857 -monash_m3_yearly,amazon/chronos-bolt-tiny,3.3685961410254928,0.15158076519486274 -monash_nn5_weekly,amazon/chronos-bolt-tiny,0.9324436794013877,0.0847385189968909 -monash_tourism_monthly,amazon/chronos-bolt-tiny,1.7895936775088157,0.1058167042693116 -monash_tourism_quarterly,amazon/chronos-bolt-tiny,2.095262637810499,0.0710732570354461 -monash_tourism_yearly,amazon/chronos-bolt-tiny,4.042821441327848,0.172613367251472 -monash_traffic,amazon/chronos-bolt-tiny,0.8836032533767518,0.2574297134210491 -monash_weather,amazon/chronos-bolt-tiny,0.8005348255663177,0.13111355494466076 -nn5,amazon/chronos-bolt-tiny,0.7228248498869763,0.1816913098894226 diff --git a/scripts/evaluation/results/chronos-t5-base-agg-rel-scores.csv b/scripts/evaluation/results/chronos-t5-base-agg-rel-scores.csv deleted file mode 100644 index 56492d3..0000000 --- a/scripts/evaluation/results/chronos-t5-base-agg-rel-scores.csv +++ /dev/null @@ -1,5 +0,0 @@ -benchmark,metric,value -in-domain,MASE,0.7007558507277635 -in-domain,WQL,0.5786300105297922 -zero-shot,MASE,0.8155209321160994 -zero-shot,WQL,0.6424634919486323 diff --git a/scripts/evaluation/results/chronos-t5-base-in-domain.csv b/scripts/evaluation/results/chronos-t5-base-in-domain.csv deleted file mode 100644 index 7925271..0000000 --- a/scripts/evaluation/results/chronos-t5-base-in-domain.csv +++ /dev/null @@ -1,16 +0,0 @@ -dataset,model,MASE,WQL -electricity_15min,amazon/chronos-t5-base,0.39879754957261204,0.07738953262286181 -m4_daily,amazon/chronos-t5-base,3.160575865614404,0.02194256368254537 -m4_hourly,amazon/chronos-t5-base,0.6938747745332102,0.026354948301302205 -m4_monthly,amazon/chronos-t5-base,0.971951848755026,0.10355213196432872 -m4_weekly,amazon/chronos-t5-base,2.0143841267657945,0.03639741235815474 -monash_electricity_hourly,amazon/chronos-t5-base,1.5717251971297332,0.1078882125804548 -monash_electricity_weekly,amazon/chronos-t5-base,1.7862927210886668,0.06255982783148449 -monash_kdd_cup_2018,amazon/chronos-t5-base,0.6335225775496138,0.2684272353843692 -monash_london_smart_meters,amazon/chronos-t5-base,0.8362014889190201,0.4265549499082726 -monash_pedestrian_counts,amazon/chronos-t5-base,0.2817708325561419,0.20810108090665583 -monash_rideshare,amazon/chronos-t5-base,0.8614480533175364,0.1356591190888703 -monash_temperature_rain,amazon/chronos-t5-base,0.9692405156151607,0.660155448791624 -taxi_30min,amazon/chronos-t5-base,0.8186287575356217,0.26236060366367003 -uber_tlc_daily,amazon/chronos-t5-base,0.8338648311528079,0.0970875577681834 -uber_tlc_hourly,amazon/chronos-t5-base,0.6647193438331641,0.15436646659512512 diff --git a/scripts/evaluation/results/chronos-t5-base-zero-shot.csv b/scripts/evaluation/results/chronos-t5-base-zero-shot.csv deleted file mode 100644 index 6078311..0000000 --- a/scripts/evaluation/results/chronos-t5-base-zero-shot.csv +++ /dev/null @@ -1,28 +0,0 @@ -dataset,model,MASE,WQL -ETTh,amazon/chronos-t5-base,0.7653491494991778,0.08087267701042929 -ETTm,amazon/chronos-t5-base,0.7737006634032871,0.07008650633028274 -dominick,amazon/chronos-t5-base,0.8194044957573132,0.33201307438298133 -ercot,amazon/chronos-t5-base,0.5014399265038706,0.013589435745554596 -exchange_rate,amazon/chronos-t5-base,2.055616906406159,0.011066070028466317 -m4_quarterly,amazon/chronos-t5-base,1.2253036947743137,0.08327936201395683 -m4_yearly,amazon/chronos-t5-base,3.639991540990927,0.13539258375263963 -m5,amazon/chronos-t5-base,0.9391874615167101,0.5867234116216755 -monash_australian_electricity,amazon/chronos-t5-base,1.2944069383163321,0.07070604202031877 -monash_car_parts,amazon/chronos-t5-base,0.9071940271035218,1.077797124337994 -monash_cif_2016,amazon/chronos-t5-base,0.9840747802099565,0.011825556826558836 -monash_covid_deaths,amazon/chronos-t5-base,42.68503365359237,0.042229910495746356 -monash_fred_md,amazon/chronos-t5-base,0.4857773806790164,0.021204829049512715 -monash_hospital,amazon/chronos-t5-base,0.7053005021431749,0.05630687524507516 -monash_m1_monthly,amazon/chronos-t5-base,1.1153039466137842,0.12724419775326076 -monash_m1_quarterly,amazon/chronos-t5-base,1.746093728928804,0.1123583549291933 -monash_m1_yearly,amazon/chronos-t5-base,4.401291522370069,0.18541586641719554 -monash_m3_monthly,amazon/chronos-t5-base,0.8627172231908679,0.09640536232169555 -monash_m3_quarterly,amazon/chronos-t5-base,1.1696030904401578,0.07392876900131434 -monash_m3_yearly,amazon/chronos-t5-base,3.1298600218573775,0.1486674447940158 -monash_nn5_weekly,amazon/chronos-t5-base,0.9334860602210187,0.08972736821598823 -monash_tourism_monthly,amazon/chronos-t5-base,1.7937702435879332,0.10260220444264027 -monash_tourism_quarterly,amazon/chronos-t5-base,1.7791494997972261,0.06852507950474919 -monash_tourism_yearly,amazon/chronos-t5-base,3.8359926053603197,0.20722699382964643 -monash_traffic,amazon/chronos-t5-base,0.8015262383138622,0.25565153982140926 -monash_weather,amazon/chronos-t5-base,0.8159511190589147,0.13802320967454584 -nn5,amazon/chronos-t5-base,0.5927076179914024,0.1630476065585159 diff --git a/scripts/evaluation/results/chronos-t5-large-agg-rel-scores.csv b/scripts/evaluation/results/chronos-t5-large-agg-rel-scores.csv deleted file mode 100644 index 94beef1..0000000 --- a/scripts/evaluation/results/chronos-t5-large-agg-rel-scores.csv +++ /dev/null @@ -1,5 +0,0 @@ -benchmark,metric,value -in-domain,MASE,0.6944869734691035 -in-domain,WQL,0.5596857927462495 -zero-shot,MASE,0.8213682201405101 -zero-shot,WQL,0.6504834081319559 diff --git a/scripts/evaluation/results/chronos-t5-large-in-domain.csv b/scripts/evaluation/results/chronos-t5-large-in-domain.csv deleted file mode 100644 index 63f4b18..0000000 --- a/scripts/evaluation/results/chronos-t5-large-in-domain.csv +++ /dev/null @@ -1,16 +0,0 @@ -dataset,model,MASE,WQL -electricity_15min,amazon/chronos-t5-large,0.3866310906621673,0.07759528615667297 -m4_daily,amazon/chronos-t5-large,3.134560968849699,0.02158279722410466 -m4_hourly,amazon/chronos-t5-large,0.6975930649233378,0.02086427219957674 -m4_monthly,amazon/chronos-t5-large,0.9585550091429409,0.10091221432814867 -m4_weekly,amazon/chronos-t5-large,2.0191422600104425,0.036912838355537186 -monash_electricity_hourly,amazon/chronos-t5-large,1.4069912853901292,0.09642382339452431 -monash_electricity_weekly,amazon/chronos-t5-large,1.7501880036182798,0.05765306465830232 -monash_kdd_cup_2018,amazon/chronos-t5-large,0.6788042816175427,0.2853553329804835 -monash_london_smart_meters,amazon/chronos-t5-large,0.8290300790418726,0.4235436387853963 -monash_pedestrian_counts,amazon/chronos-t5-large,0.2764118100521592,0.18692234491663473 -monash_rideshare,amazon/chronos-t5-large,0.8758058784466208,0.140260325368757 -monash_temperature_rain,amazon/chronos-t5-large,0.9738403865035117,0.6604571928063249 -taxi_30min,amazon/chronos-t5-large,0.8245662397270109,0.2653520120326771 -uber_tlc_daily,amazon/chronos-t5-large,0.8044165990021739,0.09499035584302248 -uber_tlc_hourly,amazon/chronos-t5-large,0.6700665937164474,0.15190288476653066 diff --git a/scripts/evaluation/results/chronos-t5-large-zero-shot.csv b/scripts/evaluation/results/chronos-t5-large-zero-shot.csv deleted file mode 100644 index 020c100..0000000 --- a/scripts/evaluation/results/chronos-t5-large-zero-shot.csv +++ /dev/null @@ -1,28 +0,0 @@ -dataset,model,MASE,WQL -ETTh,amazon/chronos-t5-large,0.78160443631164,0.07884375667736107 -ETTm,amazon/chronos-t5-large,0.7325919639389967,0.06656858270921162 -dominick,amazon/chronos-t5-large,0.8200108271155829,0.3311575649734524 -ercot,amazon/chronos-t5-large,0.6050812633742764,0.01822996942395577 -exchange_rate,amazon/chronos-t5-large,2.3439287001928744,0.014841231672174684 -m4_quarterly,amazon/chronos-t5-large,1.2169666607868148,0.08235162400898562 -m4_yearly,amazon/chronos-t5-large,3.5524979814018947,0.1325675848907479 -m5,amazon/chronos-t5-large,0.9422990989146737,0.585615077637479 -monash_australian_electricity,amazon/chronos-t5-large,1.480849838497958,0.07973968848149568 -monash_car_parts,amazon/chronos-t5-large,0.901547374873302,1.0467398096496576 -monash_cif_2016,amazon/chronos-t5-large,0.9906388185665337,0.011966178555329998 -monash_covid_deaths,amazon/chronos-t5-large,44.07354193681227,0.06108999981222163 -monash_fred_md,amazon/chronos-t5-large,0.5184400880318044,0.01675533888399231 -monash_hospital,amazon/chronos-t5-large,0.7055308474630898,0.0552450850258613 -monash_m1_monthly,amazon/chronos-t5-large,1.0888995301234758,0.12729911122909737 -monash_m1_quarterly,amazon/chronos-t5-large,1.7477134564031453,0.10618253695380094 -monash_m1_yearly,amazon/chronos-t5-large,4.250667049416348,0.17128879333643188 -monash_m3_monthly,amazon/chronos-t5-large,0.8559326975903808,0.09572577431396007 -monash_m3_quarterly,amazon/chronos-t5-large,1.1867267751420676,0.07449254281607631 -monash_m3_yearly,amazon/chronos-t5-large,3.0239493021840635,0.14814710375646464 -monash_nn5_weekly,amazon/chronos-t5-large,0.9228721852437364,0.08948447200571868 -monash_tourism_monthly,amazon/chronos-t5-large,1.7304427846580348,0.09983169221760163 -monash_tourism_quarterly,amazon/chronos-t5-large,1.6437184365114073,0.0690906057781915 -monash_tourism_yearly,amazon/chronos-t5-large,3.6268503118928535,0.17732007043832695 -monash_traffic,amazon/chronos-t5-large,0.7985975530866148,0.25313515740581755 -monash_weather,amazon/chronos-t5-large,0.8187388457436171,0.1387756772600068 -nn5,amazon/chronos-t5-large,0.5755260854173723,0.15733693855465292 diff --git a/scripts/evaluation/results/chronos-t5-mini-agg-rel-scores.csv b/scripts/evaluation/results/chronos-t5-mini-agg-rel-scores.csv deleted file mode 100644 index abd06f0..0000000 --- a/scripts/evaluation/results/chronos-t5-mini-agg-rel-scores.csv +++ /dev/null @@ -1,5 +0,0 @@ -benchmark,metric,value -in-domain,MASE,0.7249816823595568 -in-domain,WQL,0.5965372489622094 -zero-shot,MASE,0.8411995116926901 -zero-shot,WQL,0.6888397962259065 diff --git a/scripts/evaluation/results/chronos-t5-mini-in-domain.csv b/scripts/evaluation/results/chronos-t5-mini-in-domain.csv deleted file mode 100644 index 196d3e9..0000000 --- a/scripts/evaluation/results/chronos-t5-mini-in-domain.csv +++ /dev/null @@ -1,16 +0,0 @@ -dataset,model,MASE,WQL -electricity_15min,amazon/chronos-t5-mini,0.4446629660227641,0.08114657599239496 -m4_daily,amazon/chronos-t5-mini,3.1533349226194005,0.022000507013584743 -m4_hourly,amazon/chronos-t5-mini,0.7616830292996938,0.024630575107847653 -m4_monthly,amazon/chronos-t5-mini,0.9934074425853089,0.10402168689068064 -m4_weekly,amazon/chronos-t5-mini,2.1407189608104416,0.04138058102434373 -monash_electricity_hourly,amazon/chronos-t5-mini,1.3698378948313894,0.09189698159081384 -monash_electricity_weekly,amazon/chronos-t5-mini,1.9238345295706893,0.07015383787479901 -monash_kdd_cup_2018,amazon/chronos-t5-mini,0.6027861468526459,0.25493489598663444 -monash_london_smart_meters,amazon/chronos-t5-mini,0.8570035850603943,0.4356582737588471 -monash_pedestrian_counts,amazon/chronos-t5-mini,0.30374539593979855,0.2374083216051065 -monash_rideshare,amazon/chronos-t5-mini,0.8157349455509949,0.12963515638823117 -monash_temperature_rain,amazon/chronos-t5-mini,1.010161905102516,0.6919171702485583 -taxi_30min,amazon/chronos-t5-mini,0.9318379552979712,0.31229508015999674 -uber_tlc_daily,amazon/chronos-t5-mini,0.9213437323817685,0.10475291429149586 -uber_tlc_hourly,amazon/chronos-t5-mini,0.6812621470377416,0.15982192635434303 diff --git a/scripts/evaluation/results/chronos-t5-mini-zero-shot.csv b/scripts/evaluation/results/chronos-t5-mini-zero-shot.csv deleted file mode 100644 index bdc383f..0000000 --- a/scripts/evaluation/results/chronos-t5-mini-zero-shot.csv +++ /dev/null @@ -1,28 +0,0 @@ -dataset,model,MASE,WQL -ETTh,amazon/chronos-t5-mini,0.789678971785092,0.08068969536800001 -ETTm,amazon/chronos-t5-mini,0.7521219674190734,0.06791782942706617 -dominick,amazon/chronos-t5-mini,0.8207116999488602,0.34004499734299765 -ercot,amazon/chronos-t5-mini,0.5462749489237783,0.015035001020343136 -exchange_rate,amazon/chronos-t5-mini,2.1326718165798657,0.015073846769933199 -m4_quarterly,amazon/chronos-t5-mini,1.271761811062081,0.08575942238385105 -m4_yearly,amazon/chronos-t5-mini,3.7340853642679126,0.13938781939783162 -m5,amazon/chronos-t5-mini,0.9421556321929742,0.5961689098871504 -monash_australian_electricity,amazon/chronos-t5-mini,1.046297291920238,0.05424453772723559 -monash_car_parts,amazon/chronos-t5-mini,0.8913523483805221,1.0174797526818506 -monash_cif_2016,amazon/chronos-t5-mini,1.0674111822055679,0.016800831829085764 -monash_covid_deaths,amazon/chronos-t5-mini,43.69727825485175,0.08788117644141617 -monash_fred_md,amazon/chronos-t5-mini,0.46227452519609524,0.01871860604459728 -monash_hospital,amazon/chronos-t5-mini,0.7112593459108532,0.05831005112661489 -monash_m1_monthly,amazon/chronos-t5-mini,1.1756557848450433,0.14192178371159841 -monash_m1_quarterly,amazon/chronos-t5-mini,1.795009199698074,0.11760148522768847 -monash_m1_yearly,amazon/chronos-t5-mini,5.078889706085604,0.1882823108615221 -monash_m3_monthly,amazon/chronos-t5-mini,0.900404391663476,0.09935931092075681 -monash_m3_quarterly,amazon/chronos-t5-mini,1.2604342624229292,0.07807204797138119 -monash_m3_yearly,amazon/chronos-t5-mini,3.4395976709464255,0.16085249526114198 -monash_nn5_weekly,amazon/chronos-t5-mini,0.9459117943913629,0.09042762527674755 -monash_tourism_monthly,amazon/chronos-t5-mini,1.920865545569713,0.10791754513335952 -monash_tourism_quarterly,amazon/chronos-t5-mini,1.7957439111869486,0.07514539225156464 -monash_tourism_yearly,amazon/chronos-t5-mini,4.134958090482728,0.2202036957350168 -monash_traffic,amazon/chronos-t5-mini,0.8546792774237857,0.2668831661775284 -monash_weather,amazon/chronos-t5-mini,0.8607748244159247,0.15031866806333247 -nn5,amazon/chronos-t5-mini,0.6497211196906223,0.17352254058241523 diff --git a/scripts/evaluation/results/chronos-t5-small-agg-rel-scores.csv b/scripts/evaluation/results/chronos-t5-small-agg-rel-scores.csv deleted file mode 100644 index 43947fb..0000000 --- a/scripts/evaluation/results/chronos-t5-small-agg-rel-scores.csv +++ /dev/null @@ -1,5 +0,0 @@ -benchmark,metric,value -in-domain,MASE,0.7296140269944743 -in-domain,WQL,0.6086958548874499 -zero-shot,MASE,0.8303721909132112 -zero-shot,WQL,0.6649587072099045 diff --git a/scripts/evaluation/results/chronos-t5-small-in-domain.csv b/scripts/evaluation/results/chronos-t5-small-in-domain.csv deleted file mode 100644 index 206ec82..0000000 --- a/scripts/evaluation/results/chronos-t5-small-in-domain.csv +++ /dev/null @@ -1,16 +0,0 @@ -dataset,model,MASE,WQL -electricity_15min,amazon/chronos-t5-small,0.4115559557750193,0.08085148902238105 -m4_daily,amazon/chronos-t5-small,3.1384304946608896,0.02129901023419818 -m4_hourly,amazon/chronos-t5-small,0.7300874075370588,0.024686127211237932 -m4_monthly,amazon/chronos-t5-small,0.9797264456494642,0.10297069145186107 -m4_weekly,amazon/chronos-t5-small,2.0802214537692607,0.03959222330783002 -monash_electricity_hourly,amazon/chronos-t5-small,1.530308399040219,0.10765947926209926 -monash_electricity_weekly,amazon/chronos-t5-small,1.9249616494404531,0.07593976499899265 -monash_kdd_cup_2018,amazon/chronos-t5-small,0.6911172359201715,0.2863722811236367 -monash_london_smart_meters,amazon/chronos-t5-small,0.8405756252443325,0.4300875548402115 -monash_pedestrian_counts,amazon/chronos-t5-small,0.30836963006151696,0.2442543970311678 -monash_rideshare,amazon/chronos-t5-small,0.8436277753840817,0.1363421932158997 -monash_temperature_rain,amazon/chronos-t5-small,1.0176003932416664,0.6847726381172435 -taxi_30min,amazon/chronos-t5-small,0.976277213614167,0.32770172988517626 -uber_tlc_daily,amazon/chronos-t5-small,0.8694727058784919,0.0994889223610958 -uber_tlc_hourly,amazon/chronos-t5-small,0.6738672444888639,0.1573990617753753 diff --git a/scripts/evaluation/results/chronos-t5-small-zero-shot.csv b/scripts/evaluation/results/chronos-t5-small-zero-shot.csv deleted file mode 100644 index f3a970a..0000000 --- a/scripts/evaluation/results/chronos-t5-small-zero-shot.csv +++ /dev/null @@ -1,28 +0,0 @@ -dataset,model,MASE,WQL -ETTh,amazon/chronos-t5-small,0.8516754221042285,0.08667817580712385 -ETTm,amazon/chronos-t5-small,0.6825432730635727,0.06076472147001207 -dominick,amazon/chronos-t5-small,0.8108766032127683,0.3368104617474581 -ercot,amazon/chronos-t5-small,0.564879593858422,0.015547628920969682 -exchange_rate,amazon/chronos-t5-small,1.8143459139100264,0.014492477372711763 -m4_quarterly,amazon/chronos-t5-small,1.2415331521819728,0.08383826063189778 -m4_yearly,amazon/chronos-t5-small,3.738749650935195,0.1384514201649314 -m5,amazon/chronos-t5-small,0.9368713240675598,0.5896066252181699 -monash_australian_electricity,amazon/chronos-t5-small,1.2241146217392032,0.06951399165882449 -monash_car_parts,amazon/chronos-t5-small,0.8917508090523597,1.0314986717260015 -monash_cif_2016,amazon/chronos-t5-small,1.0187937383419037,0.014633240218233142 -monash_covid_deaths,amazon/chronos-t5-small,42.298997211368935,0.06339512778191682 -monash_fred_md,amazon/chronos-t5-small,0.4742159923922472,0.01486734736993978 -monash_hospital,amazon/chronos-t5-small,0.709814741753487,0.05704674270057172 -monash_m1_monthly,amazon/chronos-t5-small,1.1723041163998773,0.13799049510465802 -monash_m1_quarterly,amazon/chronos-t5-small,1.8077827825737092,0.11323432989795904 -monash_m1_yearly,amazon/chronos-t5-small,4.739967673537301,0.1730738338876877 -monash_m3_monthly,amazon/chronos-t5-small,0.8856577322724943,0.09985251429658573 -monash_m3_quarterly,amazon/chronos-t5-small,1.278907982396775,0.08094041554590593 -monash_m3_yearly,amazon/chronos-t5-small,3.382470310192457,0.157363937435307 -monash_nn5_weekly,amazon/chronos-t5-small,0.9277396908126303,0.08963913763368506 -monash_tourism_monthly,amazon/chronos-t5-small,1.9251180766131313,0.10943962474253494 -monash_tourism_quarterly,amazon/chronos-t5-small,1.7623454951333655,0.06862432764377493 -monash_tourism_yearly,amazon/chronos-t5-small,3.987690476709746,0.19960492460202509 -monash_traffic,amazon/chronos-t5-small,0.8204223927835267,0.2571189517024486 -monash_weather,amazon/chronos-t5-small,0.8550633590487968,0.1479701971025123 -nn5,amazon/chronos-t5-small,0.6130789183153671,0.16771392719859998 diff --git a/scripts/evaluation/results/chronos-t5-tiny-agg-rel-scores.csv b/scripts/evaluation/results/chronos-t5-tiny-agg-rel-scores.csv deleted file mode 100644 index 440cc39..0000000 --- a/scripts/evaluation/results/chronos-t5-tiny-agg-rel-scores.csv +++ /dev/null @@ -1,5 +0,0 @@ -benchmark,metric,value -in-domain,MASE,0.7649019745781727 -in-domain,WQL,0.6288613368129368 -zero-shot,MASE,0.8704764463925718 -zero-shot,WQL,0.7108912052035352 diff --git a/scripts/evaluation/results/chronos-t5-tiny-in-domain.csv b/scripts/evaluation/results/chronos-t5-tiny-in-domain.csv deleted file mode 100644 index e30632f..0000000 --- a/scripts/evaluation/results/chronos-t5-tiny-in-domain.csv +++ /dev/null @@ -1,16 +0,0 @@ -dataset,model,MASE,WQL -electricity_15min,amazon/chronos-t5-tiny,0.5091784254243783,0.08236334376190152 -m4_daily,amazon/chronos-t5-tiny,3.203164895930929,0.022152192084951595 -m4_hourly,amazon/chronos-t5-tiny,0.8171321441164723,0.027490760558343874 -m4_monthly,amazon/chronos-t5-tiny,1.005839207921131,0.10388015368939435 -m4_weekly,amazon/chronos-t5-tiny,2.2148332313370735,0.043429655561156084 -monash_electricity_hourly,amazon/chronos-t5-tiny,1.6190021089002615,0.10967453530956882 -monash_electricity_weekly,amazon/chronos-t5-tiny,2.0774597917676734,0.08159998975612164 -monash_kdd_cup_2018,amazon/chronos-t5-tiny,0.6730886827096076,0.2616610603634618 -monash_london_smart_meters,amazon/chronos-t5-tiny,0.8830447519225436,0.4499607073491794 -monash_pedestrian_counts,amazon/chronos-t5-tiny,0.3042105240185045,0.23387631681117071 -monash_rideshare,amazon/chronos-t5-tiny,0.8431350112476247,0.1378817076926394 -monash_temperature_rain,amazon/chronos-t5-tiny,0.9887398447367799,0.6957797286648015 -taxi_30min,amazon/chronos-t5-tiny,1.035544060665179,0.3450476958104713 -uber_tlc_daily,amazon/chronos-t5-tiny,0.93025919000775,0.1105323649942084 -uber_tlc_hourly,amazon/chronos-t5-tiny,0.697558054147913,0.16320255844336232 diff --git a/scripts/evaluation/results/chronos-t5-tiny-zero-shot.csv b/scripts/evaluation/results/chronos-t5-tiny-zero-shot.csv deleted file mode 100644 index 83d377c..0000000 --- a/scripts/evaluation/results/chronos-t5-tiny-zero-shot.csv +++ /dev/null @@ -1,28 +0,0 @@ -dataset,model,MASE,WQL -ETTh,amazon/chronos-t5-tiny,0.8184074113571701,0.08578203438707048 -ETTm,amazon/chronos-t5-tiny,0.9103621000781905,0.07975361086322658 -dominick,amazon/chronos-t5-tiny,0.8538295532466194,0.3597090770361857 -ercot,amazon/chronos-t5-tiny,0.7273437589773705,0.020843170924006626 -exchange_rate,amazon/chronos-t5-tiny,1.6621128608546154,0.01085145980896454 -m4_quarterly,amazon/chronos-t5-tiny,1.2696259955861924,0.0861404188925996 -m4_yearly,amazon/chronos-t5-tiny,3.5293881164900527,0.13281575565500411 -m5,amazon/chronos-t5-tiny,0.9394059505709506,0.5981531758388589 -monash_australian_electricity,amazon/chronos-t5-tiny,1.4558820561269024,0.07673567331332948 -monash_car_parts,amazon/chronos-t5-tiny,0.9058206654011024,1.0236307963149358 -monash_cif_2016,amazon/chronos-t5-tiny,1.09349564130852,0.014066593076202984 -monash_covid_deaths,amazon/chronos-t5-tiny,46.53079664940016,0.09201919385053775 -monash_fred_md,amazon/chronos-t5-tiny,0.48008374212956456,0.03219550761153211 -monash_hospital,amazon/chronos-t5-tiny,0.7062562198194838,0.05790409320432609 -monash_m1_monthly,amazon/chronos-t5-tiny,1.214892145549996,0.14723095246308077 -monash_m1_quarterly,amazon/chronos-t5-tiny,1.8968576926613199,0.11026972972622998 -monash_m1_yearly,amazon/chronos-t5-tiny,4.829453202075546,0.17286063726000958 -monash_m3_monthly,amazon/chronos-t5-tiny,0.9095746605884618,0.10117875324490073 -monash_m3_quarterly,amazon/chronos-t5-tiny,1.3234957548639883,0.08209032993637215 -monash_m3_yearly,amazon/chronos-t5-tiny,3.1489371074890093,0.1492445630072877 -monash_nn5_weekly,amazon/chronos-t5-tiny,0.9637480731663901,0.09205994784693056 -monash_tourism_monthly,amazon/chronos-t5-tiny,2.151677532807024,0.11356761694754255 -monash_tourism_quarterly,amazon/chronos-t5-tiny,1.9116538900950555,0.07191734222366106 -monash_tourism_yearly,amazon/chronos-t5-tiny,3.820615532600914,0.19709256337364625 -monash_traffic,amazon/chronos-t5-tiny,0.878709088458116,0.2632101606272236 -monash_weather,amazon/chronos-t5-tiny,0.8504899606521996,0.14787595319625085 -nn5,amazon/chronos-t5-tiny,0.7021735456568664,0.19071330483289695 diff --git a/scripts/evaluation/results/seasonal-naive-in-domain.csv b/scripts/evaluation/results/seasonal-naive-in-domain.csv deleted file mode 100644 index a7e806e..0000000 --- a/scripts/evaluation/results/seasonal-naive-in-domain.csv +++ /dev/null @@ -1,16 +0,0 @@ -dataset,model,MASE,WQL -electricity_15min,seasonal-naive,0.4978697476132387,0.1169378163151378 -m4_daily,seasonal-naive,3.278424323759728,0.0279332664832445 -m4_hourly,seasonal-naive,1.1932105781333862,0.0483091941403194 -m4_monthly,seasonal-naive,1.2597170386001693,0.1455332906092934 -m4_weekly,seasonal-naive,2.777295109814942,0.0633986476090776 -monash_electricity_hourly,seasonal-naive,1.839634785956572,0.1468968206229902 -monash_electricity_weekly,seasonal-naive,3.0371656285424,0.1979332504059267 -monash_kdd_cup_2018,seasonal-naive,0.9943785889052376,0.5555856702439576 -monash_london_smart_meters,seasonal-naive,0.9661872287141056,0.5413187715028914 -monash_pedestrian_counts,seasonal-naive,0.3691951941442247,0.3185271550430794 -monash_rideshare,seasonal-naive,1.2495987545425715,0.1860080644135506 -monash_temperature_rain,seasonal-naive,2.243384627173123,1.4244854980220072 -taxi_30min,seasonal-naive,1.160268631066241,0.4711417890926274 -uber_tlc_daily,seasonal-naive,1.37803447078482,0.2313550175912078 -uber_tlc_hourly,seasonal-naive,0.930916273455971,0.298849044501192 diff --git a/scripts/evaluation/results/seasonal-naive-zero-shot.csv b/scripts/evaluation/results/seasonal-naive-zero-shot.csv deleted file mode 100644 index 24b960f..0000000 --- a/scripts/evaluation/results/seasonal-naive-zero-shot.csv +++ /dev/null @@ -1,28 +0,0 @@ -dataset,model,MASE,WQL -ETTh,seasonal-naive,0.9316203114697056,0.1220896585205886 -ETTm,seasonal-naive,1.1693053852270578,0.1413480385734046 -dominick,seasonal-naive,0.8706150115348875,0.4529164093744346 -ercot,seasonal-naive,0.7613354813741452,0.0366036447606282 -exchange_rate,seasonal-naive,1.7401824286954128,0.0129841406759913 -m4_quarterly,seasonal-naive,1.6022471766126911,0.1186484661559648 -m4_yearly,seasonal-naive,3.974360261259571,0.1614389663357925 -m5,seasonal-naive,1.399206213076729,1.0240883478068443 -monash_australian_electricity,seasonal-naive,1.2533189641227642,0.0836951323308387 -monash_car_parts,seasonal-naive,1.2014638390969912,1.5999522140809177 -monash_cif_2016,seasonal-naive,1.289290577415544,0.0150830409089921 -monash_covid_deaths,seasonal-naive,46.91239825526407,0.1330848762571827 -monash_fred_md,seasonal-naive,1.1008000463101226,0.1222237702571737 -monash_hospital,seasonal-naive,0.9205278266364826,0.0726263373268254 -monash_m1_monthly,seasonal-naive,1.3144614957646543,0.1914632595030148 -monash_m1_quarterly,seasonal-naive,2.077536550805995,0.1495022062865622 -monash_m1_yearly,seasonal-naive,4.894322225232431,0.2092955931101782 -monash_m3_monthly,seasonal-naive,1.1462045758327934,0.1485446007554992 -monash_m3_quarterly,seasonal-naive,1.425343793700714,0.1012520529806161 -monash_m3_yearly,seasonal-naive,3.1717102364409517,0.1665329650420048 -monash_nn5_weekly,seasonal-naive,1.0628482559107015,0.1226908962169196 -monash_tourism_monthly,seasonal-naive,1.630939994944413,0.1041824322151567 -monash_tourism_quarterly,seasonal-naive,1.6989892627474672,0.1193750169177449 -monash_tourism_yearly,seasonal-naive,3.5520097206480883,0.2091826587673241 -monash_traffic,seasonal-naive,1.0767397173107436,0.3618532196990004 -monash_weather,seasonal-naive,1.0038475713182748,0.2165947349654047 -nn5,seasonal-naive,1.2917285866431214,0.4246208074843067 diff --git a/scripts/experiments/README.md b/scripts/experiments/README.md new file mode 100644 index 0000000..9508329 --- /dev/null +++ b/scripts/experiments/README.md @@ -0,0 +1,8 @@ +# Experiments in Real Datasets + +An example on how to run experiments with real datasets is shown in [this script](./experiment_with_real_datasets.py). + +The configurations per dataset are located in [this yaml file](./configs/datasets.yaml). + +### On electricity price forecasting datasets +At the time of writing our paper we took the electricity price forecasting datasets from [here](https://github.com/Nixtla/transfer-learning-time-series/blob/main/datasets/exogenous-vars-electricity.csv) and [here](https://github.com/Nixtla/transfer-learning-time-series/blob/main/datasets/electricity.csv). As one can see from the commit history [here](https://github.com/Nixtla/transfer-learning-time-series/pull/17), it seems that there were some fixes on how the data was preprocessed. Since the data has been taken from [zenodo](https://zenodo.org/records/4624805), we have provided both versions of these datasets so that anyone interested can run our model with both the previous and corrected dataset versions. \ No newline at end of file diff --git a/scripts/experiments/configs/datasets.yaml b/scripts/experiments/configs/datasets.yaml new file mode 100644 index 0000000..9d50ec6 --- /dev/null +++ b/scripts/experiments/configs/datasets.yaml @@ -0,0 +1,196 @@ +- name: ETTh + hf_repo: autogluon/chronos_datasets_extra + offset: -24 + prediction_length: 24 + num_rolls: 1 + covariates_fields: ["HULL", "MUFL", "MULL", "LUFL", "LULL"] + series_fields: "OT" +- name: ETTm + hf_repo: autogluon/chronos_datasets_extra + offset: -24 + prediction_length: 24 + num_rolls: 1 + covariates_fields: ["HULL", "MUFL", "MULL", "LUFL", "LULL"] + series_fields: "OT" +- name: epf_electricity_be_paper + hf_repo: autogluon/fev_datasets + offset: -24 + prediction_length: 24 + num_rolls: 1 + covariates_fields: ['exogenous1', 'exogenous2'] + series_fields: "target" +- name: epf_electricity_de_paper + hf_repo: autogluon/fev_datasets + offset: -24 + prediction_length: 24 + num_rolls: 1 + covariates_fields: ['exogenous1', 'exogenous2'] + series_fields: "target" +- name: epf_electricity_fr_paper + hf_repo: autogluon/fev_datasets + offset: -24 + prediction_length: 24 + num_rolls: 1 + covariates_fields: ['exogenous1', 'exogenous2'] + series_fields: "target" +- name: epf_electricity_np_paper + hf_repo: autogluon/fev_datasets + offset: -24 + prediction_length: 24 + num_rolls: 1 + covariates_fields: ['exogenous1', 'exogenous2'] + series_fields: "target" +- name: epf_electricity_pjm_paper + hf_repo: autogluon/fev_datasets + offset: -24 + prediction_length: 24 + num_rolls: 1 + covariates_fields: ['exogenous1', 'exogenous2'] + series_fields: "target" +- name: proenfo_bull + hf_repo: autogluon/fev_datasets + offset: -24 + prediction_length: 24 + num_rolls: 1 + covariates_fields: ['airtemperature', 'dewtemperature', 'sealvlpressure'] + series_fields: "target" +- name: proenfo_cockatoo + hf_repo: autogluon/fev_datasets + offset: -24 + prediction_length: 24 + num_rolls: 1 + covariates_fields: ['airtemperature', 'dewtemperature', 'sealvlpressure', 'winddirection', 'windspeed'] + series_fields: "target" +- name: proenfo_covid19 + hf_repo: autogluon/fev_datasets + offset: -24 + prediction_length: 24 + num_rolls: 1 + covariates_fields: ['pressure_kpa', 'cloud_cover_perc', 'humidity_perc', 'airtemperature', 'wind_direction_deg', 'wind_speed_kmh'] + series_fields: "target" +- name: proenfo_gfc12_load + hf_repo: autogluon/fev_datasets + offset: -24 + prediction_length: 24 + num_rolls: 1 + covariates_fields: ['airtemperature'] + series_fields: "target" +- name: proenfo_gfc14_load + hf_repo: autogluon/fev_datasets + offset: -24 + prediction_length: 24 + num_rolls: 1 + covariates_fields: ['airtemperature'] + series_fields: "target" +- name: proenfo_gfc17_load + hf_repo: autogluon/fev_datasets + offset: -24 + prediction_length: 24 + num_rolls: 1 + covariates_fields: ['airtemperature'] + series_fields: "target" +- name: proenfo_hog + hf_repo: autogluon/fev_datasets + offset: -24 + prediction_length: 24 + num_rolls: 1 + covariates_fields: ['airtemperature', 'dewtemperature', 'sealvlpressure', 'winddirection', 'windspeed'] + series_fields: "target" +- name: proenfo_pdb + hf_repo: autogluon/fev_datasets + offset: -24 + prediction_length: 24 + num_rolls: 1 + covariates_fields: ['airtemperature'] + series_fields: "target" +- name: proenfo_spain + hf_repo: autogluon/fev_datasets + offset: -24 + prediction_length: 24 + num_rolls: 1 + covariates_fields: [ + 'generation_biomass', + 'generation_fossil_brown_coal_lignite', + 'generation_fossil_coal_derived_gas', + 'generation_fossil_gas', + 'generation_fossil_hard_coal', + 'generation_fossil_oil', + 'generation_fossil_oil_shale', + 'generation_fossil_peat', + 'generation_geothermal', + 'generation_hydro_pumped_storage_consumption', + 'generation_hydro_run_of_river_and_poundage', + 'generation_hydro_water_reservoir', + 'generation_marine', + 'generation_nuclear', + 'generation_other', + 'generation_other_renewable', + 'generation_solar', + 'generation_waste', + 'generation_wind_offshore', + 'generation_wind_onshore', + ] + series_fields: "target" +- name: monash_rideshare + hf_repo: autogluon/chronos_datasets + offset: -24 + prediction_length: 24 + num_rolls: 1 + covariates_fields: ["price_mean", "distance_mean", "surge_mean", "temp", "rain", "humidity", "clouds", "wind"] + series_fields: "api_calls" +- name: m5_with_covariates + hf_repo: autogluon/fev_datasets + offset: -28 + prediction_length: 28 + num_rolls: 1 + covariates_fields: [ + 'snap_CA', + 'snap_TX', + 'snap_WI', + 'sell_price', + 'event_type_1_Cultural', + 'event_type_1_National', + 'event_type_1_Religious', + 'event_type_1_Sporting', + 'event_type_1_nan', + 'event_type_2_Cultural', + 'event_type_2_Religious', + 'event_type_2_nan', + ] + series_fields: "target" +- name: epf_electricity_be + hf_repo: autogluon/fev_datasets + offset: -24 + prediction_length: 24 + num_rolls: 1 + covariates_fields: ["Generation forecast", "System load forecast"] + series_fields: "target" +- name: epf_electricity_de + hf_repo: autogluon/fev_datasets + offset: -24 + prediction_length: 24 + num_rolls: 1 + covariates_fields: ["Ampirion Load Forecast", "PV+Wind Forecast"] + series_fields: "target" +- name: epf_electricity_fr + hf_repo: autogluon/fev_datasets + offset: -24 + prediction_length: 24 + num_rolls: 1 + covariates_fields: ["Generation forecast", "System load forecast"] + series_fields: "target" +- name: epf_electricity_np + hf_repo: autogluon/fev_datasets + offset: -24 + prediction_length: 24 + num_rolls: 1 + covariates_fields: ["Grid load forecast", "Wind power forecast"] + series_fields: "target" +- name: epf_electricity_pjm + hf_repo: autogluon/fev_datasets + offset: -24 + prediction_length: 24 + num_rolls: 1 + covariates_fields: ["System load forecast", "Zonal COMED load foecast"] + series_fields: "target" + diff --git a/scripts/experiments/configs/synthetic_datasets.yaml b/scripts/experiments/configs/synthetic_datasets.yaml new file mode 100644 index 0000000..2ac7ff0 --- /dev/null +++ b/scripts/experiments/configs/synthetic_datasets.yaml @@ -0,0 +1,256 @@ +- name: synthetic_datasets/simple_spikes_mult + filename: datasets/simple_spikes_mult + offset: -30 + prediction_length: 30 + num_rolls: 1 + covariates_fields: + - covariate + series_fields: target +- name: synthetic_datasets/simple_spikes_add + filename: datasets/simple_spikes_add + offset: -30 + prediction_length: 30 + num_rolls: 1 + covariates_fields: + - covariate + series_fields: target +- name: synthetic_datasets/diverse_spikes_mult + filename: datasets/diverse_spikes_mult + offset: -30 + prediction_length: 30 + num_rolls: 1 + covariates_fields: + - covariate + series_fields: target +- name: synthetic_datasets/diverse_spikes_add + filename: datasets/diverse_spikes_add + offset: -30 + prediction_length: 30 + num_rolls: 1 + covariates_fields: + - covariate + series_fields: target +- name: synthetic_datasets/noisy_spikes_mult + filename: datasets/noisy_spikes_mult + offset: -30 + prediction_length: 30 + num_rolls: 1 + covariates_fields: + - covariate + series_fields: target +- name: synthetic_datasets/noisy_spikes_add + filename: datasets/noisy_spikes_add + offset: -30 + prediction_length: 30 + num_rolls: 1 + covariates_fields: + - covariate + series_fields: target +- name: synthetic_datasets/simple_steps_mult + filename: datasets/simple_steps_mult + offset: -30 + prediction_length: 30 + num_rolls: 1 + covariates_fields: + - covariate + series_fields: target +- name: synthetic_datasets/simple_steps_add + filename: datasets/simple_steps_add + offset: -30 + prediction_length: 30 + num_rolls: 1 + covariates_fields: + - covariate + series_fields: target +- name: synthetic_datasets/diverse_steps_mult + filename: datasets/diverse_steps_mult + offset: -30 + prediction_length: 30 + num_rolls: 1 + covariates_fields: + - covariate + series_fields: target +- name: synthetic_datasets/diverse_steps_add + filename: datasets/diverse_steps_add + offset: -30 + prediction_length: 30 + num_rolls: 1 + covariates_fields: + - covariate + series_fields: target +- name: synthetic_datasets/noisy_steps_mult + filename: datasets/noisy_steps_mult + offset: -30 + prediction_length: 30 + num_rolls: 1 + covariates_fields: + - covariate + series_fields: target +- name: synthetic_datasets/noisy_steps_add + filename: datasets/noisy_steps_add + offset: -30 + prediction_length: 30 + num_rolls: 1 + covariates_fields: + - covariate + series_fields: target +- name: synthetic_datasets/simple_bells_mult + filename: datasets/simple_bells_mult + offset: -30 + prediction_length: 30 + num_rolls: 1 + covariates_fields: + - covariate + series_fields: target +- name: synthetic_datasets/simple_bells_add + filename: datasets/simple_bells_add + offset: -30 + prediction_length: 30 + num_rolls: 1 + covariates_fields: + - covariate + series_fields: target +- name: synthetic_datasets/diverse_bells_mult + filename: datasets/diverse_bells_mult + offset: -30 + prediction_length: 30 + num_rolls: 1 + covariates_fields: + - covariate + series_fields: target +- name: synthetic_datasets/diverse_bells_add + filename: datasets/diverse_bells_add + offset: -30 + prediction_length: 30 + num_rolls: 1 + covariates_fields: + - covariate + series_fields: target +- name: synthetic_datasets/noisy_bells_mult + filename: datasets/noisy_bells_mult + offset: -30 + prediction_length: 30 + num_rolls: 1 + covariates_fields: + - covariate + series_fields: target +- name: synthetic_datasets/noisy_bells_add + filename: datasets/noisy_bells_add + offset: -30 + prediction_length: 30 + num_rolls: 1 + covariates_fields: + - covariate + series_fields: target +- name: synthetic_datasets/single_bells_mult + filename: datasets/single_bells_mult + offset: -30 + prediction_length: 30 + num_rolls: 1 + covariates_fields: + - covariate + series_fields: target +- name: synthetic_datasets/single_bells_add + filename: datasets/single_bells_add + offset: -30 + prediction_length: 30 + num_rolls: 1 + covariates_fields: + - covariate + series_fields: target +- name: synthetic_datasets/single_steps_mult + filename: datasets/single_steps_mult + offset: -30 + prediction_length: 30 + num_rolls: 1 + covariates_fields: + - covariate + series_fields: target +- name: synthetic_datasets/single_steps_add + filename: datasets/single_steps_add + offset: -30 + prediction_length: 30 + num_rolls: 1 + covariates_fields: + - covariate + series_fields: target +- name: synthetic_datasets/single_spikes_mult + filename: datasets/single_spikes_mult + offset: -30 + prediction_length: 30 + num_rolls: 1 + covariates_fields: + - covariate + series_fields: target +- name: synthetic_datasets/single_spikes_add + filename: datasets/single_spikes_add + offset: -30 + prediction_length: 30 + num_rolls: 1 + covariates_fields: + - covariate + series_fields: target +- name: synthetic_datasets/single_arp_mult + filename: datasets/single_arp_mult + offset: -30 + prediction_length: 30 + num_rolls: 1 + covariates_fields: + - covariate + series_fields: target +- name: synthetic_datasets/single_arp_add + filename: datasets/single_arp_add + offset: -30 + prediction_length: 30 + num_rolls: 1 + covariates_fields: + - covariate + series_fields: target +- name: synthetic_datasets/simple_arp_mult + filename: datasets/simple_arp_mult + offset: -30 + prediction_length: 30 + num_rolls: 1 + covariates_fields: + - covariate + series_fields: target +- name: synthetic_datasets/simple_arp_add + filename: datasets/simple_arp_add + offset: -30 + prediction_length: 30 + num_rolls: 1 + covariates_fields: + - covariate + series_fields: target +- name: synthetic_datasets/diverse_arp_mult + filename: datasets/diverse_arp_mult + offset: -30 + prediction_length: 30 + num_rolls: 1 + covariates_fields: + - covariate + series_fields: target +- name: synthetic_datasets/diverse_arp_add + filename: datasets/diverse_arp_add + offset: -30 + prediction_length: 30 + num_rolls: 1 + covariates_fields: + - covariate + series_fields: target +- name: synthetic_datasets/noisy_arp_mult + filename: datasets/noisy_arp_mult + offset: -30 + prediction_length: 30 + num_rolls: 1 + covariates_fields: + - covariate + series_fields: target +- name: synthetic_datasets/noisy_arp_add + filename: datasets/noisy_arp_add + offset: -30 + prediction_length: 30 + num_rolls: 1 + covariates_fields: + - covariate + series_fields: target diff --git a/scripts/experiments/example.py b/scripts/experiments/example.py new file mode 100644 index 0000000..96f2d8d --- /dev/null +++ b/scripts/experiments/example.py @@ -0,0 +1,69 @@ +import numpy as np +import yaml + +from chronosx.chronosx import ChronosXPipeline +from chronosx.utils import ChronosDataset, load_and_split_dataset +from gluonts.ev.metrics import MASE, MeanWeightedSumQuantileLoss +from gluonts.model.evaluation import evaluate_forecasts +from pathlib import Path + + +config_path = "./configs/datasets.yaml" +output_dir = Path(f"../../output/finetune") +output_dir.mkdir(exist_ok=True, parents=True) + +with open(config_path) as fp: + backtest_configs = yaml.safe_load(fp) + +dataset_config = backtest_configs[0] +prediction_length = dataset_config["prediction_length"] +num_covariates = 2 * len(dataset_config["covariates_fields"]) + + +# Load Chronos +pipeline = ChronosXPipeline( + prediction_length=prediction_length, + num_covariates=num_covariates, +) + +# load Dataset +train_dataset, test_dataset = load_and_split_dataset(backtest_config=dataset_config) +quantized_train_dataset = ChronosDataset( + datasets=[train_dataset], + probabilities=[1.0], + tokenizer=pipeline.tokenizer, + prediction_length=prediction_length, + mode="training", +).shuffle() + +# fine tune model +_, save_model_path = pipeline.finetune( + output_dir, + quantized_train_dataset, + skip_pretrained_validation=True, +) + +# Evaluate fine tuned model +pipeline = ChronosXPipeline( + prediction_length=prediction_length, + num_covariates=num_covariates, + pretrained_model_name_or_path=output_dir / "final-checkpoint", +) + +pipeline.chronosx.eval() +forecasts = pipeline.generate_forecasts(test_dataset.input) + +metrics = ( + evaluate_forecasts( + forecasts, + test_data=test_dataset, + metrics=[ + MASE(), + MeanWeightedSumQuantileLoss(np.arange(0.05, 1, 0.05).round(2).tolist()), + ], + ) + .reset_index(drop=True) + .to_dict(orient="records") +) + +print(metrics) diff --git a/scripts/experiments/experiment_with_real_datasets.py b/scripts/experiments/experiment_with_real_datasets.py new file mode 100644 index 0000000..28e75d1 --- /dev/null +++ b/scripts/experiments/experiment_with_real_datasets.py @@ -0,0 +1,204 @@ +import numpy as np +import pandas as pd +import random +import time +import yaml + +from chronosx.chronosx import ChronosXPipeline +from chronosx.utils.chronos_dataset import ChronosDataset +from chronosx.utils.hf_data_loader import load_and_split_dataset +from chronosx.utils.utils import has_enough_observations +from functools import partial +from gluonts.dataset.split import split +from gluonts.ev.metrics import MASE, MeanWeightedSumQuantileLoss +from gluonts.itertools import Filter +from gluonts.model.evaluation import evaluate_forecasts +from pathlib import Path +from pprint import pprint + + +covariate_injection = "IIB+OIB" +chronos_model_id = "amazon/chronos-t5-small" +config_path = "./configs/datasets.yaml" +output_dir = Path("../../output/finetune") +output_metrics_dir = Path("../../output/metrics") +output_metrics_dir.mkdir(exist_ok=True, parents=True) + +min_past = 1 +shuffle_buffer_length = 100 +num_runs = 3 +max_steps = 5000 +skip_pretrained_validation = False +learning_rate_list = [0.01, 0.001, 0.0001] + +with open(config_path) as fp: + backtest_configs = yaml.safe_load(fp) + +dataset_config = backtest_configs[0] +dataset_name = dataset_config["name"] +prediction_length = dataset_config["prediction_length"] +num_covariates = 2 * len(dataset_config["covariates_fields"]) +train_dataset, test_dataset = load_and_split_dataset(backtest_config=dataset_config) + + +# Load Chronos +pipeline = ChronosXPipeline( + prediction_length=prediction_length, + num_covariates=num_covariates, + covariate_injection=covariate_injection, + pretrained_model_name_or_path=chronos_model_id, +) + +tokenizer = pipeline.tokenizer + + +train_dataset = Filter( + partial( + has_enough_observations, + min_length=min_past + 2 * prediction_length, # for validation and for train + max_missing_prop=0.5, + ), + train_dataset, +) + +quantized_val_dataset = ChronosDataset( + datasets=[train_dataset], + probabilities=[1.0], + tokenizer=tokenizer, + prediction_length=prediction_length, + min_past=min_past, + mode="validation", +) +a = list(quantized_val_dataset) + +train_dataset, _ = split(train_dataset, offset=-prediction_length) +quantized_train_dataset = ChronosDataset( + datasets=[train_dataset], + probabilities=[1.0], + tokenizer=tokenizer, + prediction_length=prediction_length, + min_past=min_past, + mode="training", +).shuffle(shuffle_buffer_length=shuffle_buffer_length) + +# zero-shot evaluation on validation set +val_of_zero_shot_pretrained_model = pipeline.evaluate_model_on_validation_set( + covariate_injection=None, + quantized_val_dataset=quantized_val_dataset, + output_dir=output_dir / dataset_name, +) + + +random.seed(int(time.time())) +seed = random.randint(0, 2**32) +val_loss_per_lr = {} +mean_val_loss_per_lr = {} + +for lr in learning_rate_list: + lr_str = str(lr) + val_loss_per_run = [] + model_paths = [] + for run_id in range(num_runs): + run_id_str = str(run_id) + output_dir_lr_run_id = Path( + output_dir / dataset_name / f"lr={lr}" / f"run_id={run_id}" + ) + output_dir_lr_run_id.mkdir(exist_ok=True, parents=True) + + quantized_train_dataset = ChronosDataset( + datasets=[train_dataset], + probabilities=[1.0], + tokenizer=tokenizer, + prediction_length=prediction_length, + min_past=min_past, + mode="training", + ).shuffle( + shuffle_buffer_length=shuffle_buffer_length, + random_seed=run_id + seed, + ) + + pipeline = ChronosXPipeline( + prediction_length=prediction_length, + num_covariates=num_covariates, + covariate_injection=covariate_injection, + pretrained_model_name_or_path=chronos_model_id, + ) + + val_loss, save_model_path = pipeline.finetune( + output_dir_lr_run_id, + quantized_train_dataset, + lr=lr, + quantized_val_dataset=quantized_val_dataset, + skip_pretrained_validation=skip_pretrained_validation, + max_steps=max_steps, + ) + + val_loss_per_run.append(val_loss) + model_paths.append(save_model_path) + + mean_val_loss = np.mean(val_loss_per_run) + val_loss_per_lr[f"{lr}"] = val_loss_per_run + mean_val_loss_per_lr[f"{lr}"] = mean_val_loss + print( + f"lr: {lr} - val_loss_per_run: {val_loss_per_run} - mean_val_loss: {mean_val_loss}" + ) + + +print("val_loss_per_lr") +pprint(val_loss_per_lr) + +print("mean_val_loss_per_lr") +pprint(mean_val_loss_per_lr) + +best_val_loss = np.inf +for lr_str, val_loss in mean_val_loss_per_lr.items(): + if val_loss < best_val_loss: + best_val_loss = val_loss + best_lr_str = lr_str + + +# evaluate model with best mean validation loss +lr = float(best_lr_str) +result_rows = [] +for run_id in range(num_runs): + pretrained_model_path = ( + output_dir / dataset_name / f"lr={lr}" / f"run_id={run_id}" / "final-checkpoint" + ) + pipeline = ChronosXPipeline( + prediction_length=prediction_length, + num_covariates=num_covariates, + covariate_injection=covariate_injection, + pretrained_model_name_or_path=pretrained_model_path, + ) + + pipeline.chronosx.eval() + forecasts = pipeline.generate_forecasts( + test_dataset.input, + ) + + metrics = ( + evaluate_forecasts( + forecasts, + test_data=test_dataset, + metrics=[ + MASE(), + MeanWeightedSumQuantileLoss(np.arange(0.05, 1, 0.05).round(2).tolist()), + ], + batch_size=5000, + ) + .reset_index(drop=True) + .to_dict(orient="records") + ) + + result_rows.append( + { + "dataset": dataset_config["name"], + "covariate_injection": covariate_injection, + **metrics[0], + } + ) + +df = pd.DataFrame(result_rows) +df = df.set_index(["dataset", "covariate_injection"]) +pprint(df.mean()) +df.to_csv(output_metrics_dir / f"{dataset_name}_max_steps={max_steps}.csv") diff --git a/scripts/experiments/synthetic_datasets/README.md b/scripts/experiments/synthetic_datasets/README.md new file mode 100644 index 0000000..0bcbd96 --- /dev/null +++ b/scripts/experiments/synthetic_datasets/README.md @@ -0,0 +1,6 @@ +# Experiments with Synthetic Datasets + +Synthetic Datasets must be generated by executing this [script](./generate_synthetic_datasets.py) + +Once the synthetic datasets are generated, experiments can be executed with this [script](./experiment_with_synthetic_datasets.py) + diff --git a/scripts/experiments/synthetic_datasets/constants.py b/scripts/experiments/synthetic_datasets/constants.py new file mode 100644 index 0000000..635b524 --- /dev/null +++ b/scripts/experiments/synthetic_datasets/constants.py @@ -0,0 +1,222 @@ +NUM_YEARS = 5 +START_DATE = "01/01/2024" +END_DATE = "12/31/2028" +NUM_EVENTS_PER_YEAR = 25 +DATASETS_INFO = { + "simple_spikes_mult": { + "function_covariates": "steps", + "op": "mult", + "function_target": "simple", + "delta": 2, + "num_events": 4 * NUM_EVENTS_PER_YEAR * NUM_YEARS, + "fixed_event_pos": 15, + }, + "simple_spikes_add": { + "function_covariates": "steps", + "op": "add", + "delta": 2, + "function_target": "simple", + "num_events": 4 * NUM_EVENTS_PER_YEAR * NUM_YEARS, + "fixed_event_pos": 15, + }, + "diverse_spikes_mult": { + "function_covariates": "steps", + "op": "mult", + "delta": 2, + "function_target": "diverse", + "num_events": 4 * NUM_EVENTS_PER_YEAR * NUM_YEARS, + "fixed_event_pos": 15, + }, + "diverse_spikes_add": { + "function_covariates": "steps", + "op": "add", + "delta": 2, + "function_target": "diverse", + "num_events": 4 * NUM_EVENTS_PER_YEAR * NUM_YEARS, + "fixed_event_pos": 15, + }, + "noisy_spikes_mult": { + "function_covariates": "steps", + "op": "mult", + "delta": 2, + "function_target": "noisy", + "num_events": 4 * NUM_EVENTS_PER_YEAR * NUM_YEARS, + "fixed_event_pos": 15, + }, + "noisy_spikes_add": { + "function_covariates": "steps", + "op": "add", + "delta": 2, + "function_target": "noisy", + "num_events": 4 * NUM_EVENTS_PER_YEAR * NUM_YEARS, + "fixed_event_pos": 15, + }, + "simple_steps_mult": { + "function_covariates": "steps", + "op": "mult", + "function_target": "simple", + "delta": 15, + "num_events": NUM_EVENTS_PER_YEAR * NUM_YEARS, + }, + "simple_steps_add": { + "function_covariates": "steps", + "op": "add", + "function_target": "simple", + "delta": 15, + "num_events": NUM_EVENTS_PER_YEAR * NUM_YEARS, + }, + "diverse_steps_mult": { + "function_covariates": "steps", + "op": "mult", + "function_target": "diverse", + "delta": 15, + "num_events": NUM_EVENTS_PER_YEAR * NUM_YEARS, + }, + "diverse_steps_add": { + "function_covariates": "steps", + "op": "add", + "function_target": "diverse", + "delta": 15, + "num_events": NUM_EVENTS_PER_YEAR * NUM_YEARS, + }, + "noisy_steps_mult": { + "function_covariates": "steps", + "op": "mult", + "function_target": "noisy", + "delta": 15, + "num_events": NUM_EVENTS_PER_YEAR * NUM_YEARS, + }, + "noisy_steps_add": { + "function_covariates": "steps", + "op": "add", + "function_target": "noisy", + "delta": 15, + "num_events": NUM_EVENTS_PER_YEAR * NUM_YEARS, + }, + "simple_bells_mult": { + "function_covariates": "bells", + "op": "mult", + "function_target": "simple", + "num_events": NUM_EVENTS_PER_YEAR * NUM_YEARS, + "fixed_event_pos": 15, + }, + "simple_bells_add": { + "function_covariates": "bells", + "op": "add", + "function_target": "simple", + "num_events": NUM_EVENTS_PER_YEAR * NUM_YEARS, + "fixed_event_pos": 15, + }, + "diverse_bells_mult": { + "function_covariates": "bells", + "op": "mult", + "function_target": "diverse", + "num_events": NUM_EVENTS_PER_YEAR * NUM_YEARS, + "fixed_event_pos": 15, + }, + "diverse_bells_add": { + "function_covariates": "bells", + "op": "add", + "function_target": "diverse", + "num_events": NUM_EVENTS_PER_YEAR * NUM_YEARS, + "fixed_event_pos": 15, + }, + "noisy_bells_mult": { + "function_covariates": "bells", + "op": "mult", + "function_target": "noisy", + "num_events": NUM_EVENTS_PER_YEAR * NUM_YEARS, + "fixed_event_pos": 15, + }, + "noisy_bells_add": { + "function_covariates": "bells", + "op": "add", + "function_target": "noisy", + "num_events": NUM_EVENTS_PER_YEAR * NUM_YEARS, + "fixed_event_pos": 15, + }, + "single_bells_mult": { + "function_covariates": "bells", + "op": "mult", + "function_target": "single", + "num_events": NUM_EVENTS_PER_YEAR * NUM_YEARS, + "fixed_event_pos": 15, + }, + "single_bells_add": { + "function_covariates": "bells", + "op": "add", + "function_target": "single", + "num_events": NUM_EVENTS_PER_YEAR * NUM_YEARS, + "fixed_event_pos": 15, + }, + "single_steps_mult": { + "function_covariates": "steps", + "op": "mult", + "function_target": "single", + "delta": 15, + "num_events": NUM_EVENTS_PER_YEAR * NUM_YEARS, + }, + "single_steps_add": { + "function_covariates": "steps", + "op": "add", + "function_target": "single", + "delta": 15, + "num_events": NUM_EVENTS_PER_YEAR * NUM_YEARS, + }, + "single_spikes_mult": { + "function_covariates": "steps", + "op": "mult", + "function_target": "single", + "delta": 2, + "num_events": 4 * NUM_EVENTS_PER_YEAR * NUM_YEARS, + "fixed_event_pos": 15, + }, + "single_spikes_add": { + "function_covariates": "steps", + "op": "add", + "function_target": "single", + "delta": 2, + "num_events": 4 * NUM_EVENTS_PER_YEAR * NUM_YEARS, + "fixed_event_pos": 15, + }, + "single_arp_mult": { + "function_covariates": "arp", + "op": "mult", + "function_target": "single", + }, + "single_arp_add": { + "function_covariates": "arp", + "op": "add", + "function_target": "single", + }, + "simple_arp_mult": { + "function_covariates": "arp", + "op": "mult", + "function_target": "simple", + }, + "simple_arp_add": { + "function_covariates": "arp", + "op": "add", + "function_target": "simple", + }, + "diverse_arp_mult": { + "function_covariates": "arp", + "op": "mult", + "function_target": "diverse", + }, + "diverse_arp_add": { + "function_covariates": "arp", + "op": "add", + "function_target": "diverse", + }, + "noisy_arp_mult": { + "function_covariates": "arp", + "op": "mult", + "function_target": "noisy", + }, + "noisy_arp_add": { + "function_covariates": "arp", + "op": "add", + "function_target": "noisy", + }, +} diff --git a/scripts/experiments/synthetic_datasets/covariates.py b/scripts/experiments/synthetic_datasets/covariates.py new file mode 100644 index 0000000..2600efe --- /dev/null +++ b/scripts/experiments/synthetic_datasets/covariates.py @@ -0,0 +1,101 @@ +import numpy as np +from gluonts.dataset.artificial import recipe as rcp + + +def make_covariate_gen(covariate_func): + def covariate_gen(series, op, function_target, **kwargs): + covariates = covariate_func( + series=series, op=op, function_target=function_target, **kwargs + ) + + if op == "add": + return covariates + series, covariates + elif op == "mult": + return np.multiply(covariates, series), covariates + else: + raise NameError() + + return covariate_gen + + +@make_covariate_gen +def steps( + series: np.ndarray, + op: str, + function_target: str, + num_events: int, + delta: int, + fixed_event_pos: int = None, +) -> np.ndarray: + + length = len(series) + step_pos = np.random.randint(0, length, num_events) + + if fixed_event_pos is not None: + step_pos[-1] = length - fixed_event_pos + + step_delta = np.random.randint(1, delta, num_events) + steps_series = np.ones(length) + gamma = get_gamma(series, function_target, op) + + for p, delta in zip(step_pos, step_delta): + steps_series[p : p + delta] = gamma + + return steps_series + + +@make_covariate_gen +def bells( + series: np.ndarray, + op: str, + function_target: str, + num_events: int, + max_sigma: int = 15, + fixed_event_pos: int = None, +): + + length = len(series) + change_pos = np.random.randint(0, length, num_events) + sigma_change = np.random.uniform(1, max_sigma, num_events).reshape(-1, 1) + + if fixed_event_pos is not None: + change_pos[-1] = length - fixed_event_pos + sigma_change[-1] = 7 + + gamma = get_gamma(series, function_target, op) + t = np.tile(np.arange(length).reshape(1, -1), (num_events, 1)) + change_pos = np.tile(change_pos.reshape(-1, 1), (1, length)) + + bells_series = gamma * ( + np.exp(-np.divide(t - change_pos, sigma_change) ** 2).sum(axis=0) + 1 + ) + + return bells_series + + +@make_covariate_gen +def arp(series: np.ndarray, op: str, function_target: str): + phi1 = np.random.uniform(0, 1) + phi2 = 1 - phi1 + + # AutoRegressive Process + target = rcp.ARp(phi=[phi1, phi2], sigma=1) + x = rcp.evaluate(dict(target=target), length=len(series))["target"] + x = (x - x.min()) / (x.max() - x.min()) + + gamma = get_gamma(series, function_target, op) + arp_series = gamma * x + + return arp_series + + +def get_gamma(series: np.ndarray, function_target: str, op: str, level: int = 5): + + gamma = ( + level + if function_target in ["simple", "single"] + else np.random.uniform(1, level) + ) + scale = 1 if op == "mult" else np.mean(np.abs(series)) + gamma *= scale + return gamma diff --git a/scripts/experiments/synthetic_datasets/experiment_with_synthetic_datasets.py b/scripts/experiments/synthetic_datasets/experiment_with_synthetic_datasets.py new file mode 100644 index 0000000..05fe0a7 --- /dev/null +++ b/scripts/experiments/synthetic_datasets/experiment_with_synthetic_datasets.py @@ -0,0 +1,213 @@ +import gc +import numpy as np +import pandas as pd +import random +import shutil +import time +import torch +import yaml + + +from chronosx.chronosx import ChronosXPipeline +from chronosx.utils.chronos_dataset import ChronosDataset +from chronosx.utils.hf_data_loader import load_and_split_dataset +from chronosx.utils.utils import has_enough_observations +from functools import partial +from gluonts.dataset.split import split +from gluonts.ev.metrics import MASE, MeanWeightedSumQuantileLoss +from gluonts.itertools import Filter +from gluonts.model.evaluation import evaluate_forecasts +from pathlib import Path +from pprint import pprint + + +covariate_injection = "IIB+OIB" +chronos_model_id = "amazon/chronos-t5-small" +config_path = "../configs/synthetic_datasets.yaml" +output_dir = Path("../../../output_synthetic_experiments/finetune") +output_metrics_dir = Path("../../../output_synthetic_experiments/metrics") +output_metrics_dir.mkdir(exist_ok=True, parents=True) + +min_past = 1 +shuffle_buffer_length = 100 +num_runs = 3 +max_steps = 5000 +skip_pretrained_validation = False +learning_rate_list = [0.01, 0.001, 0.0001] + +with open(config_path) as fp: + backtest_configs = yaml.safe_load(fp) + +for dataset_config in backtest_configs: + dataset_name = dataset_config["name"] + prediction_length = dataset_config["prediction_length"] + num_covariates = 2 * len(dataset_config["covariates_fields"]) + train_dataset, test_dataset = load_and_split_dataset(backtest_config=dataset_config) + + # Load Chronos + pipeline = ChronosXPipeline( + prediction_length=prediction_length, + num_covariates=num_covariates, + covariate_injection=covariate_injection, + pretrained_model_name_or_path=chronos_model_id, + ) + + tokenizer = pipeline.tokenizer + + train_dataset = Filter( + partial( + has_enough_observations, + min_length=min_past + 2 * prediction_length, # for validation and for train + max_missing_prop=0.5, + ), + train_dataset, + ) + + quantized_val_dataset = ChronosDataset( + datasets=[train_dataset], + probabilities=[1.0], + tokenizer=tokenizer, + prediction_length=prediction_length, + min_past=min_past, + mode="validation", + ) + + train_dataset, _ = split(train_dataset, offset=-prediction_length) + quantized_train_dataset = ChronosDataset( + datasets=[train_dataset], + probabilities=[1.0], + tokenizer=tokenizer, + prediction_length=prediction_length, + min_past=min_past, + mode="training", + ).shuffle(shuffle_buffer_length=shuffle_buffer_length) + + # zero-shot evaluation on validation set + val_of_zero_shot_pretrained_model = pipeline.evaluate_model_on_validation_set( + covariate_injection=None, + quantized_val_dataset=quantized_val_dataset, + output_dir=output_dir / dataset_name, + ) + + random.seed(int(time.time())) + seed = random.randint(0, 2**32) + val_loss_per_lr = {} + mean_val_loss_per_lr = {} + + for lr in learning_rate_list: + lr_str = str(lr) + val_loss_per_run = [] + model_paths = [] + for run_id in range(num_runs): + run_id_str = str(run_id) + output_dir_lr_run_id = Path( + output_dir / dataset_name / f"lr={lr}" / f"run_id={run_id}" + ) + output_dir_lr_run_id.mkdir(exist_ok=True, parents=True) + + quantized_train_dataset = ChronosDataset( + datasets=[train_dataset], + probabilities=[1.0], + tokenizer=tokenizer, + prediction_length=prediction_length, + min_past=min_past, + mode="training", + ).shuffle( + shuffle_buffer_length=shuffle_buffer_length, + random_seed=run_id + seed, + ) + + pipeline = ChronosXPipeline( + prediction_length=prediction_length, + num_covariates=num_covariates, + covariate_injection=covariate_injection, + pretrained_model_name_or_path=chronos_model_id, + ) + + val_loss, save_model_path = pipeline.finetune( + output_dir_lr_run_id, + quantized_train_dataset, + lr=lr, + quantized_val_dataset=quantized_val_dataset, + skip_pretrained_validation=skip_pretrained_validation, + max_steps=max_steps, + ) + + val_loss_per_run.append(val_loss) + model_paths.append(save_model_path) + + mean_val_loss = np.mean(val_loss_per_run) + val_loss_per_lr[f"{lr}"] = val_loss_per_run + mean_val_loss_per_lr[f"{lr}"] = mean_val_loss + print( + f"lr: {lr} - val_loss_per_run: {val_loss_per_run} - mean_val_loss: {mean_val_loss}" + ) + + print("val_loss_per_lr") + pprint(val_loss_per_lr) + + print("mean_val_loss_per_lr") + pprint(mean_val_loss_per_lr) + + best_val_loss = np.inf + for lr_str, val_loss in mean_val_loss_per_lr.items(): + if val_loss < best_val_loss: + best_val_loss = val_loss + best_lr_str = lr_str + + # evaluate model with best mean validation loss + lr = float(best_lr_str) + result_rows = [] + for run_id in range(num_runs): + pretrained_model_path = ( + output_dir + / dataset_name + / f"lr={lr}" + / f"run_id={run_id}" + / "final-checkpoint" + ) + pipeline = ChronosXPipeline( + prediction_length=prediction_length, + num_covariates=num_covariates, + covariate_injection=covariate_injection, + pretrained_model_name_or_path=pretrained_model_path, + ) + + pipeline.chronosx.eval() + forecasts = pipeline.generate_forecasts( + test_dataset.input, + ) + + metrics = ( + evaluate_forecasts( + forecasts, + test_data=test_dataset, + metrics=[ + MASE(), + MeanWeightedSumQuantileLoss( + np.arange(0.05, 1, 0.05).round(2).tolist() + ), + ], + batch_size=5000, + ) + .reset_index(drop=True) + .to_dict(orient="records") + ) + + result_rows.append( + { + "dataset": dataset_config["name"], + "covariate_injection": covariate_injection, + **metrics[0], + } + ) + + df = pd.DataFrame(result_rows) + df = df.set_index(["dataset", "covariate_injection"]) + pprint(df.mean()) + df.to_csv(output_metrics_dir / f"{dataset_name.split('/')[-1]}_max_steps={max_steps}.csv") + + shutil.rmtree(output_dir / dataset_name) + del pipeline, quantized_train_dataset, quantized_val_dataset, forecasts + gc.collect() + torch.cuda.empty_cache() diff --git a/scripts/experiments/synthetic_datasets/generate_synthetic_datasets.py b/scripts/experiments/synthetic_datasets/generate_synthetic_datasets.py new file mode 100644 index 0000000..3e5bdb8 --- /dev/null +++ b/scripts/experiments/synthetic_datasets/generate_synthetic_datasets.py @@ -0,0 +1,62 @@ +from pathlib import Path +import datasets +import numpy as np +import os +import pandas as pd + +from constants import START_DATE, END_DATE, DATASETS_INFO +from covariates import steps, bells, arp +from target import single, simple, diverse, noisy + + +def make_positive(x): + return x - x.min() + + +num_entries = 100 + + +def generate(): + output_dir = Path("./datasets") + output_dir.mkdir(exist_ok=True, parents=True) + + timestamp = np.array( + pd.date_range( + start=pd.to_datetime(START_DATE), end=pd.to_datetime(END_DATE), freq="D" + ) + ) + + for dataset_name, dataset_info in DATASETS_INFO.items(): + + print(f"dataset_name:{dataset_name}") + function_target = dataset_info.pop("function_target") + function_covariates = dataset_info.pop("function_covariates") + operator = dataset_info.pop("op") + + target_generator = eval(function_target) + covariate_generator = eval(function_covariates) + + entries = [] + for _ in range(num_entries): + target_before_covariates = target_generator(L=len(timestamp)) + target_after_covariates, covariate = covariate_generator( + series=target_before_covariates, + op=operator, + function_target=function_target, + **dataset_info, + ) + target_after_covariates = make_positive(target_after_covariates) + entry = { + "timestamp": timestamp, + "target": target_after_covariates, + "covariate": covariate, + } + entries.append(entry) + + dest_file = os.path.join(output_dir, dataset_name) + ds = datasets.Dataset.from_list(entries) + ds.save_to_disk(dest_file) + + +if __name__ == "__main__": + generate() diff --git a/scripts/experiments/synthetic_datasets/target.py b/scripts/experiments/synthetic_datasets/target.py new file mode 100644 index 0000000..aa61fce --- /dev/null +++ b/scripts/experiments/synthetic_datasets/target.py @@ -0,0 +1,53 @@ +import numpy as np + + +def seasonal(t): + a1, a2, a3 = np.random.uniform(-5, 5, 3) # amplitude + b1, b2, b3 = np.random.uniform(0, 1, 3) # phase + series = ( + a1 * np.sin(2 * np.pi * (t / 7 + b1)) + + a2 * np.sin(2 * np.pi * (t / 30 + b2)) + + a3 * np.sin(2 * np.pi * (t / 365 + b3)) + ) + return series + + +def trend(t): + a4, a5 = np.random.uniform(-1, 1, 2) + series = a4 + a5 * t / 365 + return series + + +def noise(y): + return np.random.normal(0, 0.25 * np.mean(np.abs(y)), len(y)) + + +def simple_seasonal(t): + a1 = np.random.uniform(-5, 5, 1) + series = a1 * np.sin(2 * np.pi * t / 7) + return series + + +def simple(L): + t = np.arange(L) + y = simple_seasonal(t) + return y + + +def single(L): + t = np.arange(L) + y = np.sin(2 * np.pi * t / 7) + return y + + +def diverse(L): + t = np.arange(L) + y = seasonal(t) + trend(t) + return y + + +def noisy(L): + t = np.arange(L) + y = seasonal(t) + trend(t) + y += noise(y) + return y diff --git a/scripts/kernel-synth.py b/scripts/kernel-synth.py deleted file mode 100644 index da2ffd3..0000000 --- a/scripts/kernel-synth.py +++ /dev/null @@ -1,200 +0,0 @@ -# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -# SPDX-License-Identifier: Apache-2.0 - -import argparse -import functools -from pathlib import Path -from typing import Optional - -import numpy as np -from gluonts.dataset.arrow import ArrowWriter -from joblib import Parallel, delayed -from sklearn.gaussian_process import GaussianProcessRegressor -from sklearn.gaussian_process.kernels import ( - RBF, - ConstantKernel, - DotProduct, - ExpSineSquared, - Kernel, - RationalQuadratic, - WhiteKernel, -) -from tqdm.auto import tqdm - -LENGTH = 1024 -KERNEL_BANK = [ - ExpSineSquared(periodicity=24 / LENGTH), # H - ExpSineSquared(periodicity=48 / LENGTH), # 0.5H - ExpSineSquared(periodicity=96 / LENGTH), # 0.25H - ExpSineSquared(periodicity=24 * 7 / LENGTH), # H - ExpSineSquared(periodicity=48 * 7 / LENGTH), # 0.5H - ExpSineSquared(periodicity=96 * 7 / LENGTH), # 0.25H - ExpSineSquared(periodicity=7 / LENGTH), # D - ExpSineSquared(periodicity=14 / LENGTH), # 0.5D - ExpSineSquared(periodicity=30 / LENGTH), # D - ExpSineSquared(periodicity=60 / LENGTH), # 0.5D - ExpSineSquared(periodicity=365 / LENGTH), # D - ExpSineSquared(periodicity=365 * 2 / LENGTH), # 0.5D - ExpSineSquared(periodicity=4 / LENGTH), # W - ExpSineSquared(periodicity=26 / LENGTH), # W - ExpSineSquared(periodicity=52 / LENGTH), # W - ExpSineSquared(periodicity=4 / LENGTH), # M - ExpSineSquared(periodicity=6 / LENGTH), # M - ExpSineSquared(periodicity=12 / LENGTH), # M - ExpSineSquared(periodicity=4 / LENGTH), # Q - ExpSineSquared(periodicity=4 * 10 / LENGTH), # Q - ExpSineSquared(periodicity=10 / LENGTH), # Y - DotProduct(sigma_0=0.0), - DotProduct(sigma_0=1.0), - DotProduct(sigma_0=10.0), - RBF(length_scale=0.1), - RBF(length_scale=1.0), - RBF(length_scale=10.0), - RationalQuadratic(alpha=0.1), - RationalQuadratic(alpha=1.0), - RationalQuadratic(alpha=10.0), - WhiteKernel(noise_level=0.1), - WhiteKernel(noise_level=1.0), - ConstantKernel(), -] - - -def random_binary_map(a: Kernel, b: Kernel): - """ - Applies a random binary operator (+ or *) with equal probability - on kernels ``a`` and ``b``. - - Parameters - ---------- - a - A GP kernel. - b - A GP kernel. - - Returns - ------- - The composite kernel `a + b` or `a * b`. - """ - binary_maps = [lambda x, y: x + y, lambda x, y: x * y] - return np.random.choice(binary_maps)(a, b) - - -def sample_from_gp_prior( - kernel: Kernel, X: np.ndarray, random_seed: Optional[int] = None -): - """ - Draw a sample from a GP prior. - - Parameters - ---------- - kernel - The GP covaraince kernel. - X - The input "time" points. - random_seed, optional - The random seed for sampling, by default None. - - Returns - ------- - A time series sampled from the GP prior. - """ - if X.ndim == 1: - X = X[:, None] - - assert X.ndim == 2 - gpr = GaussianProcessRegressor(kernel=kernel) - ts = gpr.sample_y(X, n_samples=1, random_state=random_seed) - - return ts - - -def sample_from_gp_prior_efficient( - kernel: Kernel, - X: np.ndarray, - random_seed: Optional[int] = None, - method: str = "eigh", -): - """ - Draw a sample from a GP prior. An efficient version that allows specification - of the sampling method. The default sampling method used in GaussianProcessRegressor - is based on SVD which is significantly slower that alternatives such as `eigh` and - `cholesky`. - - Parameters - ---------- - kernel - The GP covaraince kernel. - X - The input "time" points. - random_seed, optional - The random seed for sampling, by default None. - method, optional - The sampling method for multivariate_normal, by default `eigh`. - - Returns - ------- - A time series sampled from the GP prior. - """ - if X.ndim == 1: - X = X[:, None] - - assert X.ndim == 2 - - cov = kernel(X) - ts = np.random.default_rng(seed=random_seed).multivariate_normal( - mean=np.zeros(X.shape[0]), cov=cov, method=method - ) - - return ts - - -def generate_time_series(max_kernels: int = 5): - """Generate a synthetic time series from KernelSynth. - - Parameters - ---------- - max_kernels, optional - The maximum number of base kernels to use for each time series, by default 5 - - Returns - ------- - A time series generated by KernelSynth. - """ - while True: - X = np.linspace(0, 1, LENGTH) - - # Randomly select upto max_kernels kernels from the KERNEL_BANK - selected_kernels = np.random.choice( - KERNEL_BANK, np.random.randint(1, max_kernels + 1), replace=True - ) - - # Combine the sampled kernels using random binary operators - kernel = functools.reduce(random_binary_map, selected_kernels) - - # Sample a time series from the GP prior - try: - ts = sample_from_gp_prior(kernel=kernel, X=X) - except np.linalg.LinAlgError as err: - print("Error caught:", err) - continue - - # The timestamp is arbitrary - return {"start": np.datetime64("2000-01-01 00:00", "s"), "target": ts.squeeze()} - - -if __name__ == "__main__": - parser = argparse.ArgumentParser() - parser.add_argument("-N", "--num-series", type=int, default=1000_000) - parser.add_argument("-J", "--max-kernels", type=int, default=5) - args = parser.parse_args() - path = Path(__file__).parent / "kernelsynth-data.arrow" - - generated_dataset = Parallel(n_jobs=-1)( - delayed(generate_time_series)(max_kernels=args.max_kernels) - for _ in tqdm(range(args.num_series)) - ) - - ArrowWriter(compression="lz4").write_to_file( - generated_dataset, - path=path, - ) diff --git a/scripts/training/configs/chronos-gpt2.yaml b/scripts/training/configs/chronos-gpt2.yaml deleted file mode 100644 index 4d917ad..0000000 --- a/scripts/training/configs/chronos-gpt2.yaml +++ /dev/null @@ -1,35 +0,0 @@ -training_data_paths: -- "/home/ubuntu/tsmixup-data.arrow" -- "/home/ubuntu/kernelsynth-data.arrow" -probability: -- 0.9 -- 0.1 -context_length: 512 -prediction_length: 64 -min_past: 60 -max_steps: 200_000 -save_steps: 100_000 -log_steps: 500 -per_device_train_batch_size: 32 -learning_rate: 0.001 -optim: adamw_torch_fused -num_samples: 20 -shuffle_buffer_length: 100_000 -gradient_accumulation_steps: 1 -model_id: openai-community/gpt2 -model_type: causal -random_init: false -tie_embeddings: false -output_dir: ./output/ -tf32: true -torch_compile: true -tokenizer_class: "MeanScaleUniformBins" -tokenizer_kwargs: - low_limit: -15.0 - high_limit: 15.0 -n_tokens: 4096 -lr_scheduler_type: linear -warmup_ratio: 0.0 -dataloader_num_workers: 1 -max_missing_prop: 0.1 -use_eos_token: true diff --git a/scripts/training/configs/chronos-t5-base.yaml b/scripts/training/configs/chronos-t5-base.yaml deleted file mode 100644 index c7b56f7..0000000 --- a/scripts/training/configs/chronos-t5-base.yaml +++ /dev/null @@ -1,35 +0,0 @@ -training_data_paths: -- "/home/ubuntu/tsmixup-data.arrow" -- "/home/ubuntu/kernelsynth-data.arrow" -probability: -- 0.9 -- 0.1 -context_length: 512 -prediction_length: 64 -min_past: 60 -max_steps: 200_000 -save_steps: 100_000 -log_steps: 500 -per_device_train_batch_size: 32 -learning_rate: 0.001 -optim: adamw_torch_fused -num_samples: 20 -shuffle_buffer_length: 100_000 -gradient_accumulation_steps: 1 -model_id: google/t5-efficient-base -model_type: seq2seq -random_init: true -tie_embeddings: true -output_dir: ./output/ -tf32: true -torch_compile: true -tokenizer_class: "MeanScaleUniformBins" -tokenizer_kwargs: - low_limit: -15.0 - high_limit: 15.0 -n_tokens: 4096 -lr_scheduler_type: linear -warmup_ratio: 0.0 -dataloader_num_workers: 1 -max_missing_prop: 0.9 -use_eos_token: true diff --git a/scripts/training/configs/chronos-t5-large.yaml b/scripts/training/configs/chronos-t5-large.yaml deleted file mode 100644 index 189013c..0000000 --- a/scripts/training/configs/chronos-t5-large.yaml +++ /dev/null @@ -1,35 +0,0 @@ -training_data_paths: -- "/home/ubuntu/tsmixup-data.arrow" -- "/home/ubuntu/kernelsynth-data.arrow" -probability: -- 0.9 -- 0.1 -context_length: 512 -prediction_length: 64 -min_past: 60 -max_steps: 200_000 -save_steps: 100_000 -log_steps: 500 -per_device_train_batch_size: 8 -learning_rate: 0.001 -optim: adamw_torch_fused -num_samples: 20 -shuffle_buffer_length: 100_000 -gradient_accumulation_steps: 4 -model_id: google/t5-efficient-large -model_type: seq2seq -random_init: true -tie_embeddings: true -output_dir: ./output/ -tf32: true -torch_compile: true -tokenizer_class: "MeanScaleUniformBins" -tokenizer_kwargs: - low_limit: -15.0 - high_limit: 15.0 -n_tokens: 4096 -lr_scheduler_type: linear -warmup_ratio: 0.0 -dataloader_num_workers: 1 -max_missing_prop: 0.9 -use_eos_token: true diff --git a/scripts/training/configs/chronos-t5-mini.yaml b/scripts/training/configs/chronos-t5-mini.yaml deleted file mode 100644 index e99d0fc..0000000 --- a/scripts/training/configs/chronos-t5-mini.yaml +++ /dev/null @@ -1,35 +0,0 @@ -training_data_paths: -- "/home/ubuntu/tsmixup-data.arrow" -- "/home/ubuntu/kernelsynth-data.arrow" -probability: -- 0.9 -- 0.1 -context_length: 512 -prediction_length: 64 -min_past: 60 -max_steps: 200_000 -save_steps: 100_000 -log_steps: 500 -per_device_train_batch_size: 32 -learning_rate: 0.001 -optim: adamw_torch_fused -num_samples: 20 -shuffle_buffer_length: 100_000 -gradient_accumulation_steps: 1 -model_id: google/t5-efficient-mini -model_type: seq2seq -random_init: true -tie_embeddings: true -output_dir: ./output/ -tf32: true -torch_compile: true -tokenizer_class: "MeanScaleUniformBins" -tokenizer_kwargs: - low_limit: -15.0 - high_limit: 15.0 -n_tokens: 4096 -lr_scheduler_type: linear -warmup_ratio: 0.0 -dataloader_num_workers: 1 -max_missing_prop: 0.9 -use_eos_token: true diff --git a/scripts/training/configs/chronos-t5-small.yaml b/scripts/training/configs/chronos-t5-small.yaml deleted file mode 100644 index 873f483..0000000 --- a/scripts/training/configs/chronos-t5-small.yaml +++ /dev/null @@ -1,35 +0,0 @@ -training_data_paths: -- "/home/ubuntu/tsmixup-data.arrow" -- "/home/ubuntu/kernelsynth-data.arrow" -probability: -- 0.9 -- 0.1 -context_length: 512 -prediction_length: 64 -min_past: 60 -max_steps: 200_000 -save_steps: 100_000 -log_steps: 500 -per_device_train_batch_size: 32 -learning_rate: 0.001 -optim: adamw_torch_fused -num_samples: 20 -shuffle_buffer_length: 100_000 -gradient_accumulation_steps: 1 -model_id: google/t5-efficient-small -model_type: seq2seq -random_init: true -tie_embeddings: true -output_dir: ./output/ -tf32: true -torch_compile: true -tokenizer_class: "MeanScaleUniformBins" -tokenizer_kwargs: - low_limit: -15.0 - high_limit: 15.0 -n_tokens: 4096 -lr_scheduler_type: linear -warmup_ratio: 0.0 -dataloader_num_workers: 1 -max_missing_prop: 0.9 -use_eos_token: true diff --git a/scripts/training/configs/chronos-t5-tiny.yaml b/scripts/training/configs/chronos-t5-tiny.yaml deleted file mode 100644 index 18cd9b1..0000000 --- a/scripts/training/configs/chronos-t5-tiny.yaml +++ /dev/null @@ -1,35 +0,0 @@ -training_data_paths: -- "/home/ubuntu/tsmixup-data.arrow" -- "/home/ubuntu/kernelsynth-data.arrow" -probability: -- 0.9 -- 0.1 -context_length: 512 -prediction_length: 64 -min_past: 60 -max_steps: 200_000 -save_steps: 100_000 -log_steps: 500 -per_device_train_batch_size: 32 -learning_rate: 0.001 -optim: adamw_torch_fused -num_samples: 20 -shuffle_buffer_length: 100_000 -gradient_accumulation_steps: 1 -model_id: google/t5-efficient-tiny -model_type: seq2seq -random_init: true -tie_embeddings: true -output_dir: ./output/ -tf32: true -torch_compile: true -tokenizer_class: "MeanScaleUniformBins" -tokenizer_kwargs: - low_limit: -15.0 - high_limit: 15.0 -n_tokens: 4096 -lr_scheduler_type: linear -warmup_ratio: 0.0 -dataloader_num_workers: 1 -max_missing_prop: 0.9 -use_eos_token: true diff --git a/scripts/training/train.py b/scripts/training/train.py deleted file mode 100644 index c16092e..0000000 --- a/scripts/training/train.py +++ /dev/null @@ -1,702 +0,0 @@ -# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -# SPDX-License-Identifier: Apache-2.0 - -import ast -import logging -import os -import re -import sys -import json -import itertools -import random -from copy import deepcopy -from pathlib import Path -from functools import partial -from typing import List, Iterator, Optional, Dict - -import typer -from typer_config import use_yaml_config -import numpy as np -import torch -import torch.distributed as dist -from torch.utils.data import IterableDataset, get_worker_info -import transformers -from transformers import ( - AutoModelForSeq2SeqLM, - AutoModelForCausalLM, - AutoConfig, - T5Config, - Trainer, - TrainingArguments, -) -import accelerate -import gluonts -from gluonts.dataset.common import FileDataset -from gluonts.itertools import Cyclic, Map, Filter -from gluonts.transform import ( - FilterTransformation, - TestSplitSampler, - ValidationSplitSampler, - InstanceSplitter, - ExpectedNumInstanceSampler, - MissingValueImputation, - LeavesMissingValues, - LastValueImputation, -) - -from chronos import ChronosConfig, ChronosTokenizer - - -app = typer.Typer(pretty_exceptions_enable=False) - - -def is_main_process() -> bool: - """ - Check if we're on the main process. - """ - if not dist.is_torchelastic_launched(): - return True - return int(os.environ["RANK"]) == 0 - - -def log_on_main(msg: str, logger: logging.Logger, log_level: int = logging.INFO): - """ - Log the given message using the given logger, if we're on the main process. - """ - if is_main_process(): - logger.log(log_level, msg) - - -def get_training_job_info() -> Dict: - """ - Returns info about this training job. - """ - job_info = {} - - # CUDA info - job_info["cuda_available"] = torch.cuda.is_available() - if torch.cuda.is_available(): - job_info["device_count"] = torch.cuda.device_count() - - job_info["device_names"] = { - idx: torch.cuda.get_device_name(idx) - for idx in range(torch.cuda.device_count()) - } - job_info["mem_info"] = { - idx: torch.cuda.mem_get_info(device=idx) - for idx in range(torch.cuda.device_count()) - } - - # DDP info - job_info["torchelastic_launched"] = dist.is_torchelastic_launched() - - if dist.is_torchelastic_launched(): - job_info["world_size"] = dist.get_world_size() - - # Versions - job_info["python_version"] = sys.version.replace("\n", " ") - job_info["torch_version"] = torch.__version__ - job_info["numpy_version"] = np.__version__ - job_info["gluonts_version"] = gluonts.__version__ - job_info["transformers_version"] = transformers.__version__ - job_info["accelerate_version"] = accelerate.__version__ - - return job_info - - -def save_training_info(ckpt_path: Path, training_config: Dict): - """ - Save info about this training job in a json file for documentation. - """ - assert ckpt_path.is_dir() - with open(ckpt_path / "training_info.json", "w") as fp: - json.dump( - {"training_config": training_config, "job_info": get_training_job_info()}, - fp, - indent=4, - ) - - -def get_next_path( - base_fname: str, - base_dir: Path, - file_type: str = "yaml", - separator: str = "-", -): - """ - Gets the next available path in a directory. For example, if `base_fname="results"` - and `base_dir` has files ["results-0.yaml", "results-1.yaml"], this function returns - "results-2.yaml". - """ - if file_type == "": - # Directory - items = filter( - lambda x: x.is_dir() and re.match(f"^{base_fname}{separator}\\d+$", x.stem), - base_dir.glob("*"), - ) - else: - # File - items = filter( - lambda x: re.match(f"^{base_fname}{separator}\\d+$", x.stem), - base_dir.glob(f"*.{file_type}"), - ) - run_nums = list( - map(lambda x: int(x.stem.replace(base_fname + separator, "")), items) - ) + [-1] - - next_num = max(run_nums) + 1 - fname = f"{base_fname}{separator}{next_num}" + ( - f".{file_type}" if file_type != "" else "" - ) - - return base_dir / fname - - -def load_model( - model_id="google/t5-efficient-tiny", - model_type="seq2seq", - vocab_size=4096, - random_init=False, - tie_embeddings=False, - pad_token_id=0, - eos_token_id=1, -): - """ - Load the specified HuggingFace model, adjusting the vocabulary - size, special token IDs, and initialization options. - - This allows to set a model up for training on a new vocabulary - of tokens. - """ - assert model_type in ["seq2seq", "causal"] - AutoModelClass = ( - AutoModelForSeq2SeqLM if model_type == "seq2seq" else AutoModelForCausalLM - ) - if random_init: - log_on_main("Using random initialization", logger) - config = AutoConfig.from_pretrained(model_id) - if isinstance(config, T5Config): - # The default initializer_factor (1.0) in transformers is too large - config.initializer_factor = 0.05 - config.tie_word_embeddings = tie_embeddings - model = AutoModelClass.from_config(config) - else: - log_on_main(f"Using pretrained initialization from {model_id}", logger) - model = AutoModelClass.from_pretrained(model_id) - - model.resize_token_embeddings(vocab_size) - - model.config.pad_token_id = model.generation_config.pad_token_id = pad_token_id - model.config.eos_token_id = model.generation_config.eos_token_id = eos_token_id - - return model - - -def has_enough_observations( - entry: dict, min_length: int = 0, max_missing_prop: float = 1.0 -) -> bool: - """ - Check if the given entry has enough observations in the ``"target"`` attribute. - - Parameters - ---------- - entry - The data entry (dictionary) to be tested. - min_length - The minimum length the ``"target"`` attribute must have. - max_missing_prop - The maximum proportion of missing data allowed in the ``"target"`` - attribute. - """ - if ( - len(entry["target"]) >= min_length - and np.isnan(entry["target"]).mean() <= max_missing_prop - ): - return True - return False - - -class PseudoShuffledIterableDataset(IterableDataset): - """ - Shuffle entries from an iterable by temporarily accumulating them - in an intermediate buffer. - - Parameters - ---------- - base_dataset - The original iterable object, representing the dataset. - shuffle_buffer_length - Size of the buffer use to shuffle entries from the base dataset. - """ - - def __init__(self, base_dataset, shuffle_buffer_length: int = 100) -> None: - super().__init__() - self.base_dataset = base_dataset - self.shuffle_buffer_length = shuffle_buffer_length - self.generator = torch.Generator() - - def __iter__(self): - shuffle_buffer = [] - - for element in self.base_dataset: - shuffle_buffer.append(element) - if len(shuffle_buffer) >= self.shuffle_buffer_length: - idx = torch.randint( - len(shuffle_buffer), size=(), generator=self.generator - ) - yield shuffle_buffer.pop(idx) - - while shuffle_buffer: - idx = torch.randint(len(shuffle_buffer), size=(), generator=self.generator) - yield shuffle_buffer.pop(idx) - - -class ShuffleMixin: - """ - Mix-in class that datasets can inherit from to get - shuffling functionality. - """ - - def shuffle(self, shuffle_buffer_length: int = 100): - return PseudoShuffledIterableDataset(self, shuffle_buffer_length) - - -class ChronosDataset(IterableDataset, ShuffleMixin): - """ - Dataset wrapper, using a ``ChronosTokenizer`` to turn data from a time series - into a HuggingFace-compatible set of ``input_ids``, ``attention_mask`` and - ``labels``. - - Entries from the original datasets are assumed to have a ``"start"`` attribute - (of type ``pd.Period``), and a ``"target"`` attribute (of type ``np.ndarray``). - - Parameters - ---------- - datasets - Datasets containing the original time series data. - probabilities - In training mode, data will be sampled from each of the original datasets - with these probabilities. - tokenizer - Tokenizer to be used to turn sequences of real numbers into token IDs. - context_length - Samples context will be limited to this length. - prediction_length - Samples labels will be limited to this length. - drop_prob - In training mode, observations from a sample will be turned into ``np.nan``, - i.e. turned into missing values, with this probability. - min_past - Data samples will be considered only if there's at least ``min_past``-many - historical observations. - mode - One of ``"training"``, ``"validation"``, or ``"test"``. - np_dtype - Numpy float data type. - """ - - def __init__( - self, - datasets: list, - probabilities: List[float], - tokenizer: ChronosTokenizer, - context_length: int = 512, - prediction_length: int = 64, - drop_prob: float = 0.2, - min_past: Optional[int] = None, - model_type: str = "seq2seq", - imputation_method: Optional[MissingValueImputation] = None, - mode: str = "training", - np_dtype=np.float32, - ) -> None: - super().__init__() - - assert len(probabilities) == len(datasets) - assert mode in ("training", "validation", "test") - assert model_type in ("seq2seq", "causal") - - self.datasets = datasets - self.probabilities = probabilities - self.tokenizer = tokenizer - self.context_length = context_length - self.prediction_length = prediction_length - self.drop_prob = drop_prob if model_type == "seq2seq" else 0.0 - self.min_past = min_past or prediction_length - self.model_type = model_type - self.imputation_method = imputation_method or LeavesMissingValues() - self.mode = mode - self.np_dtype = np_dtype - - def preprocess_entry(self, entry: dict, mode: str) -> dict: - entry = {f: entry[f] for f in ["start", "target"]} - entry["target"] = np.asarray(entry["target"], dtype=self.np_dtype) - assert entry["target"].ndim == 1, f"got {entry['target'].ndim=}, expected 1" - - if self.model_type == "causal": - # Causal models do not play nice with missing values, so it is - # recommended to use an imputation method, e.g., LastValueImputation - entry["target"] = self.imputation_method(entry["target"]) - - if mode == "training" and self.drop_prob > 0: - target = entry["target"].copy() - drop_p = np.random.uniform(low=0.0, high=self.drop_prob) - mask = np.random.choice( - [True, False], size=len(target), p=[drop_p, 1 - drop_p] - ) - target[mask] = np.nan - entry["target"] = target - - return entry - - def _create_instance_splitter(self, mode: str): - assert mode in ["training", "test", "validation"] - - instance_sampler = { - "training": ExpectedNumInstanceSampler( - num_instances=1.0, - min_instances=1, - min_past=self.min_past, - min_future=self.prediction_length, - ), - "test": TestSplitSampler(), - "validation": ValidationSplitSampler(min_future=self.prediction_length), - }[mode] - - return InstanceSplitter( - target_field="target", - is_pad_field="is_pad", - start_field="start", - forecast_start_field="forecast_start", - instance_sampler=instance_sampler, - past_length=self.context_length, - future_length=self.prediction_length, - dummy_value=np.nan, - ) - - def create_training_data(self, data): - data = Cyclic(data) - split_transform = self._create_instance_splitter( - "training" - ) + FilterTransformation( - condition=lambda entry: (~np.isnan(entry["past_target"])).sum() > 0 - ) - data = split_transform.apply(data, is_train=True) - return data - - def create_test_data(self, data): - data = self._create_instance_splitter("test").apply(data, is_train=False) - return data - - def create_validation_data(self, data): - data = self._create_instance_splitter("validation").apply(data, is_train=False) - return data - - def to_hf_format(self, entry: dict) -> dict: - past_target = torch.tensor(entry["past_target"]).unsqueeze(0) - input_ids, attention_mask, scale = self.tokenizer.context_input_transform( - past_target - ) - future_target = torch.tensor(entry["future_target"]).unsqueeze(0) - labels, labels_mask = self.tokenizer.label_input_transform(future_target, scale) - labels[labels_mask == 0] = -100 - - if self.model_type == "causal": - # The InstanceSplitter pads time series on the left to be equal to the - # context_length. However, certain models (e.g., GPT2) with absolute - # position embeddings should not be trained with left padding. - # The following piece of code moves padding from left to right. - - assert input_ids.shape[-1] == entry["past_is_pad"].shape[0] - - # Find the index where padding starts - pad_start_idx = np.searchsorted(1 - entry["past_is_pad"], 1) - padded_input_ids, obs_input_ids = torch.tensor_split( - input_ids, [pad_start_idx], dim=-1 - ) - padded_attention_mask, obs_attention_mask = torch.tensor_split( - attention_mask, [pad_start_idx], dim=-1 - ) - - # Move padding to the right - input_ids = torch.cat( - [ - obs_input_ids, - labels, - padded_input_ids, - ], - axis=-1, - ) - attention_mask = torch.cat( - [ - obs_attention_mask, - labels_mask, - padded_attention_mask, - ], - axis=-1, - ) - - # labels for causal models are same as the input_ids. - # Internally transformers shifts the labels by one during training. - labels = input_ids.clone() - input_ids[~attention_mask] = self.tokenizer.config.pad_token_id - labels[~attention_mask] = -100 - - return { - "input_ids": input_ids.squeeze(0), - "attention_mask": attention_mask.squeeze(0), - "labels": labels.squeeze(0), - } - - def __iter__(self) -> Iterator: - preprocessed_datasets = [ - Map( - partial(self.preprocess_entry, mode=self.mode), - dataset, - ) - for dataset in self.datasets - ] - - if self.mode == "training": - iterables = [ - self.create_training_data(dataset) for dataset in preprocessed_datasets - ] - elif self.mode == "test": - iterables = [ - self.create_test_data(dataset) for dataset in preprocessed_datasets - ] - else: - iterables = [ - self.create_validation_data(dataset) - for dataset in preprocessed_datasets - ] - - worker_info = get_worker_info() - if worker_info is None: - probs = list(self.probabilities) - else: - worker_id = worker_info.id - num_workers = worker_info.num_workers - iterables = list(itertools.islice(iterables, worker_id, None, num_workers)) - probs = list( - itertools.islice(self.probabilities, worker_id, None, num_workers) - ) - - probs = [prob / sum(probs) for prob in probs] - - iterators = list(map(iter, iterables)) - if self.mode == "training": - while True: - idx = np.random.choice(range(len(iterators)), p=probs) - try: - yield self.to_hf_format(next(iterators[idx])) - except StopIteration: - probs[idx] = 0 - if sum(probs) == 0: - return - probs = [prob / sum(probs) for prob in probs] - else: - for entry in itertools.chain(*iterators): - yield self.to_hf_format(entry) - - -@app.command() -@use_yaml_config(param_name="config") -def main( - training_data_paths: str, - probability: Optional[str] = None, - context_length: int = 512, - prediction_length: int = 64, - min_past: int = 64, - max_steps: int = 200_000, - save_steps: int = 50_000, - log_steps: int = 500, - per_device_train_batch_size: int = 32, - learning_rate: float = 1e-3, - optim: str = "adamw_torch_fused", - shuffle_buffer_length: int = 100, - gradient_accumulation_steps: int = 2, - model_id: str = "google/t5-efficient-tiny", - model_type: str = "seq2seq", - random_init: bool = False, - tie_embeddings: bool = False, - output_dir: str = "./output/", - tf32: bool = True, - torch_compile: bool = True, - tokenizer_class: str = "MeanScaleUniformBins", - tokenizer_kwargs: str = "{'low_limit': -15.0, 'high_limit': 15.0}", - n_tokens: int = 4096, - n_special_tokens: int = 2, - pad_token_id: int = 0, - eos_token_id: int = 1, - use_eos_token: bool = True, - lr_scheduler_type: str = "linear", - warmup_ratio: float = 0.0, - dataloader_num_workers: int = 1, - max_missing_prop: float = 0.9, - num_samples: int = 20, - temperature: float = 1.0, - top_k: int = 50, - top_p: float = 1.0, - seed: Optional[int] = None, -): - if tf32 and not ( - torch.cuda.is_available() and torch.cuda.get_device_capability()[0] >= 8 - ): - # TF32 floating point format is available only on NVIDIA GPUs - # with compute capability 8 and above. See link for details. - # https://docs.nvidia.com/cuda/cuda-c-programming-guide/index.html#compute-capability-8-x - log_on_main( - "TF32 format is only available on devices with compute capability >= 8. " - "Setting tf32 to False.", - logger, - ) - tf32 = False - - if seed is None: - seed = random.randint(0, 2**32) - - log_on_main(f"Using SEED: {seed}", logger) - transformers.set_seed(seed=seed) - - raw_training_config = deepcopy(locals()) - output_dir = Path(output_dir) - training_data_paths = ast.literal_eval(training_data_paths) - assert isinstance(training_data_paths, list) - - if isinstance(probability, str): - probability = ast.literal_eval(probability) - elif probability is None: - probability = [1.0 / len(training_data_paths)] * len(training_data_paths) - assert isinstance(probability, list) - - assert len(training_data_paths) == len(probability) - - if dataloader_num_workers > len(training_data_paths): - log_on_main( - f"Setting the number of data loader workers to {len(training_data_paths)}, " - f"instead of {dataloader_num_workers}.", - logger, - ) - dataloader_num_workers = len(training_data_paths) - - if isinstance(tokenizer_kwargs, str): - tokenizer_kwargs = ast.literal_eval(tokenizer_kwargs) - assert isinstance(tokenizer_kwargs, dict) - - assert model_type in ["seq2seq", "causal"] - - output_dir = get_next_path("run", base_dir=output_dir, file_type="") - - log_on_main(f"Logging dir: {output_dir}", logger) - log_on_main( - f"Loading and filtering {len(training_data_paths)} datasets " - f"for training: {training_data_paths}", - logger, - ) - - log_on_main( - f"Mixing probabilities: {probability}", - logger, - ) - - train_datasets = [ - Filter( - partial( - has_enough_observations, - min_length=min_past + prediction_length, - max_missing_prop=max_missing_prop, - ), - FileDataset(path=Path(data_path), freq="h"), - ) - for data_path in training_data_paths - ] - - log_on_main("Initializing model", logger) - - model = load_model( - model_id=model_id, - model_type=model_type, - vocab_size=n_tokens, - random_init=random_init, - tie_embeddings=tie_embeddings, - pad_token_id=pad_token_id, - eos_token_id=eos_token_id, - ) - - chronos_config = ChronosConfig( - tokenizer_class=tokenizer_class, - tokenizer_kwargs=tokenizer_kwargs, - n_tokens=n_tokens, - n_special_tokens=n_special_tokens, - pad_token_id=pad_token_id, - eos_token_id=eos_token_id, - use_eos_token=use_eos_token, - model_type=model_type, - context_length=context_length, - prediction_length=prediction_length, - num_samples=num_samples, - temperature=temperature, - top_k=top_k, - top_p=top_p, - ) - - # Add extra items to model config so that it's saved in the ckpt - model.config.chronos_config = chronos_config.__dict__ - - shuffled_train_dataset = ChronosDataset( - datasets=train_datasets, - probabilities=probability, - tokenizer=chronos_config.create_tokenizer(), - context_length=context_length, - prediction_length=prediction_length, - min_past=min_past, - model_type=model_type, - imputation_method=LastValueImputation() if model_type == "causal" else None, - mode="training", - ).shuffle(shuffle_buffer_length=shuffle_buffer_length) - - # Define training args - training_args = TrainingArguments( - output_dir=str(output_dir), - per_device_train_batch_size=per_device_train_batch_size, - learning_rate=learning_rate, - lr_scheduler_type=lr_scheduler_type, - warmup_ratio=warmup_ratio, - optim=optim, - logging_dir=str(output_dir / "logs"), - logging_strategy="steps", - logging_steps=log_steps, - save_strategy="steps", - save_steps=save_steps, - report_to=["tensorboard"], - max_steps=max_steps, - gradient_accumulation_steps=gradient_accumulation_steps, - dataloader_num_workers=dataloader_num_workers, - tf32=tf32, # remove this if not using Ampere GPUs (e.g., A100) - torch_compile=torch_compile, - ddp_find_unused_parameters=False, - remove_unused_columns=False, - ) - - # Create Trainer instance - trainer = Trainer( - model=model, - args=training_args, - train_dataset=shuffled_train_dataset, - ) - log_on_main("Training", logger) - - trainer.train() - - if is_main_process(): - model.save_pretrained(output_dir / "checkpoint-final") - save_training_info( - output_dir / "checkpoint-final", training_config=raw_training_config - ) - - -if __name__ == "__main__": - logging.basicConfig(format="%(asctime)s - %(name)s - %(levelname)s - %(message)s") - logger = logging.getLogger(__file__) - logger.setLevel(logging.INFO) - app() diff --git a/src/chronos/__init__.py b/src/chronos/__init__.py deleted file mode 100644 index 3088d7e..0000000 --- a/src/chronos/__init__.py +++ /dev/null @@ -1,24 +0,0 @@ -# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -# SPDX-License-Identifier: Apache-2.0 - -from .base import BaseChronosPipeline, ForecastType -from .chronos import ( - ChronosConfig, - ChronosModel, - ChronosPipeline, - ChronosTokenizer, - MeanScaleUniformBins, -) -from .chronos_bolt import ChronosBoltConfig, ChronosBoltPipeline - -__all__ = [ - "BaseChronosPipeline", - "ForecastType", - "ChronosConfig", - "ChronosModel", - "ChronosPipeline", - "ChronosTokenizer", - "MeanScaleUniformBins", - "ChronosBoltConfig", - "ChronosBoltPipeline", -] diff --git a/src/chronos/base.py b/src/chronos/base.py deleted file mode 100644 index bf57b55..0000000 --- a/src/chronos/base.py +++ /dev/null @@ -1,164 +0,0 @@ -# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -# SPDX-License-Identifier: Apache-2.0 - -# Authors: Caner Turkmen , Abdul Fatir Ansari , Lorenzo Stella -# Original source: -# https://github.com/autogluon/autogluon/blob/f57beb26cb769c6e0d484a6af2b89eab8aee73a8/timeseries/src/autogluon/timeseries/models/chronos/pipeline/base.py - -from enum import Enum -from pathlib import Path -from typing import TYPE_CHECKING, Dict, List, Optional, Tuple, Union - -import torch - -if TYPE_CHECKING: - from transformers import PreTrainedModel - -from .utils import left_pad_and_stack_1D - - -class ForecastType(Enum): - SAMPLES = "samples" - QUANTILES = "quantiles" - - -class PipelineRegistry(type): - REGISTRY: Dict[str, "PipelineRegistry"] = {} - - def __new__(cls, name, bases, attrs): - """See, https://github.com/faif/python-patterns.""" - new_cls = type.__new__(cls, name, bases, attrs) - if name is not None: - cls.REGISTRY[name] = new_cls - - return new_cls - - -class BaseChronosPipeline(metaclass=PipelineRegistry): - forecast_type: ForecastType - dtypes = {"bfloat16": torch.bfloat16, "float32": torch.float32} - - def __init__(self, inner_model: "PreTrainedModel"): - """ - Parameters - ---------- - inner_model : PreTrainedModel - A hugging-face transformers PreTrainedModel, e.g., T5ForConditionalGeneration - """ - # for easy access to the inner HF-style model - self.inner_model = inner_model - - def _prepare_and_validate_context( - self, context: Union[torch.Tensor, List[torch.Tensor]] - ): - if isinstance(context, list): - context = left_pad_and_stack_1D(context) - assert isinstance(context, torch.Tensor) - if context.ndim == 1: - context = context.unsqueeze(0) - assert context.ndim == 2 - - return context - - def predict( - self, - context: Union[torch.Tensor, List[torch.Tensor]], - prediction_length: Optional[int] = None, - **kwargs, - ): - """ - Get forecasts for the given time series. Predictions will be - returned in fp32 on the cpu. - - Parameters - ---------- - context - Input series. This is either a 1D tensor, or a list - of 1D tensors, or a 2D tensor whose first dimension - is batch. In the latter case, use left-padding with - ``torch.nan`` to align series of different lengths. - prediction_length - Time steps to predict. Defaults to a model-dependent - value if not given. - - Returns - ------- - forecasts - Tensor containing forecasts. The layout and meaning - of the forecasts values depends on ``self.forecast_type``. - """ - raise NotImplementedError() - - def predict_quantiles( - self, - context: Union[torch.Tensor, List[torch.Tensor]], - prediction_length: Optional[int] = None, - quantile_levels: List[float] = [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9], - **kwargs, - ) -> Tuple[torch.Tensor, torch.Tensor]: - """ - Get quantile and mean forecasts for given time series. - Predictions will be returned in fp32 on the cpu. - - Parameters - ---------- - context : Union[torch.Tensor, List[torch.Tensor]] - Input series. This is either a 1D tensor, or a list - of 1D tensors, or a 2D tensor whose first dimension - is batch. In the latter case, use left-padding with - ``torch.nan`` to align series of different lengths. - prediction_length : Optional[int], optional - Time steps to predict. Defaults to a model-dependent - value if not given. - quantile_levels : List[float], optional - Quantile levels to compute, by default [0.1, 0.2, ..., 0.9] - - Returns - ------- - quantiles - Tensor containing quantile forecasts. Shape - (batch_size, prediction_length, num_quantiles) - mean - Tensor containing mean (point) forecasts. Shape - (batch_size, prediction_length) - """ - raise NotImplementedError() - - @classmethod - def from_pretrained( - cls, - pretrained_model_name_or_path: Union[str, Path], - *model_args, - **kwargs, - ): - """ - Load the model, either from a local path or from the HuggingFace Hub. - Supports the same arguments as ``AutoConfig`` and ``AutoModel`` - from ``transformers``. - """ - from transformers import AutoConfig - - torch_dtype = kwargs.get("torch_dtype", "auto") - if torch_dtype != "auto" and isinstance(torch_dtype, str): - kwargs["torch_dtype"] = cls.dtypes[torch_dtype] - - config = AutoConfig.from_pretrained(pretrained_model_name_or_path, **kwargs) - is_valid_config = hasattr(config, "chronos_pipeline_class") or hasattr( - config, "chronos_config" - ) - - if not is_valid_config: - raise ValueError("Not a Chronos config file") - - pipeline_class_name = getattr( - config, "chronos_pipeline_class", "ChronosPipeline" - ) - class_ = PipelineRegistry.REGISTRY.get(pipeline_class_name) - if class_ is None: - raise ValueError( - f"Trying to load unknown pipeline class: {pipeline_class_name}" - ) - - return class_.from_pretrained( # type: ignore[attr-defined] - pretrained_model_name_or_path, *model_args, **kwargs - ) diff --git a/src/chronos/chronos.py b/src/chronos/chronos.py deleted file mode 100644 index 31df48a..0000000 --- a/src/chronos/chronos.py +++ /dev/null @@ -1,582 +0,0 @@ -# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -# SPDX-License-Identifier: Apache-2.0 - -# Authors: Abdul Fatir Ansari , Lorenzo Stella , Caner Turkmen - -import logging -from dataclasses import dataclass -from typing import Any, Dict, List, Literal, Optional, Tuple, Union - -import torch -import torch.nn as nn -from transformers import ( - AutoConfig, - AutoModelForCausalLM, - AutoModelForSeq2SeqLM, - GenerationConfig, - PreTrainedModel, -) - -import chronos -from chronos.base import BaseChronosPipeline, ForecastType -from chronos.utils import left_pad_and_stack_1D - -logger = logging.getLogger(__file__) - - -@dataclass -class ChronosConfig: - """ - This class holds all the configuration parameters to be used - by ``ChronosTokenizer`` and ``ChronosModel``. - """ - - tokenizer_class: str - tokenizer_kwargs: Dict[str, Any] - context_length: int - prediction_length: int - n_tokens: int - n_special_tokens: int - pad_token_id: int - eos_token_id: int - use_eos_token: bool - model_type: Literal["causal", "seq2seq"] - num_samples: int - temperature: float - top_k: int - top_p: float - - def __post_init__(self): - assert ( - self.pad_token_id < self.n_special_tokens - and self.eos_token_id < self.n_special_tokens - ), f"Special token id's must be smaller than {self.n_special_tokens=}" - - def create_tokenizer(self) -> "ChronosTokenizer": - class_ = getattr(chronos, self.tokenizer_class) - return class_(**self.tokenizer_kwargs, config=self) - - -class ChronosTokenizer: - """ - A ``ChronosTokenizer`` definines how time series are mapped into token IDs - and back. - - For details, see the ``input_transform`` and ``output_transform`` methods, - which concrete classes must implement. - """ - - def context_input_transform( - self, - context: torch.Tensor, - ) -> Tuple: - """ - Turn a batch of time series into token IDs, attention map, and tokenizer_state. - - Parameters - ---------- - context - A tensor shaped (batch_size, time_length), containing the - timeseries to forecast. Use left-padding with ``torch.nan`` - to align time series of different lengths. - - Returns - ------- - token_ids - A tensor of integers, shaped (batch_size, time_length + 1) - if ``config.use_eos_token`` and (batch_size, time_length) - otherwise, containing token IDs for the input series. - attention_mask - A boolean tensor, same shape as ``token_ids``, indicating - which input observations are not ``torch.nan`` (i.e. not - missing nor padding). - tokenizer_state - An object that can be passed to ``label_input_transform`` - and ``output_transform``. Contains the relevant information - to decode output samples into real values, - such as location and scale parameters. - """ - raise NotImplementedError() - - def label_input_transform(self, label: torch.Tensor, tokenizer_state: Any) -> Tuple: - """ - Turn a batch of label slices of time series into token IDs and attention map - using the ``tokenizer_state`` provided by ``context_input_transform``. - - Parameters - ---------- - context - A tensor shaped (batch_size, time_length), containing the - timeseries to forecast. Use left-padding with ``torch.nan`` - to align time series of different lengths. - tokenizer_state - An object returned by ``context_input_transform`` containing - relevant information to preprocess data, such as location and - scale. The nature of this depends on the specific tokenizer. - This is used for tokenizing the label, in order to use the same - scaling used to tokenize the context. - - Returns - ------- - token_ids - A tensor of integers, shaped (batch_size, time_length + 1) - if ``config.use_eos_token`` and (batch_size, time_length) - otherwise, containing token IDs for the input series. - attention_mask - A boolean tensor, same shape as ``token_ids``, indicating - which input observations are not ``torch.nan`` (i.e. not - missing nor padding). - """ - raise NotImplementedError() - - def output_transform( - self, samples: torch.Tensor, tokenizer_state: Any - ) -> torch.Tensor: - """ - Turn a batch of sample token IDs into real values. - - Parameters - ---------- - samples - A tensor of integers, shaped (batch_size, num_samples, time_length), - containing token IDs of sample trajectories. - tokenizer_state - An object returned by ``input_transform`` containing - relevant context to decode samples, such as location and scale. - The nature of this depends on the specific tokenizer. - - Returns - ------- - forecasts - A real tensor, shaped (batch_size, num_samples, time_length), - containing forecasted sample paths. - """ - raise NotImplementedError() - - -class MeanScaleUniformBins(ChronosTokenizer): - def __init__( - self, low_limit: float, high_limit: float, config: ChronosConfig - ) -> None: - self.config = config - self.centers = torch.linspace( - low_limit, - high_limit, - config.n_tokens - config.n_special_tokens - 1, - ) - self.boundaries = torch.concat( - ( - torch.tensor([-1e20], device=self.centers.device), - (self.centers[1:] + self.centers[:-1]) / 2, - torch.tensor([1e20], device=self.centers.device), - ) - ) - - def _input_transform( - self, context: torch.Tensor, scale: Optional[torch.Tensor] = None - ) -> Tuple[torch.Tensor, torch.Tensor, torch.Tensor]: - context = context.to(dtype=torch.float32) - attention_mask = ~torch.isnan(context) - - if scale is None: - scale = torch.nansum( - torch.abs(context) * attention_mask, dim=-1 - ) / torch.nansum(attention_mask, dim=-1) - scale[~(scale > 0)] = 1.0 - - scaled_context = context / scale.unsqueeze(dim=-1) - token_ids = ( - torch.bucketize( - input=scaled_context, - boundaries=self.boundaries, - # buckets are open to the right, see: - # https://pytorch.org/docs/2.1/generated/torch.bucketize.html#torch-bucketize - right=True, - ) - + self.config.n_special_tokens - ) - - token_ids.clamp_(0, self.config.n_tokens - 1) - - token_ids[~attention_mask] = self.config.pad_token_id - - return token_ids, attention_mask, scale - - def _append_eos_token( - self, token_ids: torch.Tensor, attention_mask: torch.Tensor - ) -> Tuple[torch.Tensor, torch.Tensor]: - batch_size = token_ids.shape[0] - eos_tokens = torch.full((batch_size, 1), fill_value=self.config.eos_token_id) - token_ids = torch.concat((token_ids, eos_tokens), dim=1) - eos_mask = torch.full((batch_size, 1), fill_value=True) - attention_mask = torch.concat((attention_mask, eos_mask), dim=1) - - return token_ids, attention_mask - - def context_input_transform( - self, context: torch.Tensor - ) -> Tuple[torch.Tensor, torch.Tensor, torch.Tensor]: - length = context.shape[-1] - - if length > self.config.context_length: - context = context[..., -self.config.context_length :] - - token_ids, attention_mask, scale = self._input_transform(context=context) - - if self.config.use_eos_token and self.config.model_type == "seq2seq": - token_ids, attention_mask = self._append_eos_token( - token_ids=token_ids, attention_mask=attention_mask - ) - - return token_ids, attention_mask, scale - - def label_input_transform( - self, label: torch.Tensor, scale: torch.Tensor - ) -> Tuple[torch.Tensor, torch.Tensor]: - length = label.shape[-1] - - assert length == self.config.prediction_length - token_ids, attention_mask, _ = self._input_transform(context=label, scale=scale) - - if self.config.use_eos_token: - token_ids, attention_mask = self._append_eos_token( - token_ids=token_ids, attention_mask=attention_mask - ) - - return token_ids, attention_mask - - def output_transform( - self, samples: torch.Tensor, scale: torch.Tensor - ) -> torch.Tensor: - scale_unsqueezed = scale.unsqueeze(-1).unsqueeze(-1) - indices = torch.clamp( - samples - self.config.n_special_tokens - 1, - min=0, - max=len(self.centers) - 1, - ) - return self.centers[indices] * scale_unsqueezed - - -class ChronosModel(nn.Module): - """ - A ``ChronosModel`` wraps a ``PreTrainedModel`` object from ``transformers`` - and uses it to predict sample paths for time series tokens. - - Parameters - ---------- - config - The configuration to use. - model - The pretrained model to use. - """ - - def __init__(self, config: ChronosConfig, model: PreTrainedModel) -> None: - super().__init__() - self.config = config - self.model = model - - @property - def device(self): - return self.model.device - - def encode( - self, - input_ids: torch.Tensor, - attention_mask: torch.Tensor, - ): - """ - Extract the encoder embedding for the given token sequences. - - Parameters - ---------- - input_ids - Tensor of indices of input sequence tokens in the vocabulary - with shape (batch_size, sequence_length). - attention_mask - A mask tensor of the same shape as input_ids to avoid attending - on padding or missing tokens. - - Returns - ------- - embedding - A tensor of encoder embeddings with shape - (batch_size, sequence_length, d_model). - """ - assert ( - self.config.model_type == "seq2seq" - ), "Encoder embeddings are only supported for encoder-decoder models" - return self.model.encoder( - input_ids=input_ids, attention_mask=attention_mask - ).last_hidden_state - - def forward( - self, - input_ids: torch.Tensor, - attention_mask: torch.Tensor, - prediction_length: Optional[int] = None, - num_samples: Optional[int] = None, - temperature: Optional[float] = None, - top_k: Optional[int] = None, - top_p: Optional[float] = None, - ) -> torch.Tensor: - """ - Predict future sample tokens for the given token sequences. - - Arguments ``prediction_length``, ``num_samples``, ``temperature``, - ``top_k``, ``top_p`` can be used to customize the model inference, - and default to the corresponding attributes in ``self.config`` if - not provided. - - Returns - ------- - samples - A tensor of integers, shaped (batch_size, num_samples, time_length), - containing forecasted sample paths. - """ - if prediction_length is None: - prediction_length = self.config.prediction_length - if num_samples is None: - num_samples = self.config.num_samples - if temperature is None: - temperature = self.config.temperature - if top_k is None: - top_k = self.config.top_k - if top_p is None: - top_p = self.config.top_p - - preds = self.model.generate( - input_ids=input_ids, - attention_mask=attention_mask, - generation_config=GenerationConfig( - min_new_tokens=prediction_length, - max_new_tokens=prediction_length, - do_sample=True, - num_return_sequences=num_samples, - eos_token_id=self.config.eos_token_id, - pad_token_id=self.config.pad_token_id, - temperature=temperature, - top_k=top_k, - top_p=top_p, - ), - ) - - if self.config.model_type == "seq2seq": - preds = preds[..., 1:] # remove the decoder start token - else: - assert self.config.model_type == "causal" - assert preds.size(-1) == input_ids.size(-1) + prediction_length - preds = preds[..., -prediction_length:] - - return preds.reshape(input_ids.size(0), num_samples, -1) - - -class ChronosPipeline(BaseChronosPipeline): - """ - A ``ChronosPipeline`` uses the given tokenizer and model to forecast - input time series. - - Use the ``from_pretrained`` class method to load serialized models. - Use the ``predict`` method to get forecasts. - - Parameters - ---------- - tokenizer - The tokenizer object to use. - model - The model to use. - """ - - tokenizer: ChronosTokenizer - model: ChronosModel - forecast_type: ForecastType = ForecastType.SAMPLES - - def __init__(self, tokenizer, model): - super().__init__(inner_model=model.model) - self.tokenizer = tokenizer - self.model = model - - def _prepare_and_validate_context( - self, context: Union[torch.Tensor, List[torch.Tensor]] - ): - if isinstance(context, list): - context = left_pad_and_stack_1D(context) - assert isinstance(context, torch.Tensor) - if context.ndim == 1: - context = context.unsqueeze(0) - assert context.ndim == 2 - - return context - - @torch.no_grad() - def embed( - self, context: Union[torch.Tensor, List[torch.Tensor]] - ) -> Tuple[torch.Tensor, Any]: - """ - Get encoder embeddings for the given time series. - - Parameters - ---------- - context - Input series. This is either a 1D tensor, or a list - of 1D tensors, or a 2D tensor whose first dimension - is batch. In the latter case, use left-padding with - ``torch.nan`` to align series of different lengths. - - Returns - ------- - embeddings, tokenizer_state - A tuple of two tensors: the encoder embeddings and the tokenizer_state, - e.g., the scale of the time series in the case of mean scaling. - The encoder embeddings are shaped (batch_size, context_length, d_model) - or (batch_size, context_length + 1, d_model), where context_length - is the size of the context along the time axis if a 2D tensor was provided - or the length of the longest time series, if a list of 1D tensors was - provided, and the extra 1 is for EOS. - """ - context_tensor = self._prepare_and_validate_context(context=context) - token_ids, attention_mask, tokenizer_state = ( - self.tokenizer.context_input_transform(context_tensor) - ) - embeddings = self.model.encode( - input_ids=token_ids.to(self.model.device), - attention_mask=attention_mask.to(self.model.device), - ).cpu() - return embeddings, tokenizer_state - - def predict( # type: ignore[override] - self, - context: Union[torch.Tensor, List[torch.Tensor]], - prediction_length: Optional[int] = None, - num_samples: Optional[int] = None, - temperature: Optional[float] = None, - top_k: Optional[int] = None, - top_p: Optional[float] = None, - limit_prediction_length: bool = False, - ) -> torch.Tensor: - """ - Get forecasts for the given time series. - - Refer to the base method (``BaseChronosPipeline.predict``) - for details on shared parameters. - - Additional parameters - --------------------- - num_samples - Number of sample paths to predict. Defaults to what - specified in ``self.model.config``. - temperature - Temperature to use for generating sample tokens. - Defaults to what specified in ``self.model.config``. - top_k - Top-k parameter to use for generating sample tokens. - Defaults to what specified in ``self.model.config``. - top_p - Top-p parameter to use for generating sample tokens. - Defaults to what specified in ``self.model.config``. - limit_prediction_length - Force prediction length smaller or equal than the - built-in prediction length from the model. False by - default. When true, fail loudly if longer predictions - are requested, otherwise longer predictions are allowed. - - Returns - ------- - samples - Tensor of sample forecasts, of shape - (batch_size, num_samples, prediction_length). - """ - context_tensor = self._prepare_and_validate_context(context=context) - - if prediction_length is None: - prediction_length = self.model.config.prediction_length - - if prediction_length > self.model.config.prediction_length: - msg = ( - f"We recommend keeping prediction length <= {self.model.config.prediction_length}. " - "The quality of longer predictions may degrade since the model is not optimized for it. " - ) - if limit_prediction_length: - msg += "You can turn off this check by setting `limit_prediction_length=False`." - raise ValueError(msg) - logger.warning(msg) - - predictions = [] - remaining = prediction_length - - while remaining > 0: - token_ids, attention_mask, scale = self.tokenizer.context_input_transform( - context_tensor - ) - samples = self.model( - token_ids.to(self.model.device), - attention_mask.to(self.model.device), - min(remaining, self.model.config.prediction_length), - num_samples, - temperature, - top_k, - top_p, - ) - prediction = self.tokenizer.output_transform( - samples.to(scale.device), scale - ) - - predictions.append(prediction) - remaining -= prediction.shape[-1] - - if remaining <= 0: - break - - context_tensor = torch.cat( - [context_tensor, prediction.median(dim=1).values], dim=-1 - ) - - return torch.cat(predictions, dim=-1).to(dtype=torch.float32, device="cpu") - - def predict_quantiles( - self, - context: Union[torch.Tensor, List[torch.Tensor]], - prediction_length: Optional[int] = None, - quantile_levels: List[float] = [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9], - **predict_kwargs, - ) -> Tuple[torch.Tensor, torch.Tensor]: - """ - Refer to the base method (``BaseChronosPipeline.predict_quantiles``). - """ - prediction_samples = ( - self.predict(context, prediction_length=prediction_length, **predict_kwargs) - .detach() - .swapaxes(1, 2) - ) - mean = prediction_samples.mean(dim=-1) - quantiles = torch.quantile( - prediction_samples, - q=torch.tensor(quantile_levels, dtype=prediction_samples.dtype), - dim=-1, - ).permute(1, 2, 0) - - return quantiles, mean - - @classmethod - def from_pretrained(cls, *args, **kwargs): - """ - Load the model, either from a local path or from the HuggingFace Hub. - Supports the same arguments as ``AutoConfig`` and ``AutoModel`` - from ``transformers``. - """ - - config = AutoConfig.from_pretrained(*args, **kwargs) - - assert hasattr(config, "chronos_config"), "Not a Chronos config file" - - chronos_config = ChronosConfig(**config.chronos_config) - - if chronos_config.model_type == "seq2seq": - inner_model = AutoModelForSeq2SeqLM.from_pretrained(*args, **kwargs) - else: - assert chronos_config.model_type == "causal" - inner_model = AutoModelForCausalLM.from_pretrained(*args, **kwargs) - - return cls( - tokenizer=chronos_config.create_tokenizer(), - model=ChronosModel(config=chronos_config, model=inner_model), - ) diff --git a/src/chronos/chronos_bolt.py b/src/chronos/chronos_bolt.py deleted file mode 100644 index ed99701..0000000 --- a/src/chronos/chronos_bolt.py +++ /dev/null @@ -1,640 +0,0 @@ -# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -# SPDX-License-Identifier: Apache-2.0 - -# Authors: Abdul Fatir Ansari , Caner Turkmen , Lorenzo Stella -# Original source: -# https://github.com/autogluon/autogluon/blob/f57beb26cb769c6e0d484a6af2b89eab8aee73a8/timeseries/src/autogluon/timeseries/models/chronos/pipeline/chronos_bolt.py - -import copy -import logging -import warnings -from dataclasses import dataclass -from typing import List, Optional, Tuple, Union - -import torch -import torch.nn as nn -from transformers import AutoConfig -from transformers.models.t5.modeling_t5 import ( - ACT2FN, - T5Config, - T5LayerNorm, - T5PreTrainedModel, - T5Stack, -) -from transformers.utils import ModelOutput - -from .base import BaseChronosPipeline, ForecastType - -logger = logging.getLogger(__file__) - - -@dataclass -class ChronosBoltConfig: - context_length: int - prediction_length: int - input_patch_size: int - input_patch_stride: int - quantiles: List[float] - use_reg_token: bool = False - - -@dataclass -class ChronosBoltOutput(ModelOutput): - loss: Optional[torch.Tensor] = None - quantile_preds: Optional[torch.Tensor] = None - attentions: Optional[torch.Tensor] = None - cross_attentions: Optional[torch.Tensor] = None - - -class Patch(nn.Module): - def __init__(self, patch_size: int, patch_stride: int) -> None: - super().__init__() - self.patch_size = patch_size - self.patch_stride = patch_stride - - def forward(self, x: torch.Tensor) -> torch.Tensor: - length = x.shape[-1] - - if length % self.patch_size != 0: - padding_size = ( - *x.shape[:-1], - self.patch_size - (length % self.patch_size), - ) - padding = torch.full( - size=padding_size, fill_value=torch.nan, dtype=x.dtype, device=x.device - ) - x = torch.concat((padding, x), dim=-1) - - x = x.unfold(dimension=-1, size=self.patch_size, step=self.patch_stride) - return x - - -class InstanceNorm(nn.Module): - """ - See, also, RevIN. Apply standardization along the last dimension. - """ - - def __init__(self, eps: float = 1e-5) -> None: - super().__init__() - self.eps = eps - - def forward( - self, - x: torch.Tensor, - loc_scale: Optional[Tuple[torch.Tensor, torch.Tensor]] = None, - ) -> Tuple[torch.Tensor, Tuple[torch.Tensor, torch.Tensor]]: - if loc_scale is None: - loc = torch.nan_to_num(torch.nanmean(x, dim=-1, keepdim=True), nan=0.0) - scale = torch.nan_to_num( - torch.nanmean((x - loc).square(), dim=-1, keepdim=True).sqrt(), nan=1.0 - ) - scale = torch.where(scale == 0, torch.abs(loc) + self.eps, scale) - else: - loc, scale = loc_scale - - return (x - loc) / scale, (loc, scale) - - def inverse( - self, x: torch.Tensor, loc_scale: Tuple[torch.Tensor, torch.Tensor] - ) -> torch.Tensor: - loc, scale = loc_scale - return x * scale + loc - - -class ResidualBlock(nn.Module): - def __init__( - self, - in_dim: int, - h_dim: int, - out_dim: int, - act_fn_name: str, - dropout_p: float = 0.0, - use_layer_norm: bool = False, - ) -> None: - super().__init__() - - self.dropout = nn.Dropout(dropout_p) - self.hidden_layer = nn.Linear(in_dim, h_dim) - self.act = ACT2FN[act_fn_name] - self.output_layer = nn.Linear(h_dim, out_dim) - self.residual_layer = nn.Linear(in_dim, out_dim) - - self.use_layer_norm = use_layer_norm - if use_layer_norm: - self.layer_norm = T5LayerNorm(out_dim) - - def forward(self, x: torch.Tensor): - hid = self.act(self.hidden_layer(x)) - out = self.dropout(self.output_layer(hid)) - res = self.residual_layer(x) - - out = out + res - - if self.use_layer_norm: - return self.layer_norm(out) - return out - - -class ChronosBoltModelForForecasting(T5PreTrainedModel): - _keys_to_ignore_on_load_missing = [ - r"input_patch_embedding\.", - r"output_patch_embedding\.", - ] - _keys_to_ignore_on_load_unexpected = [r"lm_head.weight"] - _tied_weights_keys = ["encoder.embed_tokens.weight", "decoder.embed_tokens.weight"] - - def __init__(self, config: T5Config): - assert hasattr(config, "chronos_config"), "Not a Chronos config file" - - super().__init__(config) - self.model_dim = config.d_model - - self.chronos_config = ChronosBoltConfig(**config.chronos_config) - - # Only decoder_start_id (and optionally REG token) - if self.chronos_config.use_reg_token: - config.reg_token_id = 1 - - config.vocab_size = 2 if self.chronos_config.use_reg_token else 1 - self.shared = nn.Embedding(config.vocab_size, config.d_model) - - # Input patch embedding layer - self.input_patch_embedding = ResidualBlock( - in_dim=self.chronos_config.input_patch_size * 2, - h_dim=config.d_ff, - out_dim=config.d_model, - act_fn_name=config.dense_act_fn, - dropout_p=config.dropout_rate, - ) - - # patching layer - self.patch = Patch( - patch_size=self.chronos_config.input_patch_size, - patch_stride=self.chronos_config.input_patch_stride, - ) - - # instance normalization, also referred to as "scaling" in Chronos and GluonTS - self.instance_norm = InstanceNorm() - - encoder_config = copy.deepcopy(config) - encoder_config.is_decoder = False - encoder_config.use_cache = False - encoder_config.is_encoder_decoder = False - self.encoder = T5Stack(encoder_config, self.shared) - - self._init_decoder(config) - - self.num_quantiles = len(self.chronos_config.quantiles) - quantiles = torch.tensor(self.chronos_config.quantiles, dtype=self.dtype) - self.register_buffer("quantiles", quantiles, persistent=False) - - self.output_patch_embedding = ResidualBlock( - in_dim=config.d_model, - h_dim=config.d_ff, - out_dim=self.num_quantiles * self.chronos_config.prediction_length, - act_fn_name=config.dense_act_fn, - dropout_p=config.dropout_rate, - ) - - # Initialize weights and apply final processing - self.post_init() - - # Model parallel - self.model_parallel = False - self.device_map = None - - def _init_weights(self, module): - super()._init_weights(module) - """Initialize the weights""" - factor = self.config.initializer_factor - if isinstance(module, (self.__class__)): - module.shared.weight.data.normal_(mean=0.0, std=factor * 1.0) - elif isinstance(module, ResidualBlock): - module.hidden_layer.weight.data.normal_( - mean=0.0, - std=factor * ((self.chronos_config.input_patch_size * 2) ** -0.5), - ) - if ( - hasattr(module.hidden_layer, "bias") - and module.hidden_layer.bias is not None - ): - module.hidden_layer.bias.data.zero_() - - module.residual_layer.weight.data.normal_( - mean=0.0, - std=factor * ((self.chronos_config.input_patch_size * 2) ** -0.5), - ) - if ( - hasattr(module.residual_layer, "bias") - and module.residual_layer.bias is not None - ): - module.residual_layer.bias.data.zero_() - - module.output_layer.weight.data.normal_( - mean=0.0, std=factor * ((self.config.d_ff) ** -0.5) - ) - if ( - hasattr(module.output_layer, "bias") - and module.output_layer.bias is not None - ): - module.output_layer.bias.data.zero_() - - def encode( - self, context: torch.Tensor, mask: Optional[torch.Tensor] = None - ) -> Tuple[ - torch.Tensor, Tuple[torch.Tensor, torch.Tensor], torch.Tensor, torch.Tensor - ]: - mask = ( - mask.to(context.dtype) - if mask is not None - else torch.isnan(context).logical_not().to(context.dtype) - ) - - batch_size, _ = context.shape - if context.shape[-1] > self.chronos_config.context_length: - context = context[..., -self.chronos_config.context_length :] - mask = mask[..., -self.chronos_config.context_length :] - - # scaling - context, loc_scale = self.instance_norm(context) - - # the scaling op above is done in 32-bit precision, - # then the context is moved to model's dtype - context = context.to(self.dtype) - mask = mask.to(self.dtype) - - # patching - patched_context = self.patch(context) - patched_mask = torch.nan_to_num(self.patch(mask), nan=0.0) - patched_context = torch.where(patched_mask > 0.0, patched_context, 0.0) - # concat context and mask along patch dim - patched_context = torch.cat([patched_context, patched_mask], dim=-1) - - # attention_mask = 1 if at least one item in the patch is observed - attention_mask = ( - patched_mask.sum(dim=-1) > 0 - ) # (batch_size, patched_seq_length) - - input_embeds = self.input_patch_embedding(patched_context) - - if self.chronos_config.use_reg_token: - # Append [REG] - reg_input_ids = torch.full( - (batch_size, 1), - self.config.reg_token_id, - device=input_embeds.device, - ) - reg_embeds = self.shared(reg_input_ids) - input_embeds = torch.cat([input_embeds, reg_embeds], dim=-2) - attention_mask = torch.cat( - [ - attention_mask.to(self.dtype), - torch.ones_like(reg_input_ids).to(self.dtype), - ], - dim=-1, - ) - - encoder_outputs = self.encoder( - attention_mask=attention_mask, - inputs_embeds=input_embeds, - ) - - return encoder_outputs[0], loc_scale, input_embeds, attention_mask - - def forward( - self, - context: torch.Tensor, - mask: Optional[torch.Tensor] = None, - target: Optional[torch.Tensor] = None, - target_mask: Optional[torch.Tensor] = None, - ) -> ChronosBoltOutput: - batch_size = context.size(0) - - hidden_states, loc_scale, input_embeds, attention_mask = self.encode( - context=context, mask=mask - ) - sequence_output = self.decode(input_embeds, attention_mask, hidden_states) - - quantile_preds_shape = ( - batch_size, - self.num_quantiles, - self.chronos_config.prediction_length, - ) - quantile_preds = self.output_patch_embedding(sequence_output).view( - *quantile_preds_shape - ) - - loss = None - if target is not None: - # normalize target - target, _ = self.instance_norm(target, loc_scale) - target = target.unsqueeze(1) # type: ignore - assert self.chronos_config.prediction_length >= target.shape[-1] - - target = target.to(quantile_preds.device) - target_mask = ( - target_mask.unsqueeze(1).to(quantile_preds.device) - if target_mask is not None - else ~torch.isnan(target) - ) - target[~target_mask] = 0.0 - - # pad target and target_mask if they are shorter than model's prediction_length - if self.chronos_config.prediction_length > target.shape[-1]: - padding_shape = ( - *target.shape[:-1], - self.chronos_config.prediction_length - target.shape[-1], - ) - target = torch.cat( - [target, torch.zeros(padding_shape).to(target)], dim=-1 - ) - target_mask = torch.cat( - [target_mask, torch.zeros(padding_shape).to(target_mask)], dim=-1 - ) - - loss = ( - 2 - * torch.abs( - (target - quantile_preds) - * ( - (target <= quantile_preds).float() - - self.quantiles.view(1, self.num_quantiles, 1) - ) - ) - * target_mask.float() - ) - loss = loss.mean(dim=-2) # Mean over prediction horizon - loss = loss.sum(dim=-1) # Sum over quantile levels - loss = loss.mean() # Mean over batch - - # Unscale predictions - quantile_preds = self.instance_norm.inverse( - quantile_preds.view(batch_size, -1), - loc_scale, - ).view(*quantile_preds_shape) - - return ChronosBoltOutput( - loss=loss, - quantile_preds=quantile_preds, - ) - - def _init_decoder(self, config): - decoder_config = copy.deepcopy(config) - decoder_config.is_decoder = True - decoder_config.is_encoder_decoder = False - decoder_config.num_layers = config.num_decoder_layers - self.decoder = T5Stack(decoder_config, self.shared) - - def decode( - self, - input_embeds, - attention_mask, - hidden_states, - output_attentions=False, - ): - """ - Parameters - ---------- - input_embeds: torch.Tensor - Patched and embedded inputs. Shape (batch_size, patched_context_length, d_model) - attention_mask: torch.Tensor - Attention mask for the patched context. Shape (batch_size, patched_context_length), type: torch.int64 - hidden_states: torch.Tensor - Hidden states returned by the encoder. Shape (batch_size, patched_context_length, d_model) - - Returns - ------- - last_hidden_state - Last hidden state returned by the decoder, of shape (batch_size, 1, d_model) - """ - batch_size = input_embeds.shape[0] - decoder_input_ids = torch.full( - (batch_size, 1), - self.config.decoder_start_token_id, - device=input_embeds.device, - ) - decoder_outputs = self.decoder( - input_ids=decoder_input_ids, - encoder_hidden_states=hidden_states, - encoder_attention_mask=attention_mask, - output_attentions=output_attentions, - return_dict=True, - ) - - return decoder_outputs.last_hidden_state # sequence_outputs, b x 1 x d_model - - -class ChronosBoltPipeline(BaseChronosPipeline): - forecast_type: ForecastType = ForecastType.QUANTILES - default_context_length: int = 2048 - - def __init__(self, model: ChronosBoltModelForForecasting): - super().__init__(inner_model=model) - self.model = model - - @property - def quantiles(self) -> List[float]: - return self.model.config.chronos_config["quantiles"] - - @torch.no_grad() - def embed( - self, context: Union[torch.Tensor, List[torch.Tensor]] - ) -> Tuple[torch.Tensor, Tuple[torch.Tensor, torch.Tensor]]: - """ - Get encoder embeddings for the given time series. - - Parameters - ---------- - context - Input series. This is either a 1D tensor, or a list - of 1D tensors, or a 2D tensor whose first dimension - is batch. In the latter case, use left-padding with - ``torch.nan`` to align series of different lengths. - - Returns - ------- - embeddings, loc_scale - A tuple of two items: the encoder embeddings and the loc_scale, - i.e., the mean and std of the original time series. - The encoder embeddings are shaped (batch_size, num_patches + 1, d_model), - where num_patches is the number of patches in the time series - and the extra 1 is for the [REG] token (if used by the model). - """ - context_tensor = self._prepare_and_validate_context(context=context) - model_context_length = self.model.config.chronos_config["context_length"] - - if context_tensor.shape[-1] > model_context_length: - context_tensor = context_tensor[..., -model_context_length:] - - context_tensor = context_tensor.to( - device=self.model.device, - dtype=torch.float32, - ) - embeddings, loc_scale, *_ = self.model.encode(context=context_tensor) - return embeddings.cpu(), ( - loc_scale[0].squeeze(-1).cpu(), - loc_scale[1].squeeze(-1).cpu(), - ) - - def predict( # type: ignore[override] - self, - context: Union[torch.Tensor, List[torch.Tensor]], - prediction_length: Optional[int] = None, - limit_prediction_length: bool = False, - ) -> torch.Tensor: - """ - Get forecasts for the given time series. - - Refer to the base method (``BaseChronosPipeline.predict``) - for details on shared parameters. - Additional parameters - --------------------- - limit_prediction_length - Force prediction length smaller or equal than the - built-in prediction length from the model. False by - default. When true, fail loudly if longer predictions - are requested, otherwise longer predictions are allowed. - - Returns - ------- - torch.Tensor - Forecasts of shape (batch_size, num_quantiles, prediction_length) - where num_quantiles is the number of quantiles the model has been - trained to output. For official Chronos-Bolt models, the value of - num_quantiles is 9 for [0.1, 0.2, ..., 0.9]-quantiles. - - Raises - ------ - ValueError - When limit_prediction_length is True and the prediction_length is - greater than model's trainig prediction_length. - """ - context_tensor = self._prepare_and_validate_context(context=context) - - model_context_length = self.model.config.chronos_config["context_length"] - model_prediction_length = self.model.config.chronos_config["prediction_length"] - if prediction_length is None: - prediction_length = model_prediction_length - - if prediction_length > model_prediction_length: - msg = ( - f"We recommend keeping prediction length <= {model_prediction_length}. " - "The quality of longer predictions may degrade since the model is not optimized for it. " - ) - if limit_prediction_length: - msg += "You can turn off this check by setting `limit_prediction_length=False`." - raise ValueError(msg) - warnings.warn(msg) - - predictions = [] - remaining = prediction_length - - # We truncate the context here because otherwise batches with very long - # context could take up large amounts of GPU memory unnecessarily. - if context_tensor.shape[-1] > model_context_length: - context_tensor = context_tensor[..., -model_context_length:] - - # TODO: We unroll the forecast of Chronos Bolt greedily with the full forecast - # horizon that the model was trained with (i.e., 64). This results in variance collapsing - # every 64 steps. - context_tensor = context_tensor.to( - device=self.model.device, - dtype=torch.float32, - ) - while remaining > 0: - with torch.no_grad(): - prediction = self.model( - context=context_tensor, - ).quantile_preds.to(context_tensor) - - predictions.append(prediction) - remaining -= prediction.shape[-1] - - if remaining <= 0: - break - - central_idx = torch.abs(torch.tensor(self.quantiles) - 0.5).argmin() - central_prediction = prediction[:, central_idx] - - context_tensor = torch.cat([context_tensor, central_prediction], dim=-1) - - return torch.cat(predictions, dim=-1)[..., :prediction_length].to( - dtype=torch.float32, device="cpu" - ) - - def predict_quantiles( - self, - context: Union[torch.Tensor, List[torch.Tensor]], - prediction_length: Optional[int] = None, - quantile_levels: List[float] = [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9], - **predict_kwargs, - ) -> Tuple[torch.Tensor, torch.Tensor]: - """ - Refer to the base method (``BaseChronosPipeline.predict_quantiles``). - """ - # shape (batch_size, prediction_length, len(training_quantile_levels)) - predictions = ( - self.predict(context, prediction_length=prediction_length, **predict_kwargs) - .detach() - .swapaxes(1, 2) - ) - - training_quantile_levels = self.quantiles - - if set(quantile_levels).issubset(set(training_quantile_levels)): - # no need to perform intra/extrapolation - quantiles = predictions[ - ..., [training_quantile_levels.index(q) for q in quantile_levels] - ] - else: - # we rely on torch for interpolating quantiles if quantiles that - # Chronos Bolt was trained on were not provided - if min(quantile_levels) < min(training_quantile_levels) or max( - quantile_levels - ) > max(training_quantile_levels): - logger.warning( - f"\tQuantiles to be predicted ({quantile_levels}) are not within the range of " - f"quantiles that Chronos-Bolt was trained on ({training_quantile_levels}). " - "Quantile predictions will be set to the minimum/maximum levels at which Chronos-Bolt " - "was trained on. This may significantly affect the quality of the predictions." - ) - - # TODO: this is a hack that assumes the model's quantiles during training (training_quantile_levels) - # made up an equidistant grid along the quantile dimension. i.e., they were (0.1, 0.2, ..., 0.9). - # While this holds for official Chronos-Bolt models, this may not be true in the future, and this - # function may have to be revised. - augmented_predictions = torch.cat( - [predictions[..., [0]], predictions, predictions[..., [-1]]], - dim=-1, - ) - quantiles = torch.quantile( - augmented_predictions, - q=torch.tensor(quantile_levels, dtype=augmented_predictions.dtype), - dim=-1, - ).permute(1, 2, 0) - # NOTE: the median is returned as the mean here - mean = predictions[:, :, training_quantile_levels.index(0.5)] - return quantiles, mean - - @classmethod - def from_pretrained(cls, *args, **kwargs): - """ - Load the model, either from a local path or from the HuggingFace Hub. - Supports the same arguments as ``AutoConfig`` and ``AutoModel`` - from ``transformers``. - """ - - config = AutoConfig.from_pretrained(*args, **kwargs) - assert hasattr(config, "chronos_config"), "Not a Chronos config file" - - architecture = config.architectures[0] - class_ = globals().get(architecture) - - if class_ is None: - logger.warning( - f"Unknown architecture: {architecture}, defaulting to ChronosBoltModelForForecasting" - ) - class_ = ChronosBoltModelForForecasting - - model = class_.from_pretrained(*args, **kwargs) - return cls(model=model) diff --git a/src/chronos/utils.py b/src/chronos/utils.py deleted file mode 100644 index 1c6b7e9..0000000 --- a/src/chronos/utils.py +++ /dev/null @@ -1,20 +0,0 @@ -# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -# SPDX-License-Identifier: Apache-2.0 - - -from typing import List - -import torch - - -def left_pad_and_stack_1D(tensors: List[torch.Tensor]) -> torch.Tensor: - max_len = max(len(c) for c in tensors) - padded = [] - for c in tensors: - assert isinstance(c, torch.Tensor) - assert c.ndim == 1 - padding = torch.full( - size=(max_len - len(c),), fill_value=torch.nan, device=c.device - ) - padded.append(torch.concat((padding, c), dim=-1)) - return torch.stack(padded) diff --git a/src/chronosx/chronosx.py b/src/chronosx/chronosx.py new file mode 100644 index 0000000..5139c30 --- /dev/null +++ b/src/chronosx/chronosx.py @@ -0,0 +1,706 @@ +import inspect +import logging +import numpy as np +import torch +import itertools + +from chronos import BaseChronosPipeline +from chronos import ChronosConfig +from chronos import ChronosPipeline +from pathlib import Path +from tqdm.auto import tqdm +from transformers.generation.configuration_utils import GenerationConfig +from transformers.utils.import_utils import is_accelerate_available +from transformers import ( + AutoConfig, + T5ForConditionalGeneration, + Trainer, + TrainingArguments, +) +from typing import Any, Dict, Optional + +from gluonts.itertools import batcher +from gluonts.model.forecast import SampleForecast +from gluonts.transform import ( + InstanceSplitter, + TestSplitSampler, +) + +from chronosx.utils.transform import create_transformation + +if is_accelerate_available(): + from accelerate.hooks import AlignDevicesHook, add_hook_to_module + +from chronosx.injection_blocks.block_mapping import injection_blocks_map +from chronosx.utils.utils import count_parameters, compute_metrics, log_on_main +from chronosx.utils.prepare_covariates import prepare_covariates + +logging.basicConfig(format="%(asctime)s - %(name)s - %(levelname)s - %(message)s") +logger = logging.getLogger(__file__) +logger.setLevel(logging.INFO) + + +class ChronosX(T5ForConditionalGeneration): + def __init__(self, *args, **kwargs): + T5ForConditionalGeneration.__init__(self, *args, **kwargs) + + self.output_hidden_states = False + + if self.input_injection_class: + self.input_injection_block = self.input_injection_class( + hidden_dim=self.hidden_dim, + model_dim=self.model_dim, + num_covariates=self.num_covariates, + num_layers=self.num_layers, + ) + self.input_injection_block_decoder = self.input_injection_class( + hidden_dim=self.hidden_dim, + model_dim=self.model_dim, + num_covariates=self.num_covariates, + num_layers=self.num_layers, + ) + else: + self.input_injection_block = None + self.input_injection_block_decoder = None + + if self.output_injection_class: + self.output_injection_block = self.output_injection_class( + hidden_dim=self.hidden_dim, + model_dim=self.model_dim, + num_covariates=self.num_covariates, + num_layers=self.num_layers, + vocab_size=self.vocab_size, + ) + self.output_hidden_states = True + else: + self.output_injection_block = None + + @classmethod + def set_state( + cls, + num_covariates, + covariate_injection, + hidden_dim, + num_layers, + vocab_size, + model_dim, + ): + + cls.num_covariates = num_covariates + cls.covariate_injection = covariate_injection + cls.hidden_dim = hidden_dim + cls.num_layers = num_layers + cls.vocab_size = vocab_size + cls.model_dim = model_dim + + if cls.covariate_injection in injection_blocks_map.keys(): + (cls.input_injection_class, cls.output_injection_class) = ( + injection_blocks_map[cls.covariate_injection] + ) + else: + cls.input_injection_class = None + cls.output_injection_class = None + + return cls + + def initialize_blocks(self): + if self.input_injection_block: + self.input_injection_block.initialize_modules() + self.input_injection_block_decoder.initialize_modules() + + if self.output_injection_block: + self.output_injection_block.initialize_modules() + + def freeze(self, layer_name=None): + for name, param in self.named_parameters(): + if layer_name == "all" or layer_name in name: + param.requires_grad = False + + def unfreeze(self, layer_name=None): + for name, param in self.named_parameters(): + if layer_name == "all" or layer_name in name: + param.requires_grad = True + + def _inject_at_input( + self, + input_ids: torch.Tensor = None, + decoder_input_ids: torch.Tensor = None, + past_covariates: torch.Tensor = None, + future_covariates: torch.Tensor = None, + labels: torch.Tensor = None, + ): + # input injection + inputs_embeds = None + decoder_inputs_embeds = None + if input_ids is not None: + inputs_embeds = self.encoder.embed_tokens(input_ids) + inputs_embeds = self.input_injection_block(inputs_embeds, past_covariates) + input_ids = None + + if decoder_input_ids is None and labels is not None: + decoder_input_ids = self._shift_right(labels) + + if future_covariates is not None and decoder_input_ids is not None: + decoder_inputs_embeds = self.decoder.embed_tokens(decoder_input_ids) + + # shifting covariates + shifted_future_covariates = self._shift_right( + future_covariates.transpose(1, 2) + ).transpose(1, 2) + + decoder_inputs_embeds = self.input_injection_block( + decoder_inputs_embeds, shifted_future_covariates, is_decoder=True + ) + decoder_input_ids = None + + return (input_ids, decoder_input_ids, inputs_embeds, decoder_inputs_embeds) + + def _inject_at_output(self, output, labels, future_covariates): + last_hidden_state = output.decoder_hidden_states[-1] + if future_covariates is not None: + output.logits, output.loss = self.output_injection_block( + future_covariates=future_covariates, + labels=labels, + logits=output.logits, + last_hidden_state=last_hidden_state, + ) + + return output + + def forward( + self, + input_ids: torch.Tensor = None, + decoder_input_ids: torch.Tensor = None, + inputs_embeds: torch.Tensor = None, + decoder_inputs_embeds: torch.Tensor = None, + past_covariates: torch.Tensor = None, + future_covariates: torch.Tensor = None, + labels: torch.Tensor = None, + **kwargs, + ): + + if self.input_injection_block is not None: + (input_ids, decoder_input_ids, inputs_embeds, decoder_inputs_embeds) = ( + self._inject_at_input( + input_ids=input_ids, + decoder_input_ids=decoder_input_ids, + past_covariates=past_covariates, + future_covariates=future_covariates, + labels=labels, + ) + ) + + # Removing key 'num_items_in_batch' from kwargs. + # This is necessary as recent versions of transformers make the code break with it. + kwargs_filtered = kwargs.copy() + if "num_items_in_batch" in kwargs_filtered: + kwargs_filtered.pop("num_items_in_batch") + + output = super(ChronosX, self).forward( + input_ids=input_ids, + decoder_input_ids=decoder_input_ids, + inputs_embeds=inputs_embeds, + decoder_inputs_embeds=decoder_inputs_embeds, + labels=labels, + output_hidden_states=self.output_hidden_states, + **kwargs_filtered, + ) + + if self.output_injection_block is not None: + output = self._inject_at_output( + output=output, labels=labels, future_covariates=future_covariates + ) + + return output + + def generate(self, **kwargs): + if self.output_injection_block is not None: + self.output_injection_block.restart_generator_counter() + + if self.input_injection_block is not None: + self.input_injection_block.restart_generator_counter() + self.input_injection_block_decoder.restart_generator_counter() + + output = super(ChronosX, self).generate(**kwargs) + + if self.output_injection_block is not None: + self.output_injection_block.generating = False + + if self.input_injection_block is not None: + self.input_injection_block.generating = False + self.input_injection_block_decoder.generating = False + + return output + + def prepare_inputs_for_generation( + self, + input_ids, + past_key_values=None, + attention_mask=None, + head_mask=None, + decoder_head_mask=None, + decoder_attention_mask=None, + cross_attn_head_mask=None, + use_cache=None, + encoder_outputs=None, + future_covariates=None, + **kwargs, + ): + + kwargs_augmented = { + **kwargs, + "past_key_values": past_key_values, + "attention_mask": attention_mask, + "head_mask": head_mask, + "decoder_head_mask": decoder_head_mask, + "decoder_attention_mask": decoder_attention_mask, + "cross_attn_head_mask": cross_attn_head_mask, + "use_cache": use_cache, + "encoder_outputs": encoder_outputs, + } + + output = super(ChronosX, self).prepare_inputs_for_generation( + input_ids, + **kwargs_augmented, + ) + + output.update({"future_covariates": future_covariates}) + + return output + + def _prepare_encoder_decoder_kwargs_for_generation( + self, + inputs_tensor: torch.Tensor, + model_kwargs, + model_input_name: Optional[str], + generation_config: GenerationConfig, + ) -> Dict[str, Any]: + # 1. get encoder + encoder = self.get_encoder() + # Compatibility with Accelerate big model inference: we need the encoder to outputs stuff on the same device + # as the inputs. + if hasattr(self, "hf_device_map"): + if hasattr(encoder, "_hf_hook"): + encoder._hf_hook.io_same_device = True + else: + add_hook_to_module(encoder, AlignDevicesHook(io_same_device=True)) + + # 2. Prepare encoder args and encoder kwargs from model kwargs and generation config. + irrelevant_prefix = ["decoder_", "cross_attn", "use_cache"] + encoder_kwargs = { + argument: value + for argument, value in model_kwargs.items() + if not any(argument.startswith(p) for p in irrelevant_prefix) + } + encoder_signature = set(inspect.signature(encoder.forward).parameters) + encoder_accepts_wildcard = ( + "kwargs" in encoder_signature or "model_kwargs" in encoder_signature + ) + if not encoder_accepts_wildcard: + encoder_kwargs = { + argument: value + for argument, value in encoder_kwargs.items() + if argument in encoder_signature + } + encoder_kwargs["output_attentions"] = generation_config.output_attentions + encoder_kwargs["output_hidden_states"] = generation_config.output_hidden_states + + # 3. make sure that encoder returns `ModelOutput` + model_input_name = ( + model_input_name if model_input_name is not None else self.main_input_name + ) + encoder_kwargs["return_dict"] = True + encoder_kwargs[model_input_name] = inputs_tensor + model_kwargs["encoder_outputs"]: ModelOutput = encoder(**encoder_kwargs) # type: ignore + + # IMPORTANT: here is the place where we provide the updated token embeddings for Input Injection Block + # Remember that we inject covariates on the token embeddings. This means that + # we do not have to give to the encoder the input_ids but rather the updated token embeddings. + # That's why we remove input_ids from *encoder_kwargs* and rather use 'inputs_embeds' + if self.input_injection_block is not None: + inputs_embeds = self.encoder.embed_tokens(encoder_kwargs["input_ids"]) + inputs_embeds = self.input_injection_block( + inputs_embeds, model_kwargs["past_covariates"] + ) + encoder_kwargs.pop("input_ids") + encoder_kwargs["inputs_embeds"] = inputs_embeds + model_kwargs["encoder_outputs"]: ModelOutput = encoder(**encoder_kwargs) # type: ignore + + return model_kwargs + + +class ChronosXPipeline(ChronosPipeline): + def __init__( + self, + prediction_length: int, + num_covariates: int, + covariate_injection: str = "IIB+OIB", + device_map: str = "cuda", # use "cpu" for CPU inference + hidden_dim: int = 256, + num_layers: int = 1, + pretrained_model_name_or_path="amazon/chronos-t5-small", + layers_to_unfreeze: str = "injection_block", + ): + + pretrained_kwargs = {"device_map": device_map} + + pipeline = BaseChronosPipeline.from_pretrained( + pretrained_model_name_or_path=pretrained_model_name_or_path, + **pretrained_kwargs, + ) + + chronos_config = AutoConfig.from_pretrained( + pretrained_model_name_or_path, + **pretrained_kwargs, + ) + + self.prediction_length = prediction_length + self.num_covariates = num_covariates + self.covariate_injection = covariate_injection + self.hidden_dim = hidden_dim + self.num_layers = num_layers + self.pretrained_model_name_or_path = pretrained_model_name_or_path + self.pretrained_model = pipeline.model + self.pretrained_model_tokenizer = pipeline.tokenizer + self.tokenizer = self.create_tokenizer() + self.vocab_size = chronos_config.vocab_size + self.model_dim = chronos_config.d_model + self.layers_to_unfreeze = layers_to_unfreeze + self.chronosx = ChronosX.set_state( + num_covariates=self.num_covariates, + covariate_injection=self.covariate_injection, + hidden_dim=self.hidden_dim, + num_layers=self.num_layers, + vocab_size=self.vocab_size, + model_dim=self.model_dim, + ).from_pretrained( + self.pretrained_model_name_or_path, + **pretrained_kwargs, + ) + + def create_tokenizer(self): + inputs = self.pretrained_model_tokenizer.config.__dict__ + inputs.update({"prediction_length": self.prediction_length}) + chronos_config = ChronosConfig(**inputs) + return chronos_config.create_tokenizer() + + def prepare_model_for_finetuning(self): + + self.chronosx.initialize_blocks() + + self.chronosx.config.pad_token_id = ( + self.chronosx.generation_config.pad_token_id + ) = 0 + + self.chronosx.config.eos_token_id = ( + self.chronosx.generation_config.eos_token_id + ) = 1 + + if self.layers_to_unfreeze is not None: + self.chronosx.freeze(layer_name="all") + self.chronosx.unfreeze(layer_name=self.layers_to_unfreeze) + + def load_pretrained_zero_shot_model(self, device_map: str = "cuda"): + return ChronosX.set_state( + covariate_injection=None, + num_covariates=self.num_covariates, + hidden_dim=self.hidden_dim, + num_layers=self.num_layers, + vocab_size=self.vocab_size, + model_dim=self.model_dim, + ).from_pretrained( + self.pretrained_model_name_or_path, + **{"device_map": device_map}, + ) + + def evaluate_model_on_validation_set( + self, + quantized_val_dataset, + output_dir=Path(__file__).parent / "output" / "group0" / "finetune", + dataloader_num_workers=1, + tf32=False, + torch_compile=0, + per_device_eval_batch_size=8, + eval_accumulation_steps=4, + device_map: str = "cuda", + covariate_injection: str = None, + ): + + output_dir.mkdir(exist_ok=True, parents=True) + + training_args = TrainingArguments( + output_dir=output_dir, + dataloader_num_workers=dataloader_num_workers, + tf32=tf32, # remove this if not using Ampere GPUs (e.g., A100) + torch_compile=torch_compile, + per_device_eval_batch_size=per_device_eval_batch_size, + eval_accumulation_steps=eval_accumulation_steps, + ) + + pretrained_model_zeroshot = ChronosX.set_state( + covariate_injection=covariate_injection, + num_covariates=self.num_covariates, + hidden_dim=self.hidden_dim, + num_layers=self.num_layers, + vocab_size=self.vocab_size, + model_dim=self.model_dim, + ).from_pretrained( + self.pretrained_model_name_or_path, + **{"device_map": device_map}, + ) + + # Create Trainer instance + self.pretrained_model.eval() + trainer = Trainer( + model=pretrained_model_zeroshot, + args=training_args, + eval_dataset=quantized_val_dataset, + compute_metrics=compute_metrics, + ) + + valiation_loss = trainer.evaluate(quantized_val_dataset) + self.pretrained_model.train() + + return valiation_loss["eval_loss"] + + def train( + self, + output_dir=Path(__file__).parent / "output" / "group0" / "finetune", + per_device_train_batch_size=32, + learning_rate=0.01, + lr_scheduler_type="linear", + warmup_ratio=0.0, + optim="adamw_torch_fused", + log_steps=20, + save_steps=100, + max_steps=5000, + gradient_accumulation_steps=2, + dataloader_num_workers=1, + tf32=False, + torch_compile=0, + eval_steps=100, + per_device_eval_batch_size=8, + eval_accumulation_steps=4, + load_best_model_at_end=True, + save_total_limit=5, + quantized_train_dataset=None, + quantized_val_dataset=None, + seed=None, + ): + + output_dir.mkdir(exist_ok=True, parents=True) + + training_args = TrainingArguments( + output_dir=output_dir, + per_device_train_batch_size=per_device_train_batch_size, + learning_rate=learning_rate, + lr_scheduler_type=lr_scheduler_type, + warmup_ratio=warmup_ratio, + optim=optim, + logging_dir=str(output_dir / "logs"), + logging_strategy="steps", + logging_steps=log_steps, + save_strategy="steps", + save_steps=save_steps, + report_to="tensorboard", + max_steps=max_steps, + gradient_accumulation_steps=gradient_accumulation_steps, + dataloader_num_workers=dataloader_num_workers, + tf32=tf32, # remove this if not using Ampere GPUs (e.g., A100) + torch_compile=torch_compile, + ddp_find_unused_parameters=False, + metric_for_best_model="val_loss", + evaluation_strategy="steps", + eval_steps=eval_steps, + per_device_eval_batch_size=per_device_eval_batch_size, + eval_accumulation_steps=eval_accumulation_steps, + load_best_model_at_end=load_best_model_at_end, + logging_first_step=True, + greater_is_better=False, + save_total_limit=save_total_limit, + # seed=seed, + ) + + # Create Trainer instance + if quantized_val_dataset is None: + training_args.eval_strategy = "no" + + trainer = Trainer( + model=self.chronosx, + args=training_args, + train_dataset=quantized_train_dataset, + eval_dataset=( + {"val": quantized_val_dataset} if quantized_val_dataset else None + ), + compute_metrics=compute_metrics, + ) + + # prepare training for finetuning + trainer._signature_columns = [ + "labels", + "attention_mask", + "input_ids", + "past_covariates", + "future_covariates", + "decoder_input_ids", + ] + + log_on_main("Training", logger) + + self.prepare_model_for_finetuning() + + parameter_count = count_parameters(self.chronosx) + print(f"parameter_count: {parameter_count}") + + print("Model parameters:") + for name, params in self.chronosx.named_parameters(): + print(name, params.requires_grad) + + trainer.train() + + if quantized_val_dataset: + val_loss_finetuned_model = trainer.evaluate(quantized_val_dataset) + else: + val_loss_finetuned_model = {"eval_loss": np.nan} + + pretrained_model_path = output_dir / "final-checkpoint" + self.chronosx.save_pretrained(pretrained_model_path) + + return val_loss_finetuned_model["eval_loss"] + + def predict( + self, context: list[torch.Tensor], covariates: list[dict], num_samples: int = 20 + ): + context_tensor = self._prepare_and_validate_context(context=context) + token_ids, attention_mask, scale = self.tokenizer.context_input_transform( + context_tensor + ) + + prepared_covariates = [prepare_covariates(entry) for entry in covariates] + future_covariates = torch.tensor( + [entry["future_covariates"] for entry in prepared_covariates] + ) + past_covariates = torch.tensor( + [entry["past_covariates"] for entry in prepared_covariates] + ) + + preds = self.chronosx.generate( + input_ids=token_ids.to(self.chronosx.device), + attention_mask=attention_mask.to(self.chronosx.device), + generation_config=GenerationConfig( + min_new_tokens=self.prediction_length, + max_new_tokens=self.prediction_length, + do_sample=True, + num_return_sequences=num_samples, + eos_token_id=self.chronosx.config.eos_token_id, + pad_token_id=self.chronosx.config.pad_token_id, + ), + future_covariates=future_covariates.to(self.chronosx.device), + past_covariates=past_covariates.to(self.chronosx.device), + ) + + preds = preds[..., 1:] # remove the decoder start token + preds = preds.reshape(token_ids.size(0), num_samples, -1) + preds = self.tokenizer.output_transform(preds.to("cpu"), scale.to("cpu")) + + return preds.to(dtype=torch.float32, device="cpu") + + def finetune( + self, + output_dir, + quantized_train_dataset, + lr=0.01, + quantized_val_dataset=None, + skip_pretrained_validation=False, + max_steps=20, + seed=None, + ): + + if not skip_pretrained_validation: + assert quantized_val_dataset is not None + val_loss_pretrained_model = self.evaluate_model_on_validation_set( + covariate_injection=None, + quantized_val_dataset=quantized_val_dataset, + output_dir=output_dir, + ) + else: + print(f"val_loss_pretrained_model is set up to nan since skip_pretrained_validation is given as False") + val_loss_pretrained_model = np.nan + + eval_loss_finetuned_model = self.train( + learning_rate=lr, + quantized_train_dataset=quantized_train_dataset, + quantized_val_dataset=quantized_val_dataset, + max_steps=max_steps, + output_dir=output_dir, + seed=seed, + ) + + if quantized_val_dataset is None: + print(f"No Validation set is provided") + + print( + f"lr:{lr} - val_loss_pretrained_model: {val_loss_pretrained_model:.4f} - eval_loss_finetuned_model:{eval_loss_finetuned_model:.4f}" + ) + + if val_loss_pretrained_model < eval_loss_finetuned_model: + model = self.load_pretrained_zero_shot_model() + val_loss = val_loss_pretrained_model + else: + model = self.chronosx + val_loss = eval_loss_finetuned_model + + save_model_path = output_dir / "final-checkpoint" + model.save_pretrained(save_model_path) + + return val_loss, save_model_path + + + def generate_forecasts( + self, + test_data_input, + batch_size=32, + context_length=512, + ): + + transformation = create_transformation(include_covariates=True) + transformed_dataset = transformation(iter(test_data_input), is_train=False) + instance_splitter = InstanceSplitter( + target_field="target", + is_pad_field="is_pad", + start_field="start", + forecast_start_field="forecast_start", + instance_sampler=TestSplitSampler(), + past_length=context_length, + future_length=self.prediction_length, + time_series_fields=[ + "observed_values", + "feat_dynamic_real", + ], + dummy_value=np.nan, + ) + iterables = [instance_splitter.apply(transformed_dataset, is_train=False)] + iterators = list(map(iter, iterables)) + test_data_input_transformed = itertools.chain(*iterators) + + forecast_outputs = [] + for batch in tqdm(batcher(test_data_input_transformed, batch_size=batch_size)): + context = [torch.tensor(entry["past_target"]) for entry in batch] + covariates = [ + { + "future_feat_dynamic_real": entry.get("future_feat_dynamic_real", None), + "past_feat_dynamic_real": entry.get("past_feat_dynamic_real", None), + } + for entry in batch + ] + forecast_outputs.append(self.predict(context, covariates).numpy()) + + forecast_outputs = np.concatenate(forecast_outputs) + + # Convert forecast samples into gluonts Forecast objects + forecasts = [] + for item, ts in zip(forecast_outputs, test_data_input): + forecast_start_date = ts["start"] + len(ts["target"]) + forecasts.append(SampleForecast(samples=item, start_date=forecast_start_date)) + + return forecasts diff --git a/src/chronosx/injection_blocks/__init__.py b/src/chronosx/injection_blocks/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/chronosx/injection_blocks/basic_modules.py b/src/chronosx/injection_blocks/basic_modules.py new file mode 100644 index 0000000..b4b0b5e --- /dev/null +++ b/src/chronosx/injection_blocks/basic_modules.py @@ -0,0 +1,52 @@ +from torch import nn +import math + + +class FeedForwardNN(nn.Module): + def __init__(self, input_size, hidden_sizes, output_size): + super(FeedForwardNN, self).__init__() + self.input_size = input_size + self.hidden_sizes = hidden_sizes + self.output_size = output_size + + layers = [] + prev_size = input_size + for size in hidden_sizes: + layers.append(nn.Linear(prev_size, size)) + layers.append(nn.ReLU()) + prev_size = size + + layers.append(nn.Linear(prev_size, output_size)) + + self.network = nn.Sequential(*layers) + + def forward(self, x): + return self.network(x) + + +class InjectionBlock(nn.Module): + name = "generic" + + def __init__( + self, + ): + super(InjectionBlock, self).__init__() + self.counter = 0 + self.generating = False + + def restart_generator_counter(self): + self.counter = 0 + self.generating = True + + def initialize_modules(self, modules=None): + if modules is None: + modules = self.modules() + for module in modules: + if isinstance(module, nn.Linear): + for name, param in module.named_parameters(): + if "weight" in name: + nn.init.kaiming_uniform_(param, a=math.sqrt(5)) + elif "bias" in name: + fan_in, _ = nn.init._calculate_fan_in_and_fan_out(module.weight) + bound = 1 / math.sqrt(fan_in) if fan_in > 0 else 0 + nn.init.uniform_(param, -bound, bound) diff --git a/src/chronosx/injection_blocks/block_mapping.py b/src/chronosx/injection_blocks/block_mapping.py new file mode 100644 index 0000000..d8b166b --- /dev/null +++ b/src/chronosx/injection_blocks/block_mapping.py @@ -0,0 +1,8 @@ +from chronosx.injection_blocks.input_injection_block import InputInjectionBlock +from chronosx.injection_blocks.output_injection_block import OutputInjectionBlock + +injection_blocks_map = { + "IIB": (InputInjectionBlock, None), + "OIB": (None, OutputInjectionBlock), + "IIB+OIB": (InputInjectionBlock, OutputInjectionBlock), +} diff --git a/src/chronosx/injection_blocks/input_injection_block.py b/src/chronosx/injection_blocks/input_injection_block.py new file mode 100644 index 0000000..75fc225 --- /dev/null +++ b/src/chronosx/injection_blocks/input_injection_block.py @@ -0,0 +1,42 @@ +from torch import nn +import torch + +from chronosx.injection_blocks.basic_modules import InjectionBlock, FeedForwardNN + + +class InputInjectionBlock(InjectionBlock): + name = "input_injection_block" + + def __init__( + self, + hidden_dim: int = 256, + model_dim: int = 512, + num_covariates: int = 0, + num_layers: int = 1, + ): + super(InputInjectionBlock, self).__init__() + + self.hidden_dim = hidden_dim + self.model_dim = model_dim + self.num_covariates = num_covariates + self.num_layers = num_layers + + self.cov_in = nn.Linear(self.num_covariates, self.hidden_dim) + self.concat_dim = self.hidden_dim * 2 + + self.concat_layer = FeedForwardNN( + self.concat_dim, [self.hidden_dim] * self.num_layers, self.model_dim + ) + self.emb_in = nn.Linear(self.model_dim, self.hidden_dim) + + def forward(self, input_embeds, past_covariates, is_decoder=False): + + x = self.emb_in(input_embeds) + if self.generating and is_decoder: + past_covariates = past_covariates[:, self.counter, :].unsqueeze(1) + self.counter += 1 + x_cov = self.cov_in(past_covariates) + x = torch.cat([x, x_cov], axis=-1) + x = nn.ReLU()(x) + + return input_embeds + self.concat_layer(x) diff --git a/src/chronosx/injection_blocks/output_injection_block.py b/src/chronosx/injection_blocks/output_injection_block.py new file mode 100644 index 0000000..5f8bc7c --- /dev/null +++ b/src/chronosx/injection_blocks/output_injection_block.py @@ -0,0 +1,64 @@ +from torch import nn +from torch.nn import CrossEntropyLoss +import torch + +from chronosx.injection_blocks.basic_modules import InjectionBlock, FeedForwardNN + + +class OutputInjectionBlock(InjectionBlock): + name = "output_injection_block" + + def __init__( + self, + hidden_dim: int = 256, + model_dim: int = 512, + num_covariates: int = 0, + num_layers: int = 1, + vocab_size: int = 4096, + ): + super(OutputInjectionBlock, self).__init__() + + self.hidden_dim = hidden_dim + self.model_dim = model_dim + self.num_covariates = num_covariates + self.num_layers = num_layers + self.vocab_size = vocab_size + + # self.num_layers = num_layers + self.concat_dim = 2 * self.hidden_dim + self.cov_out = nn.Linear(self.num_covariates, self.hidden_dim) + self.concat_layer = FeedForwardNN( + self.concat_dim, [self.hidden_dim] * self.num_layers, self.vocab_size + ) + self.hidden_state_out = nn.Linear(self.model_dim, self.hidden_dim) + self.loss_fct = CrossEntropyLoss(ignore_index=-100) + + def compute_loss(self, logits, labels): + labels = labels.to(logits.device) + loss = self.loss_fct(logits.view(-1, logits.size(-1)), labels.view(-1)) + return loss + + def forward(self, future_covariates, labels, logits, last_hidden_state): + x = self.hidden_state_out(last_hidden_state) + + if self.generating: + x_cov = future_covariates[:, self.counter].unsqueeze(1) + else: + x_cov = future_covariates + + if x_cov.flatten().isnan().sum() > 0: + print(1) + + x_cov = self.cov_out(x_cov) # -> here is the problem + x = torch.concatenate([x, x_cov], axis=-1) + + x = nn.ReLU()(x) + logits = self.concat_layer(x) + logits + + if self.training: + loss = self.compute_loss(logits, labels) + else: + loss = None + self.counter += 1 + + return logits, loss diff --git a/src/chronosx/utils/__init__.py b/src/chronosx/utils/__init__.py new file mode 100644 index 0000000..9d83d6e --- /dev/null +++ b/src/chronosx/utils/__init__.py @@ -0,0 +1,9 @@ +from .chronos_dataset import ChronosDataset +from .hf_data_loader import load_and_split_dataset +from .prepare_covariates import prepare_covariates + +__all__ = [ + "ChronosDataset", + "load_and_split_dataset", + "prepare_covariates" +] \ No newline at end of file diff --git a/src/chronosx/utils/chronos_dataset.py b/src/chronosx/utils/chronos_dataset.py new file mode 100644 index 0000000..3b272d1 --- /dev/null +++ b/src/chronosx/utils/chronos_dataset.py @@ -0,0 +1,412 @@ +import itertools +import numpy as np +import torch +import typer + +from chronos import ChronosTokenizer +from chronosx.utils.transform import DropValues +from gluonts.dataset.field_names import FieldName +from gluonts.itertools import Cyclic +from torch.utils.data import IterableDataset, get_worker_info +from typing import List, Iterator, Optional +from gluonts.transform import ( + FilterTransformation, + TestSplitSampler, + ValidationSplitSampler, + InstanceSplitter, + ExpectedNumInstanceSampler, + MissingValueImputation, + Transformation, + AddObservedValuesIndicator, + SelectFields, + VstackFeatures, + Chain, + LeavesMissingValues, +) + + + + +app = typer.Typer(pretty_exceptions_enable=False) + + +class PseudoShuffledIterableDataset(IterableDataset): + """ + Shuffle entries from an iterable by temporarily accumulating them + in an intermediate buffer. + + Parameters + ---------- + base_dataset + The original iterable object, representing the dataset. + shuffle_buffer_length + Size of the buffer use to shuffle entries from the base dataset. + """ + + def __init__( + self, base_dataset, shuffle_buffer_length: int = 100, random_seed: int = None + ) -> None: + super().__init__() + self.base_dataset = base_dataset + self.shuffle_buffer_length = shuffle_buffer_length + self.generator = torch.Generator() + if random_seed: + self.generator.manual_seed(random_seed) + + def __iter__(self): + shuffle_buffer = [] + + for element in self.base_dataset: + shuffle_buffer.append(element) + if len(shuffle_buffer) >= self.shuffle_buffer_length: + idx = torch.randint( + len(shuffle_buffer), size=(), generator=self.generator + ) + yield shuffle_buffer.pop(idx) + + while shuffle_buffer: + idx = torch.randint(len(shuffle_buffer), size=(), generator=self.generator) + yield shuffle_buffer.pop(idx) + + +class ShuffleMixin: + """ + Mix-in class that datasets can inherit from to get + shuffling functionality. + """ + + def shuffle(self, shuffle_buffer_length: int = 100, random_seed: int = None): + return PseudoShuffledIterableDataset(self, shuffle_buffer_length, random_seed) + + +class ChronosDataset(IterableDataset, ShuffleMixin): + """ + Dataset wrapper, using a ``ChronosTokenizer`` to turn data from a time series + into a HuggingFace-compatible set of ``input_ids``, ``attention_mask`` and + ``labels``. + + Entries from the original datasets are assumed to have a ``"start"`` attribute + (of type ``pd.Period``), and a ``"target"`` attribute (of type ``np.ndarray``). + + Parameters + ---------- + datasets + Datasets containing the original time series data. + probabilities + In training mode, data will be sampled from each of the original datasets + with these probabilities. + tokenizer + Tokenizer to be used to turn sequences of real numbers into token IDs. + context_length + Samples context will be limited to this length. + prediction_length + Samples labels will be limited to this length. + drop_prob + In training mode, observations from a sample will be turned into ``np.nan``, + i.e. turned into missing values, with this probability. + min_past + Data samples will be considered only if there's at least ``min_past``-many + historical observations. + mode + One of ``"training"``, ``"validation"``, or ``"test"``. + np_dtype + Numpy float data type. + """ + + def __init__( + self, + datasets: list, + probabilities: List[float], + tokenizer: ChronosTokenizer, + context_length: int = 512, + prediction_length: int = 64, + drop_prob: float = 0.2, + min_past: Optional[int] = None, + model_type: str = "seq2seq", + imputation_method: Optional[MissingValueImputation] = None, + mode: str = "training", + np_dtype=np.float32, + include_covariates: bool = True, + ) -> None: + super().__init__() + + assert len(probabilities) == len(datasets) + assert mode in ("training", "validation", "test") + assert model_type in ("seq2seq", "causal") + + self.datasets = datasets + self.probabilities = probabilities + self.tokenizer = tokenizer + self.context_length = context_length + self.prediction_length = prediction_length + self.drop_prob = drop_prob if model_type == "seq2seq" else 0.0 + self.min_past = min_past or prediction_length + self.model_type = model_type + self.imputation_method = imputation_method or LeavesMissingValues() + self.mode = mode + self.np_dtype = np_dtype + self.include_covariates = include_covariates + self.time_series_fields = [ + FieldName.OBSERVED_VALUES, + ] + + if self.include_covariates: + self.time_series_fields.append(FieldName.FEAT_DYNAMIC_REAL) + + def preprocess_entry(self, entry: dict, mode: str) -> dict: + entry = {f: entry[f] for f in ["start", "target"]} + entry["target"] = np.asarray(entry["target"], dtype=self.np_dtype) + assert entry["target"].ndim == 1, f"got {entry['target'].ndim=}, expected 1" + + if self.model_type == "causal": + # Causal models do not play nice with missing values, so it is + # recommended to use an imputation method, e.g., LastValueImputation + entry["target"] = self.imputation_method(entry["target"]) + + if mode == "training" and self.drop_prob > 0: + target = entry["target"].copy() + drop_p = np.random.uniform(low=0.0, high=self.drop_prob) + mask = np.random.choice( + [True, False], size=len(target), p=[drop_p, 1 - drop_p] + ) + target[mask] = np.nan + entry["target"] = target + + return entry + + # to adapt for covariates + def create_transformation(self) -> Transformation: + + fields = [ + FieldName.START, + FieldName.TARGET, + ] + if self.include_covariates: + fields.append(FieldName.FEAT_DYNAMIC_REAL) + + return ( + SelectFields( + fields, + allow_missing=True, + ) + + DropValues(drop_prob=0.2, target_field=FieldName.TARGET) + + AddObservedValuesIndicator( + target_field=FieldName.TARGET, + output_field=FieldName.OBSERVED_VALUES, + ) + ) + + def _create_instance_splitter(self, mode: str): + assert mode in ["training", "test", "validation"] + + instance_sampler = { + "training": ExpectedNumInstanceSampler( + num_instances=1.0, + min_instances=1, + min_past=self.min_past, + min_future=self.prediction_length, + ), + "test": TestSplitSampler(), + "validation": ValidationSplitSampler(min_future=self.prediction_length), + }[mode] + + return InstanceSplitter( + target_field="target", + is_pad_field="is_pad", + start_field="start", + forecast_start_field="forecast_start", + instance_sampler=instance_sampler, + past_length=self.context_length, + future_length=self.prediction_length, + time_series_fields=self.time_series_fields, # to adapt for covariates + dummy_value=np.nan, + # dummy_value=0.0, + ) + + def create_training_data(self, data): + data = Cyclic(data) + split_transform = self._create_instance_splitter( + "training" + ) + FilterTransformation( + condition=lambda entry: (~np.isnan(entry["past_target"])).sum() > 0 + ) + data = split_transform.apply(data, is_train=True) + return data + + def create_test_data(self, data): + data = self._create_instance_splitter("test").apply(data, is_train=False) + return data + + def create_validation_data(self, data): + data = self._create_instance_splitter("validation").apply(data, is_train=False) + return data + + def to_hf_format(self, entry: dict) -> dict: + past_target = torch.tensor(entry["past_target"]).unsqueeze(0) + input_ids, attention_mask, scale = self.tokenizer.context_input_transform( + past_target + ) + + future_target = torch.tensor(entry["future_target"]).unsqueeze(0) + + if self.mode == "test": + labels = future_target.detach().clone() + else: + labels, labels_mask = self.tokenizer.label_input_transform( + future_target, scale + ) + labels[labels_mask == 0] = -100 + + # normalize covariates by mean of absolute values + past_covariates = entry.get("past_feat_dynamic_real", None) + if past_covariates is not None: + # missing value imputation for covariates + past_covariates[np.isnan(past_covariates)] = 0 + + covariates_scale = np.mean(np.abs(past_covariates), axis=0) + covariates_scale[covariates_scale < 1] = 1.0 + past_covariates = past_covariates / covariates_scale + + future_covariates = entry.get("future_feat_dynamic_real", None) + if future_covariates is not None: + # missing value imputation for covariates + future_covariates[np.isnan(future_covariates)] = 0 + + future_covariates = future_covariates / covariates_scale + + # shift covariates by one + if past_covariates is not None: + num_covariates = past_covariates.shape[-1] + past_covariates = np.concatenate( + [past_covariates, np.array([[0] * num_covariates])], axis=0 + ) + + if future_covariates is not None: + num_covariates = future_covariates.shape[-1] + future_covariates = np.concatenate( + [future_covariates, np.array([[0] * num_covariates])], axis=0 + ) + + if self.model_type == "causal": + # The InstanceSplitter pads time series on the left to be equal to the + # context_length. However, certain models (e.g., GPT2) with absolute + # position embeddings should not be trained with left padding. + # The following piece of code moves padding from left to right. + + assert input_ids.shape[-1] == entry["past_is_pad"].shape[0] + + # Find the index where padding starts + pad_start_idx = np.searchsorted(1 - entry["past_is_pad"], 1) + padded_input_ids, obs_input_ids = torch.tensor_split( + input_ids, [pad_start_idx], dim=-1 + ) + padded_attention_mask, obs_attention_mask = torch.tensor_split( + attention_mask, [pad_start_idx], dim=-1 + ) + + # Move padding to the right + input_ids = torch.cat( + [ + obs_input_ids, + labels, + padded_input_ids, + ], + axis=-1, + ) + attention_mask = torch.cat( + [ + obs_attention_mask, + labels_mask, + padded_attention_mask, + ], + axis=-1, + ) + + # labels for causal models are same as the input_ids. + # Internally transformers shifts the labels by one during training. + labels = input_ids.clone() + input_ids[~attention_mask] = self.tokenizer.config.pad_token_id + labels[~attention_mask] = -100 + return { + "input_ids": input_ids.squeeze(0), + "attention_mask": attention_mask.squeeze(0), + "labels": labels.squeeze(0), + "past_covariates": past_covariates.astype(np.float32), + "future_covariates": future_covariates.astype(np.float32), + "scale": scale, + } + + def __iter__(self) -> Iterator: + transformation = self.create_transformation() + + if self.include_covariates: + field_time = ( + FieldName.FEAT_TIME + if not self.include_covariates + else FieldName.FEAT_DYNAMIC_REAL + ) + transformation = Chain( + [ + transformation, + VstackFeatures( + output_field=FieldName.FEAT_DYNAMIC_REAL, + input_fields=[field_time], + ), + ] + ) + + if self.mode == "training": + transformed_datasets = [ + transformation.apply(dataset, is_train=True) + for dataset in self.datasets + ] + iterables = [ + self.create_training_data(transformed_dataset) + for transformed_dataset in transformed_datasets + ] + elif self.mode == "validation": + transformed_datasets = [ + transformation.apply(dataset, is_train=False) + for dataset in self.datasets + ] + iterables = [ + self.create_validation_data(transformed_dataset) + for transformed_dataset in transformed_datasets + ] + elif self.mode == "test": + transformed_datasets = [ + transformation.apply(dataset, is_train=False) + for dataset in self.datasets + ] + iterables = [ + self.create_test_data(transformed_dataset) + for transformed_dataset in transformed_datasets + ] + + worker_info = get_worker_info() + if worker_info is None: + probs = list(self.probabilities) + else: + worker_id = worker_info.id + num_workers = worker_info.num_workers + iterables = list(itertools.islice(iterables, worker_id, None, num_workers)) + probs = list( + itertools.islice(self.probabilities, worker_id, None, num_workers) + ) + + probs = [prob / sum(probs) for prob in probs] + + iterators = list(map(iter, iterables)) + if self.mode == "training": + while True: + idx = np.random.choice(range(len(iterators)), p=probs) + try: + yield self.to_hf_format(next(iterators[idx])) + except StopIteration: + probs[idx] = 0 + if sum(probs) == 0: + return + probs = [prob / sum(probs) for prob in probs] + else: + for entry in itertools.chain(*iterators): + yield self.to_hf_format(entry) diff --git a/src/chronosx/utils/epf.py b/src/chronosx/utils/epf.py new file mode 100644 index 0000000..73254dc --- /dev/null +++ b/src/chronosx/utils/epf.py @@ -0,0 +1,56 @@ +from pathlib import Path +from urllib import request +import datasets +import pandas as pd +import tempfile + + +MAIN_URL = "https://raw.githubusercontent.com/Nixtla/transfer-learning-time-series/f055eec3266eda77e183b62e9f07eff0bc6c155b/datasets/electricity.csv" +EX_URL = "https://raw.githubusercontent.com/Nixtla/transfer-learning-time-series/f055eec3266eda77e183b62e9f07eff0bc6c155b/datasets/exogenous-vars-electricity.csv" + + +def load_data(id: str): + with tempfile.TemporaryDirectory() as dir_path: + data_path = Path(dir_path) / "data.csv" + + request.urlretrieve(MAIN_URL, data_path) + main_data = pd.read_csv(data_path) + main_data = main_data[main_data.unique_id == id][["ds", "y"]] + + request.urlretrieve(EX_URL, data_path) + ex_data = pd.read_csv(data_path) + ex_data = ex_data[ex_data.unique_id == id] + + time_data = pd.date_range( + start=main_data.ds.values[0], end=main_data.ds.values[-1], freq="H" + ) + data = pd.merge(main_data, ex_data, how="left", on="ds") + + assert time_data.shape[0] == data.shape[0] + + return data + + +def generate(id: str): + data = load_data(id) + df = data[["ds", "y", "unique_id", "Exogenous1", "Exogenous2"]] + df = df.rename( + columns={ + "ds": "timestamp", + "y": "target", + "unique_id": "id", + "Exogenous1": "exogenous1", + "Exogenous2": "exogenous2", + } + ) + + ds = datasets.Dataset.from_list([df.to_dict("list")]) + + return ds + + +def get_epf_dataset(dataset_name: str): + id = dataset_name.split("_")[-2].upper() + ds = generate(id) + + return ds diff --git a/src/chronosx/utils/hf_data_loader.py b/src/chronosx/utils/hf_data_loader.py new file mode 100644 index 0000000..7594c40 --- /dev/null +++ b/src/chronosx/utils/hf_data_loader.py @@ -0,0 +1,92 @@ +import datasets +import numpy as np +import pandas as pd +from gluonts.dataset.field_names import FieldName +from gluonts.dataset.split import split +from typing import List +from .m5 import get_m5_dataset +from .epf import get_epf_dataset + + +def to_gluonts_univariate( + hf_dataset: datasets.Dataset, + series_fields: List[str], + covariates_fields: List[str] = None, +): + if isinstance(series_fields, str): + series_fields = [series_fields] + + # dataset_length = hf_dataset.info.splits["train"].num_examples * len(series_fields) + dataset_length = len(hf_dataset) * len(series_fields) + + # Assumes that all time series in the dataset have the same frequency + dataset_freq = pd.DatetimeIndex(hf_dataset[0]["timestamp"]).to_period()[0].freqstr + + gts_dataset = [] + for hf_entry in hf_dataset: + for field in series_fields: + entry = { + "start": pd.Period( + hf_entry["timestamp"][0], + freq=dataset_freq, + ), + "target": hf_entry[field], + } + + if covariates_fields: + covariates = np.array([hf_entry[field] for field in covariates_fields]) + is_nan_covariates = np.isnan(covariates) + covariates[is_nan_covariates] = -1 + covariates = np.vstack([covariates, is_nan_covariates]).astype( + np.float32 + ) + entry.update({FieldName.FEAT_DYNAMIC_REAL: covariates}) + + gts_dataset.append(entry) + + assert len(gts_dataset) == dataset_length + + return gts_dataset + + +def load_and_split_dataset(backtest_config: dict): + hf_repo = backtest_config.get("hf_repo", None) + filename = backtest_config.get("filename", None) + dataset_name = backtest_config["name"] + offset = backtest_config["offset"] + prediction_length = backtest_config["prediction_length"] + num_rolls = backtest_config["num_rolls"] + series_fields = backtest_config.get("series_fields") + covariates_fields = backtest_config.get("covariates_fields") + + if dataset_name == "m5_with_covariates": + ds = get_m5_dataset() + + elif dataset_name in [ + "epf_electricity_be_paper", + "epf_electricity_de_paper", + "epf_electricity_fr_paper", + "epf_electricity_np_paper", + "epf_electricity_pjm_paper", + ]: + ds = get_epf_dataset(dataset_name) + + elif 'synthetic_datasets' in dataset_name: + ds = datasets.load_from_disk(filename) + + else: + ds = datasets.load_dataset( + hf_repo, dataset_name, split="train", trust_remote_code=True + ) + + ds.set_format("numpy") + + gts_dataset = to_gluonts_univariate( + ds, covariates_fields=covariates_fields, series_fields=series_fields + ) + + # Split dataset for evaluation + train_dataset, test_template = split(gts_dataset, offset=offset) + test_data = test_template.generate_instances(prediction_length, windows=num_rolls) + + return train_dataset, test_data diff --git a/src/chronosx/utils/m5.py b/src/chronosx/utils/m5.py new file mode 100644 index 0000000..151c8a4 --- /dev/null +++ b/src/chronosx/utils/m5.py @@ -0,0 +1,55 @@ +from datasetsforecast.m5 import M5 +from joblib import Parallel, delayed +from pathlib import Path +from tqdm.auto import tqdm +import datasets +import pandas as pd +import pyarrow as pa + +def _process_entry(id_, ts, static_df): + entry = {"id": id_} + entry.update(ts.drop(columns=["id"]).to_dict("list")) + entry.update(static_df.loc[id_].to_dict()) + return entry + + +def generate(): + target, covariates, static = M5.load(".") + + encoded_columns = pd.get_dummies( + covariates[["event_type_1", "event_type_2"]] + ).astype(float) + + covariates = covariates[["snap_CA", "snap_TX", "snap_WI", "sell_price"]] + covariates = pd.concat([covariates, encoded_columns], axis=1) + + df = pd.concat([target, covariates], axis=1) + df = df.rename(columns={"unique_id": "id", "ds": "timestamp", "y": "target"}) + df["timestamp"] = df["timestamp"].astype("datetime64[ms]") + + static = static.rename(columns={"unique_id": "id"}).set_index("id") + + processed = Parallel(n_jobs=-1)( + delayed(_process_entry)(id_, ts, static) for id_, ts in tqdm(df.groupby("id")) + ) + + table = pa.Table.from_pylist(processed) + + splits = datasets.SplitDict() + splits.add(datasets.SplitInfo('train', num_examples=len(processed))) + info = datasets.DatasetInfo(splits=splits) + + dataset = datasets.Dataset(table, info=info, split='train') + dataset.save_to_disk(Path(__file__).parent / "m5_with_covariates") + + +def get_m5_dataset(force_computation: bool = False): + if (Path(__file__).parent / "m5_with_covariates").is_dir(): + if force_computation is True: + generate() + else: + generate() + + return datasets.load_from_disk(Path(__file__).parent / "m5_with_covariates") + + diff --git a/src/chronosx/utils/prepare_covariates.py b/src/chronosx/utils/prepare_covariates.py new file mode 100644 index 0000000..eeb3ecd --- /dev/null +++ b/src/chronosx/utils/prepare_covariates.py @@ -0,0 +1,38 @@ +import numpy as np + + +def prepare_covariates(entry: dict) -> dict: + + past_covariates = entry.get("past_feat_dynamic_real", None) + if past_covariates is not None: + # missing value imputation for covariates + past_covariates[np.isnan(past_covariates)] = 0 + + # normalize covariates by mean of absolute values, + covariates_scale = np.mean(np.abs(past_covariates), axis=0) + covariates_scale[covariates_scale < 1] = 1.0 + past_covariates = past_covariates / covariates_scale + + # shift covariates by one + num_covariates = past_covariates.shape[-1] + past_covariates = np.concatenate( + [past_covariates, np.array([[0] * num_covariates])], axis=0 + ) + + future_covariates = entry.get("future_feat_dynamic_real", None) + if future_covariates is not None: + # missing value imputation for covariates + future_covariates[np.isnan(future_covariates)] = 0 + + # normalize covariates by mean of absolute values, + future_covariates = future_covariates / covariates_scale + + num_covariates = future_covariates.shape[-1] + future_covariates = np.concatenate( + [future_covariates, np.array([[0] * num_covariates])], axis=0 + ) + + return { + "past_covariates": past_covariates.astype(np.float32), + "future_covariates": future_covariates.astype(np.float32), + } diff --git a/src/chronosx/utils/transform.py b/src/chronosx/utils/transform.py new file mode 100644 index 0000000..c7b26dc --- /dev/null +++ b/src/chronosx/utils/transform.py @@ -0,0 +1,83 @@ +import numpy as np +from gluonts.dataset.common import DataEntry +from gluonts.dataset.field_names import FieldName +from gluonts.transform import ( + AddObservedValuesIndicator, + Chain, + MapTransformation, + SelectFields, + Transformation, + VstackFeatures, +) + + +class DropValues(MapTransformation): + def __init__( + self, + drop_prob: float = 0.0, + drop_mode: str = "upto", + target_field: str = "target", + ): + assert drop_mode in ["upto", "exact"] + assert 0.0 <= drop_prob <= 1.0 + + self.drop_prob = drop_prob + self.drop_mode = drop_mode + self.target_field = target_field + + def map_transform(self, data: DataEntry, is_train: bool) -> DataEntry: + if not is_train: + return data + + target = data[self.target_field].copy().astype(float) + + if self.drop_mode == "exact": + drop_p = self.drop_prob + else: + drop_p = np.random.uniform(low=0.0, high=self.drop_prob) + + mask = np.random.choice([True, False], size=len(target), p=[drop_p, 1 - drop_p]) + target[mask] = np.nan + data[self.target_field] = target + + return data + + +def create_transformation(include_covariates: bool) -> Transformation: + + fields = [ + FieldName.START, + FieldName.TARGET, + ] + if include_covariates: + fields.append(FieldName.FEAT_DYNAMIC_REAL) + + transformation = ( + SelectFields( + fields, + allow_missing=True, + ) + + DropValues(drop_prob=0.2, target_field=FieldName.TARGET) + + AddObservedValuesIndicator( + target_field=FieldName.TARGET, + output_field=FieldName.OBSERVED_VALUES, + ) + ) + + if include_covariates: + field_time = ( + FieldName.FEAT_TIME + if not include_covariates + else FieldName.FEAT_DYNAMIC_REAL + ) + assert field_time == "feat_dynamic_real" + transformation = Chain( + [ + transformation, + VstackFeatures( + output_field=FieldName.FEAT_DYNAMIC_REAL, input_fields=[field_time] + ), + ] + ) + + return transformation diff --git a/src/chronosx/utils/utils.py b/src/chronosx/utils/utils.py new file mode 100644 index 0000000..bf6cc90 --- /dev/null +++ b/src/chronosx/utils/utils.py @@ -0,0 +1,54 @@ +from torch import nn +import logging +import numpy as np +import os +import torch +import torch.distributed as dist + + +def count_parameters(model): + return sum(p.numel() for p in model.parameters() if p.requires_grad) + + +def compute_metrics(pred, num_classes=4096): + loss = nn.CrossEntropyLoss() + logits = torch.FloatTensor(pred.predictions[0]) + labels = torch.LongTensor(pred.label_ids) + + loss_value = loss(logits.reshape(-1, num_classes), labels.reshape(-1)) + return {"loss": loss_value} + + +def is_main_process(): + if not dist.is_torchelastic_launched(): + return True + return int(os.environ["RANK"]) == 0 + + +def log_on_main(msg: str, logger: logging.Logger, log_level: int = logging.INFO): + if is_main_process(): + logger.log(log_level, msg) + + +def has_enough_observations( + entry: dict, min_length: int = 0, max_missing_prop: float = 1.0 +) -> bool: + """ + Check if the given entry has enough observations in the ``"target"`` attribute. + + Parameters + ---------- + entry + The data entry (dictionary) to be tested. + min_length + The minimum length the ``"target"`` attribute must have. + max_missing_prop + The maximum proportion of missing data allowed in the ``"target"`` + attribute. + """ + if ( + len(entry["target"]) >= min_length + and np.isnan(entry["target"]).mean() <= max_missing_prop + ): + return True + return False diff --git a/test/__init__.py b/test/__init__.py deleted file mode 100644 index 04f8b7b..0000000 --- a/test/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -# SPDX-License-Identifier: Apache-2.0 diff --git a/test/dummy-chronos-bolt-model/config.json b/test/dummy-chronos-bolt-model/config.json deleted file mode 100644 index 96eb29a..0000000 --- a/test/dummy-chronos-bolt-model/config.json +++ /dev/null @@ -1,50 +0,0 @@ -{ - "architectures": [ - "ChronosBoltModelForForecasting" - ], - "chronos_config": { - "context_length": 512, - "input_patch_size": 16, - "input_patch_stride": 16, - "prediction_length": 64, - "quantiles": [ - 0.1, - 0.2, - 0.3, - 0.4, - 0.5, - 0.6, - 0.7, - 0.8, - 0.9 - ], - "use_reg_token": true - }, - "chronos_pipeline_class": "ChronosBoltPipeline", - "classifier_dropout": 0.0, - "d_ff": 8, - "d_kv": 4, - "d_model": 8, - "decoder_start_token_id": 0, - "dense_act_fn": "relu", - "dropout_rate": 0.1, - "eos_token_id": 1, - "feed_forward_proj": "relu", - "initializer_factor": 0.05, - "is_encoder_decoder": true, - "is_gated_act": false, - "layer_norm_epsilon": 1e-06, - "model_type": "t5", - "n_positions": 512, - "num_decoder_layers": 4, - "num_heads": 4, - "num_layers": 4, - "pad_token_id": 0, - "reg_token_id": 1, - "relative_attention_max_distance": 128, - "relative_attention_num_buckets": 32, - "torch_dtype": "float32", - "transformers_version": "4.40.2", - "use_cache": true, - "vocab_size": 2 -} diff --git a/test/dummy-chronos-bolt-model/model.safetensors b/test/dummy-chronos-bolt-model/model.safetensors deleted file mode 100644 index b7b5b4e75a5318c4a952358fa0f98ac6812d3783..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 85408 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 diff --git a/test/test_chronos.py b/test/test_chronos.py deleted file mode 100644 index 763fde2..0000000 --- a/test/test_chronos.py +++ /dev/null @@ -1,388 +0,0 @@ -# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -# SPDX-License-Identifier: Apache-2.0 - -from pathlib import Path - -import pytest -import torch - -from chronos import ( - BaseChronosPipeline, - ChronosConfig, - ChronosPipeline, - MeanScaleUniformBins, -) -from test.util import validate_tensor - - -def test_base_chronos_pipeline_loads_from_huggingface(): - BaseChronosPipeline.from_pretrained("amazon/chronos-t5-tiny", device_map="cpu") - - -@pytest.mark.parametrize("n_numerical_tokens", [5, 10, 27]) -@pytest.mark.parametrize("n_special_tokens", [2, 5, 13]) -def test_tokenizer_consistency(n_numerical_tokens: int, n_special_tokens: int): - n_tokens = n_numerical_tokens + n_special_tokens - - config = ChronosConfig( - tokenizer_class="MeanScaleUniformBins", - tokenizer_kwargs=dict(low_limit=-1.0, high_limit=1.0), - n_tokens=n_tokens, - n_special_tokens=n_special_tokens, - pad_token_id=0, - eos_token_id=1, - use_eos_token=True, - model_type="seq2seq", - context_length=512, - prediction_length=64, - num_samples=20, - temperature=1.0, - top_k=50, - top_p=1.0, - ) - - tokenizer = config.create_tokenizer() - assert isinstance(tokenizer, MeanScaleUniformBins) - - context = tokenizer.centers.unsqueeze(0) # add batch dimension - scale = torch.ones((1,)) # fix the scale to one to turn off scaling - - token_ids, _, _ = tokenizer._input_transform(context, scale=scale) - - samples = tokenizer.output_transform( - token_ids.unsqueeze(1), # add sample dimension - scale=scale, - ) - - assert (samples[0, 0, :] == context).all() - - -@pytest.mark.xfail -@pytest.mark.parametrize("n_numerical_tokens", [5, 10, 27]) -@pytest.mark.parametrize("n_special_tokens", [2, 5, 13]) -@pytest.mark.parametrize("use_eos_token", [False, True]) -def test_tokenizer_fixed_data( - n_numerical_tokens: int, n_special_tokens: int, use_eos_token: bool -): - n_tokens = n_numerical_tokens + n_special_tokens - context_length = 3 - - config = ChronosConfig( - tokenizer_class="MeanScaleUniformBins", - tokenizer_kwargs=dict(low_limit=-1.0, high_limit=1.0), - n_tokens=n_tokens, - n_special_tokens=n_special_tokens, - pad_token_id=0, - eos_token_id=1, - use_eos_token=use_eos_token, - model_type="seq2seq", - context_length=512, - prediction_length=64, - num_samples=20, - temperature=1.0, - top_k=50, - top_p=1.0, - ) - - tokenizer = config.create_tokenizer() - - context = torch.tensor( - [ - [-3.7, 3.7], - [-42.0, 42.0], - ] - ) - batch_size, _ = context.shape - - token_ids, attention_mask, scale = tokenizer.context_input_transform(context) - - assert token_ids.shape == (batch_size, context_length + 1 * use_eos_token) - assert all(token_ids[:, 0] == torch.tensor([0]).repeat(batch_size)) - assert all(token_ids[:, 1] == torch.tensor([n_special_tokens]).repeat(batch_size)) - assert all(token_ids[:, 2] == torch.tensor([n_tokens - 1]).repeat(batch_size)) - - if use_eos_token: - assert all(token_ids[:, 3] == torch.tensor([1]).repeat(batch_size)) - - samples = tokenizer.output_transform( - torch.arange(n_special_tokens, n_tokens).unsqueeze(0).repeat(batch_size, 1, 1), - tokenizer_state=scale, - ) - - assert (samples[:, 0, [0, -1]] == context).all() - - -@pytest.mark.xfail -@pytest.mark.parametrize("use_eos_token", [False, True]) -def test_tokenizer_random_data(use_eos_token: bool): - context_length = 8 - n_tokens = 256 - n_special_tokens = 2 - - config = ChronosConfig( - tokenizer_class="MeanScaleUniformBins", - tokenizer_kwargs=dict(low_limit=-1.0, high_limit=1.0), - n_tokens=n_tokens, - n_special_tokens=n_special_tokens, - pad_token_id=0, - eos_token_id=1, - use_eos_token=use_eos_token, - model_type="seq2seq", - context_length=context_length, - prediction_length=64, - num_samples=20, - temperature=1.0, - top_k=50, - top_p=1.0, - ) - - tokenizer = config.create_tokenizer() - - context = torch.tensor( - [ - [torch.nan, torch.nan, 1.0, 1.1, torch.nan, 2.0], - [3.0, torch.nan, 3.9, 4.0, 4.1, 4.9], - ] - ) - - token_ids, attention_mask, scale = tokenizer.context_input_transform(context) - - assert token_ids.shape == ( - *context.shape[:-1], - context_length + 1 * use_eos_token, - ) - assert attention_mask.shape == ( - *context.shape[:-1], - context_length + 1 * use_eos_token, - ) - assert scale.shape == context.shape[:1] - - sample_ids = torch.randint(low=n_special_tokens, high=n_tokens, size=(2, 10, 4)) - sample_ids[0, 0, 0] = n_special_tokens - sample_ids[-1, -1, -1] = n_tokens - 1 - - samples = tokenizer.output_transform(sample_ids, scale) - - assert samples.shape == (2, 10, 4) - - -@pytest.mark.parametrize("model_dtype", [torch.float32, torch.bfloat16]) -@pytest.mark.parametrize("input_dtype", [torch.float32, torch.bfloat16, torch.int64]) -def test_pipeline_predict(model_dtype: torch.dtype, input_dtype: torch.dtype): - pipeline = ChronosPipeline.from_pretrained( - Path(__file__).parent / "dummy-chronos-model", - device_map="cpu", - torch_dtype=model_dtype, - ) - context = 10 * torch.rand(size=(4, 16)) + 10 - context = context.to(dtype=input_dtype) - - # input: tensor of shape (batch_size, context_length) - - samples = pipeline.predict(context, num_samples=12, prediction_length=3) - validate_tensor(samples, shape=(4, 12, 3), dtype=torch.float32) - - with pytest.raises(ValueError): - samples = pipeline.predict( - context, num_samples=7, prediction_length=65, limit_prediction_length=True - ) - - samples = pipeline.predict( - context, num_samples=7, prediction_length=65, limit_prediction_length=False - ) - validate_tensor(samples, shape=(4, 7, 65), dtype=torch.float32) - - # input: batch_size-long list of tensors of shape (context_length,) - - samples = pipeline.predict(list(context), num_samples=12, prediction_length=3) - validate_tensor(samples, shape=(4, 12, 3), dtype=torch.float32) - - with pytest.raises(ValueError): - samples = pipeline.predict( - list(context), - num_samples=7, - prediction_length=65, - limit_prediction_length=True, - ) - - samples = pipeline.predict( - list(context), - num_samples=7, - prediction_length=65, - limit_prediction_length=False, - ) - validate_tensor(samples, shape=(4, 7, 65), dtype=torch.float32) - - # input: tensor of shape (context_length,) - - samples = pipeline.predict(context[0, ...], num_samples=12, prediction_length=3) - validate_tensor(samples, shape=(1, 12, 3), dtype=torch.float32) - - with pytest.raises(ValueError): - samples = pipeline.predict( - context[0, ...], - num_samples=7, - prediction_length=65, - limit_prediction_length=True, - ) - - samples = pipeline.predict( - context[0, ...], - num_samples=7, - prediction_length=65, - ) - validate_tensor(samples, shape=(1, 7, 65), dtype=torch.float32) - - -@pytest.mark.parametrize("model_dtype", [torch.float32, torch.bfloat16]) -@pytest.mark.parametrize("input_dtype", [torch.float32, torch.bfloat16, torch.int64]) -@pytest.mark.parametrize("prediction_length", [3, 65]) -@pytest.mark.parametrize( - "quantile_levels", [[0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9], [0.1, 0.5, 0.9]] -) -def test_pipeline_predict_quantiles( - model_dtype: torch.dtype, - input_dtype: torch.dtype, - prediction_length: int, - quantile_levels: list[int], -): - pipeline = ChronosPipeline.from_pretrained( - Path(__file__).parent / "dummy-chronos-model", - device_map="cpu", - torch_dtype=model_dtype, - ) - context = 10 * torch.rand(size=(4, 16)) + 10 - context = context.to(dtype=input_dtype) - - num_expected_quantiles = len(quantile_levels) - # input: tensor of shape (batch_size, context_length) - - quantiles, mean = pipeline.predict_quantiles( - context, - num_samples=12, - prediction_length=prediction_length, - quantile_levels=quantile_levels, - ) - validate_tensor( - quantiles, (4, prediction_length, num_expected_quantiles), dtype=torch.float32 - ) - validate_tensor(mean, (4, prediction_length), dtype=torch.float32) - - # input: batch_size-long list of tensors of shape (context_length,) - - quantiles, mean = pipeline.predict_quantiles( - list(context), - num_samples=12, - prediction_length=prediction_length, - quantile_levels=quantile_levels, - ) - validate_tensor( - quantiles, (4, prediction_length, num_expected_quantiles), dtype=torch.float32 - ) - validate_tensor(mean, (4, prediction_length), dtype=torch.float32) - - # input: tensor of shape (context_length,) - - quantiles, mean = pipeline.predict_quantiles( - context[0, ...], - num_samples=12, - prediction_length=prediction_length, - quantile_levels=quantile_levels, - ) - validate_tensor( - quantiles, (1, prediction_length, num_expected_quantiles), dtype=torch.float32 - ) - validate_tensor(mean, (1, prediction_length), dtype=torch.float32) - - -@pytest.mark.parametrize("model_dtype", [torch.float32, torch.bfloat16]) -@pytest.mark.parametrize("input_dtype", [torch.float32, torch.bfloat16, torch.int64]) -def test_pipeline_embed(model_dtype: torch.dtype, input_dtype: torch.dtype): - pipeline = ChronosPipeline.from_pretrained( - Path(__file__).parent / "dummy-chronos-model", - device_map="cpu", - torch_dtype=model_dtype, - ) - d_model = pipeline.model.model.config.d_model - context = 10 * torch.rand(size=(4, 16)) + 10 - context = context.to(dtype=input_dtype) - expected_embed_length = 16 + (1 if pipeline.model.config.use_eos_token else 0) - - # input: tensor of shape (batch_size, context_length) - - embedding, scale = pipeline.embed(context) - validate_tensor( - embedding, shape=(4, expected_embed_length, d_model), dtype=model_dtype - ) - validate_tensor(scale, shape=(4,), dtype=torch.float32) - - # input: batch_size-long list of tensors of shape (context_length,) - - embedding, scale = pipeline.embed(list(context)) - validate_tensor( - embedding, shape=(4, expected_embed_length, d_model), dtype=model_dtype - ) - validate_tensor(scale, shape=(4,), dtype=torch.float32) - - # input: tensor of shape (context_length,) - embedding, scale = pipeline.embed(context[0, ...]) - validate_tensor( - embedding, shape=(1, expected_embed_length, d_model), dtype=model_dtype - ) - validate_tensor(scale, shape=(1,), dtype=torch.float32) - - -@pytest.mark.parametrize("n_tokens", [10, 1000, 10000]) -def test_tokenizer_number_of_buckets(n_tokens): - config = ChronosConfig( - tokenizer_class="MeanScaleUniformBins", - tokenizer_kwargs=dict(low_limit=-1.0, high_limit=1.0), - n_tokens=n_tokens, - n_special_tokens=2, - pad_token_id=0, - eos_token_id=1, - use_eos_token=True, - model_type="seq2seq", - context_length=512, - prediction_length=64, - num_samples=20, - temperature=1.0, - top_k=50, - top_p=1.0, - ) - tokenizer = config.create_tokenizer() - - n_numerical_tokens = config.n_tokens - config.n_special_tokens - - # The tokenizer has one bucket too many as a result of an early bug. In order to - # keep consistent with the original trained models, this is kept as it is. However, - # token ids are clipped to a maximum of `n_tokens - 1` to avoid out-of-bounds errors. - assert len(tokenizer.centers) == (n_numerical_tokens - 1) - assert len(tokenizer.boundaries) == n_numerical_tokens - - -@pytest.mark.parametrize("n_tokens", [10, 1000, 10000]) -def test_token_clipping(n_tokens): - config = ChronosConfig( - tokenizer_class="MeanScaleUniformBins", - tokenizer_kwargs={"low_limit": -15, "high_limit": 15}, - n_tokens=n_tokens, - n_special_tokens=2, - pad_token_id=0, - eos_token_id=1, - use_eos_token=True, - model_type="seq2seq", - context_length=512, - prediction_length=64, - num_samples=20, - temperature=1.0, - top_k=50, - top_p=1.0, - ) - tokenizer = config.create_tokenizer() - - huge_value = 1e22 # this large value is assigned to the largest bucket - token_ids, _, _ = tokenizer._input_transform( - context=torch.tensor([[huge_value]]), scale=torch.tensor(([1])) - ) - assert token_ids[0, 0] == config.n_tokens - 1 # and it's clipped to n_tokens - 1 diff --git a/test/test_chronos_bolt.py b/test/test_chronos_bolt.py deleted file mode 100644 index 657d0df..0000000 --- a/test/test_chronos_bolt.py +++ /dev/null @@ -1,301 +0,0 @@ -# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -# SPDX-License-Identifier: Apache-2.0 - -from pathlib import Path - -import pytest -import torch - -from chronos import BaseChronosPipeline, ChronosBoltPipeline -from chronos.chronos_bolt import InstanceNorm, Patch -from test.util import validate_tensor - - -def test_base_chronos_pipeline_loads_from_huggingface(): - BaseChronosPipeline.from_pretrained("amazon/chronos-bolt-tiny", device_map="cpu") - - -@pytest.mark.parametrize("torch_dtype", [torch.float32, torch.bfloat16]) -@pytest.mark.parametrize("input_dtype", [torch.float32, torch.bfloat16, torch.int64]) -def test_pipeline_predict(torch_dtype: torch.dtype, input_dtype: torch.dtype): - pipeline = ChronosBoltPipeline.from_pretrained( - Path(__file__).parent / "dummy-chronos-bolt-model", - device_map="cpu", - torch_dtype=torch_dtype, - ) - context = 10 * torch.rand(size=(4, 16)) + 10 - context = context.to(dtype=input_dtype) - expected_num_quantiles = len(pipeline.quantiles) - - # input: tensor of shape (batch_size, context_length) - - quantiles = pipeline.predict(context, prediction_length=3) - validate_tensor(quantiles, (4, expected_num_quantiles, 3), dtype=torch.float32) - - with pytest.raises(ValueError): - quantiles = pipeline.predict( - context, prediction_length=65, limit_prediction_length=True - ) - - quantiles = pipeline.predict(context, prediction_length=65) - validate_tensor(quantiles, (4, expected_num_quantiles, 65)) - - # input: batch_size-long list of tensors of shape (context_length,) - - quantiles = pipeline.predict(list(context), prediction_length=3) - validate_tensor(quantiles, (4, expected_num_quantiles, 3), dtype=torch.float32) - - with pytest.raises(ValueError): - quantiles = pipeline.predict( - list(context), - prediction_length=65, - limit_prediction_length=True, - ) - - quantiles = pipeline.predict(list(context), prediction_length=65) - validate_tensor(quantiles, (4, expected_num_quantiles, 65), dtype=torch.float32) - - # input: tensor of shape (context_length,) - - quantiles = pipeline.predict(context[0, ...], prediction_length=3) - validate_tensor(quantiles, (1, expected_num_quantiles, 3), dtype=torch.float32) - - with pytest.raises(ValueError): - quantiles = pipeline.predict( - context[0, ...], - prediction_length=65, - limit_prediction_length=True, - ) - - quantiles = pipeline.predict( - context[0, ...], - prediction_length=65, - ) - validate_tensor(quantiles, (1, expected_num_quantiles, 65), dtype=torch.float32) - - -@pytest.mark.parametrize("torch_dtype", [torch.float32, torch.bfloat16]) -@pytest.mark.parametrize("input_dtype", [torch.float32, torch.bfloat16, torch.int64]) -@pytest.mark.parametrize("prediction_length", [3, 65]) -@pytest.mark.parametrize( - "quantile_levels", [[0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9], [0.1, 0.5, 0.9]] -) -def test_pipeline_predict_quantiles( - torch_dtype: torch.dtype, - input_dtype: torch.dtype, - prediction_length: int, - quantile_levels: list[int], -): - pipeline = ChronosBoltPipeline.from_pretrained( - Path(__file__).parent / "dummy-chronos-bolt-model", - device_map="cpu", - torch_dtype=torch_dtype, - ) - context = 10 * torch.rand(size=(4, 16)) + 10 - context = context.to(dtype=input_dtype) - - num_expected_quantiles = len(quantile_levels) - # input: tensor of shape (batch_size, context_length) - - quantiles, mean = pipeline.predict_quantiles( - context, - prediction_length=prediction_length, - quantile_levels=quantile_levels, - ) - validate_tensor( - quantiles, (4, prediction_length, num_expected_quantiles), dtype=torch.float32 - ) - validate_tensor(mean, (4, prediction_length), dtype=torch.float32) - - # input: batch_size-long list of tensors of shape (context_length,) - - quantiles, mean = pipeline.predict_quantiles( - list(context), - prediction_length=prediction_length, - quantile_levels=quantile_levels, - ) - validate_tensor( - quantiles, (4, prediction_length, num_expected_quantiles), dtype=torch.float32 - ) - validate_tensor(mean, (4, prediction_length), dtype=torch.float32) - - # input: tensor of shape (context_length,) - - quantiles, mean = pipeline.predict_quantiles( - context[0, ...], - prediction_length=prediction_length, - quantile_levels=quantile_levels, - ) - validate_tensor( - quantiles, (1, prediction_length, num_expected_quantiles), dtype=torch.float32 - ) - validate_tensor(mean, (1, prediction_length), dtype=torch.float32) - - -@pytest.mark.parametrize("model_dtype", [torch.float32, torch.bfloat16]) -@pytest.mark.parametrize("input_dtype", [torch.float32, torch.bfloat16, torch.int64]) -def test_pipeline_embed(model_dtype: torch.dtype, input_dtype: torch.dtype): - pipeline = ChronosBoltPipeline.from_pretrained( - Path(__file__).parent / "dummy-chronos-bolt-model", - device_map="cpu", - torch_dtype=model_dtype, - ) - d_model = pipeline.model.config.d_model - context = 10 * torch.rand(size=(4, 16)) + 10 - context = context.to(dtype=input_dtype) - - # the patch size of dummy model is 16, so only 1 patch is created - expected_embed_length = 1 + ( - 1 if pipeline.model.config.chronos_config["use_reg_token"] else 0 - ) - - # input: tensor of shape (batch_size, context_length) - - embedding, loc_scale = pipeline.embed(context) - validate_tensor( - embedding, shape=(4, expected_embed_length, d_model), dtype=model_dtype - ) - validate_tensor(loc_scale[0], shape=(4,), dtype=torch.float32) - validate_tensor(loc_scale[1], shape=(4,), dtype=torch.float32) - - # input: batch_size-long list of tensors of shape (context_length,) - - embedding, loc_scale = pipeline.embed(list(context)) - validate_tensor( - embedding, shape=(4, expected_embed_length, d_model), dtype=model_dtype - ) - validate_tensor(loc_scale[0], shape=(4,), dtype=torch.float32) - validate_tensor(loc_scale[1], shape=(4,), dtype=torch.float32) - - # input: tensor of shape (context_length,) - embedding, loc_scale = pipeline.embed(context[0, ...]) - validate_tensor( - embedding, shape=(1, expected_embed_length, d_model), dtype=model_dtype - ) - validate_tensor(loc_scale[0], shape=(1,), dtype=torch.float32) - validate_tensor(loc_scale[1], shape=(1,), dtype=torch.float32) - - -# The following tests have been taken from -# https://github.com/autogluon/autogluon/blob/f57beb26cb769c6e0d484a6af2b89eab8aee73a8/timeseries/tests/unittests/models/chronos/pipeline/test_chronos_bolt.py -# Author: Caner Turkmen - - -def test_given_even_data_patch_operator_output_is_correct(): - batch_size = 17 - patch_len = 16 - - patch = Patch(patch_len, patch_len) - - batch = ( - torch.stack([torch.arange(512)] * batch_size) - + torch.arange(batch_size)[:, None] - ) - output = patch(batch) - - assert output.shape == (batch_size, 512 // patch_len, patch_len) - - assert torch.allclose( - output[:, 0], - torch.stack([torch.arange(patch_len)] * batch_size) - + torch.arange(batch_size)[:, None], - atol=1e-5, - ) - assert torch.allclose( - output[:, 1], - torch.stack([torch.arange(patch_len, 2 * patch_len)] * batch_size) - + torch.arange(batch_size)[:, None], - atol=1e-5, - ) - assert not torch.isnan(output).any() - - -def test_given_even_data_and_strides_patch_operator_output_is_correct(): - batch_size = 17 - patch_len, patch_stride = 16, 8 - - patch = Patch(patch_len, patch_stride) - - offset = torch.arange(batch_size)[:, None] - batch = torch.stack([torch.arange(512)] * batch_size) + offset - output = patch(batch) - - assert torch.allclose( - output[:, 1], - torch.stack([torch.arange(patch_stride, patch_stride + patch_len)] * batch_size) - + offset, - atol=1e-5, - ) - assert not torch.isnan(output).any() - - -def test_given_uneven_data_patch_operator_pads_and_output_is_correct(): - batch_size = 17 - patch_len = 16 - - patch = Patch(patch_len, patch_len) - - batch = ( - torch.stack([torch.arange(512 - patch_len + 1)] * batch_size) - + torch.arange(batch_size)[:, None] - ).float() - output = patch(batch) - - assert output.shape == (batch_size, 512 // patch_len, patch_len) - - # check the first portion is padded - assert torch.isnan(output[:, 0, :-1]).all() - - # check nowhere else is nan - assert not torch.isnan(output[:, 1:]).any() - - -def test_when_instancenorm_applied_then_standardization_correct(): - inorm = InstanceNorm() - - input_ = torch.tensor( - [ - [1, 2, 3, 4, 5], - [2, 3, 4, 5, 6], - ] - ).float() - - normalized, (loc, scale) = inorm(input_) - - assert normalized.shape == input_.shape - assert torch.allclose(normalized[0], normalized[1]) - assert torch.allclose(loc.squeeze(), torch.tensor([3.0, 4.0])) - assert torch.allclose(scale.squeeze(), torch.tensor(1.41421)) - - -def test_when_instancenorm_applied_and_reversed_then_nans_preserved(): - inorm = InstanceNorm() - - input_ = torch.tensor( - [ - [1, torch.nan, 3, 4, 5], - [2, 3, 4, 5, torch.nan], - ] - ).float() - - normalized, (loc, scale) = inorm(input_) - assert torch.allclose(normalized.isnan(), input_.isnan()) - - output = inorm.inverse(normalized, (loc, scale)) - assert torch.allclose(output, input_, equal_nan=True) - - -def test_when_instancenorm_applied_and_reversed_then_output_correct(): - inorm = InstanceNorm() - - input_ = torch.tensor( - [ - [1, 2, 3, 4, 5], - [2, 3, 4, 5, 1000], - ] - ).float() - - normalized, loc_scale = inorm(input_) - output = inorm.inverse(normalized, loc_scale) - - assert torch.allclose(output, input_) diff --git a/test/test_utils.py b/test/test_utils.py deleted file mode 100644 index c6a91b1..0000000 --- a/test/test_utils.py +++ /dev/null @@ -1,29 +0,0 @@ -# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -# SPDX-License-Identifier: Apache-2.0 - -import pytest -import torch - -from chronos.utils import left_pad_and_stack_1D - - -@pytest.mark.parametrize( - "tensors", - [ - [ - torch.tensor([2.0, 3.0], dtype=dtype), - torch.tensor([4.0, 5.0, 6.0], dtype=dtype), - torch.tensor([7.0, 8.0, 9.0, 10.0], dtype=dtype), - ] - for dtype in [torch.int, torch.float16, torch.float32] - ], -) -def test_pad_and_stack(tensors: list): - stacked_and_padded = left_pad_and_stack_1D(tensors) - - assert stacked_and_padded.dtype == torch.float32 - assert stacked_and_padded.shape == (len(tensors), max(len(t) for t in tensors)) - - ref = torch.concat(tensors).to(dtype=stacked_and_padded.dtype) - - assert torch.sum(torch.nan_to_num(stacked_and_padded, nan=0)) == torch.sum(ref) diff --git a/test/util.py b/test/util.py deleted file mode 100644 index 78c2e93..0000000 --- a/test/util.py +++ /dev/null @@ -1,13 +0,0 @@ -from typing import Optional, Tuple - -import torch - - -def validate_tensor( - a: torch.Tensor, shape: Tuple[int, ...], dtype: Optional[torch.dtype] = None -) -> None: - assert isinstance(a, torch.Tensor) - assert a.shape == shape - - if dtype is not None: - assert a.dtype == dtype