moeforecast 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- moeforecast-0.1.0/.github/workflows/publish-pypi.yml +77 -0
- moeforecast-0.1.0/.github/workflows/publish-testpypi.yml +71 -0
- moeforecast-0.1.0/.github/workflows/tests.yml +25 -0
- moeforecast-0.1.0/.gitignore +14 -0
- moeforecast-0.1.0/LICENSE +21 -0
- moeforecast-0.1.0/PKG-INFO +262 -0
- moeforecast-0.1.0/README.md +221 -0
- moeforecast-0.1.0/docs/design.md +39 -0
- moeforecast-0.1.0/docs/release-process.md +50 -0
- moeforecast-0.1.0/examples/quickstart.ipynb +530 -0
- moeforecast-0.1.0/pyproject.toml +59 -0
- moeforecast-0.1.0/src/moeforecast/__init__.py +57 -0
- moeforecast-0.1.0/src/moeforecast/_plotting.py +35 -0
- moeforecast-0.1.0/src/moeforecast/analysis.py +267 -0
- moeforecast-0.1.0/src/moeforecast/classification.py +227 -0
- moeforecast-0.1.0/src/moeforecast/clustering.py +454 -0
- moeforecast-0.1.0/src/moeforecast/data.py +174 -0
- moeforecast-0.1.0/src/moeforecast/datasets.py +27 -0
- moeforecast-0.1.0/src/moeforecast/experts.py +294 -0
- moeforecast-0.1.0/src/moeforecast/features.py +157 -0
- moeforecast-0.1.0/src/moeforecast/gating.py +132 -0
- moeforecast-0.1.0/src/moeforecast/metrics.py +52 -0
- moeforecast-0.1.0/src/moeforecast/model_selection.py +39 -0
- moeforecast-0.1.0/src/moeforecast/persistence.py +32 -0
- moeforecast-0.1.0/src/moeforecast/pipeline.py +576 -0
- moeforecast-0.1.0/src/moeforecast/tuning.py +121 -0
- moeforecast-0.1.0/tests/conftest.py +9 -0
- moeforecast-0.1.0/tests/test_analysis.py +66 -0
- moeforecast-0.1.0/tests/test_clustering.py +69 -0
- moeforecast-0.1.0/tests/test_data.py +75 -0
- moeforecast-0.1.0/tests/test_features.py +31 -0
- moeforecast-0.1.0/tests/test_gating.py +42 -0
- moeforecast-0.1.0/tests/test_metrics.py +34 -0
- moeforecast-0.1.0/tests/test_pipeline.py +150 -0
- moeforecast-0.1.0/tests/test_tuning.py +75 -0
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
name: PyPI release
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [master, main]
|
|
6
|
+
paths:
|
|
7
|
+
- "pyproject.toml"
|
|
8
|
+
- "src/moeforecast/**"
|
|
9
|
+
- "README.md"
|
|
10
|
+
- "LICENSE"
|
|
11
|
+
|
|
12
|
+
permissions:
|
|
13
|
+
contents: read
|
|
14
|
+
|
|
15
|
+
jobs:
|
|
16
|
+
test:
|
|
17
|
+
name: Test release candidate
|
|
18
|
+
runs-on: ubuntu-latest
|
|
19
|
+
outputs:
|
|
20
|
+
stable: ${{ steps.version.outputs.stable }}
|
|
21
|
+
steps:
|
|
22
|
+
- uses: actions/checkout@v4
|
|
23
|
+
- uses: actions/setup-python@v5
|
|
24
|
+
with:
|
|
25
|
+
python-version: "3.12"
|
|
26
|
+
- name: Install package and development dependencies
|
|
27
|
+
run: python -m pip install -e ".[dev]"
|
|
28
|
+
- name: Lint and test
|
|
29
|
+
run: |
|
|
30
|
+
ruff check .
|
|
31
|
+
ruff format --check .
|
|
32
|
+
pytest -p no:warnings
|
|
33
|
+
- name: Check whether this is a stable release
|
|
34
|
+
id: version
|
|
35
|
+
run: |
|
|
36
|
+
python - <<'PY'
|
|
37
|
+
import os
|
|
38
|
+
from pathlib import Path
|
|
39
|
+
from packaging.version import Version
|
|
40
|
+
import re
|
|
41
|
+
|
|
42
|
+
source = Path("src/moeforecast/__init__.py").read_text()
|
|
43
|
+
match = re.search(r'__version__ = "([^"]+)"', source)
|
|
44
|
+
if not match:
|
|
45
|
+
raise SystemExit("Could not read moeforecast.__version__")
|
|
46
|
+
version = Version(match.group(1))
|
|
47
|
+
stable = not version.is_prerelease and not version.is_devrelease
|
|
48
|
+
with open(os.environ["GITHUB_OUTPUT"], "a") as output:
|
|
49
|
+
output.write(f"stable={str(stable).lower()}\n")
|
|
50
|
+
if not stable:
|
|
51
|
+
print(f"{version} is a development/pre-release version; skipping PyPI publication.")
|
|
52
|
+
else:
|
|
53
|
+
print(f"Stable version {version} is eligible for PyPI publication.")
|
|
54
|
+
PY
|
|
55
|
+
|
|
56
|
+
publish:
|
|
57
|
+
name: Publish stable package to PyPI
|
|
58
|
+
needs: test
|
|
59
|
+
if: needs.test.outputs.stable == 'true'
|
|
60
|
+
runs-on: ubuntu-latest
|
|
61
|
+
environment:
|
|
62
|
+
name: pypi
|
|
63
|
+
url: https://pypi.org/p/moeforecast
|
|
64
|
+
permissions:
|
|
65
|
+
contents: read
|
|
66
|
+
id-token: write
|
|
67
|
+
steps:
|
|
68
|
+
- uses: actions/checkout@v4
|
|
69
|
+
- uses: actions/setup-python@v5
|
|
70
|
+
with:
|
|
71
|
+
python-version: "3.12"
|
|
72
|
+
- name: Build distributions
|
|
73
|
+
run: |
|
|
74
|
+
python -m pip install --upgrade build
|
|
75
|
+
python -m build
|
|
76
|
+
- name: Publish to PyPI
|
|
77
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
name: TestPyPI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [release]
|
|
6
|
+
|
|
7
|
+
permissions:
|
|
8
|
+
contents: read
|
|
9
|
+
|
|
10
|
+
concurrency:
|
|
11
|
+
group: testpypi-release-branch
|
|
12
|
+
cancel-in-progress: false
|
|
13
|
+
|
|
14
|
+
jobs:
|
|
15
|
+
test:
|
|
16
|
+
name: Test release branch
|
|
17
|
+
runs-on: ubuntu-latest
|
|
18
|
+
steps:
|
|
19
|
+
- uses: actions/checkout@v4
|
|
20
|
+
- uses: actions/setup-python@v5
|
|
21
|
+
with:
|
|
22
|
+
python-version: "3.12"
|
|
23
|
+
- name: Install package and development dependencies
|
|
24
|
+
run: python -m pip install -e ".[dev]"
|
|
25
|
+
- name: Lint and test
|
|
26
|
+
run: |
|
|
27
|
+
ruff check .
|
|
28
|
+
ruff format --check .
|
|
29
|
+
pytest -p no:warnings
|
|
30
|
+
|
|
31
|
+
publish:
|
|
32
|
+
name: Publish a development build to TestPyPI
|
|
33
|
+
needs: test
|
|
34
|
+
runs-on: ubuntu-latest
|
|
35
|
+
environment:
|
|
36
|
+
name: testpypi
|
|
37
|
+
url: https://test.pypi.org/p/moeforecast
|
|
38
|
+
permissions:
|
|
39
|
+
contents: read
|
|
40
|
+
id-token: write
|
|
41
|
+
steps:
|
|
42
|
+
- uses: actions/checkout@v4
|
|
43
|
+
- uses: actions/setup-python@v5
|
|
44
|
+
with:
|
|
45
|
+
python-version: "3.12"
|
|
46
|
+
- name: Set a unique TestPyPI version
|
|
47
|
+
env:
|
|
48
|
+
RUN_NUMBER: ${{ github.run_number }}
|
|
49
|
+
run: |
|
|
50
|
+
python - <<'PY'
|
|
51
|
+
import os
|
|
52
|
+
import re
|
|
53
|
+
from pathlib import Path
|
|
54
|
+
|
|
55
|
+
path = Path("src/moeforecast/__init__.py")
|
|
56
|
+
source = path.read_text()
|
|
57
|
+
match = re.search(r'__version__ = "(\d+\.\d+\.\d+)(?:\.dev\d+)?"', source)
|
|
58
|
+
if not match:
|
|
59
|
+
raise SystemExit("Expected __version__ in X.Y.Z or X.Y.Z.devN format")
|
|
60
|
+
version = f"{match.group(1)}.dev{os.environ['RUN_NUMBER']}"
|
|
61
|
+
path.write_text(re.sub(r'__version__ = "[^"]+"', f'__version__ = "{version}"', source, count=1))
|
|
62
|
+
print(f"Building TestPyPI version {version}")
|
|
63
|
+
PY
|
|
64
|
+
- name: Build distributions
|
|
65
|
+
run: |
|
|
66
|
+
python -m pip install --upgrade build
|
|
67
|
+
python -m build
|
|
68
|
+
- name: Publish to TestPyPI
|
|
69
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
70
|
+
with:
|
|
71
|
+
repository-url: https://test.pypi.org/legacy/
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
name: tests
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [release, master, main]
|
|
6
|
+
pull_request:
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
test:
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
strategy:
|
|
12
|
+
fail-fast: false
|
|
13
|
+
matrix:
|
|
14
|
+
python-version: ["3.10", "3.11", "3.12"]
|
|
15
|
+
steps:
|
|
16
|
+
- uses: actions/checkout@v4
|
|
17
|
+
- uses: actions/setup-python@v5
|
|
18
|
+
with:
|
|
19
|
+
python-version: ${{ matrix.python-version }}
|
|
20
|
+
- name: Install
|
|
21
|
+
run: pip install -e ".[dev]"
|
|
22
|
+
- name: Lint
|
|
23
|
+
run: ruff check . && ruff format --check .
|
|
24
|
+
- name: Test
|
|
25
|
+
run: pytest -p no:warnings
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Sebastián Torres
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: moeforecast
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Mixture-of-Experts time series forecasting with regime clustering and adaptive gating, with a scikit-learn style API.
|
|
5
|
+
Project-URL: Homepage, https://github.com/astorress/moeforecast
|
|
6
|
+
Project-URL: Issues, https://github.com/astorress/moeforecast/issues
|
|
7
|
+
Author-email: Sebastián Torres <sebastiantorres8800@gmail.com>
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Keywords: clustering,forecasting,load forecasting,mixture of experts,scikit-learn,time series
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Intended Audience :: Science/Research
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
18
|
+
Requires-Python: >=3.10
|
|
19
|
+
Requires-Dist: joblib>=1.3
|
|
20
|
+
Requires-Dist: matplotlib>=3.7
|
|
21
|
+
Requires-Dist: numpy>=1.24
|
|
22
|
+
Requires-Dist: pandas>=2.0
|
|
23
|
+
Requires-Dist: scikit-learn>=1.3
|
|
24
|
+
Requires-Dist: statsmodels>=0.14
|
|
25
|
+
Provides-Extra: all
|
|
26
|
+
Requires-Dist: holidays>=0.40; extra == 'all'
|
|
27
|
+
Requires-Dist: lightgbm>=4.0; extra == 'all'
|
|
28
|
+
Provides-Extra: dev
|
|
29
|
+
Requires-Dist: holidays>=0.40; extra == 'dev'
|
|
30
|
+
Requires-Dist: lightgbm>=4.0; extra == 'dev'
|
|
31
|
+
Requires-Dist: pytest-cov>=4.1; extra == 'dev'
|
|
32
|
+
Requires-Dist: pytest>=7.4; extra == 'dev'
|
|
33
|
+
Requires-Dist: ruff>=0.5; extra == 'dev'
|
|
34
|
+
Provides-Extra: dtw
|
|
35
|
+
Requires-Dist: tslearn>=0.6.3; extra == 'dtw'
|
|
36
|
+
Provides-Extra: holidays
|
|
37
|
+
Requires-Dist: holidays>=0.40; extra == 'holidays'
|
|
38
|
+
Provides-Extra: lightgbm
|
|
39
|
+
Requires-Dist: lightgbm>=4.0; extra == 'lightgbm'
|
|
40
|
+
Description-Content-Type: text/markdown
|
|
41
|
+
|
|
42
|
+
# moeforecast
|
|
43
|
+
|
|
44
|
+
**Mixture-of-Experts time series forecasting with a scikit-learn style API.**
|
|
45
|
+
|
|
46
|
+
`moeforecast` builds a forecaster that learns the *regimes* of a series and trains one specialised model (an *expert*) per regime. A regime is, for example, a type of day (working day, weekend, holiday) or a type of week. A gating network then combines the experts at prediction time. The method comes from hourly electricity demand forecasting, but it applies to any regularly sampled series with recurring seasonal profiles.
|
|
47
|
+
|
|
48
|
+
> Status: pre-alpha (0.1.0.dev0). The API may change.
|
|
49
|
+
|
|
50
|
+
```python
|
|
51
|
+
import pandas as pd
|
|
52
|
+
from moeforecast import prepare_series, temporal_train_test_split
|
|
53
|
+
|
|
54
|
+
df = pd.read_csv("load.csv") # your data
|
|
55
|
+
y = prepare_series(df, target="load", time_col="timestamp") # regular, sorted series
|
|
56
|
+
|
|
57
|
+
train, test = temporal_train_test_split(y, test_size="2025-01-01")
|
|
58
|
+
# Continue with the analysis and cluster-selection workflow below.
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## What the library does (and does not do)
|
|
62
|
+
|
|
63
|
+
`moeforecast` gives you the tools to **build** the model and to choose its configuration, like scikit-learn does. Benchmarking against other models, statistical comparison tests and robustness studies are left to the user.
|
|
64
|
+
|
|
65
|
+
| Step | Tool | What it gives you |
|
|
66
|
+
|---|---|---|
|
|
67
|
+
| 1. Prepare the data | `prepare_series`, `fill_gaps` | Sorted series on a regular grid; warns about gaps and never imputes silently |
|
|
68
|
+
| 2. Analyse the series | `SeriesAnalyzer` | Suggested seasonal periods and lags (ACF/PACF); ACF/PACF, seasonal-profile and MSTL plots |
|
|
69
|
+
| 3. Build features | `FeatureBuilder` | Causal calendar, Fourier, holiday (any country), lag and rolling features. The same code serves training, prediction and forecasting |
|
|
70
|
+
| 4. Rank features | `feature_relevance` | Mutual information or permutation importance |
|
|
71
|
+
| 5. Find regimes | `ProfileClusterer` | Daily, weekly or monthly profile diagnostics, distance choice, silhouette plots and explicit selection of k |
|
|
72
|
+
| 6. Predict regimes | `RegimeClassifier`, `compare_classifiers` | Regime probabilities for the gating; time-ordered comparison of classifiers |
|
|
73
|
+
| 7. Train experts | `ExpertSelector` | Best candidate model per regime (forward-chaining CV); ten default regressors, user additions/removals and MAE/RMSE/MAPE selection |
|
|
74
|
+
| 8. Combine experts | `HardGating`, `SoftGating`, `AdaptiveGating` | Expert weights per time step |
|
|
75
|
+
| 9. All together | `MoEForecaster` | `fit`, `predict`, `predict_with_metadata`, `expert_weights`, `forecast` |
|
|
76
|
+
| 10. Persist | `save`, `load` | Serialization with version check |
|
|
77
|
+
| Metrics | `moeforecast.metrics` | MAE, RMSE, MAPE, MASE |
|
|
78
|
+
|
|
79
|
+
## Installation
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
pip install "moeforecast[all]" # with LightGBM and public holidays
|
|
83
|
+
pip install "moeforecast[dtw]" # optional DTW profile clustering
|
|
84
|
+
pip install moeforecast # core only: numpy, pandas, scikit-learn, statsmodels, matplotlib
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Not on PyPI yet. Install from source:
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
git clone https://github.com/astorress/moeforecast.git
|
|
91
|
+
cd moeforecast
|
|
92
|
+
pip install -e ".[dev]"
|
|
93
|
+
pytest
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## Step by step
|
|
97
|
+
|
|
98
|
+
```python
|
|
99
|
+
from moeforecast import (
|
|
100
|
+
SeriesAnalyzer,
|
|
101
|
+
FeatureBuilder,
|
|
102
|
+
feature_relevance,
|
|
103
|
+
ProfileClusterer,
|
|
104
|
+
compare_classifiers,
|
|
105
|
+
ExpertSelector,
|
|
106
|
+
AdaptiveGating,
|
|
107
|
+
MoEForecaster,
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
# 1. Which periods and lags matter?
|
|
111
|
+
analysis = SeriesAnalyzer().fit(train)
|
|
112
|
+
print(analysis.summary()) # e.g. periods [24, 168], lags [1, 2, 23, 24, 25, 167, 168, 169]
|
|
113
|
+
analysis.plot_acf_pacf()
|
|
114
|
+
|
|
115
|
+
# 2. Features and their relevance
|
|
116
|
+
features = FeatureBuilder(lags=analysis.suggested_lags_, fourier_periods=(24, 168), holidays_country="CO")
|
|
117
|
+
X = features.fit(train).transform(train)
|
|
118
|
+
feature_relevance(X, train, method="permutation").head(15)
|
|
119
|
+
|
|
120
|
+
# 3. Inspect cluster-validation metrics before choosing the cluster count.
|
|
121
|
+
days = ProfileClusterer("D", name="day", k_range=range(2, 11))
|
|
122
|
+
days.evaluate(train)
|
|
123
|
+
days.plot_scores()
|
|
124
|
+
days.plot_silhouette() # inspect all candidate k values before deciding
|
|
125
|
+
days.set_params(n_clusters=3).fit(train) # choose k after reviewing the scores
|
|
126
|
+
days.plot_profiles()
|
|
127
|
+
|
|
128
|
+
weeks = ProfileClusterer("W", name="week", k_range=range(2, 11))
|
|
129
|
+
weeks.evaluate(train)
|
|
130
|
+
weeks.plot_scores()
|
|
131
|
+
weeks.set_params(n_clusters=2).fit(train)
|
|
132
|
+
|
|
133
|
+
# 4. Can the regimes be predicted from the features?
|
|
134
|
+
compare_classifiers(X, days.labels_for(train.index))
|
|
135
|
+
|
|
136
|
+
# 5. The full model with the chosen configuration
|
|
137
|
+
model = MoEForecaster(
|
|
138
|
+
features=features,
|
|
139
|
+
clusterers=[days, weeks],
|
|
140
|
+
selector=ExpertSelector(metric="mae", search="random", n_iter=20),
|
|
141
|
+
gating=AdaptiveGating(threshold=0.75),
|
|
142
|
+
).fit(train)
|
|
143
|
+
|
|
144
|
+
model.selector_.results_ # every candidate's CV score per expert
|
|
145
|
+
report = model.inspect()
|
|
146
|
+
report["expert_hyperparameter_searches"] # every tried parameter set and its CV score
|
|
147
|
+
pred, meta = model.predict_with_metadata(y)
|
|
148
|
+
meta["mode"].value_counts() # hard vs. soft gating decisions
|
|
149
|
+
|
|
150
|
+
# Tune experts and classifiers by forward-chaining CV ("grid" is also supported).
|
|
151
|
+
# Override any named default with your own sklearn parameter distributions.
|
|
152
|
+
selector = ExpertSelector(
|
|
153
|
+
search="random",
|
|
154
|
+
n_iter=30,
|
|
155
|
+
search_spaces={
|
|
156
|
+
"random_forest": {
|
|
157
|
+
"n_estimators": [100, 250, 500],
|
|
158
|
+
"max_depth": [None, 8, 16],
|
|
159
|
+
"min_samples_leaf": [1, 3, 5],
|
|
160
|
+
}
|
|
161
|
+
},
|
|
162
|
+
)
|
|
163
|
+
model = MoEForecaster(
|
|
164
|
+
features=features,
|
|
165
|
+
clusterers=[days, weeks],
|
|
166
|
+
selector=selector,
|
|
167
|
+
classifier_search="grid",
|
|
168
|
+
).fit(train)
|
|
169
|
+
|
|
170
|
+
# Optimize gating on data not used by fit; review scores before using the setting.
|
|
171
|
+
gating_scores = model.tune_gating(validation, search="grid", metric="mae")
|
|
172
|
+
gating_scores.head()
|
|
173
|
+
|
|
174
|
+
# Export an image of the actual fitted architecture.
|
|
175
|
+
model.plot_model("outputs/moe-architecture.png")
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
## Inspect every fitted component
|
|
179
|
+
|
|
180
|
+
The fitted estimators are not hidden behind the forecaster. `inspect()` returns
|
|
181
|
+
the actual scikit-learn objects together with copied diagnostic tables:
|
|
182
|
+
|
|
183
|
+
```python
|
|
184
|
+
report = model.inspect()
|
|
185
|
+
|
|
186
|
+
# Cluster assignments, candidate-k scores, source profiles and cluster means
|
|
187
|
+
report["clusters"]["day"]["summary"]
|
|
188
|
+
report["clusters"]["day"]["scores"]
|
|
189
|
+
report["clusters"]["day"]["assignments"]
|
|
190
|
+
report["clusters"]["day"]["mean_profiles"]
|
|
191
|
+
|
|
192
|
+
# Fitted classifier and its input features / class counts
|
|
193
|
+
classifier = report["classifiers"]["day"]
|
|
194
|
+
print(classifier["estimator"])
|
|
195
|
+
classifier["features"], classifier["class_counts"]
|
|
196
|
+
|
|
197
|
+
# Every candidate's time-series CV results and each final fitted expert
|
|
198
|
+
report["expert_selection"]
|
|
199
|
+
for expert_name, estimator in report["experts"].items():
|
|
200
|
+
print(expert_name, estimator)
|
|
201
|
+
|
|
202
|
+
# Candidate model templates, fitted feature builder, and gating estimator
|
|
203
|
+
report["candidate_experts"]
|
|
204
|
+
report["configuration"]["features"].get_params()
|
|
205
|
+
report["gating"].get_params()
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
`inspect()` also exposes the full `ProfileClusterer` objects and fitted k-means
|
|
209
|
+
estimators. `RegimeClassifier.estimator_`, `ExpertSelector.experts_`,
|
|
210
|
+
`ExpertSelector.candidate_estimators_`, `ProfileClusterer.labels_`,
|
|
211
|
+
`ProfileClusterer.scores_`, and `ProfileClusterer.mean_profiles_` remain
|
|
212
|
+
available directly for users who prefer to inspect individual components.
|
|
213
|
+
|
|
214
|
+
See the [quickstart notebook](examples/quickstart.ipynb) for a complete,
|
|
215
|
+
synthetic-data walkthrough of the public modeling API, including diagnostics,
|
|
216
|
+
regime classifiers, all gating strategies, model inspection, forecasting and
|
|
217
|
+
persistence.
|
|
218
|
+
|
|
219
|
+
HPO uses forward-chaining folds. ``ExpertSelector(search="grid"|"random")``
|
|
220
|
+
searches each candidate's parameter space independently inside each regime,
|
|
221
|
+
then compares the tuned candidates and keeps the best estimator for that expert.
|
|
222
|
+
Defaults are available for built-in model names through
|
|
223
|
+
``default_search_spaces()``; pass ``search_spaces`` to replace or extend them.
|
|
224
|
+
``MoEForecaster(classifier_search=...)`` tunes its regime classifiers.
|
|
225
|
+
``tune_gating`` requires a later validation series that was not used for fitting;
|
|
226
|
+
it searches adaptive-gating confidence thresholds, minimum expert weights and
|
|
227
|
+
probability temperature. All fitted HPO objects and per-setting scores remain
|
|
228
|
+
inspectable through ``inspect()`` and the component attributes.
|
|
229
|
+
``plot_model(path)`` saves a diagram showing the fitted clusters, classifiers,
|
|
230
|
+
experts and gating component.
|
|
231
|
+
|
|
232
|
+
## Design
|
|
233
|
+
|
|
234
|
+
These principles prevent the errors that are easy to make when a Mixture of Experts is built by hand:
|
|
235
|
+
|
|
236
|
+
- **No look-ahead.** Every feature at time *t* uses only values before *t*. `fit` only sees the training series. The classifier comparison and the expert selection use forward-chaining (time-ordered) cross-validation.
|
|
237
|
+
- **One feature function.** `FeatureBuilder.transform` is used for training, prediction and recursive forecasting, so there is no train/serve skew. Fourier terms are anchored to a fixed origin, not to where the data starts.
|
|
238
|
+
- **Typed expert identifiers.** An expert is a tuple of regime labels, e.g. `(0, 1)`, used everywhere. `"day0-week1"` is only a display name.
|
|
239
|
+
- **Deterministic choices.** Cluster labels are ordered by level and ties are broken by candidate order. Selections come from the stated metric.
|
|
240
|
+
|
|
241
|
+
[docs/design.md](docs/design.md) explains each decision.
|
|
242
|
+
|
|
243
|
+
## Release process
|
|
244
|
+
|
|
245
|
+
Development builds are published to TestPyPI from the `release` branch. Stable
|
|
246
|
+
versions are published to PyPI only after merging to `master` or `main`. See
|
|
247
|
+
[the release guide](docs/release-process.md) for the full workflow and the
|
|
248
|
+
one-time Trusted Publisher setup.
|
|
249
|
+
|
|
250
|
+
## Roadmap
|
|
251
|
+
|
|
252
|
+
- Deep-learning experts (LSTM/GRU) as an optional extra (`moeforecast[dl]`).
|
|
253
|
+
- DTW and k-Shape clustering (optional `tslearn` extra).
|
|
254
|
+
- HTML report of the analysis and the fitted model.
|
|
255
|
+
|
|
256
|
+
## Citation
|
|
257
|
+
|
|
258
|
+
If you use `moeforecast` in research, please cite the accompanying article (reference to be added).
|
|
259
|
+
|
|
260
|
+
## License
|
|
261
|
+
|
|
262
|
+
[MIT](LICENSE)
|