--- title: N-HiTS keywords: fastai sidebar: home_sidebar nb_path: "nbs/models_mqnhits__mqnhits.ipynb" ---
{% raw %}
{% endraw %} {% raw %}
{% endraw %} {% raw %}
{% endraw %} {% raw %}
{% endraw %} {% raw %}
{% endraw %} {% raw %}
{% endraw %} {% raw %}
{% endraw %}

A new model for long-horizon forecasting which incorporates novel hierarchical interpolation and multi-rate data sampling techniques to specialize blocks of its architecture to different frequency band of the time-series signal. It achieves SoTA performance on several benchmark datasets, outperforming current Transformer-based models by more than 25%. Paper available at https://arxiv.org/abs/2201.12886

{% raw %}

class MQNHITS[source]

MQNHITS(n_time_in:int, n_time_out:int, quantiles:List[float], n_x:int, n_s:int, shared_weights:bool, activation:str, initialization:str, stack_types:List[str], n_blocks:List[int], n_layers:List[int], n_mlp_units:List[List[int]], n_x_hidden:int, n_s_hidden:int, n_pool_kernel_size:List[int], n_freq_downsample:List[int], pooling_mode:str, interpolation_mode:str, batch_normalization:bool, dropout_prob_theta:float, learning_rate:float, lr_decay:float, lr_decay_step_size:int, weight_decay:float, loss_train:str, loss_hypar:float, loss_valid:str, frequency:str, random_seed:int) :: LightningModule

Hooks to be used in LightningModule.

{% endraw %} {% raw %}
{% endraw %} {% raw %}

MQNHITS.forecast[source]

MQNHITS.forecast(Y_df:DataFrame, X_df:DataFrame=None, S_df:DataFrame=None, batch_size:int=1, trainer:Trainer=None)

Method for forecasting self.n_time_out periods after last timestamp of Y_df.

Parameters

Y_df: pd.DataFrame Dataframe with target time-series data, needs 'unique_id','ds' and 'y' columns. X_df: pd.DataFrame Dataframe with exogenous time-series data, needs 'unique_id' and 'ds' columns. Note that 'unique_id' and 'ds' must match Y_df plus the forecasting horizon. S_df: pd.DataFrame Dataframe with static data, needs 'unique_id' column. bath_size: int Batch size for forecasting. trainer: pl.Trainer Trainer object for model training and evaluation.

Returns

forecast_df: pd.DataFrame Dataframe with forecasts.

{% endraw %} {% raw %}
{% endraw %}

MQN-HITS Usage Example

Load Data

{% raw %}
import pandas as pd
from neuralforecast.data.datasets.epf import EPF
from neuralforecast.data.tsloader import TimeSeriesLoader

import pylab as plt
from pylab import rcParams
plt.style.use('seaborn-whitegrid')
plt.rcParams['font.family'] = 'serif'

FONTSIZE = 19

# Load and plot data
Y_df, X_df, S_df = EPF.load_groups(directory='./data', groups=['NP'])

fig = plt.figure(figsize=(15, 6))
plt.plot(Y_df[Y_df['unique_id']=='NP'].ds, Y_df[Y_df['unique_id']=='NP'].y.values, color='#628793', linewidth=0.4)
plt.ylabel('Price [EUR/MWh]', fontsize=19)
plt.xlabel('Date', fontsize=15)
plt.show()
{% endraw %}

Declare Model and Data Parameters

{% raw %}
mc = {}
mc['model'] = 'n-hits'
mc['mode'] = 'simple'
mc['activation'] = 'ReLU'

mc['n_time_in'] = 24*3
mc['n_time_out'] = 24
mc['quantiles'] = [5, 50, 95]
mc['n_x_hidden'] = 8
mc['n_s_hidden'] = 0

mc['stack_types'] = ['identity', 'identity']
mc['constant_n_blocks'] = 1
mc['constant_n_layers'] = 2
mc['constant_n_mlp_units'] = 256
mc['n_pool_kernel_size'] = [1, 1]
mc['n_freq_downsample'] = [12, 1]
mc['pooling_mode'] = 'max'
mc['interpolation_mode'] = 'linear'
mc['shared_weights'] = False

