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 withdetectron2 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 thatdoesn’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:

# config.py:a=dict(x=1,y=2,z=dict(xx=1))b=dict(x=3,y=4)# my_code.py:fromdetectron2.configimportLazyConfigcfg=LazyConfig.load("path/to/config.py")# an omegaconf dictionaryassertcfg.a.z.xx==1

AfterLazyConfig.load,cfg will be a dictionary that contains all dictionariesdefined in the global scope of the config file. Note that:

  • All dictionaries are turned to anomegaconfconfig object during loading. This enables access to omegaconf features,such as itsaccess syntaxandinterpolation.

  • Absolute imports inconfig.py works the same as in regular Python.

  • Relative imports can only import dictionaries from config files.They are simply a syntax sugar forLazyConfig.load_rel.They can load Python files at relative path without requiring__init__.py.

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 thatuses a dictionary to describe acall 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 definedusing recursive instantiation.

We provide a helper functionLazyCall that helps create such dictionaries.The following code usingLazyCall

fromdetectron2.configimportLazyCallasLfrommy_appimportTrainer,Optimizercfg=L(Trainer)(optimizer=L(Optimizer)(lr=0.01,algo="SGD"))

creates a dictionary like this:

cfg={"_target_":"my_app.Trainer","optimizer":{"_target_":"my_app.Optimizer","lr":0.01,"algo":"SGD"}}

By representing objects using such dictionaries, a generalinstantiatefunction can turn them into actual objects, i.e.:

fromdetectron2.configimportinstantiatetrainer=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.:

A Full Mask R-CNN described in recursive instantiation (click to expand)
 1 2 3 4 5 6 7 8 91011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495
fromdetectron2.configimportLazyCallasLfromdetectron2.layersimportShapeSpecfromdetectron2.modeling.meta_archimportGeneralizedRCNNfromdetectron2.modeling.anchor_generatorimportDefaultAnchorGeneratorfromdetectron2.modeling.backbone.fpnimportLastLevelMaxPoolfromdetectron2.modeling.backboneimportBasicStem,FPN,ResNetfromdetectron2.modeling.box_regressionimportBox2BoxTransformfromdetectron2.modeling.matcherimportMatcherfromdetectron2.modeling.poolersimportROIPoolerfromdetectron2.modeling.proposal_generatorimportRPN,StandardRPNHeadfromdetectron2.modeling.roi_headsimport(StandardROIHeads,FastRCNNOutputLayers,MaskRCNNConvUpsampleHead,FastRCNNConvFCHead,)from..data.constantsimportconstantsmodel=L(GeneralizedRCNN)(backbone=L(FPN)(bottom_up=L(ResNet)(stem=L(BasicStem)(in_channels=3,out_channels=64,norm="FrozenBN"),stages=L(ResNet.make_default_stages)(depth=50,stride_in_1x1=True,norm="FrozenBN",),out_features=["res2","res3","res4","res5"],),in_features="${.bottom_up.out_features}",out_channels=256,top_block=L(LastLevelMaxPool)(),),proposal_generator=L(RPN)(in_features=["p2","p3","p4","p5","p6"],head=L(StandardRPNHead)(in_channels=256,num_anchors=3),anchor_generator=L(DefaultAnchorGenerator)(sizes=[[32],[64],[128],[256],[512]],aspect_ratios=[0.5,1.0,2.0],strides=[4,8,16,32,64],offset=0.0,),anchor_matcher=L(Matcher)(thresholds=[0.3,0.7],labels=[0,-1,1],allow_low_quality_matches=True),box2box_transform=L(Box2BoxTransform)(weights=[1.0,1.0,1.0,1.0]),batch_size_per_image=256,positive_fraction=0.5,pre_nms_topk=(2000,1000),post_nms_topk=(1000,1000),nms_thresh=0.7,),roi_heads=L(StandardROIHeads)(num_classes=80,batch_size_per_image=512,positive_fraction=0.25,proposal_matcher=L(Matcher)(thresholds=[0.5],labels=[0,1],allow_low_quality_matches=False),box_in_features=["p2","p3","p4","p5"],box_pooler=L(ROIPooler)(output_size=7,scales=(1.0/4,1.0/8,1.0/16,1.0/32),sampling_ratio=0,pooler_type="ROIAlignV2",),box_head=L(FastRCNNConvFCHead)(input_shape=ShapeSpec(channels=256,height=7,width=7),conv_dims=[],fc_dims=[1024,1024],),box_predictor=L(FastRCNNOutputLayers)(input_shape=ShapeSpec(channels=1024),test_score_thresh=0.05,box2box_transform=L(Box2BoxTransform)(weights=(10,10,5,5)),num_classes="${..num_classes}",),mask_in_features=["p2","p3","p4","p5"],mask_pooler=L(ROIPooler)(output_size=14,scales=(1.0/4,1.0/8,1.0/16,1.0/32),sampling_ratio=0,pooler_type="ROIAlignV2",),mask_head=L(MaskRCNNConvUpsampleHead)(input_shape=ShapeSpec(channels=256,width=14,height=14),num_classes="${..num_classes}",conv_dims=[256,256,256,256,256],),),pixel_mean=constants.imagenet_bgr256_mean,pixel_std=constants.imagenet_bgr256_std,input_format="BGR",)

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 refactoringto work with recursive instantiation.

Using Model Zoo LazyConfigs

We provide some configs in the model zoo using the LazyConfig system, for example:

After installing detectron2, they can be loaded by the model zoo APImodel_zoo.get_config.

Using these as references, you’re free to define custom config structure / fields for your ownproject, 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,andcfg.train contains training options in key-value form.In addition toprint(), a better way to view the structure of a config is like this:

fromdetectron2.model_zooimportget_configfromdetectron2.configimportLazyConfigprint(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, oroptimizer.lr for base learning rate.

We provide a reference training scripttools/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 thata simple config filecan let detectron2 train an ImageNet classification model from torchvision, even thoughdetectron2 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, becausecfg is only passed toinstantiate.This has the following benefits:

  • It’snon-intrusive: objects to be constructed are config-agnostic, regular Pythonfunctions/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 validcode. This gives a lot moreflexibility.

  • 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

However, the config file just defines dictionaries, which can be easily manipulated furtherby composition or overrides.The corresponding code will only be executedlater wheninstantiate 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”.