| # Lazy Configs |
|
|
| The traditional yacs-based config system provides basic, standard functionalities. |
| However, it does not offer enough flexibility for many new projects. |
| We develop an alternative, non-intrusive config system that can be used with |
| detectron2 or potentially any other complex projects. |
|
|
| ## Python Syntax |
|
|
| Our config objects are still dictionaries. Instead of using Yaml to define dictionaries, |
| we create dictionaries in Python directly. This gives users the following power that |
| doesn't exist in Yaml: |
|
|
| * Easily manipulate the dictionary (addition & deletion) using Python. |
| * Write simple arithmetics or call simple functions. |
| * Use more data types / objects. |
| * Import / compose other config files, using the familiar Python import syntax. |
|
|
| A Python config file can be loaded like this: |
| ```python |
| # config.py: |
| a = dict(x=1, y=2, z=dict(xx=1)) |
| b = dict(x=3, y=4) |
| |
| # my_code.py: |
| from detectron2.config import LazyConfig |
| cfg = LazyConfig.load("path/to/config.py") # an omegaconf dictionary |
| assert cfg.a.z.xx == 1 |
| ``` |
|
|
| After [LazyConfig.load](../modules/config.html#detectron2.config.LazyConfig.load), `cfg` will be a dictionary that contains all dictionaries |
| defined in the global scope of the config file. Note that: |
| * All dictionaries are turned to an [omegaconf](https://omegaconf.readthedocs.io/) |
| config object during loading. This enables access to omegaconf features, |
| such as its [access syntax](https://omegaconf.readthedocs.io/en/2.1_branch/usage.html#access-and-manipulation) |
| and [interpolation](https://omegaconf.readthedocs.io/en/2.1_branch/usage.html#variable-interpolation). |
| * Absolute imports in `config.py` works the same as in regular Python. |
| * Relative imports can only import dictionaries from config files. |
| They are simply a syntax sugar for [LazyConfig.load_rel](../modules/config.html#detectron2.config.LazyConfig.load_rel). |
| They can load Python files at relative path without requiring `__init__.py`. |
|
|
| [LazyConfig.save](../modules/config.html#detectron2.config.LazyConfig.save) can save a config object to yaml. |
| Note that this is not always successful if non-serializable objects appear in the config file (e.g. lambdas). |
| It is up to users whether to sacrifice the ability to save in exchange for flexibility. |
|
|
| ## Recursive Instantiation |
|
|
| The LazyConfig system heavily uses recursive instantiation, which is a pattern that |
| uses a dictionary to describe a |
| call to a function/class. The dictionary consists of: |
|
|
| 1. A "\_target\_" key which contains path to the callable, such as "module.submodule.class_name". |
| 2. Other keys that represent arguments to pass to the callable. Arguments themselves can be defined |
| using recursive instantiation. |
| |
| We provide a helper function [LazyCall](../modules/config.html#detectron2.config.LazyCall) that helps create such dictionaries. |
| The following code using `LazyCall` |
| ```python |
| from detectron2.config import LazyCall as L |
| from my_app import Trainer, Optimizer |
| cfg = L(Trainer)( |
| optimizer=L(Optimizer)( |
| lr=0.01, |
| algo="SGD" |
| ) |
| ) |
| ``` |
| creates a dictionary like this: |
| ```python |
| cfg = { |
| "_target_": "my_app.Trainer", |
| "optimizer": { |
| "_target_": "my_app.Optimizer", |
| "lr": 0.01, "algo": "SGD" |
| } |
| } |
| ``` |
| |
| By representing objects using such dictionaries, a general |
| [instantiate](../modules/config.html#detectron2.config.instantiate) |
| function can turn them into actual objects, i.e.: |
| ```python |
| from detectron2.config import instantiate |
| trainer = instantiate(cfg) |
| # equivalent to: |
| # from my_app import Trainer, Optimizer |
| # trainer = Trainer(optimizer=Optimizer(lr=0.01, algo="SGD")) |
| ``` |
|
|
| This pattern is powerful enough to describe very complex objects, e.g.: |
|
|
| <details> |
| <summary> |
| A Full Mask R-CNN described in recursive instantiation (click to expand) |
| </summary> |
|
|
| ```eval_rst |
| .. literalinclude:: ../../configs/common/models/mask_rcnn_fpn.py |
| :language: python |
| :linenos: |
| ``` |
|
|
| </details> |
|
|
| There are also objects or logic that cannot be described simply by a dictionary, |
| such as reused objects or method calls. They may require some refactoring |
| to work with recursive instantiation. |
|
|
| ## Using Model Zoo LazyConfigs |
|
|
| We provide some configs in the model zoo using the LazyConfig system, for example: |
|
|
| * [common baselines](../../configs/common/). |
| * [new Mask R-CNN baselines](../../configs/new_baselines/) |
|
|
| After installing detectron2, they can be loaded by the model zoo API |
| [model_zoo.get_config](../modules/model_zoo.html#detectron2.model_zoo.get_config). |
|
|
| Using these as references, you're free to define custom config structure / fields for your own |
| project, as long as your training script can understand them. |
| Despite of this, our model zoo configs still follow some simple conventions for consistency, e.g. |
| `cfg.model` defines a model object, `cfg.dataloader.{train,test}` defines dataloader objects, |
| and `cfg.train` contains training options in key-value form. |
| In addition to `print()`, a better way to view the structure of a config is like this: |
| ```python |
| from detectron2.model_zoo import get_config |
| from detectron2.config import LazyConfig |
| print(LazyConfig.to_py(get_config("COCO-InstanceSegmentation/mask_rcnn_R_50_FPN_1x.py"))) |
| ``` |
| From the output it's easier to find relevant options to change, e.g. |
| `dataloader.train.total_batch_size` for the batch size, or `optimizer.lr` for base learning rate. |
|
|
| We provide a reference training script |
| [tools/lazyconfig_train_net.py](../../tools/lazyconfig_train_net.py), |
| that can train/eval our model zoo configs. |
| It also shows how to support command line value overrides. |
|
|
| To demonstrate the power and flexibility of the new system, we show that |
| [a simple config file](../../configs/Misc/torchvision_imagenet_R_50.py) |
| can let detectron2 train an ImageNet classification model from torchvision, even though |
| detectron2 contains no features about ImageNet classification. |
| This can serve as a reference for using detectron2 in other deep learning tasks. |
|
|
| ## Summary |
|
|
| By using recursive instantiation to create objects, |
| we avoid passing a giant config to many places, because `cfg` is only passed to `instantiate`. |
| This has the following benefits: |
|
|
| * It's __non-intrusive__: objects to be constructed are config-agnostic, regular Python |
| functions/classes. |
| They can even live in other libraries. For example, |
| `{"_target_": "torch.nn.Conv2d", "in_channels": 10, "out_channels": 10, "kernel_size": 1}` |
| defines a conv layer. |
| * __Clarity__ of what function/classes will be called, and what arguments they use. |
| * `cfg` doesn't need pre-defined keys and structures. It's valid as long as it translates to valid |
| code. This gives a lot more __flexibility__. |
| * You can still pass huge dictionaries as arguments, just like the old way. |
|
|
| Recursive instantiation and Python syntax are orthogonal: you can use one without the other. |
| But by putting them together, the config file looks a lot like the code that will be executed: |
|
|
|  |
|
|
| However, the config file just defines dictionaries, which can be easily manipulated further |
| by composition or overrides. |
| The corresponding code will only be executed |
| later when `instantiate` is called. In some way, |
| in config files we're writing "editable code" that will be "lazily executed" later when needed. |
| That's why we call this system "LazyConfig". |
|
|