Skip to content

Commit

Permalink
Merge pull request #1 from awslabs/master
Browse files Browse the repository at this point in the history
Changes from upstream
  • Loading branch information
strawberrypie authored Oct 9, 2019
2 parents af91b83 + 93ebd61 commit 7eb9353
Show file tree
Hide file tree
Showing 18 changed files with 157 additions and 84 deletions.
20 changes: 0 additions & 20 deletions .github/CODEOWNERS

This file was deleted.

6 changes: 3 additions & 3 deletions docs/examples/basic_forecasting_tutorial/tutorial.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ In general, the datasets provided by GluonTS are objects that consists of three

- `dataset.train` is an iterable collection of data entries used for training. Each entry corresponds to one time series
- `dataset.test` is an iterable collection of data entries used for inference. The test dataset is an extended version of the train dataset that contains a window in the end of each time series that was not seen during training. This window has length equal to the recommended prediction length.
- `dataset.metadata` containts metadata of the dataset such as the frequency of the time series, a recommended prediction horizon, associated features, etc.
- `dataset.metadata` contains metadata of the dataset such as the frequency of the time series, a recommended prediction horizon, associated features, etc.


```python
Expand Down Expand Up @@ -148,7 +148,7 @@ estimator = SimpleFeedForwardEstimator(
)
```

After specifing our estimator with all the necessary hyperparameters we can train it using our training dataset `dataset.train` by invoking the `train` method of the estimator. The training algorithm returns a fitted model (or a `Predictor` in GluonTS parlance) that can be used to construct forecasts.
After specifying our estimator with all the necessary hyperparameters we can train it using our training dataset `dataset.train` by invoking the `train` method of the estimator. The training algorithm returns a fitted model (or a `Predictor` in GluonTS parlance) that can be used to construct forecasts.


```python
Expand Down Expand Up @@ -301,7 +301,7 @@ For creating your own forecast model you need to:
The training and prediction networks can be arbitrarily complex but they should follow some basic rules:

- Both should have a `hybrid_forward` method that defines what should happen when the network is called
- The trainng network's `hybrid_forward` should return a **loss** based on the prediction and the true values
- The training network's `hybrid_forward` should return a **loss** based on the prediction and the true values
- The prediction network's `hybrid_forward` should return the predictions

For example, we can create a simple training network that defines a neural network which takes as an input the past values of the time series and outputs a future predicted window of length `prediction_length`. It uses the L1 loss in the `hybrid_forward` method to evaluate the error among the predictions and the true values of the time series. The corresponding prediction network should be identical to the training network in terms of architecture (we achieve this by inheriting the training network class), and its `hybrid_forward` method outputs directly the predictions.
Expand Down
22 changes: 11 additions & 11 deletions docs/examples/extended_forecasting_tutorial/extended_tutorial.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ The first requirement to use GluonTS is to have an appropriate dataset. GluonTS
- Create an artificial dataset using GluonTS
- Convert your dataset to a GluonTS friendly format

In general, a dataset should satisfy some minimum format requirements to be compatible with GluonTS. In particular, it should be an iterable collection of data entries (time series), and each entry should have at least a `target` field, which contains the actual values of the time series, and a `start` field, which denotes the starting date of the time series. There are many more optional fields that we will go throught in this tutorial.
In general, a dataset should satisfy some minimum format requirements to be compatible with GluonTS. In particular, it should be an iterable collection of data entries (time series), and each entry should have at least a `target` field, which contains the actual values of the time series, and a `start` field, which denotes the starting date of the time series. There are many more optional fields that we will go through in this tutorial.

The datasets provided by GluonTS come in the appropriate format and they can be used without any post processing. However, a custom dataset needs to be converted. Fortunately this is an easy task.

Expand Down Expand Up @@ -87,9 +87,9 @@ In general, the datasets provided by GluonTS are objects that consists of three

- `dataset.train` is an iterable collection of data entries used for training. Each entry corresponds to one time series.
- `dataset.test` is an iterable collection of data entries used for inference. The test dataset is an extended version of the train dataset that contains a window in the end of each time series that was not seen during training. This window has length equal to the recommended prediction length.
- `dataset.metadata` containts metadata of the dataset such as the frequency of the time series, a recommended prediction horizon, associated features, etc.
- `dataset.metadata` contains metadata of the dataset such as the frequency of the time series, a recommended prediction horizon, associated features, etc.

First, let's see what the first entry of the train dataset contains. We should expect at least a `target` anf a `start` field in each entry, and the target of the test entry to have an additional window equal to `prediction_length`.
First, let's see what the first entry of the train dataset contains. We should expect at least a `target` and a `start` field in each entry, and the target of the test entry to have an additional window equal to `prediction_length`.


```python
Expand Down Expand Up @@ -264,7 +264,7 @@ Added by `Transformation`:
- `time_feat`: time related features such as the month or the day
- `feat_dynamic_const`: expands a constant value feature along the time axis
- `feat_dynamic_age`: age feature, i.e., a feature that its value is small for distant past timestamps and it monotonically increases the more we approach the current timestamp
- `observed_values`: indicator for observed values, i.e., a feature thatequals to 1 if the value is observed and 0 if the value is missing
- `observed_values`: indicator for observed values, i.e., a feature that equals to 1 if the value is observed and 0 if the value is missing
- `is_pad`: indicator for each time step that shows if it is padded (if the length is not enough)
- `forecast_start`: forecast start date

