本专栏是书《深度学习入门》的阅读笔记一共八章:
第一章深度学习中的Python基础。主要讲解了深度学习将要用到的python的基础知识以及简单介绍了numpy库和matpoltlib库,本书编写深度学习神经网络代码仅使用Python和numpy库,不使用目前流行的各种深度学习框架,适合入门新手学习理论知识。
第二章感知机。主要介绍了神经网络和深度学习的基本单元感知机。感知机接收多个输入,产生一个输出,单层感知器可以实现与门,或门以及与非门,但是不能实现异或门,异或门的实现需要借助多层感知机,这也就是说,单层感知机只能表示线性空间,而非线性空间的表示需要借助多层感知机。
第三章神经网络——基于numpy的代码详解。主要讲解了神经网络的构成,神经网络中的激活函数,神经网络中层与层的矩阵乘法,3层神经网络的代码,输出层的设计和批处理。
第四章神经网络的学习算法——随机梯度下降numpy代码详解。主要讲解了神经网络中的学习算法,介绍了损失函数,通过微分求导的方法求梯度,随机梯度下降算法的原理以及基于numpy的代码详解。
第五章误差反向传播算法——基于numpy的代码详解。这一章主要介绍了,实现误差反向传播的一种工具,计算图,从最简单的加法节点乘法节点的计算图开始,介绍了加法节点乘法节点的反向传播,一步步深入,到加法层,乘法层,激活函数层等层的反向传播,最终把各种层的反向传播拼接在一起,就形成了整个网络的反向传播。
第六章与学习相关的技巧
本章将主要介绍除了随机梯度下降以外的更新参数的方法,权重的初始值设定,解决过拟合的正则化办法,以及超参数的设置经验。
6.1更新参数的其他方法
6.1.1随机梯度下降(stochastic gradient descend SGD)的缺点
神经网络学习的目的是确定参数使得损失函数的值最小,属于参数最优化(optimization)问题,我们之前学过的更新参数的方法是随机梯度下降算法(SGD),让我们再来回顾一下SGD算法的核心思想:
代码实现为:
class SGD:
def __init__(self,lr):
self.lr=lr
def update(self,params,grads):
for key in params.keys():
params[key]-=self.lr*grads[key]
下面我们针对一个二元函数最优化问题说明SGD算法的缺点,求函数的最小值。
函数的图像为:
梯度的图像为:
利用SGD算法进行优化,参数的变化图像为:
可以看到,当函数形状非均向,比如延伸状,利用SGD算法进行搜索的效率就会很低,SGD低效的根本原因是梯度指向的方向不是最小值方向。
6.1.2Momentum
Momentum在物理学中翻译为动量,使用Momentum方法更新参数的直观理解是,把更新参数想象成小球从山顶下落的过程,v表示速度,表示原有的摩擦力,参数的梯度表示新的受力,公式如下:
代码实现为:
class Momentum:
def __init__(self,lr,momentum):
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():#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]
让我们再来看一下,用此方法解决上面函数求极值问题的参数更新路径:
6.1.3AdaGrad
在学习率设置的技巧中,有一种技巧被称作是学习率衰减,即随着学习的进行,学习率的值逐渐减小。而AdaGrad的思想进一步改善了这种思想,AdaGrad为每一个参数量身定制了一个学习率的衰减规则,让我们一起来看一下它的公式:
实现代码为:
class AdaGrad:
def __init__(self,lr):
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)+1e-7)
#加一个极小值避免出现h为0的情况
用此方法解决以上函数求极值问题,参数的更新路径如下:
6.1.4Adam
Adam方法的原理比较复杂,这里只给出它的直观解释,Adam的方法就是结合了Momentum和AdaGrad两种方法,将两种方法结合就是Adam方法,用这种方法更新参数的路径图为:
6.2权重的初始值
当激活函数为sigmoid或者tanh时,使用“Xavier初始值”,具体方法为,如果前一层的神经元数量为n,那么本层神经元的权重初始化为标准差为的高斯分布,实现代码如下:
import numpy as np
#node_num表示隐藏层神经元个数
W=np.random.randn(node_num,node_num)*np.sqrt(node_num)
当激活函数为ReLU时,一般采用“He初始值”,具体方法为,如果前一层的神经元数量为n,那么本层神经元的权重初始化为标准差为的高斯分布。
6.3Batch Normalization
上一节我们看到,使用适当的初始化权重的方法,会使各层的激活值有适当的广度,从而可以顺利的进行学习。Batch Normalization的思想就是这样,以进行学习的mini-batch为单位,对每一层神经元的输入数据进行正规化处理,即使数据具有均值为0,方差为1的正规化。公式参考如下:
这里对具有m个数据的mini-batch集合求均值
和方差
,进行均值为0方差为1的正规化。接着对数据进行进行缩放和平移处理,公式如下:
和
是超参数,初始值设为1和0,然后通过神经网络进行学习确定合适的参数。Batch Normalization的计算图如下:
使用Batch Normalization处理的好处有:
1.加快学习
2.不那么依赖参数的初始值,对初始值不是那么敏感
3.抑制过拟合
6.4正则化
6.4.1过拟合
过拟合是指,神经网络对于训练数据过于拟合,导致对测试数据拟合效果很差。见百度百科的图片:
过拟合产生的原因主要有两个:
1.模型拥有大量参数,表现力强。
2.训练数据少。
6.4.2权重衰减
权重衰减是一种抑制过拟合的有效手段,权重衰减的核心思想就是通过在学习的过程中对大的权重值进行惩罚,来达到抑制过拟合的目的。具体方法是,因为神经网络的学习目的是尽可能的减小损失函数的值,我们可以通过在损失函数中加入权重的惩罚项来达到对大的权重进行惩罚的目的,具体的惩罚措施有,L1范数,L2范数,L∞范数,实际上,我们可以从中挑选一个作为惩罚项,加入到损失函数中,这样因为神经网络会不断地减小损失函数的值,因此就会对大的权重进行抑制。
常用的是L2范数,L2范数实际上就是求平方和再开方,例如权重,那么其L2范数为
,L1范数实际上是绝对值求和,即其L1范数为
,而L∞范数实际上是求最大值,相当于各个元素中最大的一个,其L∞范数为
。
6.4.3Dropout
Dropout是另一种常用的抑制过拟合的方法,更多用于隐藏层比较多,网络结构比较复杂的网络中。Dropout通过在每次训练时,随机“删除”一部分隐藏神经元,被“删除”的神经元在本次训练中不参与数据传递。但是在测试时,每一个神经元都参数与数据传递,并且各个神经元的输出要乘以训练时删除神经元的比例。这种思想有点类似于机器学习中的集成学习,就是让多个模型单独进行学习,测试时取这几个模型输出的平均值。如图所示:
实现代码为:
class Dropout:
def __init__(self,dropout_ratio):#删除神经元的比例
self.dropout_ratio=dropout_ratio
self.mask=None
def forward(self,x,train_flag=True):
if train_flag:
self.mask=np.random.rand(*x.shape)>self.dropout_ratio
return x*self.mask
else:
return x*(1.0-self.dropout_ratio)
def backward(self,dout):
return dout*self.mask
6.5超参数(hyper-parameter)的选择
这里所指的超参数是,比如隐藏层神经元的个数,学习率,权重衰减系数等需要人工进行设置的参数,这些参数对于神经网络的学习很重要,如果能够确定合适的超参数则会使得学习过程事半功倍但是如果超参数设置的不合适,则会让学习过程变得艰难甚至无法学习。
超参数最优化的步骤:
步骤0:设定超参数的范围
步骤1:从设定的超参数范围中随机采样
步骤2:使用步骤1中采样得到的超参数进行学习,通过验证数据进行评估
步骤3:重复步骤1和步骤2缩小超参数的选择范围
例如,我们设定学习率的范围是到
,权重衰减系数的范围为
到
,随机采样的代码实现为:
weight_decay=10**np.random.uniform(-8,-4)
lr=10**np.random.uniform(-6,-2)