第 6 章 模型优化方法#

在第5章内容中,我们详细介绍了深度学习模型训练过程中会用到的一些辅助技能和工具,以提高模型在训练过程的效率。在本章内容中我们将从模型优化的角度来介绍如何更快以及更好地训练一个深度学习模型。在本章内容中,我们将会详细介绍深度学习中常见的模型优化策略和方法,包括学习率调度器、梯度裁剪策略以及各种不同的归一化技术。同时,我们也将介绍基于梯度下降算法的各种改进版本,包括动量法、AdaGrad、AdaDelta和Adam等。这些算法为构建和优化深度学习模型提供了全面的指导和实践方法,并且能够帮助模型在训练过程中更快地进行收敛。

6.1 学习率调度器#

在深度学习模型训练过程中,当模型结果不太理想时最常用的做法之一就是动态调整学习率,即在模型的训练过程中采用某种策略让学习率动态变化。如最简单的方法可以是每迭代一轮学习率降为之前的一半。当然, 还可以通过其它一些更为复杂的策略来进行控制,例如在2017年谷歌发布的Transformer模型中,论文作者就采用了如下公式来动态调整学习率:

$$ \text{learning\_rate}=d\_{\text{model}}^{-0.5}\cdot \text{min}(\text{Steps}^{-0.5},\text{Steps}\cdot \text{ Warmup\_Steps}^{-1.5}) \tag{6-1} $$

其中$d_{\text{model}}$表示模型的维度,$\text{Steps}$表示小批量迭代的次数,$\text{Warmup\_Steps}$表示前期线性增长的迭代次数。

根据式(6-1)中的计算方式,模型在训练过程中学习率的变化情况将如图6-1所示。

图 6-1. Transformer动态学习率变化图

如图6-1所示,3条曲线分别表示3组参数下学习率随着迭代次的变化情况,其中到达顶点之前的线性增长阶段便是“热身”(Warmup)阶段。

当然,除了上述提到的两种学习率动态调整方式,常见的还有常数、线性、余弦变换等变换策略,并且还可以直接借助Transformers框架中的optimization模块来实现。在本节接下来的内容中,我们将会先介绍如何直接使用Transformers框架中的optimization模块来快速实现学习率动态调整的目的,然后再简单介绍一下各个方法背后的实现逻辑以及如何模仿来实现自定义的调整方法。

6.1.1 使用示例#

Transformers 框架是一个基于 PyTorch 和 TensorFlow 的开源自然语言处理框架,准确来说它应该是一个基于现有深度学习框架封装而来的高阶API库,由 HuggingFace 公司于2019年所发布 [2] [3]。Transformers 旨在提供便捷的训练、评估和部署最先进的NLP模型,特别是基于注意力机制的 Transformer 模型。该框架是目前最受欢迎和广泛使用的NLP框架之一,主要用于构建和训练自然语言处理模型,包括文本分类、序列标注、机器翻译、问答等任务。在后续的章节内容中我们也将陆续使用到该库。

图 6-2. Hugging Face公司标志图

Transformers 框架包含了各种最先进的NLP模型,如BERT、GPT、RoBERTa、T5等,并且还提供了大量预训练的模型和权重,用户可以直接使用这些预训练模型进行各种NLP任务的微调和迁移学习,无需从头开始训练模型,这大大节省了训练时间和资源。同时,Transformers 框架支持多种语言接口,包括Python、Java、JavaScript等,使得用户可以在不同的平台和环境中使用 Transformers 框架进行NLP任务的开发和部署。此外,Transformers 框架还支持分布式训练和模型并行化,使得用户能够高效地训练大规模的NLP模型。

在使用之前首先需要通过如下命令完成在Transformers框架的安装:

1 pip install transformers

接着通过如下方式导入其中的optimization模块:

1 from transformers import optimization

optimization模块中,一共包含了6种常见的学习率动态调整方式,包括constant、constant_with_warmup、linear、polynomial、cosine 和cosine_with_restarts,其分别通过一个函数来返回对应的实例化对象,下面我们开始分别对其使用方法进行介绍。以下完整示例代码可以参见Code/Chapter06/C01_LearningRate/mian.py文件。

