优化算法 - 梯度下降

梯度下降

尽管梯度下降(gradient descent)很少直接用于深度学习,但了解它是理解下一节随机梯度下降算法的关键。例如,由于学习率过大,优化问题可能会发散,这种现象早已在梯度下降中出现。同样地,预处理(preconditioning)是梯度下降中的一种常用技术,还被沿用到更高级的算法中。让我们从简单的一维梯度下降开始

1 - 一维梯度下降

%matplotlib inline
import numpy as np
import torch
from d2l import torch as d2l
def f(x): # 目标函数
    return x ** 2

def f_grad(x): # 目标函数的梯度(导数)
    return 2 * x

接下来,我们使用x = 10作为初始值,并假设 η = 0.2 \eta=0.2 η=0.2。使用梯度下降法迭代x共10次,我们可以得到,x的值最终将接近最优解

def gd(eta,f_grad):
    x = 10.0
    results = [x]
    for i in range(10):
        x -= eta * f_grad(x)
        results.append(float(x))
    print(f'epoch 10,x: {x:f}')
    return results

results = gd(0.2,f_grad)
epoch 10,x: 0.060466

对进行x优化的过程可以绘制如下

def show_trace(results,f):
    n = max(abs(min(results)),abs(max(results)))
    f_line = torch.arange(-n,n,0.01)
    d2l.set_figsize()
    d2l.plot([f_line, results], [[f(x) for x in f_line], [f(x) for x in results]], 'x', 'f(x)', fmts=['-', '-o'])

show_trace(results,f)


