延迟配置#
概要
通过使用递归实例化来创建对象,避免了将大量的配置传递到许多地方,因为 cfg
只传递给 instantiate
。这有以下好处:
非侵入式(non-intrusive)的: 要构造的对象是与配置无关的常规 Python 函数/类。他们甚至可以用于其他库。利用
{"_target_": "torch.nn.Conv2d", "in_channels": 10, "out_channels": 10, "kernel_size": 1}
定义了卷积层更加明确调用什么函数/类,以及它们使用什么参数。
cfg
不需要预定义的键和结构。只要它转换为有效的代码,它就是有效的。这就提供了更多的灵活性。你仍然可以像以前一样传递大的字典作为参数。
传统的基于 yacs 的配置系统提供基本的标准功能。然而,它并没有为许多新项目提供足够的灵活性。detectron2 开发了一种替代的非侵入式配置系统,可用于 detectron2 或任何其他复杂的项目。
Python 语法#
配置对象仍然是字典。不使用 YAML 定义字典,而是直接在 Python 中创建字典。这为用户提供了 YAML 中不存在的以下功能:
使用 Python 轻松操作字典(添加和删除)。
编写简单的算术或调用简单的函数。
使用更多的数据类型/对象。
使用熟悉的 Python 导入语法导入/合成其他配置文件。
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
load()
后, cfg
是字典,它包含配置文件全局作用域中定义的所有字典。注意:
在加载过程中,所有字典都转换为 omegaconf 配置对象。这允许访问 omegaconf 特性,例如它的访问语法和插值。
在
config.py
中绝对导入的工作原理与在常规 Python 中相同。相对导入只能从配置文件中导入字典。它们只是
load_rel()
的语法糖。它们可以在相对路径上加载 Python 文件,而不需要__init__.py
。
save()
可以将配置对象保存到 YAML。注意,如果配置文件中出现了不可序列化的对象(例如 lambdas),这并不总是成功的。是否牺牲 save 的能力来换取灵活性取决于用户。
递归模板具现化#
LazyConfig 系统大量使用递归实例化,这种模式使用字典来描述对函数/类的调用。该词典包括:
包含可调用对象路径的
_target_
键,如module.submodule.class_name
。表示传递给可调用对象的参数的其他键。参数本身可以使用递归实例化来定义。
辅助函数 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"
}
}
通过使用这样的字典表示对象,一般实例化函数可以将它们转换为实际对象,即:
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"))
这个模式非常强大,足以描述非常复杂的对象,例如, 递归实例化中描述的全掩码 R-CNN
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",
)
还有一些对象或逻辑不能简单地用字典来描述,例如重用的对象或方法调用。它们可能需要一些重构来处理递归实例化。
使用模型动物园 LazyConfigs#
使用 LazyConfig 系统在模型动物园中提供了一些配置,例如:
在安装 detectron2 之后,可以通过模型动物园 API 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 的参考。