Package: chanfig
CHANfiGIntroductionCHANfiG aims to make your configuration easier.There are tons of configurable parameters in training a Machine Learning model.
To configure all these parameters, researchers usually need to write gigantic config files, sometimes even thousands of lines.
Most of the configs are just replicates of the default arguments of certain functions, resulting in many unnecessary declarations.
It is also very hard to alter the configurations.
One needs to navigate and open the right configuration file, make changes, save and exit.
These had wasted an uncountable[^uncountable] amount of precious timeand are no doubt a crime.
Usingargparsecould relieve the burdens to some extent.
However, it takes a lot of work to make it compatible with existing config files, and its lack of nesting limits its potential.CHANfiG would like to make a change.You just type the alternations in the command line, and leave everything else to CHANfiG.CHANfiG is highly inspired byYACS.
Different from the paradigm of YACS(your code + a YACS config for experiment E (+ external dependencies + hardware + other nuisance terms ...) = reproducible experiment E),
The paradigm of CHANfiG is:your code + command line arguments (+ optional CHANfiG config + external dependencies + hardware + other nuisance terms ...) = reproducible experiment E (+ optional CHANfiG config for experiment E)ComponentsA Config is basically a nested dict structure.However, the default Python dict is hard to manipulate.The only way to access a dict member is throughdict['name'], which is obviously extremely complex.
Even worse, if the dict is nested like a config, member access could be something likedict['parent']['children']['name'].Enough is enough, it is time to make a change.We need attribute-style access, and we need it now.dict.nameanddict.parent.children.nameis all you need.Although there have been some other works that achieve a similar functionality of attribute-style access to dict members.
Their Config object either uses a separate dict to store information from attribute-style access (EasyDict), which may lead to inconsistency between attribute-style access and dict-style access;
or reuse the existing__dict__and redirect dict-style access (ml_collections), which may result in confliction between attributes and members of Config.To overcome the aforementioned limitations, we inherit the Python built-indictto createFlatDict,DefaultDict,NestedDict,Config, andRegistry.
We also introduceVariableto allow sharing a value across multiple places, andConfigParserto parse command line arguments.FlatDictFlatDictimproves the defaultdictin 3 aspects.Dict OperationsFlatDictincorporates amergemethod that allows you to merge aMapping, anIterable, or a path to theFlatDict.
Different from built-inupdate,mergeassign values instead of replace, which makes it works better withDefaultDict.dictin Python is ordered since Python 3.7, but there isn't a built-in method to help you sort adict.FlatDictsupportssortto help you manage your dict.Moreover,FlatDictcomes withdifferenceandintersect, which makes it very easy to compare aFlatDictwith otherMapping,Iterable, or a path.ML OperationsFlatDictsupportstomethod similar to PyTorch Tensors.
You can simply convert all member values ofFlatDictto a certain type or pass to a device in the same way.FlatDictalso integratescpu,gpu(cuda), andtpu(xla) methods for easier access.IO OperationsFlatDictprovidesjson,jsons,yamlandyamlsmethods to dumpFlatDictto a file or string.
It also providesfrom_json,from_jsons,from_yamlandfrom_yamlsmethods to build aFlatDictfrom a string or file.FlatDictalso includesdumpandloadmethods which determines the type by its extension and dump/loadFlatDictto/from a file.DefaultDictTo facility the needs of default values, we incorporateDefaultDictwhich acceptsdefault_factoryand works just like acollections.defaultdict.NestedDictSince most Configs are in a nested structure, we further propose aNestedDict.Based onDefaultDict,NestedDictprovidesall_keys,all_values, andall_itemsmethods to allow iterating over the whole nested structure at once.NestedDictalso comes withapplyandapply_methods, which made it easier to manipulate the nested structure.ConfigConfigextends the functionality by supportingfreezeanddefrost, and by adding a built-inConfigParserto pare command line arguments.Note thatConfigalso hasdefault_factory=Config()by default for convenience.RegistryRegistryextends theNestedDictand supportsregister,lookup, andbuildto help you register constructors and build objects from aConfig.VariableHave one value for multiple names at multiple places? We got you covered.Just wrap the value withVariable, and one alteration will be reflected everywhere.Variablealso supportstype,choices,validator, andrequiredto ensure the correctness of the value.To make it even easier,Variablealso supportshelpto provide a description when usingConfigParser.ConfigParserConfigParserextendsArgumentParserand providesparseandparse_configto parse command line arguments.UsageCHANfiG has great backward compatibility with previous configs.No matter if your old config is json or yaml, you could directly read from them.And if you are using yacs, just replaceCfgNodewithConfigand enjoy all the additional benefits that CHANfiG provides.Moreover, if you find a name in the config is too long for command-line, you could simply callself.add_argumentwith properdestto use a shorter name in command-line, as you do withargparse.fromchanfigimportConfig,VariableclassModel:def__init__(self,encoder,dropout=0.1,activation='ReLU'):self.encoder=Encoder(**encoder)self.dropout=Dropout(dropout)self.activation=getattr(Activation,activation)defmain(config):model=Model(**config.model)optimizer=Optimizer(**config.optimizer)scheduler=Scheduler(**config.scheduler)dataset=Dataset(**config.dataset)dataloader=Dataloader(**config.dataloader)classTestConfig(Config):def__init__(self):super().__init__()dropout=Variable(0.1)self.name="CHANfiG"self.seed=1013self.data.batch_size=64self.model.encoder.num_layers=6self.model.decoder.num_layers=6self.model.dropout=dropoutself.model.encoder.dropout=dropoutself.model.decoder.dropout=dropoutself.activation="GELU"self.optim.lr=1e-3self.add_argument("--batch_size",dest="data.batch_size")self.add_argument("--lr",dest="optim.lr")defpost(self):self.id=f"{self.name}_{self.seed}"if__name__=='__main__':# config = Config.load('config.yaml')  # in case you want to read from a yaml# config = Config.load('config.json')  # in case you want to read from a json# existing_configs = {'data.batch_size': 64, 'model.encoder.num_layers': 8}# config = Config(**existing_configs)  # in case you have some config in dict to loadconfig=TestConfig()config=config.parse()# config.merge('dataset.yaml')  # in case you want to merge a yaml# config.merge('dataset.json')  # in case you want to merge a json# note that the value of merge will override current valuesconfig.model.decoder.num_layers=8config.freeze()print(config)# main(config)# config.yaml('config.yaml')  # in case you want to save a yaml# config.json('config.json')  # in case you want to save a jsonAll you need to do is just run a line:pythonmain.py--model.encoder.num_layers8--model.dropout=0.2--lr5e-3You could also load a default configure file and make changes based on it:Note, you must specifyconfig.parse(default_config='config')to correctly load the default config.pythonmain.py--configmeow.yaml--model.encoder.num_layers8--model.dropout=0.2--lr5e-3If you have made it dump current configurations, this should be in the written file:activation:GELUdata:batch_size:64id:CHANfiG_1013model:decoder:dropout:0.1num_layers:6dropout:0.1encoder:dropout:0.1num_layers:6name:CHANfiGoptim:lr:0.005seed:1013{"name":"CHANfiG","seed":1013,"data":{"batch_size":64},"model":{"encoder":{"num_layers":6,"dropout":0.1},"decoder":{"num_layers":6,"dropout":0.1},"dropout":0.1},"activation":"GELU","optim":{"lr":0.005},"id":"CHANfiG_1013"}Define the default arguments in function, put alterations in CLI, and leave the rest to CHANfiG.InstallationInstall the most recent stable version on pypi:pipinstallchanfigInstall the latest version from source:pipinstallgit+https://github.com/ZhiyuanChen/CHANfiGIt works the way it should have worked.LicenseCHANfiG is multi-licensed under the following licenses:The UnlicenseGNU Affero General Public License v3.0 or laterGNU General Public License v2.0 or laterBSD 4-Clause "Original" or "Old" LicenseMIT LicenseApache License 2.0You can choose any (one or more) of these licenses if you use this work.SPDX-License-Identifier: Unlicense OR AGPL-3.0-or-later OR GPL-2.0-or-later OR BSD-4-Clause OR MIT OR Apache-2.0[^uncountable]: fun fact: time is always uncountable.