延迟配置

传统的基于 yacs 的配置系统提供了基本、标准的功能。但是,它没有为许多新项目提供足够的灵活性。我们开发了一种可选的、非侵入式的配置系统,它可以与 detectron2 或任何其他复杂项目一起使用。

Python 语法

我们的配置对象仍然是字典。我们直接在 Python 中创建字典,而不是使用 Yaml 定义字典。这为用户提供了 Yaml 中不存在的以下功能

  • 使用 Python 轻松操作字典(添加和删除)。

  • 编写简单的算术运算或调用简单的函数。

  • 使用更多的数据类型/对象。

  • 使用熟悉的 Python import 语法导入/组合其他配置文件。

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

LazyConfig.load 之后,cfg 将成为一个字典,包含配置文件全局作用域中定义的所有字典。请注意

  • 所有字典在加载期间都会转换为 omegaconf 配置对象。这使您可以访问 omegaconf 功能,例如它的 访问语法插值

  • config.py 中的绝对导入与普通 Python 中的导入相同。

  • 相对导入只能从配置文件中导入字典。它们只是 LazyConfig.load_rel 的语法糖。它们可以在不需 __init__.py 的情况下加载相对路径下的 Python 文件。

LazyConfig.save 可以将配置对象保存为 yaml。请注意,如果配置文件中出现不可序列化的对象(例如 lambda),则该操作并不总是成功。用户需要自行决定是牺牲保存功能来换取灵活性。

递归实例化

延迟配置系统大量使用递归实例化,这是一种使用字典来描述对函数/类的调用的模式。字典包含

  1. 一个“_target_”键,它包含可调用的路径,例如“module.submodule.class_name”。

  2. 其他表示要传递给可调用的参数的键。参数本身可以使用递归实例化来定义。

我们提供了一个帮助函数 LazyCall,它可以帮助创建这样的字典。以下使用 LazyCall 的代码

from detectron2.config import LazyCall as L
from my_app import Trainer, Optimizer
cfg = L(Trainer)(
  optimizer=L(Optimizer)(
    lr=0.01,
    algo="SGD"
  )
)

创建一个这样的字典

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

通过使用这样的字典来表示对象,一个通用的 instantiate 函数可以将它们转换为实际对象,即

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

这种模式功能强大,足以描述非常复杂的对象,例如

递归实例化中描述的完整 Mask R-CNN(点击展开)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
from detectron2.config import LazyCall as L
from detectron2.layers import ShapeSpec
from detectron2.modeling.meta_arch import GeneralizedRCNN
from detectron2.modeling.anchor_generator import DefaultAnchorGenerator
from detectron2.modeling.backbone.fpn import LastLevelMaxPool
from detectron2.modeling.backbone import BasicStem, FPN, ResNet
from detectron2.modeling.box_regression import Box2BoxTransform
from detectron2.modeling.matcher import Matcher
from detectron2.modeling.poolers import ROIPooler
from detectron2.modeling.proposal_generator import RPN, StandardRPNHead
from detectron2.modeling.roi_heads import (
    StandardROIHeads,
    FastRCNNOutputLayers,
    MaskRCNNConvUpsampleHead,
    FastRCNNConvFCHead,
)

from ..data.constants import constants

model = 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",
)

还有一些对象或逻辑不能简单地用字典来描述,例如重用对象或方法调用。它们可能需要一些重构才能与递归实例化一起使用。

使用模型库延迟配置

我们在模型库中提供了一些使用延迟配置系统的配置,例如

在安装 detectron2 后,可以使用模型库 API model_zoo.get_config 加载它们。

以这些配置为参考,您可以自由地为自己的项目定义自定义的配置结构/字段,只要您的训练脚本可以理解它们。尽管如此,我们的模型库配置仍然遵循一些简单的约定以保持一致性,例如 cfg.model 定义了一个模型对象,cfg.dataloader.{train,test} 定义了数据加载器对象,并且 cfg.train 包含键值对形式的训练选项。除了 print(),查看配置结构的更好方法是

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

从输出中更容易找到要更改的相关选项,例如 dataloader.train.total_batch_size 用于批次大小,或者 optimizer.lr 用于基本学习率。

我们提供了一个参考训练脚本 tools/lazyconfig_train_net.py,它可以训练/评估我们的模型库配置。它还展示了如何支持命令行值覆盖。

为了展示新系统的强大和灵活性,我们展示了 一个简单的配置文件 可以让 detectron2 训练 torchvision 中的 ImageNet 分类模型,即使 detectron2 不包含关于 ImageNet 分类的功能。这可以作为在其他深度学习任务中使用 detectron2 的参考。

总结

通过使用递归实例化创建对象,我们避免将巨大的配置传递到许多地方,因为 cfg 仅传递给 instantiate。这具有以下优点

  • 它是**非侵入式的**:要构造的对象是配置无关的,是普通的 Python 函数/类。它们甚至可以存在于其他库中。例如,{"_target_": "torch.nn.Conv2d", "in_channels": 10, "out_channels": 10, "kernel_size": 1} 定义了一个卷积层。

  • **清晰地**显示了将要调用的函数/类以及它们使用的参数。

  • cfg 不需要预定义的键和结构。只要它可以转换为有效的代码,它就是有效的。这提供了更多的**灵活性**。

  • 您仍然可以像以前一样传递巨大的字典作为参数。

递归实例化和 Python 语法是正交的:您可以使用其中一个而不使用另一个。但是,将它们组合在一起,配置文件看起来很像将要执行的代码

img

但是,配置文件只定义了字典,这些字典可以很容易地通过组合或覆盖进一步操作。相应的代码只会在以后调用 instantiate 时执行。在某种程度上,我们在配置文件中编写的是“可编辑代码”,这些代码将在以后需要时“延迟执行”。这就是我们称此系统为“延迟配置”的原因。