中国山东建设监理协会网站,游戏怎么做充值网站,沈阳市绿云网站建设,注册空壳公司判几年本篇笔记主要介绍torch.optim模块#xff0c;记录学习过程
在深度学习中#xff0c;我们通常会使用优化算法来调整神经网络的权重和偏差#xff0c;以便模型能够更好地拟合训练数据。torch.optim是PyTorch中的一个模块#xff0c;它提供了各种优化算法的实现#xff0c;用…本篇笔记主要介绍torch.optim模块记录学习过程
在深度学习中我们通常会使用优化算法来调整神经网络的权重和偏差以便模型能够更好地拟合训练数据。torch.optim是PyTorch中的一个模块它提供了各种优化算法的实现用于自动化地优化神经网络的参数。换句话说torch.optim可以帮助我们让模型更好地学习从而提高性能。
优化器 Optimizer
优化器主要是在模型训练阶段对模型可学习参数进行更新, 常用优化器有 SGDRMSpropAdam等优化器初始化时传入模型的可学习参数以及其他超参数如 lrmomentum等在训练过程中先调用 optimizer.zero_grad() 清空梯度再调用 loss.backward() 反向传播最后调用optimizer.step()更新模型参数
简单使用示例如下所示
import torch
import numpy as np
import warnings
warnings.filterwarnings(ignore) #ignore warnings
# 定义数据
x torch.linspace(-np.pi, np.pi, 2000)
y torch.sin(x)p torch.tensor([1, 2, 3])
xx x.unsqueeze(-1).pow(p)
# 定义模型
model torch.nn.Sequential(torch.nn.Linear(3, 1),torch.nn.Flatten(0, 1))
# 定义损失函数:损失函数是一个衡量模型预测与实际值之间差距的函数
loss_fn torch.nn.MSELoss(reductionsum)
# 学习率:learning_rate参数表示学习率它控制了每次参数更新的步长。
learning_rate 1e-3
# 优化器:选择一个优化算法来优化模型的参数
optimizer torch.optim.RMSprop(model.parameters(), lrlearning_rate)
# 迭代学习:训练模型
for t in range(1, 1001):y_pred model(xx)loss loss_fn(y_pred, y)if t % 100 0:print(No.{: 5d}, loss: {:.6f}.format(t, loss.item()))# 梯度清零optimizer.zero_grad() # 反向传播计算梯度loss.backward() # 梯度下降法更新参数optimizer.step()1.1 PyTorch 中的优化器
所有优化器都是继承父类 Optimizer如下列表是 PyTorch 提供的优化器: (具体用法和优缺点后续更新。。。。。。。。。。。)
SGDASGDAdadeltaAdagradAdamAdamWAdamaxSparseAdamRMSpropRpropLBFGS
1.2 父类Optimizer 基本原理
Optimizer 是所有优化器的父类它主要有如下公共方法:
add_param_group(param_group): 添加模型可学习参数组 step(closure): 进行一次参数更新 zero_grad(): 清空上次迭代记录的梯度信息 state_dict(): 返回 dict 结构的参数状态 load_state_dict(state_dict): 加载 dict 结构的参数状态
上述方法一一解读
1.2.1 初始化 Optimizer
初始化优化器只需要将模型的**可学习参数(params)和超参数(defaults)**分别传入优化器的构造函数
关键几个点为
self.defaults defaultsself.state defaultdict(dict)self.param_groups [] 特别重要最需要记住的属性特别是属性的内容和其形式。self.add_param_group方法往self.param_groups里面放东西。
下面是Optimizer的初始化函数核心代码:
class Optimizer(object):def __init__(self, params, defaults):# 字典类型子类传入用于表示全部参数组的默认超参self.defaults defaults# 判断参数是否为torch.Tensor格式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.param_groups []param_groups list(params)# 确保param_groups为一个字典if not isinstance(param_groups[0], dict):param_groups [{params: param_groups}]for param_group in param_groups:# 添加模型可学习参数组self.add_param_group(param_group)该初始化方法接收两个参数一个是params一个是defaults。
这两个分开说先说params最常见的就是model.parameters()当然net.parameters()也是一样的就是模型类的对象的变量名不同如下所示。
optimizer optim.SGD( net.parameters(), # params的一种形式lrLR, momentum0.9)params是个生成器只返回各模型层的参数没有参数名。
注意__init__方法中param_groups list(params)得到一个新的变量 param_groups(这里self.param_groups是干什么的呢。
param_groups list(params)list可以把生成器的元素都取出来所以很明显param_groups就是一个Parameter类对象的列表里面的元素是每个网络层的参数weight和bias如果有。
if not isinstance(param_groups[0], dict):param_groups [{params: param_groups}]param_groups[0]是Parameter类不是dict这种形式的param_groups会被改造将整个param_groups作为值params作为键形成一个键值对放在字典里然后重新赋值给param_groups。
现在我们要记得 param_groups的形式一个列表里面是一个字典字典的键是params值为所有网络层的参数。
for param_group in param_groups:self.add_param_group(param_group)将param_groups中的每个元素送进self.add_param_group这个列表中。现在的param_groups里只有一个元素{“param”: [参数]}。
1.2.2 add_param_group
该方法在初始化函数中用到主要用来向 self.param_groups添加不同分组的模型参数
def add_param_group(self, param_group):rAdd 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 madetrainable and added to the :class:Optimizer as training progresses.Arguments:param_group (dict): Specifies what Tensors should be optimized along with groupspecific optimization options.# 步骤1判断传进来的参数是否是一个字典必然是一个字典不是字典报错。assert isinstance(param_group, dict), param group must be a dict# 步骤2取出字典里的params的值就是参数的列表这是个列表然后一系列判断走到第3步params param_group[params]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:# 步骤3重新以列表的形式赋值回去param_group[params] list(params)# 步骤4判断参数的列表里边的元素类型必然是Parameter类型也就是Tensor类型的并且是叶子结点。for param in param_group[params]: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(cant optimize a non-leaf Tensor)# 利用默认参数给所有组设置统一的超参# 步骤5将defaults这个字典里的键值对拿出来放到现在的param_group这个字典里这样该字典构成一个具有完整参数的字典# 其所有键为dict_keys([params, lr, momentum, dampening, weight_decay, nesterov])方便step()方法调用。for name, default in self.defaults.items():if default is required and name not in param_group:raise ValueError(parameter group didnt 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, stacklevel3)# 步骤6 判定当前字典中的参数组和之前的参数组是不是一样的。对于当前来说self.param_groups是空的所以直接到第7步param_set set()for group in self.param_groups:param_set.update(set(group[params]))# 步骤7判断param_set集合是否和param_group[params]这个集合中具有相同元素没有返回True反之False。显然没有所以7不执行。if not param_set.isdisjoint(set(param_group[params])):raise ValueError(some parameters appear in more than one parameter group)# 步骤8将构造完整的param_group这个字典加到self.param_groups中去。self.param_groups.append(param_group)将上述代码分为8个步骤 请认真看一遍代码以及注释 现在我们知道self.param_groups这个列表中具有字典每个字典的keys为dict_keys([‘params’, ‘lr’, ‘momentum’, ‘dampening’, ‘weight_decay’, ‘nesterov’])当然每个键都有其对应的值。这些键值对是构建SGD实例时传进来的参数。
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, momentum0.9
)我们可以对比一下 这种形式前面讲的 和 上述model.parameters() 这种方式的不同。 前者是包含着字典的列表后者是生成器进入到Optimizer类的初始化函数中前者不变还是包含2个字典的列表后者先变成参数的列表再变成只有一个字典的列表该字典中只有一个键值对params和所有参数值在执行self.add_param_group()方法的循环里前者需要遍历两个字典后者只有一个字典也就是说最终在self.param_groups这个列表中前者最终具有两个具有全部键值对的字典后者只有一个在如上self.add_param_group()方法中步骤2处取出前者字典中的params对应的值为生成器在步骤3处将其变成了参数的列表后者在步骤2和步骤3处没变化因为在初始化一开始就使用list()将model.parameters()的所有参数取出来了。在如上self.add_param_group()方法中标记为步骤4处没区别。在步骤5处有区别并且该区别非常关键。这是对不同参数设置不同学习率和动量的地方。 param_group是循环中取出的字典对于如上有两个字典的来说就是将self.defaults里面取出来的键值对分别放到两个字典中去。param_group.setdefault(name, default)这句代码。如果param_group这个字典中比如第一个字典具有lr这个键的保持不变对于第二个字典里面没有lr这个键的将default和name设置为新的键值对。不知道的同学可以看一下dict中setdefault这个方法的功能。前者的步骤6和步骤7由于存在两个字典所以第二次需要和第一次进行对比看看两个字典里面的params这个键对应的参数是否相同如果相同引用的就是同一块地址设置不同的学习率等参数就没意义了就会报错。最后将两个具有所有键值对但是参数不同的两个字典加入到self.param_groups这个列表中。 1.2.3 step
此方法主要完成一次模型参数的更新
基类 Optimizer 定义了 step 方法接口如下所示
def step(self, closure):rPerforms a single optimization step (parameter update).Arguments:closure (callable): A closure that reevaluates the model andreturns the loss. Optional for most optimizers... note::Unless otherwise specified, this function should not modify the.grad field of the parameters.raise NotImplementedError子类如 SGD 需要实现 step 方法如下所示: torch.no_grad()def step(self, closureNone):Performs a single optimization step.Arguments:closure (callable, optional): A closure that reevaluates the modeland returns the loss.loss Noneif closure is not None:with torch.enable_grad():loss closure()#对参数进行遍历for group in self.param_groups: params_with_grad [] #有梯度的网路参数收集列表d_p_list [] #收集网络参数的梯度列表momentum_buffer_list []# #以下为一些超参数的收集weight_decay group[weight_decay]momentum group[momentum]dampening group[dampening]nesterov group[nesterov]lr group[lr]# 对而网络参数进行逐个遍历更新for p in group[params]: if p.grad is None:continued_p p.gradif weight_decay ! 0:d_p d_p.add(p, alphaweight_decay)if momentum ! 0:param_state self.state[p]if momentum_buffer not in param_state:buf param_state[momentum_buffer] torch.clone(d_p).detach()else:buf param_state[momentum_buffer]buf.mul_(momentum).add_(d_p, alpha1 - dampening)if nesterov:d_p d_p.add(buf, alphamomentum)else:d_p bufp.add_(d_p, alpha-group[lr])return lossstep 方法可传入闭包函数 closure主要目的是为了实现如Conjugate Gradient和LBFGS等优化算法这些算法需要对模型进行多次评估Python 中闭包概念在一个内部函数中对外部作用域的变量进行引用(并且一般外部函数的返回值为内部函数)那么内部函数就被认为是闭包
下面是 closure 的简单示例:
from torch.nn import CrossEntropyLossdummy_model DummyModel().cuda()optimizer SGD(dummy_model.parameters(), lr1e-2, momentum0.9, weight_decay1e-4)
# 定义loss
loss_fn CrossEntropyLoss()
# 定义数据
batch_size 2
data torch.randn(64, 3, 64, 128).cuda() # 制造假数据shape64 * 3 * 64 * 128
data_label torch.randint(0, 10, size(64,), dtypetorch.long).cuda() # 制造假的labelfor batch_index in range(10):batch_data data[batch_index*batch_size: batch_index*batch_size batch_size]batch_label data_label[batch_index*batch_size: batch_index*batch_size batch_size]def closure():optimizer.zero_grad() # 清空梯度output dummy_model(batch_data) # forwardloss loss_fn(output, batch_label) # 计算lossloss.backward() # backwardprint(No.{: 2d} loss: {:.6f}.format(batch_index, loss.item()))return lossoptimizer.step(closureclosure) # 更新参数1.2.4 zero_grad
在反向传播计算梯度之前对上一次迭代时记录的梯度清零参数set_to_none 设置为 True 时会直接将参数梯度设置为 None从而减小内存使用
但通常情况下不建议设置这个参数因为梯度设置为 None 和 0 在 PyTorch 中处理逻辑会不一样。
def zero_grad(self, set_to_none: bool False):rSets the gradients of all optimized :class:torch.Tensor s to zero.Arguments:set_to_none (bool): instead of setting to zero, set the grads to None.This is will in general have lower memory footprint, and can modestly improve performance.However, it changes certain behaviors. For example:1. When the user tries to access a gradient and perform manual ops on it,a None attribute or a Tensor full of 0s will behave differently.2. If the user requests zero_grad(set_to_noneTrue) followed by a backward pass, .gradsare guaranteed to be None for params that did not receive a gradient.3. torch.optim optimizers have a different behavior if the gradient is 0 or None(in one case it does the step with a gradient of 0 and in the other it skipsthe step altogether).for group in self.param_groups:for p in group[params]:if p.grad is not None:if set_to_none:p.grad Noneelse:if p.grad.grad_fn is not None:p.grad.detach_()else:p.grad.requires_grad_(False)p.grad.zero_()1.2.5 state_dict() 和 load_state_dict
这两个方法实现序列化和反序列化功能。
state_dict(): 将优化器管理的参数和其状态信息以 dict 形式返回load_state_dict(state_dict): 加载之前返回的 dict更新参数和其状态
两个方法可用来实现模型训练中断后继续训练功能
def state_dict(self):rReturns the state of the optimizer as a :class:dict.It contains two entries:* state - a dict holding current optimization state. Its contentdiffers between optimizer classes.* param_groups - a dict containing all parameter groups# Save order indices instead of Tensorsparam_mappings {}start_index 0def pack_group(group):nonlocal start_indexpacked {k: v for k, v in group.items() if k ! params}param_mappings.update({id(p): i for i, p in enumerate(group[params], start_index)if id(p) not in param_mappings})packed[params] [param_mappings[id(p)] for p in group[params]]start_index len(packed[params])return packedparam_groups [pack_group(g) for g in self.param_groups]# Remap state to use order indices as keyspacked_state {(param_mappings[id(k)] if isinstance(k, torch.Tensor) else k): vfor k, v in self.state.items()}return {state: packed_state,param_groups: param_groups,}2.1 基类: _LRScheduler
学习率调整类主要的逻辑功能就是每个 epoch 计算参数组的学习率更新 optimizer对应参数组中的lr值从而应用在optimizer里可学习参数的梯度更新。
所有的学习率调整策略类的父类是torch.optim.lr_scheduler._LRScheduler基类 _LRScheduler 定义了如下方法:
step(epochNone): 子类公用get_lr(): 子类需要实现get_last_lr(): 子类公用print_lr(is_verbose, group, lr, epochNone): 显示 lr 调整信息state_dict(): 子类可能会重写load_state_dict(state_dict): 子类可能会重写
2.1.1 初始化
LR_scheduler是用于调节学习率lr的在代码中我们经常看到这样的一行代码
scheduler.step()在pytorch代码中各种类型scheduler大多基于 _LRScheduler 类
基类的初始化函数可传入两个参数,
第一是optimizer就是之前我们讲过的优化器的实例第二个参数last_epoch是最后一次 epoch 的 index默认值是 -1代表初次训练模型
此时会对optimizer里的各参数组设置初始学习率 initial_lr。若last_epoch传入值大于 -1则代表从某个 epoch 开始继续上次训练此时要求optimizer的参数组中有initial_lr初始学习率信息。
初始化函数内部的 with_counter 函数主要是为了确保lr_scheduler.step()是在optimizer.step()之后调用的 (PyTorch1.1 发生变化). 注意在__init__函数最后一步调用了self.step()即_LRScheduler在初始化时已经调用过一次step()方法。
class _LRScheduler(object):def __init__(self, optimizer, last_epoch-1, verboseFalse):# Attach optimizerif not isinstance(optimizer, Optimizer):raise TypeError({} is not an Optimizer.format(type(optimizer).__name__))self.optimizer optimizer# Initialize epoch and base learning ratesif last_epoch -1:for group in optimizer.param_groups:group.setdefault(initial_lr, group[lr])else:for i, group in enumerate(optimizer.param_groups):if initial_lr not in group:raise KeyError(param initial_lr is not specified in param_groups[{}] when resuming an optimizer.format(i))self.base_lrs list(map(lambda group: group[initial_lr], optimizer.param_groups))self.last_epoch last_epoch# Following https://github.com/pytorch/pytorch/issues/20124# We would like to ensure that lr_scheduler.step() is called after# optimizer.step()def with_counter(method):if getattr(method, _with_counter, False):# optimizer.step() has already been replaced, return.return method# Keep a weak reference to the optimizer instance to prevent# cyclic references.instance_ref weakref.ref(method.__self__)# Get the unbound method for the same purpose.func method.__func__cls instance_ref().__class__del methodwraps(func)def wrapper(*args, **kwargs):instance instance_ref()instance._step_count 1wrapped func.__get__(instance, cls)return wrapped(*args, **kwargs)# Note that the returned function here is no longer a bound method,# so attributes like __func__ and __self__ no longer exist.wrapper._with_counter Truereturn wrapperself.optimizer.step with_counter(self.optimizer.step)self.optimizer._step_count 0self._step_count 0self.verbose verboseself.step()2.1.2 step
当模型完成一个 epoch 训练时需要调用step()方法该方法里对last_epoch自增之后在内部上下文管理器类里调用子类实现的get_lr()方法获得各参数组在此次 epoch 时的学习率并更新到 optimizer的param_groups属性之中最后记录下最后一次调整的学习率到self._last_lr此属性将在get_last_lr()方法中返回。在这个方法中用到了上下文管理功能的内部类 _enable_get_lr_call实例对象添加了_get_lr_called_within_step属性这个属性可在子类中使用。此外需要注意的是step方法中的参数epoch已经废弃了在使用时可以直接忽略这个参数。
def step(self, epochNone):# Raise a warning if old pattern is detected# https://github.com/pytorch/pytorch/issues/20124if self._step_count 1:if not hasattr(self.optimizer.step, _with_counter):warnings.warn(...) # 移除了警告信息# Just check if there were two first lr_scheduler.step() calls before optimizer.step()elif self.optimizer._step_count 1:warnings.warn(...) # 移除了警告信息self._step_count 1class _enable_get_lr_call:def __init__(self, o):self.o odef __enter__(self):self.o._get_lr_called_within_step Truereturn selfdef __exit__(self, type, value, traceback):self.o._get_lr_called_within_step Falsewith _enable_get_lr_call(self):if epoch is None:self.last_epoch 1values self.get_lr()else:warnings.warn(EPOCH_DEPRECATION_WARNING, UserWarning)self.last_epoch epochif hasattr(self, _get_closed_form_lr):values self._get_closed_form_lr()else:values self.get_lr()for i, data in enumerate(zip(self.optimizer.param_groups, values)):param_group, lr dataparam_group[lr] lrself.print_lr(self.verbose, i, lr, epoch)self._last_lr [group[lr] for group in self.optimizer.param_groups]2.1.3 get_last_lr、get_lr和print_lr
get_last_lr()方法比较简单就是step()方法调用后记录的最后一次 optimizer各参数组里更新后的学习率信息 get_lr() 方法是抽象方法定义了更新学习率策略的接口不同子类继承后会有不同的实现.其返回值是[lr1, lr2, …]结构 print_lr(is_verbose, group, lr, epochNone)): 该方法提供了显示 lr 调整信息的功能
def get_last_lr(self): Return last computed learning rate by current scheduler.return self._last_lrdef get_lr(self):# Compute learning rate using chainable form of the schedulerraise NotImplementedErrordef print_lr(self, is_verbose, group, lr, epochNone):Display the current learning rate.if is_verbose:if epoch is None:print(Adjusting learning rate of group {} to {:.4e}..format(group, lr))else:print(Epoch {:5d}: adjusting learning rate of group {} to {:.4e}..format(epoch, group, lr))2.1.4 state_dict 和 load_state_dict
这两个方法和Optimizer里的方法功能是一样的就是为了保存和重新加载状态信息需要注意的是这里不会重复记录self.optimizer属性的状态信息因为 Optimizer 有自己实现的对应方法。
state_dict(): 以字典 dict 形式返回当前实例除 self.optimizer 之外的其他所有属性信息 load_state_dict(state_dict): 重新载入之前保存的状态信息
def state_dict(self):Returns the state of the scheduler as a :class:dict.It contains an entry for every variable in self.__dict__ whichis not the optimizer.return {key: value for key, value in self.__dict__.items() if key ! optimizer}def load_state_dict(self, state_dict):Loads the schedulers state.Arguments:state_dict (dict): scheduler state. Should be an object returnedfrom a call to :meth:state_dict.self.__dict__.update(state_dict)参考 https://zhuanlan.zhihu.com/p/346205754?utm_mediumsocialutm_oi73844937195520utm_id0 https://zhuanlan.zhihu.com/p/539642125