如何分层设置学习率¶
在训练神经网络模型时,有时候需要为不同的网络层指定不同的学习率。例如,当我们在使用预训练的模型时,常常在预训练的主干网络模型上加入一些分支网络,这个时候我们希望在进行训练过程中,主干网络只进行微调,不需要过多改变参数,因此需要设置较小的学习率。而分支网络则需要快速地收敛,所以需要设置较大的学习率。这时设置统一的学习率很难满足要求,故需要对不同的网络层设置不同的学习率提升训练表现。
这篇文章以 MobileNet_v2 为例,展示如何在 Eager 和 Graph 模式下在不同层设置不同的学习率。
Eager模式¶
基础实现¶
此处使用的例子,基于 OneFlow 的 Eager 模式 修改得来。 导入库、加载数据集、搭建网络等都不变:
import oneflow as flow
import oneflow.nn as nn
import flowvision
import flowvision.transforms as transforms
BATCH_SIZE = 64
EPOCH_NUM = 1
DEVICE = "cuda" if flow.cuda.is_available() else "cpu"
print("Using {} device".format(DEVICE))
training_data = flowvision.datasets.CIFAR10(
root="data",
train=True,
transform=transforms.ToTensor(),
download=True,
source_url="https://oneflow-public.oss-cn-beijing.aliyuncs.com/datasets/cifar/cifar-10-python.tar.gz",
)
train_dataloader = flow.utils.data.DataLoader(
training_data, BATCH_SIZE, shuffle=True
)
model = flowvision.models.mobilenet_v2().to(DEVICE)
model.classifer = nn.Sequential(nn.Dropout(0.2), nn.Linear(model.last_channel, 10))
model.train()
loss_fn = nn.CrossEntropyLoss().to(DEVICE)
然后,为了使网络的不同层使用不同的学习率,需要准备一个字典,网络参数对应 params
,学习率对应 lr
。
param_groups = [
{'params':model.features.parameters(), 'lr':1e-3},
{'params':model.adaptive_avg_pool2d.parameters(), 'lr':1e-4},
{'params':model.classifier.parameters(), 'lr':1e-5},
]
optimizer = flow.optim.SGD(param_groups)
param_groups
是一个 list
,每一项是一个字典,将不同的参数分组保存在不同的字典中,字典属性 params
指定了参数,lr
属性指定了学习率大小。优化器接收 params_groups
这个 list
后,会遍历这个 list
中的每一项。对其中的 params
使用指定的学习率 lr
进行更新。
接下来对模型进行训练
for t in range(EPOCH_NUM):
print(f"Epoch {t+1}\n-------------------------------")
size = len(train_dataloader.dataset)
for batch, (x, y) in enumerate(train_dataloader):
x = x.to(DEVICE)
y = y.to(DEVICE)
# Compute prediction error
pred = model(x)
loss = loss_fn(pred, y)
# Backpropagation
optimizer.zero_grad()
loss.backward()
optimizer.step()
current = batch * BATCH_SIZE
if batch % 5 == 0:
print(f"loss: {loss:>7f} [{current:>5d}/{size:>5d}]")
自定义分层的学习率衰减策略¶
在 Eager 模式下,不同层设置不同的学习率实现很简单,我们只需要直接指定不同参数 lr
。然而,我们经常需要配合学习率衰减策略一起使用,这时,上面的方法不能满足要求。
不过,我们依然可以通过动态调整 param_groups
中各个字典的 lr
属性达到目的。
在之前代码的基础上,我们为每个字典新增一个属性 lr_decay_scale
作为衰减因子。
param_groups = [
{'params':model.features.parameters(), 'lr':1e-3, 'lr_scale':0.9},
{'params':model.adaptive_avg_pool2d.parameters(), 'lr':1e-4, 'lr_scale':0.8},
{'params':model.classifier.parameters(), 'lr':1e-5, 'lr_scale':0.7},
]
optimizer = flow.optim.SGD(param_groups)
然后自定义一个学习率调整函数。它读取字典中 lr_decay_scale
属性,更新 lr
属性。
def adjust_learning_rate(optimizer):
for param_group in optimizer.param_groups:
param_group["lr"] *= param_group["lr_scale"]
这样,在训练过程中,调用 adjust_learning_rate
,就可以分层地、动态调整学习率。
for t in range(EPOCH_NUM):
print(f"Epoch {t+1}\n-------------------------------")
size = len(train_dataloader.dataset)
for batch, (x, y) in enumerate(train_dataloader):
x = x.to(DEVICE)
y = y.to(DEVICE)
# Compute prediction error
pred = model(x)
loss = loss_fn(pred, y)
# Backpropagation
optimizer.zero_grad()
loss.backward()
optimizer.step()
current = batch * BATCH_SIZE
if batch % 5 == 0:
print(f"loss: {loss:>7f} [{current:>5d}/{size:>5d}]")
# Adjust the learning rate per 10 batches
if batch % 10 == 0:
adjust_learning_rate(optimizer)
Graph模式¶
在 Graph 模式下,同样的,我们导入必要的库,设置参数和设备,准备数据集。
import oneflow as flow
import oneflow.nn as nn
import flowvision
import flowvision.transforms as transforms
BATCH_SIZE = 64
EPOCH_NUM = 1
DEVICE = "cuda" if flow.cuda.is_available() else "cpu"
print("Using {} device".format(DEVICE))
training_data = flowvision.datasets.CIFAR10(
root="data",
train=True,
transform=transforms.ToTensor(),
download=True,
source_url="https://oneflow-public.oss-cn-beijing.aliyuncs.com/datasets/cifar/cifar-10-python.tar.gz",
)
train_dataloader = flow.utils.data.DataLoader(
training_data, BATCH_SIZE, shuffle=True, drop_last=True
)
搭建模型并设置损失函数。
model = flowvision.models.mobilenet_v2().to(DEVICE)
model.classifer = nn.Sequential(nn.Dropout(0.2), nn.Linear(model.last_channel, 10))
model.train()
loss_fn = nn.CrossEntropyLoss().to(DEVICE)
在设置优化器时,在 Eager 模式下,我们可以直接指定 params_groups
中的 lr
属性来设置学习率,而在 Graph 模型下,我们需要对不同的参数设置 lr_scale
属性来达到修改 lr
的目的。其中的 lr_scale
是 Graph 模式下内置的标准参数。
param_groups = [
{'params':model.features.parameters(), 'lr_scale':0.9},
{'params':model.adaptive_avg_pool2d.parameters(), 'lr_scale':0.8},
{'params':model.classifier.parameters(), 'lr_scale':0.7},
]
optimizer = flow.optim.SGD(param_groups, lr=1e-3)
一旦配置了 lr_scale
属性,OneFlow 会在静态图编译阶段检测到,并且在运行时使用lr=lr*lr_scale
来更新学习率。
接下来的使用同 使用 Graph 做训练 中一样,即:
class GraphMobileNetV2(flow.nn.Graph):
def __init__(self):
super().__init__()
self.model = model
self.loss_fn = loss_fn
self.add_optimizer(optimizer)
def build(self, x, y):
y_pred = self.model(x)
loss = self.loss_fn(y_pred, y)
loss.backward()
return loss
训练静态图模型。
graph_mobile_net_v2 = GraphMobileNetV2()
for t in range(EPOCH_NUM):
print(f"Epoch {t+1}\n-------------------------------")
size = len(train_dataloader.dataset)
for batch, (x, y) in enumerate(train_dataloader):
x = x.to(DEVICE)
y = y.to(DEVICE)
loss = graph_mobile_net_v2(x, y)
current = batch * BATCH_SIZE
if batch % 5 == 0:
print(f"loss: {loss:>7f} [{current:>5d}/{size:>5d}]")
至此,我们了解了在 Eager 模式和 Graph 模式下如何设置分层学习率。