Expand Down Expand Up @@ -497,7 +497,7 @@ The transformer has done what we asked. In particular it has added:
- a field for the age feature (`feat_dynamic_age`)
- some extra useful fields (`past_is_pad`, `forecast_start`)

It has done one more important thing: it has splitted the window in past and future and has added the corresponding prefixes to all time dependent fields. This way we can easily use e.g., the `past_target` field as input and the `future_target` field to calculate the error of our predictions. Of course, the length of the past is equal to the `context_length` and of the future equal to the `prediction_length`.
It has done one more important thing: it has split the window into past and future and has added the corresponding prefixes to all time dependent fields. This way we can easily use e.g., the `past_target` field as input and the `future_target` field to calculate the error of our predictions. Of course, the length of the past is equal to the `context_length` and of the future equal to the `prediction_length`.


```python
Expand All @@ -517,9 +517,9 @@ Just for comparison, let's see again what were the fields in the original datase
[k for k in next(iter(train_ds)).keys()]
```

Now, we can move on and see how the test dataset is split. As we saw, the transformation splits the windows to past and future. However, during inference (`is_train=False` in the transformation), the splitter always cuts the last window (of length `context_length`) of the dataset so it can be used to predict the subsequent unknown values of length `prediction_length`.
Now, we can move on and see how the test dataset is split. As we saw, the transformation splits the windows into past and future. However, during inference (`is_train=False` in the transformation), the splitter always cuts the last window (of length `context_length`) of the dataset so it can be used to predict the subsequent unknown values of length `prediction_length`.

So, how is the test dataset split in past and futere since we do not know the future target? And what about the time dependent features?
So, how is the test dataset split in past and future since we do not know the future target? And what about the time dependent features?


