深度之眼Pytorch框架训练营第四期——PyTorch中的优化器

优化器(Optimizer)

1、优化器概念
  • 机器学习模块中,数据读取,构建模型,得到损失函数后,就需要构造优化器,最优化的过程依赖的算法称为优化器
  • 深度学习优化器的两个核心是梯度学习率,前者决定参数更新的方向后者决定参数更新程度
  • 深度学习优化器之所以采用梯度是因为,对于高维的函数其更高阶导的计算复杂度大,应用到深度学习的优化中不实际
  • 深度学习的优化器有许多种类,大致可以分为两类:
  • 一类为优化过程中,学习率不受梯度影响,全程不变或者按照一定的learning schedule随时间变化,这类包括最常见的SGD(随机梯度下降法),带MomentumSGD,带NesterovSGD
  • 一类是优化过程中,学习率随着梯度自适应的改变,并尽可能去消除给定的全局学习率的影响,常见的有Adagrad ,Adadelta, RMSprop, Adam等
2、PyTorch中的优化器基类:Optimizer

PyTorch中的所有优化器均是Optimizer的子类,因此应当首先了解Optimizer这个基类

(1)参数组概念
  • Optimizer对参数的管理是基于组的概念,可以为每一组参数配置特定的lr,momentum,weight_decay 等等
  • 参数组在Optimizer中表现为一个list(self.param_groups),其中每个元素是一个字典,表示一个参数及其相应配置,在字典中中包含'params''weight_decay''lr''momentum'等字段
(2)基类属性
def __init__(self, params, defaults):
    torch._C._log_api_usage_once("python.optimizer")
    self.defaults = defaults

    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)
    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}]

    for param_group in param_groups:
        self.add_param_group(param_group)

__init__()函数中可以看出,一共有三个属性:

  • defaults:优化器超参数
  • state:参数缓存,如momentum的缓存
  • param_groups:管理的参数组
(3)基类方法
  • zero_grad:清空所管理参数的梯度,这是由于PyTorch中张量梯度不自动清零这个特性导致在每一次更新前需要进行此操作
def zero_grad(self):
    r"""Clears the gradients of all optimized :class:`torch.Tensor` s."""
    for group in self.param_groups:
        for p in group['params']:
            if p.grad is not None:
                p.grad.detach_()
                p.grad.zero_()
  • step(closure):执行一步权值更新, 其中可传入参数closure(一个闭包)。如,当采用 LBFGS 优化方法时,需要多次计算,因此需要传入一个闭包去允许它们重新计算 loss
def step(self, closure):
    r"""Performs a single optimization step (parameter update).

    Arguments:
        closure (callable): A closure that reevaluates the model and
            returns the loss. Optional for most optimizers.

    .. note::
        Unless otherwise specified, this function should not modify the
        ``.grad`` field of the parameters.
    """
    raise NotImplementedError
  • add_param_group():给optimizer管理的参数组中增加一组参数,可为该组参数定制 lr, momentum, weight_decay等,在finetune常用
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.

    Arguments:
        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"

    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:
        param_group['params'] = list(params)

    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("can't optimize a non-leaf Tensor")

    for name, default in self.defaults.items():
        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)

    param_set = set()
    for group in self.param_groups:
        param_set.update(set(group['params']))

    if not param_set.isdisjoint(set(param_group['params'])):
        raise ValueError("some parameters appear in more than one parameter group")

    self.param_groups.append(param_group)
  • state_dict():获取模型当前的参数,以一个有序字典形式返回。 这个有序字典中,key是各层参数名,value就是参数