# Optimization and regularization parameters
mc['initialization'] = 'lecun_normal'
mc['learning_rate'] = 0.001
mc['batch_size'] = 1
mc['n_windows'] = 64
mc['lr_decay'] = 0.5
mc['lr_decay_step_size'] = 333
mc['max_epochs'] = None
mc['max_steps'] = 1
mc['early_stop_patience'] = 5
mc['eval_freq'] = 100
mc['batch_normalization'] = False
mc['dropout_prob_theta'] = 0.0
mc['dropout_prob_exogenous'] = 0.0
mc['weight_decay'] = 0
mc['loss_train'] = 'MQ'
mc['loss_hypar'] = 0.5
mc['loss_valid'] = mc['loss_train']
mc['random_seed'] = 1

# Data Parameters
mc['idx_to_sample_freq'] = 1
mc['val_idx_to_sample_freq'] = 1
mc['n_val_weeks'] = 52
mc['normalizer_y'] = 'median'
mc['normalizer_x'] = 'median'
mc['complete_windows'] = False
mc['frequency'] = 'H'

print(65*'=')
print(pd.Series(mc))
print(65*'=')

mc['n_mlp_units'] = len(mc['stack_types']) * [ mc['constant_n_layers'] * [int(mc['constant_n_mlp_units'])] ]
mc['n_blocks'] =  len(mc['stack_types']) * [ mc['constant_n_blocks'] ]
mc['n_layers'] =  len(mc['stack_types']) * [ mc['constant_n_layers'] ]
=================================================================
model                                   n-hits
mode                                    simple
activation                                ReLU
n_time_in                                   72
n_time_out                                  24
quantiles                          [5, 50, 95]
n_x_hidden                                   8
n_s_hidden                                   0
stack_types               [identity, identity]
constant_n_blocks                            1
constant_n_layers                            2
constant_n_mlp_units                       256
n_pool_kernel_size                      [1, 1]
n_freq_downsample                      [12, 1]
pooling_mode                               max
interpolation_mode                      linear
shared_weights                           False
initialization                    lecun_normal
learning_rate                            0.001
batch_size                                   1
n_windows                                   64
lr_decay                                   0.5
lr_decay_step_size                         333
max_epochs                                None
max_steps                                    1
early_stop_patience                          5
eval_freq                                  100
batch_normalization                      False
dropout_prob_theta                         0.0
dropout_prob_exogenous                     0.0
weight_decay                                 0
loss_train                                  MQ
loss_hypar                                 0.5
loss_valid                                  MQ
random_seed                                  1
idx_to_sample_freq                           1
val_idx_to_sample_freq                       1
n_val_weeks                                 52
normalizer_y                            median
normalizer_x                            median
complete_windows                         False
frequency                                    H
dtype: object
=================================================================
{% endraw %}

Instantiate Loaders and Model

{% raw %}
from neuralforecast.experiments.utils import create_datasets

train_dataset, val_dataset, test_dataset, scaler_y = create_datasets(mc=mc,
                                                                     S_df=S_df, Y_df=Y_df, X_df=X_df,
                                                                     f_cols=['Exogenous1', 'Exogenous2'],
                                                                     ds_in_val=294*24,
                                                                     ds_in_test=728*24)

train_loader = TimeSeriesLoader(dataset=train_dataset,
                                batch_size=int(mc['batch_size']),
                                n_windows=mc['n_windows'],
                                shuffle=True)

val_loader = TimeSeriesLoader(dataset=val_dataset,
                              batch_size=int(mc['batch_size']),
                              shuffle=False)

test_loader = TimeSeriesLoader(dataset=test_dataset,
                               batch_size=int(mc['batch_size']),
                               shuffle=False)

