schedule.md 14.6 KB
Newer Older
limm's avatar
limm committed
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
# 自定义训练优化策略

在我们的算法库中,已经提供了通用数据集(如ImageNet,CIFAR)的[默认训练策略配置](https://github.com/open-mmlab/mmpretrain/blob/main/configs/_base_/schedules)。如果想要在这些数据集上继续提升模型性能,或者在不同数据集和方法上进行新的尝试,我们通常需要修改这些默认的策略。

在本教程中,我们将介绍如何在运行自定义训练时,通过修改配置文件进行构造优化器、参数化精细配置、梯度裁剪、梯度累计以及定制动量调整策略等。同时也会通过模板简单介绍如何自定义开发优化器和构造器。

## 配置训练优化策略

我们通过 `optim_wrapper` 来配置主要的优化策略,包括优化器的选择,混合精度训练的选择,参数化精细配置,梯度裁剪以及梯度累计。接下来将分别介绍这些内容。

### 构造 PyTorch 内置优化器

MMPretrain 支持 PyTorch 实现的所有优化器,仅需在配置文件中,指定优化器封装需要的 `optimizer` 字段。

如果要使用 [`SGD`](torch.optim.SGD),则修改如下。这里要注意所有优化相关的配置都需要封装在 `optim_wrapper` 配置里。

```python
optim_wrapper = dict(
    type='OptimWrapper',
    optimizer=dict(type='SGD', lr=0.0003, weight_decay=0.0001)
)
```

```{note}
配置文件中的 'type' 不是构造时的参数,而是 PyTorch 内置优化器的类名。
更多优化器选择可以参考{external+torch:ref}`PyTorch 支持的优化器列表<optim:algorithms>`。
```

要修改模型的学习率,只需要在优化器的配置中修改 `lr` 即可。
要配置其他参数,可直接根据 [PyTorch API 文档](torch.optim) 进行。

例如,如果想使用 [`Adam`](torch.optim.Adam) 并设置参数为 `torch.optim.Adam(params, lr=0.001, betas=(0.9, 0.999), eps=1e-08, weight_decay=0, amsgrad=False)`
则需要进行如下修改:

```python
optim_wrapper = dict(
    type='OptimWrapper',
    optimizer = dict(
        type='Adam',
        lr=0.001,
        betas=(0.9, 0.999),
        eps=1e-08,
        weight_decay=0,
        amsgrad=False),
)
```

````{note}
考虑到对于单精度训练来说,优化器封装的默认类型就是 `OptimWrapper`,我们在这里可以直接省略,因此配置文件可以进一步简化为:

```python
optim_wrapper = dict(
    optimizer=dict(
        type='Adam',
        lr=0.001,
        betas=(0.9, 0.999),
        eps=1e-08,
        weight_decay=0,
        amsgrad=False))
```
````

### 混合精度训练

如果我们想要使用混合精度训练(Automactic Mixed Precision),我们只需简单地将 `optim_wrapper` 的类型改为 `AmpOptimWrapper`

```python
optim_wrapper = dict(type='AmpOptimWrapper', optimizer=...)
```

另外,为了方便,我们同时在启动训练脚本 `tools/train.py` 中提供了 `--amp` 参数作为开启混合精度训练的开关,更多细节可以参考[训练教程](../user_guides/train.md)

### 参数化精细配置

在一些模型中,不同的优化策略需要适应特定的参数,例如不在 BatchNorm 层使用权重衰减,或者在不同层使用不同的学习率等等。
我们需要用到 `optim_wrapper` 中的 `paramwise_cfg` 参数来进行精细化配置。

- **为不同类型的参数设置超参乘子**

  例如,我们可以在 `paramwise_cfg` 配置中设置 `norm_decay_mult=0.` 来改变归一化层权重和偏移的衰减为0。

  ```python
  optim_wrapper = dict(
      optimizer=dict(type='SGD', lr=0.8, weight_decay=1e-4),
      paramwise_cfg=dict(norm_decay_mult=0.))
  ```

  支持更多类型的参数配置,参考以下列表:

  - `bias_lr_mult`:偏置的学习率系数(不包括正则化层的偏置以及可变形卷积的 offset),默认值为 1
  - `bias_decay_mult`:偏置的权值衰减系数(不包括正则化层的偏置以及可变形卷积的 offset),默认值为 1
  - `norm_decay_mult`:正则化层权重和偏置的权值衰减系数,默认值为 1
  - `flat_decay_mult`: 一维参数的权值衰减系数,默认值为 1
  - `dwconv_decay_mult`:Depth-wise 卷积的权值衰减系数,默认值为 1
  - `bypass_duplicate`:是否跳过重复的参数,默认为 `False`
  - `dcn_offset_lr_mult`:可变形卷积(Deformable Convolution)的学习率系数,默认值为 1

- **为特定参数设置超参乘子**

  MMPretrain 通过 `paramwise_cfg``custom_keys` 参数来配置特定参数的超参乘子。

  例如,我们可以通过以下配置来设置所有 `backbone.layer0` 层的学习率和权重衰减为0, `backbone` 的其余层和优化器保持一致,另外 `head` 层的学习率为0.001.

  ```python
  optim_wrapper = dict(
      optimizer=dict(type='SGD', lr=0.01, weight_decay=0.0001),
      paramwise_cfg=dict(
          custom_keys={
              'backbone.layer0': dict(lr_mult=0, decay_mult=0),
              'backbone': dict(lr_mult=1),
              'head': dict(lr_mult=0.1)
          }))
  ```

### 梯度裁剪

在训练过程中,损失函数可能接近于一些异常陡峭的区域,从而导致梯度爆炸。而梯度裁剪可以帮助稳定训练过程,更多介绍可以参见[该页面](https://paperswithcode.com/method/gradient-clipping)

目前我们支持在 `optim_wrapper` 字段中添加 `clip_grad` 参数来进行梯度裁剪,更详细的参数可参考 [PyTorch 文档](torch.nn.utils.clip_grad_norm_)

用例如下:

```python
optim_wrapper = dict(
    optimizer=dict(type='SGD', lr=0.01, weight_decay=0.0001),
    # norm_type: 使用的范数类型,此处使用范数2。
    clip_grad=dict(max_norm=35, norm_type=2))
```

### 梯度累计

计算资源缺乏缺乏时,每个训练批次的大小(batch size)只能设置为较小的值,这可能会影响模型的性能。

可以使用梯度累计来规避这一问题。我们支持在 `optim_wrapper` 字段中添加 `accumulative_counts` 参数来进行梯度累计。

用例如下:

```python
train_dataloader = dict(batch_size=64)
optim_wrapper = dict(
    optimizer=dict(type='SGD', lr=0.01, weight_decay=0.0001),
    accumulative_counts=4)
```

表示训练时,每 4 个 iter 执行一次反向传播。由于此时单张 GPU 上的批次大小为 64,也就等价于单张 GPU 上一次迭代的批次大小为 256,也即:

```python
train_dataloader = dict(batch_size=256)
optim_wrapper = dict(
    optimizer=dict(type='SGD', lr=0.01, weight_decay=0.0001))
```

## 配置参数优化策略

在训练过程中,优化参数例如学习率、动量,通常不会是固定不变,而是随着训练进程的变化而调整。PyTorch 支持一些学习率调整的调度器,但是不足以完成复杂的策略。在 MMPretrain 中,我们提供 `param_scheduler` 来更好地控制不同优化参数的策略。

### 配置学习率调整策略

深度学习研究中,广泛应用学习率衰减来提高网络的性能。我们支持大多数 PyTorch 学习率调度器, 其中包括 `ExponentialLR`, `LinearLR`, `StepLR`, `MultiStepLR` 等等。

- **单个学习率策略**

  多数情况下,我们使用单一学习率策略,这里 `param_scheduler` 会是一个字典。比如在默认的 ResNet 网络训练中,我们使用阶梯式的学习率衰减策略 [`MultiStepLR`](mmengine.optim.MultiStepLR),配置文件为:

  ```python
  param_scheduler = dict(
      type='MultiStepLR',
      by_epoch=True,
      milestones=[100, 150],
      gamma=0.1)
  ```

  或者我们想使用 [`CosineAnnealingLR`](mmengine.optim.CosineAnnealingLR) 来进行学习率衰减:

  ```python
  param_scheduler = dict(
      type='CosineAnnealingLR',
      by_epoch=True,
      T_max=num_epochs)
  ```

- **多个学习率策略**

  然而在一些其他情况下,为了提高模型的精度,通常会使用多种学习率策略。例如,在训练的早期阶段,网络容易不稳定,而学习率的预热就是为了减少这种不稳定性。

  整个学习过程中,学习率将会通过预热从一个很小的值逐步提高到预定值,再会通过其他的策略进一步调整。

  在 MMPretrain 中,我们同样使用 `param_scheduler` ,将多种学习策略写成列表就可以完成上述预热策略的组合。

  例如:

  1. 在前50次迭代中逐**迭代次数****线性**预热

  ```python
    param_scheduler = [
        # 逐迭代次数,线性预热
        dict(type='LinearLR',
            start_factor=0.001,
            by_epoch=False,  # 逐迭代次数
            end=50),  # 只预热50次迭代次数
        # 主要的学习率策略
        dict(type='MultiStepLR',
            by_epoch=True,
            milestones=[8, 11],
            gamma=0.1)
    ]
  ```

  2. 在前10轮迭代中逐**迭代次数****线性**预热

  ```python
    param_scheduler = [
        # 在前10轮迭代中,逐迭代次数,线性预热
        dict(type='LinearLR',
            start_factor=0.001,
            by_epoch=True,
            end=10,
            convert_to_iter_based=True,  # 逐迭代次数更新学习率.
        ),
        # 在 10 轮次后,通过余弦退火衰减
        dict(type='CosineAnnealingLR', by_epoch=True, begin=10)
    ]
  ```

  注意这里增加了 `begin``end` 参数,这两个参数指定了调度器的**生效区间**。生效区间通常只在多个调度器组合时才需要去设置,使用单个调度器时可以忽略。当指定了 `begin``end` 参数时,表示该调度器只在 [begin, end) 区间内生效,其单位是由 `by_epoch` 参数决定。在组合不同调度器时,各调度器的 `by_epoch` 参数不必相同。如果没有指定的情况下,`begin` 为 0, `end` 为最大迭代轮次或者最大迭代次数。

  如果相邻两个调度器的生效区间没有紧邻,而是有一段区间没有被覆盖,那么这段区间的学习率维持不变。而如果两个调度器的生效区间发生了重叠,则对多组调度器叠加使用,学习率的调整会按照调度器配置文件中的顺序触发(行为与 PyTorch 中 [`ChainedScheduler`](torch.optim.lr_scheduler.ChainedScheduler) 一致)。

  ```{tip}
  为了避免学习率曲线与预期不符, 配置完成后,可以使用 MMPretrain 提供的 [学习率可视化工具](../useful_tools/scheduler_visualization.md) 画出对应学习率调整曲线。
  ```

### 配置动量调整策略

MMPretrain 支持动量调度器根据学习率修改优化器的动量,从而使损失函数收敛更快。用法和学习率调度器一致。

我们支持的动量策略和详细的使用细节可以参考[这里](https://github.com/open-mmlab/mmengine/blob/main/mmengine/optim/scheduler/momentum_scheduler.py)。我们只将调度器中的 `LR` 替换为了 `Momentum`,动量策略可以直接追加 `param_scheduler` 列表中。

这里是一个用例:

```python
param_scheduler = [
    # 学习率策略
    dict(type='LinearLR', ...),
    # 动量策略
    dict(type='LinearMomentum',
         start_factor=0.001,
         by_epoch=False,
         begin=0,
         end=1000)
]
```

## 新增优化器或者优化器构造器

```{note}
本部分将修改 MMPretrain 源码或者向 MMPretrain 框架添加代码,初学者可跳过。
```

### 新增优化器

在学术研究和工业实践中,可能需要使用 MMPretrain 未实现的优化方法,可以通过以下方法添加。

1. 定义一个新的优化器

   一个自定义的优化器可根据如下规则进行定制:

   假设我们想添加一个名为 `MyOptimzer` 的优化器,其拥有参数 `a`, `b``c`
   可以创建一个名为 `mmpretrain/engine/optimizer` 的文件夹,并在目录下的一个文件,如 `mmpretrain/engine/optimizer/my_optimizer.py` 中实现该自定义优化器:

   ```python
   from mmpretrain.registry import OPTIMIZERS
   from torch.optim import Optimizer


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

       def __init__(self, a, b, c):
           ...

       def step(self, closure=None):
           ...
   ```

2. 注册优化器

   要注册上面定义的上述模块,首先需要将此模块导入到主命名空间中。有两种方法可以实现它。

   修改 `mmpretrain/engine/optimizers/__init__.py`,将其导入至 `mmpretrain.engine` 包。

   ```python
   # 在 mmpretrain/engine/optimizers/__init__.py 中
   ...
   from .my_optimizer import MyOptimizer # MyOptimizer 是我们自定义的优化器的名字

   __all__ = [..., 'MyOptimizer']
   ```

   在运行过程中,我们会自动导入 `mmpretrain.engine` 包并同时注册 `MyOptimizer`

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

   之后,用户便可在配置文件的 `optim_wrapper.optimizer` 域中使用 `MyOptimizer`

   ```python
   optim_wrapper = dict(
       optimizer=dict(type='MyOptimizer', a=a_value, b=b_value, c=c_value))
   ```

### 新增优化器构造器

某些模型可能具有一些特定于参数的设置以进行优化,例如为所有 BatchNorm 层设置不同的权重衰减。

尽管我们已经可以使用 [`optim_wrapper.paramwise_cfg` 字段](#参数化精细配置)来配置特定参数的优化设置,但可能仍然无法覆盖你的需求。

当然你可以在此基础上进行修改。我们默认使用 [`DefaultOptimWrapperConstructor`](mmengine.optim.DefaultOptimWrapperConstructor) 来构造优化器。在构造过程中,通过 `paramwise_cfg` 来精细化配置不同设置。这个默认构造器可以作为新优化器构造器实现的模板。

我们可以新增一个优化器构造器来覆盖这些行为。

```python
# 在 mmpretrain/engine/optimizers/my_optim_constructor.py 中
from mmengine.optim import DefaultOptimWrapperConstructor
from mmpretrain.registry import OPTIM_WRAPPER_CONSTRUCTORS


@OPTIM_WRAPPER_CONSTRUCTORS.register_module()
class MyOptimWrapperConstructor:

    def __init__(self, optim_wrapper_cfg, paramwise_cfg=None):
        ...

    def __call__(self, model):
        ...
```

这是一个已实现的 [OptimWrapperConstructor](mmpretrain.engine.optimizers.LearningRateDecayOptimWrapperConstructor) 具体例子。

接下来类似 [新增优化器教程](#新增优化器) 来导入并使用新的优化器构造器。

1. 修改 `mmpretrain/engine/optimizers/__init__.py`,将其导入至 `mmpretrain.engine` 包。

   ```python
   # 在 mmpretrain/engine/optimizers/__init__.py 中
   ...
   from .my_optim_constructor import MyOptimWrapperConstructor

   __all__ = [..., 'MyOptimWrapperConstructor']
   ```

2. 在配置文件的 `optim_wrapper.constructor` 字段中使用 `MyOptimWrapperConstructor`

   ```python
   optim_wrapper = dict(
       constructor=dict(type='MyOptimWrapperConstructor'),
       optimizer=...,
       paramwise_cfg=...,
   )
   ```