图解深度学习-梯度下降法优化器可视化(SGD, Momentum,Adam, Adagrad and RMSProp)

前言

主要用了Jupyter Notebook的插件ipywidgets,可以进行交互,可以自己调整参数来看结果,用于理解来说非常好,今后也会继续用这些东西来做图解深度学习,主要代码来自链接,框架是他的,我主要做了点修改,更加方便理解和调试,可以动手改变学习率,训练次数等一些参数,可视化非常友好,当然也可以自己改变咯。提供下修改后的jupyter的文件链接

%matplotlib inline
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import ticker, cm
import seaborn as sns
from ipywidgets import *
import math

#绘图元素比例  比较小
sns.set_context('paper', font_scale=2)
#设置显示中文字体
font = {'family' : 'SimHei',
#         'weight' : 'bold',
        'size'   : '15'}
plt.rc('font', **font)               # 步骤一(设置字体的更多属性)
plt.rc('axes', unicode_minus=False)  # 步骤二(解决坐标轴负数的负号显示问题)

定义了4个基本函数

  1. f_2d(x1, x2) 定义了一个简单的 2 维函数, f ( x 1 , x 2 ) = 0.5 x 1 2 + 2 x 2 2 f(x_1, x_2) = 0.5 x_1^2 + 2x_2^2 f(x1,x2)=0.5x12+2x22. 我们的目标是使用这个函数测试各种梯度下降算法,寻找极小值点。
  2. f_grad(x1, x2) 计算 f_2d() 沿两个方向的梯度
  3. train_2d() 函数使用给用的梯度下降算法,迭代N步,返回更新数据,即x1,x2的值
  4. plot_2d() 函数画出 f_2d 函数的等高图以及N步更新轨迹
  5. 学习率,迭代次数,变量初始值均可在空间中调试,方便观察
def f_2d(x1, x2):
    '''优化的目标函数 有2个变量'''
    return 0.5 * x1 ** 2 + 2 * x2 ** 2

def f_grad(x1, x2):
    '''x1和x2的梯度'''
    dfdx1 =  x1
    dfdx2 = 4 * x2
    return dfdx1, dfdx2

def train_2d(trainer, lr,epoch=50,init_x1=-4,init_x2=-4):
    """自定义训练器:trainer
       lr:学习率
       epoch:轮次
       init_x1:x1的初始值 便于观察
       init_x2:x1的初始值
    """
    x1, x2 = init_x1, init_x2
    s_x1, s_x2 = 0, 0
    res = [(x1, x2)]
    for i in range(epoch):
        x1, x2, s_x1, s_x2, lr = trainer(x1, x2, s_x1, s_x2, lr)
        res.append((x1, x2))
    return res

def plot_2d(res, figsize=(10, 8), title=None):
    x1_, x2_ = zip(*res)
    fig = plt.figure(figsize=figsize)
    plt.plot([0], [0], 'r*', ms=15)
    plt.text(0.0, 0.25, '最小值', color='k')
    plt.plot(x1_[0], x2_[0], 'ro', ms=10)
    plt.text(x1_[0]+0.2, x2_[0]-0.15, '起点', color='k')
    plt.plot(x1_, x2_, '-o', color='r')
    
    plt.plot(x1_[-1], x2_[-1], 'bo',ms=10)
    plt.text(x1_[-1]+0.1, x2_[-1]-0.4, '终点', color='k')
    
    x1 = np.linspace(-5,5, 100)
    x2 = np.linspace(-5,5, 100)
    x1, x2 = np.meshgrid(x1, x2)
    #画出等高线区域,并用彩虹颜色填充,设置透明度
    cp=plt.contourf(x1, x2, f_2d(x1, x2),alpha=0.75, cmap=cm.rainbow)
    #画出等高线
    C=plt.contour(x1, x2, f_2d(x1, x2),colors='black')
    plt.clabel(C,inline=True,fontsize=15)
    cbar = fig.colorbar(cp)
    cbar.set_label('损失值')
    plt.xlabel('x1')
    plt.ylabel('x2')
    plt.title(title)
    plt.show()
    x1,x2=res[-1]
    loss=f_2d(x1,x2)
    print('最小损失值:',loss,'x1:',x1,' x2:',x2)

机器学习原理

