【深度学习】神经网络的优化方法

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/Daycym/article/details/83865971

前言

\quad\quad 我们都知道,神经网络的学习目的是找到使损失函数的值尽可能小的参数,这是一个寻找最优参数的问题,解决这个问题的过程可以称为最优化,但由于参数空间非常复杂,无法轻易找到最优解,而且在深度学习中,参数的数量非常大,导致最优化问题更加复杂。

\quad\quad 在这之前,我们是将参数的梯度(导数)作为线索,使参数沿着梯度方向更新,并重复执行多次,从而逐渐靠近最优参数,这个过程称为随机梯度下降法(SGD)

SGD是一个简单的方法,但是比起胡乱地搜索参数空间,也算是“聪明”的方法
但是,针对不同的问题,SGD并不一定是最好的,SGD也存在自身的缺陷

下面我们介绍几种常用的最优化方法,并python实现

SGD

  • 数学表达式
    W W η L W W \leftarrow W - \eta \frac{\partial L}{\partial W}

W W 为权重参数, η \eta 为学习率, L W \frac{\partial L}{\partial W} 表示损失函数关于 W W 的梯度
一般 η \eta 取 0.01 或 0.001

  • python实现
class SGD:
    """随机梯度下降法"""
    def __init__(self, lr=0.01):
        self.lr = lr  # 学习率
        
    # params,grads是字典型数据,比如params['W1'],grads['W1']   
    # 保存了权重参数和他们的梯度
    def update(self, params, grads):  
        for key in params.keys():
        	# 根据SGD公式
            params[key] -= self.lr * grads[key] 
  • 神经网络中如何使用
# 构建网络
network = TwoLayerNet(...)
# 选择最优化方法(如果需要其他方法,可以改用其他的方法)
optimizer = SGD()
# 学习
for i in range(10000):
	# 批数据
	x_batch, t_batch = get_mini_batch(...)
	# 求梯度
	grads = network.gradient(x_batch, t_batch)
	# 权重参数
	params = network.params
	# 优化,更新权重参数
	optimizer.update(params, grads)

上面代码是进行神经网络参数更新的一般步骤,其中optimizer 就是进行参数最优化的对象

  • 如果我们有了新的最优化方法,就可以创建一个和SGD类似的类,里面也包括update方法,这样只需要将上面代码中的optimizer = SGD()换成自己定义的类
  • SGD的缺点

在这里插入图片描述

如果,梯度方向不指向最小值的方向,就会以相对低效的路径更新,出现如图的“之”型
容易收敛到局部最优,并且容易被困在鞍点
为了改正SGD的缺陷,出现了Momentum、AdaGrad、Adam等方法

Momentum

  • 数学表达式
    v α v η L W v \leftarrow \alpha v - \eta \frac{\partial L}{\partial W}
    W W + v W \leftarrow W + v

这里出现了一个新变量 v v ,对应物理上的速度
式中, α v \alpha v 这一项表示在物体不受任何力时,该项承担物体逐渐减速的任务( α \alpha 设定为0.9之类)

在这里插入图片描述

一开始,梯度为负,小球加速向下运行(梯度可以表示为加速度),并且加速度减小
当梯度为0(加速度为0),由于 v α v v \leftarrow \alpha v ,速度逐渐减小,也就是小球还会往右边运动,这样避免陷入局部最小值

  • python实现
class Momentum:

    """Momentum SGD"""

    def __init__(self, lr=0.01, momentum=0.9):
        self.lr = lr  # 学习率
        self.momentum = momentum  #动量
        self.v = None  # 什么都不保存,第一次调用update时,会以字典形式保存与参数结构相同的数据
        
    def update(self, params, grads):
        if self.v is None:
            self.v = {}
            for key, val in params.items():                                
                self.v[key] = np.zeros_like(val)
                
        for key in params.keys():
            self.v[key] = self.momentum*self.v[key] - self.lr*grads[key] 
            params[key] += self.v[key]
  • 评价

和SGD相比,Momentum可以更快地朝最小值靠近,减弱“之”字型的变动程度

AdaGrad

在神经网络学习中,学习率的值很重要。学习率过小,会导致学习花费过多时间;学习率过大,会导致学习发散而不能正确进行

  • 学习率衰减

在这里插入图片描述

随着学习的进行,是学习率逐渐减小,一开始“多”学,然后逐渐“少”学

上面学习率衰减是针对所有参数的,而AdaGrad是针对“每个”参数,赋予其“定制”的值,也就是每个参数的学习率不同;AdaGrad会为每个元素适当的调整学习率

  • 数学表达式
    h h + L W L W h \leftarrow h + \frac{\partial L}{\partial W} \odot \frac{\partial L}{\partial W}
    W W η 1 h L W W \leftarrow W - \eta \frac{1}{\sqrt h}\frac{\partial L}{\partial W}