1. constant策略

optimization模块中可以通过get_constant_schedule函数来返回对应的常数动态学习率调整方法。顾名思义,常数学习率动态调整就是学习率是一个恒定不变的常数,也就是说相当于没用。为了方便后续对学习率的变化过程可视化,这里先随意定义一个网络模型,示例代码如下所示:

1 class Model(nn.Module):
2     def __init__(self):
3         super(Model, self).__init__()
4         self.fc = nn.Linear(2,5)
5 
6     def forward(self, x):
7         out = self.fc(x).sum()
8         return out

进一步,在模型训练的过程中,我们可以通过以下方式来进行使用:

 1 if __name__ == '__main__':
 2     x = torch.rand([3, 2])
 3     model = Model()
 4     steps = 1000
 5     optimizer = torch.optim.Adam(model.parameters(), lr=1.0)
 6     scheduler = optimization.get_constant_schedule(optimizer, last_epoch=-1)
 7     name,lrs = "constant",[]
 8     for _ in range(steps):
 9         loss = model(x)
10         optimizer.zero_grad()
11         loss.backward()
12         optimizer.step()
13         scheduler.step()
14         lrs.append(scheduler.get_last_lr())

在上述代码中,第6行便是用来得到常数学习率变化的实例化对象,其中last_epoch用于在恢复训练时指定上次结束时的epoch数量,因为有些方法学习率的变化会与epoch数有关,如果不考虑模型恢复的话指定为-1即可,这部分内容将在本节末尾处进行详细介绍。第13行是对学习率进行更新。第14行是保存每次变化后的学习率便于可视化。

在整个迭代过程结束之后便可以得到如图6-3所示的可视化结果。

图 6-3. constant学习率变化图

如图6-3所示,模型在整个训练过程中学习率并没有发生变化,一直保持着1.0的初始值。

2. constant_with_warmup策略

optimization模块中可以通过get_constant_schedule_with_warmup函数来返回constant_with_warmup策略对应的动态学习率调整实例化方法。从名字可以看出,该方法最终得到的是一个带warmup的常数学习率变化。在模型训练的过程中可以通过以下方式进行使用:

1 scheduler = optimization.get_constant_schedule_with_warmup(
2             optimizer, num_warmup_steps=300)

其中num_warmup_steps表示warmup的迭代次数。

最后,该方法的可视化结果如图6-4所示。

图 6-4. constant_with_warmup学习率变化图学习率变化图

从图6-4可以看出,constant_with_warmup仅仅只是在最初的300个steps中以线性的方式进行增长,之后便是同样保持为常数。

3. linear策略

从名字可以看出,该方法最终得到的是一个带warmup的线性变换学习率调整方法。在模型训练的过程中,可以通过以下方式进行使用:

1 scheduler = optimization.get_linear_schedule_with_warmup(optimizer,
2                        num_warmup_steps=300,num_training_steps=steps)

其中num_training_steps表示整个模型训练的step数。

最后,该方法的可视化结果如图6-5所示。

图 6-5. linear学习率变化图学习率变化图

从图6-5可以看出,linear动态学习率调整策略先是在最初的300个迭代过程中以线性方式进行增长,之后便是同样以线性的方式进行递减,直到衰减到0为止。

4. polynomial策略

optimization模块中可以通过get_polynomial_decay_schedule_with_warmup函数来返回带热身的多项式动态学习率调整实例化方法。在模型训练的过程中可以通过以下方式进行使用:

1 scheduler = optimization.get_polynomial_decay_schedule_with_warmup(optimizer,
2             num_warmup_steps=300,num_training_steps=steps, lr_end = 1e-7, power=3)

其中power表示多项式的次数,当power=1时(默认)等价于get_linear_schedule_with_warmup函数。lr_end表示学习率衰减到的最小值。

最后,该方法的可视化结果如图6-6所示。

图 6-6. polynomial学习率变化图(power=3)

从图6-6可以看出,polynomial动态学习率调整策略先是在最初的300个迭代过程中以线性方式进行增长,之后便是多项式的方式进行递减,直到衰减到lr_end后保持不变。

