mirror of
https://github.com/FoundationVision/GLEE.git
synced 2025-06-03 14:51:17 +08:00
171 lines
7.1 KiB
Markdown
171 lines
7.1 KiB
Markdown
|
# 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".
|