mc['n_x'], mc['n_s'] = train_dataset.get_n_variables()
{% endraw %} {% raw %}
model = MQNHITS(n_time_in=int(mc['n_time_in']),
              n_time_out=int(mc['n_time_out']),
              quantiles=mc['quantiles'],
              n_x=mc['n_x'],
              n_s=mc['n_s'],
              n_s_hidden=int(mc['n_s_hidden']),
              n_x_hidden=int(mc['n_x_hidden']),
              shared_weights=mc['shared_weights'],
              initialization=mc['initialization'],
              activation=mc['activation'],
              stack_types=mc['stack_types'],
              n_blocks=mc['n_blocks'],
              n_layers=mc['n_layers'],
              n_mlp_units=mc['n_mlp_units'],
              n_pool_kernel_size=mc['n_pool_kernel_size'],
              n_freq_downsample=mc['n_freq_downsample'],
              pooling_mode=mc['pooling_mode'],
              interpolation_mode=mc['interpolation_mode'],
              batch_normalization = mc['batch_normalization'],
              dropout_prob_theta=mc['dropout_prob_theta'],
              learning_rate=float(mc['learning_rate']),
              lr_decay=float(mc['lr_decay']),
              lr_decay_step_size=float(mc['lr_decay_step_size']),
              weight_decay=mc['weight_decay'],
              loss_train=mc['loss_train'],
              loss_hypar=float(mc['loss_hypar']),
              loss_valid=mc['loss_valid'],
              frequency=mc['frequency'],
              random_seed=int(mc['random_seed']))
{% endraw %}

Train Model

{% raw %}
from pytorch_lightning.callbacks import EarlyStopping

early_stopping = EarlyStopping(monitor="val_loss", 
                               min_delta=1e-4, 
                               patience=mc['early_stop_patience'],
                               verbose=False,
                               mode="min")

trainer = pl.Trainer(max_epochs=mc['max_epochs'], 
                     max_steps=mc['max_steps'],
                     gradient_clip_val=1.0,
                     progress_bar_refresh_rate=1, 
                     log_every_n_steps=1,
                     check_val_every_n_epoch=mc['eval_freq'],
                     callbacks=[early_stopping])

trainer.fit(model, train_loader, val_loader)
/Users/cchallu/opt/anaconda3/envs/neuralforecast/lib/python3.7/site-packages/pytorch_lightning/trainer/connectors/callback_connector.py:91: LightningDeprecationWarning: Setting `Trainer(progress_bar_refresh_rate=1)` is deprecated in v1.5 and will be removed in v1.7. Please pass `pytorch_lightning.callbacks.progress.TQDMProgressBar` with `refresh_rate` directly to the Trainer's `callbacks` argument instead. Or, to disable the progress bar pass `enable_progress_bar = False` to the Trainer.
  f"Setting `Trainer(progress_bar_refresh_rate={progress_bar_refresh_rate})` is deprecated in v1.5 and"

  | Name  | Type   | Params
---------------------------------
0 | model | _NHITS | 717 K 
---------------------------------
717 K     Trainable params
0         Non-trainable params
717 K     Total params
2.870     Total estimated model params size (MB)
Validation sanity check:   0%|          | 0/1 [00:00<?, ?it/s]
/Users/cchallu/opt/anaconda3/envs/neuralforecast/lib/python3.7/site-packages/pytorch_lightning/trainer/data_loading.py:133: UserWarning: The dataloader, val_dataloader 0, does not have many workers which may be a bottleneck. Consider increasing the value of the `num_workers` argument` (try 12 which is the number of cpus on this machine) in the `DataLoader` init to improve performance.
  f"The dataloader, {name}, does not have many workers which may be a bottleneck."
/Users/cchallu/opt/anaconda3/envs/neuralforecast/lib/python3.7/site-packages/torch/nn/functional.py:3635: UserWarning: Default upsampling behavior when mode=linear is changed to align_corners=False since 0.4.0. Please specify align_corners=True if the old behavior is desired. See the documentation of nn.Upsample for details.
  "See the documentation of nn.Upsample for details.".format(mode)
                                                                      
/Users/cchallu/opt/anaconda3/envs/neuralforecast/lib/python3.7/site-packages/pytorch_lightning/trainer/data_loading.py:133: UserWarning: The dataloader, train_dataloader, does not have many workers which may be a bottleneck. Consider increasing the value of the `num_workers` argument` (try 12 which is the number of cpus on this machine) in the `DataLoader` init to improve performance.
  f"The dataloader, {name}, does not have many workers which may be a bottleneck."
