mmpretrain/docs/zh_CN/user_guides/config.md

404 lines
20 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# 学习配置文件
为了管理深度学习实验的各种设置,我们使用配置文件来记录所有这些配置。这种配置文件系统具有模块化和继承特性,更多细节可以在{external+mmengine:doc}`MMEngine 中的教程 <tutorials/config>`。
MMClassification 主要使用 python 文件作为配置文件,所有配置文件都放置在 [`configs`](https://github.com/open-mmlab/mmclassification/tree/1.x/configs) 文件夹下,目录结构如下所示:
```text
MMClassification/
├── configs/
│ ├── _base_/ # 原始配置文件夹
│ │ ├── datasets/ # 原始数据集
│ │ ├── models/ # 原始模型
│ │ ├── schedules/ # 原始优化策略
│ │ └── default_runtime.py # 原始运行设置
│ ├── resnet/ # ResNet 算法文件夹
│ ├── swin_transformer/ # Swin 算法文件夹
│ ├── vision_transformer/ # ViT 算法文件夹
│ ├── ...
└── ...
```
可以使用 `python tools/misc/print_config.py /PATH/TO/CONFIG` 命令来查看完整的配置信息,从而方便检查所对应的配置文件。
本文主要讲解 MMClassification 配置文件的命名和结构,以及如何基于已有的配置文件修改,并以 [ResNet50 配置文件](https://github.com/open-mmlab/mmclassification/blob/1.x/configs/resnet/resnet50_8xb32_in1k.py) 逐行解释。
## 配置文件结构
`configs/_base_` 文件夹下有 4 个基本组件类型,分别是:
- [模型(model)](https://github.com/open-mmlab/mmclassification/tree/1.x/configs/_base_/models)
- [数据(data)](https://github.com/open-mmlab/mmclassification/tree/1.x/configs/_base_/datasets)
- [训练策略(schedule)](https://github.com/open-mmlab/mmclassification/tree/1.x/configs/_base_/schedules)
- [运行设置(runtime)](https://github.com/open-mmlab/mmclassification/blob/1.x/configs/_base_/default_runtime.py)
你可以通过继承一些基本配置文件轻松构建自己的训练配置文件。我们称这些被继承的配置文件为 _原始配置文件_,如 `_base_` 文件夹中的文件一般仅作为原始配置文件。
下面使用 [ResNet50 配置文件](https://github.com/open-mmlab/mmclassification/blob/1.x/configs/resnet/resnet50_8xb32_in1k.py) 作为案例进行说明并注释每一行含义。
```python
_base_ = [ # 此配置文件将继承所有 `_base_` 中的配置
'../_base_/models/resnet50.py', # 模型配置
'../_base_/datasets/imagenet_bs32.py', # 数据配置
'../_base_/schedules/imagenet_bs256.py', # 训练策略配置
'../_base_/default_runtime.py' # 默认运行设置
]
```
我们将在下面分别解释这四个原始配置文件。
### 模型配置
模型原始配置文件包含一个 `model` 字典数据结构,主要包括网络结构、损失函数等信息:
- `type` 分类器名称, 对于图像分类任务,通常为 `ImageClassifier`,更多细节请参考 [API 文档](mmcls.models.classifiers)。
- `backbone` 主干网设置,主干网络为主要的特征提取网络,比如 `ResNet`, `Swin Transformer`, `Vision Transformer` 等等。更多可用选项请参考 [API 文档](mmcls.models.backbones)。
- `neck` 颈网络设置,颈网络主要是连接主干网和分类的中间网络,比如 `GlobalAveragePooling` 等,更多可用选项请参考 [API 文档](mmcls.models.necks)。
- `head` 头网络设置,头网络主要是最后关联任务的分类部件,更多可用选项请参考 [API 文档](mmcls.models.heads)。
- `loss` 损失函数设置, 支持 `CrossEntropyLoss`, `LabelSmoothLoss` 等,更多可用选项参考 [API 文档](mmcls.models.losses)。
- `data_preprocessor`: 图像输入的预处理模块,输入在进入模型前的预处理操作,例如 `ClsDataPreprocessor`, 有关详细信息,请参阅 [API 文档](mmcls.models.utils.data_preprocessor)。
- `train_cfg`:训练模型时的额外设置。在 MMCLS 中,我们主要使用它来配置批量增强,例如 `Mixup``CutMix`。有关详细信息,请参阅 [文档](mmcls.models.utils.batch_augments)。
```{note}
配置文件中的 'type' 不是构造时的参数,而是类名。
```
以下是 ResNet50 的模型配置['configs/_base_/models/resnet50.py'](https://github.com/open-mmlab/mmclassification/blob/1.x/configs/_base_/models/resnet50.py)
```python
model = dict(
type='ImageClassifier', # 分类器类型, 目前只有 'ImageClassifier'
backbone=dict(
type='ResNet', # 主干网络类型
# 除了 `type` 之外的所有字段都来自 `ResNet` 类的 __init__ 方法
# 可查阅 https://mmclassification.readthedocs.io/zh_CN/1.x/api/generated/mmcls.models.ResNet.html
depth=50,
num_stages=4, # 主干网络状态(stages)的数目,这些状态产生的特征图作为后续的 head 的输入。
out_indices=(3, ), # 输出的特征图输出索引。
frozen_stages=-1, # 冻结主干网的层数
style='pytorch'),
neck=dict(type='GlobalAveragePooling'), # 颈网络类型
head=dict(
type='LinearClsHead', # 分类颈网络类型
# 除了 `type` 之外的所有字段都来自 `LinearClsHead` 类的 __init__ 方法
# 可查阅 https://mmclassification.readthedocs.io/zh_CN/1.x/api/generated/mmcls.models.LinearClsHead.html
num_classes=1000,
in_channels=2048,
loss=dict(type='CrossEntropyLoss', loss_weight=1.0), # 损失函数配置信息
topk=(1, 5), # 评估指标Top-k 准确率, 这里为 top1 与 top5 准确率
))
```
### 数据
数据原始配置文件主要包括预处理设置、dataloader 以及 评估器等设置:
- `data_preprocessor`: 模型输入预处理配置, 与 `model.data_preprocessor` 相同,但优先级更低。
- `train_evaluator | val_evaluator | test_evaluator`: 构建评估器,参考 [API 文档](mmcls.evaluation)。
- `train_dataloader | val_dataloader | test_dataloader`: 构建 dataloader
- `samples_per_gpu`: 每个 GPU 的 batch size
- `workers_per_gpu`: 每个 GPU 的线程数
- `sampler`: 采样器配置
- `dataset`: 数据集配置
- `type`: 数据集类型, MMClassification 支持 `ImageNet``Cifar` 等数据集 ,参考 [API 文档](mmcls.datasets)
- `pipeline`: 数据处理流水线,参考相关教程文档 [如何设计数据处理流水线](https://mmclassification.readthedocs.io/zh_CN/1.x/api/generated/tutorials/data_pipeline.html)
以下是 ResNet50 的数据配置 ['configs/_base_/datasets/imagenet_bs32.py'](https://github.com/open-mmlab/mmclassification/blob/1.x/configs/_base_/datasets/imagenet_bs32.py)
```python
dataset_type = 'ImageNet'
# 预处理配置
data_preprocessor = dict(
# 输入的图片数据通道以 'RGB' 顺序
mean=[123.675, 116.28, 103.53], # 输入图像归一化的 RGB 通道均值
std=[58.395, 57.12, 57.375], # 输入图像归一化的 RGB 通道标准差
to_rgb=True, # 是否将通道翻转,从 BGR 转为 RGB 或者 RGB 转为 BGR
)
train_pipeline = [
dict(type='LoadImageFromFile'), # 读取图像
dict(type='RandomResizedCrop', scale=224), # 随机放缩裁剪
dict(type='RandomFlip', prob=0.5, direction='horizontal'), # 随机水平翻转
dict(type='PackClsInputs'), # 准备图像以及标签
]
test_pipeline = [
dict(type='LoadImageFromFile'), # 读取图像
dict(type='ResizeEdge', scale=256, edge='short'), # 短边对其256进行放缩
dict(type='CenterCrop', crop_size=224), # 中心裁剪
dict(type='PackClsInputs'), # 准备图像以及标签
]
# 构造训练集 dataloader
train_dataloader = dict(
batch_size=32, # 每张GPU的 batchsize
num_workers=5, # 每个GPU的线程数
dataset=dict( # 训练数据集
type=dataset_type,
data_root='data/imagenet',
ann_file='meta/train.txt',
data_prefix='train',
pipeline=train_pipeline),
sampler=dict(type='DefaultSampler', shuffle=True), # 默认采样器
persistent_workers=True, # 是否保持进程可以缩短每个epoch的准备时间
)
# 构造验证集 dataloader
val_dataloader = dict(
batch_size=32,
num_workers=5,
dataset=dict(
type=dataset_type,
data_root='data/imagenet',
ann_file='meta/val.txt',
data_prefix='val',
pipeline=test_pipeline),
sampler=dict(type='DefaultSampler', shuffle=False),
persistent_workers=True,
)
# 验证集评估设置,使用准确率为指标, 这里使用 topk1 以及 top5 准确率
val_evaluator = dict(type='Accuracy', topk=(1, 5))
test_dataloader = val_dataloader # test dataloader配置这里直接与 val_dataloader相同
test_evaluator = val_evaluator # 测试集的评估配置,这里直接与 val_evaluator 相同
```
```{note}
'model.data_preprocessor' 既可以在 `model=dict(data_preprocessor=dict())`中定义,也可以使用此处的 `data_preprocessor` 定义, 同时配置时,优先使用 `model.data_preprocessor` 的配置。
```
### 训练策略
训练策略原始配置文件主要包括预优化器设置和训练、验证及测试的循环控制器(LOOP)
- `optim_wrapper`: 优化器装饰器配置信息,我们使用优化器装饰配置优化进程。
- `optimizer`: 支持 `pytorch` 所有的优化器,参考相关 {external+mmengine:doc}`MMEngine <tutorials/optim_wrapper>` 文档。
- `paramwise_cfg`: 根据参数的类型或名称设置不同的优化参数,参考相关 [学习策略文档](../advanced_guides/schedule.md) 文档。
- `accumulative_counts`: 积累几个反向传播后再优化参数,你可以用它通过小批量来模拟大批量。
- `param_scheduler` : 学习率策略,你可以指定训练期间的学习率和动量曲线。有关详细信息,请参阅 MMEngine 中的 {external+mmengine:doc}`文档 <tutorials/param_scheduler>`。
- `train_cfg | val_cfg | test_cfg`: 训练、验证以及测试的循环执行器配置,请参考相关的{external+mmengine:doc}`MMEngine 文档 <design/runner>`。
以下是 ResNet50 的训练策略配置['configs/_base_/schedules/imagenet_bs256.py'](https://github.com/open-mmlab/mmclassification/blob/1.x/configs/_base_/schedules/imagenet_bs256.py)
```python
optim_wrapper = dict(
# 使用 SGD 优化器来优化参数
optimizer=dict(type='SGD', lr=0.1, momentum=0.9, weight_decay=0.0001))
# 学习率参数的调整策略
# 'MultiStepLR' 表示使用多步策略来调度学习率LR
param_scheduler = dict(
type='MultiStepLR', by_epoch=True, milestones=[30, 60, 90], gamma=0.1)
# 训练的配置, 迭代 100 个epoch每一个训练 epoch 后都做验证集评估
# 'by_epoch=True' 默认使用 `EpochBaseLoop`, 'by_epoch=False' 默认使用 `IterBaseLoop`
train_cfg = dict(by_epoch=True, max_epochs=100, val_interval=1)
# 使用默认的验证循环控制器
val_cfg = dict()
# 使用默认的测试循环控制器
test_cfg = dict()
# 通过默认策略自动缩放学习率,此策略适用于总批次大小 256
# 如果你使用不同的总批量大小,比如 512 并启用自动学习率缩放
# 我们将学习率扩大到 2 倍
auto_scale_lr = dict(base_batch_size=256)
```
### 运行设置
本部分主要包括保存权重策略、日志配置、训练参数、断点权重路径和工作目录等等。
以下是几乎所有算法都使用的运行配置['configs/_base_/default_runtime.py'](https://github.com/open-mmlab/mmclassification/blob/1.x/configs/_base_/default_runtime.py)
```python
# 默认所有注册器使用的域
default_scope = 'mmcls'
# 配置默认的hook
default_hooks = dict(
# 记录每次迭代的时间。
timer=dict(type='IterTimerHook'),
# 每 100 次迭代打印一次日志。
logger=dict(type='LoggerHook', interval=100),
# 启用默认参数调度hook。
param_scheduler=dict(type='ParamSchedulerHook'),
# 每个epoch保存检查点。
checkpoint=dict(type='CheckpointHook', interval=1),
# 在分布式环境中设置采样器种子。
sampler_seed=dict(type='DistSamplerSeedHook'),
# 验证结果可视化,默认不启用,设置 True 时启用。
visualization=dict(type='VisualizationHook', enable=False),
)
# 配置环境
env_cfg = dict(
# 是否开启cudnn benchmark
cudnn_benchmark=False,
# 设置多进程参数
mp_cfg=dict(mp_start_method='fork', opencv_num_threads=0),
# 设置分布式参数
dist_cfg=dict(backend='nccl'),
)
# 设置可视化工具
vis_backends = [dict(type='LocalVisBackend')] # 使用磁盘(HDD)后端
visualizer = dict(
type='ClsVisualizer', vis_backends=vis_backends, name='visualizer')
# 设置日志级别
log_level = 'INFO'
# 从哪个检查点加载
load_from = None
# 是否从加载的检查点恢复训练
resume = False
```
## 继承并修改配置文件
为了精简代码、更快的修改配置文件以及便于理解,我们建议继承现有方法。
对于在同一算法文件夹下的所有配置文件MMClassification 推荐只存在 **一个** 对应的 _原始配置_ 文件。
所有其他的配置文件都应该继承 _原始配置_ 文件,这样就能保证配置文件的最大继承深度为 3。
例如,如果在 ResNet 的基础上做了一些修改,用户首先可以通过指定 `_base_ = './resnet50_8xb32_in1k.py'`(相对于你的配置文件的路径),来继承基础的 ResNet 结构、数据集以及其他训练配置信息,然后修改配置文件中的必要参数以完成继承。如想在基础 resnet50 的基础上使用 `CutMix` 训练增强,将训练轮数由 100 改为 300 和修改学习率衰减轮数,同时修改数据集路径,可以建立新的配置文件 `configs/resnet/resnet50_8xb32-300e_in1k.py` 文件中写入以下内容:
```python
# 在 'configs/resnet/' 创建此文件
_base_ = './resnet50_8xb32_in1k.py'
# 模型在之前的基础上使用 CutMix 训练增强
model = dict(
train_cfg=dict(
augments=dict(type='CutMix', alpha=1.0)
)
)
# 优化策略在之前基础上训练更多个 epoch
train_cfg = dict(max_epochs=300, val_interval=10) # 训练300个 epoch每10个 epoch 评估一次
param_scheduler = dict(step=[150, 200, 250]) # 学习率调整也有所变动
# 使用自己的数据集目录
train_dataloader = dict(
dataset=dict(data_root='mydata/imagenet/train'),
)
val_dataloader = dict(
batch_size=64, # 验证时没有反向传播,可以使用更大的 batchsize
dataset=dict(data_root='mydata/imagenet/val'),
)
test_dataloader = dict(
batch_size=64, # 测试时没有反向传播,可以使用更大的 batchsize
dataset=dict(data_root='mydata/imagenet/val'),
)
```
### 使用配置文件里的中间变量
用一些中间变量,中间变量让配置文件更加清晰,也更容易修改。
例如数据集里的 `train_pipeline` / `test_pipeline` 是作为数据流水线的中间变量。我们首先要定义它们,然后将它们传递到 `train_dataloader` / `test_dataloader` 中。如果想修改训练或测试时输入图片的大小,就需要修改 `train_pipeline` / `test_pipeline` 这些中间变量。
```python
bgr_mean = [103.53, 116.28, 123.675]
train_pipeline = [
dict(type='LoadImageFromFile'),
dict(type='RandomResizedCrop', scale=224, backend='pillow', interpolation='bicubic'),
dict(type='RandomFlip', prob=0.5, direction='horizontal'),
dict(
type='RandAugment',
policies='timm_increasing',
num_policies=2,
total_level=10,
magnitude_level=6,
magnitude_std=0.5,
hparams=dict(pad_val=[round(x) for x in bgr_mean], interpolation='bicubic')),
dict(type='PackClsInputs'),
]
test_pipeline = [
dict(type='LoadImageFromFile'),
dict(type='ResizeEdge', scale=236, edge='short', backend='pillow', interpolation='bicubic'),
dict(type='CenterCrop', crop_size=224),
dict(type='PackClsInputs')
]
train_dataloader = dict(dataset=dict(pipeline=train_pipeline))
val_dataloader = dict(dataset=dict(pipeline=val_pipeline))
test_dataloader = dict(dataset=dict(pipeline=val_pipeline))
```
### 忽略基础配置文件里的部分内容
有时,您需要设置 `_delete_=True` 去忽略基础配置文件里的一些域内容。可以查看 {external+mmengine:doc}`MMEngine 文档 <tutorials/config>` 进一步了解该设计。
以下是一个简单应用案例。 如果在上述 ResNet50 案例中 使用余弦调度 ,使用继承并直接修改会报 `get unexcepected keyword 'step'` 错, 因为基础配置文件 `param_scheduler` 域信息的 `'step'` 字段被保留下来了,需要加入 `_delete_=True` 去忽略基础配置文件里的 `param_scheduler` 相关域内容:
```python
_base_ = '../../configs/resnet/resnet50_8xb32_in1k.py'
# 学习率调整策略
param_scheduler = dict(type='CosineAnnealingLR', by_epoch=True, _delete_=True)
```
### 引用基础配置文件里的变量
有时,您可以引用 `_base_` 配置信息的一些域内容,这样可以避免重复定义。可以查看 {external+mmengine:doc}`MMEngine 文档 <tutorials/config>` 进一步了解该设计。
以下是一个简单应用案例,在训练数据预处理流水线中使用 `auto augment` 数据增强,参考配置文件 [`configs/resnest/resnest50_32xb64_in1k.py`](https://github.com/open-mmlab/mmclassification/blob/1.x/configs/resnest/resnest50_32xb64_in1k.py)。 在定义 `train_pipeline` 时,可以直接在 `_base_` 中加入定义 auto augment 数据增强的文件命名,再通过 `{{_base_.auto_increasing_policies}}` 引用变量:
```python
_base_ = [
'../_base_/models/resnest50.py', '../_base_/datasets/imagenet_bs64.py',
'../_base_/default_runtime.py', './_randaug_policies.py',
]
train_pipeline = [
dict(type='LoadImageFromFile'),
dict(
type='RandAugment',
policies={{_base_.policies}}, # 这里使用了 _base_ 里的 `policies` 参数。
num_policies=2,
magnitude_level=12),
dict(type='EfficientNetRandomCrop', scale=224, backend='pillow'),
dict(type='RandomFlip', prob=0.5, direction='horizontal'),
dict(type='ColorJitter', brightness=0.4, contrast=0.4, saturation=0.4),
dict(
type='Lighting',
eigval=EIGVAL,
eigvec=EIGVEC,
alphastd=0.1,
to_rgb=False),
dict(type='PackClsInputs'),
]
train_dataloader = dict(dataset=dict(pipeline=train_pipeline))
```
## 通过命令行参数修改配置信息
当用户使用脚本 "tools/train.py" 或者 "tools/test.py" 提交任务,以及使用一些工具脚本时,可以通过指定 `--cfg-options` 参数来直接修改所使用的配置文件内容。
- 更新配置文件内的字典
可以按照原始配置文件中字典的键的顺序指定配置选项。
例如,`--cfg-options model.backbone.norm_eval=False` 将主干网络中的所有 BN 模块更改为 `train` 模式。
- 更新配置文件内列表的键
一些配置字典在配置文件中会形成一个列表。例如,训练流水线 `data.train.pipeline` 通常是一个列表。
例如,`[dict(type='LoadImageFromFile'), dict(type='TopDownRandomFlip', flip_prob=0.5), ...]` 。如果要将流水线中的 `'flip_prob=0.5'` 更改为 `'flip_prob=0.0'`,您可以这样指定 `--cfg-options data.train.pipeline.1.flip_prob=0.0`
- 更新列表/元组的值。
当配置文件中需要更新的是一个列表或者元组,例如,配置文件通常会设置 `val_evaluator = dict(type='Accuracy', topk=(1, 5))`,用户如果想更改 `topk`
需要指定 `--cfg-options val_evaluator.topk="(1,3)"`。注意这里的引号 " 对于列表以及元组数据类型的修改是必要的,
并且 **不允许** 引号内所指定的值的书写存在空格。