第六章--与学习相关的技巧(上)

写在前面,本人初次学习深度学习,本文用于记录和梳理知识点。
1 参数的更新
神经网络的学习的目的是找到使损失函数的值尽可能小的参数。这是寻找最优参数的问题,解决这个问题的过程称为最优化。
在前几章中,为了寻找最优参数,我们将参数的梯度(导数)作为了线索。使用参数的梯度,沿梯度方向更新参数,并重复这个步骤多次,从而逐渐靠近最优参数,这个过程称为随机梯度下降法,简称SGD。本文将介绍SGD、Momentum、AdaGrad、Adam四种更新参数的方法。
2 SGD
用数学式可以将SGD写成如下的式子(6.1):
在这里插入图片描述
SGD是朝着梯度方向只前进一定距离的简单方法。
实现SGD类:

class SGD:
    def __init__(self,lr = 0.01):
        self.lr = lr

    def update(self,params,grads):
        for key in params.keys():
            params[key] -= self.lr * grads[key]

将SGD类应用在第五章两层神经网络实现程序中,替换成以下(其他更新方法一样):

#更新权重参数
    # for key in ('W1','b1','W2','b2'):
    #     #不断的去更新权重参数,学习率可表示更新的幅度
    #     network.params[key] -= learning_rate * grad[key]
    optimizer.update(params=network.params,grads=grad)	#需初始定义 optimizer = SGD()

SGD的缺点
如图6-3所示,SGD呈“之”字形移动。这是相当低效的路径。也就是说,SGD的缺点是,如果函数的形状非均向,比如呈延伸状,搜索的路径就会非常低效。即SGD低效的根本原因是,梯度的方向并没有指向最小值的方向。
在这里插入图片描述
对于此类图的说明:这类图中的曲线被称为等高线,外圈高度越高,内圈高度越低,为找到最优参数,沿着梯度方向(最小值方向)更新参数,也就是说,向等高线越低的方向进行,其实可以将上图看做一个碗(后续的图也一样)。
3 Momentum
用数学式表示Momentum方法,如下所示:
在这里插入图片描述
新变量v对应物理上的速度。式(6.3)表示了物体在梯度方向上受力,在这个力的作用下,物体的速度增加这一物理法则。
在物体不受任何力时,阿尔法*v这一项承担使物体逐渐减速的任务(阿尔法值设定0.9之类的值),这一项对应物理上的地面摩擦或空气阻力。
Momentum代码实现:

import numpy as np
class Momentum:
    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():  #key:W、b   val对应的参数数组
                self.v[key] = np.zeros_like(val)    #目的是构建一个与val同维度的数组,并初始化所有变量为零。

        for key in params.keys():
            #根据梯度grads更新v 再更新权重参数
            self.v[key] = self.momentum * self.v[key] - self.lr * grads[key]   
            params[key] += self.v[key]

图6-5中,更新路径就像小球在碗中滚动一样。和SGD相比,我们发现“之”字形的“程度”减轻了,可以更快地朝x轴方向靠近,减弱“之”字形的变动程度。对于小球的方向,受x轴力与y轴方向的正或反向力影响。
在这里插入图片描述
4 AdaGrad
学习率衰减法:即随着学习的进行,使学习率逐渐减小。也就是说,一开始“多学”,然后逐渐“少”学的方法。逐渐减小学习率的想法,相当于将“全体”参数的学习率值一起降低。AdaGrad进一步发展了这个想法,为参数的每一个元素适当地调整学习率,同时进行学习。用数学式表示AdaGrad的更新方法:
在这里插入图片描述
变量h保存了以前的所有梯度值的平方和(6.5式中符号表示对应矩阵元素的乘法)。6.6式表示,更新参数,通过乘以1/更号h,调整学习的尺度。这意味着,参数的元素中变动比较大(被大幅更新)的元素的学习率将变小。也就是说,可以按参数的元素进行学习率衰减,使变动大的参数的学习率逐渐减小。
AdaGrad代码实现:

class 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]	#式6-5
           params[key] -= self.lr * grads[key] / (np.sqrt(self.h[key]) + 1e-7)  #式6-6  1e-7防止当self.h[key]中有0时,将0用作除数的情况

由图6-6的结果所示,函数的取值高效地向着最小值移动。刚开始y轴方向上的梯度较大,所以变动较大,但是在后面会根据这个较大的变动按比例进行调整(不需要纠结比例问题),减小更新的步伐。因此,y轴方向上的更新程度被减弱,“之”字形的变动程度有所衰减。
在这里插入图片描述
5 Adam
Adam方法的基本思路:将Momentum参照小球在碗中滚动的物理规则进行移动和AdaGrad为参数的每一个元素适当地调整更新步伐,这两种方法相结合起来。
用数学式表示Adam如下:
请添加图片描述
Adam代码实现以及参数说明:

class Adam:
    def __init__(self,lr = 0.001,beta1 = 0.9,beta2 = 0.999):
        self.lr = lr	#初始化学习率
        #beta1、beta2控制移动平均的指数衰减率
        self.beta1 = beta1
        self.beta2 = beta2
        self.iter = 0   #初始化步长
        #m设定为第一个矩阵,v设定为第二个矩阵
        self.m = None
        self.v = None
    def update(self,params,grads):
        if self.m is None:
            self.m,self.v = {},{}
            #构建一个key(W1/W2/b1/b2)对应的与val同维度的数组,并初始化所有变量为零。
            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] += (1.0 - self.beta1) * (grads[key] - self.m[key])
            #(计算偏差校正的第二原始矩估计)
            self.v[key] += (1.0 - self.beta2) * (grads[key]**2 - self.v[key])
            
            #进行参数更新
            params[key] -= lr_t * self.m[key] / (np.sqrt(self.v[key]) + 1e-7)