[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mW4T0Pxz-1663162129056)(https://yingziimage.oss-cn-beijing.aliyuncs.com/img/202209142121652.svg)]

学习率

学习率(learning rate)决定⽬标函数能否收敛到局部最⼩值,以及何时收敛到最⼩值。学习率η可由算法设计者设置。请注意,如果我们使⽤的学习率太⼩,将导致x的更新⾮常缓慢,需要更多的迭代。例如,考虑同⼀优化问题中η = 0.05的进度。如下所⽰,尽管经过了10个步骤,我们仍然离最优解很远

show_trace(gd(0.05, f_grad), f)
epoch 10,x: 3.486784

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jvWKYOtW-1663162129057)(https://yingziimage.oss-cn-beijing.aliyuncs.com/img/202209142121653.svg)]

show_trace(gd(1.1, f_grad), f)
epoch 10,x: 61.917364

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-giSKaps3-1663162129057)(https://yingziimage.oss-cn-beijing.aliyuncs.com/img/202209142121654.svg)]

局部最小值

为了演⽰⾮凸函数的梯度下降,考虑函数f(x) = x · cos(cx),其中c为某常数。这个函数有⽆穷多个局部最⼩值。根据我们选择的学习率,我们最终可能只会得到许多解的⼀个。下⾯的例⼦说明了(不切实际的)⾼学习率如何导致较差的局部最⼩值

c = torch.tensor(0.15 * np.pi)
def f(x): # 目标函数
    return x * torch.cos(c * x)

def f_grad(x): # 目标函数的梯度
    return torch.cos(c * x) - c * x * torch.sin(c * x)

show_trace(gd(2,f_grad),f)
epoch 10,x: -1.528166

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Svj3FZpW-1663162129057)(https://yingziimage.oss-cn-beijing.aliyuncs.com/img/202209142121655.svg)]

2 - 多元梯度下降

def train_2d(trainer,steps=20,f_grad=None):
    """用定制的训练机优化2D目标函数"""
    # s1和s2是稍后将使用的内部状态变量
    x1,x2,s1,s2 = -5,-2,0,0
    results = [(x1,x2)]
    for i in range(steps):
        if f_grad:
            x1, x2, s1, s2 = trainer(x1, x2, s1, s2, f_grad)
        else:
            x1, x2, s1, s2 = trainer(x1, x2, s1, s2)
        results.append((x1,x2))
    print(f'epoch {i + 1},x1:{float(x1):f},x2: {float(x2):f}')
    return results
def show_trace_2d(f, results): #@save
    """显⽰优化过程中2D变量的轨迹"""
    d2l.set_figsize()
    d2l.plt.plot(*zip(*results), '-o', color='#ff7f0e')
    x1, x2 = torch.meshgrid(torch.arange(-5.5, 1.0, 0.1),torch.arange(-3.0, 1.0, 0.1))
    d2l.plt.contour(x1, x2, f(x1, x2), colors='#1f77b4')
    d2l.plt.xlabel('x1')
    d2l.plt.ylabel('x2')

接下来,我们观察学习率η = 0.1时优化变量x的轨迹。可以看到,经过20步之后,x的值接近其位于[0, 0]的最⼩值。虽然进展相当顺利,但相当缓慢

def f_2d(x1, x2): # ⽬标函数
    return x1 ** 2 + 2 * x2 ** 2

def f_2d_grad(x1, x2): # ⽬标函数的梯度
    return (2 * x1, 4 * x2)

def gd_2d(x1, x2, s1, s2, f_grad):
    g1, g2 = f_grad(x1, x2)
    return (x1 - eta * g1, x2 - eta * g2, 0, 0)

eta = 0.1
show_trace_2d(f_2d, train_2d(gd_2d, f_grad=f_2d_grad))
epoch 20,x1:-0.057646,x2: -0.000073


C:\Users\20919\anaconda3\envs\d2l\lib\site-packages\torch\functional.py:478: UserWarning: torch.meshgrid: in an upcoming release, it will be required to pass the indexing argument. (Triggered internally at  C:\actions-runner\_work\pytorch\pytorch\builder\windows\pytorch\aten\src\ATen\native\TensorShape.cpp:2895.)
  return _VF.meshgrid(tensors, **kwargs)  # type: ignore[attr-defined]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TXfOFbE1-1663162129058)(https://yingziimage.oss-cn-beijing.aliyuncs.com/img/202209142121656.svg)]

3 - 自适应方法

正如我们在 11.3.1节中所看到的,选择“恰到好处”的学习率η是很棘⼿的。如果我们把它选得太⼩,就没有什么进展;如果太⼤,得到的解就会振荡,甚⾄可能发散。如果我们可以⾃动确定η,或者完全不必选择学习率,会怎么样?除了考虑⽬标函数的值和梯度、还考虑它的曲率的⼆阶⽅法可以帮我们解决这个问题。虽然由于计算代价的原因,这些⽅法不能直接应⽤于深度学习,但它们为如何设计⾼级优化算法提供了有⽤的思维直觉,这些算法可以模拟下⾯概述的算法的许多理想特性

牛顿法

c = torch.tensor(0.5)

def f(x): # 目标函数
    return torch.cosh(c * x)

def f_grad(x): # 目标函数的梯度
    return c * torch.sinh(c * x)

def f_hess(x): # 目标函数的Hessian
    return c ** 2 * torch.cosh(c * x)

def newton(eta=1):
    x = 10.0
    results = [x]
    for i in range(10):
        x -= eta * f_grad(x) / f_hess(x)
        results.append(float(x))
    print('epoch 10, x:', x)
    return results

show_trace(newton(),f)
epoch 10, x: tensor(0.)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xYOQ98s3-1663162129058)(https://yingziimage.oss-cn-beijing.aliyuncs.com/img/202209142121657.svg)]

现在让我们考虑⼀个⾮凸函数,⽐如f(x) = x cos(cx),c为某些常数。请注意在⽜顿法中,我们最终将除以Hessian。这意味着如果⼆阶导数是负的,f的值可能会趋于增加。这是这个算法的致命缺陷!让我们看看实践中会发⽣什么

c = torch.tensor(0.15 * np.pi)

def f(x): # ⽬标函数
    return x * torch.cos(c * x)

def f_grad(x): # ⽬标函数的梯度
    return torch.cos(c * x) - c * x * torch.sin(c * x)

def f_hess(x): # ⽬标函数的Hessian
    return - 2 * c * torch.sin(c * x) - x * c**2 * torch.cos(c * x)

show_trace(newton(), f)
epoch 10, x: tensor(26.8341)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mTTOa5ah-1663162129059)(https://yingziimage.oss-cn-beijing.aliyuncs.com/img/202209142121658.svg)]

这发⽣了惊⼈的错误。我们怎样才能修正它?⼀种⽅法是⽤取Hessian的绝对值来修正,另⼀个策略是重新引⼊学习率。这似乎违背了初衷,但不完全是——拥有⼆阶信息可以使我们在曲率较⼤时保持谨慎,⽽在⽬标函数较平坦时则采⽤较⼤的学习率。让我们看看在学习率稍⼩的情况下它是如何⽣效的,⽐如η =0.5。如我们所⻅,我们有了⼀个相当⾼效的算法

show_trace(newton(0.5), f)
epoch 10, x: tensor(7.2699)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5j6dqmMl-1663162129059)(https://yingziimage.oss-cn-beijing.aliyuncs.com/img/202209142121659.svg)]

收敛性分析

预处理

梯度下降和线搜索

4 - 小结

  • 学习率的⼤⼩很重要:学习率太⼤会使模型发散,学习率太⼩会没有进展
  • 梯度下降会可能陷⼊局部极⼩值,⽽得不到全局最⼩值
  • 在⾼维模型中,调整学习率是很复杂的
  • 预处理有助于调节⽐例
  • ⽜顿法在凸问题中⼀旦开始正常⼯作,速度就会快得多
  • 对于⾮凸问题,不要不作任何调整就使⽤⽜顿法
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值