Skip to content

Commit

Permalink
Merge pull request #237 from unit8co/develop
Browse files Browse the repository at this point in the history
Release
  • Loading branch information
LeoTafti authored Nov 9, 2020
2 parents 928357e + 66368c5 commit da56272
Show file tree
Hide file tree
Showing 28 changed files with 1,843 additions and 1,059 deletions.
30 changes: 28 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,33 @@ Darts is still in an early development phase and we cannot always guarantee back

## [Unreleased](https://github.com/unit8co/darts/tree/develop)

[Full Changelog](https://github.com/unit8co/darts/compare/0.4.0...develop)
[Full Changelog](https://github.com/unit8co/darts/compare/0.5.0...develop)

## [0.5.0](https://github.com/unit8co/darts/tree/0.5.0) (2020-11-09)

[Full Changelog](https://github.com/unit8co/darts/compare/0.4.0...0.5.0)
### For users of the library:
**Added:**
- Ensemble models, a new kind of `ForecastingModel` which allows to ensemble multiple models to make predictions:
- `EnsembleModel` is the abstract base class for ensemble models. Classes deriving from `EnsembleModel` must implement the `ensemble()` method, which takes in a `List[TimeSeries]` of predictions from the constituent models, and returns the ensembled prediction (a single `TimeSeries` object)
- `RegressionEnsembleModel`, a concrete implementation of `EnsembleModel `which allows to specify any regression model (providing `fit()` and `predict()` methods) to use to ensemble the constituent models' predictions.
- A new method to `TorchForecastingModel`: `untrained_model()` returns the model as it was initally created, allowing to retrain the exact same model from scratch. Works both when specifying a `random_state` or not.
- New `ForecastingModel.backtest()` and `RegressionModel.backtest()` functions which by default compute a single error score from the historical forecasts the model would have produced.
- A new `reduction` parameter allows to specify whether to compute the mean/median/… of errors or (when `reduction` is set to `None`) to return a list of historical errors.
- The previous `backtest()` functionality still exists but has been renamed `historical_forecasts()`
- Added a new `last_points_only` parameter to `historical_forecasts()`, `backtest()` and `gridsearch()`

**Changed:**
- 🔴 Renamed `backtest()` into `historical_forecasts()`
- `fill_missing_values()` and `MissingValuesFiller` used to remove the variable names when used with `fill='auto'` – not anymore.
- Modified the default plotting style to increase contrast and make plots lighter.

**Fixed:**
- Small mistake in the `NaiveDrift` model implementation which caused the first predicted value to repeat the last training value.

### For developers of the library:
**Changed:**
- `@random_method` decorator now always assigns a `_random_instance` field to decorated methods (seeded with a random seed). This doesn't change the observed behavior, but allows to deterministically "reset" `TorchForecastingModel` by saving `_random_instance` along with the other parameters of the model upon creation.

## [0.4.0](https://github.com/unit8co/darts/tree/0.4.0) (2020-10-28)

Expand Down Expand Up @@ -61,7 +87,7 @@ Darts is still in an early development phase and we cannot always guarantee back
```

### For developers of the library
### Changed
**Changed:**
- GitHub release workflow is now triggered manually from the GitHub "Actions" tab in the repository, providing a `#major`, `#minor`, or `#patch` argument. [\#211](https://github.com/unit8co/darts/pull/211)
- (A limited number of) notebook examples are now run as part of the GitHub develop workflow.

Expand Down
27 changes: 16 additions & 11 deletions darts/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,22 +33,27 @@ class ModelMode(Enum):
colors = cycler(color=['black', '003DFD', 'b512b8', '11a9ba', '0d780f', 'f77f07', 'ba0f0f'])

u8plots_mplstyle = {
'font.family' : 'Arial',
'figure.facecolor' : '#f0f0f0',
'axes.facecolor' : '#f0f0f0',
'font.family' : 'sans serif',
'axes.edgecolor' : 'black',
'axes.grid' : True,
'axes.labelcolor': '#333333',
'axes.labelweight' : 600,
'axes.linewidth' : 1,
'axes.prop_cycle' : colors,
'lines.linewidth' : 1.3,
'axes.spines.top' : False,
'axes.spines.right' : False,
'axes.spines.bottom' : False,
'axes.spines.left' : False,
'axes.linewidth' : 1,
'axes.edgecolor' : 'black',
'grid.color' : '#dedede',
'legend.frameon' : False,
'lines.linewidth' : 1.3,
'xtick.bottom' : False,
'ytick.left' : False,
'axes.grid' : True,
'grid.color' : '#d0d0d0',
'grid.alpha' : 0.5,
'legend.frameon' : False}
'xtick.color': '#333333',
'xtick.labelsize':'small',
'ytick.color': '#333333',
'ytick.labelsize':'small',
'xtick.bottom' : False,
}


mpl.rcParams.update(u8plots_mplstyle)
2 changes: 1 addition & 1 deletion darts/metrics/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@
-------
"""

from .metrics import mae, mse, rmse, rmsle, mape, mase, ope, marre, r2_score, coefficient_of_variation
from .metrics import mae, mse, rmse, rmsle, mape, smape, mase, ope, marre, r2_score, coefficient_of_variation
5 changes: 5 additions & 0 deletions darts/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,8 @@

# Regression
from .standard_regression_model import StandardRegressionModel

# Ensembling
from .ensemble_model import EnsembleModel
from .baselines import NaiveEnsembleModel
from .regression_ensemble_model import RegressionEnsembleModel
29 changes: 27 additions & 2 deletions darts/models/baselines.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@
A collection of simple benchmark models.
"""

from typing import List, Optional
import numpy as np

from .forecasting_model import UnivariateForecastingModel
from .forecasting_model import ForecastingModel, UnivariateForecastingModel
from .ensemble_model import EnsembleModel
from ..timeseries import TimeSeries
from ..logging import raise_if_not, get_logger

Expand Down Expand Up @@ -95,5 +97,28 @@ def predict(self, n: int):
first, last = self.training_series.first_value(), self.training_series.last_value()
slope = (last - first) / (len(self.training_series) - 1)
last_value = last + slope * n
forecast = np.linspace(last, last_value, num=n)
forecast = np.linspace(last, last_value, num=n + 1)[1:]
return self._build_forecast_series(forecast)


class NaiveEnsembleModel(EnsembleModel):

def __init__(self, models: List[ForecastingModel]):
""" Naive combination model
Naive implementation of `EnsembleModel`
Returns the average of all predictions of the constituent models
"""
super().__init__(models)

def fit(self, training_series: TimeSeries, target_series: Optional[TimeSeries] = None) -> None:
super().fit(training_series, target_series)

for model in self.models:
if isinstance(model, UnivariateForecastingModel):
model.fit(self.training_series)
else:
model.fit(self.training_series, self.target_series)

def ensemble(self, predictions: List[TimeSeries]):
return sum(predictions) / len(self.models)
73 changes: 73 additions & 0 deletions darts/models/ensemble_model.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
"""
Ensemble model
--------------
"""

from abc import abstractmethod
from typing import List, Optional

from ..timeseries import TimeSeries
from ..logging import get_logger, raise_if_not
from ..models.forecasting_model import ForecastingModel

logger = get_logger(__name__)


class EnsembleModel(ForecastingModel):
"""
Abstract base class for ensemble models.
Ensemble models take in a list of forecasting models and ensemble their predictions
to make a single one according to the rule defined by their `ensemble()` method.
Parameters
----------
models
List of forecasting models whose predictions to ensemble
"""
def __init__(self, models: List[ForecastingModel]):
raise_if_not(isinstance(models, list) and models,
"Cannot instantiate EnsembleModel with an empty list of models",
logger)
raise_if_not(all(isinstance(model, ForecastingModel) for model in models),
"All models must be instances of darts.models.ForecastingModel",
logger)
super().__init__()
self.models = models

def fit(self, training_series: TimeSeries, target_series: Optional[TimeSeries] = None) -> None:
"""
Fits the model on the provided series.
Note that `EnsembleModel.fit()` does NOT call `fit()` on each of its constituent forecasting models.
It is left to classes inheriting from EnsembleModel to do so appropriately when overriding `fit()`
"""
super().fit(training_series, target_series)

def predict(self, n: int) -> TimeSeries:
super().predict(n)

predictions = []
for model in self.models:
predictions.append(model.predict(n))

return self.ensemble(predictions)

@abstractmethod
def ensemble(self, predictions: List[TimeSeries]) -> TimeSeries:
"""
Defines how to ensemble the individual models' predictions to produce a single prediction.
Parameters
----------
predictions
Individual predictions to ensemble
Returns
-------
TimeSeries
The predicted `TimeSeries` obtained by ensembling the individual predictions
"""
pass

@property
def min_train_series_length(self) -> int:
return max(model.min_train_series_length for model in self.models)
Loading

0 comments on commit da56272

Please sign in to comment.