Programmatic space search with a focus on flexibility
Project description
superparams
A high-level experiment manager.
- 📄 One Python file for your entire experiment, flexible and easily versionable.
- 💚 No boilerplate like parsing a text file with configuration variables, multiprocessing code, nor logging/saving results.
- ♻️ Easily re-run failed experiment settings.
Installation
pip install superparams
View on PyPi.
Usage
Superparams incentivises use of Python's built-in dataclass to specify both the parameters and the experiment-specific logic in one place. It bundles this with a bunch of quality-of-life improvements for managing your experiments.
# file: experiments/params.py
import dataclasses as dc
import superparams as sp
@dc.dataclass
class Hyperparams(sp.Experiment):
steps :int = 100
batch_size :int = sp.search(16, 32)
# and automatic string substitution!
dataset_path :str = 'data/raw/{dataset_name}'
dataset_name :str = sp.search('alphabet', 'numbers')
def run(self) -> dict | pl.DataFrame:
'''
Runs this setting of parameters (override this method)
Auto-stores the returned dict/pl.DataFrame in a parquet table.
'''
results = {
'total samples': self.batch_size * self.steps,
'path': self.dataset_path
}
print(results)
# automatically save results in a parquet file by returning them
return results
def format_results(self, results: pl.DataFrame) -> None | pl.DataFrame:
'''
Useful for plotting and post-processing,
optionally can return formatted dataframe to be saved
'''
results.plot.bar('path', 'total_samples').show()
This constructs an iterator to grid-search the parameter settings,
meaning you could add a snippet like the following to invoke your experiment
from the terminal with python -m experiments.params.
# file: experiments/params.py
if __name__ == '__main__':
for h in Hyperparams():
results = h.run()
print(f'Setting ({h.steps}, {h.batch_size}): {results}')
# Setting (100, 16): {'total_samples': 1600}
# Setting (100, 32): {'total_samples': 3200}
But we promised no boilerplate! Instead, you can invoke from the terminal, which handles result-caching for you, and enables easy multiprocessing.
experiment params.Hyperparams --n_proc 2
This will:
- print a nice overview of the running experiments
- store results and log under
experiments/progress/params/Hyperparams - prompt you to resume interrupted/failed experiment settings
- do the multiprocessing for you :)
[!WARNING] Python-native
multiprocessingshares theHyperparamsdata with each process by pickling it!. This is woefully inefficient, and poses a massive bottleneck if sharing >10MB data. Consider refactoring such that eachrunmethod instantiates this data itself.In the future, I may do a refactor that shares the data more efficiently; but this is not trivial in Python and definitely not possible in all cases. See Python docs.
Flexibility
Dataclasses don't require Java-style repetitive constructors. To modify your hyperparameter combination, simply instantiate it as follows.
Hyperparams(batch_size=search(2,4,8))
Multiprocessing
You can run multiple settings on multiple processes.
params = Hyperparams()
params.run_all(n_proc = os.cpu_count() - 2)
Also note that Experiment objects have access to concurrency-related fields initialised by superparams. These are:
rank: the process id of this experiment setting, i.e.rank in {0,1,2,3}ifn_proc = 4.n_proc: parameter passed to then_procfield.
Mutable Dataclass Attributes
Python throws a tantrum if you try to assign a mutable value to a dataclass:
@dataclass
class Params(Experiment):
iterable = [1,2,3]
# Error > you should use field(default_factory=lambda: [1,2,3])
This is ugly. Python does this to protect you in case you were to instantiate a second set of Params(), and modify the iterable. As it's a class attribute, you'd be modifying both instantiated Params objects.
I think this is stupid and limits the potential of dataclasses (especially given that frozen = True is a setting that enforces this yet still raises the error). For now, using iterable = search([1,2,3]) should work. In the future, I may rewrite the built-in dataclass to not follow this pattern to make it more explicit.
Note a similar thing is much more likely to happen in functions, where it is not guarded by Python. E.g. in
def function(items = [1,2,3])
print(items)
items.append(4)
function() # [1,2,3]
function() # [1,2,3,4]
Further reference in Python docs.
TODO
-
just running
experimentlists the available experiments. -
improve progress reporting to work better across multiple processes.
-
caching experiments based on the hyperparameters,
-
allowing operations based on the hyperparameters in
format_resultse.g.max(dimension). -
try merging
.progress.locklockfile with the.progressfile, to avoid this litter, requires multiprocessing tests :). -
allow running multiple experiments defined in one file. I.e.
experiment datasetruns all classes found in dataset. We should be able to do this by checking if the final component is a filename, or a directory name. -
allow relative imports in libraries imported by experiment.
-
Encapsulate current
__main__into a class, so the user can just addpython # some/path/to/custom/experiments/__main__.py from superparams import entrypoint entrypoint() -
smarter experiment lookup: users may want to have a single file for all their experiments, or spread it into different folders.
experiment RQ1runs all experiments in the fileRQ1.pyexperiment index.RQ1runs the experimentRQ1in the fileindex.py, or the fileRQ1.pyin the folderexperiments/index.
-
dataclassesimprovements -
get rid of this annoying
@dataclassannotation, replace with@experiment; force immutability for provided mutable class attributes, rather thandataclassesdefault approach. -
check compatitibility with
python=3.10, python=3.11.
Alternatives
Any decent package should list viable alternatives. Here are some that I considered, but ended up building this package instead.
- wandb sweeps is best used for Bayesian hyperparameter search to optimise a DL model; but requires specifying settings in JSON files.
- ray tune enables SOTA algorithms like PBT (similar to genetic optimisation) and HyperBand/ASHA (large population with early stopping), and allows for relatively unsupervised optimisation by specifying a search space and objective in Python. It is also compatible with Keras Hyperopt and Pytorch Optuna.
- orion is similar to ray tune, but more or less a wrapper around an argument parser you need to set up yourself (so you have to specify everything in plain-text cli commands).
- hydra is probably most-similar in features to
superparams, but relies onyamlfor specification and doesn't collate results nicely into apandasdataframe.
I think of superparams as more open-ended than ray-tune: there may not be a direct objective to optimise as the right objective is often not yet established in the early stages of experimentation. And, by allowing everything to be specified in a single Python dataclass, you maintain flexibility by not assuming that the entire optimisation is a black-box. To me, it is valuable to be able to specify all parameters and logic in a single place, completely in lsp-understandable python.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file superparams-0.0.11.tar.gz.
File metadata
- Download URL: superparams-0.0.11.tar.gz
- Upload date:
- Size: 22.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.4.28
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
cc13866f3d17391852b616799ef8be34d817e32b0810636f7fa5b38d36fbec7d
|
|
| MD5 |
a79ae0cf5156ca038ec4a98d120e4232
|
|
| BLAKE2b-256 |
0d8f78d367a3df2fc965e1d592fc7e1f640ca2a51ce7af5a6c67d4ceaa142cf3
|
File details
Details for the file superparams-0.0.11-py3-none-any.whl.
File metadata
- Download URL: superparams-0.0.11-py3-none-any.whl
- Upload date:
- Size: 14.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.4.28
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6917dc5e0ee5d93919423dd9fae774f285a6432abe28a9239ea4c1ca3ac72b91
|
|
| MD5 |
5aa06389a550ddc1de0970f89c9f7f6c
|
|
| BLAKE2b-256 |
d575f710f14663a657d2162f145e9f0fc2391297edede8ac930f42eddc340775
|