Forecasting with Chronos

Open In Colab Open In SageMaker Studio Lab

AutoGluon-TimeSeries (AG-TS) now features Chronos, a family of pretrained time series forecasting models. Chronos models are based on language model architectures, and work by quantizing time series into buckets which are treated as tokens. Language models are then trained on these token sequences using cross-entropy loss.

The current iteration of Chronos models, available on Hugging Face 🤗, is based on the T5 architecture and was trained on a large corpus of open-source time series data augmented with synthetic data generation techniques. The Chronos paper provides greater detail about the models and how they were trained.

AG-TS provides a robust and easy way to use Chronos through the familiar TimeSeriesPredictor API.

  • Chronos can be combined with other forecasting models to build accurate ensembles using the "high_quality" and "best_quality" presets.

  • Alternatively, Chronos can be used as a standalone zero-shot model with presets such as "chronos_small" or "chronos_base".

from autogluon.timeseries import TimeSeriesDataFrame, TimeSeriesPredictor

Getting Started with Chronos

Chronos is available in 5 model sizes with different numbers of parameters: tiny (8M), mini (20M), small (46M), base (200M), and large (710M). Being a pretrained model for zero-shot forecasting, Chronos is different from other models available in AG-TS. Specifically, Chronos models do not really fit time series data. However, when predict is called, they carry out a relatively more expensive computation that scales linearly with the number of time series in the dataset. In this aspect, they behave like local statistical models such as ETS or ARIMA, where expensive computation happens during inference. Differently from statistical models, however, computation in the larger Chronos models requires an accelerator chip to run in a reasonable amount of time.

The easiest way to get started with Chronos is through model-specific presets available in the TimeSeriesPredictor. As of v1.1, the TimeSeriesPredictor.fit method has a separate Chronos preset for each model size, such as "chronos_small" or "chronos_base".

Alternatively, Chronos can be combined with other time series models using presets "chronos_ensemble", "chronos_large_ensemble", "high_quality" and "best_quality". More details about these presets are available in the documentation for TimeSeriesPredictor.fit.

Note that the model sizes small and higher require a GPU to run. However, models tiny and mini can be run on the CPU as well.

Let’s work with a subset of the M4 competition data set to see Chronos-tiny in action.

data = TimeSeriesDataFrame(
    "https://autogluon.s3.amazonaws.com/datasets/timeseries/m4_hourly_tiny/train.csv"
)
data.head()
target
item_id timestamp
H1 1750-01-01 00:00:00 605.0
1750-01-01 01:00:00 586.0
1750-01-01 02:00:00 586.0
1750-01-01 03:00:00 559.0
1750-01-01 04:00:00 511.0
prediction_length = 24
train_data, test_data = data.train_test_split(prediction_length)

predictor = TimeSeriesPredictor(prediction_length=prediction_length).fit(
    train_data, presets="chronos_tiny",
)
Beginning AutoGluon training...
AutoGluon will save models to 'AutogluonModels/ag-20241108_175404'
=================== System Info ===================
AutoGluon Version:  1.1.2b20241108
Python Version:     3.11.9
Operating System:   Linux
Platform Machine:   x86_64
Platform Version:   #1 SMP Tue Sep 24 10:00:37 UTC 2024
CPU Count:          8
GPU Count:          1
Memory Avail:       28.62 GB / 30.95 GB (92.5%)
Disk Space Avail:   213.67 GB / 255.99 GB (83.5%)
===================================================
Setting presets to: chronos_tiny
Fitting with arguments:
{'enable_ensemble': True,
 'eval_metric': WQL,
 'hyperparameters': {'Chronos': {'model_path': 'tiny'}},
 'known_covariates_names': [],
 'num_val_windows': 1,
 'prediction_length': 24,
 'quantile_levels': [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9],
 'random_seed': 123,
 'refit_every_n_windows': 1,
 'refit_full': False,
 'skip_model_selection': True,
 'target': 'target',
 'verbosity': 2}
/home/ci/autogluon/timeseries/src/autogluon/timeseries/predictor.py:296: SettingWithCopyWarning: 
A value is trying to be set on a copy of a slice from a DataFrame.
Try using .loc[row_indexer,col_indexer] = value instead

See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy
  df[self.target] = df[self.target].astype("float64")
Inferred time series frequency: 'h'
Provided train_data has 13520 rows, 20 time series. Median time series length is 676 (min=676, max=676).
Provided data contains following columns:
target: 'target'
AutoGluon will gauge predictive performance using evaluation metric: 'WQL'
This metric's sign has been flipped to adhere to being higher_is_better. The metric score can be multiplied by -1 to get the metric value.
===================================================
Starting training. Start time is 2024-11-08 17:54:04
Models that will be trained: ['Chronos[tiny]']
Training timeseries model Chronos[tiny].
0.00    s     = Training runtime
Training complete. Models trained: ['Chronos[tiny]']
Total runtime: 0.00 s
Best model: Chronos[tiny]

