Quickstart
While this is a quickstart, and you can play with and modify any of these examples for your own use. Reading through the Eunomia overview will greatly help.
The most important aspects to understand from the Eunomia overview are that:
- Individual "input configs" called "Options", listed in the
__defaults__
keys, are merged in a depth first search fashion starting from the user defined "entrypoint" to form the "output config" or "merged config".W.I.P:
Individual "input configs" are called "Options" because they can be overridden from the command line. Options are mutually exclusive and are contained in a "Group"- Groups and Options can be nested within Groups. Options however cannot contain children. An input config always has a root group.
- When input configs are merged into the output config, the
__package__
reserved key defines the new path in the output config. By default this path is<group>
which is an alias for that path to the current group that the option resides in. The other common alias is<root>
or a non-prefixed merge into the output config.
- All eunomia reserved keys with the prefix and suffix
__
(eg.__defaults__
and__package__
) are stripped from the input configs when merging takes place. - Variable substitution can take place during merging, only variables already merged into the output config via DFS are made available for substitution.
Simple YAML Example
Given the following input configs:
Input Configs
```yaml
./configs/default.yaml
defaults: - framework: betavae - dataset: shapes3d
trainer:
epochs: 100
yaml
./configs/framework/betavae.yaml
target: 'disent.frameworks.vae.unsupervised.BetaVae'
beta: 4
yaml
./configs/dataset/shapes3d.yaml
target: 'disent.data.groundtruth.Shapes3dData' folder: '/tmp/data/shapes3d' ```
With the YAML backend, folders and nested folders indicate groups, while YAML files indicate options or input config files.
We generate our output config by specifying the entrypoint as the option
default
which resides in the root group /
.
Code
```python3 from eunomia import eunomia_load from ruamel import yaml
config = eunomia_load('./configs', 'default')
keys are ordered by insertion order
print(yaml.round_trip_dump(config), end='') ```
The resulting output config of this entrypoint and input config in yaml format is:
Output Config
yaml
trainer:
epochs: 100
framework:
_target_: disent.frameworks.vae.unsupervised.BetaVae
beta: 4
dataset:
_target_: disent.data.groundtruth.Shapes3dData
folder: /tmp/data/shapes3d
Equivalent Python
The above yaml input config generates the following representation behind the scenes using the yaml backend.
If you would prefer to specify everything manually in python we can use the following equivalent pythonic nested approach, with the Object backend.
Remember, nested Group objects correspond to folders in the previous example, and Option objects correspond to YAML files.
Input Configs
```python3 from eunomia import eunomia_load from eunomia.config import Group, Option from ruamel import yaml
group = Group({ 'framework': Group({ 'betavae': Option({ 'target': 'disent.frameworks.vae.unsupervised.BetaVae', 'beta': 4, }) }), 'dataset': Group({ 'shapes3d': Option({ 'target': 'disent.data.groundtruth.Shapes3dData', 'folder': '/tmp/data/shapes3d', }) }), 'default': Option( data={ 'trainer': { 'epochs': 100 } }, defaults=[ 'framework/' + 'betavae', 'dataset/' + 'shapes3d', ] ) })
config = eunomia_load(group, 'default')
keys are ordered by insertion order
print(yaml.round_trip_dump(config), end='') ```
The resulting output of the merged config in yaml format is:
Output Config
yaml
trainer:
epochs: 100
framework:
_target_: disent.frameworks.vae.unsupervised.BetaVae
beta: 4
dataset:
_target_: disent.data.groundtruth.Shapes3dData
folder: /tmp/data/shapes3d
Custom Backends
All backends in fact generate these Group
and Option
objects behind the
scenes for merging by the Loader
. You can easily write your own backend.
Custom Backends
The eunomia_loader
helper function automatically determines the correct
backend to use based on the input datatype.
- string are considered to be paths to the root config folder for the YAML backend.
- Group objects are passed to the Object backend.
- Dictionaries although not covered in the quickstart are passed to the Dictionary backend.
If you wish you can even write your own backends by subclassing
`#!python eunomia.backend.Backend`. The overridden function should
return a new Group
object from the data passed to the constructor.
You can pass the instantiated version of your backend to the eunomia
,
eunomia_loader
or eunomia_runner
functions with the backend=
parameter.
Entrypoint & Packages
You'll notice that in the previous example we had the __defaults__
key
which contained keys and values corresponding to the nested options which
should be merged into the config.
- keys correspond to a nested group:
/group/subgroup
is an absolute group path starting from the rootgroup/subgroup
is a relative group path starting from the group containing the option.
- values correspond to the name of an option in the group specified by the key.
If we add new files to the YAML example, we can change these defaults to point to them.
Input Configs
```yaml
./configs/alternate.yaml
defaults: - /dataset: dsprites - /framework: vae
trainer: epochs: 50 ```
```yaml
./configs/dataset/dsprites.yaml
package: data.groundtruth
target: 'disent.data.groundtruth.DSpritesData' folder: '/tmp/data/dsprites' ```
```yaml
./configs/framework/vae.yaml
package:
target: 'disent.frameworks.vae.unsupervised.Vae' ```
Notice that various other differences exist from the previous example.
Input Changes
-
In
configs/alternate.yaml
we use absolute instead of relative paths to the groups in the defaults list, also the defaults list is reordered, which means that this time the dataset is visited before the framework by the merge algorithm. -
In
configs/dataset/dsprites.yaml
we explicitly specify the packagedata.groundtruth
instead of the default package<group>
. -
In
configs/framework/vae.yaml
we explicitly specify the package<root>
instead of the default package<group>
.
Selecting the new entrypoint is as simple as changing the 'default'
argument to 'alternate'
in the first YAML example.
Code
```python3 from eunomia import eunomia_load from ruamel import yaml
changing the entry point gives as an example of why
eunomia is so powerful! We can reuse options while
only merging a subset of them.
config = eunomia_load('./configs', 'alternate')
keys are ordered by insertion order
print(yaml.round_trip_dump(config), end='') ```
The resulting output of the merged config in yaml format is:
Output Config
yaml
trainer:
epochs: 50
data:
groundtruth:
_target_: disent.data.groundtruth.DSpritesData
folder: /tmp/data/dsprites
_target_: disent.frameworks.vae.unsupervised.Vae
Variable Substitution
Eunomia supports variable interpolation. We add a new option at the root of the
configs directory called advanced.yaml
to demonstrate these features.
Input Configs
```yaml
./configs/advanced.yaml
defaults: - framework: betavae
trainer: epochs: 100
subbed: # we can reference values, if the reference template ${...} is the only component # of the string, the actual value, not necessarily as string, is returned. epochs_ref: ${trainer.epochs} beta_sub_ref: 'beta is ${framework.beta}'
# we can evaluate expressions, if the expression template is the only component # of the string, the actual value, not necessarily as string, is returned. beta_expr: ${=conf.framework.beta * 2} epochs_sub_expr: 'epochs is ${=conf.trainer.epochs + 42}'
# fstrings don't need to be nested within expression templates ${=f'...'} # they are automatically detected and evaluated fstring: f'{conf.trainer.epochs:05d}' ```
String substitution is based heavily on f-strings. There are three supported types of substitutions that can occur:
- References:
...${<ref>}...
eg.${trainer.epochs}
is replaced with100
. - Expressions:
...${=<expr>}...
or...${=<expr>;}...
eg.${=conf.trainer.epochs + 42}
evaluates to142
. While attempts are made to enforce the safety of evaluated expressions, no guarantee can be made. Do not evaluate untrusted code! - F-Strings:
f"<f-string>"
orf'<f-string>'
, these are special cases of the Expressions that only evaluate as normal pythonic f-strings. Behind the scenes they are wrapped as if they were${=f"<f-string>"}
or${=f'<f-string>'}
Raw Values
Since string substitution takes place by parsing a string, if the expression or reference is the only component of the string, then the raw value is used and it is not converted to a string.
Note the resulting value for subbed.beta_expr
will be the number 8
while the value for
subbed.beta_sub_ref
will be the string 'beta is 4'
Please note that the interpreter only supports a limited subset of python, and it can only interpret expressions, not statements:
Interpreter Limitations
The python interpreter for these expressions supports a very limited subset of the language. Statements,
assignments, lambdas are not supported. Various common symbols are made available by
default similar to the asteval
library, as well as conf
which points to the currently merging config.
Possible Dictionary Parsing Bug
The semicolon ;
can usually be left out at the end of an expression, however due to a limitation
of the current grammar used to parse expressions, the semicolon may need to be left in when used
to evaluate dictionaries. eg. ${={'foo': 'bar'};}
instead of ${={'foo': 'bar'}}
As with the previous example, we change the entrypoint to 'advanced''
.
Code
```python3 from eunomia import eunomia_load from ruamel import yaml
config = eunomia_load('./configs', 'advanced')
keys are ordered by insertion order
print(yaml.round_trip_dump(config), end='') ```
The resulting output of the merged config in yaml format is:
Output Config
yaml
trainer:
epochs: 100
subbed:
epochs_ref: 100
beta_sub_ref: beta is 4
beta_expr: 8
epochs_sub_expr: epochs is 142
fstring: '00100'
framework:
_target_: disent.frameworks.vae.unsupervised.BetaVae
beta: 4