Source code for tfts.models.nbeats
"""
`N-BEATS: Neural basis expansion analysis for interpretable time series forecasting
<https://arxiv.org/abs/1905.10437>`_
"""
from typing import List, Optional
import tensorflow as tf
from tensorflow.keras.layers import Add, Lambda, Layer, Subtract
from ..layers.nbeats_layer import GenericBlock, SeasonalityBlock, TrendBlock
from ..layers.util_layer import ShapeLayer, ZerosLayer
from .base import BaseConfig, BaseModel
[docs]
class NBeatsConfig(BaseConfig):
model_type: str = "nbeats"
def __init__(
self,
stack_types=["trend_block", "seasonality_block"],
nb_blocks_per_stack=3,
num_block_layers=4,
hidden_size=64,
thetas_dims=(4, 8),
share_weights_in_stack=False,
):
super(NBeatsConfig, self).__init__()
self.stack_types = stack_types
self.nb_blocks_per_stack = nb_blocks_per_stack
self.num_block_layers = num_block_layers
self.hidden_size = hidden_size
self.thetas_dims = thetas_dims
self.share_weights_in_stack = share_weights_in_stack
[docs]
class NBeats(BaseModel):
"""NBeats model"""
def __init__(
self,
predict_sequence_length: int = 1,
config: Optional[NBeatsConfig] = None,
):
super().__init__()
self.config = config or NBeatsConfig()
self.predict_sequence_length = predict_sequence_length
self.train_sequence_length = 1 # Temp
self.stack_types = self.config.stack_types
self.nb_blocks_per_stack = self.config.nb_blocks_per_stack
self.hidden_size = self.config.hidden_size
self.num_block_layers = self.config.num_block_layers
# Create custom layers
self.shape_layer = ShapeLayer()
self.squeeze_layer = Lambda(lambda t: tf.squeeze(t, 2), output_shape=lambda s: (s[0], s[1]))
self.zeros_layer = ZerosLayer(predict_sequence_length)
self.expand_dims_layer = Lambda(lambda x: tf.expand_dims(x, -1), output_shape=lambda s: s + (1,))
self.block_type = {"trend_block": TrendBlock, "seasonality_block": SeasonalityBlock, "general": GenericBlock}
self.stacks = []
for stack_type in self.stack_types:
self.stacks.append(self.create_stack(stack_type))
def __call__(
self, inputs: tf.Tensor, output_hidden_states: Optional[bool] = None, return_dict: Optional[bool] = None
):
if isinstance(inputs, (list, tuple)):
print("NBeats only support single variable prediction, so ignore encoder_features and decoder_features")
x, encoder_features, _ = inputs
else: # for single variable prediction
x = inputs
shape = self.shape_layer(x)
self.train_sequence_length = shape[1]
x = self.squeeze_layer(x)
forecast = self.zeros_layer(x)
backcast = x
for stack_id in range(len(self.stacks)):
for block_id in range(len(self.stacks[stack_id])):
b, f = self.stacks[stack_id][block_id](backcast)
backcast = Subtract()([backcast, b])
forecast = Add()([forecast, f])
# Final expansion using Keras layer
forecast = self.expand_dims_layer(forecast)
return forecast
def create_stack(self, stack_type):
blocks: List[Layer] = []
for block_id in range(self.nb_blocks_per_stack):
block_fn = self.block_type[stack_type]
block = block_fn(
self.train_sequence_length, self.predict_sequence_length, self.hidden_size, self.num_block_layers
)
blocks.append(block)
return blocks