机器学习是一种新的编程模式。这种模式先假设任何输入 x 与输出 y 之间存在连续几何变换,
其函数形式可用 y = f ( x , θ ) y= f(x, \theta) y=f(x,θ) 表示。其中 θ \theta θ 是函数参数,初始化为随机数。
注意,初始化过程并不任意,这里有一篇很好的文章讲参数初始化
deeplearning.ai

机器学习通过逐步微调函数参数 θ \theta θ的方式,试图最小化模型预言 y ^ \hat{y} y^ 与真实标签 y y y的差别。
此差别最简单的数学表达式是 MAE 或者 MSE,即
L ( θ ) = ∣ y ^ − y ∣ L(\theta) = |\hat{y} - y| L(θ)=y^y

L ( θ ) = ∣ y ^ − y ∣ 2 L(\theta) = |\hat{y} - y|^2 L(θ)=y^y2

为了最小化误差 L ( θ ) L(\theta) L(θ), 我们只需要计算误差对参数 θ \theta θ 的梯度,通过链式规则,
将误差反向传递到 θ \theta θ, 并使用梯度下降,更新 θ \theta θ 即可。

如果增加 θ \theta θ, 损失函数 L ( θ ) L(\theta) L(θ) 增大,那么损失函数对参数 θ \theta θ的梯度为正数, 即 ∇ θ L ( θ ) > 0 \nabla_{\theta} L(\theta) > 0 θL(θ)>0,
只要将 θ \theta θ 减去一个正数 η ⋅ ∇ θ L ( θ ) \eta \cdot \nabla_{\theta} L(\theta) ηθL(θ) 即可使损失函数减小。
其中 η \eta η 是学习率 (Learning Rate, 简写为 lr),为一个很小的正数。

如果增加 θ \theta θ, 损失函数 L ( θ ) L(\theta) L(θ) 变小,那么 ∇ θ L ( θ ) &lt; 0 \nabla_{\theta} L(\theta) &lt; 0 θL(θ)<0,
θ \theta θ 减去一个负数 η ⋅ ∇ θ L \eta \cdot \nabla_{\theta} L ηθL 同样可使损失函数减小。
你发现无论梯度是正是负,只要将参数减去学习率乘以梯度,总可以将损失降低。

这就是梯度下降的原理!

接下来会以交互式可视化的方式,展示几种最常用的梯度下降算法。

原始的梯度下降算法

θ = θ − η ⋅ ∇ θ L ( θ ) \theta = \theta - \eta \cdot \nabla_{\theta} L(\theta) θ=θηθL(θ)

#最原始的梯度下降法
def sgd(x1, x2, s1, s2, lr):
    dfdx1, dfdx2 = f_grad(x1, x2)
    return (x1 - lr * dfdx1, x2 - lr * dfdx2, 0, 0, lr)

#定义了4个变量控件,可以随时调节,查看效果 (最小值,最大值,步长)
@interact(lr=(0, 1, 0.001),epoch=(0,100,1),init_x1=(-5,5,0.1),init_x2=(-5,5,0.1),continuous_update=False)
def visualize_gradient_descent(lr=0.05,epoch=50,init_x1=-4,init_x2=-2.4):
    res = train_2d(sgd, lr,epoch,init_x1,init_x2)
    plot_2d(res,(12,8), title='原始SGD')

在这里插入图片描述

带动量的梯度下降算法

v t = γ v t − 1 + η ⋅ ∇ θ L ( θ ) v_t = \gamma v_{t-1} + \eta \cdot \nabla_{\theta} L(\theta) vt=γvt1+ηθL(θ)
θ = θ − v t \theta = \theta - v_t θ=θvt

可见下图。对以前的梯度做了指数加权平均,不会像原始的那样直接折线那么厉害,因为有之前速度影响,所以瞬间就折,而是会再向原来那个方向前进一段距离,也可以理解为对原始梯度做了一个平滑,然后再用来做梯度下降

@interact(lr=(0, 0.99, 0.001), gamma=(0, 0.99, 0.001),continuous_update=False,
         epoch=(0,100,1),init_x1=(-5,5,0.1),init_x2=(-5,5,0.1))