Epoch 0: 100%|██████████| 1/1 [00:00<00:00,  8.11it/s, loss=0.246, v_num=35, train_loss_step=0.246, train_loss_epoch=0.246]
{% endraw %}

Make Predictions

{% raw %}
model.return_decomposition = False
outputs = trainer.predict(model, val_loader)

print("outputs[0][0].shape", outputs[0][0].shape)
print("outputs[0][1].shape", outputs[0][1].shape)
print("outputs[0][2].shape", outputs[0][2].shape)
/Users/cchallu/opt/anaconda3/envs/neuralforecast/lib/python3.7/site-packages/pytorch_lightning/trainer/data_loading.py:133: UserWarning: The dataloader, predict_dataloader 0, does not have many workers which may be a bottleneck. Consider increasing the value of the `num_workers` argument` (try 12 which is the number of cpus on this machine) in the `DataLoader` init to improve performance.
  f"The dataloader, {name}, does not have many workers which may be a bottleneck."
Predicting: 100%|██████████| 1/1 [00:00<?, ?it/s]
outputs[0][0].shape torch.Size([7033, 24])
outputs[0][1].shape torch.Size([7033, 24, 3])
outputs[0][2].shape torch.Size([7033, 24])
{% endraw %}

Forecast

{% raw %}
day = '2016-08-03'
day2 = '2016-08-04'
Y_forecast_df = Y_df[Y_df['ds']<day]
Y_future_true = Y_df[(Y_df['ds']>=day) & (Y_df['ds']<day2)]
Y_forecast_df.tail()
unique_id ds y
31435 NP 2016-08-02 19:00:00 -0.227484
31436 NP 2016-08-02 20:00:00 -0.320752
31437 NP 2016-08-02 21:00:00 -0.359425
31438 NP 2016-08-02 22:00:00 -0.469754
31439 NP 2016-08-02 23:00:00 -0.527763
{% endraw %} {% raw %}
X_forecast_df = X_df[X_df['ds']<day2]
X_forecast_df.tail()
unique_id ds Exogenous1 Exogenous2 week_day day_0 day_1 day_2 day_3 day_4 day_5 day_6
31459 NP 2016-08-03 19:00:00 -0.733092 0.573754 -0.337245 0.0 0.0 1.92748 0.0 0.0 0.0 0.0
31460 NP 2016-08-03 20:00:00 -0.812364 0.473019 -0.337245 0.0 0.0 1.92748 0.0 0.0 0.0 0.0
31461 NP 2016-08-03 21:00:00 -0.846161 0.403818 -0.337245 0.0 0.0 1.92748 0.0 0.0 0.0 0.0
31462 NP 2016-08-03 22:00:00 -0.922747 0.391554 -0.337245 0.0 0.0 1.92748 0.0 0.0 0.0 0.0
31463 NP 2016-08-03 23:00:00 -1.096278 0.327609 -0.337245 0.0 0.0 1.92748 0.0 0.0 0.0 0.0
{% endraw %} {% raw %}
model.return_decomposition = False
forecast_df = model.forecast(Y_df=Y_forecast_df, X_df=X_forecast_df, S_df=S_df, batch_size=2)
INFO:root:Train Validation splits

INFO:root:                              ds                    
                             min                 max
unique_id sample_mask                               
NP        0           2013-01-01 2016-08-02 23:00:00
          1           2016-08-03 2016-08-03 23:00:00
INFO:root:
Total data 			31464 time stamps 
Available percentage=100.0, 	31464 time stamps 
Insample  percentage=0.08, 	24 time stamps 
Outsample percentage=99.92, 	31440 time stamps 

/Users/cchallu/opt/anaconda3/envs/neuralforecast/lib/python3.7/site-packages/pytorch_lightning/trainer/connectors/callback_connector.py:91: LightningDeprecationWarning: Setting `Trainer(progress_bar_refresh_rate=1)` is deprecated in v1.5 and will be removed in v1.7. Please pass `pytorch_lightning.callbacks.progress.TQDMProgressBar` with `refresh_rate` directly to the Trainer's `callbacks` argument instead. Or, to disable the progress bar pass `enable_progress_bar = False` to the Trainer.
  f"Setting `Trainer(progress_bar_refresh_rate={progress_bar_refresh_rate})` is deprecated in v1.5 and"
/Users/cchallu/opt/anaconda3/envs/neuralforecast/lib/python3.7/site-packages/pytorch_lightning/trainer/data_loading.py:133: UserWarning: The dataloader, predict_dataloader 0, does not have many workers which may be a bottleneck. Consider increasing the value of the `num_workers` argument` (try 12 which is the number of cpus on this machine) in the `DataLoader` init to improve performance.
  f"The dataloader, {name}, does not have many workers which may be a bottleneck."
