diff --git a/gists/timeseries/nhits/README.md b/gists/timeseries/nhits/README.md new file mode 100644 index 00000000..630473c1 --- /dev/null +++ b/gists/timeseries/nhits/README.md @@ -0,0 +1,83 @@ +# N-HiTS is a neural breakthrough — NNS needs no network 🚦 + +[N-HiTS](https://arxiv.org/abs/2201.12886) (Neural Hierarchical Interpolation for +Time Series, AAAI 2023) is one of the strongest deep-learning forecasters around. +By combining **multi-rate input sampling** with **hierarchical interpolation** — +built on the N-BEATS lineage — it reports roughly **20% better long-horizon +accuracy than Transformer models at ~50× less compute**. It is, however, still a +neural network: it needs a training loop, hyperparameters, and the hardware to go +with them. + +Marco Peixeiro's walkthrough, [*All About +N-HiTS*](https://www.datasciencewithmarco.com/blog/all-about-n-hits-the-latest-breakthrough-in-time-series-forecasting), +applies N-HiTS to exactly this problem: forecast the last **120 steps** (five +days) of the hourly **Interstate 94 Westbound** traffic-volume series, scored by +MAE. This example runs the *same* dataset, the *same* `[:-120]` / `[-120:]` +split, and the *same* metric — but with `NNS.ARMA` instead of a neural network: +**no training loop, no hyperparameter search**, seasonal periods discovered +directly from the data by `nns_seas`. + +## Result + +![NNS traffic forecast](nhits_traffic_forecast.png) + +``` +MAE on traffic data: 236.16791666666677 +``` + +Head-to-head on the identical series, split, and metric (deep-learning numbers +from the article): + +| Model | MAE | Beats the naive baseline? | +|-------|----:|:--:| +| **NNS.ARMA.optim** | **236** | ✅ | +| Naive seasonal (weekly, K=168) | 249 | — | +| N-HiTS | 266 | ❌ | +| N-BEATS | worse than N-HiTS | ❌ | + +The article's punchline is that **neither N-HiTS nor N-BEATS beats the simple +naive seasonal baseline** on this series ("the baseline still outperforms +N-HiTS"). NNS is the only method here that does — an MAE of **236** versus the +baseline's 249 and N-HiTS's 266 — and it gets there in seconds with none of the +training machinery a neural model needs. (In fairness, the article notes this is +a small, repetitive sample, which is partly why the baseline is so strong; the +point is that NNS clears a bar the neural models did not.) + +## Run it + +```bash +pip install ovvo-nns pandas numpy scikit-learn matplotlib +python nhits_traffic.py +``` + +Writes `nhits_traffic_forecast.png` next to the script. Data is the hourly +traffic-volume series from the +[OVVO-Financial/NNS](https://github.com/OVVO-Financial/NNS) repo. + +## How it works + +```python +seas_result = NNS.nns_seas(train_set, plot=False) +periods = seas_result.get("periods", []) + +nns_estimates = NNS.nns_arma_optim( + variable=train_set, + h=120, + seasonal_factor=periods, + obj_fn=lambda predicted, actual: np.mean(np.abs(predicted - actual)), + objective="min", + negative_values=False, +) +``` + +`nns_seas` finds the candidate seasonal periods; `nns_arma_optim` cross-validates +combinations of them and forecasts `h=120` steps ahead. The `obj_fn` is set to +MAE so the optimizer targets the same metric the benchmark reports. (`nns_arma_optim` +silently discards any periods too long to estimate, so the full `nns_seas` list can +be passed straight in.) + +--- + +N-HiTS background from Marco Peixeiro's +[*All About N-HiTS*](https://www.datasciencewithmarco.com/blog/all-about-n-hits-the-latest-breakthrough-in-time-series-forecasting) +and the [original paper](https://arxiv.org/abs/2201.12886) (Challu, Olivares, et al.). diff --git a/gists/timeseries/nhits/nhits_traffic.py b/gists/timeseries/nhits/nhits_traffic.py new file mode 100644 index 00000000..2a2ef2a8 --- /dev/null +++ b/gists/timeseries/nhits/nhits_traffic.py @@ -0,0 +1,67 @@ +"""N-HiTS is a neural breakthrough -- NNS forecasts the same traffic series with no network. + +N-HiTS (Neural Hierarchical Interpolation for Time Series, AAAI 2023) is a deep +learning model -- multi-rate input sampling plus hierarchical interpolation, built +on N-BEATS -- that reports ~20% better long-horizon accuracy than Transformer +forecasters at a fraction of the compute. It still needs a GPU-era training loop. + +This script forecasts the last 120 steps of the hourly traffic-volume series with +NNS.ARMA -- no neural network, no training loop, no hyperparameter search -- and +lands a competitive MAE in a handful of lines. Seasonal periods are discovered +directly from the data by nns_seas. + +See: https://www.datasciencewithmarco.com/blog/all-about-n-hits-the-latest-breakthrough-in-time-series-forecasting +""" + +import matplotlib + +matplotlib.use("Agg") # headless; drop this line for an interactive window +import matplotlib.pyplot as plt +import numpy as np +import pandas as pd +from sklearn.metrics import mean_absolute_error + +import nns as NNS + +# Load data +url = ( + "https://raw.githubusercontent.com/OVVO-Financial/NNS/" + "Data-and-Simulation-Routines/Datasets/traffic_data.csv" +) +df = pd.read_csv(url) + +# Columns are 'date_time' and 'traffic_volume' +y = df["traffic_volume"].values + +# Train / test split -- hold out the last 120 points +train_set = y[:-120] +test_set = y[-120:] + +# Discover seasonal periods from the training data +seas_result = NNS.nns_seas(train_set, plot=False) +periods = seas_result.get("periods", []) + +# Optimize the seasonal ARMA combination (MAE objective to match the benchmark) +nns_estimates = NNS.nns_arma_optim( + variable=train_set, + h=120, + seasonal_factor=periods, + obj_fn=lambda predicted, actual: np.mean(np.abs(predicted - actual)), + objective="min", + plot=False, + negative_values=False, +) + +# Evaluate +mae = mean_absolute_error(test_set, nns_estimates["results"]) +print(f"MAE on traffic data: {mae}") + +# Plot actual vs. forecast +plt.figure(figsize=(10, 6)) +plt.plot(test_set, label="Actual Traffic", color="black", linewidth=2) +plt.plot(nns_estimates["results"], label="NNS Forecast", color="blue", linewidth=2) +plt.legend() +plt.title("Traffic Volume Forecast (Actual Data)") +plt.tight_layout() +plt.savefig("nhits_traffic_forecast.png", dpi=120) # so the README can show the figure +plt.show() diff --git a/gists/timeseries/nhits/nhits_traffic_forecast.png b/gists/timeseries/nhits/nhits_traffic_forecast.png new file mode 100644 index 00000000..2a38b98f Binary files /dev/null and b/gists/timeseries/nhits/nhits_traffic_forecast.png differ