\odot 表示对应矩阵元素相乘,在更新参数时,通过更改 1 h \frac{1}{\sqrt h} 就可以调整学习的尺度
这意味着,参数的元素中变化较大的元素的学习率将变小,这样就可以按参数的元素进行学习率衰减,使变动大的参数的学习率逐渐减小

  • python实现
class AdaGrad:

    """AdaGrad"""

    def __init__(self, lr=0.01):
        self.lr = lr
        self.h = None
        
    def update(self, params, grads):
        if self.h is None:
            self.h = {}
            for key, val in params.items():
                self.h[key] = np.zeros_like(val)
            
        for key in params.keys():
            self.h[key] += grads[key] * grads[key]
            params[key] -= self.lr * grads[key] / (np.sqrt(self.h[key]) + 1e-7)  # 加上1e-7为了避免有0

Adam

Adam的理论有些复杂,直观上,就是上面两种方法的结合。

  • 数学表达式
    m t = β 1 m t 1 + ( 1 β 1 ) L W m_t = \beta_1m_{t-1} + (1-\beta_1)\frac{\partial L}{\partial W}
    v t = β 2 v t 1 + ( 1 β 2 ) L W L W v_t = \beta_2v_{t-1} + (1-\beta_2)\frac{\partial L}{\partial W} \odot \frac{\partial L}{\partial W}
    m t ^ = m t 1 β 1 t \hat{m_t} = \frac{m_t}{1-\beta_1^t}
    v t ^ = v t 1 β 2 t \hat{v_t} = \frac{v_t}{1-\beta_2^t}
    W W α 1 v t ^ + ϵ m t ^ W \leftarrow W - \alpha \frac{1}{\sqrt{\hat{v_t}}+\epsilon}\hat{m_t}

  • python实现

class Adam:

    """Adam (http://arxiv.org/abs/1412.6980v8)"""

    def __init__(self, lr=0.001, beta1=0.9, beta2=0.999):
        self.lr = lr
        self.beta1 = beta1
        self.beta2 = beta2
        self.iter = 0
        self.m = None
        self.v = None
        
    def update(self, params, grads):
        if self.m is None:
            self.m, self.v = {}, {}
            for key, val in params.items():
                self.m[key] = np.zeros_like(val)
                self.v[key] = np.zeros_like(val)
        
        self.iter += 1
        lr_t  = self.lr * np.sqrt(1.0 - self.beta2**self.iter) / (1.0 - self.beta1**self.iter)         
        
        for key in params.keys():
            #self.m[key] = self.beta1*self.m[key] + (1-self.beta1)*grads[key]
            #self.v[key] = self.beta2*self.v[key] + (1-self.beta2)*(grads[key]**2)
            self.m[key] += (1 - self.beta1) * (grads[key] - self.m[key])
            self.v[key] += (1 - self.beta2) * (grads[key]**2 - self.v[key])
            
            params[key] -= lr_t * self.m[key] / (np.sqrt(self.v[key]) + 1e-7)
            
            #unbias_m += (1 - self.beta1) * (grads[key] - self.m[key]) # correct bias
            #unbisa_b += (1 - self.beta2) * (grads[key]*grads[key] - self.v[key]) # correct bias
            #params[key] += self.lr * unbias_m / (np.sqrt(unbisa_b) + 1e-7)

β 1 β_1 的默认值为0.9, β 2 β_2 的默认值为.999, ϵ ϵ 默认为10−7

此外还有,Nesterov、RMSprop,这里仅给出代码,有兴趣的可以看看:

class Nesterov:

    """Nesterov's Accelerated Gradient (http://arxiv.org/abs/1212.0901)"""

    def __init__(self, lr=0.01, momentum=0.9):
        self.lr = lr
        self.momentum = momentum
        self.v = None
        
    def update(self, params, grads):
        if self.v is None:
            self.v = {}
            for key, val in params.items():
                self.v[key] = np.zeros_like(val)
            
        for key in params.keys():
            self.v[key] *= self.momentum
            self.v[key] -= self.lr * grads[key]
            params[key] += self.momentum * self.momentum * self.v[key]
            params[key] -= (1 + self.momentum) * self.lr * grads[key]

class RMSprop:

    """RMSprop"""

    def __init__(self, lr=0.01, decay_rate = 0.99):
        self.lr = lr
        self.decay_rate = decay_rate
        self.h = None
        
    def update(self, params, grads):
        if self.h is None:
            self.h = {}
            for key, val in params.items():
                self.h[key] = np.zeros_like(val)
            
        for key in params.keys():
            self.h[key] *= self.decay_rate
            self.h[key] += (1 - self.decay_rate) * grads[key] * grads[key]
            params[key] -= self.lr * grads[key] / (np.sqrt(self.h[key]) + 1e-7)

分析

在这里插入图片描述

在这里插入图片描述

第一张图为不同算法在损失平面等高线上随时间的变化情况,第二张图为不同算法在鞍点处的行为比较。

图片来自优化方法总结:SGD,Momentum,AdaGrad,RMSProp,Adam
其中也包含了公式,可参考

猜你喜欢

转载自blog.csdn.net/Daycym/article/details/83865971