Skip to main content

A time-series forecasting model using UR2CUTE: Using Repetitively 2 CNN for Unsteady Timeseries Estimation

Project description

UR2CUTE

Using Repetitively 2 CNNs for Unsteady Timeseries Estimation. UR2CUTE is a dual-stage, PyTorch powered model dedicated to intermittent demand forecasting. A classifier estimates demand occurrence while a regressor predicts magnitude, allowing the library to focus on the sparse structure typical of slow-moving inventory.

What's New in 2.3

Quality-of-life and evaluation release — no modeling changes:

  • backtest(df, n_windows=5) — built-in rolling-origin evaluation with the standard intermittent-demand metrics (MASE, RMSSE, MAE, RMSE, bias), per window and summarized. Pass refit=True for fully out-of-sample refits per origin.
  • predict(df, return_components=True) — exposes the ungated occurrence probability and quantity per horizon step, for service-level decisions or custom gating.
  • predict(df, integer_output=False) — continuous (non-rounded) forecasts for fractional demand (kg, liters, ...).
  • Robustness: inference inputs outside the training range (record spikes, longer zero-gaps than ever seen) are now clipped instead of extrapolated blindly; a warning is raised when the series is too short for a leakage-free train/validation split (early stopping and the balanced threshold then tune on training data).
  • Model files now embed a format_version for forward-compatible migrations, and load_model documents the standard pickle security caveat.

Overview

Intermittent demand is dominated by long zero stretches punctuated by irregular spikes. Traditional statistical models struggle to capture both the timing and the size of those bursts. UR2CUTE tackles the problem with a hurdle-style architecture:

  1. A dilated-causal CNN classifier predicts the probability of non-zero demand for each step in the forecast horizon.
  2. A dilated-causal CNN regressor estimates the corresponding quantities.
  3. Final forecasts combine the two outputs through an adaptive threshold so the regressor only contributes when demand is likely.

Both CNNs consume the same multichannel temporal window — target history, optional covariates, and engineered intermittency channels — so the convolutions slide over a genuine time axis and capture long inter-demand gaps cheaply.

The estimator follows the scikit-learn API, includes thorough input validation, and automatically selects CPU or GPU devices.

Features

  • Pure PyTorch implementation with GPU support when available.
  • Direct multi-step forecasting: predicts the entire horizon in a single forward pass.
  • Multichannel temporal windows: target history, optional external covariates, and engineered intermittency channels (time-since-last-demand, causal rolling mean).
  • Dilated causal convolutions (TCN-style) so each CNN slides over a genuine time axis and cheaply covers long inter-demand gaps.
  • Customizable hyperparameters (epochs, batch size, independent learning rates, dropout).
  • Occurrence threshold modes: a fixed value, "auto" (fraction of zeros), or "balanced" (tuned on validation to drive the forecast bias toward zero).
  • Early stopping that restores the best weights, kept in memory during training.
  • Reproducible results through explicit random seed management.
  • Model persistence through save_model and load_model.
  • Built-in rolling-origin backtest with intermittent-demand metrics (MASE, RMSSE, bias), and access to ungated forecast components via predict(..., return_components=True).
  • Complete type hints and packaged type information (py.typed).

Dependencies

  • Python 3.7 or newer
  • PyTorch 1.7+
  • NumPy 1.20+ (for sliding_window_view)
  • pandas
  • scikit-learn

Installation

From PyPI

pip install UR2CUTE

From Source

git clone https://github.com/FH-Prevail/UR2CUTE_torch.git
cd UR2CUTE_torch
pip install -e .

# Optional extras
pip install -e ".[dev]"
pip install -e ".[test]"
pip install -e ".[docs]"

Verify Installation

from UR2CUTE import UR2CUTE
print(UR2CUTE.__module__)

Quick Start

import pandas as pd
import torch
from UR2CUTE import UR2CUTE

data = pd.DataFrame(
    {
        "date": pd.date_range("2023-01-01", periods=50, freq="W"),
        "target": [0, 5, 0, 0, 12, 0, 0, 0, 7, 0] * 5,
        "promo": [0, 1, 0, 0, 1, 0, 0, 1, 0, 0] * 5,
        "price": [10.0, 9.5, 9.5, 9.5, 10.0, 10.0, 10.0, 9.8, 9.8, 9.8] * 5,
    }
)

