Skip to content

Commit c663171

Browse files
authored
docs: add ask and tell Documentations (#236)
2 parents 0c2c9c1 + 13b22c7 commit c663171

File tree

6 files changed

+173
-9
lines changed

6 files changed

+173
-9
lines changed

docs/api/index.md

Lines changed: 0 additions & 2 deletions
This file was deleted.

docs/api/overview.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
2+
This content tree contains the core components and utilities of NePS, designed to simplify and enhance the process of running optimization experiments. Below is an overview of the files and their purposes:
3+
4+
- [`neps.run`](neps/api.md): Provides the built-in NePS runtime to **sample new trials** and **evaluate them automatically**.
5+
- [`neps.runtime`](neps/runtime.md): Implements the `Worker`, offering functions to create a worker, sample new trials, and evaluate them.
6+
- `neps.optimizers`:
7+
- [`neps.algorithms`](neps/optimizers/algorithms.md): Contains a collection of optimization algorithms, such as random search, ASHA, PriorBand, HyperBand, and more, for sampling new trials.
8+
- [`neps.AskAndTell`](neps/optimizers/ask_and_tell.md): An alternative to `neps.run` that allows full control of the evaluation loop. This is useful when you don’t want to use NePS’ runtime but still want to benefit from its optimizers and state management.
9+
- [`neps.state`](neps/state/neps_state.md): Manages the state of workers, trials, and optimizers, ensuring reproducibility and continuity.
10+
- [`neps.status`](neps/status/status.md): Provides functions to retrieve the status of a run and export it to CSV files for analysis.
11+
- [`neps.plot`](neps/plot/plot.md): Includes tools to visualize the results of a neural pipeline search run.

docs/getting_started.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,5 +68,8 @@ Understand how to leverage multi-fidelity optimization for efficient model tunin
6868
* **[Utilizing Expert Priors for Hyperparameters](examples/efficiency/expert_priors_for_hyperparameters.md)**:
6969
Learn how to incorporate expert priors for more efficient hyperparameter selection.
7070

71+
* **[Benefiting NePS State and Optimizers with custom runtime](examples/experimental/ask_and_tell_example.md)**:
72+
Learn how to use AskAndTell, an advanced tool for leveraging optimizers and states while enabling a custom runtime for trial execution.
73+
7174
* **[Additional NePS Examples](examples/index.md)**:
7275
Explore more examples, including various use cases and advanced configurations in NePS.

docs/index.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,10 @@ In addition to the features offered by traditional HPO and NAS libraries, NePS s
1717
NePS excels in efficiently tuning hyperparameters using algorithms that enable users to make use of their prior knowledge, while also using many other efficiency boosters.
1818
- [PriorBand: Practical Hyperparameter Optimization in the Age of Deep Learning (NeurIPS 2023)](https://arxiv.org/abs/2306.12370)
1919
- [πBO: Augmenting Acquisition Functions with User Beliefs for Bayesian Optimization (ICLR 2022)](https://arxiv.org/abs/2204.11051) <br /> <br />
20-
1. **Neural Architecture Search (NAS) with Expressive Search Spaces:** <br />
20+
2. **Neural Architecture Search (NAS) with Expressive Search Spaces:** <br />
2121
NePS provides capabilities for designing and optimizing architectures in an expressive and natural fashion.
2222
- [Construction of Hierarchical Neural Architecture Search Spaces based on Context-free Grammars (NeurIPS 2023)](https://arxiv.org/abs/2211.01842) <br /> <br />
23-
1. **Zero-effort Parallelization and an Experience Tailored to DL:** <br />
23+
3. **Zero-effort Parallelization and an Experience Tailored to DL:** <br />
2424
NePS simplifies the process of parallelizing optimization tasks both on individual computers and in distributed
2525
computing environments. As NePS is made for deep learners, all technical choices are made with DL in mind and common
2626
DL tools such as Tensorboard are [embraced](https://automl.github.io/neps/latest/reference/analyse/#visualizing-results).
@@ -101,6 +101,8 @@ Discover how NePS works through these examples:
101101

102102
- **[Utilizing Expert Priors for Hyperparameters](examples/efficiency/expert_priors_for_hyperparameters.md)**: Learn how to incorporate expert priors for more efficient hyperparameter selection.
103103

104+
- **[Benefiting NePS State and Optimizers with custom runtime](examples/experimental/ask_and_tell_example.md)**: Learn how to use AskAndTell, an advanced tool for leveraging optimizers and states while enabling a custom runtime for trial execution.
105+
104106
- **[Additional NePS Examples](examples/index.md)**: Explore more examples, including various use cases and advanced configurations in NePS.
105107

106108
## Contributing

neps/optimizers/ask_and_tell.py

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,21 +12,25 @@
1212
[`tell()`][neps.optimizers.ask_and_tell.AskAndTell.tell] results back to the optimizer.
1313
1414
```python
15-
from neps import AskAndTell
15+
import neps
1616
1717
# Wrap an optimizer
18-
my_optimizer = AskAndTell(MyOptimizer(space, ...))
18+
space = neps.SearchSpace({"a": neps.Float(0, 1), "b": neps.Integer(1, 10)})
19+
my_optimizer = neps.AskAndTell(optimizer=neps.algorithms.random_search(space))
1920
2021
# Ask for a new configuration
2122
trial = my_optimizer.ask()
2223
2324
# The things you would normally get into `evaluate_pipeline`
24-
config_id = trial.config_id
25+
config_id = trial.metadata.id
2526
config = trial.config
26-
previous_config_id = trial.metadata.previous_trial_id
27-
previous_trial_path = trial.metadata.previous_trial_location
27+
# other metadata you might want to use:
28+
# trial.metadata.previous_trial_id, trial.metadata.previous_trial_location
2829
2930
# Evaluate the configuration
31+
def evaluate(config):
32+
# Dummy evaluation function
33+
return config["a"] * 2 + config["b"]
3034
loss = evaluate(config)
3135
3236
# Tell the optimizer the result
Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
"""
2+
# AskAndTell Example: Custom Trial Execution with NePS
3+
4+
This script demonstrates how to use the `AskAndTell` interface from NePS to implement a custom trial execution workflow.
5+
The `AskAndTell` interface provides full control over the evaluation loop, allowing you to manage how trials are executed
6+
and results are reported back to the optimizer. This is particularly useful when you need to handle trial execution manually.
7+
8+
## Aim of This File
9+
10+
The goal of this script is to run a **successive halving** optimization process with 3 rungs. The first rung will evaluate
11+
9 trials in parallel. The trials are managed manually using the `AskAndTell` interface, and the SLURM scheduler is used
12+
to execute the trials. This setup demonstrates how to efficiently manage parallel trial execution and integrate NePS
13+
with external job schedulers.
14+
15+
## How to Use This Script
16+
17+
1. **Define the Search Space**:
18+
The search space is defined using `neps.SearchSpace`.
19+
20+
2. **Initialize the Optimizer**:
21+
We use the `successive_halving` algorithm from NePS to optimize the search space. The optimizer is wrapped with
22+
the `AskAndTell` interface to enable manual control of the evaluation loop.
23+
24+
3. **Submit Jobs**:
25+
- The `submit_job` function submits a job to the SLURM scheduler using a generated script.
26+
- The `get_job_script` function generates a SLURM job script that executes the `train_worker` function for a given trial.
27+
28+
4. **Train Worker**:
29+
- The `train_worker` function reads the trial configuration, evaluates a dummy objective function, and writes the
30+
results to a JSON file.
31+
32+
5. **Main Loop**:
33+
- The `main` function manages the optimization process:
34+
- It launches initial jobs based on the number of parallel trials specified.
35+
- It monitors the status of active jobs, retrieves results, and submits new trials as needed.
36+
- The loop continues until all trials are completed.
37+
38+
6. **Run the Script**:
39+
- Use the command line to run the script:
40+
```bash
41+
python ask_and_tell_example.py --parallel 9 --results-dir results
42+
```
43+
- `--parallel`: Specifies the number of trials to evaluate in parallel initially.
44+
- `--results-dir`: Specifies the directory where results will be saved.
45+
46+
## Key Features Demonstrated
47+
- Custom trial execution using SLURM.
48+
- Integration of NePS optimizers with manual control over the evaluation loop.
49+
- Efficient management of parallel trials and result reporting.
50+
51+
This script serves as a template for implementing custom trial execution workflows with NePS.
52+
"""
53+
import argparse
54+
import time
55+
from pathlib import Path
56+
import json
57+
import neps
58+
import os
59+
import subprocess
60+
import json, sys
61+
62+
from neps.optimizers.ask_and_tell import AskAndTell
63+
64+
def submit_job(pipeline_directory: Path, script: str) -> int:
65+
script_path = pipeline_directory / "submit.sh"
66+
print(f"Submitting the script {script_path} (see below): \n\n{script}")
67+
68+
# You may want to remove the below check and not ask before submitting every time
69+
script_path.write_text(script)
70+
os.system(f"sbatch {script_path}")
71+
output = subprocess.check_output(["sbatch", str(script_path)]).decode().strip()
72+
job_id = int(output.split()[-1])
73+
return job_id
74+
75+
def get_job_script(pipeline_directory, trial_file):
76+
script = f"""#!/bin/bash
77+
#SBATCH --job-name=mnist_toy
78+
#SBATCH --partition=bosch_cpu-cascadelake
79+
#SBATCH --output={pipeline_directory}/%j.out
80+
#SBATCH --error={pipeline_directory}/%j.err
81+
python -c "import neps.neask_andtell_example; ask_andtell_example.train_worker('{trial_file}')"
82+
"""
83+
return script
84+
85+
def train_worker(trial_file):
86+
trial_file = Path(trial_file)
87+
with open(trial_file) as f:
88+
trial = json.load(f)
89+
90+
config = trial["config"]
91+
# Dummy objective
92+
loss = (config["a"] - 0.5)**2 + ((config["b"] + 2)**2) / 5
93+
94+
out_file = trial_file.parent / f"result_{trial['id']}.json"
95+
with open(out_file, "w") as f:
96+
json.dump({"loss": loss}, f)
97+
98+
def main(parallel: int, results_dir: Path):
99+
space = neps.SearchSpace(
100+
{"a": neps.Integer(1, 13, is_fidelity=True), "b": neps.Float(1, 5)}
101+
)
102+
opt = neps.algorithms.successive_halving(space, eta=3)
103+
ask_tell = AskAndTell(opt)
104+
105+
results_dir.mkdir(exist_ok=True, parents=True)
106+
active = {}
107+
108+
# launch initial jobs
109+
for _ in range(parallel):
110+
trial = ask_tell.ask()
111+
if trial is None:
112+
break
113+
trial_file = results_dir / f"trial_{trial.id}.json"
114+
with open(trial_file, "w") as f:
115+
json.dump({"id": trial.id, "config": trial.config}, f)
116+
job_id = submit_job(results_dir, get_job_script(results_dir, trial_file))
117+
active[job_id] = trial
118+
119+
# monitor loop
120+
while active:
121+
for job_id, trial in list(active.items()):
122+
result_file = results_dir / f"result_{trial.id}.json"
123+
if result_file.exists():
124+
result = json.load(result_file.open())
125+
ask_tell.tell(trial, {"objective_to_minimize": result["loss"]})
126+
del active[job_id]
127+
new_trial = ask_tell.ask()
128+
if new_trial:
129+
new_file = results_dir / f"trial_{new_trial.id}.json"
130+
json.dump({"id": new_trial.id, "config": new_trial.config}, new_file.open("w"))
131+
new_job_id = submit_job(results_dir, get_job_script(results_dir, new_file))
132+
active[new_job_id] = new_trial
133+
time.sleep(5)
134+
135+
if __name__ == "__main__":
136+
parser = argparse.ArgumentParser()
137+
parser.add_argument(
138+
"--parallel", type=int, default=9,
139+
help="Number of trials to evaluate in parallel initially"
140+
)
141+
parser.add_argument(
142+
"--results-dir", type=Path, default=Path("results"),
143+
help="Path to save the results inside"
144+
)
145+
args = parser.parse_args()
146+
main(args.parallel, args.results_dir)

0 commit comments

Comments
 (0)