```python
Expand Down Expand Up @@ -590,7 +590,7 @@ estimator = SimpleFeedForwardEstimator(

## 3.2 Getting a predictor

After specifing our estimator with all the necessary hyperparameters we can train it using our training dataset `dataset.train` by invoking the `train` method of the estimator. The training algorithm returns a fitted model (or a `Predictor` in GluonTS parlance) that can be used to construct forecasts.
After specifying our estimator with all the necessary hyperparameters we can train it using our training dataset `dataset.train` by invoking the `train` method of the estimator. The training algorithm returns a fitted model (or a `Predictor` in GluonTS parlance) that can be used to construct forecasts.

We should emphasize here that a single model, as the one defined above, is trained over all the time series contained in the training dataset `train_ds`. This results in a **global** model, suitable for prediction for all the time series in `train_ds` and possibly for other unseen related time series.

Expand Down Expand Up @@ -774,7 +774,7 @@ For creating our own forecast model we need to:
The training and prediction networks can be arbitrarily complex but they should follow some basic rules:

- Both should have a `hybrid_forward` method that defines what should happen when the network is called
- The trainng network's `hybrid_forward` should return a **loss** based on the prediction and the true values
- The training network's `hybrid_forward` should return a **loss** based on the prediction and the true values
- The prediction network's `hybrid_forward` should return the predictions

The estimator should also follow some rules:
Expand Down Expand Up @@ -1156,9 +1156,9 @@ plot_prob_forecasts(tss[0], forecasts[0])

In the previous networks we used only the target and did not leverage any of the features of the dataset. Here we expand the probabilistic network by including the `feat_dynamic_real` field of the dataset that could enhance the forecasting power of our model. We achieve this by concatenating the target and the features to an enhanced vector that forms the new network input.

All the features that are available in a dataset can be potentionaly used as inputs to our model. However, for the purposes of this example we will restrict ourselves to using only one feature.
All the features that are available in a dataset can be potentially used as inputs to our model. However, for the purposes of this example we will restrict ourselves to using only one feature.

An important issue that a practicioner needs to deal with often is the different orders of magnitude in the values of the time series in a dataset. It is extremely helpful for a model to be trained and forecast values that lie roughly in the same value range. To address this issue, we add a `Scaler` to out model, that computes the scale of each time series. Then we can scale accordingly the values of the time series or any related features and use these as inputs to the network.
An important issue that a practitioner needs to deal with often is the different orders of magnitude in the values of the time series in a dataset. It is extremely helpful for a model to be trained and forecast values that lie roughly in the same value range. To address this issue, we add a `Scaler` to out model, that computes the scale of each time series. Then we can scale accordingly the values of the time series or any related features and use these as inputs to the network.


```python
Expand Down
17 changes: 14 additions & 3 deletions src/gluonts/core/serde.py
Original file line number Diff line number Diff line change
Expand Up @@ -208,9 +208,13 @@ def _dump_code(x: Any) -> str:
return "(" + ", ".join(elems) + ",)"
elif isinstance(x, str):
return '"' + x + '"' # TODO: escape comp characters
elif isinstance(x, float):
elif isinstance(x, float) or np.issubdtype(type(x), np.inexact):
return str(x) if math.isfinite(x) else 'float("' + str(x) + '")'
elif isinstance(x, int) or x is None:
elif (
isinstance(x, int)
or np.issubdtype(type(x), np.integer)
or x is None
):
return str(x)
else:
x = fqname_for(x.__class__)
Expand Down Expand Up @@ -386,7 +390,11 @@ def encode(v: Any) -> Any:
return None
elif isinstance(v, (float, int, str)):
return v
elif isinstance(v, list) or type(v) == tuple:
elif np.issubdtype(type(v), np.inexact):
return float(v)
elif np.issubdtype(type(v), np.integer):
return int(v)
elif isinstance(v, (list, set)) or type(v) == tuple:
return [encode(v) for v in v]
elif isinstance(v, tuple) and not hasattr(v, "_asdict"):
return tuple([encode(v) for v in v])
Expand Down Expand Up @@ -542,6 +550,9 @@ def decode(r: Any) -> Any:
# r = [ y1, ..., yn ]
elif type(r) == list:
return [decode(y) for y in r]
# r = { y1, ..., yn }
elif type(r) == set:
return {decode(y) for y in r}
# r = a
else:
return r
44 changes: 31 additions & 13 deletions src/gluonts/dataset/artificial/recipe.py
Original file line number Diff line number Diff line change
Expand Up @@ -360,7 +360,7 @@ class RandomCat:
def __init__(
self,
cardinalities: List[int],
prob_fun: Callable = RandomSymmetricDirichlet(alpha=1.0),
prob_fun: Callable = RandomSymmetricDirichlet(alpha=1.0, shape=(0,)),
) -> None:
self.cardinalities = cardinalities
self.prob_fun = prob_fun
Expand All @@ -370,25 +370,40 @@ def __call__(self, x, field_name, global_state, **kwargs):
probs = [self.prob_fun(x, length=c) for c in self.cardinalities]
global_state[field_name] = probs
probs = global_state[field_name]
cats = [
np.random.choice(np.arange(len(probs[i])), p=probs[i])
for i in range(len(probs))
]
cats = np.array(
[
np.random.choice(np.arange(len(probs[i])), p=probs[i])
for i in range(len(probs))
]
)
return cats


class Lag(Lifted):
@validated()
def __init__(self, input: ValueOrCallable, lag: int = 0) -> None:
def __init__(
self,
input: ValueOrCallable,
lag: ValueOrCallable = 0,
pad_const: int = 0,
) -> None:
self.input = input
self.lag = lag
self.pad_const = pad_const

def __call__(self, x, *args, **kwargs):
feat = resolve(self.input, x, *args, **kwargs)
if self.lag != 0:
lag = resolve(self.lag, x, *args, **kwargs)

