• 优化算法 - 梯度下降


    梯度下降

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

    1 - 一维梯度下降

    %matplotlib inline
    import numpy as np
    import torch
    from d2l import torch as d2l
    
    • 1
    • 2
    • 3
    • 4
    def f(x): # 目标函数
        return x ** 2
    
    def f_grad(x): # 目标函数的梯度(导数)
        return 2 * x
    
    • 1
    • 2
    • 3
    • 4
    • 5

    接下来,我们使用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)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    epoch 10,x: 0.060466
    
    • 1

    对进行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)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7


    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(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)
    
    • 1
    epoch 10,x: 3.486784
    
    • 1

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

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

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(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)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    epoch 10,x: -1.528166
    
    • 1

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(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')
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    接下来,我们观察学习率η = 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))
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    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]
    
    • 1
    • 2
    • 3
    • 4
    • 5

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(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)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    epoch 10, x: tensor(0.)
    
    • 1

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(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)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    epoch 10, x: tensor(26.8341)
    
    • 1

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

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

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

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

    收敛性分析

    预处理

    梯度下降和线搜索

    4 - 小结

    • 学习率的⼤⼩很重要:学习率太⼤会使模型发散,学习率太⼩会没有进展
    • 梯度下降会可能陷⼊局部极⼩值,⽽得不到全局最⼩值
    • 在⾼维模型中,调整学习率是很复杂的
    • 预处理有助于调节⽐例
    • ⽜顿法在凸问题中⼀旦开始正常⼯作,速度就会快得多
    • 对于⾮凸问题,不要不作任何调整就使⽤⽜顿法
  • 相关阅读:
    【网络协议】Https
    SQL每日一练(牛客新题库)——第5天:高级查询
    1.3.8 利用三层交换机实现 VLAN 间路由
    中国在生成式人工智能专利方面处于领先地位
    【java,系统结构设计】三层类结构设计
    Google Earth Engine(GEE)——
    测试开发——项目
    C#基础知识
    阿里技术风险与效能部负责人张瓅玶:从底层资源到核心竞争力 阿里巴巴的深度用云实践
    GPT-5: 超越人类语言的模型,你还不了解一下?
  • 原文地址:https://blog.csdn.net/mynameisgt/article/details/126860778