def visualize_sgd_momentum(lr=0.1, gamma=0.1,epoch=10,init_x1=-4,init_x2=-2.4):
    '''lr: learning rate
    gamma: parameter for momentum sgd
    每次都会根据上一次的动量来进行更新
    '''
    
    def momentum(x1, x2, v1, v2, lr):
        dfdx1, dfdx2 = f_grad(x1, x2)
        v1 = gamma * v1 + lr * dfdx1
        v2 = gamma * v2 + lr * dfdx2
        x1 = x1 - v1
        x2 = x2 - v2
        return (x1, x2, v1, v2, lr)
    
    res = train_2d(momentum, lr,epoch,init_x1,init_x2)
    plot_2d(res, title='momentum')

在这里插入图片描述

带惯性的梯度下降算法

v t = γ v t − 1 + ( 1 − γ ) ⋅ ∇ θ L ( θ ) v_t = \gamma v_{t-1} + (1 - \gamma) \cdot \nabla_{\theta} L(\theta) vt=γvt1+(1γ)θL(θ)
θ = θ − η v t \theta = \theta - \eta v_t θ=θηvt

@interact(lr=(0, 0.99, 0.01), gamma=(0, 0.99, 0.01),
          continuous_update=False,epoch=(0,100,1),init_x1=(-5,5,0.1),init_x2=(-5,5,0.1))
def visualize_sgd_inertia(lr=0.1, gamma=0.1,epoch=10,init_x1=-4,init_x2=-2.4):
    '''lr: learning rate
    gamma: parameter for inertia sgd'''
    
    def inertia(x1, x2, v1, v2, lr):
        dfdx1, dfdx2 = f_grad(x1, x2)
        v1 = gamma * v1 + (1 - gamma) * dfdx1
        v2 = gamma * v2 + (1 - gamma) * dfdx2
        x1 = x1 - lr * v1
        x2 = x2 - lr * v2
        return (x1, x2, v1, v2, lr)
    
    res = train_2d(inertia, lr,epoch,init_x1,init_x2)
    plot_2d(res, title='inertia')

在这里插入图片描述

Adagrad 自适应调整的梯度下降

g t = ∇ θ L ( θ ) g_t = \nabla_{\theta} L(\theta) gt=θL(θ)

G = ∑ t g t 2 G = \sum_{t} g_t^2 G=tgt2

θ = θ − η G + ϵ ⋅ g t \theta = \theta - \frac{\eta}{\sqrt{G + \epsilon}} \cdot g_t θ=θG+ϵ ηgt

可以自动调节学习率,但是下降的太快,学习率很小,可能导致后期学不到东西了
此方法也经常用于参数未标准化时,提供不通的学习率,以便于快速到达最小点

@interact(lr=(0, 4, 0.01),
          continuous_update=False,epoch=(0,100,1),init_x1=(-5,5,0.1),init_x2=(-5,5,0.1))
def visualize_adagrad(lr=0.1,epoch=10,init_x1=-4,init_x2=-2.4):
    '''lr: learning rate'''
    def adagrad_2d(x1, x2, s1, s2, lr):
        g1, g2 = f_grad(x1, x2)
        eps = 1e-6
        s1 += g1 ** 2
        s2 += g2 ** 2
        x1 -= lr / math.sqrt(s1 + eps) * g1
        x2 -= lr / math.sqrt(s2 + eps) * g2
        return x1, x2, s1, s2, lr
    
    res = train_2d(adagrad_2d, lr,epoch,init_x1,init_x2)
    plot_2d(res, title='Adagrad')

在这里插入图片描述

RMSProp

g = ∇ θ L ( θ ) g = \nabla_{\theta} L(\theta) g=θL(θ)

E [ g 2 ] = γ E [ g 2 ] + ( 1 − γ ) g 2 E\left[g^2\right] = \gamma E\left[g^2\right] + (1-\gamma) g^2 E[g2]=γE[g2]+(1γ)g2

θ = θ − η E [ g 2 ] + ϵ ⋅ g \theta = \theta - \frac{\eta}{\sqrt{E\left[g^2\right] + \epsilon}} \cdot g θ=θE[g2]+ϵ ηg

对Adagrad做了相应修改,让学习率不会下降了太快导致后期学不到东西,相当于做了归一化,调整不同参数的步长,加快收敛速度

