butchc 0.6.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.
- butchc-0.6.0/LICENSE +21 -0
- butchc-0.6.0/PKG-INFO +424 -0
- butchc-0.6.0/README.md +392 -0
- butchc-0.6.0/butchc/__init__.py +27 -0
- butchc-0.6.0/butchc/_parallel.py +91 -0
- butchc-0.6.0/butchc/_prune.py +381 -0
- butchc-0.6.0/butchc/_sampling.py +108 -0
- butchc-0.6.0/butchc/_tree.py +319 -0
- butchc-0.6.0/butchc/_update.py +288 -0
- butchc-0.6.0/butchc/_utils.py +207 -0
- butchc-0.6.0/butchc/_validate.py +440 -0
- butchc-0.6.0/butchc/interop/__init__.py +39 -0
- butchc-0.6.0/butchc/interop/_configspace.py +413 -0
- butchc-0.6.0/butchc/interop/_json.py +57 -0
- butchc-0.6.0/butchc/optimizer.py +386 -0
- butchc-0.6.0/butchc.egg-info/PKG-INFO +424 -0
- butchc-0.6.0/butchc.egg-info/SOURCES.txt +29 -0
- butchc-0.6.0/butchc.egg-info/dependency_links.txt +1 -0
- butchc-0.6.0/butchc.egg-info/requires.txt +6 -0
- butchc-0.6.0/butchc.egg-info/top_level.txt +1 -0
- butchc-0.6.0/pyproject.toml +49 -0
- butchc-0.6.0/setup.cfg +4 -0
- butchc-0.6.0/tests/test_batch.py +322 -0
- butchc-0.6.0/tests/test_constants.py +175 -0
- butchc-0.6.0/tests/test_interop.py +630 -0
- butchc-0.6.0/tests/test_optimizer.py +890 -0
- butchc-0.6.0/tests/test_prune.py +245 -0
- butchc-0.6.0/tests/test_sampling.py +178 -0
- butchc-0.6.0/tests/test_tree.py +459 -0
- butchc-0.6.0/tests/test_update.py +241 -0
- butchc-0.6.0/tests/test_utils.py +143 -0
butchc-0.6.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 BUTChC contributors
|
|
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.
|
butchc-0.6.0/PKG-INFO
ADDED
|
@@ -0,0 +1,424 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: butchc
|
|
3
|
+
Version: 0.6.0
|
|
4
|
+
Summary: Bayesian Update Tree Chained Conditionally — a dependency-free probabilistic black-box hyperparameter optimizer
|
|
5
|
+
License: MIT
|
|
6
|
+
Project-URL: Homepage, https://github.com/AdventuresInDataScience/BUTChC
|
|
7
|
+
Project-URL: Repository, https://github.com/AdventuresInDataScience/BUTChC
|
|
8
|
+
Project-URL: Documentation, https://github.com/AdventuresInDataScience/BUTChC/blob/main/docs/api.md
|
|
9
|
+
Project-URL: Changelog, https://github.com/AdventuresInDataScience/BUTChC/blob/main/CHANGELOG.md
|
|
10
|
+
Project-URL: Issues, https://github.com/AdventuresInDataScience/BUTChC/issues
|
|
11
|
+
Keywords: hyperparameter optimization,bayesian optimization,black-box optimization,tree search,machine learning
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Intended Audience :: Science/Research
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
22
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
23
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
24
|
+
Requires-Python: >=3.8
|
|
25
|
+
Description-Content-Type: text/markdown
|
|
26
|
+
License-File: LICENSE
|
|
27
|
+
Provides-Extra: test
|
|
28
|
+
Requires-Dist: pytest>=7; extra == "test"
|
|
29
|
+
Provides-Extra: configspace
|
|
30
|
+
Requires-Dist: ConfigSpace>=0.6; extra == "configspace"
|
|
31
|
+
Dynamic: license-file
|
|
32
|
+
|
|
33
|
+
# BUTChC
|
|
34
|
+
|
|
35
|
+
**B**ayesian **U**pdate **T**ree **Ch**ained **C**onditionally — a dependency-free, probabilistic black-box hyperparameter optimizer.
|
|
36
|
+
|
|
37
|
+
BUTChC maintains a probability distribution over a **hierarchical, conditional search space** and refines it from observed objective values. Parameters can depend on choices made higher up the tree: `momentum` exists only when `optimizer=sgd`, `kernel_size` only when `model=cnn`. Invalid combinations are unreachable by construction rather than discovered by trial, and each branch learns its own parameters from only the trials that used it.
|
|
38
|
+
|
|
39
|
+
No gradients, no differentiability, no assumptions about the objective's internals.
|
|
40
|
+
|
|
41
|
+
- **Zero dependencies** — Python ≥ 3.8 standard library only
|
|
42
|
+
- **Conditional search spaces** — nested arbitrarily deep via `next_level`
|
|
43
|
+
- **Non-parametric continuous model** — a weighted KDE elite archive, no distributional assumptions
|
|
44
|
+
- **Scale-invariant** — updates use rank, not raw objective, so an objective in the millions behaves like one in `[0, 1]`
|
|
45
|
+
- **Parallel evaluation** — bring your own executor; threads, processes, joblib, dask
|
|
46
|
+
- **Reproducible** — `seed` gives a private RNG, and the answer does not depend on which worker finishes first
|
|
47
|
+
- **Fails fast** — search spaces and hyperparameters are validated before the first objective call
|
|
48
|
+
- **Prunable** — `prune` turns a finished run into a smaller space, to search again or hand to another optimiser
|
|
49
|
+
|
|
50
|
+
Against TPE across 18 benchmark problems at 30 paired seeds: **13 significant wins, zero significant losses**, at **73–145× lower per-trial cost**.
|
|
51
|
+
|
|
52
|
+
📖 **[API reference](https://github.com/AdventuresInDataScience/BUTChC/blob/main/docs/api.md)** · **[Examples](https://github.com/AdventuresInDataScience/BUTChC/blob/main/docs/examples.md)** · **[Design notes](https://github.com/AdventuresInDataScience/BUTChC/blob/main/docs/design.md)** · **[Limitations](https://github.com/AdventuresInDataScience/BUTChC/blob/main/docs/limitations.md)** · **[Changelog](https://github.com/AdventuresInDataScience/BUTChC/blob/main/CHANGELOG.md)**
|
|
53
|
+
|
|
54
|
+
---
|
|
55
|
+
|
|
56
|
+
## Install
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
pip install butchc
|
|
60
|
+
pip install "butchc[configspace]" # optional ConfigSpace interop
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
## Quick start
|
|
66
|
+
|
|
67
|
+
```python
|
|
68
|
+
from butchc import BUTChC_optimize
|
|
69
|
+
|
|
70
|
+
searchspace = {
|
|
71
|
+
# Top-level choice of model family. Each choice unlocks its own
|
|
72
|
+
# sub-parameters via 'next_level'.
|
|
73
|
+
'model_type': {
|
|
74
|
+
'values': ['svm', 'random_forest', 'neural_net'],
|
|
75
|
+
'next_level': {
|
|
76
|
+
'svm': {
|
|
77
|
+
'kernel': {'values': ['rbf', 'linear', 'poly']},
|
|
78
|
+
'C': {'min': 0.01, 'max': 100.0, 'log': True},
|
|
79
|
+
'gamma': {'min': 1e-4, 'max': 10.0, 'log': True},
|
|
80
|
+
},
|
|
81
|
+
'random_forest': {
|
|
82
|
+
'n_estimators': {'values': [50, 100, 200, 500]},
|
|
83
|
+
'max_depth': {'min': 2, 'max': 30, 'int': True},
|
|
84
|
+
'max_features': {'values': ['sqrt', 'log2']},
|
|
85
|
+
},
|
|
86
|
+
'neural_net': {
|
|
87
|
+
'learning_rate': {'min': 1e-4, 'max': 1e-1, 'log': True},
|
|
88
|
+
'hidden_units': {'values': [64, 128, 256, 512]},
|
|
89
|
+
'dropout': {'min': 0.0, 'max': 0.5},
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
},
|
|
93
|
+
# Always present, whatever model_type is chosen
|
|
94
|
+
'preprocessing': {'values': ['standard_scaler', 'min_max', 'none']},
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
def objective(config):
|
|
98
|
+
# config carries 'model_type' and 'preprocessing', plus only the params
|
|
99
|
+
# of the chosen branch, e.g.
|
|
100
|
+
# {'model_type': 'svm', 'kernel': 'rbf', 'C': 4.2, 'gamma': 0.01, ...}
|
|
101
|
+
# 'dropout' never appears in an svm config; 'C' never in a neural_net one.
|
|
102
|
+
return cross_val_score(build_model(config), X, y).mean() # higher is better
|
|
103
|
+
|
|
104
|
+
results = BUTChC_optimize(
|
|
105
|
+
searchspace = searchspace,
|
|
106
|
+
objective = objective,
|
|
107
|
+
budget = 150,
|
|
108
|
+
seed = 0,
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
print(results['best_params'])
|
|
112
|
+
print(f"Best score: {results['best_value']:.4f}")
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
BUTChC always **maximizes**. Negate to minimize.
|
|
116
|
+
|
|
117
|
+
### Slow objective? Use an executor
|
|
118
|
+
|
|
119
|
+
```python
|
|
120
|
+
from concurrent.futures import ThreadPoolExecutor
|
|
121
|
+
|
|
122
|
+
with ThreadPoolExecutor(max_workers=8) as pool:
|
|
123
|
+
results = BUTChC_optimize(searchspace, objective, budget=200,
|
|
124
|
+
batch=8, executor=pool, seed=0)
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### Already have a ConfigSpace?
|
|
128
|
+
|
|
129
|
+
```python
|
|
130
|
+
results = BUTChC_optimize(configuration_space, objective, budget=200, seed=0)
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
---
|
|
134
|
+
|
|
135
|
+
## Defining a search space
|
|
136
|
+
|
|
137
|
+
A search space is a plain dict — JSON-serializable, and no imports needed to write one.
|
|
138
|
+
|
|
139
|
+
```python
|
|
140
|
+
'activation': {'values': ['relu', 'tanh', 'elu']} # categorical
|
|
141
|
+
'dropout': {'min': 0.0, 'max': 0.5} # linear
|
|
142
|
+
'learning_rate': {'min': 1e-5, 'max': 1e-1, 'log': True} # log10-uniform
|
|
143
|
+
'n_layers': {'min': 1, 'max': 6, 'int': True} # integer-valued
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
Use `log: True` whenever the range spans more than about one order of magnitude. Sampled linearly, `[1e-5, 1e-1]` places 99.99% of its mass above `1e-3`, leaving the bottom three decades effectively unreachable.
|
|
147
|
+
|
|
148
|
+
A categorical node can map each of its choices to a sub-searchspace via `next_level`. Those parameters are sampled and updated only when their parent value is chosen:
|
|
149
|
+
|
|
150
|
+
```python
|
|
151
|
+
'optimizer': {
|
|
152
|
+
'values': ['adam', 'sgd', 'lbfgs'],
|
|
153
|
+
'next_level': {
|
|
154
|
+
'adam': {'lr': {'min': 1e-4, 'max': 1e-2, 'log': True}},
|
|
155
|
+
'sgd': {'lr': {'min': 1e-3, 'max': 1e-1, 'log': True},
|
|
156
|
+
'momentum': {'min': 0.0, 'max': 0.99}},
|
|
157
|
+
# 'lbfgs' takes no sub-params — omitting it is fine
|
|
158
|
+
},
|
|
159
|
+
}
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
Each branch keeps its own model, so learning the best `lr` for adam does not interfere with learning the best `lr` for sgd. `next_level` is the only nesting mechanism and it composes, so nesting is arbitrarily deep — [a worked four-level space](https://github.com/AdventuresInDataScience/BUTChC/blob/main/docs/examples.md#nesting-more-than-one-level) shows how.
|
|
163
|
+
|
|
164
|
+
This is also how you express an invalid combination. "`penalty=elasticnet` only works with `solver=saga`" becomes a `solver` node whose branches carry different `penalty` values — the invalid pairing is then unreachable, and no budget is spent finding that out. Constraints that cross the tree rather than nest are the exception; see [limitations](https://github.com/AdventuresInDataScience/BUTChC/blob/main/docs/limitations.md#search-spaces-butchc-cannot-express).
|
|
165
|
+
|
|
166
|
+
Any node can carry a `prior` and a `prior_strength` measured in pseudo-trials. See the [API reference](https://github.com/AdventuresInDataScience/BUTChC/blob/main/docs/api.md#search-space-format) for the full format, priors, and the uniqueness rule for names.
|
|
167
|
+
|
|
168
|
+
---
|
|
169
|
+
|
|
170
|
+
## How it works
|
|
171
|
+
|
|
172
|
+
1. **Initialize** a probability tree from the search space. Categorical nodes start uniform; continuous nodes start with an evenly spaced reservoir covering the range.
|
|
173
|
+
|
|
174
|
+
2. **Sample** by traversing the tree. Categorical nodes draw from a temperature-scaled softmax. Continuous nodes pick a reservoir point weighted by its rank, add Silverman-bandwidth Gaussian jitter, and *reflect* back into range.
|
|
175
|
+
|
|
176
|
+
3. **Evaluate** the objective.
|
|
177
|
+
|
|
178
|
+
4. **Rank** the result against every finite objective seen so far, counting ties as half. Continuous nodes apply the `gamma` gate and convert what survives into a `quality` in `[0, 1]`; categorical nodes use the raw rank, ungated.
|
|
179
|
+
|
|
180
|
+
5. **Update**. Categorical nodes score each choice by its recency-weighted mean rank, smoothed by `alpha` pseudo-visits. Continuous nodes append to an elite archive, evict the worst-scoring entry, and reweight geometrically by rank.
|
|
181
|
+
|
|
182
|
+
6. **Repeat**, with categorical commitment ramping up over the budget.
|
|
183
|
+
|
|
184
|
+
Two properties are load-bearing. **Reflection rather than clipping**: jitter clipped to `[min, max]` deposits probability mass on each bound and biases every search toward interval edges. **Rank rather than raw objective**: raw-value weighting makes behaviour depend on units, and one catastrophic outlier could dominate the archive permanently.
|
|
185
|
+
|
|
186
|
+
Full reasoning in [design notes](https://github.com/AdventuresInDataScience/BUTChC/blob/main/docs/design.md).
|
|
187
|
+
|
|
188
|
+
---
|
|
189
|
+
|
|
190
|
+
## Tuning
|
|
191
|
+
|
|
192
|
+
| Parameter | Start | Increase if… | Decrease if… |
|
|
193
|
+
|---|---|---|---|
|
|
194
|
+
| `lambda_` | `2.0` | tree adapts too slowly | converging too fast to a suboptimal region |
|
|
195
|
+
| `alpha` | `3.0` | many categorical options, small budget | want faster commitment to early evidence |
|
|
196
|
+
| `temp` | `1.0` | categorical exploration too greedy | budget spent on clearly bad choices |
|
|
197
|
+
| `gamma` | `0.85` | objective is noisy; want only strong trials to count | want more trials contributing signal |
|
|
198
|
+
| `explore` | `0.05` | search collapses to a local optimum early | objective is expensive and smooth |
|
|
199
|
+
| `batch` | `1` | you have idle workers | you have no executor |
|
|
200
|
+
| `budget` | 10× param count | rolling loss has not plateaued | rolling loss flat after 20% of the run |
|
|
201
|
+
|
|
202
|
+
`lambda_` and `alpha` act on different node types and do not interact: `lambda_` controls how sharply continuous archives concentrate, `alpha` how slowly categorical nodes commit. Note `lambda_` saturates — it acts only through `min(RANK_SHARPNESS * lambda_, MAX_SHARPNESS)`, so any value at or above 3.33 is clipped and does nothing. See the [API reference](https://github.com/AdventuresInDataScience/BUTChC/blob/main/docs/api.md#butchc_optimize).
|
|
203
|
+
|
|
204
|
+
The two knobs most worth reaching for are not in this table. `KDE_RESERVOIR_SIZE` (default 25) and `MIN_BANDWIDTH_FRACTION` (default 0.0003) between them decide how hard the continuous model concentrates, and they carry more of the measured gain than anything else. They interact, so retune them together: a **smooth, high-dimensional** space wants the gentler pair (`50` and `0.001`), while conditional and multimodal spaces want the sharp defaults.
|
|
205
|
+
|
|
206
|
+
### Tuning for your problem's shape
|
|
207
|
+
|
|
208
|
+
The defaults are an average over problem shapes. `benchmarks/tune.py --regime` (in the repository, not the installed package) sweeps against problems sharing one property and prints what that shape wants: `branched` spaces want faster commitment, `noisy` ones want `explore 0.1`, `multimodal` ones want a short warm-up. The table is in the [API reference](https://github.com/AdventuresInDataScience/BUTChC/blob/main/docs/api.md#tuning-by-problem-shape).
|
|
209
|
+
|
|
210
|
+
---
|
|
211
|
+
|
|
212
|
+
## Results
|
|
213
|
+
|
|
214
|
+
Median best objective over 30 paired seeds — same budget, same seeds, every
|
|
215
|
+
method. Every problem is a maximization with optimum 0, so nearer zero is
|
|
216
|
+
better (Styblinski's offset is rounded, so it can read fractionally above 0).
|
|
217
|
+
Re-running the commands below from a clone of the repository reproduces every
|
|
218
|
+
table in this section.
|
|
219
|
+
|
|
220
|
+
```bash
|
|
221
|
+
python benchmarks/evaluate.py 30
|
|
222
|
+
python benchmarks/evaluate.py 10 --suite heldout --methods tpe,butchc
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
Random search is a low bar, so the comparator carried throughout is TPE — same
|
|
226
|
+
niche, and what a user choosing against BUTChC would actually reach for.
|
|
227
|
+
`benchmarks/baselines.py` carries a dependency-free TPE and an Optuna
|
|
228
|
+
`TPESampler` wrapper. `evaluate.py` also reports paired win-loss records and
|
|
229
|
+
two-sided exact sign-test p-values; those records, not the medians, are what
|
|
230
|
+
the claims here rest on.
|
|
231
|
+
|
|
232
|
+
The suite is split in half. Defaults were selected by coordinate descent
|
|
233
|
+
against the **tuned-on** problems only; the **held-out** problems were never
|
|
234
|
+
consulted during that sweep, so they are the fairer test of whether the
|
|
235
|
+
defaults generalise.
|
|
236
|
+
|
|
237
|
+
### Tuned on
|
|
238
|
+
|
|
239
|
+
| Problem | Budget | Random | TPE | BUTChC | vs TPE |
|
|
240
|
+
|---|---|---|---|---|---|
|
|
241
|
+
| 2D quadratic | 200 | -0.0425 | -0.0004 | **-0.0000** | 30-0, p=0.000 |
|
|
242
|
+
| 5D sphere | 500 | -3.7393 | -0.1312 | **-0.0000** | 29-1, p=0.000 |
|
|
243
|
+
| Rosenbrock | 500 | -0.1036 | **-0.0202** | -0.0368 | 13-17, p=0.585 |
|
|
244
|
+
| Rastrigin 4D | 600 | -17.5005 | -8.6510 | **-5.1169** | 26-4, p=0.000 |
|
|
245
|
+
| 10D sphere | 1000 | -19.0750 | -2.2176 | **-0.0036** | 30-0, p=0.000 |
|
|
246
|
+
| Ackley 5D | 600 | -14.1027 | -4.3008 | **-0.0323** | 30-0, p=0.000 |
|
|
247
|
+
| Log-scale target | 200 | -0.0085 | -0.0006 | **-0.0000** | 29-1, p=0.000 |
|
|
248
|
+
| Branch trap | 300 | -0.6219 | -1.0004 | **-0.0189** | 25-5, p=0.000 |
|
|
249
|
+
| Categorical mix | 300 | -0.3226 | -0.0680 | **-0.0004** | 28-2, p=0.000 |
|
|
250
|
+
| Integer mix | 300 | -0.2727 | -0.0016 | **-0.0000** | 26-4, p=0.000 |
|
|
251
|
+
| Plateau (ties) | 300 | -0.5000 | -0.5000 | -0.5000 | 0-0, p=1.000 |
|
|
252
|
+
|
|
253
|
+
### Held out
|
|
254
|
+
|
|
255
|
+
| Problem | Budget | Random | TPE | BUTChC | vs TPE |
|
|
256
|
+
|---|---|---|---|---|---|
|
|
257
|
+
| Griewank 6D | 800 | -1.0183 | -0.4915 | **-0.4383** | 18-12, p=0.362 |
|
|
258
|
+
| Styblinski 4D | 600 | -20.0007 | -4.0751 | **+0.0007** | 30-0, p=0.000 |
|
|
259
|
+
| Nested pipeline | 400 | -0.2970 | -0.0096 | **-0.0089** | 15-15, p=1.000 |
|
|
260
|
+
| Optimiser choice | 300 | -0.0277 | -0.0007 | **-0.0001** | 25-5, p=0.000 |
|
|
261
|
+
| Rastrigin 8D | 1000 | -62.4076 | -39.0921 | **-21.5439** | 28-2, p=0.000 |
|
|
262
|
+
| 20D sphere | 1500 | -69.0434 | -18.8961 | **-0.1255** | 30-0, p=0.000 |
|
|
263
|
+
|
|
264
|
+
Four of the six held-out problems are significant wins on defaults that never
|
|
265
|
+
saw them.
|
|
266
|
+
|
|
267
|
+
### Under observation noise
|
|
268
|
+
|
|
269
|
+
Scoring the *reported* best on the noise-free function (5D sphere, N(0,1)
|
|
270
|
+
noise, budget 400): random -4.8604, TPE -0.8099, BUTChC **-0.4490** — 19-11
|
|
271
|
+
against TPE at p=0.200, better on the median but not separable at 30 seeds.
|
|
272
|
+
|
|
273
|
+
### What the records show
|
|
274
|
+
|
|
275
|
+
**13 significant wins, zero significant losses, 5 ties**, across all 18
|
|
276
|
+
problems. The margins are large where they are large: `Styblinski 4D` reaches
|
|
277
|
+
the optimum outright (+0.0007 against -4.0751, 30-0), `20D sphere` lands 150×
|
|
278
|
+
nearer it, `Ackley 5D` 133× nearer. `Branch trap` is 25-5 on a problem where
|
|
279
|
+
TPE does *worse than random*, because the trap is precisely the conditional
|
|
280
|
+
structure a flat model cannot see.
|
|
281
|
+
|
|
282
|
+
The five ties are genuine non-results rather than hidden losses, and each has a
|
|
283
|
+
reason; they are set out in full in
|
|
284
|
+
[limitations](https://github.com/AdventuresInDataScience/BUTChC/blob/main/docs/limitations.md#the-non-results-in-full).
|
|
285
|
+
|
|
286
|
+
### How long a result takes to arrive
|
|
287
|
+
|
|
288
|
+
Final quality says where a method ends up, not when. `evaluate.py --anytime`
|
|
289
|
+
reports the other axis. The clearest framing is **how much budget BUTChC needs
|
|
290
|
+
to match TPE's final answer**:
|
|
291
|
+
|
|
292
|
+
| Problem | Budget | Trials BUTChC needed | Fraction of budget |
|
|
293
|
+
|---|---|---|---|
|
|
294
|
+
| 20D sphere | 1500 | 148 | **1/10.1** |
|
|
295
|
+
| 10D sphere | 1000 | 138 | **1/7.3** |
|
|
296
|
+
| Styblinski 4D | 600 | 146 | 1/4.1 |
|
|
297
|
+
| Ackley 5D | 600 | 155 | 1/3.9 |
|
|
298
|
+
| 5D sphere | 500 | 128 | 1/3.9 |
|
|
299
|
+
| Rastrigin 8D | 1000 | 325 | 1/3.1 |
|
|
300
|
+
| Integer mix | 300 | 100 | 1/3.0 |
|
|
301
|
+
| Categorical mix | 300 | 107 | 1/2.8 |
|
|
302
|
+
| Branch trap | 300 | 111 | 1/2.7 |
|
|
303
|
+
| Nested pipeline | 400 | 324 | 1/1.2 |
|
|
304
|
+
|
|
305
|
+
On every problem it reaches, BUTChC matches TPE's *final* result partway
|
|
306
|
+
through its own budget — median around a third of it.
|
|
307
|
+
|
|
308
|
+
Against TPE's own arrival time the picture is split. On high-dimensional
|
|
309
|
+
problems BUTChC is far quicker: `20D sphere` in 148 trials against TPE's 1133,
|
|
310
|
+
on 30 of 30 seeds against TPE's 15. On conditional problems it arrives later
|
|
311
|
+
even while matching or beating TPE's final quality — `Nested pipeline` 324
|
|
312
|
+
against 188, `Optimiser choice` 206 against 115.
|
|
313
|
+
|
|
314
|
+
Reliability is consistently BUTChC's. At 50% of the achievable range it reaches
|
|
315
|
+
the target on 27–30 of 30 seeds where TPE manages 10–29; at 99% on `20D sphere`
|
|
316
|
+
it arrives on 29 of 30 seeds while TPE never arrives at all. Full tables in
|
|
317
|
+
[`dev/tools/results/`](https://github.com/AdventuresInDataScience/BUTChC/tree/main/dev/tools/results).
|
|
318
|
+
|
|
319
|
+
### What the optimiser itself costs
|
|
320
|
+
|
|
321
|
+
Sample efficiency is one axis; the wall clock the optimiser spends choosing is
|
|
322
|
+
another. With the objective stubbed to a constant, so the number is all
|
|
323
|
+
optimiser:
|
|
324
|
+
|
|
325
|
+
| Optimiser | 1D | 5D | 20D |
|
|
326
|
+
|---|---|---|---|
|
|
327
|
+
| **BUTChC** (pure Python, 0 deps) | **14 µs** | **52 µs** | **193 µs** |
|
|
328
|
+
| TPE (this repo, pure Python) | 1037 µs | 5219 µs | 21270 µs |
|
|
329
|
+
| Optuna `TPESampler` (numpy-backed) | 1433 µs | 6789 µs | 27864 µs |
|
|
330
|
+
|
|
331
|
+
BUTChC is **73–145× cheaper per trial** than either TPE. Zero dependencies is
|
|
332
|
+
not costing speed here: the numpy-backed implementation pays ~145× more per
|
|
333
|
+
suggestion, because TPE refits Parzen estimators over the whole history and
|
|
334
|
+
scores candidates, while BUTChC draws a reservoir point and jitters it —
|
|
335
|
+
`O(d·K)` with `K = 25`. The gap widens with width, since BUTChC only touches
|
|
336
|
+
the parameters on the sampled path while a flat model touches all of them.
|
|
337
|
+
|
|
338
|
+
Reproduce with `python benchmarks/overhead.py`.
|
|
339
|
+
|
|
340
|
+
### What batching costs
|
|
341
|
+
|
|
342
|
+
A batch of `k` leaves the model stale for `k-1` evaluations. Median extra regret against sequential, 18 problems × 20 seeds at matched budgets:
|
|
343
|
+
|
|
344
|
+
| `batch` | 2 | 4 | 8 | 16 | 32 |
|
|
345
|
+
|---|---|---|---|---|---|
|
|
346
|
+
| Extra regret | −3.3% | −0.1% | +2.6% | +21.3% | +43.1% |
|
|
347
|
+
|
|
348
|
+
```bash
|
|
349
|
+
python benchmarks/batch_cost.py 20 --sizes 1,2,4,8,16,32
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
Up to `k=8` the cost sits inside seed noise, which makes an 8× wall-clock speedup close to free. Past `k=16` it is real. Batch sizes above your worker count pay the cost for nothing.
|
|
353
|
+
|
|
354
|
+
### The spaces this is built for
|
|
355
|
+
|
|
356
|
+
The 18 problems above are synthetic and were written in this repository. To
|
|
357
|
+
measure the *shape* of real conditional spaces independently,
|
|
358
|
+
[`dev/eval/pcs_stats.py`](https://github.com/AdventuresInDataScience/BUTChC/blob/main/dev/eval/pcs_stats.py)
|
|
359
|
+
parses eleven configuration spaces published by other people, for other
|
|
360
|
+
purposes, years before this library existed, and reports how much of each is
|
|
361
|
+
inactive in a typical configuration. The five most conditional:
|
|
362
|
+
|
|
363
|
+
| Space | Params | Median active | Inactive | Depth |
|
|
364
|
+
|---|---|---|---|---|
|
|
365
|
+
| AutoWEKA | 786 | 14 | **98.2%** | 4 |
|
|
366
|
+
| auto-sklearn (2017) | 138 | 16 | **88.4%** | 2 |
|
|
367
|
+
| SparrowToRiss | 222 | 67 | 69.8% | 4 |
|
|
368
|
+
| SATenstein | 54 | 26 | 51.9% | 4 |
|
|
369
|
+
| clasp 3.1.4 | 98 | 59 | 39.8% | 3 |
|
|
370
|
+
|
|
371
|
+
```bash
|
|
372
|
+
python dev/eval/pcs_stats.py --download
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
In AutoWEKA, 98.2% of the declared parameters are inactive in any given
|
|
376
|
+
configuration — the share a flat optimiser searches and a conditional one
|
|
377
|
+
skips. All 174 of its multi-parent conditions are chain-shaped, so the space is
|
|
378
|
+
representable as a tree exactly rather than approximately.
|
|
379
|
+
|
|
380
|
+
This measures the premise the library is built on, in spaces nobody here
|
|
381
|
+
designed. It is not a head-to-head: no BUTChC-versus-TPE run has been executed
|
|
382
|
+
on these spaces yet. Both halves of that are set out in
|
|
383
|
+
[limitations](https://github.com/AdventuresInDataScience/BUTChC/blob/main/docs/limitations.md#what-the-benchmarks-establish-and-what-they-do-not).
|
|
384
|
+
|
|
385
|
+
---
|
|
386
|
+
|
|
387
|
+
## Where it fits
|
|
388
|
+
|
|
389
|
+
Reach for BUTChC when the search space is conditional, when it is wide, when
|
|
390
|
+
the objective is noisy or on an awkward scale, or when adding a dependency is
|
|
391
|
+
not an option. Those are the axes it is measured strongest on.
|
|
392
|
+
|
|
393
|
+
Reach for something else when the budget is under ~50 trials on a smooth
|
|
394
|
+
low-dimensional objective, where a Gaussian process will do better, or when
|
|
395
|
+
your parameters interact strongly *within* a branch — sibling nodes are
|
|
396
|
+
modelled independently, and `Rosenbrock` measures what that costs.
|
|
397
|
+
|
|
398
|
+
The complete list of what the library does not model, does not express, and has
|
|
399
|
+
not yet measured is in **[limitations](https://github.com/AdventuresInDataScience/BUTChC/blob/main/docs/limitations.md)**. The one
|
|
400
|
+
line worth carrying from it: the 18 benchmark problems are synthetic and were
|
|
401
|
+
written alongside the optimiser, so benchmark your own space before committing
|
|
402
|
+
to it.
|
|
403
|
+
|
|
404
|
+
---
|
|
405
|
+
|
|
406
|
+
## Versioning
|
|
407
|
+
|
|
408
|
+
Defaults and internals change between minor versions, so **seeded runs do not reproduce across them**. See the [changelog](https://github.com/AdventuresInDataScience/BUTChC/blob/main/CHANGELOG.md).
|
|
409
|
+
|
|
410
|
+
## Development
|
|
411
|
+
|
|
412
|
+
```bash
|
|
413
|
+
git clone https://github.com/AdventuresInDataScience/BUTChC
|
|
414
|
+
cd BUTChC
|
|
415
|
+
pip install -e ".[test]"
|
|
416
|
+
pytest
|
|
417
|
+
```
|
|
418
|
+
|
|
419
|
+
The benchmark suite lives in `benchmarks/`, and a map of the source for
|
|
420
|
+
contributors is in [docs/codemap.md](https://github.com/AdventuresInDataScience/BUTChC/blob/main/docs/codemap.md).
|
|
421
|
+
|
|
422
|
+
## License
|
|
423
|
+
|
|
424
|
+
MIT — see [LICENSE](https://github.com/AdventuresInDataScience/BUTChC/blob/main/LICENSE).
|