react 前端框架如何驱动企业数字化转型与创新发展
1094
2022-11-15
pytorch 自定义损失函数、优化器(Optimizer)和学习率策略
文章目录
本节内容梯度下降回顾Pytorch 实现梯度下降与参数更新自定义损失函数自定义优化器自定义学习率策略
使用 `LambdaLR` 实现简单的学习率策略通过继承 `_LRScheduler` 实现自定义的学习率策略通过手动更新Optimizer中的学习率来自定义学习策略
参考资料
本节内容
梯度下降回顾理解Pytorch模型定义、前向传播、反向传播、更新梯度的过程学会并理解自定义损失函数学会并理解优化器的作用和使用学会并理解自定义学习策略的多种方法
梯度下降回顾
我们首先来简单的回顾一下梯度下降,我们使用一个很简单的例子来说明。
之后我们使用均方误差损失函数(MSE)来求出损失:
Pytorch 实现梯度下降与参数更新
本节我们来对上一节的例子使用Pytorch进行实验,来感受一下Pytorch的梯度下降:
theta = Variable(torch.FloatTensor([100]), requires_grad=True) # θb = Variable(torch.FloatTensor([50]), requires_grad=True) x = 3y_hat = 348 # 真实值
由于 theta 和 b 是需要求梯度的,所以用Variable封装
y = theta * x +
输出为:
tensor([350.], grad_fn=
之后我们使用均方误差损失函数(MSE)来求出损失:
L = (y-y_hat) ** 2
输出为:
tensor([4.], grad_fn=
接下来,我们使用损失对需要L函数中的变量进行求导:
L.backward()print("theta.grad:", theta.grad)print("b.grad:", b.grad)
输出为:
theta.grad: tensor([12.])b.grad: tensor([4.])
optimizer = torch.optim.SGD([theta, b], lr=2) # 传入要更新的参数和学习率optimizer.step() # 更新参数print("theta:", theta)print("b:", b)
在看完这个例子后,相信你已经知道pytorch的模型、损失函数、优化器在整个模型训练中都扮演者什么样的作用了,其实就是以下几点:
自定义损失函数
看完了上面两节,自定义损失函数应该就不攻自破了。其实损失函数就是定义一个模型,再多进行一步前向传递即可。这里我们将上一节的例子再进行一下改造。
首先,我们定义出我们的模型:
class SimpleModel(nn.Module): def __init__(self): super(SimpleModel, self).__init__() self.theta = Variable(torch.FloatTensor([100]), requires_grad=True) # θ self.b = Variable(torch.FloatTensor([50]), requires_grad=True) def forward(self, x): return self.theta * x + self.b
接下来定义损失函数:
class SimpleMSELoss(nn.Module): def __init__(self): super(SimpleMSELoss, self).__init__() def forward(self, y, y_hat): return (y - y_hat) ** 2
读者们可以尝试下如何把它们两个合并
最后我们来使用一下:
x = 3y_hat = 348 # 真实值model = SimpleModel()criteria = SimpleMSELoss()y = model(3)loss = criteria(y, y_hat)loss.backward()print("theta.grad:", model.theta.grad)print("b.grad:", model.b.grad)
输出为:
theta.grad: tensor([12.])theta.b: tensor([4.])
自定义优化器
在Pytorch中自定义优化器需要继承基类torch.optim.Optimizer,然后实现其step方法即可。
Optimizer的部分源码如下:
class Optimizer(object): def __init__(self, params, defaults): ... param_groups = list(params) ... if not isinstance(param_groups[0], dict): param_groups = [{'params': param_groups}] ... def step(self, closure): raise
Optimizer的初始化方法需要两个必填参数:
params:这个就是模型参数,严格来说应该是要进行更新的参数。Optimizer会将其放在param_groups这个变量下defaults:这个是模型的一些默认配置。传空字典也没关系
Optimizer最重要的是step方法,这个是用户需要进行参数更新的地方,用户需要自己实现该方法,通常是从param_groups拿出模型参数,然后进行更新。
我们还接着之前的例子,实现一个简单的优化器,我们优化器的需求是:和SGD类似,但是每次学习率都乘以一个[0-1]的随机数。代码如下:
class MyOptimizer(Optimizer): def __init__(self, params, lr): self.lr = lr super(MyOptimizer, self).__init__(params, {}) def step(self, closure=False): # 这个closure我也不知道干啥的,应该不重要 random_num = 0.4 # 假设生成的随机数为0.4 for param_group in self.param_groups: params = param_group['params'] # 从param_group中拿出参数 for param in params: # 循环更新每一个参数的值 param.data = param.data - self.lr * random_num * param.grad
然后用法和内置的Optimizer一致:
optimizer = MyOptimizer([model.theta, model.b], lr=2)optimizer.step()print("theta:", model.theta)print("b:", model.b)
输出为:
theta: tensor([90.4000], requires_grad=True)b: tensor([46.8000], requires_grad=True)
自定义学习率策略
要定义学习策略,其实最好的方式就是把他集成在Optimizer中,例如torch.optim.Adagrad就是这么做的。但很多时候我们并不想改变原有的复杂算法,只是想对他的学习率动一下手脚,此时就用到了自定义学习策略。
Pytorch实现了很多自定义学习策略,具体可参考该链接。
我们先使用一个Pytorch实现好的,来看一下怎么用。假设需求为:更新100次参数,每10步将学习率减小一半。针对这个需求,我们可以使用torch.optim.lr_scheduler.StepLR来完成。代码如下:
epoch = 100 # 循环100次,每一个epoch更新一次参数theta_list = [] # 记录一下theta的变化# 1.初始化要更新的参数theta,初始化为1theta = Variable(torch.FloatTensor([1]), requires_grad=True)# 2. 定义SGD优化器,学习率设为0.1optimizer = torch.optim.SGD([theta], lr=0.1)# 3. 定义学习率策略,每{step_size}步,学习率乘以{gamma}lr_scheduler = StepLR(optimizer, step_size=10, gamma=0.5)# 4. 开始进行模型训练for i in range(epoch): # 4.1 手动设置theta的梯度为0.1 theta.grad = torch.FloatTensor([0.1]) # 4.2 将梯度更新到theta参数上 optimizer.step() # 4.3 调整学习率。注意:调整学习率要放在`optimizer.step()`之后 lr_scheduler.step() # 4.4 清空梯度 optimizer.zero_grad() # 记录一下theta的变化 theta_list.append(theta.item()) # 打印一下学习率和theta参数 print("epoch:{}, lr:{}, theta:{}".format(i, optimizer.param_groups[0]['lr'], theta.item()))# 5.绘制theta变化plt.plot(range(epoch), theta_list)plt.show()
输出为:
epoch:0, lr:0.1, theta:0.9900000095367432epoch:1, lr:0.1, theta:0.9800000190734863... 略epoch:8, lr:0.1, theta:0.9100000858306885epoch:9, lr:0.05, theta:0.9000000953674316epoch:10, lr:0.05, theta:0.8950001001358032...略epoch:18, lr:0.05, theta:0.8550001382827759epoch:19, lr:0.025, theta:0.8500001430511475epoch:20, lr:0.025, theta:0.8475001454353333... 略
上述例子中,我使用了0.1学习率的来对参数theta进行更新,而参数theta的梯度每次都固定为1。可以看到,前9次theta每次都是下降0.01。从第10次开始,学习率减小了一半,然后每隔10次学习率都会减小一半。
使用 LambdaLR 实现简单的学习率策略
通常我们的学习率策略并不是很复杂,此时我们可以使用Pytorch提供的torch.optim.lr_scheduler.LambdaLR 来实现,该方法接收两个重要参数:
optimizer:该参数就是优化器对象,每个学习率策略都要传lr_lambda:该参数接收一个函数,该函数有一个参数last_epoch,表示上次是第几个epoch(其实指的就是你之前调用过几次lr_scheduler.step())。你可以通过该参数自定义你的学习率策略,优化器在实际执行时使用的学习率会乘以该函数的返回结果,即lr * lr_lambda(last_epoch)。注意,这里的lr是你定义Optimizer时传入的那个学习率,而且始终不变。
例如,要实现前面的每10次将学习率降低一半,我们可以这么写:
lr_scheduler = LambdaLR(optimizer, lr_lambda=lambda epoch: 0.5 ** (epoch // 10))
通过继承 _LRScheduler 实现自定义的学习率策略
可能你的学习率策略比较复杂,LambdaLR 也无法满足你,此时你就要自己实现一个学习率策略。
Pytorch中,所有的学习率策略都要继承基类 torch.optim.lr_scheduler._LRScheduler,并实现该基类的一个重要方法def get_lr(self),你只需要按照你的学习率策略返回学习率即可。
但要实现你的学习率策略,你可能还需要用到_LRScheduler类的这些方法和属性:
self.last_epoch:获取之前执行了多少次lr_scheduler.step()了self.base_lrs:基础学习率,也就是定义Optimizer时指定的那个学习率。注意,这个是一个list,可能是因为Optimizer要更新的那些参数也不一定就是使用同一个学习率,一般只有一个。同理,get_lr 方法也要返回一个list,且数量与self.base_lrs相同。self.optimizer:你可以通过该属性得到优化器对象。你可以通过代码[group['lr'] for group in self.optimizer.param_groups] 获取到优化器中的学习率。你的学习率策略改变的就是这个值。self.get_last_lr():获取上次的学习率。这个方法其实就是返回了属性self._last_lr。但这里有个坑,self._last_lr是在第一次调用step()方法后才会生成,所以使用的时候可能会报错,你可以通过在__init__方法中初始化self._last_lr来避免报错。
有了上述这些方法,我们就可以自定义学习率策略来实现前面的每10次将学习率降低一半的策略了,代码如下:
class MyLR(_LRScheduler): def __init__(self, optimizer): # 初始化self._last_lr避免报错 self._last_lr = [group['lr'] for group in optimizer.param_groups] # 在_LRScheduler的初始化时,会调用step()方法 super(MyLR, self).__init__(optimizer) def get_lr(self): # 获取之前的学习率 last_lrs = self.get_last_lr() if self.last_epoch != 0 and self.last_epoch % 10 == 0: # 每10个epoch将学习率降低一半 return [lr * 0.5 for lr in last_lrs] else: return last_lrs lr_scheduler = MyLR(optimizer)
通过手动更新Optimizer中的学习率来自定义学习策略
其实要自定义学习策略不一定非要向上述说的那样那么麻烦,你可以直接像如下代码直接更新optimizer中的学习率:
for p in optimizer.param_groups: p['lr'] =
这样其实就灵活多了,我可以自定义一个普通类来记录学习率策略需要用到的东西,然后使用上述代码更新即可。例如:
epoch = 100 # 循环100次,每一个epoch更新一次参数theta_list = [] # 记录一下theta的变化# 1.初始化要更新的参数theta,初始化为1theta = Variable(torch.FloatTensor([1]), requires_grad=True)# 2. 定义SGD优化器,学习率设为0.1optimizer = torch.optim.SGD([theta], lr=0.1)# 3. 开始进行模型训练for i in range(epoch): # 3.1 手动设置theta的梯度为0.1 theta.grad = torch.FloatTensor([0.1]) # 3.2 将梯度更新到theta参数上 optimizer.step() """ 直接调整optimizer的学习率 """ for p in optimizer.param_groups: if (i + 1) % 10 == 0: p['lr'] = p['lr'] * 0.5 # 3.4 清空梯度 optimizer.zero_grad() # 记录一下theta的变化 theta_list.append(theta.item()) # 打印一下学习率和theta参数 print("epoch:{}, lr:{}, theta:{}".format(i, optimizer.param_groups[0]['lr'], theta.item()))# 4.绘制theta变化plt.plot(range(epoch), theta_list)plt.show()
参考资料
torch.optim.lr_scheduler._LRScheduler Class Reference:CODE FOR TORCH.OPTIM.LR_SCHEDULER
torch.optim.lr_scheduler:调整学习率
版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。
发表评论
暂时没有评论,来抢沙发吧~