def state_dict(self):
    r"""Returns the state of the optimizer as a :class:`dict`.

    It contains two entries:

    * state - a dict holding current optimization state. Its content
        differs between optimizer classes.
    * param_groups - a dict containing all parameter groups
    """
    # Save ids instead of Tensors
    def pack_group(group):
        packed = {k: v for k, v in group.items() if k != 'params'}
        packed['params'] = [id(p) for p in group['params']]
        return packed
    param_groups = [pack_group(g) for g in self.param_groups]
    # Remap state to use ids as keys
    packed_state = {(id(k) if isinstance(k, torch.Tensor) else k): v
                    for k, v in self.state.items()}
    return {
        'state': packed_state,
        'param_groups': param_groups,
    }
  • load_state_dict():将state_dict中的参数加载到当前网络,常用于finetune
def load_state_dict(self, state_dict):
    r"""Loads the optimizer state.

    Arguments:
        state_dict (dict): optimizer state. Should be an object returned
            from a call to :meth:`state_dict`.
    """
    # deepcopy, to be consistent with module API
    state_dict = deepcopy(state_dict)
    # Validate the state_dict
    groups = self.param_groups
    saved_groups = state_dict['param_groups']

    if len(groups) != len(saved_groups):
        raise ValueError("loaded state dict has a different number of "
                         "parameter groups")
    param_lens = (len(g['params']) for g in groups)
    saved_lens = (len(g['params']) for g in saved_groups)
    if any(p_len != s_len for p_len, s_len in zip(param_lens, saved_lens)):
        raise ValueError("loaded state dict contains a parameter group "
                         "that doesn't match the size of optimizer's group")

    # Update the state
    id_map = {old_id: p for old_id, p in
              zip(chain(*(g['params'] for g in saved_groups)),
                  chain(*(g['params'] for g in groups)))}

    def cast(param, value):
        r"""Make a deep copy of value, casting all tensors to device of param."""
        if isinstance(value, torch.Tensor):
            # Floating-point types are a bit special here. They are the only ones
            # that are assumed to always match the type of params.
            if param.is_floating_point():
                value = value.to(param.dtype)
            value = value.to(param.device)
            return value
        elif isinstance(value, dict):
            return {k: cast(param, v) for k, v in value.items()}
        elif isinstance(value, container_abcs.Iterable):
            return type(value)(cast(param, v) for v in value)
        else:
            return value

    # Copy state assigned to params (and cast tensors to appropriate types).
    # State that is not assigned to params is copied as is (needed for
    # backward compatibility).
    state = defaultdict(dict)
    for k, v in state_dict['state'].items():
        if k in id_map:
            param = id_map[k]
            state[param] = cast(param, v)
        else:
            state[k] = v

    # Update parameter groups, setting their 'params' value
    def update_group(group, new_group):
        new_group['params'] = group['params']
        return new_group
    param_groups = [
        update_group(g, ng) for g, ng in zip(groups, saved_groups)]
    self.__setstate__({'state': state, 'param_groups': param_groups})
3、Pytorch中的十种优化器
(1)学习率(learning rate)

梯度下降公式为:
w i + 1 = w i − g ( w i ) w_{i+1}=w_{i}-g\left(w_{i}\right) wi+1=wig(wi)
如下图所示,对于函数 y = 4 x 2 y = 4x^2 y=4x2 y ′ = 8 x y^{\prime} = 8x y=8x,如果采用梯度下降公式,

在这里插入图片描述

xyg(y)
x0=2y0=16g(y0)=16
x1=2-16=-14y1=784g(y1)=-112
x2=-14+112=98y2=38416g(y2)=784