device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
print(f"Using device: {device}")

model = UR2CUTE(
    n_steps_lag=12,
    forecast_horizon=4,
    external_features=["promo", "price"],
    threshold="balanced",
)
model.fit(data, target_col="target")
print(model.predict(data))

Parameters

Parameter Description Default
n_steps_lag Length of the lookback window (past time steps each CNN sees). 12
forecast_horizon Number of future periods predicted per call. 8
external_features Optional list of column names used as exogenous inputs. None
epochs Training epochs for both CNN models. 100
batch_size Training batch size. 32
threshold Occurrence cutoff: a float, "auto" (fraction of zeros), or "balanced" (tuned on validation to minimize forecast bias). "balanced"
patience Early stopping patience (epochs). 10
random_seed Global random seed applied to NumPy, Python, and PyTorch. 42
classification_lr Learning rate for the classifier. 0.0021
regression_lr Learning rate for the regressor. 0.0021
dropout_classification Dropout applied inside the classifier. 0.4
dropout_regression Dropout applied inside the regressor. 0.2
regressor_nonzero_only When True, the regressor is trained only on sequences where the forecast horizon contains at least one non-zero value. Set to False to train the regressor on all sequences. True
verbose Enables progress output and early-stopping logs. False

Usage Patterns

Auto Threshold

model = UR2CUTE(threshold="auto")
model.fit(df, "target")
print(model.threshold_)

External Features

covariates = ["promotion", "price", "weekday"]
model = UR2CUTE(external_features=covariates)
model.fit(df, "target")

Progress Logging

Training is silent by default; pass verbose=True for per-epoch loss and early-stopping logs.

model = UR2CUTE(verbose=True)
model.fit(df, "target")

Forecast Components (probability & quantity)

out = model.predict(df, return_components=True)
out["forecast"]     # gated forecast (same as plain predict)
out["probability"]  # P(demand > 0) per horizon step, before the threshold
out["quantity"]     # regressor magnitude per step, before gating/rounding

Continuous Demand

preds = model.predict(df, integer_output=False)  # no rounding (kg, liters, ...)

Rolling-Origin Backtest

result = model.backtest(df, n_windows=5)      # reuse the fitted model (fast)
print(result["summary"])                       # mean MASE, RMSSE, MAE, RMSE, bias
print(result["windows"])                       # one row per origin

result = model.backtest(df, n_windows=5, refit=True)  # refit per origin (honest, slow)

Model Persistence

trained = UR2CUTE().fit(train_df, "target")
trained.save_model("production_model.pkl")

loaded = UR2CUTE.load_model("production_model.pkl")
preds = loaded.predict(new_df)

How It Works

  1. Preprocessing – validates the input frame, builds the multichannel feature matrix (target + covariates + engineered intermittency channels), slides it into multi-step temporal windows, and splits chronologically into train and validation partitions.
  2. Scaling – fits per-channel min-max scaling on the training windows only, and applies a log1p transform to non-negative targets to tame spikes before scaling, preventing validation leakage.
  3. Classification Stage – trains a dilated-causal CNN with BCEWithLogitsLoss and per-step pos_weight to estimate the probability of demand for each future horizon step under heavy zero-class imbalance.
  4. Regression Stage – trains a dilated-causal CNN regressor with a Huber (SmoothL1) loss, robust to demand spikes. By default (regressor_nonzero_only=True) only sequences where the horizon contains at least one non-zero value are used, keeping the regressor focused on demand magnitude; if no such sequences exist it falls back to the full dataset. Set regressor_nonzero_only=False to train on all sequences instead.
  5. Inference – transforms the latest observed window, runs both networks, rescales quantities, and zeros out forecasts whose occurrence probability falls below the stored threshold.

Performance

Internal benchmarks show UR2CUTE outperforming Croston, AutoARIMA, Prophet, gradient boosted trees, and random forests on sparse demand series, especially in MAE% and RMSE%. Improvements stem from the dedicated occurrence model, the multichannel temporal window with engineered intermittency signals, and the dilated causal filters tuned to each dataset.

