# 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:

![img](./lazyconfig.jpg)

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".