在这里插入图片描述
可以看出并没有逐渐收敛到 x = 0 x=0 x=0,因此就引出了学习率,控制更新的步伐,此时公式就变为:
w i + 1 = w i − LR ⁡ × g ( w i ) w_{i+1}=w_{i} - \operatorname{LR}\times g\left(w_{i}\right) wi+1=wiLR×g(wi)
例如,我们将学习率设置为 L R = 0.01 LR=0.01 LR=0.01,则得到如下的图像:
在这里插入图片描述
可以看出控制更新步伐后,就能逐渐向 x = 0 x=0 x=0移动,最终收敛到 x = 0 x=0 x=0,那么学习率的大小对算法有什么影响呢?
在这里插入图片描述
从上面这幅图中可以看出,学习率越小,收敛的速度往往越慢,但是学习率大并不意味着收敛速度就一定更快,例如上面的函数 f ( x ) = 4 x 2 f(x)=4x^2 f(x)=4x2,如果初始点为 x 0 = 2 x_0=2 x0=2,则显然,当学习率 L R = 0.125 LR=0.125 LR=0.125时,根据公式 x 1 = x 0 − L R × f ′ ( x 0 ) = 2 − 0.125 × 16 = 0 x_1 = x_0 - LR \times f^{\prime}(x_0)= 2 - 0.125 \times 16 = 0 x1=x0LR×f(x0)=20.125×16=0,一次迭代就能收敛到 x = 0 x=0 x=0,所以学习率 L R LR LR不能太大,也不能太小

(2)动量(momentum)
  • 动量一词来源于物理学,用于刻画物体的运动状态,这里引入动量,主要是为了结合当前梯度与上一次更新信息,用于当前更新,从而加快算法的收敛速度
  • 首先需要了解指数加权平均的概念:指数加权平均通常用于时间序列中求取平均值的方法,如果用 θ t \theta_t θt表示当前时刻的参数值, v t v_t vt表示当前时刻参数的平均值,则有公式:
    v t = β v t − 1 + ( 1 − β ) θ t v_t = \beta v_{t-1} + (1-\beta)\theta_{t} vt=βvt1+(1β)θt
    通过上面的递推关系,我们可以得到:
    v N = ∑ i N ( 1 − β ) β i θ N − i v_{N}=\sum_{i}^{N}(1-\beta) \beta^{i} \theta_{N-i} vN=iN(1β)βiθNi
    由于 β \beta β是一个小于1的数,因此,从上式可以看出离 v N v_{N} vN越远,权重越小,同时权重是呈指数下降的趋势
  • 超参数 β \beta β β \beta β是一个超参数,如下图所示,不同的 β \beta β,衰减的速度不同, β \beta β可以理解为记忆周期,即 β \beta β越大,记忆周期越长,通常 β \beta β设置为0.9,表示的物理含义是——“关注最近10个时间段( 1 1 − β \frac{1}{1-\beta} 1β1)的变化”

在这里插入图片描述

  • Pytorch随机梯度下降+动量:加上动量后,更新公式变化为:
    v i = m ∗ v i − 1 + g ( w i ) w i + 1 = w i − l r ∗ v i \begin{aligned} {v}_{{i}}&={m} * {v}_{{i}-\mathbf{1}}+{g}\left({w}_{{i}}\right) \\ {w}_{{i}+1}&={w}_{{i}}-{l} {r} * {v}_{{i}} \end{aligned} viwi+1=mvi1+g(wi)=wilrvi
    其中, m m mmomentum系数
  • 实例:
    在这里插入图片描述

左右两幅图中,左边的图设置学习率为0.3和0.1,均没有加momentum系数,而右边对学习率为0.1的情况加上了momentum系数,设置m=0.9,可以看出有一个振荡收敛的过程

(3)优化器——torch.optim.SGD
  • PyTorch中最常用且最实用的优化器为SGD优化器,深度学习中90%的任务都可以用SGD进行优化
optim.SGD(params, 
          lr=<object object>, 
          momentum=0, 
          dampening=0, 
          weight_decay=0, 
          nesterov=False)
  • 参数:
  • params:管理的参数组
  • lr:初始学习率
  • momentum:动量系数 β \beta β
  • weight_decay:L2正则化系数
  • nesterov:是否采用NAG
