Package: omegaconf-argparse
omegaconf-argparseIntegration between Omegaconf and argparse for mixed config file and CLI argumentsFlexible configuration management is important during experimentation, e.g. when training machine learning models.Ideally, we want both a configuration file to hold more "stable" hyperparameter values and the flexibility to
change values through command line arguments for rapid experimentation.This package provides a barebones solution based on the excellentOmegaConfpackage.Specifically, we extend theOmegaConfclass with a staticfrom_argparsemethod to parse arguments provided using argparse,
and provide utility functions to merge the default CLI values, YAML configuration file values and user provided CLI arguments.InstallationInstall package from PyPi:pip install omegaconf-argparseThen in your code you can import:from omegacli import OmegaConf, generate_config_template, parse_configUsageLet's start with an example. You have an argparse based script, and you want to move to a more reproducible setup
based on configuration files, without losing the flexibility of cli arguments.Inexamples/mnist.pywe have modified the example MNIST training script frompytorch repo.The diff is:11a12,13> from omegacli import generate_config_template, parse_config>
158a161,172>     parser.add_argument(>         "--generate-config",>         action="store_true",>         default=False,>         help="Generate example YAML configuration file",>     )>     parser.add_argument(>         "--config-path",>         type=str,>         default=None,>         help="Path to configuration file",>     )159a174,182
>>     if args.generate_config:>         import sys>>         generate_config_template(parser, args.config)>         sys.exit(0)>>     args = parse_config(parser, args.config)Note we have added two command line arguments--generate-configand--config-path.When we runpythonmnist.py--generate-config--config-pathconfig.yamlit will create aconfig.yamlfile, which can be used from now on:batch_size:64test_batch_size:1000epochs:nulllr:1.0gamma:0.7no_cuda:falseno_mps:falsedry_run:falseseed:1log_interval:10save_model:falseNow if we run:python mnist.py --config config.yamlThe script will use the values provided in theconfig.yamlfile. If we change the configuration:lr:0.1The training will uselr=0.1.At any time we can run a quick experiment (let's say withgamma=1.0) and override the config values using the CLI:python mnist.py --config config.yaml --gamma 1.0When we are confident with our experiments we can set the best hyperparameter values in the configuration file and push to a remote repo for reproducibility.How it worksWe provide a high-level utility functionparse_configthat merges a YAML configuration file with anargparse.ArgumentParser:importiofromomegacliimportparse_configmock_config_file=io.StringIO('''model:hidden: 100''')parser=argparse.ArgumentParser("My cool model")parser.add_argument("--hidden",dest="model.hidden",type=int,default=20)cfg=parse_config(parser,mock_config_file)>>>{'model':{'hidden':100}}type(cfg)>>><class'omegaconf.dictconfig.DictConfig'>cfg=parse_config(parser,mock_config_file,args=["--hidden","200"])>>>{'model':{'hidden':200}}mock_config_file=io.StringIO('''random_value: hello''')cfg=parse_config(parser,mock_config_file)>>>{'model':{'hidden':20},'random_value':'hello'}You can also use the patchedOmegaConfclass directly:importargparsefromomegacliimportOmegaConfparser=argparse.ArgumentParser("My cool model")parser.add_argument("--hidden",dest="model.hidden",type=int,default=20)user_provided_args,default_args=OmegaConf.from_argparse(parser,args=["--hidden","100"])user_provided_args>>>{'model':{'hidden':100}}default_args>>>{}user_provided_args,default_args=OmegaConf.from_argparse(parser)user_provided_args>>>{}default_args>>>{'model':{'hidden':20}}NOTE: thefrom_argparsemethod calls theparser.parse_args().Merging of provided valuesThe precedence for merging is as followsdefault cli args values < config file values < user provided cli argsE.g.:if you don't include a value in your configuration it will take the default value from the argparse argumentsif you provide a cli arg (e.g. run the script with --bsz 64) it will override the value in the config fileConventionsTo create a nested configuration structure and match with the argparse provided CLI args,
we use thedestkwarg when adding an argument withparser.add_argument.
Specifically, we follow the convention that the destination is a string, delimited by.to indicate nested structure.For example:parser.add_argument("--hidden",dest="model_hidden",type=int,default=20)will create a top-level element in the configuration:user_provided_args,default_args=OmegaConf.from_argparse(parser,args=["--hidden","100"])user_provided_args>>>{'model_hidden':100}This will match with the following entry in the YAML file:model_hidden:100The following:parser.add_argument("--hidden",dest="model.hidden",type=int,default=20)will create a nested structure in the configuration:user_provided_args,default_args=OmegaConf.from_argparse(parser,args=["--hidden","100"])user_provided_args>>>{'model':{'hidden':100}}and will match with the following YAML entry:model:hidden:100The parsing is recursive, so you can go as deep as you want.Generate a configuration file based on an argument parserRun:fromomegacliimportgenerate_config_templategenerate_config_template(parser,"example-config.yaml")This will initialize a configuration file, that is consistent with the argument parser.
You can use this as a starting point for saving and editing your configuration.Similar solutionsHydra: A more feature-rich, but more complex solution. If you are willing to introduce the dependency you can use itPytorch-Lightning: PL introduced a similar functionality. You can use it if you are in the PL ecosystem.Why create a separate package?OmegaConfplans to remain agnostic to the command line argument parser, therefore we cannot merge this solution upstream.See related issue.