Skip to content

Commit 944c625

Browse files
authored
update benchmark
1 parent 1d1324b commit 944c625

82 files changed

Lines changed: 5605 additions & 788 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/lint.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ jobs:
1818
with:
1919
python-version: 3.8
2020
- name: Install Black
21-
run: pip install black[jupyter]
21+
run: pip install "black[jupyter]==24.8.0"
2222
- name: Run Black
2323
run: black --check .
2424

.github/workflows/pypi_release.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,11 @@ jobs:
2323
- name: Install poetry
2424
shell: bash
2525
run: |
26-
curl -sSL https://install.python-poetry.org | python3 -
26+
curl -sSL https://install.python-poetry.org | python3 - --version 1.8.5
2727
python -m pip install poetry-dynamic-versioning[plugin]
2828
2929
- name: Set poetry path variable
30-
run: echo "/Users/runner/.local/bin" >> $GITHUB_PATH
30+
run: echo "$HOME/.local/bin" >> "$GITHUB_PATH"
3131

3232
- name: Build
3333
run: |

.github/workflows/test.yml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ jobs:
2121
exclude:
2222
- python-version: 3.9
2323
tf-version: 2.13.1
24+
- os: macOS-latest
25+
tf-version: 2.13.1
2426

2527
steps:
2628
- uses: actions/checkout@v4
@@ -37,9 +39,9 @@ jobs:
3739
- name: Install poetry
3840
shell: bash
3941
run: |
40-
curl -sSL https://install.python-poetry.org | python3 -
42+
curl -sSL https://install.python-poetry.org | python3 - --version 1.8.5
4143
- name: Set poetry path variable
42-
run: echo "/Users/runner/.local/bin" >> $GITHUB_PATH
44+
run: echo "$HOME/.local/bin" >> "$GITHUB_PATH"
4345

4446
- name: Configure poetry
4547
shell: bash

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,3 +96,4 @@ coverage.xml
9696
!/weights/.gitkeep
9797
CLAUDE.md
9898
temp/
99+
*.keras

.pre-commit-config.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ repos:
1919
hooks:
2020
- id: isort
2121
- repo: https://github.com/psf/black
22-
rev: 22.3.0
22+
rev: 24.8.0
2323
hooks:
2424
- id: black
2525
- repo: https://github.com/nbQA-dev/nbQA

benchmark/README.md

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
# TFTS Benchmark System
2+
3+
A flexible benchmarking framework for evaluating TFTS models across multiple datasets with multiple metrics and multiple runs. Designed for reproducibility and paper-quality results.
4+
5+
## Usage
6+
7+
### Command-Line
8+
9+
```bash
10+
python -m benchmark.cli \
11+
--models rnn transformer dlinear \
12+
--datasets sine air_passengers \
13+
--metrics mae rmse mape \
14+
--runs 3 \
15+
--epochs 50 \
16+
--output-dir results/
17+
```
18+
19+
### Python API
20+
21+
```python
22+
from benchmark import BenchmarkConfig, BenchmarkRunner
23+
24+
config = BenchmarkConfig(
25+
models=["rnn", "transformer", "dlinear"],
26+
datasets=["sine", "air_passengers"],
27+
metrics=["mae", "rmse", "mape"],
28+
runs=3,
29+
epochs=50,
30+
output_dir="benchmark_results",
31+
)
32+
33+
runner = BenchmarkRunner(config)
34+
results = runner.run()
35+
36+
results.print_table() # console
37+
results.to_csv("results.csv") # CSV
38+
results.to_latex("results.tex") # LaTeX for papers
39+
```
40+
41+
## Adding a New Dataset
42+
43+
```python
44+
from benchmark import Dataset
45+
import pandas as pd
46+
47+
class MyDataset(Dataset):
48+
name = "my_dataset"
49+
description = "Description of my dataset"
50+
train_length = 24
51+
predict_sequence_length = 8
52+
53+
def prepare_data(self, **kwargs):
54+
# Load your data from any source (CSV, DB, API, etc.)
55+
x, y = ...
56+
return x, y
57+
58+
def get_train_valid_split(self, **kwargs):
59+
x, y = self.prepare_data(**kwargs)
60+
# split into train/valid
61+
return (x_train, y_train), (x_valid, y_valid)
62+
```
63+
64+
Then register it:
65+
66+
```python
67+
from benchmark import BenchmarkRunner, DatasetRegistry
68+
69+
registry = DatasetRegistry()
70+
registry.register("my_dataset", MyDataset)
71+
72+
config = BenchmarkConfig(datasets=["my_dataset"], ...)
73+
runner = BenchmarkRunner(config, dataset_registry=registry)
74+
results = runner.run()
75+
```
76+
77+
## Architecture
78+
79+
- **BenchmarkRunner**: Orchestrates running models on datasets, collecting results.
80+
- **Dataset**: Abstract base; each dataset subclass implements `prepare_data()` and returns standardized format.
81+
- **DatasetRegistry**: Maintains a registry of all available datasets.
82+
- **ModelRegistry**: Wraps existing tfts model mapping.
83+
- **BenchmarkMetrics**: Computes standard time-series metrics (MAE, MSE, RMSE, MAPE, etc.)
84+
- **BenchmarkResults**: Formats and exports results (CSV, JSON, LaTeX, console table).
85+
86+
## Metrics
87+
88+
Available metrics:
89+
- `mae`: Mean Absolute Error
90+
- `mse`: Mean Squared Error
91+
- `rmse`: Root Mean Squared Error
92+
- `mape`: Mean Absolute Percentage Error
93+
- `smape`: Symmetric MAPE
94+
- `r2`: R-squared
95+
96+
## Migrated Example Benchmarks
97+
98+
The previous `examples/benchmarks` tasks are available as registered datasets:
99+
100+
- `forecasting_sticker_sales`
101+
- `CMI_detect_sleep_states`
102+
103+
Both support `data_path` overrides through `per_dataset_config`. If the source
104+
CSV is not available, they generate deterministic placeholder data so the
105+
benchmark runner and CLI remain usable without Kaggle downloads.
106+
107+
## Output Files
108+
109+
After running, the following files are generated in `output_dir`:
110+
- `results.json`: Raw results for each run.
111+
- `results.csv`: Averaged results (mean/std per model-dataset).
112+
- `results.tex`: LaTeX table for papers.

