From 3f3dd5b0bdba34afd34a3f66d56205a729376be9 Mon Sep 17 00:00:00 2001 From: Mashiro <57566630+HAOCHENYE@users.noreply.github.com> Date: Sat, 12 Feb 2022 21:35:05 +0800 Subject: [PATCH] [Docs] add config docs (#6) * add config user doc * add config user doc * Fix typo * fix typo * reference mmcv * fix typo * fix typo * Restructuring config.md * Update introduction Co-authored-by: Kai Chen <chenkaidev@gmail.com> * Fix Co-authored-by: Zaida Zhou <58739961+zhouzaida@users.noreply.github.com> * Update docs/zh_cn/tutorials/config.md Co-authored-by: Zaida Zhou <58739961+zhouzaida@users.noreply.github.com> * Fix title level Co-authored-by: Zaida Zhou <58739961+zhouzaida@users.noreply.github.com> * refactor the doc * remove **bold** on titles * Fix python comment * Fix python comment * Update docs/zh_cn/tutorials/config.md Co-authored-by: Zaida Zhou <58739961+zhouzaida@users.noreply.github.com> Co-authored-by: Kai Chen <chenkaidev@gmail.com> Co-authored-by: Zaida Zhou <58739961+zhouzaida@users.noreply.github.com> --- docs/zh_cn/tutorials/config.md | 266 +++++++++++++++++++++++++++++++++ 1 file changed, 266 insertions(+) create mode 100644 docs/zh_cn/tutorials/config.md diff --git a/docs/zh_cn/tutorials/config.md b/docs/zh_cn/tutorials/config.md new file mode 100644 index 00000000..06441e77 --- /dev/null +++ b/docs/zh_cn/tutorials/config.md @@ -0,0 +1,266 @@ +# é…置(Config) + +MMEngine 实现了抽象的é…置类,为用户æ供统一的é…置访问接å£ã€‚é…置类能够支æŒä¸åŒæ ¼å¼çš„é…置文件,包括 `python`,`json`,`yaml`,用户å¯ä»¥æ ¹æ®éœ€æ±‚选择自己åå¥½çš„æ ¼å¼ã€‚é…置类æ供了类似å—典或者 Python 对象属性的访问接å£ï¼Œç”¨æˆ·å¯ä»¥å分自然地进行é…ç½®å—段的读å–和修改。为了方便算法框架管ç†é…置文件,é…置类也实现了一些特性,例如é…置文件的å—段继承ç‰ã€‚ + +## é…ç½®æ–‡ä»¶è¯»å– + +é…置类æä¾›äº†ç»Ÿä¸€çš„æŽ¥å£ `Config.fromfile()`,æ¥è¯»å–和解æžé…置文件。 + +åˆæ³•çš„é…ç½®æ–‡ä»¶åº”è¯¥å®šä¹‰ä¸€ç³»åˆ—é”®å€¼å¯¹ï¼Œè¿™é‡Œä¸¾å‡ ä¸ªä¸åŒæ ¼å¼é…置文件的例å。 + +Python æ ¼å¼ï¼š + +```Python +test_int = 1 +test_list = [1, 2, 3] +test_dict = dict(key1='value1', key2=0.1) +``` + +Json æ ¼å¼ï¼š + +```json +{ + "test_int": 1, + "test_list": [1, 2, 3], + "test_dict": {"key1": "value1", "key2": 0.1} +} +``` + +YAML æ ¼å¼ï¼š + +```yaml +test_int: 1 +test_list: [1, 2, 3] +test_dict: + key1: "value1" + key2: 0.1 +``` + +对于以上三ç§æ ¼å¼çš„文件,å‡è®¾æ–‡ä»¶å分别为 `config.py`,`config.json`,`config.yml`,则我们调用 `Config.fromfile('config.xxx')` 接å£éƒ½ä¼šå¾—到相åŒçš„ç»“æžœï¼Œæž„é€ äº†åŒ…å« 3 个å—段的é…置对象。我们以 `config.py` 为例: + +```python +from mmengine import Config + +cfg = Config.fromfile('/path/to/config.py') +# Config (path: config.py): {'test_int': 1, 'test_list': [1, 2, 3], 'test_dict': {'key1: 'value1', "key2": 0.1} +``` + +## é…置文件的使用 + +通过读å–é…置文件æ¥åˆå§‹åŒ–é…置对象åŽï¼Œå°±å¯ä»¥åƒä½¿ç”¨æ™®é€šå—典或者 Python ç±»ä¸€æ ·æ¥ä½¿ç”¨è¿™ä¸ªå˜é‡äº†ã€‚ +我们æ供了两ç§è®¿é—®æŽ¥å£ï¼Œå³ç±»ä¼¼å—å…¸çš„æŽ¥å£ `cfg['key']` 或者类似 Python å¯¹è±¡å±žæ€§çš„æŽ¥å£ `cfg.key`。这两ç§æŽ¥å£éƒ½æ”¯æŒè¯»å†™ã€‚ + +```python +cfg = Config.fromfile('config.py') + +cfg.test_int # 1 +cfg.test_list # [1, 2, 3] +cfg.test_dict # ConfigDict(key1='value1', key2=0.1) +cfg.test_int = 2 # 这里å‘生了é…ç½®å—段修改,test_int å—段的值å˜æˆäº† 2 + +cfg['test_int'] # 2 +cfg['test_list'] # [1, 2, 3] +cfg['test_dict'] # ConfigDict(key1='value1', key2=0.1) +cfg['test_list'][1] = 3 # 这里å‘生了å—段修改,test_list å—段的值å˜æˆäº† [1, 3, 3] +``` + +注æ„,é…置文件ä¸å®šä¹‰çš„嵌套å—段(å³ç±»ä¼¼å—典的å—段),在 Config ä¸ä¼šå°†å…¶è½¬åŒ–为 ConfigDict 类,该类具有和 Python 内置å—典类型相åŒçš„接å£ï¼Œå¯ä»¥ç›´æŽ¥å½“åšæ™®é€šå—典使用。 + +在算法库ä¸ï¼Œå¯ä»¥å°†é…置与注册器结åˆèµ·æ¥ä½¿ç”¨ï¼Œè¾¾åˆ°é€šè¿‡é…置文件æ¥æŽ§åˆ¶æ¨¡å—æž„é€ çš„ç›®çš„ã€‚è¿™é‡Œä¸¾ä¸€ä¸ªåœ¨é…置文件ä¸å®šä¹‰ä¼˜åŒ–器的例å。 + +å‡è®¾æˆ‘们已ç»å®šä¹‰äº†ä¸€ä¸ªä¼˜åŒ–器的注册器 OPTIMIZERS,包括了å„ç§ä¼˜åŒ–器。那么首先写一个 `config_sgd.py`: + +```python +optimizer = dict(type='SGD', lr=0.1, momentum=0.9, weight_decay=0.0001) +``` + +然åŽåœ¨ç®—法库ä¸å¯ä»¥é€šè¿‡å¦‚下代ç æž„é€ ä¼˜åŒ–å™¨å¯¹è±¡ã€‚ + +```python +from mmengine import Config +from mmengine.Registry import OPTIMIZERS + +cfg = Config.fromfile('config_sgd.py') +optimizer = OPTIMIZERS.build(cfg.optimizer) +# 这里 optimizer 就是一个 torch.optim.SGD 对象 +``` + +è¿™æ ·ï¼Œæˆ‘ä»¬å°±å¯ä»¥åœ¨ä¸æ”¹åŠ¨ç®—法库代ç 的情况下,仅通过修改é…置文件,æ¥ä½¿ç”¨ä¸åŒçš„优化器。 + +## é…置文件的继承 + +有时候,两个ä¸åŒçš„é…置文件之间的差异很å°ï¼Œå¯èƒ½ä»…ä»…åªæ”¹äº†ä¸€ä¸ªå—段,我们就需è¦å°†æ‰€æœ‰å†…容å¤åˆ¶ç²˜è´´ä¸€æ¬¡ï¼Œè€Œä¸”在åŽç»è§‚察的时候,ä¸å®¹æ˜“定ä½åˆ°å…·ä½“差异的å—段。åˆæœ‰äº›æƒ…况下,多个é…置文件å¯èƒ½éƒ½æœ‰ç›¸åŒçš„一批å—段,我们ä¸å¾—ä¸åœ¨è¿™äº›é…置文件ä¸è¿›è¡Œå¤åˆ¶ç²˜è´´ï¼Œç»™åŽç»çš„修改和维护带æ¥äº†ä¸ä¾¿ã€‚ + +为了解决这些问题,我们给é…ç½®æ–‡ä»¶å¢žåŠ äº†ç»§æ‰¿çš„æœºåˆ¶ï¼Œå³ä¸€ä¸ªé…置文件 A å¯ä»¥å°†å¦ä¸€ä¸ªé…置文件 B 作为自己的基础,直接继承了 B ä¸æ‰€æœ‰å—段,而ä¸å¿…显å¼å¤åˆ¶ç²˜è´´ã€‚ + +### 继承机制概述 + +这里我们举一个例åæ¥è¯´æ˜Žç»§æ‰¿æœºåˆ¶ã€‚定义如下两个é…置文件, + +`optimizer_cfg.py`: + +```python +optimizer = dict(type='SGD', lr=0.02, momentum=0.9, weight_decay=0.0001) +``` + +`resnet50.py`: + +```python +_base_ = ['optimizer_cfg.py'] +model = dict(type='ResNet', depth=50) +``` + +虽然我们在 `resnet50.py` ä¸æ²¡æœ‰å®šä¹‰ optimizer å—段,但由于我们写了 `_base_ = ['optimizer_cfg.py']`,会使这个é…置文件获得 `optimizer_cfg.py` ä¸çš„所有å—段。 + +```python +cfg = Config.fromfile('resnet50.py') +cfg.optimizer # ConfigDict(type='SGD', lr=0.02, momentum=0.9, weight_decay=0.0001) +``` + +这里 `_base_` 是é…置文件的ä¿ç•™å—段,指定了该é…置文件的继承æ¥æºã€‚支æŒç»§æ‰¿å¤šä¸ªæ–‡ä»¶ï¼Œå°†åŒæ—¶èŽ·å¾—这多个文件ä¸çš„所有å—段,但是è¦æ±‚继承的多个文件ä¸**没有**相åŒå称的å—段,å¦åˆ™ä¼šæŠ¥é”™ã€‚ + +`runtime_cfg.py`: + +```python +gpu_ids = [0, 1] +``` + +`resnet50.py`: + +```python +_base_ = ['optimizer_cfg.py', 'runtime_cfg.py'] +model = dict(type='ResNet', depth=50) +``` + +这时,读å–é…置文件 `resnet50.py` 会获得 3 个å—段 `model`,`optimizer`,`gpu_ids`。 + +通过这ç§æ–¹å¼ï¼Œæˆ‘们å¯ä»¥å°†é…置文件进行拆分,定义一些通用é…置文件,在实际é…置文件ä¸ç»§æ‰¿å„ç§é€šç”¨é…置文件,å¯ä»¥å‡å°‘具体任务的é…ç½®æµç¨‹ã€‚ + +### 修改继承å—段 + +有时候,我们继承一个é…置文件之åŽï¼Œå¯èƒ½éœ€è¦å¯¹å…¶ä¸ä¸ªåˆ«å—段进行修改,例如继承了 `optimizer_cfg.py` 之åŽï¼Œæƒ³å°†å¦ä¹ 率从 0.02 修改为 0.01。 + +这时候,åªéœ€è¦åœ¨æ–°çš„é…置文件ä¸ï¼Œé‡æ–°å®šä¹‰ä¸€ä¸‹éœ€è¦ä¿®æ”¹çš„å—段å³å¯ã€‚注æ„由于 optimizer 这个å—段是一个å—典,我们åªéœ€è¦é‡æ–°å®šä¹‰è¿™ä¸ªå—典里é¢éœ€ä¿®æ”¹çš„下级å—段å³å¯ã€‚è¿™ä¸ªè§„åˆ™ä¹Ÿé€‚ç”¨äºŽå¢žåŠ ä¸€äº›ä¸‹çº§å—段。 + +`resnet50_lr0.01.py`: + +```python +_base_ = ['optimizer_cfg.py', 'runtime_cfg.py'] +model = dict(type='ResNet', depth=50) +optimizer = dict(lr=0.01) +``` + +读å–这个é…置文件之åŽï¼Œå°±å¯ä»¥å¾—到期望的结果。 + +```python +cfg = Config.fromfile('resnet50_lr0.01.py') +cfg.optimizer # ConfigDict(type='SGD', lr=0.01, momentum=0.9, weight_decay=0.0001) +``` + +对于éžå—典类型的å—段,例如整数,å—符串,列表ç‰ï¼Œé‡æ–°å®šä¹‰å³å¯å®Œå…¨è¦†ç›–,例如下é¢çš„写法就将 `gpu_ids` 这个å—段的值修改æˆäº† `[0]`。 + +```python +_base_ = ['optimizer_cfg.py', 'runtime_cfg.py'] +model = dict(type='ResNet', depth=50) +gpu_ids = [0] +``` + +### åˆ é™¤å—å…¸ä¸çš„ key + +有时候我们对于继承过æ¥çš„å—典类型å—段,ä¸ä»…仅是想修改其ä¸æŸäº› key,å¯èƒ½è¿˜éœ€è¦åˆ 除其ä¸çš„一些 key。这时候在é‡æ–°å®šä¹‰è¿™ä¸ªå—典时,需è¦æŒ‡å®š `_delete_=True`,表示将没有在新定义的å—å…¸ä¸å‡ºçŽ°çš„ key å…¨éƒ¨åˆ é™¤ã€‚ + +`resnet50.py`: + +```python +_base_ = ['optimizer_cfg.py', 'runtime_cfg.py'] +model = dict(type='ResNet', depth=50) +optimizer = dict(_delete_=True, type='SGD', lr=0.01) +``` + +这时候,`optimizer` 这个å—å…¸ä¸å°±åªæœ‰ `type` å’Œ `lr` 这两个 key,`momentum` å’Œ `weight_decay` å°†ä¸å†è¢«ç»§æ‰¿ã€‚ + +```python +cfg = Config.fromfile('resnet50_lr0.01.py') +cfg.optimizer # ConfigDict(type='SGD', lr=0.01) +``` + +### 引用被继承文件ä¸çš„å˜é‡ + +有时我们想é‡å¤åˆ©ç”¨ `_base_` ä¸å®šä¹‰çš„å—段内容,就å¯ä»¥é€šè¿‡ `{{_base_.xxxx}}` 获å–æ¥èŽ·å–对应å˜é‡çš„æ‹·è´ã€‚例如: + +```python +_base_ = ['resnet50.py'] +a = {{_base_.model}} +# ç‰ä»·äºŽ a = dict(type='ResNet', depth=50) +``` + +## 其他进阶用法 + +这里介ç»ä¸€ä¸‹é…置类的进阶用法,这些å°æŠ€å·§å¯èƒ½ä½¿ç”¨æˆ·å¼€å‘和使用算法库更简å•æ–¹ä¾¿ã€‚ + +### 预定义å—段 + +有时候我们希望é…置文件ä¸çš„一些å—段和当å‰è·¯å¾„或者文件åç‰ç›¸å…³ï¼Œè¿™é‡Œä¸¾ä¸€ä¸ªå…¸åž‹ä½¿ç”¨åœºæ™¯çš„例å。在è®ç»ƒæ¨¡åž‹æ—¶ï¼Œæˆ‘们会在é…置文件ä¸å®šä¹‰ä¸€ä¸ªå·¥ä½œç›®å½•ï¼Œå˜æ”¾è¿™ç»„实验é…置的模型和日志,那么对于ä¸åŒçš„é…置文件,我们期望定义ä¸åŒçš„工作目录。用户的一ç§å¸¸è§é€‰æ‹©æ˜¯ï¼Œç›´æŽ¥ä½¿ç”¨é…置文件å作为工作目录å的一部分,例如对于é…置文件 `config_setting1.py`,工作目录就是 `./work_dir/config_setting1`。 + +使用预定义å—段å¯ä»¥æ–¹ä¾¿åœ°å®žçŽ°è¿™ç§éœ€æ±‚,在é…置文件 `config_setting1.py` ä¸å¯ä»¥è¿™æ ·å†™ï¼š + +```Python +work_dir = './work_dir/{{ fileBasenameNoExtension }}' +``` + +这里 `{{ fileBasenameNoExtension }}` 表示该é…置文件的文件å(ä¸å«æ‹“展å),在é…置类读å–é…置文件的时候,会将这ç§ç”¨åŒèŠ±æ‹¬å·åŒ…èµ·æ¥çš„å—符串自动解æžä¸ºå¯¹åº”的实际值。 + +```Python +cfg = Config.fromfile('./config_setting1.py') +cfg.work_dir # "./work_dir/config_setting1" +``` + +ç›®å‰æ”¯æŒçš„预定义å—段有以下四ç§ï¼Œå˜é‡åå‚考自 [VS Code](https://code.visualstudio.com/docs/editor/variables-reference) ä¸çš„相关å—段: + +- `{{ fileDirname }}` - 当å‰æ–‡ä»¶çš„目录å,例如 `/home/your-username/your-project/folder` +- `{{ fileBasename }}` - 当å‰æ–‡ä»¶çš„文件å,例如 `file.py` +- `{{ fileBasenameNoExtension }}` - 当å‰æ–‡ä»¶ä¸åŒ…å«æ‰©å±•å的文件å,例如 file +- `{{ fileExtname }}` - 当å‰æ–‡ä»¶çš„扩展å,例如 `.py` + +### 导入自定义 Python æ¨¡å— + +å°†é…置与注册器结åˆèµ·æ¥ä½¿ç”¨æ—¶ï¼Œå¦‚果我们往注册器ä¸æ³¨å†Œäº†ä¸€äº›è‡ªå®šä¹‰çš„类,就å¯èƒ½ä¼šé‡åˆ°ä¸€äº›é—®é¢˜ã€‚å› ä¸ºè¯»å–é…置文件的时候,这部分代ç å¯èƒ½è¿˜æ²¡æœ‰è¢«æ‰§è¡Œåˆ°ï¼Œæ‰€ä»¥å¹¶æœªå®Œæˆæ³¨å†Œè¿‡ç¨‹ï¼Œä»Žè€Œå¯¼è‡´æž„建自定义类的时候报错。 + +例如我们新实现了一ç§ä¼˜åŒ–器 `SuperOptim`,相应代ç 在 my_package/my_module.py ä¸ã€‚ + +```python +from mmengine.registry import OPTIMIZERS + +@OPTIMIZERS.register_module() +class SuperOptim: + pass +``` + +我们为这个优化器的使用写了一个新的é…置文件 `optimizer_cfg.py`: + +```python +optimizer = dict(type='SuperOptim') +``` + +那么就需è¦åœ¨è¯»å–é…ç½®æ–‡ä»¶å’Œæž„é€ ä¼˜åŒ–å™¨ä¹‹å‰ï¼Œå¢žåŠ 一行 `from my_package import my_module` æ¥ä¿è¯å°†è‡ªå®šä¹‰çš„ç±» `SuperOptim` 注册到 OPTIMIZERS 注册器ä¸ï¼š + +```python +from mmengine import Config +from mmengine.Registry import OPTIMIZERS + +from my_package import my_module + +cfg = Config.fromfile('config_super_optim.py') +optimizer = OPTIMIZERS.build(cfg.optimizer) +``` + +è¿™æ ·å°±ä¼šå¯¼è‡´é™¤äº†ä¿®æ”¹é…置文件之外,还需è¦æ ¹æ®é…置文件的内容,æ¥å¯¹åº”修改è®ç»ƒæºä»£ç (å³å¢žåŠ 一些 import è¯å¥ï¼‰ï¼Œè¿èƒŒäº†æˆ‘们希望仅通过修改é…置文件就能控制模å—æž„é€ å’Œä½¿ç”¨çš„åˆè¡·ã€‚ + +为了解决这个问题,我们给é…置文件定义了一个ä¿ç•™å—段 `custom_imports`,用于将需è¦æå‰å¯¼å…¥çš„ Python 模å—,直接写在é…置文件ä¸ã€‚对于上述例å,就å¯ä»¥å°†é…置文件写æˆå¦‚下: + +```python +custom_imports = dict(imports=['my_package.my_module'], allow_failed_imports=False) +optimizer = dict(type='SuperOptim') +``` + +è¿™æ ·æˆ‘ä»¬å°±ä¸ç”¨åœ¨è®ç»ƒä»£ç ä¸å¢žåŠ 对应的 import è¯å¥ï¼Œåªéœ€è¦ä¿®æ”¹é…置文件就å¯ä»¥å®žçŽ°éžä¾µå…¥å¼å¯¼å…¥è‡ªå®šä¹‰æ³¨å†Œæ¨¡å—。 -- GitLab