diff --git a/CHANGELOG.md b/CHANGELOG.md index 9b921ad3de..3cd106209e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,9 +11,12 @@ but cannot always guarantee backwards compatibility. Changes that may **break co **Improved** +**Improved** + - 🚀🚀 Added new forecasting model `PatchTSTFMModel` : IBM's pre-trained ~260M-parameter foundational model for zero-shot forecasting. It supports univariate, multivariate, and multiple time series forecasting without training and can output deterministic or probabilistic forecasts. [#3120](https://github.com/unit8co/darts/pull/3120) by [Dennis Bader](https://github.com/dennisbader). - Added `use_longer_projection_head` to `TimesFM2p5Model` to enable longer non-autoregressive prediction horizons (up to 1024 steps for `output_chunk_length + output_chunk_shift`). [#3121](https://github.com/unit8co/darts/pull/3121) by [Zhihao Dai](https://github.com/daidahao). - `TimeSeries.from_dataframe()` now supports time columns of type `pl.Date` for `polars.DataFrame`. [#3124](https://github.com/unit8co/darts/pull/3124) by [Dennis Bader](https://github.com/dennisbader) +- Added `save()` and `load()` methods to anomaly detection classes (AnomalyScorer, Detector, Aggregator, AnomalyModel) to persist and reload trained scorers, detectors, aggregators, and anomaly models. [#3051](https://github.com/unit8co/darts/issues/3051) by [Robert Ruidisch](https://github.com/robrui). - Custom encoders now support functions that return multiple components. Simply pass such a function via the `"custom"` encoder key in the `add_encoders` model input parameter. [#3069](https://github.com/unit8co/darts/pull/3069) by [Moritz Waldleben](https://github.com/mwaldleben). **Fixed** @@ -35,6 +38,8 @@ but cannot always guarantee backwards compatibility. Changes that may **break co **Improved** +**Improved** + **Fixed** - Fixed a `ValueError` in `backtest()` when using `overlap_end=True` with `predict_likelihood_parameters=True` and a quantile metric. The final forecast window could extend beyond the series end, producing an empty intersection that caused a reshape failure in the metric computation. [#3111](https://github.com/unit8co/darts/pull/3111) by [Dennis Bader](https://github.com/dennisbader) @@ -62,6 +67,8 @@ but cannot always guarantee backwards compatibility. Changes that may **break co ### For users of the library: +**Improved** + **Improved** - 🚀🚀 Dramatically reduced import times by deferring heavy third-party dependencies (torch, sklearn, scipy, ...) until they are actually needed. This benefits cold-start scenarios (serverless functions, CLI tools, short-lived scripts), CI pipelines, and interactive development workflows where fast feedback loops matter. Here are some import speed-up examples: [#3066](https://github.com/unit8co/darts/pull/3066) by [Dennis Bader](https://github.com/dennisbader) - TimeSeries, metrics, datasets, data transformers: 8x faster (2.4 → 0.3 seconds) @@ -108,6 +115,8 @@ but cannot always guarantee backwards compatibility. Changes that may **break co **Improved** +**Improved** + - Improvements to data transformers: [#3023](https://github.com/unit8co/darts/pull/3023) by [Krzsztof Pęczek](https://github.com/u8-krpeczek) - Added parameter `columns` to all data transformers (e.g. `Scaler`, `Diff`, `BoxCox`, ...) to apply the transformations only on subset of components. This makes it much easier to build multivariate pipelines where different components require different transformations. - Improvements to `TFTExplainer` : [#3039](https://github.com/unit8co/darts/pull/3039) by [ReinerBRO](https://github.com/ReinerBRO). @@ -120,6 +129,8 @@ but cannot always guarantee backwards compatibility. Changes that may **break co ### For users of the library: +**Improved** + **Fixed** - Removed an incorrect warning being raised by `NeuralForecastModel` regarding using static covariates with multivariate base models. [#3036](https://github.com/unit8co/darts/pull/3036) by [Dennis Bader](https://github.com/dennisbader). @@ -130,6 +141,8 @@ but cannot always guarantee backwards compatibility. Changes that may **break co **Improved** +**Improved** + - 🚀🚀 Added new forecasting model `NeuralForecastModel` to convert any of the **30+ NeuralForecast base models** into a Darts `TorchForecastingModel`. This includes models such as NBEATSx, PatchTST, TimeXer, KAN, and many more. Like all Darts torch models, it supports univariate, multivariate, probabilistic forecasting, optimized backtesting and more. Depending on the base model, it also supports past, future, and static covariates. [#3002](https://github.com/unit8co/darts/pull/3002) by [Zhihao Dai](https://github.com/daidahao) - Check out our new [NeuralForecastModel Notebook](https://unit8co.github.io/darts/examples/26-NeuralForecast-examples.html) for detailed examples. [#3026](https://github.com/unit8co/darts/pull/3026) by [Dennis Bader](https://github.com/dennisbader). - 🚀🚀 Added **fine-tuning** support to all `TorchForecastingModel` and `FoundationModel` (such as `Chronos2Model` and `TimesFM2p5Model`) via the new `enable_finetuning` parameter. Supports full training, and partial fine-tuning by selectively freezing or unfreezing layers by name pattern. [#2964](https://github.com/unit8co/darts/issues/2964) by [Alain Gysi](https://github.com/Kurokabe). @@ -155,6 +168,8 @@ but cannot always guarantee backwards compatibility. Changes that may **break co ### For users of the library: +**Improved** + **Migration from PyPI "u8darts" package to "darts" package** - The `darts` PyPI package now replaces `u8darts` with the same installation options (e.g. `darts`, `darts[torch]`, `darts[notorch]`, `darts[all]`). No code changes are required. See the [migration guide](https://github.com/unit8co/darts/blob/master/INSTALL.md#important-darts-pypi-package-changes-as-of-version-0410) for details. @@ -205,6 +220,8 @@ but cannot always guarantee backwards compatibility. Changes that may **break co **Improved** +**Improved** + - Improvements to `SKLearnModel`: - 🚀🚀 Optimized auto-regressive historical forecasts (when `forecast_horizon > output_chunk_length`), increasing throughput by multiple orders of magnitude! Now all historical forecasting scenarios are optimized. [#2921](https://github.com/unit8co/darts/pull/2921) by [Alain Gysi](https://github.com/Kurokabe) - `predict()` now raises a more informative exception when the input target series are too short. [#2921](https://github.com/unit8co/darts/pull/2921) by [Dennis Bader](https://github.com/dennisbader). @@ -243,6 +260,8 @@ but cannot always guarantee backwards compatibility. Changes that may **break co **Improved** +**Improved** + - Improvements to forecasting models: - 🚀🚀 Added new forecasting model `Chronos2Model`: Amazon's pre-trained 120M-parameter foundational model for zero-shot forecasting. `Chronos2Model` can be used for univariate, multivariate, and multiple time series forecasting without any training. It supports past and future covariates and can output deterministic or probabilistic forecasts. Check out the new [Chronos-2 Foundation Model Notebook](https://unit8co.github.io/darts/examples/25-FoundationModel-examples.html) for examples and usage of Chronos-2. [#2944](https://github.com/unit8co/darts/pull/2944) by [Zhihao Dai](https://github.com/daidahao). @@ -277,6 +296,8 @@ but cannot always guarantee backwards compatibility. Changes that may **break co **Improved** +**Improved** + - Improvements to `historical_forecasts()`, `backtest()` and `residuals()` : Added support for **global simulation mode**, **moving validation sets** for early stopping, and improved verbosity control for better model training workflows. - 🚀🚀 Added support for global simulation mode with parameter `apply_globally: bool`. If `True`, computes the output only on the time intersection of all series. With `retrain=True`, activates global model- and data transformer fitting (e.g. fits global models on all series jointly). If `False` (default), computes the output on the entire extent of each individual series and performs local fitting. [#2916](https://github.com/unit8co/darts/pull/2916) by [Dennis Bader](https://github.com/dennisbader). - 🚀 Added parameter `val_length` which will extract a validation set of length `val_length` after the end of each training set when `retrain=True`. The validation set is passed to the fit method of the underlying forecasting model if it supports it. This is especially useful for early stopping mechanisms to reduce overfitting and / or training times. Also check out the new `load_best` fit parameter for our torch models (described further below) to automatically load the best model on the validation set. [#2894](https://github.com/unit8co/darts/pull/2894) by [Dennis Bader](https://github.com/dennisbader). @@ -323,6 +344,8 @@ but cannot always guarantee backwards compatibility. Changes that may **break co **Improved** +**Improved** + - Improved the documentation regarding the new classification models from version 0.37.0. [#2875](https://github.com/unit8co/darts/pull/2875) by [Dennis Bader](https://github.com/dennisbader). **Fixed** @@ -335,6 +358,8 @@ but cannot always guarantee backwards compatibility. Changes that may **break co **Improved** +**Improved** + - 🚀🚀 Added the first **classification forecasting models** for time series labeling or forecasting future class labels. Check out [this new notebook](https://unit8co.github.io/darts/examples/24-SKLearnClassifierModel-examples.html) that demonstrates all added capabilities on the example of time series labeling. [#2765](https://github.com/unit8co/darts/pull/2765) by [Jonas Blanc](https://github.com/jonasblanc) and [Dennis Bader](https://github.com/dennisbader). - All models come with the following support: - Uni- and multivariate classification. @@ -381,6 +406,8 @@ but cannot always guarantee backwards compatibility. Changes that may **break co **Improved** +**Improved** + - Improvements to `TimeSeries` : - 🚀🚀 Migrated the `TimeSeries` backend from `xarray` to `numpy`, resulting in **drastic performance improvements** throughout Darts without any changes to the user experience! We're talking about free speed boosts for creating and manipulating series of **up to multiple orders of magnitude**, which will positively affect any downstream task. You can find some [benchmarks in the images here](https://github.com/unit8co/darts/pull/2807). [#2807](https://github.com/unit8co/darts/pull/2807) by [Dennis Bader](https://github.com/dennisbader). - Added parameter `copy: bool = True` to all TimeSeries constructor and factory methods (`TimeSeries.from_*`). This allows to create your time series without copying the data. Defaults to `True` to maintain the existing behavior. @@ -440,6 +467,8 @@ but cannot always guarantee backwards compatibility. Changes that may **break co **Improved** +**Improved** + - 🚀🚀 Added **new forecasting models**: - `AutoMFLES` : a simple time series method based on gradient boosting time series decomposition as proposed by Tyler Blume in [this repository](https://github.com/tblume1992/MFLES). Our implementation is based on [AutoMFLES](https://nixtlaverse.nixtla.io/statsforecast/docs/models/mfles.html) from Nixtla's `statsforecast` library. [#2747](https://github.com/unit8co/darts/pull/2747) by [Che Hang Ng](https://github.com/CheHangNg). - `StatsForecastModel` : allows to use **any forecasting model from StatsForecast** (see the [models here](https://nixtlaverse.nixtla.io/statsforecast/index.html#models)) in Darts with support for future covariates, probabilistic forecasting (sampled, direct quantile, and / or conformal predictions), and transferable series forecasting. [#2770](https://github.com/unit8co/darts/pull/2770) by [Dennis Bader](https://github.com/dennisbader). @@ -484,6 +513,8 @@ but cannot always guarantee backwards compatibility. Changes that may **break co **Improved** +**Improved** + - Improvements to `TimeSeries`: - `from_dataframe()` and `from_series()` now support creating `TimeSeries` from **additional DataFrame backends** (Polars, PyArrow, ...). We leverage `narwhals` as the compatibility layer between DataFrame libraries. See the [narwhals documentation](https://narwhals-dev.github.io/narwhals/) for all supported backends. [#2661](https://github.com/unit8co/darts/pull/2661) by [Jules Authier](https://github.com/authierj) - Added **new export methods**: `to_dataframe()` and `to_series()`. These methods support exporting `TimeSeries` to DataFrames and Series for all backends supported by `narwhals`. See the [narwhals documentation](https://narwhals-dev.github.io/narwhals/) for all supported backends. [#2701](https://github.com/unit8co/darts/pull/2701) by [Jules Authier](https://github.com/authierj) @@ -526,6 +557,8 @@ but cannot always guarantee backwards compatibility. Changes that may **break co **Improved** +**Improved** + - Improvements to `TimeSeries`: - Added more resampling methods to `TimeSeries.resample()`. This allows to aggregate values when down-sampling and to fill or keep the holes when up-sampling. [#2654](https://github.com/unit8co/darts/pull/2654) by [Jonas Blanc](https://github.com/jonasblanc) - Added the `title` attribute to `TimeSeries.plot()`. This allows to set a title for the plot. [#2639](https://github.com/unit8co/darts/pull/2639) by [Jonathan Koch](https://github.com/jonathankoch99). @@ -563,6 +596,8 @@ but cannot always guarantee backwards compatibility. Changes that may **break co **Improved** +**Improved** + - 🚀🚀 Introducing Conformal Prediction: You can now add calibrated prediction intervals to any pre-trained global forecasting model with our first two conformal prediction models : [#2552](https://github.com/unit8co/darts/pull/2552) by [Dennis Bader](https://github.com/dennisbader). - `ConformalNaiveModel`: Uses past point forecast errors to produce calibrated forecast intervals with a specified coverage probability. - `ConformalQRModel`: Combines quantile regression (or any probabilistic model) with conformal prediction techniques. It adjusts the quantile estimates to generate calibrated prediction intervals with a specified coverage probability. @@ -619,6 +654,8 @@ but cannot always guarantee backwards compatibility. Changes that may **break co **Improved** +**Improved** + - Improvements to `metrics`: - Added support for computing metrics on one or multiple quantiles `q`, either from probabilistic or quantile forecasts. [#2530](https://github.com/unit8co/darts/pull/2530) by [Dennis Bader](https://github.com/dennisbader). - Added quantile interval metrics `miw` (Mean Interval Width, time aggregated) and `iw` (Interval Width, per time step / non-aggregated) which compute the width of quantile intervals `q_intervals` (expected to be a tuple or sequence of tuples with (lower quantile, upper quantile)). [#2530](https://github.com/unit8co/darts/pull/2530) by [Dennis Bader](https://github.com/dennisbader). @@ -674,6 +711,8 @@ but cannot always guarantee backwards compatibility. Changes that may **break co **Improved** +**Improved** + - 🚀🚀 All `GlobalForecastingModel` now support being trained with sample weights (regression-, ensemble-, and neural network models) : [#2404](https://github.com/unit8co/darts/pull/2404), [#2410](https://github.com/unit8co/darts/pull/2410), [#2417](https://github.com/unit8co/darts/pull/2417) and [#2418](https://github.com/unit8co/darts/pull/2418) by [Anton Ragot](https://github.com/AntonRagot) and [Dennis Bader](https://github.com/dennisbader). - Added parameters `sample_weight` and `val_sample_weight` to `fit()`, `historical_forecasts()`, `backtest()`, `residuals`, and `gridsearch()` to apply weights to each observation, label (each step in the output chunk), and target component in the training and evaluation set. Supported by both deterministic and probabilistic models. The sample weight can either be `TimeSeries` themselves or built-in weight generators "linear" and "exponential" decay. In case of a `TimeSeries` it is handled identically as the covariates (e.g. pass multiple weight series with multiple target series, relevant time frame extraction is handled automatically for you, ...). You can find a [sample weights example here](https://unit8co.github.io/darts/quickstart/00-quickstart.html#Sample-Weights). - 🚀🚀 Improvements to the Anomaly Detection Module through major refactor. The refactor includes major performance optimization for the majority of processes and improvements to the API, consistency, reliability, and the documentation. Some of these necessary changes come at the cost of breaking changes : [#1477](https://github.com/unit8co/darts/pull/1477) by [Dennis Bader](https://github.com/dennisbader), [Samuele Giuliano Piazzetta](https://github.com/piaz97), [Antoine Madrona](https://github.com/madtoinou), [Julien Herzen](https://github.com/hrzn), [Julien Adda](https://github.com/julien12234). @@ -741,6 +780,8 @@ but cannot always guarantee backwards compatibility. Changes that may **break co **Improved** +**Improved** + - 🚀🚀 New forecasting model: `TSMixerModel` as proposed in the [TSMixer paper](https://arxiv.org/abs/2303.06053). An MLP based model that combines temporal, static and cross-sectional feature information using stacked mixing layers. [#2293](https://github.com/unit8co/darts/pull/2293), by [Dennis Bader](https://github.com/dennisbader) and [Cristof Rojas](https://github.com/cristof-r). - 🚀🚀 Improvements to metrics, historical forecasts, backtest, and residuals through major refactor. The refactor includes optimization of multiple process and improvements to consistency, reliability, and the documentation. Some of these necessary changes come at the cost of breaking changes. [#2284](https://github.com/unit8co/darts/pull/2284) by [Dennis Bader](https://github.com/dennisbader). - **Metrics**: @@ -822,6 +863,8 @@ but cannot always guarantee backwards compatibility. Changes that may **break co **Improved** +**Improved** + - Improvements to `GlobalForecastingModel` : - 🚀🚀🚀 All global models (regression and torch models) now support shifted predictions with model creation parameter `output_chunk_shift`. This will shift the output chunk for training and prediction by `output_chunk_shift` steps into the future. [#2176](https://github.com/unit8co/darts/pull/2176) by [Dennis Bader](https://github.com/dennisbader). - Improvements to `TimeSeries`, [#2196](https://github.com/unit8co/darts/pull/2196) by [Dennis Bader](https://github.com/dennisbader): @@ -877,6 +920,8 @@ but cannot always guarantee backwards compatibility. Changes that may **break co **Improved** +**Improved** + - Added `darts.utils.statistics.plot_ccf` that can be used to plot the cross correlation between a time series (e.g. target series) and the lagged values of another time series (e.g. covariates series). [#2122](https://github.com/unit8co/darts/pull/2122) by [Dennis Bader](https://github.com/dennisbader). - Improvements to `TimeSeries` : Improved the time series frequency inference when using slices or pandas DatetimeIndex as keys for `__getitem__`. [#2152](https://github.com/unit8co/darts/pull/2152) by [DavidKleindienst](https://github.com/DavidKleindienst). @@ -892,6 +937,8 @@ but cannot always guarantee backwards compatibility. Changes that may **break co **Improved** +**Improved** + - 🔴 Added `CustomRNNModule` and `CustomBlockRNNModule` for defining custom RNN modules that can be used with `RNNModel` and `BlockRNNModel`. The custom `model` must now be a subclass of the custom modules. [#2088](https://github.com/unit8co/darts/pull/2088) by [Dennis Bader](https://github.com/dennisbader). **Fixed** @@ -910,6 +957,8 @@ but cannot always guarantee backwards compatibility. Changes that may **break co **Improved** +**Improved** + - Improvements to `TorchForecastingModel` : - 🚀🚀 We optimized `historical_forecasts()` for pre-trained `TorchForecastingModel` running up to 20 times faster than before (and even more when tuning the batch size)!. [#2013](https://github.com/unit8co/darts/pull/2013) by [Dennis Bader](https://github.com/dennisbader). - Added callback `darts.utils.callbacks.TFMProgressBar` to customize at which model stages to display the progress bar. [#2020](https://github.com/unit8co/darts/pull/2020) by [Dennis Bader](https://github.com/dennisbader). @@ -948,6 +997,8 @@ No changes. **Improved** +**Improved** + - Improvements to `RegressionModel`, [#1962](https://github.com/unit8co/darts/pull/1962) by [Antoine Madrona](https://github.com/madtoinou): - 🚀🚀 All models now support component/column-specific lags for target, past, and future covariates series. - Improvements to `TorchForecastingModel` : @@ -987,6 +1038,8 @@ No changes. ### For users of the library: +**Improved** + **Installation** - 🔴 Removed Prophet, LightGBM, and CatBoost dependencies from PyPI packages (`darts`, `u8darts`, `u8darts[torch]`), and conda-forge packages (`u8darts`, `u8darts-torch`) to avoid installation issues that some users were facing (installation on Apple M1/M2 devices, ...). [#1589](https://github.com/unit8co/darts/pull/1589) by [Julien Herzen](https://github.com/hrzn) and [Dennis Bader](https://github.com/dennisbader). @@ -1047,6 +1100,8 @@ No changes. **Improved** +**Improved** + - General model improvements: - New baseline forecasting model `NaiveMovingAverage`. [#1557](https://github.com/unit8co/darts/pull/1557) by [Janek Fidor](https://github.com/JanFidor). - New models `StatsForecastAutoCES`, and `StatsForecastAutoTheta` from Nixtla's statsforecasts library as local forecasting models without covariates support. AutoTheta supports probabilistic forecasts. [#1476](https://github.com/unit8co/darts/pull/1476) by [Boyd Biersteker](https://github.com/Beerstabr). @@ -1125,6 +1180,8 @@ Patch release **Improved** +**Improved** + - 🚀🚀🚀 Brand new Darts module dedicated to anomaly detection on time series: `darts.ad`. More info on the API doc page: https://unit8co.github.io/darts/generated_api/darts.ad.html. [#1256](https://github.com/unit8co/darts/pull/1256) by [Julien Adda](https://github.com/julien12234) @@ -1196,6 +1253,8 @@ Patch release **Improved** +**Improved** + - New explainability feature. The class `ShapExplainer` in `darts.explainability` can provide Shap-values explanations of the importance of each lag and each dimension in producing each forecasting lag for `RegressionModel`s. [#909](https://github.com/unit8co/darts/pull/909) by [Maxime Dumonal](https://github.com/dumjax). - New model: `StatsForecastsETS`. Similarly to `StatsForecastsAutoARIMA`, this model offers the ETS model from Nixtla's `statsforecasts` library as a local forecasting model supporting future covariates. [#1171](https://github.com/unit8co/darts/pull/1171) by [Julien Herzen](https://github.com/hrzn). - Added support for past and future covariates to `residuals()` function. [#1223](https://github.com/unit8co/darts/pull/1223) by [Eliane Maalouf](https://github.com/eliane-maalouf). @@ -1225,6 +1284,8 @@ Patch release **Improved** +**Improved** + - New model: Catboost, incl `quantile`, `poisson` and `gaussian` likelihoods support. [#1007](https://github.com/unit8co/darts/pull/1007), [#1044](https://github.com/unit8co/darts/pull/1044) by [Jonas Racine](https://github.com/jonasracine). - Extension of the `add_encoders` option to `RegressionModel`s. It is now straightforward to add calendar based or custom past or future covariates to these models, similar to torch models. [#1093](https://github.com/unit8co/darts/pull/1093) by [Dennis Bader](https://github.com/dennisbader). - Introduction of `StaticCovariatesTransformer`, categorical static covariate support for `TFTModel`, example and user-guide updates on static covariates. [#1081](https://github.com/unit8co/darts/pull/1081) by [Dennis Bader](https://github.com/dennisbader). @@ -1258,6 +1319,8 @@ Patch release **Improved** +**Improved** + - Added support for static covariates in `TimeSeries` class. [#966](https://github.com/unit8co/darts/pull/966) by [Dennis Bader](https://github.com/dennisbader). - Added support for static covariates in TFT model. [#966](https://github.com/unit8co/darts/pull/966) by [Dennis Bader](https://github.com/dennisbader). - Support for storing hierarchy of components in `TimeSeries` (in view of hierarchical reconciliation) [#1012](https://github.com/unit8co/darts/pull/1012) by [Julien Herzen](https://github.com/hrzn). @@ -1294,6 +1357,8 @@ Patch release **Improved** +**Improved** + - New model: `NHiTS` implementing the N-HiTS model. [#898](https://github.com/unit8co/darts/pull/898) by [Julien Herzen](https://github.com/hrzn). - New model: `StatsForecastAutoARIMA` implementing the (faster) AutoARIMA version of @@ -1322,6 +1387,8 @@ Patch release **Improved** +**Improved** + - `LinearRegressionModel` and `LightGBMModel` can now be probabilistic, supporting quantile and poisson regression. [#831](https://github.com/unit8co/darts/pull/831), [#853](https://github.com/unit8co/darts/pull/853) by [Gian Wiher](https://github.com/gnwhr). @@ -1358,6 +1425,8 @@ Patch release ### For users of the library: +**Improved** + **Fixed** - Fixed issues with (now deprecated) `torch_device_str` parameter, and improved documentation @@ -1376,6 +1445,8 @@ Patch release **Improved** +**Improved** + - 🚀 Support for [PyTorch Lightning](https://github.com/PyTorchLightning/pytorch-lightning): All deep learning models are now implemented using PyTorch Lightning. This means that many more features are now available via PyTorch Lightning trainers functionalities; such as tailored callbacks, or multi-GPU training. @@ -1418,6 +1489,8 @@ Patch release ### For users of the library: +**Improved** + - Fixed an incompatibility with latest version of Pandas ([#752](https://github.com/unit8co/darts/pull/752)) by [Julien Herzen](https://github.com/hrzn). - Fixed non contiguous error when using lstm_layers > 1 on GPU. ([#740](https://github.com/unit8co/darts/pull/740)) @@ -1438,6 +1511,8 @@ Patch release **Improved** +**Improved** + - The [documentation page](https://unit8co.github.io/darts/index.html) has been revamped and now contains a brand new Quickstart guide, as well as a User Guide section, which will be populated over time. - The [API documentation](https://unit8co.github.io/darts/generated_api/darts.html) has been revamped and improved, @@ -1462,6 +1537,8 @@ Patch release ### For users of the library: +**Improved** + **Added**: - On-the-fly encoding of position and calendar information in Torch-based models. @@ -1523,6 +1600,8 @@ Patch release ### For users of the library: +**Improved** + **Added**: - Probabilistic N-BEATS: The `NBEATSModel` can now produce probabilistic forecasts, @@ -1551,6 +1630,8 @@ Patch release ### For users of the library: +**Improved** + **Added**: - Factory methods in `TimeSeries` are now `classmethods`, which makes inheritance of @@ -1564,6 +1645,8 @@ Patch release ### For users of the library: +**Improved** + **Added**: - New forecasting model, [Temporal Fusion Transformer](https://arxiv.org/abs/1912.09363) (`TFTModel`). @@ -1607,6 +1690,8 @@ Patch release ### For users of the library: +**Improved** + **Added**: - Improved probabilistic forecasting with neural networks @@ -1644,6 +1729,8 @@ Patch release ### For users of the library: +**Improved** + **Added:** - New model: `LightGBMModel` is a new regression model. Regression models allow to predict future values @@ -1665,6 +1752,8 @@ Patch release ### For users of the library: +**Improved** + **Fixed:** - A bug with memory pinning that was causing issues with training models on GPUs. @@ -1677,6 +1766,8 @@ Patch release ### For users of the library: +**Improved** + **Added:** - 🔴 Improvement of the covariates support. Before, some models were accepting a `covariates` (or `exog`) @@ -1722,6 +1813,8 @@ Patch release ### For users of the library: +**Improved** + **Added:** - Improved `GaussianProcessFilter`, now handling missing values, and better handling @@ -1753,6 +1846,8 @@ Patch release ### For users of the library: +**Improved** + **Added:** - Multiple forecasting models can now produce probabilistic forecasts by specifying a `num_samples` parameter when calling `predict()`. Stochastic forecasts are stored by utilizing the new `samples` dimension in the refactored `TimeSeries` class (see 'Changed' section). Models supporting probabilistic predictions so far are `ARIMA`, `ExponentialSmoothing`, `RNNModel` and `TCNModel`. @@ -1800,6 +1895,8 @@ ts: TimeSeries = AirPassengers().load() ### For users of the library: +**Improved** + **Added:** - `RandomForest` algorithm implemented. Uses the scikit-learn `RandomForestRegressor` to predict future values from (lagged) exogenous @@ -1842,6 +1939,8 @@ ts: TimeSeries = AirPassengers().load() ### For users of the library: +**Improved** + **Added:** - `darts` Pypi package. It is now possible to `pip install darts`. The older name `u8darts` is still maintained @@ -1876,6 +1975,8 @@ ts: TimeSeries = AirPassengers().load() ### For users of the library: +**Improved** + **Added:** - `Pipeline.invertible()` a getter which returns whether the pipeline is invertible or not. @@ -1914,6 +2015,8 @@ ts: TimeSeries = AirPassengers().load() ### For users of the library: +**Improved** + **Added:** - Ensemble models, a new kind of `ForecastingModel` which allows to ensemble multiple models to make predictions: @@ -1945,6 +2048,8 @@ ts: TimeSeries = AirPassengers().load() ### For users of the library: +**Improved** + **Added:** - Data (pre) processing abilities using `DataTransformer`, `Pipeline` : @@ -2010,6 +2115,8 @@ ts: TimeSeries = AirPassengers().load() ### For users of the library: +**Improved** + **Added:** - Better indexing on TimeSeries (support for column/component indexing) [#150](https://github.com/unit8co/darts/pull/150) diff --git a/darts/ad/_save_load.py b/darts/ad/_save_load.py new file mode 100644 index 0000000000..685010cefa --- /dev/null +++ b/darts/ad/_save_load.py @@ -0,0 +1,92 @@ +""" +Save/Load Mixin for Anomaly Detection +-------------------------------------- + +Provides shared save() and load() methods for AD base classes to +avoid code duplication across AnomalyScorer, Detector, Aggregator, +and AnomalyModel. +""" + +import datetime +import os +import pickle + +from darts.logging import get_logger, raise_log + +logger = get_logger(__name__) + + +class SaveableMixin: + """Mixin that adds pickle-based save() and load() to anomaly detection classes. + + Subclasses inherit these methods and can override the default path pattern + ``{ClassName}_{YYYY-mm-dd_HH_MM_SS}.pkl``. + + Example + ------- + >>> from darts.ad.scorers import KMeansScorer + >>> scorer = KMeansScorer(window=10, k=8) + >>> scorer.fit(series) + >>> scorer.save("my_scorer.pkl") + >>> loaded = KMeansScorer.load("my_scorer.pkl") + """ + + def save( + self, + path: str | os.PathLike | None = None, + **pkl_kwargs, + ) -> None: + """Saves the object under a given path or generates a default path. + + Parameters + ---------- + path + Path under which to save the object at its current state. If no path + is specified, a default path ``"{ClassName}_{YYYY-mm-dd_HH_MM_SS}.pkl"`` + is generated automatically. + pkl_kwargs + Keyword arguments passed to ``pickle.dump()``. + """ + if path is None: + path = ( + f"{type(self).__name__}" + f"_{datetime.datetime.now().strftime('%Y-%m-%d_%H_%M_%S')}.pkl" + ) + if isinstance(path, str | os.PathLike): + with open(path, "wb") as handle: + pickle.dump(obj=self, file=handle, **pkl_kwargs) + else: + raise_log( + ValueError( + "Argument 'path' has to be a filepath (str or PathLike), " + f"but was '{path.__class__}'." + ), + logger=logger, + ) + + @staticmethod + def load(path: str | os.PathLike) -> "SaveableMixin": + """Loads an object from a given path. + + Parameters + ---------- + path + Path from which to load the object. + """ + if isinstance(path, str | os.PathLike): + if not os.path.exists(path): + raise_log( + FileNotFoundError(f"The file {path} doesn't exist"), + logger=logger, + ) + with open(path, "rb") as handle: + obj = pickle.load(file=handle) + else: + raise_log( + ValueError( + "Argument 'path' has to be a filepath (str or PathLike), " + f"but was '{path.__class__}'." + ), + logger=logger, + ) + return obj diff --git a/darts/ad/aggregators/aggregators.py b/darts/ad/aggregators/aggregators.py index cfd52b9fe3..d1b7eb462c 100644 --- a/darts/ad/aggregators/aggregators.py +++ b/darts/ad/aggregators/aggregators.py @@ -25,6 +25,7 @@ import numpy as np from darts import TimeSeries +from darts.ad._save_load import SaveableMixin from darts.ad.utils import ( _assert_fit_called, _check_input, @@ -37,12 +38,13 @@ logger = get_logger(__name__) -class Aggregator(ABC): +class Aggregator(SaveableMixin, ABC): """Base class for Aggregators.""" def __init__(self): self.width_trained_on: int | None = None + @abstractmethod def __str__(self): """returns the name of the aggregator""" diff --git a/darts/ad/anomaly_model/anomaly_model.py b/darts/ad/anomaly_model/anomaly_model.py index 1b4fc9f889..17bdbe5f9d 100644 --- a/darts/ad/anomaly_model/anomaly_model.py +++ b/darts/ad/anomaly_model/anomaly_model.py @@ -14,6 +14,7 @@ from typing_extensions import Self from darts import TimeSeries +from darts.ad._save_load import SaveableMixin from darts.ad.scorers.scorers import AnomalyScorer from darts.ad.utils import ( _assert_same_length, @@ -27,7 +28,7 @@ logger = get_logger(__name__) -class AnomalyModel(ABC): +class AnomalyModel(SaveableMixin, ABC): """Base class for all anomaly models.""" def __init__(self, model, scorer): @@ -41,6 +42,7 @@ def __init__(self, model, scorer): ) self.model = model + def fit( self, series: TimeSeriesLike, diff --git a/darts/ad/detectors/detectors.py b/darts/ad/detectors/detectors.py index f07d32d6d1..4caa0ec885 100644 --- a/darts/ad/detectors/detectors.py +++ b/darts/ad/detectors/detectors.py @@ -22,6 +22,7 @@ import numpy as np from darts import TimeSeries +from darts.ad._save_load import SaveableMixin from darts.ad.utils import ( _assert_fit_called, _check_input, @@ -34,12 +35,13 @@ logger = get_logger(__name__) -class Detector(ABC): +class Detector(SaveableMixin, ABC): """Base class for all detectors""" def __init__(self, *args: Any, **kwargs: Any) -> None: self.width_trained_on: int | None = None + def detect( self, series: TimeSeriesLike, diff --git a/darts/ad/scorers/scorers.py b/darts/ad/scorers/scorers.py index c012ff1daf..a15b4c1db6 100644 --- a/darts/ad/scorers/scorers.py +++ b/darts/ad/scorers/scorers.py @@ -21,6 +21,7 @@ import numpy as np from darts import TimeSeries, metrics +from darts.ad._save_load import SaveableMixin from darts.ad.utils import ( _assert_same_length, _check_input, @@ -38,7 +39,7 @@ logger = get_logger(__name__) -class AnomalyScorer(ABC): +class AnomalyScorer(SaveableMixin, ABC): """Base class for all anomaly scorers""" def __init__(self, is_univariate: bool, window: int) -> None: @@ -64,6 +65,7 @@ def __init__(self, is_univariate: bool, window: int) -> None: self.window = window self._is_univariate = is_univariate + def score_from_prediction( self, series: TimeSeriesLike, diff --git a/darts/tests/ad/test_aggregators.py b/darts/tests/ad/test_aggregators.py index ddbfb4dc44..8a86c4d536 100644 --- a/darts/tests/ad/test_aggregators.py +++ b/darts/tests/ad/test_aggregators.py @@ -11,6 +11,7 @@ FittableAggregator, OrAggregator, ) +from darts.ad.scorers import KMeansScorer from darts.models import MovingAverageFilter # element shape : (model_cls, model_kwargs, expected metrics) @@ -631,3 +632,24 @@ def test_ensemble_aggregator_multiple_series(self): self.mts_anomalies1, self.mts_anomalies2, ] == input_series_copy + + def test_save_load_aggregator(self, tmp_path): + """Test save/load for aggregators.""" + from darts.ad.aggregators import AndAggregator, OrAggregator + for agg in [AndAggregator(), OrAggregator()]: + path = tmp_path / f"{type(agg).__name__}.pkl" + agg.save(str(path)) + loaded = type(agg).load(str(path)) + assert type(loaded) == type(agg) + + def test_save_load_fittable_aggregator(self, tmp_path): + """Test save/load preserves fitted FittableAggregator.""" + from darts.ad.aggregators import EnsembleSklearnAggregator, FittableAggregator + from sklearn.ensemble import GradientBoostingClassifier + agg = EnsembleSklearnAggregator( + model=GradientBoostingClassifier(), + ) + path = tmp_path / "ensemble.pkl" + agg.save(str(path)) + loaded = EnsembleSklearnAggregator.load(str(path)) + assert type(loaded) == type(agg) diff --git a/darts/tests/ad/test_anomaly_model.py b/darts/tests/ad/test_anomaly_model.py index 0e759d9c34..609cacde83 100644 --- a/darts/tests/ad/test_anomaly_model.py +++ b/darts/tests/ad/test_anomaly_model.py @@ -1491,3 +1491,33 @@ def test_immutabilty(self): # Check that the original series is not modified assert series == input_series_copy + + def test_save_load_filtering_anomaly_model(self, tmp_path): + """Test save/load for FilteringAnomalyModel.""" + model = FilteringAnomalyModel( + model=MovingAverageFilter(window=10), + scorer=Norm(), + ) + model.fit(self.train, allow_model_training=True) + path = tmp_path / "filtering_am.pkl" + model.save(str(path)) + loaded = FilteringAnomalyModel.load(str(path)) + scores_orig = model.score(self.test) + scores_loaded = loaded.score(self.test) + for s_orig, s_loaded in zip(scores_orig, scores_loaded): + assert s_orig == s_loaded + + def test_save_load_forecasting_anomaly_model(self, tmp_path): + """Test save/load for ForecastingAnomalyModel.""" + model = ForecastingAnomalyModel( + model=SKLearnModel(lags=5), + scorer=Norm(), + ) + model.fit(self.train, allow_model_training=True) + path = tmp_path / "forecasting_am.pkl" + model.save(str(path)) + loaded = ForecastingAnomalyModel.load(str(path)) + scores_orig = model.score(self.test) + scores_loaded = loaded.score(self.test) + for s_orig, s_loaded in zip(scores_orig, scores_loaded): + assert s_orig == s_loaded diff --git a/darts/tests/ad/test_detectors.py b/darts/tests/ad/test_detectors.py index 846d978c59..8781065784 100644 --- a/darts/tests/ad/test_detectors.py +++ b/darts/tests/ad/test_detectors.py @@ -703,3 +703,25 @@ def test_iqr_detector_detect_logic(self): detection = detector.detect(anomalous_ts) assert detection.sum(axis=0).all_values().flatten()[0] == expected_anomalies + + def test_save_load_detector(self, tmp_path): + """Test save/load for detectors.""" + for config in list_detectors: + detector_cls, kwargs = config + detector = detector_cls(**kwargs) + path = tmp_path / f"{detector_cls.__name__}.pkl" + detector.save(str(path)) + loaded = detector_cls.load(str(path)) + assert type(loaded) == type(detector) + + def test_save_load_fitted_detector(self, tmp_path): + """Test save/load preserves fitted QuantileDetector.""" + from darts.ad.detectors.quantile_detector import QuantileDetector + detector = QuantileDetector(low_quantile=0.1, high_quantile=0.9) + detector.fit(self.train) + path = tmp_path / "quantile.pkl" + detector.save(str(path)) + loaded = QuantileDetector.load(str(path)) + detection_orig = detector.detect(self.test) + detection_loaded = loaded.detect(self.test) + assert detection_orig == detection_loaded diff --git a/darts/tests/ad/test_scorers.py b/darts/tests/ad/test_scorers.py index 5d8418ec2b..1b148d1975 100644 --- a/darts/tests/ad/test_scorers.py +++ b/darts/tests/ad/test_scorers.py @@ -1741,3 +1741,48 @@ def test_immutability(self): # Check that the original series is not modified assert series == input_series_copy + + def test_save_load_non_fittable(self, tmp_path): + """Test save/load for non-fittable scorers.""" + import tempfile + for scorer in list_NonFittableAnomalyScorer: + path = tmp_path / f"{type(scorer).__name__}.pkl" + scorer.save(str(path)) + loaded = type(scorer).load(str(path)) + assert type(loaded) == type(scorer) + assert loaded.window == scorer.window + + def test_save_load_kmeans(self, tmp_path): + """Test save/load preserves fitted KMeansScorer.""" + scorer = KMeansScorer(window=5, k=4) + scorer.fit(self.train) + path = tmp_path / "kmeans.pkl" + scorer.save(str(path)) + loaded = KMeansScorer.load(str(path)) + assert loaded.window == scorer.window + scores_orig = scorer.score(self.test) + scores_loaded = loaded.score(self.test) + assert scores_orig == scores_loaded + + def test_save_load_wasserstein(self): + """Test save/load with default path.""" + scorer = KMeansScorer(window=3, k=3) + scorer.fit(self.train) + scorer.save() + import glob, os + files = glob.glob("KMeansScorer_*.pkl") + assert len(files) == 1 + loaded = KMeansScorer.load(files[0]) + assert loaded.window == scorer.window + os.remove(files[0]) + + def test_save_load_pyod(self, tmp_path): + """Test save/load for PyODScorer.""" + from pyod.models.knn import KNN + from darts.ad.scorers import PyODScorer + scorer = PyODScorer(window=5, model=KNN()) + scorer.fit(self.train) + path = tmp_path / "pyod.pkl" + scorer.save(str(path)) + loaded = PyODScorer.load(str(path)) + assert loaded.window == scorer.window