benchmark/__init__.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
"""TFTS Benchmark System.
2+
3+
A flexible benchmarking framework for evaluating TFTS modelsacross multiple
4+
datasets with multiple metrics and multiple runs. Designed for reproducibility
5+
and paper-quality results.
6+
7+
Example::
8+
9+
from benchmark import BenchmarkRunner, BenchmarkConfig
10+
from benchmark.datasets import GrocerysalesDataset, RecruitRestaurantDataset
11+
12+
config = BenchmarkConfig(
13+
models=["rnn", "transformer", "dlinear"],
14+
datasets=["grocery_sales", "recruit_restaurant"],
15+
metrics=["mae", "rmse", "mape"],
16+
runs=5,
17+
output_dir="benchmark_results",
18+
)
19+
20+
runner = BenchmarkRunner(config)
21+
results = runner.run()
22+
results.to_latex("benchmark_results.tex")
23+
results.to_csv("benchmark_results.csv")
24+
"""
25+
26+
from benchmark.base import BenchmarkConfig, Dataset
27+
from benchmark.formatter import BenchmarkResults
28+
from benchmark.registry import DatasetRegistry, ModelRegistry
29+
from benchmark.runner import BenchmarkRunner
30+
31+
__all__ = [
32+
"BenchmarkRunner",
33+
"BenchmarkConfig",
34+
"BenchmarkResults",
35+
"Dataset",
36+
"DatasetRegistry",
37+
"ModelRegistry",
38+
]