(4)Pytorch中其他九种优化器
<1>torch.optim.ASGD
optim.ASGD(params, 
           lr=0.01, 
           lambd=0.0001, 
           alpha=0.75, 
           t0=1000000.0, 
           weight_decay=0)
  • 功能:ASGD也称为SAG,均表示随机平均梯度下降(Averaged Stochastic Gradient Descent),简单地说ASGD就是用空间换时间的一种SGD
  • 参数:
  • params:管理的参数组
  • lr:初始学习率
  • lambd:衰减项
  • alpha:power for eta update
  • t0:point at which to start averaging
  • weight_decay:权值衰减系数,也就是 L2 正则项的系数
<2> `torch.optim.Rprop
torch.optim.Rprop(params,
                  lr=0.01, 
                  etas=(0.5, 1.2), 
                  step_sizes=(1e-06, 50))
  • 功能:实现 Rprop 优化方法(弹性反向传播),适用于 full-batch,不适用于 mini-batch
<3>torch.optim.Adagrad
torch.optim.Adagrad(params, 
                    lr=0.01, 
                    lr_decay=0, 
                    weight_decay=0, 
                    initial _accumulator_value=0)
  • 功能:实现 Adagrad 优化方法(Adaptive Gradient),Adagrad 是一种自适应优化方法,是自适应的为各个参数分配不同的学习率。这个学习率的变化,会受到梯度的大小和迭代次数的影响。梯度越大,学习率越小;梯度越小,学习率越大。缺点是训练后期,学习率过小, 因为 Adagrad 累加之前所有的梯度平方作为分母
<4> torch.optim.Adadelta
torch.optim.Adadelta(params, 
                     lr=1.0, 
                     rho=0.9,  
                     eps=1e- 06, 
                     weight_decay=0)
  • 功能:实现 Adadelta 优化方法。Adadelta 是 Adagrad 的改进。Adadelta 分母中采用距离当前时间点比较近的累计项,这可以避免在训练后期,学习率过小的问题
<5> torch.optim.RMSprop
torch.optim.RMSprop(params,
                    lr=0.01, 
                    alpha=0.99, 
                    eps=1e- 08, 
                    weight_decay=0, 
                    momentum=0, 
                    centered=False)
  • 功能:实现 RMSprop 优化方法(Hinton 提出),RMS 是均方根(root meam square)的意 思。RMSprop 和 Adadelta 一样,也是对 Adagrad 的一种改进。RMSprop 采用均方根作为分 母,可缓解 Adagrad 学习率下降较快的问题,并且引入均方根,可以减少摆动
<6> torch.optim.Adam
torch.optim.Adam(params, 
                 lr=0.001, 
                 betas=(0.9, 0.999), 
                 eps=1e- 08, 
                 weight_decay=0, 
                 amsgrad=False)
  • 功能:实现 Adam(Adaptive Moment Estimation))优化方法。Adam 是一种自适应学习率的优化方法,Adam 利用梯度的一阶矩估计和二阶矩估计动态的调整学习率, Adam 是结合了 Momentum 和 RMSprop,并进行了偏差修正
<7> torch.optim.Adamax
torch.optim.Adamax(params, 
                   lr=0.002, 
                   betas=(0.9, 0.999),
                   eps=1e- 08, 
                   weight_decay=0)
  • 功能:实现 Adamax 优化方法。Adamax 是对 Adam 增加了一个学习率上限的概念,所以也称之为 Adamax
<8> torch.optim.SparseAdam
torch.optim.SparseAdam(params,
                       lr=0.001,
                       betas=(0.9, 0.999), 
                       eps=1e-08) 
  • 功能:针对稀疏张量的一种“阉割版”Adam 优化方法
<9> torch.optim.LBFGS
torch.optim.LBFGS(params, 
                  lr=1, 
                  max_iter=20, 
                  max_eval=None, 
                  tolerance_ grad=1e-05, 
                  tolerance_change=1e-09, 
                  history_size=100, 
                  line_search_fn=None)
  • 功能:实现 L-BFGS(Limited-memory Broyden–Fletcher–Goldfarb–Shanno)优化方法。 L-BFGS 属于拟牛顿算法。L-BFGS 是对 BFGS 的改进,特点就是节省内存
  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值