Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduces DeepVAR model #491

Merged
merged 15 commits into from
Dec 4, 2019
13 changes: 13 additions & 0 deletions src/gluonts/model/deepvar/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,16 @@
# Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License").
# You may not use this file except in compliance with the License.
# A copy of the License is located at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# or in the "license" file accompanying this file. This file is distributed
# on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
# express or implied. See the License for the specific language governing
# permissions and limitations under the License.

# Relative imports
from ._estimator import DeepVAREstimator

Expand Down
152 changes: 116 additions & 36 deletions src/gluonts/model/deepvar/_estimator.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,16 @@
# Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License").
# You may not use this file except in compliance with the License.
# A copy of the License is located at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# or in the "license" file accompanying this file. This file is distributed
# on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
# express or implied. See the License for the specific language governing
# permissions and limitations under the License.

# Standard library imports
from typing import List, Optional, Callable, Tuple

Expand All @@ -6,10 +19,15 @@
import pandas as pd
import numpy as np
from mxnet.gluon import HybridBlock
from pandas.tseries.frequencies import to_offset

# First-party imports
from gluonts.core.component import validated
from gluonts.distribution import DistributionOutput, StudentTOutput
from gluonts.distribution import (
DistributionOutput,
StudentTOutput,
LowrankMultivariateGaussianOutput,
)
from gluonts.model.estimator import GluonEstimator
from gluonts.model.predictor import Predictor, RepresentableBlockPredictor
from gluonts.support.util import copy_parameters
Expand Down Expand Up @@ -66,27 +84,9 @@ def __call__(self, index: pd.DatetimeIndex) -> np.ndarray:
return np.vstack([np.cos(steps), np.sin(steps)])


def get_granularity(freq_str: str) -> Tuple[int, str]:
"""
Splits a frequency string such as "7D" into the multiple 7 and the base
granularity "D".
Parameters
----------
freq_str
Frequency string of the form [multiple][granularity] such as "12H",
"5min", "1D" etc.
"""
freq_regex = r"\s*((\d+)?)\s*([^\d]\w*)"
m = re.match(freq_regex, freq_str)
assert m is not None, "Cannot parse frequency string: %s" % freq_str
groups = m.groups()
multiple = int(groups[1]) if groups[1] is not None else 1
granularity = groups[2]
return multiple, granularity


def time_features_from_frequency_str(freq_str: str) -> List[TimeFeature]:
multiple, granularity = get_granularity(freq_str)
offset = to_offset(freq_str)
multiple, granularity = offset.n, offset.name