benchmark/base.py

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
"""Base classes for the TFTS benchmark system."""
2+
3+
from abc import ABC, abstractmethod
4+
from dataclasses import dataclass, field
5+
import logging
6+
from typing import Any, Callable, Dict, List, Optional, Tuple, Union
7+
8+
import numpy as np
9+
import pandas as pd
10+
import tensorflow as tf
11+
12+
logger = logging.getLogger(__name__)
13+
14+
15+
@dataclass
16+
class BenchmarkConfig:
17+
"""Configuration for a benchmark run.
18+
19+
Attributes:
20+
models: List of model names to evaluate (e.g., ["rnn", "transformer"]).
21+
Use ``["all"]`` to run all registered models.
22+
datasets: List of dataset names to evaluate (e.g., ["sine", "grocery_sales"]).
23+
Use ``["all"]`` to run all registered datasets.
24+
metrics: List of metric names to compute.
25+
Available: ``"mae"``, ``"mse"``, ``"rmse"``, ``"mape"``, ``"smape"``, ``"r2"``.
26+
runs: Number of runs per model-dataset pair (for statistical significance).
27+
epochs: Number of training epochs per run.
28+
batch_size: Batch size for training.
29+
learning_rate: Learning rate for the optimizer.
30+
train_length: Lookback window length. If None, each dataset provides its own.
31+
predict_sequence_length: Forecast horizon. If None, each dataset provides its own.
32+
seed: Base seed. Each run uses ``seed + run_idx`` for reproducibility.
33+
output_dir: Directory to save results.
34+
save_models: Whether to save trained model weights.
35+
verbose: Verbosity level (0=silent, 1=progress, 2=detailed).
36+
device: Device to run on (e.g., ``"/gpu:0"`` or ``"/cpu:0"``).
37+
per_dataset_config: Optional per-dataset configuration overrides.
38+
Keys are dataset names, values are dicts with keys like ``train_length``,
39+
``predict_sequence_length``, ``epochs``, etc.
40+
"""
41+
42+
models: List[str] = field(default_factory=lambda: ["all"])
43+
datasets: List[str] = field(default_factory=lambda: ["all"])
44+
metrics: List[str] = field(default_factory=lambda: ["mae", "rmse", "mape"])
45+
runs: int = 1
46+
epochs: int = 50
47+
batch_size: int = 32
48+
learning_rate: float = 1e-3
49+
train_length: Optional[int] = None
50+
predict_sequence_length: Optional[int] = None
51+
seed: int = 42
52+
output_dir: str = "benchmark_results"
53+
save_models: bool = False
54+
verbose: int = 1
55+
device: str = ""
56+
per_dataset_config: Dict[str, Dict[str, Any]] = field(default_factory=dict)
57+
58+
def __post_init__(self):
59+
if self.runs < 1:
60+
raise ValueError("runs must be >= 1")
61+
if self.epochs < 1:
62+
raise ValueError("epochs must be >= 1")
63+
64+
def get_dataset_config(self, dataset_name: str) -> Dict[str, Any]:
65+
"""Get configuration for a specific dataset, merging with defaults."""
66+
config = {
67+
"epochs": self.epochs,
68+
"batch_size": self.batch_size,
69+
"learning_rate": self.learning_rate,
70+
"train_length": self.train_length,
71+
"predict_sequence_length": self.predict_sequence_length,
72+
}
73+
if dataset_name in self.per_dataset_config:
74+
config.update(self.per_dataset_config[dataset_name])
75+
return config
76+
77+
78+
class Dataset(ABC):
79+
"""Abstract base class for benchmark datasets.
80+
81+
Each dataset subclass implements ``prepare_data()`` to load and format
82+
the data. The class also provides metadata about the dataset.
83+
84+
Attributes:
85+
name: Unique identifier for the dataset.
86+
description: Human-readable description.
87+
train_length: Default lookback window length (can be overridden by config).
88+
predict_sequence_length: Default forecast horizon (can be overridden by config).
89+
"""
90+
91+
name: str = ""
92+
description: str = ""
93+
train_length: int = 24
94+
predict_sequence_length: int = 8
95+
num_features: int = 1
96+
is_multivariate: bool = False
97+
is_grouped: bool = False
98+
target_column: str = "target"
99+
time_column: str = "time"
100+
group_column: Optional[str] = None
101+
102+
@abstractmethod
103+
def prepare_data(self, **kwargs) -> Union[
104+
Tuple[np.ndarray, np.ndarray],
105+
Tuple[Tuple[np.ndarray, np.ndarray], Tuple[np.ndarray, np.ndarray]],
106+
tf.data.Dataset,
107+
]:
108+
"""Prepare and return the dataset.
109+
110+
Returns:
111+
Either (x_train, y_train), ((x_train, y_train), (x_valid, y_valid)),
112+
or a tf.data.Dataset.
113+
"""
114+
raise NotImplementedError
115+
116+
@abstractmethod
117+
def get_train_valid_split(self, **kwargs) -> Tuple[Tuple[np.ndarray, np.ndarray], Tuple[np.ndarray, np.ndarray]]:
118+
"""Return train and validation splits.
119+
120+
Returns:
121+
Tuple of (x_train, y_train), (x_valid, y_valid).
122+
"""
123+
raise NotImplementedError
124+
125+
@classmethod
126+
def list_params(cls) -> Dict[str, Any]:
127+
"""Return dataset parameters for display."""
128+
return {
129+
"name": cls.name,
130+
"description": cls.description,
131+
"train_length": cls.train_length,
132+
"predict_sequence_length": cls.predict_sequence_length,
133+
"num_features": cls.num_features,
134+
"is_multivariate": cls.is_multivariate,
135+
"is_grouped": cls.is_grouped,
136+
}

0 commit comments

Comments
 (0)