从一条曲线谈损失函数优化方法

作者: breezedancer | 来源:发表于2018-04-27 17:37 被阅读177次

    损失函数也叫目标函数,他是衡量预测值和实际值的相似程度的指标。我们希望预测值和真实值尽量接近,就需要估计一系列参数来拟合,这个参数集使得误差越小就说明这个算法还不错。一个损失函数有可能存在多个局部最小点,我们就需要至少找到在局部地区的最小值。

    找到生成最小值的一组参数的算法被称为优化算法。我们发现随着算法复杂度的增加,则算法倾向于更高效地逼近最小值。我们将在这篇文章中讨论以下算法:

    • 随机梯度下降法(批次、随机、mini-batch)
    • 动量算法(物理里面的动量含义)
    • RMSProp
    • Adam 算法

    随机梯度下降法

    随便找一本书介绍 SGD,都会出现这个公式


    image

    θ是你试图找到最小化 J 的参数,这里的 J 称为目标函数,α叫做学习率。目标函数的来龙去脉可以参考之前的文章。我们先假设θ取一个值,然后不停的修正这个值,从而使得最小化J。可以假设θ是一个山坡上一个点,而最后的导数部分是该点的坡度;学习率就是一个摩擦系数,学习率大就说明摩擦越小。

    算法说明

    随机梯度下降法:
    1、初始化参数(θ,学习率)
    2、计算每个θ处的梯度
    3、更新参数
    4、重复步骤 2 和 3,直到代价值稳定

    随便举个例子:
    下面是一个目标函数和他的导数


    image

    用 python 实现这两个曲线

    
    import numpy as np
    import matplotlib.pyplot as plt
    
    def minimaFunction(theta):
        return np.cos(3*np.pi*theta)/theta
    
    def minimaFunctionDerivative(theta):
        const1 = 3*np.pi
        const2 = const1*theta
        return -(const1*np.sin(const2)/theta)-np.cos(const2)/theta**2
    #从0.1-2.1,步长0.01
    theta = np.arange(.1,2.1,.01)
    Jtheta = minimaFunction(theta)
    dJtheta = minimaFunctionDerivative(theta)
    
    plt.plot(theta,Jtheta,'m--',label = r'$J(\theta)$')
    plt.plot(theta,dJtheta/30,'g-',label = r'$dJ(\theta)/30$')
    plt.legend()
    
    axes = plt.gca()
    
    plt.ylabel(r'$J(\theta),dJ(\theta)/30$')
    plt.xlabel(r'$\theta$')
    plt.title(r'$J(\theta),dJ(\theta)/30 $ vs $\theta$')
    
    plt.show()
    
    image

    图中虚线有3处局部最低点,在靠近0附件是全局最小的。
    使用下面的程序模拟逐步找到最小值

    import numpy as np
    import matplotlib.pyplot as plt
    import matplotlib.animation as animation
    
    #给定参数逐步找到最优值
    def optimize(iterations, oF, dOF,params,learningRate):
        oParams = [params]
        #喜欢次数
        for i in range(iterations):
            # 计算参数的导数
            dParams = dOF(params)
            # 更新参数值
            params = params-learningRate*dParams
            # 参数追加到数组,方便演示
            oParams.append(params)
        return np.array(oParams)
    
    #损失函数
    def minimaFunction(theta):
        return np.cos(3*np.pi*theta)/theta
    #损失函数导数
    def minimaFunctionDerivative(theta):
        const1 = 3*np.pi
        const2 = const1*theta
        return -(const1*np.sin(const2)/theta)-np.cos(const2)/theta**2
    #基本参数设定
    theta = .6
    iterations=45
    learningRate = .0007
    optimizedParameters = optimize(iterations,\
                                   minimaFunction,\
                                   minimaFunctionDerivative,\
                                   theta,\
                                   learningRate)
    
    #  plt 绘制损失函数曲线
    thetaR = np.arange(.1,2.1,.01)
    Jtheta = minimaFunction(thetaR)
    
    # 在损失函数上绘制参数点
    JOptiTheta = minimaFunction(optimizedParameters)
    
    # 创建动画
    fig, ax = plt.subplots()
    line, = ax.plot(thetaR,Jtheta,'m-')
    axes = plt.gca()
    axes.set_ylim([-4,6])#y 周范围
    axes.set_xlim([0,2])#x周范围
    
    # 构建动画参数
    Writer = animation.writers['ffmpeg']
    writer = Writer(fps=15, metadata=dict(artist='Me'), bitrate=1800)
    
    # 动画动作
    def animate(i):
        line, = ax.plot(optimizedParameters[i],JOptiTheta[i],'or')  # update the data
        plt.title(r'Updating $\theta$ through SGD $\theta$ = %f J($\theta$) = %f' %(optimizedParameters[i],JOptiTheta[i]))
        return line,
    
    #动画
    ani = animation.FuncAnimation(fig, animate, np.arange(1, iterations),
                                  interval=1, blit=True)
    #保存
    ani.save('sgd1.mp4', writer=writer)
    
    image

    如果我们的学习率很大,我们可以自己调参数进行测试,会发现红点数据有可能冲到另外一个坡度,形成震荡。把参数跳到0.01就可以发现这个现象。

    动量 SGD

    用户想要使用非常大的学习速率来快速学习感兴趣的参数。不幸的是,当代价函数波动较大时,这可能导致不稳定,之前的视频学习参数过大,基本就没什么点可以看到。
    动量 SGD 试图使用过去的梯度预测学习率来解决这个问题


    image
    γ 和 ν 值允许用户对 dJ(θ) 的前一个值和当前值进行加权来确定新的θ值。人们通常选择γ和ν的值来创建指数加权移动平均值,如下所示: image β参数的最佳选择是 0.9。选择一个等于 1-1/t 的β值可以让用户更愿意考虑νdw 的最新 t 值。这种简单的改变可以使优化过程产生显著的结果!我们现在可以使用更大的学习率,并在尽可能短的时间内收敛!
    #给定参数逐步找到最优值
    def optimize(iterations, oF, dOF,params,learningRate,beta):
        oParams = [params]
        vdw=0.0
        #喜欢次数
        for i in range(iterations):
            # 计算参数的导数
            dParams = dOF(params)
            # 应用公式求得 vdw
            vdw = vdw*beta+(1.0-beta)*dParams
            # 更新参数值
            params = params-learningRate*vdw
            # 参数追加到数组,方便演示
            oParams.append(params)
        return np.array(oParams)
    
    image

    RMSProp

    精益求精,我们继续看看如何再优化。
    RMS prop 试图通过观察关于每个参数的函数梯度的相对大小,来改善动量函数。因此,我们可以取每个梯度平方的加权指数移动平均值,并按比例归一化梯度下降函数。具有较大梯度的参数的 sdw 值将变得比具有较小梯度的参数大得多,从而使代价函数平滑下降到最小值。可以在下面的等式中看到:


    image

    这里的 epsilon 是为数值稳定性而添加的,可以取 10e-7。我理解的意思是防止除以0吧。
    既然公式给出了,我们就继续用代码来实现

    def optimize(iterations, oF, dOF,params,learningRate,beta):
        oParams = [params]
        sdw=0.0
        eps = 10**(-7)
        #喜欢次数
        for i in range(iterations):
            # 计算参数的导数
            dParams = dOF(params)
            # 应用公式求得 sdw
            sdw = sdw*beta+(1.0-beta)*dParams**2
            # 更新参数值
            params = params-learningRate*dParams/(sdw**.5+eps)
            # 参数追加到数组,方便演示
            oParams.append(params)
        return np.array(oParams)
    
    image

    看来效果越来越好了。

    Adam 算法

    我们是否可以做得更好?结合上面动量和RMSProp结合成一种算法,以获得两全其美的效果。公式如下:


    image

    其中贝塔有2个参数,分别可以设置为0.9和0.999,贝塔的 t 次方,t 表示迭代次数(需要+1)

    
    #给定参数逐步找到最优值
    def optimize(iterations, oF, dOF,params,learningRate,beta1,beta2):
        oParams = [params]
        sdm=0.0
        vdm=0.0
        vdwCorr = 0.0
        sdwCorr = 0.0
        eps = 10**(-7)
        #喜欢次数
        for i in range(iterations):
            # 计算参数的导数
            dParams = dOF(params)
            # 应用公式求得
            vdm=vdm*beta1+(1-beta1)*dParams
            sdm=sdm*beta2+(1-beta1)*dParams**2
    
            vdwCorr=vdm/(1.0-beta1**(i+1))
            sdwCorr=sdm/(1.0-beta2**(i+1))
    
            # 更新参数值
            params = params-learningRate*vdwCorr/(sdwCorr**.5+eps)
    
            # 参数追加到数组,方便演示
            oParams.append(params)
        return np.array(oParams)
    

    学习率修改为0.3,也能比较好的工作。


    image

    当然,针对多维也是一样操作,需要考虑导数的时候各个维度,参数也需要对应出现。

    相关文章

      网友评论

      • LostAbaddon:“这里的 epsilon 是为数值稳定性而添加的,可以取 10e-7。我理解的意思是防止除以0吧。”
        还有一个原因是当分母过小的时候这个分式的值会过大,而且变化也会过于激烈,所以会选择这样的做法来做一个人为要求的上线。而且相对简单粗暴的max函数,这样得到的曲线在小端更光滑,效果更好。
        breezedancer:@LostAbaddon 谢谢解答
      • IT人故事会:老铁,经常看别人的分享.感谢别人的分享,感谢!关注了

      本文标题:从一条曲线谈损失函数优化方法

      本文链接:https://www.haomeiwen.com/subject/vnrilftx.html