Config System

Given that the traditional yacs-based config system or python argparse command-line options suffer from providing enough flexibility for the development of new project, we adopted the alternative and non-intrusive config system called lazy config from detectron2.

Please refer to detectron2 lazy-config tutorials for more details about the syntax and basic usage of lazy config.

Default Configs in detrex

Our detrex has defined a standard set of config namespaces for later usage. Users can modify these configs according to their own needs.

In summary, the pre-defined namespaces are model, train, dataloader, optimizer, lr_multiplier

model

This is configuration for model definition. We define all model configs under projects/, You can refer to projects/dab_detr/configs/models for more examples.

Here is the example of dab-detr-r50 model config:

# dab_detr_r50.py
import torch.nn as nn

from detrex.modeling.matcher import HungarianMatcher
from detrex.modeling.criterion import SetCriterion
from detrex.layers import PositionEmbeddingSine
from detrex.modeling.backbone import ResNet, BasicStem

from detectron2.config import LazyCall as L

from projects.dab_detr.modeling import (
    DABDETR,
    DabDetrTransformer,
    DabDetrTransformerDecoder,
    DabDetrTransformerEncoder,
)


model = L(DABDETR)(
    backbone=L(ResNet)(
        stem=L(BasicStem)(in_channels=3, out_channels=64, norm="FrozenBN"),
        stages=L(ResNet.make_default_stages)(
            depth=50,
            stride_in_1x1=False,
            norm="FrozenBN",
        ),
        out_features=["res2", "res3", "res4", "res5"],
        freeze_at=1,
    ),
    in_features=["res5"],  # only use last level feature in DAB-DETR
    in_channels=2048,
    position_embedding=L(PositionEmbeddingSine)(
        num_pos_feats=128,
        temperature=20,
        normalize=True,
    ),
    transformer=L(DabDetrTransformer)(
        encoder=L(DabDetrTransformerEncoder)(
            embed_dim=256,
            num_heads=8,
            attn_dropout=0.0,
            feedforward_dim=2048,
            ffn_dropout=0.0,
            activation=L(nn.PReLU)(),
            num_layers=6,
        ),
        decoder=L(DabDetrTransformerDecoder)(
            embed_dim=256,
            num_heads=8,
            attn_dropout=0.0,
            feedforward_dim=2048,
            ffn_dropout=0.0,
            activation=L(nn.PReLU)(),
            num_layers=6,
            modulate_hw_attn=True,
        ),
    ),
    embed_dim=256,
    num_classes=80,
    num_queries=300,
    criterion=L(SetCriterion)(
        num_classes=80,
        matcher=L(HungarianMatcher)(
            cost_class=2.0,
            cost_bbox=5.0,
            cost_giou=2.0,
            cost_class_type="focal_loss_cost",
            alpha=0.25,
            gamma=2.0,
        ),
        weight_dict={
            "loss_class": 1,
            "loss_bbox": 5.0,
            "loss_giou": 2.0,
        },
        loss_class_type="focal_loss",
        alpha=0.25,
        gamma=2.0,
    ),
    aux_loss=True,
    pixel_mean=[123.675, 116.280, 103.530],
    pixel_std=[58.395, 57.120, 57.375],
    freeze_anchor_box_centers=True,
    select_box_nums_for_evaluation=300,
    device="cuda",
)

which can be loaded like:

# user's own config.py
from dab_detr_r50 import model

# check the loaded model config
assert model.embed_dim == 256

# modify model config according to your own needs
model.embed_dim = 512

After defining model configs in python files. Please import it in the global scope of the final config file as model.

You can access and change all keys in the model config according to your own needs.

train

This is the configuration for training and evalution. The default training config can be found in configs/common/train.py.

The default training config is as follows:

train = dict(

    # Directory where output files are written to
    output_dir="./output",

    # The initialize checkpoint to be loaded
    init_checkpoint="",

    # The total training iterations
    max_iter=90000,

    # options for Automatic Mixed Precision
    amp=dict(enabled=False),

    # options for DistributedDataParallel
    ddp=dict(
        broadcast_buffers=False,
        find_unused_parameters=False,
        fp16_compression=False,
    ),

    # options for Gradient Clipping during training
    clip_grad=dict(
        enabled=False,
        params=dict(
            max_norm=0.1,
            norm_type=2,
        ),
    ),

    #  # options for Fast Debugging
    fast_dev_run=dict(enabled=False),

    # options for PeriodicCheckpointer, which saves a model checkpoint
    # after every `checkpointer.period` iterations,
    # and only `checkpointer.max_to_keep` number of checkpoint will be kept.
    checkpointer=dict(period=5000, max_to_keep=100),

    # Run evaluation after every `eval_period` number of iterations
    eval_period=5000,

    # Output log to console every `log_period` number of iterations.
    log_period=20,
    device="cuda"
    # ...
)

dataloader

This is the configuration for dataset and dataloader. We use the built-in dataset in detectron2 for simplicity. Please see configs/common/data for more examples.

Here we take the coco_detr.py for detr-like model as an example:

from omegaconf import OmegaConf

import detectron2.data.transforms as T
from detectron2.config import LazyCall as L
from detectron2.data import (
    build_detection_test_loader,
    build_detection_train_loader,
    get_detection_dataset_dicts,
)
from detectron2.evaluation import COCOEvaluator

from detrex.data import DetrDatasetMapper

dataloader = OmegaConf.create()

