metawalker 0.2.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.
Files changed (40) hide show
  1. metawalker-0.2.0/LICENSE +21 -0
  2. metawalker-0.2.0/PKG-INFO +157 -0
  3. metawalker-0.2.0/README.md +103 -0
  4. metawalker-0.2.0/docs/package.md +137 -0
  5. metawalker-0.2.0/pyproject.toml +27 -0
  6. metawalker-0.2.0/setup.cfg +4 -0
  7. metawalker-0.2.0/src/metawalker/__init__.py +19 -0
  8. metawalker-0.2.0/src/metawalker/api.py +192 -0
  9. metawalker-0.2.0/src/metawalker/asktell.py +189 -0
  10. metawalker-0.2.0/src/metawalker/baselines.py +67 -0
  11. metawalker-0.2.0/src/metawalker/certify.py +116 -0
  12. metawalker-0.2.0/src/metawalker/cma.py +67 -0
  13. metawalker-0.2.0/src/metawalker/compare.py +104 -0
  14. metawalker-0.2.0/src/metawalker/es.py +20 -0
  15. metawalker-0.2.0/src/metawalker/importance.py +189 -0
  16. metawalker-0.2.0/src/metawalker/policies/cov_hp_s0.npy +0 -0
  17. metawalker-0.2.0/src/metawalker/policies/mom_s0.npy +0 -0
  18. metawalker-0.2.0/src/metawalker/policies/rlcma_mlp_s0.npy +0 -0
  19. metawalker-0.2.0/src/metawalker/policy.py +94 -0
  20. metawalker-0.2.0/src/metawalker/problems.py +166 -0
  21. metawalker-0.2.0/src/metawalker/recommend.py +118 -0
  22. metawalker-0.2.0/src/metawalker/rlbo.py +258 -0
  23. metawalker-0.2.0/src/metawalker/sklearn.py +40 -0
  24. metawalker-0.2.0/src/metawalker/space.py +91 -0
  25. metawalker-0.2.0/src/metawalker/train.py +136 -0
  26. metawalker-0.2.0/src/metawalker/walker.py +192 -0
  27. metawalker-0.2.0/src/metawalker.egg-info/PKG-INFO +157 -0
  28. metawalker-0.2.0/src/metawalker.egg-info/SOURCES.txt +38 -0
  29. metawalker-0.2.0/src/metawalker.egg-info/dependency_links.txt +1 -0
  30. metawalker-0.2.0/src/metawalker.egg-info/requires.txt +13 -0
  31. metawalker-0.2.0/src/metawalker.egg-info/top_level.txt +1 -0
  32. metawalker-0.2.0/tests/test_asktell.py +107 -0
  33. metawalker-0.2.0/tests/test_certify.py +95 -0
  34. metawalker-0.2.0/tests/test_honeypot_env.py +107 -0
  35. metawalker-0.2.0/tests/test_importance.py +86 -0
  36. metawalker-0.2.0/tests/test_metawalker.py +175 -0
  37. metawalker-0.2.0/tests/test_recommend.py +71 -0
  38. metawalker-0.2.0/tests/test_regression.py +93 -0
  39. metawalker-0.2.0/tests/test_rules_warmup.py +64 -0
  40. metawalker-0.2.0/tests/test_train.py +52 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Anik Ghosh
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,157 @@
1
+ Metadata-Version: 2.4
2
+ Name: metawalker
3
+ Version: 0.2.0
4
+ Summary: Learned black-box optimizers (RL-controlled CMA-ES, learned walkers) as a drop-in for GridSearchCV
5
+ License: MIT
6
+ Requires-Python: >=3.10
7
+ Description-Content-Type: text/markdown
8
+ License-File: LICENSE
9
+ Requires-Dist: numpy>=1.24
10
+ Requires-Dist: cma>=3.3
11
+ Requires-Dist: scipy>=1.10
12
+ Provides-Extra: sklearn
13
+ Requires-Dist: scikit-learn>=1.3; extra == "sklearn"
14
+ Provides-Extra: optuna
15
+ Requires-Dist: optuna>=3.0; extra == "optuna"
16
+ Provides-Extra: test
17
+ Requires-Dist: pytest; extra == "test"
18
+ Requires-Dist: scikit-learn>=1.3; extra == "test"
19
+ Dynamic: license-file
20
+
21
+ # metawalker
22
+
23
+ Learned black-box optimizers for **big, complex search spaces**, packaged as a drop-in replacement for
24
+ scikit-learn's `GridSearchCV` / `RandomizedSearchCV` and as a plain `minimize()` function.
25
+
26
+ The optimizers were trained once, with evolution strategies, on cheap synthetic problems. After that they run
27
+ on numpy alone, and each policy is a few KB.
28
+
29
+ ```bash
30
+ pip install "metawalker[sklearn]" # from the repo: pip install -e ".[sklearn]"
31
+ ```
32
+
33
+ ## Use it
34
+
35
+ ```python
36
+ from metawalker import minimize, Float, Int, Categorical
37
+
38
+ space = {"lr": Float(1e-5, 1e-1, log=True), "layers": Int(1, 8), "act": ["relu", "tanh"], "dropout": (0.0, 0.5)}
39
+ result = minimize(train_and_score, space, budget=300, method="auto", maximize=True, recheck=3)
40
+ result.best_params, result.best_value, result.recheck_values
41
+ ```
42
+
43
+ As a scikit-learn search estimator (it uses the same internal hook as GridSearchCV, so `cv`, `scoring`,
44
+ `refit`, `n_jobs`, `cv_results_`, `best_params_` and `best_estimator_` all behave the same):
45
+
46
+ ```python
47
+ from metawalker.sklearn import MetaWalkerCV
48
+
49
+ search = MetaWalkerCV(model, {"C": Float(1e-3, 1e3, log=True), "kernel": ["rbf", "linear"]},
50
+ n_iter=100, method="rlcma", cv=5).fit(X, y)
51
+ ```
52
+
53
+ A GridSearchCV `param_grid` (a dict of value lists) works unchanged as `param_space`.
54
+
55
+ ## Choose your own panel: test methods on your own losses
56
+
57
+ You know your problem best, so the package helps you measure which method fits it. Give `compare()` your own
58
+ loss functions, and optionally some simulated stand-ins shaped like your problem. It runs every method several
59
+ times on each, with the same budget, and reports average rank, head-to-head win rates and time per run:
60
+
61
+ ```python
62
+ from metawalker import compare, make_problem, minimize
63
+
64
+ res = compare({"my_loss": (my_fn, my_space),
65
+ "stand-in": make_problem("plateau", n=80, noise=0.01)}, # sphere, ellipsoid, rastrigin,
66
+ methods=["rlcma", "walker_hp", "cmaes", "gp"], # rosenbrock, ackley, levy, plateau
67
+ budget=200, seeds=5)
68
+ print(res.summary())
69
+
70
+ minimize(my_fn, my_space, budget=600, method=["rlcma", "gp"]) # then run the panel you picked
71
+ ```
72
+
73
+ The simulated problems come from the same generator as our benchmark: rotated and shifted, with a known
74
+ optimum, so `compare()` scores them by true regret rather than by lucky noisy readings.
75
+
76
+ ## What else it does for you
77
+
78
+ ```python
79
+ import metawalker as mw
80
+
81
+ # 1. don't know your landscape? probe it (~8% of the budget), then let it pick from the benchmark's rules
82
+ res = mw.minimize_auto(my_fn, my_space, budget=500)
83
+ print(res.recommendation.reasons) # e.g. "92% of probes returned the same value: a plateau objective -> walker_hp"
84
+
85
+ # 2. a best result you can trust: re-test the top candidates on fresh evaluations, pick by mean, report a CI
86
+ cert = mw.certify(my_fn, res, top_k=5, repeats=5)
87
+ print(cert.summary()) # cert.shrinkage = how much the raw best score was inflated by noise
88
+
89
+ # 3. which parameters mattered, which direction helps, and the best range (free, from the search history)
90
+ print(mw.importance(res).summary())
91
+
92
+ # 4. train an optimizer specialized to YOUR kind of problem, on cheap simulated stand-ins
93
+ policy = mw.train([lambda: my_simulator_factory()], init="walker_hp", generations=50, n_jobs=8)
94
+ policy.save("my_optimizer.npz"); policy.minimize(my_fn, my_space, budget=300)
95
+
96
+ # 5. evaluate on your own workers / cluster, or plug into an existing Optuna study
97
+ with mw.AskTell(my_space, budget=200, method="rlcma") as at:
98
+ while not at.done:
99
+ tid, params = at.ask(); at.tell(tid, run_somewhere(params))
100
+ from metawalker.asktell import MetaWalkerSampler # optuna.create_study(sampler=MetaWalkerSampler(dists, budget=200))
101
+
102
+ # 6. compare methods on your own losses, in parallel
103
+ mw.compare({"mine": (my_fn, my_space)}, methods=["rlcma", "gp", "cmaes"], seeds=5, n_jobs=8)
104
+ ```
105
+
106
+ Notes:
107
+ - `compare(n_jobs>1)` and `train(n_jobs>1)` need picklable functions, plus an `if __name__ == "__main__":` guard on Windows.
108
+ - `certify()` and `importance()` read the search direction from the Result.
109
+ - Importance computed from an optimizer's history is biased toward the regions that optimizer sampled. Random-search histories give the cleanest estimate.
110
+
111
+ ## Which method
112
+
113
+ | method | what it is | where it did best in our benchmark |
114
+ |---|---|---|
115
+ | `rlcma` | CMA-ES whose step size and restarts are set by a learned policy | 50–500 parameters: beat CMA-ES on 72–99% of test problems and TPE on 95%+ |
116
+ | `walker` | a learned multi-start walker; one small network shared by every parameter, so it runs at any size | tied CMA-ES overall, won at 500 parameters; ~1 s of optimizer overhead |
117
+ | `walker_hp` | the walker, also trained on plateau-shaped objectives | flat, mostly-zero objectives (e.g. searching for RL honeypots); fastest to a valid high-exploit level in a 169-parameter honeypot search |
118
+ | `walker_hp_warm` | `walker_hp` after a Latin-hypercube warm-up of 20% of the budget (`warmup=0.2`) | **recommended for rare-signal objectives**, where only a tiny region scores at all: on a 0.5%-live plateau it found the region in 9/10 runs, against 1/10 (10 parameters) and 5/10 (50) for `walker_hp` |
119
+ | `walker_rule` | hand-coded walker distilled from the learned one: move each coordinate with p = 0.6, pull 0.5 toward the best walker, step size `s *= exp(0.5 (success EMA - 0.35))` | as good as `walker`, with no network (mean rank 1.69 vs 1.83 over 12 problems) |
120
+ | `cmaes` | CMA-ES with IPOP restarts (pycma) | small, noisy problems (under ~20 parameters) |
121
+ | `cmaes_rule` | CMA-ES with the hand rule distilled from `rlcma`: sigma × 0.8 after a generation that did not improve, unless stalled 5+ generations or the population is flat | ties `rlcma` (wins 50%) and beats `cmaes` on 78% of runs; `rlcma` stays ahead on Levy/Rastrigin at 10 parameters |
122
+ | `gp` | Optuna Gaussian-process Bayesian optimization (needs `metawalker[optuna]`) | **expensive** objectives up to ~200 parameters: tied with `rlcma` overall and better at 100–200 parameters, but it costs ~1 hour of its own compute per 1000-evaluation run, and that cost grows fast with evaluations and dimensions |
123
+ | `tpe` | Optuna TPE (needs `metawalker[optuna]`) | baseline; `rlcma` beat it on 70–100% of problems |
124
+ | `random` | random search | baseline |
125
+ | `auto` | `cmaes` below 20 parameters, `rlcma` from 20 up; `gp` up to 200 parameters if you pass `expensive=True` | |
126
+ | `[...]` | any list of methods = your own panel: the budget is split across them and the best is kept | |
127
+ | `panel` | the default panel: `rlcma`, `walker_hp`, `cmaes` | when you don't know the landscape |
128
+
129
+ Any method also takes `warmup=f`. It spends that fraction of the budget on Latin-hypercube points first, and
130
+ those points count toward the budget and appear in the history. The walkers then start from the best 4 points
131
+ and the CMA-based methods from the best one, while the other methods simply run on what is left. Use it when the
132
+ signal is rare: without it the walkers shrink and restart instead of exploring, so plain random search finds a
133
+ tiny live region more often than they do. The distilled rules come from the policy study (`results/analysis/policy_report.md`). The numbers
134
+ above are medians over 3 seeds at budget 1000, from sphere, Rosenbrock, Rastrigin, Ackley, Levy and plateau at
135
+ 10 and 50 parameters. `compare()` accepts all of these methods.
136
+
137
+ **Benchmark scope.** Rotated and shifted synthetic functions (sphere, ellipsoid, Rastrigin, Rosenbrock, plus
138
+ held-out Ackley and Levy) with and without noise, a honeypot-like plateau family, and a real RL honeypot
139
+ search. Budgets were 300–10,000 evaluations. Each learned method was trained 3 times from scratch, with
140
+ results reported per training run. A comparison against Optuna GP, Optuna multivariate TPE, SMAC3 and
141
+ Nevergrad NGOpt is in progress.
142
+
143
+ ## Behavior to know
144
+
145
+ - **Invalid configurations:** if your function raises or returns nan/inf, that counts as a failed trial
146
+ (`result.n_failed`). The search keeps going.
147
+ - **Noisy objectives:** the best observed value is biased upward (the winner's curse). Use `recheck=k` to
148
+ re-evaluate the winner k more times.
149
+ - **Categorical parameters** are one coordinate split into equal bins. That's fine for a few choices, but
150
+ unordered categoricals with many choices are a known weak spot.
151
+ - **1-parameter searches** with the CMA-based methods add an ignored dummy coordinate, because pycma needs 2+.
152
+ - The `panel` budget is split equally, with no racing between methods.
153
+
154
+ ## Tests
155
+
156
+ `pytest` from the repo root. `tests/test_regression.py` checks that every optimizer reproduces the
157
+ pre-refactor benchmark code bit for bit (`tests/golden.json`).
@@ -0,0 +1,103 @@
1
+ # MetaWalker: learned search policies for honeypot and hyperparameter search
2
+
3
+ The Python package is `metawalker` (`pip install -e .`, then `import metawalker`).
4
+
5
+ RL-trained black-box optimizers for expensive, noisy, many-parameter searches. The motivating application is
6
+ AI-safety "honeypot" search: find the gridworld level and PPO settings (169 parameters) under which a PPO agent
7
+ takes a planted exploit instead of the honest task.
8
+
9
+ ## Proposal → code
10
+
11
+ Each part of the proposal (`CS4180_project_proposal.md`) and where it lives. `src/metawalker/` is the library
12
+ (`pip install -e .`); `experiments/` holds the scripts that produce every result in `results/`. "Written by" follows
13
+ the proposal's LLM-assistance disclosure: Aniket wrote the base algorithms; the rest is supporting code (the
14
+ experiment scripts and shell scripts).
15
+
16
+ | Proposal | Code | Written by |
17
+ |---|---|---|
18
+ | The MDP: one search per episode, budget, noise, invalid points | `src/metawalker/problems.py` (`Problem`) | Aniket |
19
+ | Sparse reward: −log10(final regret / random-point value) | `problems.py` (`Problem.score`) | Aniket |
20
+ | Dense reward: log improvement per step | `src/metawalker/walker.py` (`navigate`, the `actor` branch) | Aniket |
21
+ | Walker (Table 1) | `walker.py`; network layout, action sets, features in `src/metawalker/policy.py` | Aniket |
22
+ | RL-controlled CMA-ES (Table 1) | `src/metawalker/cma.py` (`cmaes` with a policy) | Aniket |
23
+ | rlbo (Table 1) | `src/metawalker/rlbo.py` | Aniket |
24
+ | OpenAI-ES | update step: `src/metawalker/es.py`; training loops: `experiments/benchmark.py` (`train`), `experiments/rlbo_bench.py` | Aniket (update), supporting code (loops) |
25
+ | PPO and branching double DQN | `experiments/rlalgos.py` | supporting code |
26
+ | Baselines: CMA-ES, 1/5th-rule walkers | `cma.py` (`cmaes`), `walker.py` (`navigate` with no policy) | Aniket |
27
+ | Baselines: Optuna GP / TPE, SMAC3, NGOpt, random search | `src/metawalker/baselines.py` | supporting code |
28
+ | MetaWalker-Bench: test functions and the flat honeypot-like family | `problems.py`; suites and reports in `experiments/benchmark.py` | Aniket (functions), supporting code (suites) |
29
+ | Real honeypot: JAX PPO gridworld, 169 genes, validity checks | `experiments/honeypot/env.py` | supporting code |
30
+ | Comparison 1: same number of tries | `experiments/benchmark.py` (`test`, `hp` suites), `experiments/compare_hpo.py`, `experiments/rlbo_bench.py` | supporting code |
31
+ | Comparison 2: same running time as GP | `experiments/timematched.py`, `experiments/rerun_gp.py` | supporting code |
32
+ | Comparison 3: budgets 300–10,000 | `experiments/benchmark.py` (`budgets` suite) | supporting code |
33
+ | Comparison 4: real honeypot, same PPO compute | `experiments/honeypot/search.py`, `results/honeypot/summarize.py` | supporting code |
34
+ | Comparison 5: ablations, what the policies learned | ablation variants in `experiments/benchmark.py` (`hpx_norecheck`, `hpx_nojump`); `experiments/analysis/` | supporting code |
35
+ | Risk: re-test the best on new seeds | `experiments/honeypot/search.py` (`remeasure`), `src/metawalker/certify.py` | supporting code |
36
+ | Risk: Wilson confidence intervals | `experiments/benchmark.py` (`wilson`) | supporting code |
37
+ | Risk: self-checks for bugs | `tests/` | supporting code |
38
+ | Cluster runs (CPU-only Explorer) | `cluster/*.sbatch` | supporting code |
39
+
40
+ **The rest** is not in the proposal. It is the package's user-facing API, all supporting code: `api.py`
41
+ (`minimize`), `sklearn.py` (`MetaWalkerCV`), `asktell.py`, `space.py`, `compare.py`, `recommend.py`,
42
+ `importance.py`, `train.py`, and `experiments/demo.py`.
43
+
44
+ `tests/` checks the library and the experiments. `tests/test_regression.py` confirms that every optimizer
45
+ reproduces the pre-refactor code bit for bit. `cluster/` holds the Slurm scripts.
46
+
47
+ ## Results
48
+
49
+ ### Benchmark functions (`results/reports/`)
50
+
51
+ Rotated and shifted sphere, ellipsoid, rastrigin, rosenbrock, ackley and levy, plus a honeypot-like plateau
52
+ family, at n = 5–500.
53
+
54
+ | Comparison | Result |
55
+ |---|---|
56
+ | rlbo vs Optuna GP, equal evaluations (1,000) | rlbo better on 76–100% of instances at every n; 23 s vs 4,296 s per run |
57
+ | rlbo vs CMA-ES, equal evaluations | rlbo better on 91–100% for n ≥ 20; tie at n = 5 |
58
+ | RL-CMA vs GP, same wall-clock as GP | RL-CMA better on 90–100% of 4,620 runs at every n (plain CMA-ES too) |
59
+ | Walkers on the plateau family | about −1.5 vs −0.6 to −0.9 for CMA-ES (log10 regret, lower is better) |
60
+
61
+ ### Real honeypot, 169 parameters (`results/honeypot/`; `python results/honeypot/summarize.py`)
62
+
63
+ Equal PPO compute: 200 PPO-costing evaluations per search, structurally invalid levels free. The score is the
64
+ certified exploit rate: the best of the top candidates, re-measured on fresh PPO seeds. 5 search seeds per method.
65
+
66
+ | Method | Score |
67
+ |---|---|
68
+ | **rlbo** (3 policies, 15 searches) | **0.88** |
69
+ | CMA-ES | 0.75–0.77 |
70
+ | walker with jump / re-check + level cache | 0.69 |
71
+ | Optuna GP | 0.55 |
72
+
73
+ ### What the policies learned (`results/analysis/policy_report.md`)
74
+
75
+ - The walker jumps on 70% of its steps until it finds signal, then searches locally and re-checks its best
76
+ point. On the real honeypot, removing re-check drops it from 0.81 to 0.48, and removing jump to 0.59.
77
+ - The budget-aware walkers front-load their gains. That wins small budgets and loses to CMA-ES at 10,000
78
+ evaluations.
79
+ - Three silent training bugs are documented in the reports:
80
+ - a seed leak that started walkers next to the optimum;
81
+ - ES rank ties turning into random steps;
82
+ - a BLAS thread pile-up that slowed training about 300×.
83
+
84
+ ## Quick start
85
+
86
+ ```bash
87
+ pip install -e ".[sklearn,optuna,test]" # the library (+ torch for Optuna's GP, jax/flax/optax for the honeypot)
88
+ pytest # library, regression and experiment tests
89
+ python -m experiments.benchmark smoke_hpx # quick self-check of the walker and its new actions
90
+ ```
91
+
92
+ ```python
93
+ from metawalker import minimize, Float, Int
94
+ result = minimize(train_and_score, {"lr": Float(1e-5, 1e-1, log=True), "layers": Int(1, 8)}, budget=300,
95
+ maximize=True, recheck=3)
96
+ ```
97
+
98
+ See `docs/package.md` for the library API, `proposal_notes.md` for the project proposal and `docs/plan.md` for
99
+ the project plan. The cluster runs
100
+ used Northeastern's Explorer: run `sbatch cluster/setup.sbatch` once, then use the generic `cluster/run.sbatch`
101
+ and `cluster/honeypot.sbatch`. Usage examples are in their headers.
102
+
103
+ MIT licensed.
@@ -0,0 +1,137 @@
1
+ # metawalker
2
+
3
+ Learned black-box optimizers for **big, complex search spaces**, packaged as a drop-in replacement for
4
+ scikit-learn's `GridSearchCV` / `RandomizedSearchCV` and as a plain `minimize()` function.
5
+
6
+ The optimizers were trained once, with evolution strategies, on cheap synthetic problems. After that they run
7
+ on numpy alone, and each policy is a few KB.
8
+
9
+ ```bash
10
+ pip install "metawalker[sklearn]" # from the repo: pip install -e ".[sklearn]"
11
+ ```
12
+
13
+ ## Use it
14
+
15
+ ```python
16
+ from metawalker import minimize, Float, Int, Categorical
17
+
18
+ space = {"lr": Float(1e-5, 1e-1, log=True), "layers": Int(1, 8), "act": ["relu", "tanh"], "dropout": (0.0, 0.5)}
19
+ result = minimize(train_and_score, space, budget=300, method="auto", maximize=True, recheck=3)
20
+ result.best_params, result.best_value, result.recheck_values
21
+ ```
22
+
23
+ As a scikit-learn search estimator (it uses the same internal hook as GridSearchCV, so `cv`, `scoring`,
24
+ `refit`, `n_jobs`, `cv_results_`, `best_params_` and `best_estimator_` all behave the same):
25
+
26
+ ```python
27
+ from metawalker.sklearn import MetaWalkerCV
28
+
29
+ search = MetaWalkerCV(model, {"C": Float(1e-3, 1e3, log=True), "kernel": ["rbf", "linear"]},
30
+ n_iter=100, method="rlcma", cv=5).fit(X, y)
31
+ ```
32
+
33
+ A GridSearchCV `param_grid` (a dict of value lists) works unchanged as `param_space`.
34
+
35
+ ## Choose your own panel: test methods on your own losses
36
+
37
+ You know your problem best, so the package helps you measure which method fits it. Give `compare()` your own
38
+ loss functions, and optionally some simulated stand-ins shaped like your problem. It runs every method several
39
+ times on each, with the same budget, and reports average rank, head-to-head win rates and time per run:
40
+
41
+ ```python
42
+ from metawalker import compare, make_problem, minimize
43
+
44
+ res = compare({"my_loss": (my_fn, my_space),
45
+ "stand-in": make_problem("plateau", n=80, noise=0.01)}, # sphere, ellipsoid, rastrigin,
46
+ methods=["rlcma", "walker_hp", "cmaes", "gp"], # rosenbrock, ackley, levy, plateau
47
+ budget=200, seeds=5)
48
+ print(res.summary())
49
+
50
+ minimize(my_fn, my_space, budget=600, method=["rlcma", "gp"]) # then run the panel you picked
51
+ ```
52
+
53
+ The simulated problems come from the same generator as our benchmark: rotated and shifted, with a known
54
+ optimum, so `compare()` scores them by true regret rather than by lucky noisy readings.
55
+
56
+ ## What else it does for you
57
+
58
+ ```python
59
+ import metawalker as mw
60
+
61
+ # 1. don't know your landscape? probe it (~8% of the budget), then let it pick from the benchmark's rules
62
+ res = mw.minimize_auto(my_fn, my_space, budget=500)
63
+ print(res.recommendation.reasons) # e.g. "92% of probes returned the same value: a plateau objective -> walker_hp"
64
+
65
+ # 2. a best result you can trust: re-test the top candidates on fresh evaluations, pick by mean, report a CI
66
+ cert = mw.certify(my_fn, res, top_k=5, repeats=5)
67
+ print(cert.summary()) # cert.shrinkage = how much the raw best score was inflated by noise
68
+
69
+ # 3. which parameters mattered, which direction helps, and the best range (free, from the search history)
70
+ print(mw.importance(res).summary())
71
+
72
+ # 4. train an optimizer specialized to YOUR kind of problem, on cheap simulated stand-ins
73
+ policy = mw.train([lambda: my_simulator_factory()], init="walker_hp", generations=50, n_jobs=8)
74
+ policy.save("my_optimizer.npz"); policy.minimize(my_fn, my_space, budget=300)
75
+
76
+ # 5. evaluate on your own workers / cluster, or plug into an existing Optuna study
77
+ with mw.AskTell(my_space, budget=200, method="rlcma") as at:
78
+ while not at.done:
79
+ tid, params = at.ask(); at.tell(tid, run_somewhere(params))
80
+ from metawalker.asktell import MetaWalkerSampler # optuna.create_study(sampler=MetaWalkerSampler(dists, budget=200))
81
+
82
+ # 6. compare methods on your own losses, in parallel
83
+ mw.compare({"mine": (my_fn, my_space)}, methods=["rlcma", "gp", "cmaes"], seeds=5, n_jobs=8)
84
+ ```
85
+
86
+ Notes:
87
+ - `compare(n_jobs>1)` and `train(n_jobs>1)` need picklable functions, plus an `if __name__ == "__main__":` guard on Windows.
88
+ - `certify()` and `importance()` read the search direction from the Result.
89
+ - Importance computed from an optimizer's history is biased toward the regions that optimizer sampled. Random-search histories give the cleanest estimate.
90
+
91
+ ## Which method
92
+
93
+ | method | what it is | where it did best in our benchmark |
94
+ |---|---|---|
95
+ | `rlcma` | CMA-ES whose step size and restarts are set by a learned policy | 50–500 parameters: beat CMA-ES on 72–99% of test problems and TPE on 95%+ |
96
+ | `walker` | a learned multi-start walker; one small network shared by every parameter, so it runs at any size | tied CMA-ES overall, won at 500 parameters; ~1 s of optimizer overhead |
97
+ | `walker_hp` | the walker, also trained on plateau-shaped objectives | flat, mostly-zero objectives (e.g. searching for RL honeypots); fastest to a valid high-exploit level in a 169-parameter honeypot search |
98
+ | `walker_hp_warm` | `walker_hp` after a Latin-hypercube warm-up of 20% of the budget (`warmup=0.2`) | **recommended for rare-signal objectives**, where only a tiny region scores at all: on a 0.5%-live plateau it found the region in 9/10 runs, against 1/10 (10 parameters) and 5/10 (50) for `walker_hp` |
99
+ | `walker_rule` | hand-coded walker distilled from the learned one: move each coordinate with p = 0.6, pull 0.5 toward the best walker, step size `s *= exp(0.5 (success EMA - 0.35))` | as good as `walker`, with no network (mean rank 1.69 vs 1.83 over 12 problems) |
100
+ | `cmaes` | CMA-ES with IPOP restarts (pycma) | small, noisy problems (under ~20 parameters) |
101
+ | `cmaes_rule` | CMA-ES with the hand rule distilled from `rlcma`: sigma × 0.8 after a generation that did not improve, unless stalled 5+ generations or the population is flat | ties `rlcma` (wins 50%) and beats `cmaes` on 78% of runs; `rlcma` stays ahead on Levy/Rastrigin at 10 parameters |
102
+ | `gp` | Optuna Gaussian-process Bayesian optimization (needs `metawalker[optuna]`) | **expensive** objectives up to ~200 parameters: tied with `rlcma` overall and better at 100–200 parameters, but it costs ~1 hour of its own compute per 1000-evaluation run, and that cost grows fast with evaluations and dimensions |
103
+ | `tpe` | Optuna TPE (needs `metawalker[optuna]`) | baseline; `rlcma` beat it on 70–100% of problems |
104
+ | `random` | random search | baseline |
105
+ | `auto` | `cmaes` below 20 parameters, `rlcma` from 20 up; `gp` up to 200 parameters if you pass `expensive=True` | |
106
+ | `[...]` | any list of methods = your own panel: the budget is split across them and the best is kept | |
107
+ | `panel` | the default panel: `rlcma`, `walker_hp`, `cmaes` | when you don't know the landscape |
108
+
109
+ Any method also takes `warmup=f`. It spends that fraction of the budget on Latin-hypercube points first, and
110
+ those points count toward the budget and appear in the history. The walkers then start from the best 4 points
111
+ and the CMA-based methods from the best one, while the other methods simply run on what is left. Use it when the
112
+ signal is rare: without it the walkers shrink and restart instead of exploring, so plain random search finds a
113
+ tiny live region more often than they do. The distilled rules come from the policy study (`results/analysis/policy_report.md`). The numbers
114
+ above are medians over 3 seeds at budget 1000, from sphere, Rosenbrock, Rastrigin, Ackley, Levy and plateau at
115
+ 10 and 50 parameters. `compare()` accepts all of these methods.
116
+
117
+ **Benchmark scope.** Rotated and shifted synthetic functions (sphere, ellipsoid, Rastrigin, Rosenbrock, plus
118
+ held-out Ackley and Levy) with and without noise, a honeypot-like plateau family, and a real RL honeypot
119
+ search. Budgets were 300–10,000 evaluations. Each learned method was trained 3 times from scratch, with
120
+ results reported per training run. A comparison against Optuna GP, Optuna multivariate TPE, SMAC3 and
121
+ Nevergrad NGOpt is in progress.
122
+
123
+ ## Behavior to know
124
+
125
+ - **Invalid configurations:** if your function raises or returns nan/inf, that counts as a failed trial
126
+ (`result.n_failed`). The search keeps going.
127
+ - **Noisy objectives:** the best observed value is biased upward (the winner's curse). Use `recheck=k` to
128
+ re-evaluate the winner k more times.
129
+ - **Categorical parameters** are one coordinate split into equal bins. That's fine for a few choices, but
130
+ unordered categoricals with many choices are a known weak spot.
131
+ - **1-parameter searches** with the CMA-based methods add an ignored dummy coordinate, because pycma needs 2+.
132
+ - The `panel` budget is split equally, with no racing between methods.
133
+
134
+ ## Tests
135
+
136
+ `pytest` from the repo root. `tests/test_regression.py` checks that every optimizer reproduces the
137
+ pre-refactor benchmark code bit for bit (`tests/golden.json`).
@@ -0,0 +1,27 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "metawalker"
7
+ version = "0.2.0"
8
+ description = "Learned black-box optimizers (RL-controlled CMA-ES, learned walkers) as a drop-in for GridSearchCV"
9
+ readme = "docs/package.md"
10
+ license = {text = "MIT"}
11
+ requires-python = ">=3.10"
12
+ dependencies = ["numpy>=1.24", "cma>=3.3", "scipy>=1.10"]
13
+
14
+ [project.optional-dependencies]
15
+ sklearn = ["scikit-learn>=1.3"]
16
+ optuna = ["optuna>=3.0"]
17
+ test = ["pytest", "scikit-learn>=1.3"]
18
+
19
+ [tool.setuptools.packages.find]
20
+ where = ["src"]
21
+
22
+ [tool.setuptools.package-data]
23
+ metawalker = ["policies/*.npy"]
24
+
25
+ [tool.pytest.ini_options]
26
+ pythonpath = ["src", "."]
27
+ testpaths = ["tests"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,19 @@
1
+ """metawalker: learned black-box optimizers for big, complex search spaces.
2
+
3
+ from metawalker import minimize, minimize_auto, recommend, certify, importance, compare, train
4
+ from metawalker.sklearn import MetaWalkerCV # needs scikit-learn
5
+ """
6
+ from .api import METHODS, Result, minimize
7
+ from .asktell import AskTell
8
+ from .certify import Certified, certify
9
+ from .compare import Comparison, compare
10
+ from .importance import Importance, importance
11
+ from .problems import FAMILIES, TestProblem, make_problem
12
+ from .recommend import Recommendation, minimize_auto, recommend
13
+ from .space import Categorical, Float, Int, Space
14
+ from .train import TrainedPolicy, train
15
+
16
+ __all__ = ['minimize', 'minimize_auto', 'recommend', 'Recommendation', 'certify', 'Certified', 'importance',
17
+ 'Importance', 'compare', 'Comparison', 'train', 'TrainedPolicy', 'AskTell', 'Result', 'METHODS',
18
+ 'make_problem', 'TestProblem', 'FAMILIES', 'Space', 'Float', 'Int', 'Categorical']
19
+ __version__ = '0.2.0'