@interact(lr=(0, 4, 0.001), 
          gamma=(0, 0.99, 0.001),
          continuous_update=False,epoch=(0,100,1),init_x1=(-5,5,0.1),init_x2=(-5,5,0.1))
def visualize_rmsprop(lr=0.1, gamma=0.9,epoch=50,init_x1=-4,init_x2=-4):
    '''lr: learning rate, 
       gamma: momentum'''  
    def rmsprop_2d(x1, x2, s1, s2, lr):
        eps = 1e-6
        g1, g2 = f_grad(x1, x2)
        s1 = gamma * s1 + (1 - gamma) * g1 ** 2
        s2 = gamma * s2 + (1 - gamma) * g2 ** 2
        x1 -= lr / math.sqrt(s1 + eps) * g1
        x2 -= lr / math.sqrt(s2 + eps) * g2
        return x1, x2, s1, s2, lr

    res = train_2d(rmsprop_2d, lr,epoch,init_x1,init_x2)
    plot_2d(res, title='RMSProp')

在这里插入图片描述

Adam

g = ∇ θ L ( θ ) g = \nabla_{\theta} L(\theta) g=θL(θ)

m = β 1 m + ( 1 − β 1 ) g m = \beta_1 m + (1 - \beta_1) g m=β1m+(1β1)g

n = β 2 n + ( 1 − β 2 ) g 2 n = \beta_2 n + (1 - \beta_2) g^2 n=β2n+(1β2)g2

m ^ = m ( 1 − β 1 t ) \hat{m} = \frac{m}{(1 - \beta_1^t)} m^=(1β1t)m

n ^ = n ( 1 − β 2 t ) \hat{n} = \frac{n}{(1 - \beta_2^t)} n^=(1β2t)n

θ = θ − η n ^ + ϵ m ^ \theta = \theta - \frac{\eta}{\sqrt{\hat{n}} + \epsilon} \hat{m} θ=θn^ +ϵηm^

结合Momentum和RMSProp的2个特征,即做指数加权平均,又做归一化

@interact(lr=(0, 1, 0.001), 
          beta1=(0, 0.999, 0.001),
          beta2=(0, 0.999, 0.001),
          continuous_update=False,epoch=(0,100,1),init_x1=(-5,5,0.1),init_x2=(-5,5,0.1))
def visualize_adam(lr=0.1, beta1=0.9, beta2=0.999,epoch=10,init_x1=-4,init_x2=-2.4):
    '''lr: learning rate
    beta1: parameter for E(g)
    beta2: parameter for E(g^2)
    '''  
    def Deltax(m, n, g, t):
        eps = 1.0E-6
        m = beta1 * m + (1 - beta1) * g
        n = beta2 * n + (1 - beta2) * g*g
        m_hat = m / (1 - beta1**t)
        n_hat = n / (1 - beta2**t)
        dx = lr * m_hat / (math.sqrt(n_hat) + eps)
        return m, n, dx
        
    def adam_2d(x1, x2, m1, n1, m2, n2, lr, t):
        '''m1, m2: E(g1), E(g2)
           n1, n2: E(g1^2), E(g2^2) where E() is expectation
           lr: learning rate
           t: time step'''
        eps = 1e-6
        g1, g2 = f_grad(x1, x2)
        m1, n1, dx1 = Deltax(m1, n1, g1, t)
        m2, n2, dx2 = Deltax(m2, n2, g2, t)       
        x1 -= dx1
        x2 -= dx2
        return x1, x2, m1, n1, m2, n2, lr
    
    def train_adam(trainer, lr,epoch=10,init_x1=-4,init_x2=-4):
        """Train a 2d object function with a customized trainer"""
        x1, x2 = init_x1,init_x2
        m1, n1, m2, n2 = 0, 0, 0, 0
        res = [(x1, x2)]
        for i in range(epoch):
            x1, x2, m1, n1, m2, n2, lr = trainer(x1, x2, m1, n1, m2, n2, lr, i+1)
            res.append((x1, x2))
        return res
    
    res = train_adam(adam_2d, lr,epoch,init_x1,init_x2)
    plot_2d(res, title='adam')

在这里插入图片描述

好了,今天就到这里了,希望对学习理解有帮助,大神看见勿喷,仅为自己的学习理解,能力有限,请多包涵。

  • 13
    点赞
  • 33
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值