customize_runtime.md 15 KB
Newer Older
1
# 自定义运行时配置
2
3
4

## 自定义优化器设置

5
优化器相关的配置是由 `optim_wrapper` 管理的,其通常有三个字段:`optimizer``paramwise_cfg``clip_grad`。更多细节请参考 [OptimWrapper](https://mmengine.readthedocs.io/zh_CN/latest/tutorials/optim_wrapper.html)。如下所示,使用 `AdamW` 作为`优化器`,骨干网络的学习率降低 10 倍,并添加了梯度裁剪。
6
7
8
9

```python
optim_wrapper = dict(
    type='OptimWrapper',
10
    # 优化器
11
12
13
14
15
16
17
    optimizer=dict(
        type='AdamW',
        lr=0.0001,
        weight_decay=0.05,
        eps=1e-8,
        betas=(0.9, 0.999)),

18
    # 参数级学习率及权重衰减系数设置
19
20
21
22
23
24
    paramwise_cfg=dict(
        custom_keys={
            'backbone': dict(lr_mult=0.1, decay_mult=1.0),
        },
        norm_decay_mult=0.0),

25
    # 梯度裁剪
26
27
28
    clip_grad=dict(max_norm=0.01, norm_type=2))
```

29
30
### 自定义 PyTorch 支持的优化器

31
我们已经支持使用所有 PyTorch 实现的优化器,且唯一需要修改的地方就是改变配置文件中的 `optim_wrapper` 字段中的 `optimizer` 字段。例如,如果您想使用 `Adam`(注意这样可能会使性能大幅下降),您可以这样修改:
32
33

```python
34
35
36
optim_wrapper = dict(
    type='OptimWrapper',
    optimizer=dict(type='Adam', lr=0.0003, weight_decay=0.0001))
37
38
```

39
为了修改模型的学习率,用户只需要修改 `optimizer` 中的 `lr` 字段。用户可以根据 PyTorch 的 [API 文档](https://pytorch.org/docs/stable/optim.html?highlight=optim#module-torch.optim)直接设置参数。
40
41
42
43
44
45
46

### 自定义并实现优化器

#### 1. 定义新的优化器

一个自定义优化器可以按照如下过程定义:

47
假设您想要添加一个叫 `MyOptimizer` 的,拥有参数 `a``b``c` 的优化器,您需要创建一个叫做 `mmdet3d/engine/optimizers` 的目录。接下来,应该在目录下某个文件中实现新的优化器,比如 `mmdet3d/engine/optimizers/my_optimizer.py`
48
49
50
51

```python
from torch.optim import Optimizer

52
53
from mmdet3d.registry import OPTIMIZERS

54
55
56
57

@OPTIMIZERS.register_module()
class MyOptimizer(Optimizer):

58
    def __init__(self, a, b, c):
59
        pass
60
61
62
63
```

#### 2. 将优化器添加到注册器

64
为了找到上述定义的优化器模块,该模块首先需要被引入主命名空间。有两种实现方法:
65

66
- 修改 `mmdet3d/engine/optimizers/__init__.py` 导入该模块。
67

68
  新定义的模块应该在 `mmdet3d/engine/optimizers/__init__.py` 中被导入,从而被找到并且被添加到注册器中:
69

70
71
72
  ```python
  from .my_optimizer import MyOptimizer
  ```
73

74
- 在配置中使用 `custom_imports` 来人工导入新优化器。
75

76
77
78
  ```python
  custom_imports = dict(imports=['mmdet3d.engine.optimizers.my_optimizer'], allow_failed_imports=False)
  ```
79

80
  模块 `mmdet3d.engine.optimizers.my_optimizer` 会在程序开始被导入,且 `MyOptimizer` 类在那时会自动被注册。注意到应该只有包含 `MyOptimizer` 类的包被导入。`mmdet3d.engine.optimizers.my_optimizer.MyOptimizer`**不能**被直接导入。
81

82
  事实上,用户可以在这种导入的方法中使用完全不同的文件目录结构,只要保证根目录能在 `PYTHONPATH` 中被定位。
83
84
85

#### 3. 在配置文件中指定优化器

86
接下来您可以在配置文件的 `optimizer` 字段中使用 `MyOptimizer`。在配置文件中,优化器在 `optimizer` 字段中以如下方式定义:
87
88

```python
89
90
91
optim_wrapper = dict(
    type='OptimWrapper',
    optimizer=dict(type='SGD', lr=0.02, momentum=0.9, weight_decay=0.0001))
92
93
94
95
96
```

为了使用您自己的优化器,该字段可以改为:

```python
97
98
99
optim_wrapper = dict(
    type='OptimWrapper',
    optimizer=dict(type='MyOptimizer', a=a_value, b=b_value, c=c_value))
100
101
```

102
### 自定义优化器封装构造器
103

104
部分模型可能会拥有一些参数专属的优化器设置,比如 BatchNorm 层的权重衰减 (weight decay)。用户可以通过自定义优化器封装构造器来对那些细粒度的参数进行调优。
105
106

```python
107
from mmengine.optim import DefaultOptimWrapperConstructor
108

109
from mmdet3d.registry import OPTIM_WRAPPER_CONSTRUCTORS
110
111
112
from .my_optimizer import MyOptimizer


113
114
@OPTIM_WRAPPER_CONSTRUCTORS.register_module()
class MyOptimizerWrapperConstructor(DefaultOptimWrapperConstructor):
115

116
117
118
    def __init__(self,
                 optim_wrapper_cfg: dict,
                 paramwise_cfg: Optional[dict] = None):
119
        pass
120

121
    def __call__(self, model: nn.Module) -> OptimWrapper:
122

123
        return optim_wrapper
124
125
```

126
默认优化器封装构造器在[这里](https://github.com/open-mmlab/mmengine/blob/main/mmengine/optim/optimizer/default_constructor.py#L18)实现。这部分代码也可以用作新优化器封装构造器的模板。
127
128
129

### 额外的设置

130
没有在优化器部分实现的技巧应该通过优化器封装构造器或者钩子来实现(比如逐参数的学习率设置)。我们列举了一些常用的可以稳定训练过程或者加速训练的设置。我们欢迎提供更多类似设置的 PR 和 issue。
131

132
- __使用梯度裁剪 (gradient clip) 来稳定训练过程__:一些模型依赖梯度裁剪技术来裁剪训练中的梯度,以稳定训练过程。举例如下:
133

134
  ```python
135
136
  optim_wrapper = dict(
      _delete_=True, clip_grad=dict(max_norm=35, norm_type=2))
137
  ```
138

139
  如果您的配置继承了一个已经设置了 `optim_wrapper` 的基础配置,那么您可能需要 `_delete_=True` 字段来覆盖基础配置中无用的设置。更多细节请参考[配置文档](https://mmdetection3d.readthedocs.io/zh_CN/dev-1.x/user_guides/config.html)
140

141
- __使用动量调度器 (momentum scheduler) 来加速模型收敛__:我们支持用动量调度器来根据学习率更改模型的动量,这样可以使模型更快地收敛。动量调度器通常和学习率调度器一起使用,例如,如下配置文件在 [3D 检测](https://github.com/open-mmlab/mmdetection3d/blob/dev-1.x/configs/_base_/schedules/cyclic-20e.py)中被用于加速模型收敛。更多细节请参考 [CosineAnnealingLR](https://github.com/open-mmlab/mmengine/blob/main/mmengine/optim/scheduler/lr_scheduler.py#L43)[CosineAnnealingMomentum](https://github.com/open-mmlab/mmengine/blob/main/mmengine/optim/scheduler/momentum_scheduler.py#L71) 的实现方法。
142
143

  ```python
144
  param_scheduler = [
145
146
147
      # 学习率调度器
      # 在前 8 个 epoch,学习率从 0 升到 lr * 10
      # 在接下来 12 个 epoch,学习率从 lr * 10 降到 lr * 1e-4
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
      dict(
          type='CosineAnnealingLR',
          T_max=8,
          eta_min=lr * 10,
          begin=0,
          end=8,
          by_epoch=True,
          convert_to_iter_based=True),
      dict(
          type='CosineAnnealingLR',
          T_max=12,
          eta_min=lr * 1e-4,
          begin=8,
          end=20,
          by_epoch=True,
          convert_to_iter_based=True),
164
165
166
      # 动量调度器
      # 在前 8 个 epoch,动量从 0 升到 0.85 / 0.95
      # 在接下来 12 个 epoch,动量从 0.85 / 0.95 升到 1
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
      dict(
          type='CosineAnnealingMomentum',
          T_max=8,
          eta_min=0.85 / 0.95,
          begin=0,
          end=8,
          by_epoch=True,
          convert_to_iter_based=True),
      dict(
          type='CosineAnnealingMomentum',
          T_max=12,
          eta_min=1,
          begin=8,
          end=20,
          by_epoch=True,
          convert_to_iter_based=True)
  ]
184
  ```
185

186
## 自定义训练调度
187

188
默认情况下我们使用阶梯式学习率衰减的 1 倍训练调度,这会调用 MMEngine 中的 [`MultiStepLR`](https://github.com/open-mmlab/mmengine/blob/main/mmengine/optim/scheduler/lr_scheduler.py#L144)。我们在[这里](https://github.com/open-mmlab/mmengine/blob/main/mmengine/optim/scheduler/lr_scheduler.py)支持了很多其他学习率调度,比如`余弦退火``多项式衰减`调度。下面是一些样例:
189

190
- 多项式衰减调度:
191

192
  ```python
193
194
195
196
197
198
199
200
  param_scheduler = [
      dict(
          type='PolyLR',
          power=0.9,
          eta_min=1e-4,
          begin=0,
          end=8,
          by_epoch=True)]
201
  ```
202

203
- 余弦退火调度:
204

205
  ```python
206
207
208
209
210
211
212
213
  param_scheduler = [
      dict(
          type='CosineAnnealingLR',
          T_max=8,
          eta_min=lr * 1e-5,
          begin=0,
          end=8,
          by_epoch=True)]
214
  ```
215

216
## 自定义训练循环控制器
217

218
默认情况下,我们在 `train_cfg` 中使用 `EpochBasedTrainLoop`,并在每一个训练 epoch 完成后进行一次验证,如下所示:
219
220

```python
221
train_cfg = dict(type='EpochBasedTrainLoop', max_epochs=12, val_begin=1, val_interval=1)
222
223
```

224
事实上,[`IterBasedTrainLoop`](https://github.com/open-mmlab/mmengine/blob/main/mmengine/runner/loops.py#L185)[`EpochBasedTrainLoop`](https://github.com/open-mmlab/mmengine/blob/main/mmengine/runner/loops.py#L18) 都支持动态间隔验证,如下所示:
225
226

```python
227
228
# 在第 365001 次迭代之前,我们每隔 5000 次迭代验证一次。
# 在第 365000 次迭代之后,我们每隔 368750 次迭代验证一次,
229
# 这意味着我们在训练结束后进行验证。
230
231
232
233
234
235
236
237
238

interval = 5000
max_iters = 368750
dynamic_intervals = [(max_iters // interval * interval + 1, max_iters)]
train_cfg = dict(
    type='IterBasedTrainLoop',
    max_iters=max_iters,
    val_interval=interval,
    dynamic_intervals=dynamic_intervals)
239
240
241
242
243
244
245
246
```

## 自定义钩子

### 自定义并实现钩子

#### 1. 实现一个新钩子

247
MMEngine 提供了一些实用的[钩子](https://mmengine.readthedocs.io/zh_CN/latest/tutorials/hook.html),但有些场合用户可能需要实现一个新的钩子。在 v1.1.0rc0 之后,MMDetection3D 在训练时支持基于 MMEngine 自定义钩子。因此用户可以直接在 mmdet3d 或者基于 mmdet3d 的代码库中实现钩子并通过更改训练配置来使用钩子。这里我们给出一个在 mmdet3d 中创建并使用新钩子的例子。
248
249

```python
250
251
252
from mmengine.hooks import Hook

from mmdet3d.registry import HOOKS
253
254
255
256
257
258
259


@HOOKS.register_module()
class MyHook(Hook):

    def __init__(self, a, b):

260
    def before_run(self, runner) -> None:
261

262
    def after_run(self, runner) -> None:
263

264
    def before_train(self, runner) -> None:
265

266
    def after_train(self, runner) -> None:
267

268
    def before_train_epoch(self, runner) -> None:
269

270
271
272
273
274
275
276
277
278
279
280
281
    def after_train_epoch(self, runner) -> None:

    def before_train_iter(self,
                          runner,
                          batch_idx: int,
                          data_batch: DATA_BATCH = None) -> None:

    def after_train_iter(self,
                         runner,
                         batch_idx: int,
                         data_batch: DATA_BATCH = None,
                         outputs: Optional[dict] = None) -> None:
282
283
```

284
用户需要根据钩子的功能指定钩子在每个训练阶段时的行为,具体包括如下阶段:`before_run``after_run``before_train``after_train``before_train_epoch``after_train_epoch``before_train_iter`,和 `after_train_iter`。有更多的位点可以插入钩子,详情可参考 [base hook class](https://github.com/open-mmlab/mmengine/blob/main/mmengine/hooks/hook.py#L9)
285
286
287

#### 2. 注册新钩子

288
接下来我们需要导入 `MyHook`。假设新钩子位于文件 `mmdet3d/engine/hooks/my_hook.py` 中,有两种实现方法:
289

290
- 修改 `mmdet3d/engine/hooks/__init__.py` 导入该模块。
291

292
  新定义的模块应该在 `mmdet3d/engine/hooks/__init__.py` 中被导入,从而被找到并且被添加到注册器中:
293

294
295
296
  ```python
  from .my_hook import MyHook
  ```
297

298
- 在配置中使用 `custom_imports` 来人为地导入新钩子。
299

300
301
302
  ```python
  custom_imports = dict(imports=['mmdet3d.engine.hooks.my_hook'], allow_failed_imports=False)
  ```
303
304
305
306
307
308
309
310
311

#### 3. 更改配置文件

```python
custom_hooks = [
    dict(type='MyHook', a=a_value, b=b_value)
]
```

312
您可以将字段 `priority` 设置为 `'NORMAL'` 或者 `'HIGHEST'` 来设置钩子的优先级,如下所示:
313
314
315
316
317
318
319

```python
custom_hooks = [
    dict(type='MyHook', a=a_value, b=b_value, priority='NORMAL')
]
```

320
321
322
323
324
325
326
默认情况下,注册阶段钩子的优先级为 `'NORMAL'`

### 使用 MMDetection3D 中实现的钩子

如果 MMDetection3D 中已经实现了该钩子,您可以直接通过更改配置文件来使用该钩子。

#### 例子:`DisableObjectSampleHook`
327

328
我们实现了一个名为 [DisableObjectSampleHook](https://github.com/open-mmlab/mmdetection3d/blob/dev-1.x/mmdet3d/engine/hooks/disable_object_sample_hook.py) 的自定义钩子在训练阶段达到指定 epoch 后禁用 `ObjectSample` 增强策略。
329

330
331
332
333
334
如果有需要的话我们可以在配置文件中设置它:

```python
custom_hooks = [dict(type='DisableObjectSampleHook', disable_after_epoch=15)]
```
335
336
337

### 更改默认的运行时钩子

338
有一些常用的钩子通过 `default_hooks` 注册,它们是:
339

340
341
- `IterTimerHook`:该钩子用来记录加载数据的时间 'data_time' 和模型训练一步的时间 'time'。
- `LoggerHook`:该钩子用来从`执行器(Runner)`的不同组件收集日志并将其写入终端,json 文件,tensorboard 和 wandb 等。
342
343
344
- `ParamSchedulerHook`:该钩子用来更新优化器中的一些超参数,例如学习率和动量。
- `CheckpointHook`:该钩子用来定期地保存检查点。
- `DistSamplerSeedHook`:该钩子用来设置采样和批采样的种子。
345
- `Det3DVisualizationHook`:该钩子用来可视化验证和测试过程的预测结果。
346

347
`IterTimerHook``ParamSchedulerHook``DistSamplerSeedHook` 都很简单,通常不需要修改,因此此处我们将介绍如何使用 `LoggerHook``CheckpointHook``Det3DVisualizationHook`
348

349
#### CheckpointHook
350

351
除了定期地保存检查点,[`CheckpointHook`](https://github.com/open-mmlab/mmengine/blob/main/mmengine/hooks/checkpoint_hook.py#L18) 提供了其它的可选项例如 `max_keep_ckpts``save_optimizer` 等。用户可以设置 `max_keep_ckpts` 只保存少量的检查点或者通过 `save_optimizer` 决定是否保存优化器的状态。参数的更多细节请参考[此处](https://github.com/open-mmlab/mmengine/blob/main/mmengine/hooks/checkpoint_hook.py#L18)
352
353

```python
354
355
356
357
358
359
default_hooks = dict(
    checkpoint=dict(
        type='CheckpointHook',
        interval=1,
        max_keep_ckpts=3,
        save_optimizer=True))
360
361
```

362
#### LoggerHook
363

364
`LoggerHook` 允许设置日志记录间隔。详细介绍可参考[文档](https://github.com/open-mmlab/mmengine/blob/main/mmengine/hooks/logger_hook.py#L19)
365
366

```python
367
default_hooks = dict(logger=dict(type='LoggerHook', interval=50))
368
```
369
370
371
372
373
374
375
376
377
378
379
380
381
382

#### Det3DVisualizationHook

`Det3DVisualizationHook` 使用 `DetLocalVisualizer` 来可视化预测结果,`Det3DLocalVisualizer` 支持不同的后端,例如 `TensorboardVisBackend``WandbVisBackend`(更多细节请参考[文档](https://github.com/open-mmlab/mmengine/blob/main/mmengine/visualization/vis_backend.py))。用户可以添加多个后端来进行可视化,如下所示。

```python
default_hooks = dict(
    visualization=dict(type='Det3DVisualizationHook', draw=True))

vis_backends = [dict(type='LocalVisBackend'),
                dict(type='TensorboardVisBackend')]
visualizer = dict(
    type='Det3DLocalVisualizer', vis_backends=vis_backends, name='visualizer')
```