深度学习中的配置系统
配置系统是很多深度学习套件和算法库的重要组件,一个优秀的配置系统可以方便用户修改训练所需的超参数、管理实验并且增强项目可读性等。配置系统不光可以用于大型的算法库,也可以用于个人进行快速实验和迭代。然而当前深度学习社区的配置系统都或多或少存在一些 不够方便 的地方,本文将会介绍一些已有的配置系统,并尝试对其进行改进。
YACS
YACS是一个轻量级的配置系统,Detectron和maskrcnn-benchmark便是使用的YACS,其使用可读性较好的YAML文件,格式为:
MODEL:
TYPE: mask_rcnn
CONV_BODY: FPN.add_fpn_ResNet50_conv5_body
NUM_CLASSES: 81
FASTER_RCNN: True
MASK_ON: True
该配置的第二行的TYPE为所要实例化的类,解析配置文件时若某个字典中包含TYPE,则表示应该将其实例化。
OpenMMLab
OpenMMLab系列的算法库提供了注册器(Registry)来维护了一个字符串到 类或函数的全局映射,这样便可以更加方便的通过字符串寻找到对应的类,使用方式为:
import torch.nn as nn
from mmengine import Registry
ACTIVATION = Registry('activation')
# 使用注册器管理模块
@ACTIVATION.register_module()
class Sigmoid(nn.Module):
def __init__(self):
super().__init__()
def forward(self, x):
print('call Sigmoid.forward')
return x
然而随着codebase规模的不断增大,每次运行时会引入额外的注册开销和无关依赖。
OpenMMLab系列的算法库支持YAML,JSON和python作为配置文件,其中json和python的可读性奇差:
train_pipeline = [
dict(type='LoadImageFromFile', backend_args={{_base_.backend_args}}),
dict(type='LoadAnnotations', with_bbox=True, with_mask=True),
dict(type='RandomFlip', prob=0.5),
dict(
type='RandomChoice',
transforms=[[
dict(
type='RandomChoiceResize',
scales=[(480, 1333), (512, 1333), (544, 1333), (576, 1333),
(608, 1333), (640, 1333), (672, 1333), (704, 1333),
(736, 1333), (768, 1333), (800, 1333)],
keep_ratio=True)
],
[
dict(
type='RandomChoiceResize',
scales=[(400, 1333), (500, 1333), (600, 1333)],
keep_ratio=True),
dict(
type='RandomCrop',
crop_type='absolute_range',
crop_size=(384, 600),
allow_negative_crop=True),
]]),
dict(type='PackDetInputs')
]
PaddleDetection
PaddleDectetion的配置系统较为复杂且精妙,其只维护了一个全局的注册字典,配置文件格式如下:
metric: COCO
num_classes: 80
TrainDataset:
!COCODataSet
image_dir: train2017
anno_path: annotations/instances_train2017.json
dataset_dir: dataset/coco
data_fields: ['image', 'gt_bbox', 'gt_class', 'is_crowd']
不同于前面两种配置系统使用type,PaddleDetection使用yaml的representer实现通过!{}来获取python对象。此外PaddleDetection在实现类时添加了类属性——__shared__和__inject__,如:
from ppdet.core.workspace import register
@register
class BBoxPostProcess(object):
__shared__ = ['num_classes']
__inject__ = ['decode', 'nms']
def __init__(self, num_classes=80, decode=None, nms=None):
# 省略内容
pass
def __call__(self, head_out, rois, im_shape, scale_factor):
# 省略内容
pass
__shared__表示这些参数是全局共享的,__inject__表示这些参数是全局字典中已经封装好的模块,即实现了配置文件的嵌套,可谓十分之精妙了。
然而该配置系统过于定制化,不能很好的适应各种情况。
PaddleSeg
PaddleSeg中的配置格式并无特别之处,与前文相同,但是其解析、检查和构建对象都对进行了硬编码,这样的好处是编写代码时有补全。
LazyConfig
由于YAML和PYTHON配置文件一直被诟病不能跳转和补全,尤其是PYTHON,可读性更是一坨。detectron2提出了
LazyConfig,形式如:
import torch.nn as nnfrom detectron2.config import LazyCall, instantiate
# 通过LazyCall创建一个config对象
conv_config = LazyCall(nn.Conv2d)(in_channels=16, out_channels=16, kernel_size=3, stride=1, padding=1)
conv = instantiate(conv_config)
这里的conv_config将类对象和实例化所需参数都存储起来,等到需要实例化时才进行实例化,因此叫做Lazy。这样编写配置文件时可以为对应的类提供补全和跳转,并且通过该方式可以直接舍弃注册器,可谓是一箭双雕。
然而该配置系统不能对类的参数进行补全和检查。
OpenMMLab python config
同样为了支持跳转和补全,OpenMMLab提供了新式的python配置文件——即将type后的字符串变为类,可读性仍然是一坨,远没有detectron2优雅。
ExCore
为了一定程度上解决上述问题,我开发了ExCore,主要用于个人的实验。ExCore使用TOML作为配置文件,并且借助LSP(language server protocol)的力量在一定程度上弥补了配置文件和python文件之间的鸿沟——即为TOML配置文件提供了补全、跳转、查看文档、参数检查等功能,先看几个动图再介绍。 补全,查看文档和参数检查:

可以看到会有类名及其参数的补全以及查看对应类的docstring,并且会对required参数进行提示。
跳转:

跳转以插件的形式实现,目前仅支持NeoVim,见excore.nvim