5. cosine策略

optimization模块中可以通过get_cosine_schedule_with_warmup来返回基于cosine函数变化的动态学习率调整方法。在模型训练过程中可以通过如下方式进行使用:

1 scheduler = optimization.get_cosine_schedule_with_warmup(optimizer,
2               num_warmup_steps=300,num_training_steps=steps,num_cycles=2)

其中num_cycles表示循环的次数。

最后,该方法的可视化结果如图6-7所示。

图 6-7. cosine学习率变化图(num_cycles=2)

从图6-7可以看出,cosine动态学习率调整策略先是在最初的300个steps中以线性的方式进行增长,之后便是以余弦函数的方式进行周期性变换。

6. cosine_with_restarts策略

optimization模块中可以通过get_cosine_with_hard_restarts_schedule_with_warmup来返回基于cosine函数的硬重启动态学习率调整方法。所谓硬重启是指学习率衰减到0之后直接变回到最大值再次进行衰减。在模型训练过程中可以通过如下方式进行使用:

1 scheduler = optimization.get_cosine_with_hard_restarts_schedule_with_warmup(optimizer,
2           num_warmup_steps=300,num_training_steps=steps,num_cycles=2)

最后,该方法的可视化结果如图6-8所示。

图 6-8. cosine_with_restarts学习率变化图(num_cycles=2)

从图6-8可以看出,cosine_with_restarts动态学习率调整策略先是在最初的300个迭代过程中以线性方式进行增长,之后便是以余弦函数的方式进行周期性衰减,当达到最小值时再直接恢复到初始学习率。

7. get_scheduler方法

通过上述6个函数便能够返回得到相应的动态学习率调整方法。当然,如果你并不需要修改一些特定的参数,例如多项式中的power和余弦变换中的num_cycles等,那么还可以使用一个更加简单的统一接口来调用上述6个方法,示例代码如下所示:

1 from transformers import get_scheduler
2 def get_scheduler(
3     name: Union[str, SchedulerType],
4     optimizer: Optimizer,
5     num_warmup_steps: Optional[int] = None,
6     num_training_steps: Optional[int] = None):

在上述代码中,第3行name表示指定学习率调整的方式,可选项就是上面介绍的6种,并且通过constant、constant_with_warmup、linear、polynomial、cosine 和cosine_with_restarts这6个关键字就能够返回得到对应的方法;而对于其它特定的参数则会保持每个方法对应的默认值。例如通过get_scheduler函数返回get_cosine_with_hard_restarts_schedule_with_warmup时,num_cycles则为1

例如可以以如下方式来使用cosine_with_restarts策略进行学习率动态调整:

1 scheduler = get_scheduler(name="cosine_with_restarts", optimizer=optimizer,
2                          num_warmup_steps=300, num_training_steps=steps)

6.1.2 实现原理#

在介绍完Transformes框架中常见的6种学习率动态调整方法及其使用示例后,我们再来简单地一下其背后的实现原理。对于Transformers框架中所实现的这6种学习率动态调整方法本质上也是基于PyTorch框架中的LambdaLR类而来,其定义

1 from torch.optim.lr_scheduler import LambdaLR
2 class LambdaLR(_LRScheduler):
3     def __init__(self, optimizer, lr_lambda, last_epoch=-1):
4         pass

通过这个接口,只需要指定优化器、学习率系数的计算方式(函数)以及last_epoch参数来实例化类LambdaLR便可以返回得到相应的实例化对象。下面我们开始逐一进行介绍。

1. constant策略实现

对于constant的计算过程来说比较简单, 只需要传入一个返回值始终为1.0的匿名函数即可。因为返回的1将会作为一个系数乘以初始设定的学习率,以此来保证学习率不发生改变,实现代码如下所示:

1 def get_constant_schedule(Optimizer, last_epoch = -1):
2     return LambdaLR(optimizer, lambda _: 1, last_epoch=last_epoch)

在上述代码中,lambda _:1就是对应返回值为1的匿名函数,其中_表示不需要传入参数。

2. constant_with_warmup策略实现

52