if lag > 0:
lagged_feat = np.concatenate(
(np.zeros(self.lag), feat[: -self.lag])
(self.pad_const * np.ones(lag), feat[:-lag])
)
elif lag < 0:
lagged_feat = np.concatenate(
(feat[-lag:], self.pad_const * np.ones(-lag))
)

else:
lagged_feat = feat
return lagged_feat
Expand Down Expand Up @@ -418,7 +433,7 @@ def __call__(
)
if global_state[field_name][c] is None:
global_state[field_name][c] = self.fun(
x, length, field_name, *args, **kwargs
x, length=length, field_name=field_name, *args, **kwargs
)
return global_state[field_name][c]

Expand Down Expand Up @@ -563,7 +578,7 @@ def __init__(
self,
low: ValueOrCallable,
high: ValueOrCallable,
shape: Sequence[int] = (0,),
shape: Optional[Sequence[int]] = (0,),
) -> None:
self.low = low
self.high = high
Expand All @@ -572,9 +587,12 @@ def __init__(
def __call__(self, x: Env, length: int, *args, **kwargs):
low = resolve(self.low, x, length, *args, **kwargs)
high = resolve(self.high, x, length, *args, **kwargs)
s = np.array(self.shape)
s[s == 0] = length
return np.random.randint(low, high, s)
if self.shape is not None:
s = np.array(self.shape)
s[s == 0] = length
return np.random.randint(low, high, s)
else:
return np.random.randint(low, high)


class RandomChangepoints(Lifted):
Expand Down
4 changes: 4 additions & 0 deletions src/gluonts/dataset/loader.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,10 @@ def stack(self, xs):
return mx.nd.array(data, dtype=data.dtype, ctx=self.ctx)
elif isinstance(xs[0], mx.nd.NDArray):
return mx.nd.stack(*xs)
elif isinstance(xs[0], list):
return [self.stack(t) for t in zip(*[x for x in xs])]
elif isinstance(xs[0], tuple):
return tuple([self.stack(t) for t in zip(*[x for x in xs])])
else:
return xs # stack all other types as list

Expand Down
6 changes: 6 additions & 0 deletions src/gluonts/distribution/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@
from .transformed_distribution import TransformedDistribution
from .transformed_distribution_output import TransformedDistributionOutput
from .uniform import Uniform, UniformOutput
from .box_cox_tranform import (
BoxCoxTransformOutput,
InverseBoxCoxTransformOutput,
)

__all__ = [
"Distribution",
Expand Down Expand Up @@ -64,6 +68,8 @@
"TransformedPiecewiseLinear",
"TransformedDistribution",
"TransformedDistributionOutput",
"InverseBoxCoxTransformOutput",
"BoxCoxTransformOutput",
"bijection",
]

Expand Down
3 changes: 2 additions & 1 deletion src/gluonts/distribution/distribution_output.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,9 @@ def __init__(
domain_map: Callable[..., Tuple[Tensor]],
float_type: DType = np.float32,
prefix: Optional[str] = None,
**kwargs,
) -> None:
super().__init__()
super().__init__(**kwargs)
self.args_dim = args_dim
self.float_type = float_type
self.proj = [
Expand Down
9 changes: 7 additions & 2 deletions src/gluonts/evaluation/_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,10 @@ def get_metrics_per_ts(
pred_target = np.array(self.extract_pred_target(time_series, forecast))
pred_target = np.ma.masked_invalid(pred_target)

mean_fcst = forecast.mean
try:
mean_fcst = forecast.mean
except:
mean_fcst = None
median_fcst = forecast.quantile(0.5)
seasonal_error = self.seasonal_error(time_series, forecast)
# For MSIS: alpha/2 quantile may not exist. Find the closest.
Expand All @@ -230,7 +233,9 @@ def get_metrics_per_ts(

metrics = {
"item_id": forecast.item_id,
"MSE": self.mse(pred_target, mean_fcst),
"MSE": self.mse(pred_target, mean_fcst)
if mean_fcst is not None
else None,
"abs_error": self.abs_error(pred_target, median_fcst),
"abs_target_sum": self.abs_target_sum(pred_target),
"abs_target_mean": self.abs_target_mean(pred_target),
Expand Down
Loading

0 comments on commit 7eb9353

Please sign in to comment.