本文和之前的文章是连续的,欢迎阅读之前的关于model的属性和方法文章。
PyTorch中的优化器:管理并更新模型中可学习参数的值,使得模型输出更接近真实标签。
Optimizer类是所有优化器的基类,下面先分析一下其属性和常用方法。
因为分析Optimizer类的__init__方法,需要用到子类的某些参数,我们以SGD为例,先说明一下,看一下SGD类的__init__方法代码,非常的简单,一系列判断,然后将params这个参数单独列出来,将其余参数以字典的形式放到defaults里面,然后继承父类的初始化,将params和defaults传进去。这里面值得注意的就是params和defaults的形式。
class SGD(Optimizer):
def __init__(self, params, lr=required, momentum=0, dampening=0,
weight_decay=0, nesterov=False):
if lr is not required and lr < 0.0:
raise ValueError("Invalid learning rate:{}".format(lr))
if momentum < 0.0:
raise ValueError("Invalid momentum value:{}".format(momentum))
if weight_decay < 0.0:
raise ValueError("Invalid weight_decay value:{}".format(weight_decay))
defaults=dict(lr=lr, momentum=momentum, dampening=dampening,
weight_decay=weight_decay, nesterov=nesterov)
if nesterov and (momentum <=0 or dampening !=0):
raise ValueError("Nesterov momentum requires a momentum and zero dampening")
super(SGD, self).__init__(params, defaults)
Optimizer类的初始化代码如下。
class Optimizer(object):
Args:
params (iterable): an iterable of :class:`torch.Tensor` s or
:class:`dict` s. Specifies what Tensors should be optimized.
defaults: (dict): a dict containing default values of optimization
options (used when a parameter group doesn't specify them).
"""
def __init__(self, params, defaults):
torch._C._log_api_usage_once("python.optimizer")
self.defaults=defaults
self._hook_for_profile()
if isinstance(params, torch.Tensor):
raise TypeError("params argument given to the optimizer should be "
"an iterable of Tensors or dicts, but got " +
torch.typename(params))
self.state=defaultdict(dict)
self.param_groups=[]
param_groups=list(params) # 1
if len(param_groups)==0:
raise ValueError("optimizer got an empty parameter list")
if not isinstance(param_groups[0], dict):
param_groups=[{'params': param_groups}]# 2
for param_group in param_groups: # 3
self.add_param_group(param_group)
关键几个点为:
代码虽长,就这几个点就可以了,甚至self.state也不需关注,只需要明白其余三个点即可。
该初始化方法接收两个参数,一个是params,一个是defaults。这两个分开说,先说params,最常见的就是model.parameters(),当然net.parameters()也是一样的,就是模型类的对象的变量名不同,如下所示。
optimizer=optim.SGD(
***************************
net.parameters(), # params的一种形式
***************************
lr=LR, momentum=0.9
)
通过之前的文章,我们知道这种params是个生成器,只返回各模型层的参数,没有参数名。model.parameters(),注意__init__方法中我注释的1部分的那句代码,我们得到一个新的变量------------------> param_groups,和self.param_groups好像,前者就是为后者服务的。param_groups=list(params),list可以把生成器的元素都取出来,所以,很明显,param_groups就是一个Parameter类对象的列表,里面的元素是每个网络层的参数weight和bias(如果有)。
很明显,param_groups[0]是Parameter类,不是dict,所以,这种形式的param_groups会被改造,将整个param_groups作为值,"params"作为键,形成一个键值对,放在字典里,然后重新赋值给param_groups。
现在我们要记得param_groups的形式,一个列表,里面是一个字典,字典的键是"params",值为所有网络层的参数。
注释为3的代码,将param_groups中的每个元素送进self.add_param_group这个列表中。现在的param_groups里只有一个元素{"param":[参数]},看一下这个方法的作用,代码如下。
功能:将参数放到self.param_groups这个列表中
参数:param_group,一个字典
返回值:无
def add_param_group(self, param_group):
r"""Add a param group to the :class:`Optimizer` s `param_groups`.
This can be useful when fine tuning a pre-trained network as frozen layers can be made
trainable and added to the :class:`Optimizer` as training progresses.
Args:
param_group (dict): Specifies what Tensors should be optimized along with group
specific optimization options.
"""
assert isinstance(param_group, dict), "param group must be a dict" # 1
params=param_group['params']# 2
if isinstance(params, torch.Tensor):
param_group['params']=[params]
elif isinstance(params, set):
raise TypeError('optimizer parameters need to be organized in ordered collections, but '
'the ordering of tensors in sets will change between runs. Please use a list instead.')
else:
param_group['params']=list(params) # 3
for param in param_group['params']: # 4
if not isinstance(param, torch.Tensor):
raise TypeError("optimizer can only optimize Tensors, "
"but one of the params is " + torch.typename(param))
if not param.is_leaf:
raise ValueError("can't optimize a non-leaf Tensor")
for name, default in self.defaults.items(): # 5
if default is required and name not in param_group:
raise ValueError("parameter group didn't specify a value of required optimization parameter " +
name)
else:
param_group.setdefault(name, default)
params=param_group['params']
if len(params) !=len(set(params)):
warnings.warn("optimizer contains a parameter group with duplicate parameters; "
"in future, this will cause an error; "
"see github.com/pytorch/pytorch/issues/40967 for more information", stacklevel=3)
param_set=set() # 6
for group in self.param_groups:
param_set.update(set(group['params']))
if not param_set.isdisjoint(set(param_group['params'])): # 7
raise ValueError("some parameters appear in more than one parameter group")
self.param_groups.append(param_group) # 8
将上述代码分为8个步骤,第1步判断传进来的参数是否是一个字典,必然是一个字典,不是字典报错。第2步,取出字典里的"params"的值,就是参数的列表,这是个列表,然后一系列判断,走到第3步,又重新以列表的形式赋值回去,一套走下来,还是熟悉的配方,没变化。第4步,判断参数的列表里边的元素类型,那必然是Parameter类型的,也就是Tensor类型的,并且是叶子结点没问题。第5步,将defaults这个字典里的键值对拿出来,放到现在的param_group这个字典里,这样该字典构成一个具有完整参数的字典,其所有键为:dict_keys(['params', 'lr', 'momentum', 'dampening', 'weight_decay', 'nesterov']),方便step()方法调用。
第6步和第7步是一起的,判定当前字典中的参数组和之前的参数组是不是一样的。对于当前来说,self.param_groups是空的,所以直接到第7步,判断param_set集合是否和param_group["params"]这个集合中具有相同元素,没有返回True,反之False。显然没有,所以7不执行。然后执行第8步,将构造完整的param_group这个字典,加到我们一直强调非常重要的self.param_groups中去。
现在我们知道self.param_groups这个列表中具有字典,每个字典的keys为dict_keys(['params', 'lr', 'momentum', 'dampening', 'weight_decay', 'nesterov']),当然,每个键都有其对应的值。这些键值对是构建SGD实例时,传进来的参数。
开头的SGD类的defaults变量的代码如下:
defaults=dict(lr=lr, momentum=momentum, dampening=dampening,
weight_decay=weight_decay, nesterov=nesterov)
构建SGD实例后打印一下defaults这个变量的结果:
{'lr': 0.01,
'momentum': 0.9,
'dampening': 0,
'weight_decay': 0,
'nesterov': False}
重新看一下第5步,对于defaults中的items(),获取键值对,然后判断一下,肯定是False,因为default和required不是一个东西。default是float是bool,required是一个表示优化器参数的单例类,代码如下:
class _RequiredParameter(object):
"""Singleton class representing a required parameter for an Optimizer."""
def __repr__(self):
return "<required parameter>"
required=_RequiredParameter()
所以,在这个地方会把lr,momentum,nag,weight_decay等参数全部加到param_group这个字典里去,然后再加到self.param_groups这个列表中。
总结一下:SGD类对象初始化时,继承父类的属性和方法,经过一系列操作,self.param_groups这个列表中,具有一个字典,字典里面的键为dict_keys(['params', 'lr', 'momentum', 'dampening', 'weight_decay', 'nesterov']),每个键具有对应的值。
params还有一种常见形式如下。
fcParamsId=list(map(id, resnet18_ft.fc.parameters())) # 返回的是parameters的 内存地址
features_params=filter(lambda p: id(p) not in fcParamsId, resnet18_ft.parameters())
optimizer=optim.SGD(
*************************************************************
[{'params': features_params, 'lr': LR * 0.1}, # 这个列表是params的另一种形式
{'params': resnet18_ft.fc.parameters()}],
***************************************************************
'lr': LR, momentum=0.9
)
我们可以对比一下这种形式和上述model.parameters()这种方式的不同。
两个字典的有什么用呢?如上我们两个字典里面的"params",一个是特征提取层的参数,一个是fc层的参数。我们如果只有小数据集进行finetune时,可以将第一个字典里面的lr设置的很小,那么他就几乎不更新。着重训练fc层的参数。
这是怎么实现的呢?我们可以看一下SGD类中step()方法的代码结构如下:将self.param_groups里的字典分别取出,对每个参数进行处理,然后放到sgd中进行参数更新。注意这里不是反向传播,不是梯度计算过程。各参数梯度在这step之前已经计算完保存好了,此处只是更新参数,所以读取进来的字典没有先后顺序。
def step(self, closure=None):
......
for group in self.param_groups:
......
for p in group['params']:
......
F.sgd(params_with_grad,
d_p_list,
momentum_buffer_list,
weight_decay=weight_decay,
momentum=momentum,
lr=lr,
dampening=dampening,
nesterov=nesterov)
常用优化器算法原理,看这两篇就够了。
Alex Chung:深度学习中常用优化器的总结陶将:优化算法Optimizer比较和总结公司名称: 天富娱乐-天富医疗器械销售公司
手 机: 13800000000
电 话: 400-123-4567
邮 箱: admin@youweb.com
地 址: 广东省广州市天河区88号