• 深度学习 --- stanford cs231学习笔记七(训练神经网络之梯度下降优化器)


    5,梯度下降优化器

            

    5,1 梯度下降在深度学习中的作用 

            在深度学习中,权重W的值是否合理是由损失函数L来判断的。L越小,表示W的设置越happy。L越大,表示W的值越unhappy。 为了让L越来越小,常用的方法是梯度下降法。

    5,2 梯度下降法的基本原理 

            梯度下降法的原理是基于函数f在点P处的梯度一定是函数f在P点处的所有方向导数中增加最大的方向导数。因此,只要沿着梯度方向移动自变量 x,函数值 f 就会以最快的速度增加。要想让x沿着梯度方向移动,只需让自变量x加上梯度。且,不论梯度是正还是负,函数值f都会增加。 

             对梯度下降法而言,则正好相反。我们希望尽快找到函数的最小值,以及此时的自变量x。因此,我们应该让自变量x不断地朝着梯度的反方向移动,这样函数值就会很快减小。而让x沿着梯度的反方向移动的方法,则是让x减去梯度。

    以一元一次函数y=x和y=-x为例:

            图中x0表示自变量x的初始位置,红点表示x0加梯度后的坐标,蓝点表示x0减去梯度后的位置。对函数y=x而言,梯度为正1,x0=2加上梯度后会朝着x轴的正向移动,函数值增加。对函数y=-x而言,梯度为负1,x0=0加上梯度后会朝着x轴的反方向移动,函数值还是在增加。

            对梯度下降法而言,则是希望把x0朝着蓝点方向移动。对于两幅图中的两个函数,我同时让x0减去梯度,得到了图中的蓝点。如果继续移动,则函数值会越来越低,直到函数的最小值。

           

    1. import numpy as np
    2. import matplotlib.pyplot as plt
    3. #y=x
    4. def f(x):
    5. return x
    6. def df(x):
    7. return 1
    8. #y=-x
    9. def ff(x):
    10. return -x
    11. def dff(x):
    12. return -1
    13. x=np.linspace(-np.pi,np.pi,300)
    14. #画y=x
    15. fig,axs=plt.subplots(1,2, figsize=(14, 6))
    16. y=f(x)
    17. axs[0].plot(x,y,label='y=x')
    18. axs[0].set_title('y = x (update x with grad=1)')
    19. axs[0].set_xlabel('x')
    20. axs[0].set_ylabel('y')
    21. axs[0].axhline(0, color='black', linewidth=0.5)
    22. axs[0].axvline(0, color='black', linewidth=0.5)
    23. #当前x的位置
    24. x0=2
    25. axs[0].scatter(x0,f(x0),color='black', s=100,label='x0')
    26. #沿着函数增加的方向移动x(移动lr个单位的梯度)
    27. #注意我这里是用自变量加梯度
    28. lr=0.5
    29. x1=x0+lr*df(x0)
    30. x2=x0-lr*df(x0)
    31. axs[0].scatter(x1,f(x1),color='red', s=100,label='x0+grad')
    32. axs[0].scatter(x2,f(x2),color='cyan', s=100,label='x0-grad')
    33. axs[0].legend()
    34. #画y=-x
    35. y=ff(x)
    36. axs[1].plot(x,y,label='y=-x')
    37. axs[1].set_title('y = -x (update x with grad=-1)')
    38. axs[1].set_xlabel('x')
    39. axs[1].set_ylabel('y')
    40. axs[1].axhline(0, color='black', linewidth=0.5)
    41. axs[1].axvline(0, color='black', linewidth=0.5)
    42. #当前x的位置
    43. x0=0
    44. axs[1].scatter(x0,ff(x0),color='black', s=100,label='x0')
    45. #沿着函数增加的方向移动x(移动lr个单位的梯度)
    46. #注意我这里依然是用自变量加梯度
    47. lr=1.5
    48. x1=x0+lr*dff(x0)
    49. x2=x0-lr*dff(x0)
    50. axs[1].scatter(x1,ff(x1),color='red', s=100,label='x0+grad')
    51. axs[1].scatter(x2,ff(x2),color='cyan', s=100,label='x0-grad')
    52. axs[1].legend()

            如果要以深度学习的损失函数为例,下图中权重W为自变量,损失函数L(x,W)所对应的梯度如下图中Grad(L(x,W))所示。现在,为了让目标函数L(损失函数)的值迅速减小,就要让自变量W沿着梯度的反方向移动这样一来,损失函数L的函数值就会迅速减小。即,通过改变自变量W的值,使得函数L的值小于当前值,直至等于0或更小。

            换句话说,梯度下降法就是要找到能够令损失函数L的值最小值的W。只不过在找到这一W的过程是循序渐进的(通过调整学习率),并非一蹴而就。

            下面是我用python写的一个二元函数的梯度下降法的例子,为了凸显函数关于某一个维度的变化,即,为了模拟损失函数L(W,x)只关于W去更新,使得损失函数L最小化。我的这个例子是让二元函数f(x,y)只关于x更新的demo。

    1. import numpy as np
    2. import matplotlib.pyplot as plt
    3. #目标函数
    4. def f(x,y):
    5. return (x-1)**2+(y-3)**2
    6. #目标函数关于x的梯度
    7. def f_prime(x):
    8. pfpx=2*(x-1)
    9. return pfpx
    10. #SGD只针对 x 变量进行梯度下降
    11. def SGD(x0,y0,lr,it):
    12. points = [[x0,y0]]#x catch
    13. x=x0
    14. for _ in range(it):
    15. grad=f_prime(x)
    16. x-=lr*grad
    17. points.append([x,y0])
    18. return np.array(points)
    19. #main
    20. x0=5
    21. y0=5
    22. lr=0.1
    23. it=20
    24. points=SGD(x0,y0,lr,it)
    25. # 绘制目标函数
    26. x = np.linspace(-3, 7, 400)
    27. y = np.linspace(0, 7, 400)
    28. X, Y = np.meshgrid(x, y)
    29. Z = f(X, Y)
    30. #绘制等高线图
    31. plt.figure(figsize=(10, 6))
    32. plt.contour(X, Y, Z, levels=np.logspace(-1, 3, 20), cmap='jet')
    33. # 绘制梯度下降的点
    34. plt.plot(points[:, 0], points[:, 1], 'ro-')
    35. # 显示起点和终点
    36. plt.plot(x0, y0, 'go', label='Starting point')
    37. plt.plot(points[-1, 0], points[-1, 1], 'bo', label='End point')
    38. #显示令原函数为0的点
    39. plt.plot(1,3,'k^',label='f(x,y)=0')
    40. # 图形设置
    41. plt.xlabel('x')
    42. plt.ylabel('y')
    43. plt.title('Gradient Descent Optimization for $f(x, y) = (x - 1)^2 + (y - 3)^2$ (Only updating x)')
    44. plt.legend()
    45. plt.grid(True)
    46. plt.show()
    47. # 绘制三维图像
    48. fig = plt.figure(figsize=(12, 8))
    49. ax = fig.add_subplot(111, projection='3d')
    50. surface = ax.plot_surface(X, Y, Z, cmap='RdYlBu', alpha=0.5, edgecolor='none')
    51. # 设置视角
    52. ax.view_init(elev=30, azim=100) # 例如,仰角为30度,方位角为45度
    53. # 绘制梯度下降的点
    54. points_z = f(points[:, 0], points[:, 1])
    55. ax.plot(points[:, 0], points[:, 1], points_z, 'ro-', markersize=5, label='Gradient Descent Path')
    56. # 显示起点和终点
    57. ax.scatter(x0, y0, f(x0, y0), color='g', s=100, label='Starting Point')
    58. ax.scatter(points[-1, 0], points[-1, 1], points_z[-1], color='b', s=100, label='End Point')
    59. #显示令原函数为0的点
    60. ax.scatter(1,3,f(1,3),color='k',marker='^',s=100,label='f(x,y)=0')
    61. # 添加颜色条
    62. fig.colorbar(surface, shrink=0.5, aspect=10)
    63. # 图形设置
    64. ax.set_xlabel('x')
    65. ax.set_ylabel('y')
    66. ax.set_zlabel('f(x, y)')
    67. ax.set_title('Gradient Descent on $f(x, y) = (x - 1)^2 + (y - 3)^2$ (Only updating x)')
    68. ax.legend()
    69. plt.show()

    运行结果: 

            从运行结果中可以看到,因为整个迭代过程只用到了f(x,y)关于x的偏导(限制了y的更新),因此,本来应该沿着令f(x,y)为0的点[x=1,y=3](此时函数值最小等于0)移动的start point,现在只有x方向的移动,即朝着x=1移动。


            如果能同时更新两个自变量,则自变量会朝着目标点,即朝着函数值为0的黑三角移动。

     相应的python代码为:

    1. import numpy as np
    2. import matplotlib.pyplot as plt
    3. #目标函数
    4. def f(x,y):
    5. return (x-1)**2+(y-3)**2
    6. #目标函数关于全部自变量的梯度
    7. def f_prime(x,y):
    8. pfpx=2*(x-1)
    9. pfpy=2*(y-3)
    10. grad=np.array([pfpx,pfpy])
    11. return grad
    12. #SGD
    13. def SGD(x0,y0,lr,it):
    14. points = [[x0,y0]]#x catch
    15. x=x0
    16. y=y0
    17. for _ in range(it):
    18. grad=f_prime(x,y)
    19. x-=lr*grad[0]
    20. y-=lr*grad[1]
    21. points.append([x,y])
    22. return np.array(points)
    23. #main
    24. x0=5
    25. y0=2
    26. lr=0.1
    27. it=20
    28. points=SGD(x0,y0,lr,it)
    29. # 绘制目标函数
    30. x = np.linspace(-1, 7, 400)
    31. y = np.linspace(-1, 7, 400)
    32. X, Y = np.meshgrid(x, y)
    33. Z = f(X, Y)
    34. #绘制等高线图
    35. plt.figure(figsize=(10, 6))
    36. plt.contour(X, Y, Z, levels=np.logspace(-1, 3, 20), cmap='jet')
    37. # 绘制梯度下降的点
    38. plt.plot(points[:, 0], points[:, 1], 'ro-')
    39. # 显示起点和终点
    40. plt.plot(x0, y0, 'go', label='Starting point')
    41. plt.plot(points[-1, 0], points[-1, 1], 'bo', label='End point')
    42. #显示令原函数为0的点
    43. plt.plot(1,3,'k^',label='f(x,y)=0')
    44. # 图形设置
    45. plt.xlabel('x')
    46. plt.ylabel('y')
    47. plt.title('Gradient Descent Optimization for $f(x, y) = (x - 1)^2 + (y - 3)^2$ ')
    48. plt.legend()
    49. plt.grid(True)
    50. plt.show()
    51. # 绘制三维图像
    52. fig = plt.figure(figsize=(12, 8))
    53. ax = fig.add_subplot(111, projection='3d')
    54. surface = ax.plot_surface(X, Y, Z, cmap='RdYlBu', alpha=0.5, edgecolor='none')
    55. # 设置视角
    56. ax.view_init(elev=30, azim=60) # 例如,仰角为30度,方位角为45度
    57. # 绘制梯度下降的点
    58. points_z = f(points[:, 0], points[:, 1])
    59. ax.plot(points[:, 0], points[:, 1], points_z, 'ro-', markersize=5, label='Gradient Descent Path')
    60. # 显示起点和终点
    61. ax.scatter(x0, y0, f(x0, y0), color='g', s=100, label='Starting Point')
    62. ax.scatter(points[-1, 0], points[-1, 1], points_z[-1], color='b', s=100, label='End Point')
    63. #显示令原函数为0的点
    64. ax.scatter(1,3,f(1,3),color='k',marker='^',s=100,label='f(x,y)=0')
    65. # 添加颜色条
    66. fig.colorbar(surface, shrink=0.5, aspect=5)
    67. # 图形设置
    68. ax.set_xlabel('x')
    69. ax.set_ylabel('y')
    70. ax.set_zlabel('f(x, y)')
    71. ax.set_title('Gradient Descent on $f(x, y) = (x - 1)^2 + (y - 3)^2$ ')
    72. ax.legend()
    73. plt.show()

    5,3 常规梯度下降法的不足之处

            由于梯度下降法本身就是在不断地沿着梯度的反方向下降,直到找到最低点。因此,当该点下降到局部最低点时,或者是下降到一个平坦区域时,就无法继续下降了。而此时所对应的函数值并不是函数的最小值。

    例如下面这种情况:

    1. import numpy as np
    2. import matplotlib.pyplot as plt
    3. from mpl_toolkits.mplot3d import Axes3D
    4. # 目标函数
    5. def f(x, y):
    6. return x**4 - 2 * x**2 + y**2
    7. # 目标函数关于全部自变量的梯度
    8. def f_prime(x, y):
    9. df_dx = 4 * x**3 - 4 * x
    10. df_dy = 2 * y
    11. return np.array([df_dx, df_dy])
    12. # SGD
    13. def SGD(x0, y0, lr, it):
    14. points = [[x0, y0]]
    15. x, y = x0, y0
    16. for _ in range(it):
    17. grad = f_prime(x, y)
    18. x -= lr * grad[0]
    19. y -= lr * grad[1]
    20. points.append([x, y])
    21. return np.array(points)
    22. # 参数设置
    23. x0 = 0
    24. y0 = 1.5
    25. lr = 0.5 # 较大的学习率
    26. it = 30
    27. # 执行梯度下降法
    28. points = SGD(x0, y0, lr, it)
    29. # 绘制等高线图
    30. plt.figure(figsize=(10, 6))
    31. x = np.linspace(-2, 2, 400)
    32. y = np.linspace(-2, 2, 400)
    33. X, Y = np.meshgrid(x, y)
    34. Z = f(X, Y)
    35. contour=plt.contour(X, Y, Z, levels=np.linspace(-2, 2, 100), cmap='jet')
    36. plt.colorbar(contour, shrink=0.8, extend='both') # 添加颜色条
    37. # 绘制梯度下降的点
    38. plt.plot(points[:, 0], points[:, 1], 'ro-')
    39. # 显示起点和终点
    40. plt.plot(x0, y0, 'go', label='Starting point')
    41. plt.plot(points[-1, 0], points[-1, 1], 'bo', label='End point')
    42. # 图形设置
    43. plt.xlabel('x')
    44. plt.ylabel('y')
    45. plt.title('Gradient Descent Optimization for $f(x, y) = x^4 - 2x^2 + y^2$ with High Learning Rate')
    46. plt.legend()
    47. plt.grid(True)
    48. plt.show()
    49. # 绘制三维图像
    50. fig = plt.figure(figsize=(12, 8))
    51. ax = fig.add_subplot(111, projection='3d')
    52. surface = ax.plot_surface(X, Y, Z, cmap='RdYlBu', alpha=0.5)
    53. # 绘制梯度下降的点
    54. points_z = f(points[:, 0], points[:, 1])
    55. ax.plot(points[:, 0], points[:, 1], points_z, 'ro-', markersize=5, label='Gradient Descent Path')
    56. # 显示起点和终点
    57. ax.scatter(x0, y0, f(x0, y0), color='g', s=100, label='Starting Point')
    58. ax.scatter(points[-1, 0], points[-1, 1], points_z[-1], color='b', s=100, label='End Point')
    59. # 添加颜色条
    60. fig.colorbar(surface, shrink=0.5, aspect=5)
    61. # 图形设置
    62. ax.set_xlabel('x')
    63. ax.set_ylabel('y')
    64. ax.set_zlabel('f(x, y)')
    65. ax.set_title('Gradient Descent on $f(x, y) = x^4 - 2x^2 + y^2$ with High Learning Rate')
    66. ax.legend()
    67. plt.show()

    这是起始点的选择不恰当引起的的: 

    这是学习率的选择引起的:


     5,4 SGD+momentum

            为了克服传统梯度下降法的缺点,即,碰到local min或saddle points时失效的情况。

            SGD+Momentum相对于之前的变化在于,原有的SGD是走一步算一步梯度,然后再按这个梯度更新,因此如果走到了局部最小值处或者鞍点,当前点的梯度就为0。梯度为0,就走不下去了,只能在原点大转。

            SGD+Momentum为了让点继续走下去,就引入了“惯性”的概念。具体来说,Momentum在更新时不光考虑当前点的梯度,也会考虑前一步的梯度。也就是在走这一步之前,把上一步的惯性也考虑进去。

            比如说前面遇到的鞍点或局部最低点,因为按照前一点的梯度去更新,正好走到了这里。但如果前一点所使用的梯度是加上了上上一点的梯度的,也就是加上了惯性,那么这一步就比SGD迈的大,迈的远。

            就好像下图中的黑圈,如果按照SGD的梯度走,则正好走到红点的位置。但如果加上了上一步的惯性,步子迈的要比SGD大,就能成功的越过局部低点和鞍点继续往下走。

     对于SGD+Momentum而言,下面两个公式 是等价的:


    5,5 Nesterov Momentum

             SGD+Momentum的出现是为了能够越过局部最低点和鞍点,但有时候如果步子迈的太大也不好,即,冲过头了。例如,在已经快接近全局最低点的地方,需要反复几次才能走到最低点,也就是会出现震荡。

            Nesterov Momentum的做法是,不再按照当前点的梯度+前一点的梯度去走。而是按照下一步的梯度+前一点的梯度去跟新。

      

            这样就能防止步子迈的过大,使初始点在下降的过程中,既能越过鞍点和局部最低点,也能避免震荡。

    小结:

            不论是SGD+Momentum还是Nesterov Momentum算法都是借助物理中动量的概念设计的算法。下面会介绍一些别的算法如Adagrad, RMSprop和Adam等,他们都属于自适应算法,通过自适应的调整学习率,处理不同梯度的变化,帮助越过鞍点。


    5,6 AdaGrad(Adaptive Gradient Algorithm)

            AdaGrad(Adaptive Gradient Algorithm)是一种自适应学习率优化算法,它根据历史的梯度来调整每次的学习率。

            他的整体思路跟SGD一样,还是用原始梯度乘以学习率去更新W。所不同的是,他用梯度的平方和的平方根作为对学习率的惩罚(惩罚就是让学习率除以这个值)去动态的调整学习率。走的步数越多,梯度的平方和就越大,惩罚的就越厉害。 

            因此,刚开始的时候学习率的衰减小,即步伐大。越是到了后面,学习率的衰减就越来越大,下降的步伐也就会越来越小。这也符合梯度下降的构想,刚开始的时候步伐大,容易越过鞍点和局部小值点,到了后面越是接近全局最小值点了,步子也正好应该小了,免得出现震荡。


    5,7 RMSProp

            AdaGrad的效果很好,但有一个潜在的问题,也就是他的那个自适应的惩罚项。随着步数增多,对学习率的惩罚越来越大,这也是我们希望看到了,因为,大概了这个时候已经下降到函数的最低点了。但如果没有呢?

            也就是说,如果还没有走到谷底,对学习率的惩罚就已经很大了呢?这个时候,就好像梯度消失一样,学习率几乎为0,w无法更新了。相当于是眼看着就要到谷底了,却脚崴了,走不动道了。因此,RMSProp就对AdaGrad的这一问题进行了改进。

            在grad_sqared的计算中RMSProp加入了一个系数decay_rate。根据公式可以看出,当decay_rate为0时,RMSProp就退化成了AdaGrad算法,此时学习率的惩罚项依然是梯度的平方和开根号。当decay_rate为1时,每一步学习率的惩罚项都等于当前梯度的平方根,即只与当前梯度有关。

            这也就是说,随着decay_rate这个参数从0逐渐增加到1,对学习率的惩罚也越来越弱。这样就能避免步数太多了以后崴脚的情况。


    (全文完) 

    --- 作者,松下J27

     参考文献(鸣谢): 

    1,Stanford University CS231n: Deep Learning for Computer Vision

    2,训练神经网络(第二部分)_哔哩哔哩_bilibili

    3,10 Training Neural Networks I_哔哩哔哩_bilibili

    4,Schedule | EECS 498-007 / 598-005: Deep Learning for Computer Vision 

    版权声明:所有的笔记,可能来自很多不同的网站和说明,在此没法一一列出,如有侵权,请告知,立即删除。欢迎大家转载,但是,如果有人引用或者COPY我的文章,必须在你的文章中注明你所使用的图片或者文字来自于我的文章,否则,侵权必究。 ----松下J27 

  • 相关阅读:
    Java Excel Poi 字体颜色设置
    计算读取速度
    Linux内核VFS详解
    IntelliJ IDEA远程调试:使用IDEA Remote Debug进行高效调试的指南
    计组大作业|硬件小学期的思路
    【无标题】
    单元测试框架-Pytest(简单学习)
    异形双柱体阵列纳米粒:球形纳米粒/圆盘形纳米粒子/盘状纳米粒子/棒状纳米粒子
    如何将项目部署到服务器上(全套教程)
    原型(克隆)模式
  • 原文地址:https://blog.csdn.net/daduzimama/article/details/139991716