=Paper=
{{Paper
|id=Vol-3226/paper10
|storemode=property
|title=Confr – A Configuration System for Machine Learning Projects
|pdfUrl=https://ceur-ws.org/Vol-3226/paper10.pdf
|volume=Vol-3226
|authors=Mattias Arro
|dblpUrl=https://dblp.org/rec/conf/itat/Arro22
}}
==Confr – A Configuration System for Machine Learning Projects
==
confr – A Configuration System for Machine Learning Projects Mattias Arro1 1 www.uxo.ai Abstract Finding a performant machine learning model usually requires exploring different combinations of model hyperparameters, preprocessing steps, data generation and train logic. To facilitate a clear analysis of the factors that determine accuracy, it is useful to make the data processing and train pipeline highly configurable such that a combination of a code version and configuration file uniquely determines the behaviour of the system. A poor configuration system can lead to repetitive code that is hard to maintain, understand, and brittle due to insufficient configuration validation logic. This paper outlines the design and usage of confr, a concise and flexible configuration system geared towards Python-based machine learning projects. It combines some of the capabilities of commonly used systems (such as gin-config, OmegaConf, and Hydra) into a library which aims to reduce repetitive code and maintenance overhead. It can be used both as part of a notebook-based and script-based workloads, and can be used for ensuring that there is no accidental difference between inference-time and train-time behaviour. Keywords machine learning, configuration, experiment management, reproducibility 1. Introduction many train runs (on the same code and data version) cre- ates a dataset of hyperparameter sets and corresponding The goal of machine learning (ML) practitioners is to find evaluation metrics. The relationship between hyperpa- a ”good model”. This is broadly determined by three fac- rameters and metrics can be analysed for insights that tors: (training and validation) data, code (preprocessing, inform future hyperparameter choices or code changes. model implementation, train loop), and hyperparame- A highly configurable train pipeline also lends itself for ters (configuration). By hyperparameters we mean any automatic model tuning approaches such as brute-force non-learnable parameter/configuration that influences grid/random search, or methods like Bayesian optimi- how the code gets executed, which may be in the data sation that use ML to find values for hyperparameters processing, model intialisation or inference, or train loop. which maximise validation accuracy. When running experiments, exact and immutable ver- There are many ways to make a system configurable, sions of the three should always be stored, so that we such as creating an ad hoc solution from scratch or using can (1) analyse the factors that influence accuracy and a 3rd party config1 system. The following is a list of (2) reproduce the results of an earlier experiment. qualities we would expect from a config system, which Early in developing a ML system, code tends to hard- we will later use to evaluate our proposed system confr code most choices for data processing, model implemen- against alternatives. tation and hyperparameters. Experimentation in this setting would require changes to the code, which means 1. Minimise boilerplate code. There should not that to compare two experiments one needs to find the be much repetitive code to have a highly config- differences of code used in each train run, or rely on the urable system. experimenter’s description of the hypothesis that was 2. Minimise repetitive config. There should be tested. Given that most ML experiments are done in ways to reuse, rather than repeat, individual con- notebooks where comparing code with version control is fig values. difficult, comparing large numbers of such experiments 3. Composability of config objects. It should be is not feasible. possible to reuse and compose different configu- A better approach is to make the code highly config- rations, which is crucial for large systems. urable, so that alternative behaviours can be achieved by 4. Low maintenance overhead. A config system using different hyperparameter values. Now triggering should reduce (rather than add to) the difficulty of refactoring and developing the code base. ITAT’22: Information technologies – Applications and Theory, Septem- 5. Low cognitive load. A config system should ber 23–27, 2022, Zuberec, Slovakia make it easy to understand which variables are Envelope-Open mattias.arro@gmail.com (M. Arro) © 2022 Copyright for this paper by its authors. Use permitted under Creative Commons License 1 Attribution 4.0 International (CC BY 4.0). We use the words ”config” and ”configuration” interchangeably, as CEUR Workshop Proceedings http://ceur-ws.org ISSN 1613-0073 CEUR Workshop Proceedings (CEUR-WS.org) is common in industry. configurable and where that configuration comes 2. Related Work from. It is highly related (but not fully deter- mined) by the following three qualities. In this section we provide a brief overview of existing 6. Clearly identifyable configurable arguments. configuration systems. We first describe what typical It should be clear just by looking at a function if its ad hoc solutions look like, and then look at three config argument is a configurable hyperparameter. This systems which are commonly used4 for ML workloads: improves readability and decreases the chance of OmegaConf [2], gin-config [1], and Hydra [3]. gin-config accidentally forgetting to provide a config value. is explicitly designed for ML work, while OmegaConf 7. Consistent mapping between config keys and Hydra are generic config systems. and variables / arguments. The system should encourage a one-to-one mapping between keys2 2.1. Ad Hoc Systems in the config file and configurable arguments. Machine learning projects that do not use a specialised Such mapping alleviates the cognitive load in config system tend to consist of Python scripts where hy- translating differences between config keys and perparameters are defined as command line arguments. variable names, and makes it easy to search for Libraries like argparse5 and click6 can be used for easier all places where a config key is used. parsing of command line arguments or providing argu- 8. Global config values. In most cases we expect ments via environment variables. The individual config the value of a config key to be the same across the values get passed to downstream functions where neces- code base, and a config system should encourage sary. this. For example, if a preprocessing function Alternatively, a regular Python dictionary containing assumes max_img_dimensions = (128, 128) , the configuration might be read from a YAML/JSON file, then so should the function which builds the neu- and passed along to functions that depend on it. Either ral network whose input tensor would have the the full configuration object or individual config values shape (128, 128, 3) - otherwise the model’s in- might be passed along, depending on programming style. put dimensions would be incompatible with the It is generally not easy to unify such CLI-based and file- images created by the preprocessor. based configuration styles without using a specialised 9. Centralised, multi-key validation of config. config system. Validation of config values should be centralised in a single place, rather than scattered around the project in an ad hoc manner. This (1) ensures we 2.2. OmegaConf fail quickly with a helpful message when read- OmegaConf builds on YAML file format, and adds a pow- ing a faulty config (rather than waiting for the erful interpolation mechanism, which enables accessing relevant code path to be reached, which may hap- config keys from other parts of the file. The below exam- pen at a much later state), and (2) allows defining ple shows both absolute (${server.host}$ ) and relative validation logic that sets constraints on several (${.url}$ ) references to other parts of the config file. config keys simultaneously. 10. Usable in a notebook as well as CLI. The system should be easy to use in an exploratory notebook-based setting, where one might want to dynamically (re-)define and access config val- ues, and to ultimately write the current active configuration to a file. It should also work in a script-oriented CLI3 setting, where main config- uration is loaded from a file, and certain config OmegaConf configuration is represented as a Python values or whole sections can be overridden via object, which can be accessed as a nested object or dic- command line arguments. tionary: 11. Configurable Python references and single- tons. It should be possible to refer to Python ob- jects (functions, classes, constants, objects) and create global singletons from callable references (functions, classes). 4 By ”commonly used” we mean systems we were able to find by doing relevant Google searches, reading relevant discussion threads, 2 Configuration files are (possibly nested) key-value pairs. We call and looking at GitHub activity for these projects. 5 the names of configurable hyperparameters as ”config keys”. https://docs.python.org/3/library/argparse.html 3 6 CLI - Command Line Interface https://click.palletsprojects.com/ object as an argument, which can be passed to down- stream functions. Therefore the config files support all the syntax and functionality of OmegaConf, and some features that Hydra adds. For detailed examples, refer to Hydra documention7 . 2.3. gin-config gin-config makes use of @gin.configurable decora- tors around function and class definitions. For func- tions (and initialisers of classes) decorated like this, ar- guments are substituted from the global config which is initialised with gin.parse_config_file("conf.gin") . Attributes with default value of gin.CONFIGURED need to have a config specified in the .gin config file; other arguments can (but do not have to be) configured with gin. 3. Overview of confr In this section, we show the basics of how to use confr. For complete and up-to-date documentation see https://github.com/mattiasarro/confr. 3.1. Basic Usage In confr, configs are initialised similarly to gin-config: functions and classes can be decorated with confr.bind , which ensures that function and class initialiser argu- The config file is a custom text format, which is a sim- ments will be substituted from the currently active config. plified subset of Python. Using dnn.num_outputs = 10 The following example shows a function that expects at would ensure all functions named dnn will have least the num_outputs key (and optionally layer_sizes ) the value of num_outputs substituted as 10 . Us- to be defined in conf/base.yaml. Before calling any confr- ing path.to.mymodule.dnn.num_outputs = 10 would configured functions, confr.init must be called, which ensure it happens only to the function in the creates an implicit global config object. path.to.mymodule module. As special syntax, values that start with ”@” refer to other gin-configurable func- tions or classes; values that start with ”@” and end with ”()” first get called before being passed as arguments. 2.4. Hydra Hydra is a feature-rich config system. The entrypoint function of the program using Hydra should be anno- tated with a @hydra.main decorator, which defines the directory where config files are stored, and config name (filename without the .yaml extension in the directory). When called, the function receives an OmegaConf cfg 7 https://hydra.cc/docs/intro/ All config files use the special form of YAML used for this is to make a preprocessing or augmentation func- by OmegaConf (with a few special cases described in tion as a configurable argument, so that you can try out the next subsections). In the default case, the argu- different preprocessors without changing the code. For ments get bound to top-level config keys of the same example: name in the YAML file. So in the above example, our conf/base.yaml could look like this: 3.2. Custom Config Key to Argument Mapping In some cases, a one-to-one mapping of config keys to function argument names can be limiting, so we offer two ways to customise this. Assume we have the following config: When predict is called, the Python module my.module is imported and the resize_and_crop at- tribute is read from it. Any Python object could be refer- enced in config files - function, class, variable, constant - as long as its module is available on PYTHONPATH, which usually includes all modules in the project root as When wrapping a function with confr.bind , we could well as installed libraries such as tensorflow. tell it to only map keys under the neural_net config key: Config values which start with a ”@” and end with ”() ” are singletons - Python references which get called before becoming part of the current active config and being passed as keyword arguments. For example you might define an encoder: @my.module.my_encoder() key-value pair in the config. Now if a function defines an argument as encoder=confr.value , then @my.module gets imported and its my_encoder() callable gets called The other option is to pass the full path in the config before being passed as the argument value. Once to confr.value : my_encoder() is called, its return value gets mem- oized and any subsequent functions which use the encoder config will receive the same, pre-initialised object. Singletons can be referenced in other parts of the config using the familiar OmegaConf format of ${config_key.subkey.singleton}$ . Note that my_model1 and my_model2 in the following listing are the same object. 3.3. Python References and Singletons confr adds special syntax to the YAML format supported by OmgaConf, which can be used to load Python object or initialise global singletons. Config values which start with a ”@” are ”Python references”8 . A common use 8 References to Python objects and references to other config values in the file look quite similar, since they both use dot notation. Python references start with a ”@”, and they refer Python modules like in absolute imports (which usually correspond to folder structure). Referencing other parts of a config file start with a ”$” and refer to the ”path” in the YAML file, which follows the nesting of config keys. 3.4. Scoped Arguments in Singletons list of p_thresh values and calculate accuracy for each p_thresh . If you would like to configure input arguments specifi- Our first attempt at solving this would look like this: cally for singletons, you can do the following: Now my_model1 singleton will be initialized with location="/path/to/weights.h5" and my_model2 singleton will be initialized with location="/path/to/weights2.h5" . This way they can both define an input argument called location and still receive a unique value at initialization time. This would work if precision is the only place that We call my_model1/location as a scoped argument, i.e. uses the p_thresh that is passed in. But if precision the value of location is present in only the my_model1 calls sub_function whose p_thresh value comes from singleton scope. confr, then the value of p_thresh in sub_function will Note that you can still use the regular, non- be the same as in the config file and not the one we passed scoped arguments along with scoped ones. For ex- to precision . What we need here is to temporarily set ample, both my_model1 and my_model2 might define the value of p_thresh config key in the whole confr, like img_h=confr.value , and this value will be the same this: when initializing both singletons. 3.5. Call-time Overrides When running a Python program configured with confr, individual values can be overriden in two ways: 1. Passing command-line arguments such as --key1.subkey1=value . 3.7. Config Spanning Multiple Files 2. Setting environment variables such as confr__key1__subkey1=value . Suppose you have the following config files: 3.6. Run-time overrides When working in a notebook, modifying the YAML file to change the active config is cumbersome. You could instead initialize the config at the start of the notebook by selectively providing overrides to the keys you care about like this: If you do not want to re-initialise the whole con- Once the config is loaded, the effective final config fig, but would like to set individual config values, use would look like this, because the _file special key tells confr.set("my_key", value) . Doing this would not confr to take the configuration for neural_net subkeys re-initialise other config keys or singletons that may de- from another file. pend on my_key . But you could also override which neural net config You may also want to provide overrides to config gets used, by passing --neural_net._file=deep when values temporarily, for the duration of calling a func- running the program. Note that there is a convention be- tion (and any downstream functions called by this func- tween the config keys and the folders from where _file tion). For example, you might want to iterate over a references are searched. 3.9. Validation Two types of validations can be done with confr. Each config file with name filename.yaml can have an op- tional filename_types.yaml counterpart, which de- fines the datatype of all (or a subset of) the config keys. Currently, only primitive Python types are supported, 3.8. Accessing the Active Config but more complex solutions will be added. For example: Sometimes we need to explicitly fetch the value of a key in our config system. You can use confr.get and confr.set accessors to modify the current active conf: However, once the config gets complex enough, there is a need to validate different combinations of config values. For example, imagine we have the following config, which states that 50% of the samples come from You can also save the current active config as a YAML labelled dataset, 25% come from data generator 1 and 25% file, for example at the end of training. The code for come from generator 2: training the model and doing inference should be in the same version control project; train-time and inference- time pre-processing should be handled by the same func- tion(s). This way, if code for inference is initialised from the same code revision and active config that was used during training, there would be no accidental difference between train time and test time behaviour. In confr we can define a validator that ensures that everything in samples_per_batch sums to batch_size . 4. Evaluation 5. Low cognitive load. • BAD: gin-config, Hydra. It is not clear, We will now evaluate the competing systems and confr on without reading the configuration file and the desired qualities outlined in the introduction, giving upstream code carefully, which arguments each a somewhat subjective BAD / OK / GOOD mark. are configured and where the value comes 1. Minimise boilerplate code. from. • BAD: ad hoc, OmegaConf. Passing down • OK: ad hoc, OmegaConf, confr. The initial configuration dictionaries or individual cognitive load of understanding how a con- config values can be very verbose. So can figurarion is loaded in ad hoc systems and be setting up CLI arguments in ad hoc sys- OmegaConf is low (for example, it’s easy tems. to understand what reading a YAML file or • OK: Hydra. Different functions can request using argparse does). However it is much a config object, and read individual keys harder to reason about how the whole sys- from it. However generally there is a single tem behaves due to the next three qualities. config object that gets passed along. In confr, it takes more effort to think about many possible places where configurations • GOOD: gin, confr. Configurable function can come from (multiple files, command arguments receive a value directly from line overrides), but it is easier to reason the config system, which is most concise. about the whole system due to the follow- 2. Minimise repetitive config. ing three qualities. • BAD: ad hoc. No way to reuse / refer to 6. Clearly identifyable configurable argu- other values in YAML/JSON files. ments. • OK: gin-config. It is possible to reuse val- • BAD: ad hoc, OmegaConf, Hydra. When ues, but in a cumbersome way, and the con- reading code (that is ”far away” from the fig file format is somewhat verbose (since part that initialises config), it is not clear all occurences of a config value need to be which arguments are configurable. listed). • OK: gin-config. It is possible to make it ex- • GOOD: OmegaConf, Hydra, confr. It is plicit that some arguments should receive possible to reuse values and create concise a value from configuration, but this is not config files. a requirement. 3. Composability of config objects. • GOOD: confr. It is intentionally not pos- • BAD: ad hoc, OmegaConf, gin-config. Not sible to configure an argument in confr supported. without making it explicit in code that the • GOOD: Hydra, confr. Supported. value is configurable. 4. Low maintenance overhead. 7. Consistent mapping between config keys • BAD: ad hoc, gin-config. Passing along and variables / arguments. config objects/values slows down refactor- • BAD: ad hoc, OmegaConf, gin-config, Hy- ing. gin-config also often requires config dra. In all these systems such consistency changes when the relevant code changes is not encouraged, which makes it harder (renaming/moving functions). Ad hoc sys- to read, understand and refactor. tems might need to maintain CLI argument • GOOD: confr. Such consistency is enforced lists. by default, though in rare cases it is possi- • OK: OmegaConf, Hydra. Moving code ble to bypass this (for example when you around requires changes to passing of con- need to use an externally-provided config fig values, but this is less troublesome than file). changes required by gin-config or main- 8. Global config values. taining CLI argument lists in ad hoc sys- • BAD: ad hoc, OmegaConf, Hydra. It is easy tems. to have multiple configuration objects, or • GOOD: confr. Config files and other parts to have a different value in different places of the source generally do not need to be for the same config key. changed on renaming or moving functions, • GOOD: gin-config, confr. There can be because the config file makes no assump- only one globally active configuration. In tions about where the config is used and confr, each config key always has the same config values do not need to be propagated. value. 9. Centralised, multi-key validation of config. There are features not supported by confr which other • BAD: gin-config. No validation system pro- libraries provide that will be added in later versions: tab vided, hard to add one. completion and more detailed type checking provided by Hydra. Hydra also has some features that were intention- • OK: ad hoc, OmegaConf, Hydra. Cen- ally not made part of confr, such as multi-run options, tralised validation system can be added custom working dir and logger config, since these were through user code to OmegaConf or file- not considered relevant with respect to the evalation cri- based ad hoc systems, though this is harder teria, which we considered most useful for a ML-oriented to do in CLI-based ad hoc systems in a con- config system. cise way. Hydra has a validation system, but this is mostly limited to data type based checks, which are insufficient. References • GOOD: confr. Supported, built in. 10. Usable in a notebook as well as CLI. [1] Holtmann-Rice, D., Guadarrama, S., Silberman, N.: Gin Config. https://github.com/google/gin-config • BAD: ad hoc, OmegaConf, gin-config. Ad (2018) hoc systems tend to work well either for [2] OmegaConf. https://github.com/omry/omegaconf notebook-based approaches (e.g. reading (2012) a global YAML file as a config dict), or [3] Yadan, E.: Hydra - A framework for elegantly con- work as a sophisticated CLI script, but not figuring complex applications, https://github.com/- both. There is no support to override op- facebookresearch/hydra (2019) tions from the CLI in OmegaConf and gin- config. • GOOD: Hydra, confr. Can be used in both settings. 11. Configurable Python references and single- tons. • BAD: ad hoc. Usually not supported. • OK: OmegaConf, Hydra. Supported, but cumbersome to use. • GOOD: gin-config, confr. Supported, easy to use. The results are summarised in the following table. From it we can see that confr exhibits the good quali- ties of Hydra and gin-config, while alleviating some of the downsides one or the other. This is not a coincidence, since confr was in many ways inspired by these two, though the exact way confr achieves these qualities may be different.