features = {
"M": ["weekofyear"],
Expand All @@ -109,7 +109,8 @@ def time_features_from_frequency_str(freq_str: str) -> List[TimeFeature]:
def get_lags_for_frequency(
freq_str: str, num_lags: Optional[int] = None
) -> List[int]:
multiple, granularity = get_granularity(freq_str)
offset = to_offset(freq_str)
multiple, granularity = offset.n, offset.name

if granularity == "M":
lags = [[1, 12]]
Expand All @@ -131,6 +132,75 @@ def get_lags_for_frequency(


class DeepVAREstimator(GluonEstimator):
"""
Constructs a DeepVAR estimator, which is a multivariate variant of DeepAR.

These models have been described as VEC-LSTM in this paper:
https://arxiv.org/abs/1910.03002

Note that this implementation will change over time and we further work on
this method. To replicate the results of the paper, please refer to our
(frozen) implementation here:
https://github.com/mbohlkeschneider/gluon-ts/tree/mv_release


Parameters
----------
freq
Frequency of the data to train on and predict
prediction_length
Length of the prediction horizon
target_dim
Dimensionality of the input dataset
trainer
Trainer object to be used (default: Trainer())
context_length
Number of steps to unroll the RNN for before computing predictions
(default: None, in which case context_length = prediction_length)
num_layers
Number of RNN layers (default: 2)
num_cells
Number of RNN cells for each layer (default: 40)
cell_type
Type of recurrent cells to use (available: 'lstm' or 'gru';
default: 'lstm')
num_parallel_samples
Number of evaluation samples per time series to increase parallelism
during inference. This is a model optimization that does not affect
the accuracy (default: 100)
dropout_rate
Dropout regularization parameter (default: 0.1)
cardinality
Number of values of each categorical feature (default: [1])
embedding_dimension
Dimension of the embeddings for categorical features
(default: 5])
distr_output
Distribution to use to evaluate observations and sample predictions
(default: LowrankMultivariateGaussianOutput with dim=target_dim and
rank=5). Note that target dim of the DistributionOutput and the
estimator constructor call need to match. Also note that the rank in
this constructor is meaningless if the DistributionOutput is
constructed outside of this class.
rank
Rank for the LowrankMultivariateGaussianOutput. (default: 5)
scaling
Whether to automatically scale the target values (default: true)
pick_incomplete
whether training examples can be sampled with only a part of
past_length time-units
lags_seq
Indices of the lagged target values to use as inputs of the RNN
(default: None, in which case these are automatically determined
based on freq)
time_features
Time features to use as inputs of the RNN (default: None, in which
case these are automatically determined based on freq)
conditioning_length
Set maximum length for conditioning the marginal transformation

"""

@validated()
def __init__(
self,
Expand All @@ -142,17 +212,18 @@ def __init__(
num_layers: int = 2,
num_cells: int = 40,
cell_type: str = "lstm",
num_eval_samples: int = 100,
num_parallel_samples: int = 100,
dropout_rate: float = 0.1,
cardinality: List[int] = [1],
embedding_dimension: int = 5,
distr_output: DistributionOutput = StudentTOutput(),
distr_output: Optional[DistributionOutput] = None,
rank: Optional[int] = 5,
scaling: bool = True,
conditioning_length: int = 200,
pick_incomplete: bool = False,
lags_seq: Optional[List[int]] = None,
time_features: Optional[List[TimeFeature]] = None,
use_copula=False,
conditioning_length: int = 200,
use_marginal_transformation=False,
**kwargs,
) -> None:
super().__init__(trainer=trainer, **kwargs)
Expand All @@ -166,7 +237,7 @@ def __init__(
assert num_layers > 0, "The value of `num_layers` should be > 0"
assert num_cells > 0, "The value of `num_cells` should be > 0"
assert (
num_eval_samples > 0
num_parallel_samples > 0
), "The value of `num_eval_samples` should be > 0"
assert dropout_rate >= 0, "The value of `dropout_rate` should be >= 0"
assert all(
Expand All @@ -180,18 +251,25 @@ def __init__(
self.context_length = (
context_length if context_length is not None else prediction_length
)

if distr_output is not None:
self.distr_output = distr_output
else:
self.distr_output = LowrankMultivariateGaussianOutput(
dim=target_dim, rank=rank
)

self.prediction_length = prediction_length
self.target_dim = target_dim
self.distr_output = distr_output
self.num_layers = num_layers
self.num_cells = num_cells
self.cell_type = cell_type
self.num_sample_paths = num_eval_samples
self.num_parallel_samples = num_parallel_samples
self.dropout_rate = dropout_rate
self.cardinality = cardinality
self.embedding_dimension = embedding_dimension
self.conditioning_length = conditioning_length
self.use_copula = use_copula
self.use_marginal_transformation = use_marginal_transformation

self.lags_seq = (
lags_seq
Expand All @@ -209,16 +287,18 @@ def __init__(
self.pick_incomplete = pick_incomplete
self.scaling = scaling

if self.use_copula:
if self.use_marginal_transformation:
self.output_transform: Optional[
Callable
] = cdf_to_gaussian_forward_transform
else:
self.output_transform = None

def create_transformation(self) -> Transformation:
def copula_transformation(use_copula: bool) -> Transformation:
if use_copula:
def use_marginal_transformation(
marginal_transformation: bool
) -> Transformation:
if marginal_transformation:
return CDFtoGaussianTransform(
target_field=FieldName.TARGET,
observed_values_field=FieldName.OBSERVED_VALUES,
Expand Down Expand Up @@ -264,7 +344,7 @@ def copula_transformation(use_copula: bool) -> Transformation:
field=FieldName.FEAT_STATIC_CAT, value=[0.0]
),
TargetDimIndicator(
field_name="target_dimensions",
field_name="target_dimension_indicator",
target_field=FieldName.TARGET,
),
AsNumpyArray(field=FieldName.FEAT_STATIC_CAT, expected_ndim=1),
Expand All @@ -282,7 +362,7 @@ def copula_transformation(use_copula: bool) -> Transformation:
],
pick_incomplete=self.pick_incomplete,
),
copula_transformation(self.use_copula),
use_marginal_transformation(self.use_marginal_transformation),
]
)

Expand All @@ -309,7 +389,7 @@ def create_predictor(
) -> Predictor:
prediction_network = DeepVARPredictionNetwork(
target_dim=self.target_dim,
num_sample_paths=self.num_sample_paths,
num_parallel_samples=self.num_parallel_samples,
num_layers=self.num_layers,
num_cells=self.num_cells,
cell_type=self.cell_type,
Expand Down
Loading