# the defined train loader
dataloader.train = L(build_detection_train_loader)(
    dataset=L(get_detection_dataset_dicts)(names="coco_2017_train"),
    mapper=L(DetrDatasetMapper)(
        # the defined two augmentations which will be random-selected during training.
        augmentation=[
            L(T.RandomFlip)(),
            L(T.ResizeShortestEdge)(
                short_edge_length=(480, 512, 544, 576, 608, 640, 672, 704, 736, 768, 800),
                max_size=1333,
                sample_style="choice",
            ),
        ],
        augmentation_with_crop=[
            L(T.RandomFlip)(),
            L(T.ResizeShortestEdge)(
                short_edge_length=(400, 500, 600),
                sample_style="choice",
            ),
            L(T.RandomCrop)(
                crop_type="absolute_range",
                crop_size=(384, 600),
            ),
            L(T.ResizeShortestEdge)(
                short_edge_length=(480, 512, 544, 576, 608, 640, 672, 704, 736, 768, 800),
                max_size=1333,
                sample_style="choice",
            ),
        ],
        is_train=True,
        mask_on=False,  # with instance mask or not
        img_format="RGB",  # input image format
    ),
    total_batch_size=16,  # training batch size
    num_workers=4,
)

# the defined test loader
dataloader.test = L(build_detection_test_loader)(
    dataset=L(get_detection_dataset_dicts)(names="coco_2017_val", filter_empty=False),
    mapper=L(DetrDatasetMapper)(
        # use no augmentation in testing
        augmentation=[
            L(T.ResizeShortestEdge)(
                short_edge_length=800,
                max_size=1333,
            ),
        ],
        augmentation_with_crop=None,
        is_train=False,
        mask_on=False,
        img_format="RGB",
    ),
    num_workers=4,
)

# the defined evaluator for evaluation
dataloader.evaluator = L(COCOEvaluator)(
    dataset_name="${..test.dataset.names}",
)

We adopted the built-in coco datasets and detection dataloader usage from detectron2, please refer to the following tutorials if you want to use custom datasets:

optimizer

This is the configuration for optimizer. The default configuration can be found in configs/common/optim.py.

detrex uilizes detectron2.solver.build.get_default_optimizer_params which needs the nn.Module as argument and returns the parameter groups.

# configs/common/optim.py
import torch

from detectron2.config import LazyCall as L
from detectron2.solver.build import get_default_optimizer_params


AdamW = L(torch.optim.AdamW)(
    params=L(get_default_optimizer_params)(
        # params.model is meant to be set to the model object, before instantiating
        # the optimizer.
        base_lr="${..lr}",
        weight_decay_norm=0.0,
    ),
    lr=1e-4,
    betas=(0.9, 0.999),
    weight_decay=0.1,
)

if you want to use torch.optim.SGD in training, you can modify your config as follows:

import torch
from configs.commom.optim import AdamW as optim

optim._target_ = torch.optim.SGD

# Remove the incompatible arguments
del optim.betas

# Add the needed arguments
optim.momentum = 0.9

lr_multiplier

This is the configuration for lr_multiplier which is combined with detectron2.engine.hooks.LRScheduler and performs learning scheduler function during training.

The default lr_multiplier config can be found in configs/common/coco_schedule.py, we defined the commonly 50 epochs scheduler referred to in the papers as follows:

from fvcore.common.param_scheduler import MultiStepParamScheduler

from detectron2.config import LazyCall as L
from detectron2.solver import WarmupParamScheduler

def default_coco_scheduler(epochs=50, decay_epochs=40, warmup_epochs=0):
    """
    Returns the config for a default multi-step LR scheduler such as "50epochs",
    commonly referred to in papers, where every 1x has the total length of 1440k
    training images (~12 COCO epochs). LR is decayed once at the end of training.

    Args:
        epochs (int): total training epochs.
        decay_epochs (int): lr decay steps.
        warmup_epochs (int): warmup epochs.

    Returns:
        DictConfig: configs that define the multiplier for LR during training
    """
    # total number of iterations assuming 16 batch size, using 1440000/16=90000
    total_steps_16bs = epochs * 7500
    decay_steps = decay_epochs * 7500
    warmup_steps = warmup_epochs * 7500
    scheduler = L(MultiStepParamScheduler)(
        values=[1.0, 0.1],
        milestones=[decay_steps, total_steps_16bs],
    )
    return L(WarmupParamScheduler)(
        scheduler=scheduler,
        warmup_length=warmup_steps / total_steps_16bs,
        warmup_method="linear",
        warmup_factor=0.001,
    )

Please refer to fvcore.common.param_scheduler.ParamScheduler for more details about the ParamScheduler usage in detectron2.

Get the Default Config

Users don’t have to rewrite all contents in config every time. You can use the default built-in detrex configs using detrex.config.get_config.

After building detrex from source, you can use get_config to get the default configs as follows:

from detrex.config import get_config

# get the default config
dataloader = get_config("common/data/coco_detr.py").dataloader
optimizer = get_config("common/optim.py").AdamW
lr_multiplier = get_config("common/coco_schedule.py").lr_multiplier_50ep
train = get_config("common/train.py").train

# modify the config
train.max_iter = 375000
train.output_dir = "path/to/your/own/dir"

LazyConfig Best Practices

  1. Treat the configs you write as actual “code”: Avoid copying them or duplicating them. Import the common parts between configs.

  2. Keep the configs you write as simple as possible: Do not include keys that do not affect the experimental setting.