权重的初始值
关于权重的初始值的设定关系到神经网络的学习能否成功。
权值衰减:就是一种以减少权重参数的值为目的进行学习的方法。通过减小权重参数的值来抑制过拟合的发生。
如果想减小权重的值,一开始就应该将初始值设为较小的值,但是不能设为0,否则就无法进行学习,也就是说不能将权重初始值设定为一样的值。否则在误差反向传播中,所有的权重参数将会进行一样的更新,而权重参数不能均一化。

引入梯度消失概念:以激活函数sigmoid函数为例。
神经网络主要的训练方法是BP算法,BP算法的基础是导数的链式法则,也就是多个导数的乘积。sigmoid的导数最大为0.25,且大部分的激活值(激活函数的输出数据)都被推向两侧饱和区域(0和1区域),这就导致大部分数值经过sigmoid激活函数之后,其导数都非常的小,多个小于等于0.25的数值相乘,其运算结果很小。随着神经网络的层数的加深,最后更新浅层网络(前面的层)权重参数就基本不会有什么波动,也就没有将loss的信息传递到浅层网络,这样网络就无法训练学习。这样称之为梯度消失。

隐藏层的激活值的分布
以下将以sigmoid为激活函数,用不同的初始化权重参数查看激活值的分布。
实验以五层神经网络为例,代码如下:

import numpy as np
import matplotlib.pyplot as plt


def sigmoid(x):
    return 1 / (1 + np.exp(-x))


def ReLU(x):
    return np.maximum(0, x)


def tanh(x):
    return np.tanh(x)
    
input_data = np.random.randn(1000, 100)  # 1000个数据 x
node_num = 100  # 各隐藏层的节点(神经元)数
hidden_layer_size = 5  # 隐藏层有5层
activations = {}  # 激活值的结果保存在这里

x = input_data

for i in range(hidden_layer_size):
    if i != 0:
        x = activations[i-1]

    # 改变初始值进行实验!
    w = np.random.randn(node_num, node_num) * 1 #
    # w = np.random.randn(node_num, node_num) * 0.01
    # w = np.random.randn(node_num, node_num) * np.sqrt(1.0 / node_num)
    # w = np.random.randn(node_num, node_num) * np.sqrt(2.0 / node_num)
    # print('w',w)    #输出5个w权重参数矩阵 100*100

    a = np.dot(x, w)


    # 将激活函数的种类也改变,来进行实验!
    z = sigmoid(a)
    # z = ReLU(a)
    # z = tanh(a)

    activations[i] = z

# 绘制直方图
for i, a in activations.items():
    #plt.subplot(nrows,ncols,index,**kwargs)
    #参数说明:前面三个整数用于描述子图的位置信息,分别是行数、列数、索引值,子图将分布在行列的索引值位置上。
    plt.subplot(1, len(activations), i+1)
    plt.title(str(i+1) + "-layer")  #plt.title()函数用于设置图像标题
    if i != 0: plt.yticks([], [])   #设置x或y轴对应显示的标签
    # plt.xlim(0.1, 1)
    # plt.ylim(0, 7000)

    # 将一个大区间划分为等间隔的小区间,并统计每个区间上样本出现的频数之和。
    #plt.hist(x,bins,range,...)
    # 参数说明:x作直方图所用的数据,必须是一维数组;多维数组需要进行偏平化再作图;必选参数
    #bins直方图的柱数,即要分的组数,默认为10
    #range 元组类型或None,剔除较大和较小的离群值,如果None,则默认为(x.min(),x.max());即x轴的范围
    plt.hist(a.flatten(), 30,range=(0,1))
plt.show()

使用不同标准差的高斯分布和Xavier初始值作为权重参数时,各层激活值的分布结果:
使用标准差为1的高斯分布
在这里插入图片描述
使用标准差为0.01的高斯分布作为权重参数
在这里插入图片描述
使用Xavier初始值作为权重初始值
在这里插入图片描述
结果分析:Xavier初始值是以激活函数是线性函数为前提而推导出来的。因为sigmoid 函数和 tanh 函数左右对称,且中央附近可以视作线性函数,所以适合使用Xavier初始值。
ReLU的权重初始值
当激活函数使用ReLU时,一般推荐使用ReLU专用的初始值,也称“He初始值”。当前一层的节点数为n时,He初始值使用标准差为根号下2/n的高斯分布。如图可知,当初始值为He初始值时,各层中分布的广度相同,由于即使层加深,数据的广度也能保持不变,因此逆向传播时,也会传递合适的值。
在这里插入图片描述
总结:当激活函数使用ReLU时,权重初始值使用He初始值,当激活函数是sigmoid或tanh等S型曲线函数时,初始值使用Xavier初始值。

基于MNIST数据集的权重初始值的比较
此处涉及全连接的多层神经网络,将会专门写一篇文章来概述,并且测试不同激活函数与不同权重初始值的关联影响。
此处主要是了解不同权重初始值在使用ReLU作为激活函数时对学习的影响。也算验证当激活函数使用ReLU时,权重初始值使用He初始值的实验。
在这里插入图片描述
横轴是学习的迭代次数,纵轴是损失函数的值。
这个实验室基于5层神经网络,每层有100个神经元,激活函数使用ReLU。当std为0.01时,完全无法学习,使用Xavier和He初始值时,学习进行的很顺利。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

打瞌睡的猫.

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值