As promised, Chronos does not take any time to fit. The fit call merely serves as a proxy for the TimeSeriesPredictor to do some of its chores under the hood, such as inferring the frequency of time series and saving the predictor’s state to disk.

Let’s use the predict method to generate forecasts, and the plot method to visualize them.

predictions = predictor.predict(train_data)
predictor.plot(
    data=data, 
    predictions=predictions, 
    item_ids=["H1", "H2"],
    max_history_length=200,
);
/home/ci/autogluon/timeseries/src/autogluon/timeseries/predictor.py:296: SettingWithCopyWarning: 
A value is trying to be set on a copy of a slice from a DataFrame.
Try using .loc[row_indexer,col_indexer] = value instead

See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy
  df[self.target] = df[self.target].astype("float64")
Model not specified in predict, will default to the model with the best validation score: Chronos[tiny]
Model Chronos[tiny] failed to predict with the following exception:
Traceback (most recent call last):
  File "/home/ci/autogluon/timeseries/src/autogluon/timeseries/trainer/abstract_trainer.py", line 1209, in get_model_pred_dict
    model_pred_dict[model_name] = self._predict_model(
                                  ^^^^^^^^^^^^^^^^^^^^
  File "/home/ci/autogluon/timeseries/src/autogluon/timeseries/trainer/abstract_trainer.py", line 1136, in _predict_model
    return model.predict(data, known_covariates=known_covariates)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/ci/autogluon/timeseries/src/autogluon/timeseries/models/abstract/abstract_timeseries_model.py", line 330, in predict
    predictions = self._predict(data=data, known_covariates=known_covariates, **kwargs)
                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/ci/autogluon/timeseries/src/autogluon/timeseries/models/chronos/model.py", line 329, in _predict
    np.concatenate(batch_quantiles, axis=0).reshape(-1, len(self.quantile_levels)),
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
ValueError: all the input array dimensions except for the concatenation axis must match exactly, but along dimension 1, the array at index 0 has size 16 and the array at index 1 has size 4
---------------------------------------------------------------------------
RuntimeError                              Traceback (most recent call last)
Cell In[4], line 1
----> 1 predictions = predictor.predict(train_data)
      2 predictor.plot(
      3     data=data, 
      4     predictions=predictions, 
      5     item_ids=["H1", "H2"],
      6     max_history_length=200,
      7 );

File ~/autogluon/timeseries/src/autogluon/timeseries/predictor.py:850, in TimeSeriesPredictor.predict(self, data, known_covariates, model, use_cache, random_seed)
    848 if known_covariates is not None:
    849     known_covariates = self._to_data_frame(known_covariates)
--> 850 predictions = self._learner.predict(
    851     data,
    852     known_covariates=known_covariates,
    853     model=model,
    854     use_cache=use_cache,
    855     random_seed=random_seed,
    856 )
    857 return predictions.reindex(original_item_id_order, level=ITEMID)

File ~/autogluon/timeseries/src/autogluon/timeseries/learner.py:185, in TimeSeriesLearner.predict(self, data, known_covariates, model, use_cache, random_seed, **kwargs)
    183 known_covariates = self.feature_generator.transform_future_known_covariates(known_covariates)
    184 known_covariates = self._align_covariates_with_forecast_index(known_covariates=known_covariates, data=data)
--> 185 return self.load_trainer().predict(
    186     data=data,
    187     known_covariates=known_covariates,
    188     model=model,
    189     use_cache=use_cache,
    190     random_seed=random_seed,
    191     **kwargs,
    192 )

File ~/autogluon/timeseries/src/autogluon/timeseries/trainer/abstract_trainer.py:922, in AbstractTimeSeriesTrainer.predict(self, data, known_covariates, model, use_cache, random_seed, **kwargs)
    912 def predict(
    913     self,
    914     data: TimeSeriesDataFrame,
   (...)
    919     **kwargs,
    920 ) -> TimeSeriesDataFrame:
    921     model_name = self._get_model_for_prediction(model)
--> 922     model_pred_dict = self.get_model_pred_dict(
    923         model_names=[model_name],
    924         data=data,
    925         known_covariates=known_covariates,
    926         use_cache=use_cache,
    927         random_seed=random_seed,
    928     )
    929     return model_pred_dict[model_name]

File ~/autogluon/timeseries/src/autogluon/timeseries/trainer/abstract_trainer.py:1224, in AbstractTimeSeriesTrainer.get_model_pred_dict(self, model_names, data, known_covariates, record_pred_time, raise_exception_if_failed, use_cache, random_seed)
   1221             pred_time_dict_marginal[model_name] = None
   1223 if len(failed_models) > 0 and raise_exception_if_failed:
-> 1224     raise RuntimeError(f"Following models failed to predict: {failed_models}")
   1225 if self.cache_predictions and use_cache:
   1226     self._save_cached_pred_dicts(
   1227         dataset_hash, model_pred_dict=model_pred_dict, pred_time_dict=pred_time_dict_marginal
   1228     )

RuntimeError: Following models failed to predict: ['Chronos[tiny]']

Configuring for Performance

Looks good! As with all large deep learning models, however, some fine-grained control of inference parameters can be needed to both optimize the speed and avoid out-of-memory issues on specific hardware. For this, we will need to dive a bit deeper, configuring hyperparameters of the TimeSeriesPredictor directly.

predictor = TimeSeriesPredictor(prediction_length=prediction_length).fit(
    train_data,
    hyperparameters={
        "Chronos": {
            "model_path": "tiny",
            "batch_size": 64,
            "device": "cpu",
        }
    },
    skip_model_selection=True,
    verbosity=0,
)
%%time
predictions = predictor.predict(train_data)
CPU times: user 2min 4s, sys: 42.4 s, total: 2min 47s
Wall time: 11.2 s

Above, we used the following configuration options for the TimeSeriesPredictor:

  • we set skip_model_selection=True to skip running backtests during fit, as we will only consider a single model.

  • in the hyperparameters for the Chronos model,

    • model_path allows us to change the model size or select different pretrained weights. This parameter can be a model string like tiny or base, a Hugging Face path like amazon/chronos-t5-mini, or a path to a local folder with custom weights.

    • batch_size configures the number of time series for which predictions are generated in parallel.

    • device instructs Chronos to run the model on CPU.

As we see, inference speed is slower on the CPU compared to the GPU, taking about 400ms per time series. To overcome this limitation, AutoGluon implementation of Chronos supports several deep learning compilers that can optimize model performance on CPUs.

For example, we can set optimization_strategy="openvino" to use the OpenVINO compiler for Intel CPUs to speed up Chronos inference. Behind the scenes, AutoGluon will use Hugging Face optimum for this conversion.

Note that this requires installing the optional OpenVINO dependency for AG-TS.

!pip install -q "autogluon.timeseries[chronos-openvino]"

To speed up the inference even further, we can persist the model after calling fit. The TimeSeriesPredictor.persist method tells AutoGluon to keep the Chronos model in device memory for fast, on-demand inference instead of loading the model from disk each time.

%%capture
predictor = TimeSeriesPredictor(prediction_length=prediction_length).fit(
    train_data,
    hyperparameters={
        "Chronos": {
            "model_path": "tiny",
            "batch_size": 64,
            "device": "cpu",
            "optimization_strategy": "openvino",
        }
    },
    skip_model_selection=True,
    verbosity=0,
)
predictor.persist()
%%time
predictions = predictor.predict(train_data)
CPU times: user 1min 8s, sys: 9.19 s, total: 1min 17s
Wall time: 2.9 s

That reduced the inference time by ~3x!

We could have also used the ONNX runtime by providing optimization_strategy="onnx". For a discussion of these and other hyperparameters of Chronos, see the Chronos model documentation.

FAQ

How accurate is Chronos?

In several independent evaluations we found Chronos to be effective in zero-shot forecasting. The accuracy of Chronos-large often exceeds statistical baseline models, and is often comparable to deep learning models such as TemporalFusionTransformer or PatchTST.

What hardware do larger Chronos models require?

We tested Chronos on AWS g5.2xlarge and p3.2xlarge instances that feature NVIDIA A10G and V100 GPUs, with at least 16GiB of GPU memory and 32GiB of main memory.

Can I fine-tune Chronos?

The current iteration of Chronos on AutoGluon does not support fine tuning, although we will provide this functionality in later versions of AutoGluon.

Does Chronos work with covariates or features?

The current iteration of Chronos does not support covariates or features, however we will provide this functionality in later versions. In the meanwhile, presets such as chronos_ensemble combine Chronos with models that do take advantage of features.

Where can I ask specific questions on Chronos?

The AutoGluon team are among the core developers of Chronos. So you can ask Chronos-related questions on AutoGluon channels such as the Discord server, or GitHub. You can also join the discussion on the Chronos GitHub page.