Predicting: 100%|██████████| 1/1 [00:00<00:00, 57.53it/s]
forecast.shape (1, 24, 3)
forecast.shape (24, 3)
/Users/cchallu/opt/anaconda3/envs/neuralforecast/lib/python3.7/site-packages/torch/nn/functional.py:3635: UserWarning: Default upsampling behavior when mode=linear is changed to align_corners=False since 0.4.0. Please specify align_corners=True if the old behavior is desired. See the documentation of nn.Upsample for details.
  "See the documentation of nn.Upsample for details.".format(mode)
{% endraw %} {% raw %}
forecast_df.head()
unique_id ds y_5 y_50 y_95
0 NP 2016-08-03 00:00:00 -0.753195 -0.584903 -0.262628
1 NP 2016-08-03 01:00:00 -0.793891 -0.660660 -0.183137
2 NP 2016-08-03 02:00:00 -0.631817 -0.527431 -0.259866
3 NP 2016-08-03 03:00:00 -0.638283 -0.419250 -0.257997
4 NP 2016-08-03 04:00:00 -0.654396 -0.589125 -0.340072
{% endraw %} {% raw %}
def plot_single_prediction(ax, x_plot, y_plot_hat):    
    ax.plot(x_plot,y_plot_hat[:,2], 
            label='Forecast', color='blue')
    ax.fill_between(x_plot,
                    y1=y_plot_hat[:,1], y2=y_plot_hat[:,3],
                    facecolor='blue', alpha=0.4, label='[q25-q75]')
    ax.fill_between(x_plot,
                    y1=y_plot_hat[:,0], y2=y_plot_hat[:,4],
                    facecolor='blue', alpha=0.2, label='[q1-q99]')
{% endraw %} {% raw %}
serie = 'NP'

Y_plot_df = Y_future_true[Y_future_true['unique_id']==serie]
Y_hat_plot_df = forecast_df[forecast_df['unique_id']==serie]

plt.plot(Y_plot_df['y'].values, c='black', label='True')
plt.plot(Y_hat_plot_df['y_5'].values, c='blue', label='Forecast')
plt.plot(Y_hat_plot_df['y_50'].values, c='blue')
plt.plot(Y_hat_plot_df['y_95'].values, c='blue')
plt.fill_between(x=range(24),
                 y1=forecast_df[forecast_df['unique_id']=='NP']['y_5'].values,
                 y2=forecast_df[forecast_df['unique_id']=='NP']['y_95'].values,
                 alpha=0.2, label='p5-p95')
plt.ylabel('Normalized Electricity Price')
plt.xlabel('Prediction step')
plt.legend()
plt.show()
plt.close()
{% endraw %}