Changelog

2.2 — better defaults

Informed by benchmarking on real intermittent data:

  • threshold now defaults to "balanced" (was 0.5). It tunes the occurrence cutoff on the validation split to drive forecast bias toward zero — the previous fixed/"auto" cutoffs systematically under-forecast. Adds no training cost.
  • verbose now defaults to False (was True), which suits fitting many series in a loop. Pass verbose=True to restore per-epoch logging.

Both are behaviour changes for code relying on the old defaults; set the parameters explicitly to pin previous behaviour.

2.1 — bias-aware threshold

  • Added threshold="balanced": tunes the occurrence cutoff on the validation split to reduce the systematic under-forecast of fixed/"auto" cutoffs.

2.0 — architecture overhaul (breaking)

Version 2.0.0 keeps the two-CNN hurdle design but overhauls how the networks see the data:

  • Multichannel temporal windows replace the old flat lag vector. Each CNN receives (channels, window) where the window axis is genuine time: target history, optional covariates, and engineered intermittency channels (time-since-last-demand, causal rolling mean).
  • Dilated causal convolutions (TCN-style) so the convolutions slide over consecutive time steps and cover long inter-demand gaps cheaply.
  • Better training objectives: BCEWithLogitsLoss with per-step pos_weight for the classifier and a Huber (SmoothL1) loss for the regressor.
  • Smarter scaling: per-channel min-max plus an automatic log1p transform on non-negative targets.

Migration notes: models saved with 1.x cannot be loaded in 2.x — retrain and re-save. The default n_steps_lag changed from 3 to 12 (it is now a lookback window, not a count of lag columns).

Citation

@article{mirshahi2024intermittent,
  title={Intermittent Time Series Demand Forecasting Using Dual Convolutional Neural Networks},
  author={Mirshahi, Sina and Brandtner, Patrick and Kominkova Oplatkova, Zuzana},
  journal={MENDEL -- Soft Computing Journal},
  volume={30},
  number={1},
  year={2024},
  publisher={MENDEL Journal}
}

License

UR2CUTE is released under the MIT License. See LICENSE for the full text.

Contributors

  • Sina Mirshahi
  • Patrick Brandtner
  • Zuzana Kominkova Oplatkova
  • Taha Falatouri
  • Mehran Naseri
  • Farzaneh Darbanian

Acknowledgments

This work was carried out at:

  • Department of Informatics and Artificial Intelligence, Tomas Bata University
  • Department for Logistics, University of Applied Sciences Upper Austria, Steyr
  • Josef Ressel-Centre for Predictive Value Network Intelligence, Steyr

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

ur2cute-2.3.1.tar.gz (25.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

ur2cute-2.3.1-py3-none-any.whl (19.4 kB view details)

Uploaded Python 3

File details

Details for the file ur2cute-2.3.1.tar.gz.

File metadata

  • Download URL: ur2cute-2.3.1.tar.gz
  • Upload date:
  • Size: 25.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.5

File hashes

Hashes for ur2cute-2.3.1.tar.gz
Algorithm Hash digest
SHA256 fdf91662bcb501582342031d9c8197fe40997c8539537c72bfc270895a226b3f
MD5 fb0507359cbe8d6f54a430aef5664278
BLAKE2b-256 2019b2dc26dab913cd0c9775561e9b96a582c8c9c32d7a8aa6efba385521e8a1

See more details on using hashes here.

File details

Details for the file ur2cute-2.3.1-py3-none-any.whl.

File metadata

  • Download URL: ur2cute-2.3.1-py3-none-any.whl
  • Upload date:
  • Size: 19.4 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.5

File hashes

Hashes for ur2cute-2.3.1-py3-none-any.whl
Algorithm Hash digest
SHA256 e473b82283b16ea62d69b98ad92fa2e7780f63cde4242292f623ab8123741af1
MD5 5f5cc35b294ccfeef1d30de49d3e6dff
BLAKE2b-256 633c7ead1f6d173a878f9cb710f2225f1e5668b1809ebfe67bb92a78919fb82e

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page