赞
踩
前期回顾:
Pytorch学习笔记(1):基本概念、安装、张量操作、逻辑回归
Pytorch学习笔记(2):数据读取机制(DataLoader与Dataset)
Pytorch学习笔记(3):图像的预处理(transforms)
Pytorch学习笔记(4):模型创建(Module)、模型容器(Containers)、AlexNet构建
Pytorch学习笔记(5):torch.nn---网络层介绍(卷积层、池化层、线性层、激活函数层)
pytorch的优化器:管理并更新模型中可学习参数的值,使得模型输出更接近真实标签
导数:函数在指定坐标轴上的变化率
方向导数:指定方向上的变化率
梯度:一个向量,方向为方向导数取得最大值的方向
梯度下降:朝着梯度下降的负方向去变化,下降是最快的
基本方法
• zero_grad():清空所管理参数的梯度, 这里注意Pytorch有一个特性就是张量梯度不自动清零
• step():执行一步更新
- class Optimizer(object):
-
- def zero_grad(self):
- for group in self.param_groups:
- for p in group['params']:
- if p.grad is not None:
- p.grad.detach_()
- p.grad.zero_()
- class Optimizer(object):
- def __init__(self, params, defaults):
- self.defaults = defaults
- self.state = defaultdict(dict)
- self.param_groups = []
• add_param_group():添加参数组,不同的组有不同的超参数。
例如:fine_tune中,我们对模型前面这些特征提取部分的权重参数,希望学习率小一些,更新的慢一些,将其设置为一组参数;而在后面的全连接层,我们希望其学习率大一些,更新的快一些,这些设置为一组参数。
- class Optimizer(object):
- def add_param_group(self, param_group):
- for group in self.param_groups:
- param_set.update(set(group['params’]))
-
- self.param_groups.append(param_group)
• state_dict():获取优化器当前状态信息字典
• load_state_dict() :加载状态信息字典
这两个方法的作用是回复断点的训练,例如,一个模型需要训练十天,而在第三天的时候因为断电等问题停止了训练,而我们重新启动训练时就不需要从0开始了,直接从断点处加载状态信息,继续训练。
- class Optimizer(object):
-
- def state_dict(self):
- return {
- 'state': packed_state, 'param_groups': param_groups,
- }
- def load_state_dict(self, state_dict):
pytorch特性:张量梯度不自动清零
下面我们学习一下优化器具体使用方法:
(1)构建可学习参数和学习率
代码:
- set_seed(1) # 设置随机种子
-
- #构建可学习参数
- weight = torch.randn((2, 2), requires_grad=True)
- weight.grad = torch.ones((2, 2))
-
- #传入可学习参数,学习率设置为1
- optimizer = optim.SGD([weight], lr=0.1)
(2)step(): 一次梯度下降更新参数
代码:
- # ----------------------------------- step -----------------------------------
- print("weight before step:{}".format(weight.data))
- optimizer.step() # 修改lr=1 0.1观察结果
- print("weight after step:{}".format(weight.data))
输出结果:
(3)zero_grad:更新梯度,将梯度清零
代码:
- # ----------------------------------- zero_grad -----------------------------------
-
- print("weight before step:{}".format(weight.data))
- optimizer.step() # 修改lr=1 0.1观察结果
- print("weight after step:{}".format(weight.data))
-
- print("weight in optimizer:{}\nweight in weight:{}\n".format(id(optimizer.param_groups[0]['params'][0]), id(weight)))
-
- print("weight.grad is {}\n".format(weight.grad))
- optimizer.zero_grad()
- print("after optimizer.zero_grad(), weight.grad is\n{}".format(weight.grad))
输出结果:
(4)add_param_groups:增加组参数
代码:
- # ----------------------------------- add_param_group -----------------------------------
- print("optimizer.param_groups is\n{}".format(optimizer.param_groups))
-
- w2 = torch.randn((3, 3), requires_grad=True)
-
- optimizer.add_param_group({"params": w2, 'lr': 0.0001})
-
- print("optimizer.param_groups is\n{}".format(optimizer.param_groups))
输出结果:
(5)state_dict:保存优化器的状态信息
代码:进行十次更新
- # ----------------------------------- state_dict -----------------------------------
-
- optimizer = optim.SGD([weight], lr=0.1, momentum=0.9)
- opt_state_dict = optimizer.state_dict()
-
- print("state_dict before step:\n", opt_state_dict)
-
- for i in range(10):
- optimizer.step()
-
- print("state_dict after step:\n", optimizer.state_dict())
- #保存状态信息
- torch.save(optimizer.state_dict(), os.path.join(BASE_DIR, "optimizer_state_dict.pkl"))
输出结果:
(6)load_state_dict:加载状态信息
在之前的十次更新后继续更新,而非从头更新。
举个栗子:假设我们训练一个模型,训练10次,此时停电了,该怎么办?难道要像个大冤种一样从头开始吗?漏!state_dict()方法里面保存了优化器的各种状态信息,我们通过torch.save就可以将这些状态保存到文件(.pkl),然后就可以通过load_state_dict()来导入这个状态信息,让优化器在这个基础上进行训练,而不用从头开始啦!
代码:
- # -----------------------------------load state_dict -----------------------------------
-
- optimizer = optim.SGD([weight], lr=0.1, momentum=0.9)
- state_dict = torch.load(os.path.join(BASE_DIR, "optimizer_state_dict.pkl"))
-
- print("state_dict before load state:\n", optimizer.state_dict())
- optimizer.load_state_dict(state_dict)
- print("state_dict after load state:\n", optimizer.state_dict())
输出结果:
主要功能:随机梯度下降法
主要参数:
常用优化器:
学习率(Learning rate):作为监督学习以及深度学习中重要的超参,其决定着目标函数能否收敛到局部最小值以及何时收敛到最小值。合适的学习率能够使目标函数在合适的时间内收敛到局部最小值。
这里我们以梯度下降为例,来观察一下不同的学习率对代价函数的收敛过程的影响:
当学习率设置的过小时,收敛过程如下:
当学习率设置的过大时,收敛过程如下:
由上图可以看出来,当学习率设置的过小时,收敛过程将变得十分缓慢。而当学习率设置的过大时,梯度可能会在最小值附近来回震荡,甚至可能无法收敛。
我们通过代码进一步理解一下:
代码:
- # ------------------------------ gradient descent ------------------------------
- #记录loss和迭代次数用来画loss曲线
- iter_rec, loss_rec, x_rec = list(), list(), list()
-
- lr = 0.5 # /1. /.5 /.2 /.1 /.125
- max_iteration = 4 # /1. 4 /.5 4 /.2 20 200
-
- for i in range(max_iteration):
-
- y = func(x)
- y.backward()
-
- print("Iter:{}, X:{:8}, X.grad:{:8}, loss:{:10}".format(
- i, x.detach().numpy()[0], x.grad.detach().numpy()[0], y.item()))
-
- x_rec.append(x.item())
-
- x.data.sub_(lr * x.grad) # x -= x.grad 数学表达式意义: x = x - x.grad # 0.5 0.2 0.1 0.125
- x.grad.zero_()
-
- iter_rec.append(i)
- loss_rec.append(y.detach().numpy())
-
- plt.subplot(121).plot(iter_rec, loss_rec, '-ro')
- plt.xlabel("Iteration")
- plt.ylabel("Loss value")
-
- x_t = torch.linspace(-3, 3, 100)
- y = func(x_t)
- plt.subplot(122).plot(x_t.numpy(), y.numpy(), label="y = 4*x^2")
- plt.grid()
- y_rec = [func(torch.tensor(i)).item() for i in x_rec]
- plt.subplot(122).plot(x_rec, y_rec, '-ro')
- plt.legend()
- plt.show()
首先,我们设置学习率为1,看一下输出结果:
我们可以看上面的图像,loss是不断上升的,这说明这个跨度是有问题的
接着,我们修改学习率,设置为0.1,再看一下输出结果:
我们可以看到,loss减少了
然后,我们把迭代次数设置的大一点,看loss是否还会继续降低
经过多次尝试,我们发现当lr设置为0.125,loss值就可以一步到达0
我们发现,当loss上升不降的时候,有可能是学习率的问题,所以我们一般会尝试一个小的学习率。 慢慢的去进行优化。
最后,我们设置多个学习率来观察不同大小的lr对loss 的影像
代码:
- # ------------------------------ multi learning rate ------------------------------
-
- iteration = 100
- num_lr = 10
- lr_min, lr_max = 0.01, 0.2 # .5 .3 .2
-
- lr_list = np.linspace(lr_min, lr_max, num=num_lr).tolist()
- loss_rec = [[] for l in range(len(lr_list))]
- iter_rec = list()
-
- for i, lr in enumerate(lr_list):
- x = torch.tensor([2.], requires_grad=True)
- for iter in range(iteration):
-
- y = func(x)
- y.backward()
- x.data.sub_(lr * x.grad) # x.data -= x.grad
- x.grad.zero_()
-
- loss_rec[i].append(y.item())
-
- for i, loss_r in enumerate(loss_rec):
- plt.plot(range(len(loss_r)), loss_r, label="LR: {}".format(lr_list[i]))
- plt.legend()
- plt.xlabel('Iterations')
- plt.ylabel('Loss value')
- plt.show()
输出结果:
综上所述,lr 从来控制更新的步伐,设置学习率时,不能设置的太大,如0.3,0.5,此时的loss值是激增,容易引发梯度爆炸;也不能设置太小,设置太小就很难收敛。
学习率是可以控制更新的步伐的。 我们在训练模型的时候,一般开始的时候学习率会比较大,这样可以以一个比较快的速度到达最优点的附近,然后再把学习率降下来, 缓慢的去收敛到最优值。
举个栗子:以打高尔夫为例,开始的时候击球的力度会大一些,让高尔夫球落在洞口附近。然后来到洞口,这次就需要小一点的击球力度,让球逐渐靠近洞口。
再看看函数的例子:
在模型的训练过程中,需要调整学习率,设置较大的学习率,让loss快速下降,此时曲线是震荡的
如果继续采用较大的学习率,那loss就无法继续下降。学习率前期要大,后期要小。
功能:等间隔调整学习率
主要参数:
调整方式:lr = lr * gamma
代码:
- torch.manual_seed(1)
-
- LR = 0.1
- iteration = 10
- max_epoch = 200
- # ------------------------------ fake data and optimizer ------------------------------
-
- weights = torch.randn((1), requires_grad=True)
- target = torch.zeros((1))
-
- optimizer = optim.SGD([weights], lr=LR, momentum=0.9)
- # ------------------------------ 1 Step LR ------------------------------
- scheduler_lr = optim.lr_scheduler.StepLR(optimizer, step_size=50, gamma=0.1) # 设置学习率下降策略
-
- lr_list, epoch_list = list(), list()
- for epoch in range(max_epoch):
-
- # 获取当前lr,新版本用 get_last_lr()函数,旧版本用get_lr()函数,具体看UserWarning
- lr_list.append(scheduler_lr.get_last_lr())
- epoch_list.append(epoch)
-
- for i in range(iteration):
-
- loss = torch.pow((weights - target), 2)
- loss.backward()
-
- optimizer.step()
- optimizer.zero_grad()
-
- scheduler_lr.step()
-
- plt.plot(epoch_list, lr_list, label="Step LR Scheduler")
- plt.xlabel("Epoch")
- plt.ylabel("Learning rate")
- plt.legend()
- plt.show()
输出:
功能:按给定间隔调整学习率
主要参数:
调整方式:lr = lr * gamma
代码:
- # ------------------------------ 2 Multi Step LR ------------------------------
- milestones = [50, 125, 160]
- scheduler_lr = optim.lr_scheduler.MultiStepLR(optimizer, milestones=milestones, gamma=0.1)
-
- lr_list, epoch_list = list(), list()
- for epoch in range(max_epoch):
-
- lr_list.append(scheduler_lr.get_lr())
- epoch_list.append(epoch)
-
- for i in range(iteration):
-
- loss = torch.pow((weights - target), 2)
- loss.backward()
-
- optimizer.step()
- optimizer.zero_grad()
-
- scheduler_lr.step()
-
- plt.plot(epoch_list, lr_list, label="Multi Step LR Scheduler\nmilestones:{}".format(milestones))
- plt.xlabel("Epoch")
- plt.ylabel("Learning rate")
- plt.legend()
- plt.show()
输出:
功能:按指数衰减调整学习率
主要参数:
调整方式:lr = lr * gamma ** epoch
代码:
-
- # ------------------------------ 3 Exponential LR ------------------------------
- gamma = 0.95
- scheduler_lr = optim.lr_scheduler.ExponentialLR(optimizer, gamma=gamma)
- lr_list, epoch_list = list(), list()
- for epoch in range(max_epoch):
-
- lr_list.append(scheduler_lr.get_lr())
- epoch_list.append(epoch)
-
- for i in range(iteration):
- loss = torch.pow((weights - target), 2)
- loss.backward()
-
- optimizer.step()
- optimizer.zero_grad()
-
- scheduler_lr.step()
-
- plt.plot(epoch_list, lr_list, label="Exponential LR Scheduler\nmilestones:{}".format(gamma))
- plt.xlabel("Epoch")
- plt.ylabel("Learning rate")
- plt.legend()
- plt.show()
输出:
功能:余弦周期调整学习率
主要参数:
代码:
- # ------------------------------ 4 Cosine Annealing LR ------------------------------
- t_max = 50
- scheduler_lr = optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=t_max, eta_min=0.)
-
- lr_list, epoch_list = list(), list()
- for epoch in range(max_epoch):
-
- lr_list.append(scheduler_lr.get_lr())
- epoch_list.append(epoch)
-
- for i in range(iteration):
- loss = torch.pow((weights - target), 2)
- loss.backward()
-
- optimizer.step()
- optimizer.zero_grad()
-
- scheduler_lr.step()
-
- plt.plot(epoch_list, lr_list, label="CosineAnnealing LR Scheduler\nmilestones:{}".format(t_max))
- plt.xlabel("Epoch")
- plt.ylabel("Learning rate")
- plt.legend()
- plt.show()
输出:
功能:监控指标,当指标不再变化则调整。可以监控loss或者准确率,当不再变化的时候,我们再去调整。
主要参数:
代码:
- # ------------------------------ 5 Reduce LR On Plateau ------------------------------
- loss_value = 0.5
- accuray = 0.9
-
- factor = 0.1
- mode = "min"
- patience = 10 # 连续10次损失不下降时执行一次lr=lr*0.1
- cooldown = 10 # 先冷却10个epoch,这时候不监控
- min_lr = 1e-4
- verbose = True # 会打印多少次信息,更新多少次学习率
-
- scheduler_lr = optim.lr_scheduler.ReduceLROnPlateau(optimizer, factor=factor, mode=mode, patience=patience,
- cooldown=cooldown, min_lr=min_lr, verbose=verbose)
-
- for epoch in range(max_epoch):
- for i in range(iteration):
-
- # train(...)
-
- optimizer.step()
- optimizer.zero_grad()
-
- #if epoch == 5:
- # loss_value = 0.4
- #放入监控标量
- scheduler_lr.step(loss_value)
输出:
功能:自定义调整策略
主要参数:
代码:
- # ------------------------------ 6 lambda ------------------------------
- lr_init = 0.1
-
- weights_1 = torch.randn((6, 3, 5, 5))
- weights_2 = torch.ones((5, 5))
-
- optimizer = optim.SGD([
- {'params': [weights_1]},
- {'params': [weights_2]}], lr=lr_init)
-
- lambda1 = lambda epoch: 0.1 ** (epoch // 20)
- lambda2 = lambda epoch: 0.95 ** epoch
-
- scheduler = torch.optim.lr_scheduler.LambdaLR(optimizer, lr_lambda=[lambda1, lambda2])
-
- lr_list, epoch_list = list(), list()
- for epoch in range(max_epoch):
- for i in range(iteration):
- # train(...)
-
- optimizer.step()
- optimizer.zero_grad()
-
- scheduler.step()
-
- lr_list.append(scheduler.get_lr())
- epoch_list.append(epoch)
-
- print('epoch:{:5d}, lr:{}'.format(epoch, scheduler.get_lr()))
-
- plt.plot(epoch_list, [i[0] for i in lr_list], label="lambda 1")
- plt.plot(epoch_list, [i[1] for i in lr_list], label="lambda 2")
- plt.xlabel("Epoch")
- plt.ylabel("Learning Rate")
- plt.title("LambdaLR")
- plt.legend()
- plt.show()
-
输出:
下面我们总结一下学习率:
(1)有序调整:Step、MultiStep、Exponential和CosineAnnealing
(2)自适应调整:ReduceLROnPlateau
(3)自定义调整:Lambda
momentum(动量、冲量):结合当前梯度与上一次更新信息,用于当前更新
(图片来源:翻滚的小@强)
指数加权平均:在时间序列中经常使用的求取平均值的方法,求取当前时刻的平均值,距离平均值越近的参数值,参考性越大,所占的权重也越大。
代码:
- #构建权重的计算公式,观察每一个时刻的权重
- def exp_w_func(beta, time_list):
- return [(1 - beta) * np.power(beta, exp) for exp in time_list]
-
-
- beta = 0.9
- num_point = 100
- time_list = np.arange(num_point).tolist()
- # ------------------------------ exponential weight ------------------------------
- weights = exp_w_func(beta, time_list)
-
- plt.plot(time_list, weights, '-ro', label="Beta: {}\ny = B^t * (1-B)".format(beta))
- plt.xlabel("time")
- plt.ylabel("weight")
- plt.legend()
- plt.title("exponentially weighted average")
- plt.show()
-
- print(np.sum(weights))
输出:
当time = 0,时权重为0.1,越往前权重越小,距离当前时刻越远,对当前时刻平均值的影响就越小
设置不同的β,看一下权重的变化 :
- # ------------------------------ multi weights ------------------------------
- beta_list = [0.98, 0.95, 0.9, 0.8]
- w_list = [exp_w_func(beta, time_list) for beta in beta_list]
- for i, w in enumerate(w_list):
- plt.plot(time_list, w, label="Beta: {}".format(beta_list[i]))
- plt.xlabel("time")
- plt.ylabel("weight")
- plt.legend()
- plt.show()
输出结果:
β可以理解为记忆周期的概念,β值越小,记忆周期越短。
举个栗子:
通常,我们会将β设置为0.9,它可以更关注当前时刻十天左右的情况。10 = 1/(1-β))
了解了指数加权平均,我们再来看momentum。其实所谓的momentum梯度下降, 基本的想法是计算梯度的指数加权平均数,并利用该梯度更新权重 。
梯度下降(原来的梯度下降方式):
pytorch中更新公式(加入动量之后的梯度下降方式):
这里lr乘的不是梯度了,而是变化量,当前的变化量也会考虑到之前的梯度
接下来我们看看带momentum的loss变化情况:
代码:
- # ------------------------------ SGD momentum ------------------------------
- def func(x):
- return torch.pow(2*x, 2) # y = (2x)^2 = 4*x^2 dy/dx = 8x
-
- iteration = 100
- m = 0.9 # .9 .63
-
- lr_list = [0.01, 0.03]
-
- momentum_list = list()
- loss_rec = [[] for l in range(len(lr_list))]
- iter_rec = list()
-
- for i, lr in enumerate(lr_list):
- x = torch.tensor([2.], requires_grad=True)
-
- momentum = 0. if lr == 0.03 else m
- momentum_list.append(momentum)
-
- optimizer = optim.SGD([x], lr=lr, momentum=momentum)
-
- for iter in range(iteration):
-
- y = func(x)
- y.backward()
-
- optimizer.step()
- optimizer.zero_grad()
-
- loss_rec[i].append(y.item())
-
- for i, loss_r in enumerate(loss_rec):
- plt.plot(range(len(loss_r)), loss_r, label="LR: {} M:{}".format(lr_list[i], momentum_list[i]))
- plt.legend()
- plt.xlabel('Iterations')
- plt.ylabel('Loss value')
- plt.show()
输出结果:
lr设置为0.01和0.03,首先两个都不加momentum
可以看到,学习率大的收敛更快
接着我们给学习率0.01的这个加一个动量momentum=0.9, 再看看效果:
输出结果:
在最开始的低点,是比lr = 0.03更快的,但是又开始震荡, 这说明动量设置的太大了。前面梯度一旦大小变化,这里就会震荡,当然会发现震荡会越来越小最后趋于平缓,这是因为不断平均的梯度越来越多。
接着我们给将动量设置小一点吧,动量momentum=0.65, 再看看效果:
输出结果:
可以看到,小学习率下降的更快了
由此可知,合适的动量可以加速loss的下降
本文参考:
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。