《深度学习》——深度学习基础知识(全连接神经网络)
文章目录
- 1.神经网络简介
- 2.什么是神经网络
- 3.神经元是如何工作的
- 3.1激活函数
- 3.2参数的初始化
- 3.2.1随机初始化
- 3.2.2标准初始化
- 3.2.3Xavier初始化(tf.keras中默认使用的)
- 3.2.4He初始化
- 4.神经网络的搭建
- 4.1通过Sequential构建神经网络
- 4.2通过Functional API构建神经网络(函数式编程)
- 4.3通过model的子类来构建神经网络
- 5.神经网络的优缺点
- 6.损失函数
- 6.1分类任务中的损失函数
- 6.1.1多分类任务(交叉熵损失)
- 6.1.2二分类任务(交叉熵损失)
- 6.2回归任务中的损失函数
- 6.2.1MAE损失
- 6.2.2MSE损失
- 6.2.3smooth L1损失
- 7.神经网络的优化算法
- 7.1梯度下降算法
- 7.2反向传播算法(BP算法)
- 7.2.1前向传播与反向传播
- 7.2.2链式法则
- 7.2.3反向传播算法
- 7.3梯度下降优化方法
- 7.3.1动量算法(Momentum,解决鞍点问题)
- 7.3.2AdaGrad(对学习率进行修正)
- 7.3.2RMSprop
- 7.3.4Adam(重点常用,容易造成梯度爆炸)
- 7.4学习率退火
- 7.4.1分段常数衰减
- 7.4.2指数衰减
- 7.4.3 1/t衰减
- 8.深度学习的正则化
- 8.1 L1和L2正则化
- 8.2Dropout正则化(随机失活)
- 8.3提前停止正则化
- 8.4批标准化(BN层)正则化
学习目标:
- 知道深度学习与机器学习的关系、
- 知道神经网络是什么
- 知道常见的激活函数
- 知道参数初始化的常见方法
- 能够利用tf.keras构建神经网络模型
- 了解神经网络的优缺点
1.神经网络简介
在介绍神经网络之前,我们先看下面这幅图:人工智能>机器学习>深度学习
深度学习是机器学习的⼀个⼦集,也就是说深度学习是实现机器学习的⼀
种⽅法。与机器学习算法的主要区别如下图所示:
传统机器学习依赖于人工设计特征,并进行特征提取,而深度学习算法不需要人工,而是依赖算法自动提取特征,这也是深度学习被看做黑盒子、可解释性差的原因。
随着计算机硬件的飞速发展,现阶段通过拥有众多层数神经网络来模拟人脑解释数据、处理图像,包括图像、文本、音频等内容,目前来看常见的神经网络包括:
- 卷积神经网络(CNN)
- 循环神经网络(RNN)
- 生成对抗网络(GAN)
- 深度强化网络(DRN)
2.什么是神经网络
人工神经网络,也简称为神经网络,是一种模拟生物神经网络结构和功能的计算模型。人脑可以看作是一个生物神经网络,有众多的神经元连接而成,各个神经元传递复杂的电信号,树突接收到信号,然后对信号进行处理,通过轴突输出信号,下面是生物神经网络示意图:
但其实本质上神经网络与生物学没有一点关系,只是借用这个好理解一下。
那么我们该如何构建神经网络中的神经元呢?
受神经元的启发,人工神经网络接受来自其他神经元或外部源的输入,每个输入都有一个相关的权重(w),它是根据输入对当前神经元的重要性来确认的,对该输入加权并与其他输入求和后,经过一个激活函数f,计算得到该神经元的输出。
那么,接下俩我们就利用神经元来构建神经网络,相近层之间的神经元相互连接,并给每一个链接分配一个强度,如下图所示:
神经网络中信息只像一个方向移动,即从输入节点向前移动,通过隐藏节点,在想输出节点移动,网络中没有循环或者环,其基本的结构为:
- 输入层:即输入x的那一层
- 输出层:即输出y的那一层
- 隐藏层:输入层与输出层之间的层都是隐藏层
特点是:
- 同一层的神经元之间没有连接
- 第N层的每个神经元和第N-1的所有神经元相连(即全连接),第N-1层神经元的输出就是第N层神经元的输入
- 每一个连接都有权重
3.神经元是如何工作的
人工神经元接收到一个或多个输入,对他们进行加权求和,总和通过一个非线性函数产生输出。
所有的输入xi,与相应的权重wi相乘并求和:
将求和结果送到激活函数中,得到最终的输出结果:
f(x)为激活函数
3.1激活函数
在神经元中引入了激活函数,它的本质是向神经网络中引⼊非线性因素
的,通过激活函数,神经网络就可以拟合各种曲线。如果不用激活函数,每一层输出都是上层输⼊的线性函数,无论神经网络有多少层,输出都是输⼊的线性组合,引⼊⾮线性函数作为激活函数,那输出不再是输⼊的线性组合,可以逼近任意函数。常⽤的激活函数有:
1.sigmoid函数
数学表达式为:
曲线图像为:
sigmoid 在定义域内处处可导,且两侧导数逐渐趋近于0。 如果X的值很⼤或者很⼩的时候,那么函数的梯度(函数的斜率)会⾮常⼩,在反向传播的过程中,导致了向低层传递的梯度也变得⾮常⼩。此时,⽹络参数很难得到有效训练。这种现象被称为梯度消失。 ⼀般来说, sigmoid ⽹络在 5层之内就会产⽣梯度消失现象。⽽且,该激活函数并不是以0为中⼼的,所以在实践中这种激活函数使⽤的很少。sigmoid函数⼀般只⽤于⼆分类的输出层。因为他是在(0,1)之间的。
实现方法:
import tensorflow as tf
import tensorflow.keras as keras
import matplotlib.pyplot as plt
import numpy as np
x = np.linspace(-10, 10, 100)
y = tf.nn.sigmoid(x)
plt.plot(x, y)
plt.grid() # 有网格显示
输出结果为:
2.tanh(双曲正切曲线)
数学表达式为:
曲线如下图:
tanh也是⼀种⾮常常⻅的激活函数。与sigmoid相⽐,它是以0为中⼼的,使得其收敛速度要⽐sigmoid快,减少迭代次数。然⽽,从图中可以看出,tanh两侧的导数也为0,同样会造成梯度消失。
若使⽤时可在隐藏层使⽤tanh函数,在输出层使⽤sigmoid函数。
实现方法为:
import tensorflow as tf
import matplotlib.pylab as plt
import numpy as np
x = np.linspace(-10, 10, 100)
y = tf.nn.tanh(x, y)
plt.plot(x, y)
plt.grid()
3.relu函数
数学表达式为:
曲线如下图所示:
ReLU是⽬前最常⽤的激活函数。 从图中可以看到,当x<0时,relu导数为0,⽽当x>0时,则不存在饱和问题。所以,ReLU 能够在x>0时保持梯度不衰减,从⽽缓解梯度消失问题。然⽽,随着训练的推进,部分输⼊会落⼊⼩于0区域,导致对应权重⽆法更新。这种现象被称为“神经元死亡”。
与sigmoid相⽐,RELU的优势是:
- 采⽤sigmoid函数,计算量⼤(指数运算),反向传播求误差梯度时,求导涉及除法,计算量相对⼤,⽽采⽤Relu激活函数,整个过程的计算量节省很多。
- sigmoid函数反向传播时,很容易就会出现梯度消失的情况,从⽽⽆法完成深层⽹络的训练。
- Relu会使⼀部分神经元的输出为0,这样就造成了⽹络的稀疏性,并且减少了参数的相互依存关系,缓解了过拟合问题的发⽣。
实现方法为:
import tensorflow as tf
import matplotlib.pyplot as plt
import numpy as np
x = np.linspace(-10, 10, 100)
y = tf.nn.relu(x)
plt.plot(x, y)
plt.grid()
4.leaky_relu函数
它是relu的改进,数学表达式为:
曲线如下图:
leaky_relu解决了当x<10时,神经元消失的情况
实现方法为:
import tensorflow as tf
import matplotlib.pyplot as plt
import numpy as np
x = np.linspace(-10, 10, 100)
y = tf.nn.leaky_relu(x)
plt.plot(x, y)
plt.grid()
5.softmax函数
softmax⽤于多分类过程中,它是⼆分类函数sigmoid在多分类上的推⼴,⽬的是将多分类的结果以概率的形式展现出来。
计算⽅法如下图所示:
使用方法:
softmax直⽩来说就是将⽹络输出的logits通过softmax函数,就映射成
为(0,1)的值,⽽这些值的累和为1(满⾜概率的性质),那么我们将它理解成概率,选取概率最⼤(也就是值对应最⼤的)接点,作为我们的预测⽬标类别。
示例:
import tensorflow as tf
import matplotlib.pyplot as plt
import numpy as np
x = tf.constant([0.2, 0.02, 0.15, 0.15, 1.3, 0.5, 0.06, 1.1, 0.05, 3.75])
y = tf.nn.softmax(x)
print(y)
输出结果为:
我们可以得到9的概率最大,那么这个数字就被识别成了9。
那么,我们如何选择激活函数呢?
1.隐藏层:
- 优先选择relu函数
- 如果relu函数效果不好,可以尝试一下leaky_relu等
- 如果使用了relu,需要注意一下神经元死亡问题,避免出现过多的神经元死亡
- 不要使用sigmoid函数,可以尝试使用tanh函数
2.输出层
- 二分类问题选择sigmoid激活函数
- 多分类问题选择softmax激活函数
- 回归问题选择identity激活函数
3.2参数的初始化
在深度学习中,神经网络的权重初始化方法对模型的收敛速度和性能有着至关重要的影响。说白了,神经网络其实就是对权重参数w 的不停迭代更新,以达到更好的性能。因此,对权重w 的初始化则显得至关重要,一个好的权重初始化虽然不能完全解决梯度消失和梯度爆炸的问题,但是对于处理这两个问题是有很大的帮助的,并且十分有利于模型性能和收敛速度。
对于某⼀个神经元来说,需要初始化的参数有两类:⼀类是权重W,还有⼀类是偏置b,偏置b初始化为0即可。⽽权重W的初始化⽐较重要,我们着重来介绍常⻅的初始化⽅式。
3.2.1随机初始化
随机初始化从均值为0,标准差为1的高斯分布中取样,使用一些很小的值对参数w进行初始化
3.2.2标准初始化
权重参数初始化从区间均匀随机取值,即在(-1/√d,1/√d)均匀分布中生成当前神经元的权重,其中d为每个神经元的输入数量。
3.2.3Xavier初始化(tf.keras中默认使用的)
网络训练的过程中, 容易出现梯度消失(梯度特别的接近0)和梯度爆炸(梯度特别的大)的情况,导致大部分反向传播得到的梯度不起作用或者起反作用. 研究人员希望能够有一种好的权重初始化方法: 让网络前向传播或者反向传播的时候, 卷积的输出和前传的梯度比较稳定. 合理的方差既保证了数值一定的不同, 又保证了数值一定的稳定.(通过卷积权重的合理初始化, 让计算过程中的数值分布稳定)
Xavier初始化也称为Glorot初始化,因为发明人为Xavier Glorot。Xavier initialization是 Glorot 等人为了解决随机初始化的问题提出来的另一种初始化方法,他们的思想就是尽可能的让输入和输出服从相同的分布,这样就能够避免后面层的激活函数的输出值趋向于0。
因为权重多使用高斯或均匀分布初始化,而两者不会有太大区别,只要保证两者的方差一样就可以了,所以高斯和均匀分布我们一起说。
1.正态化Xavier初始化(正态分布):Glorot 正态分布初始化器,也称为 Xavier 正态分布初始化器。以0为中心,标准差为stddev = sqrt(2 / (fan_in + fan_out))
的正态分布中抽取样本,其中fan_in是输入神经元的个数,fan_out是输出神经元的个数。
实现方法如下:
import tensorflow as tf
# 实例化
initailizer = tf.keras.initializers.glorot_normal()
values = initailizer((9, 1))
print(values)
2.标准化Xavier初始化(均匀分布):Glorot 均匀分布初始化器,也称为 Xavier 均匀分布初始化器。他是从[-limit, limit]中的均匀分布中抽取样本,其中limit是sqrt(6 / (fan_in + fan_out))
其中 fan_in 是输⼊神经元的个数,fan_out 是输出的神经元个数。
import tensorflow as tf
# 实例化
initailizer = tf.keras.initializers.glorot_uniform()
values = initailizer((9, 1))
print(values)
3.2.4He初始化
he初始化,也称为Kaiming初始化,出⾃⼤神何恺明之⼿,它的基本思想是正向传播时,激活值的⽅差保持不变;反向传播时,关于状态值的梯度的⽅差保持不变。在tf.keras中也有两种
1.正态化的He初始化(正态分布):He 正态分布初始化是以 0 为中⼼,标准差为stddev = sqrt(2 / fan_in)
的截断正态分布中抽取样本, 其中 fan_in 是输⼊神经元的个数,在tf.keras中的实现⽅法为:
import tensorflow as tf
# 实例化
initailizer = tf.keras.initializers.he_normal()
# 采样得到权重
values = initailizer((9, 1))
print(values)
2.标准化的He初始化(均匀分布):He 均匀⽅差缩放初始化器。它从 [-limit,limit] 中的均匀分布中抽取样本, 其中limit是sqrt(6 / fan_in)
其中 fan_in 输⼊神经元的个数。实现为:
import tensorflow as tf
# 实例化
initailizer = tf.keras.initializers.he_uniform()
# 采样得到权重
values = initailizer((9, 1))
print(values)
4.神经网络的搭建
接下来我们来搭建如下图所示的神经网络模型:
tf.Keras中构建模有两种⽅式,⼀种是通过Sequential构建,⼀种是通过Model类构建。前者是按⼀定的顺序对层进⾏堆叠,⽽后者可以⽤来构建较复杂的⽹络模型。
⾸先我们介绍下⽤来构建⽹络的全连接层:
tf.keras.layers.Dense(
units, activation=None, use_bias=True, kernel_initializ='glorot_uniform'
bias_initializer='zeros')
主要参数解释:
- units: 当前层中包含的神经元个数
- Activation: 激活函数,relu,sigmoid等
- use_bias: 是否使⽤偏置,默认使⽤偏置
- Kernel_initializer: 权重的初始化⽅式,默认是Xavier初始化
- bias_initializer: 偏置的初始化⽅式,默认为0
4.1通过Sequential构建神经网络
Sequential() 提供⼀个层的列表,就能快速地建⽴⼀个神经⽹络模型,实现⽅法如下所示:(我们都是通过如上图的网络)
import tensorflow as tf
model = tf.keras.Sequential(
[
#第一个隐藏层,
tf.keras.layers.Dense(3, activation="relu", kernel_initializer="he_normal", name="layer1", input_shape=(3,)),
#第二个隐藏层,
tf.keras.layers.Dense(2, activation="relu", kernel_initializer="he_normal", name="layer2"),
#输出层
tf.keras.layers.Dense(2, activation="sigmoid", kernel_initializer="he_normal", name="layer3")
],
name="sequential" #把我们构建的模型重新起一个名字
)
model.summary() #查看模型的架构
通过这种sequential的⽅式只能构建简单的序列模型,较复杂的模型没有办法实现。就是这一层的输出只能作为下一层的输入,每一层的输出只有一个,不能有多个。即单输入单输出的网络。
4.2通过Functional API构建神经网络(函数式编程)
tf.keras 提供了 Functional API,建⽴更为复杂的模型,使⽤⽅法是将层作为可调⽤的对象并返回张量,并将输⼊向量和输出向量提供给tf.keras.Model 的 inputs 和 outputs 参数,实现⽅法如下:(还是以刚才的网络为例子)
import tensorflow as tf
#定义模型的输入
inputs = tf.keras.Input(shape=(3,), name="input")
#第一个隐藏层
x = tf.keras.layers.Dense(3, activation="relu", name="layer1")(inputs) #都使用默认的初始化方式
#第二个隐藏层
x = tf.keras.layers.Dense(2, activation="relu", name="layer2")(x)
#输出层
outputs = tf.keras.layers.Dense(2, activation="sigmoid", name="output")(x)
#构建模型
model = tf.keras.Model(inputs=inputs, outputs=outputs, name="functional API")
model.summary()
4.3通过model的子类来构建神经网络
通过model的⼦类构建模型,此时需要在init中定义神经⽹络的层,在call⽅法中定义⽹络的前向传播过程,实现⽅法如下:
import tensorflow as tf
#定义一个model的子类
class MyModel(tf.keras.Model):
#在__init__中定义网络的层结构
def __init__(self):
super(MyModel, self).__init__()
#第一个隐藏层
self.layer1 = tf.keras.layers.Dense(3, activation="relu", name="layer1")
#第二个隐藏层
self.layer2 = tf.keras.layers.Dense(2, activation="relu", name="layer2")
#输出层
self.layer3 = tf.keras.layers.Dense(2, activation="sigmoid", name="layers")
#在call中定义网络的前向传播
def call(self, inputs):
x = self.layer1(inputs)
x = self.layer2(x)
outputs = self.layer3(x)
return outputs
#实例化这个模型
mdoel = MyModel()
#设置一个输入
x = tf.ones((1, 3))
y = model(x)
model.summary() #没有初始化输入的话则不可以展示模型结构
5.神经网络的优缺点
优点:
- 精度⾼,性能优于其他的机器学习⽅法,甚⾄在某些领域超过了⼈类
- 可以近似任意的⾮线性函数
- 随之计算机硬件的发展,近年来在学界和业界受到了热捧,有⼤量的
框架和库可供调⽤
缺点:
- ⿊箱,很难解释模型是怎么⼯作的
- 训练时间⻓,需要⼤量的计算⼒
- ⽹络结构复杂,需要调整超参数
- ⼩数据集上表现不佳,容易发⽣过拟合
6.损失函数
在深度学习中,损失函数是用来衡量模型参数的质量的函数,衡量的方式是比较网络输出与真实值输出的差异。
6.1分类任务中的损失函数
6.1.1多分类任务(交叉熵损失)
在多分类任务通常使⽤softmax将logits转换为概率的形式,所以多分类的交叉熵损失也叫做softmax损失,它的计算⽅法是:
其中,y是样本x属于某⼀个类别的真实概率,⽽f(x)是样本属于某⼀类别的预测分数,S是softmax函数,L⽤来衡量p,q之间差异性的损失结果。
示例:
从概率⻆度理解,我们的⽬的是最⼩化正确类别所对应的预测概率的对数的负值,如下图所示:
Q:为什么在log函数前加负号?
A:我们知道log的数学函数图像,我们只需要(0, 1)之间的图像,当我们取得的概率值越大时,如果损失函数log前没有负号时,那么反而他的损失值是越大的,与我们的预期想法是不同的,那么我们只需要在log函数前加一个负号,就会使得取得的概率值越大时,损失值反而越小。
示例:
6.1.2二分类任务(交叉熵损失)
在处理⼆分类任务时,我们不在使⽤softmax激活函数,⽽是使sigmoid激活函数,那损失函数也相应的进⾏调整,使⽤⼆分类的交叉熵损失函
数:
其中,y是样本x属于某⼀个类别的真实概率,⽽y^是样本属于某⼀类别的预测概率,L⽤来衡量真实值与预测值之间差异性的损失结果。
示例:
6.2回归任务中的损失函数
6.2.1MAE损失
Mean absolute loss(MAE)也被称为L1 Loss,是以绝对误差作为距离:
其中,yi为真实值,f(xi)为预测值。
曲线如下图所示:
特点是:由于L1 loss具有稀疏性,为了惩罚较⼤的值,因此常常将其作为正则项添加到其他loss中作为约束。L1 loss的最⼤问题是梯度在零点不平滑,导致会跳过极⼩值
示例:
6.2.2MSE损失
Mean Squared Loss/ Quadratic Loss(MSE loss)也被称为L2 loss,或欧⽒距离,它以误差的平⽅和作为距离:
其中,yi为真实值,f(xi)为预测值。
曲线如下图所示:
特点是:L2 loss也常常作为正则项。当预测值与⽬标值相差很⼤时, 梯度容易爆炸。
示例:
6.2.3smooth L1损失
Smooth L1损失函数如下式所示:
从上图中可以看出,该函数实际上就是⼀个分段函数,在[-1,1]之间实际上就是L2损失,这样解决了L1的不光滑问题,在[-1,1]区间外,实际上就是L1损失,这样就解决了离群点梯度爆炸的问题。通常在⽬标检测中使⽤该损失函数。
示例:
7.神经网络的优化算法
7.1梯度下降算法
我们在机器学习的过程中,已经了解到了梯度下降算法,我们简单回顾一下。从数学上的⻆度来看,梯度的⽅向是函数增⻓速度最快的⽅向,那么梯度的反⽅向就是函数减少最快的⽅向,所以有:
其中,η是学习率,如果学习率太⼩,那么每次训练之后得到的效果都太⼩,增⼤训练的时间成本。如果,学习率太⼤,那就有可能直接跳过最优解,进⼊⽆限的训练中。解决的⽅法就是,学习率也需要随着训练的进⾏⽽变化。
在上图中我们展示了⼀维和多维的损失函数,损失函数呈碗状。在训练过程中损失函数对权重的偏导数就是损失函数在该位置点的梯度。我们可以看到,沿着负梯度⽅向移动,就可以到达损失函数底部,从⽽使损失函数最⼩化。这种利⽤损失函数的梯度迭代地寻找局部最⼩值的过程就是梯度下降的过程。
根据在进⾏迭代时使⽤的样本量,将梯度下降算法分为以下三类:
实际中使⽤较多的是⼩批量的梯度下降算法,tf.keras中通过以下⽅法实现:
tf.keras.optimizers.SGD(
learning_rate=0.01, momentum=0.0, nesterov=False, name='SGD'
)
其中,learning_rate为学习率,其他的可以先不用了解。虽然上面写的SGD,但实际是用的原理是MBGD。
示例:
上面使用Variable是因为他不可以改变形状,但可以改变值的内容,因为之后我们会修改var的值,在之后都是使用Variable来创建变量。
在进⾏模型训练时,有三个基础的概念:
实际上,梯度下降的⼏种⽅式的根本区别就在于 Batch Size不同,,如下表所示:
注意:上表中 Mini-Batch 的 Batch 个数为 N / B + 1 是针对未整除的情况。整除则是 N / B。(即上取整)
示例:假设数据集有 50000 个训练样本,现在选择 Batch Size = 256 对模型进⾏训练。
- 每个 Epoch 要训练的图⽚数量:50000
- 训练集具有的 Batch 个数:50000/256+1=196
- 每个 Epoch 具有的 Iteration 个数:196
- 10个 Epoch 具有的 Iteration 个数:1960
7.2反向传播算法(BP算法)
利⽤反向传播算法对神经⽹络进⾏训练。该⽅法与梯度下降算法相结合,对⽹络中所有权重计算损失函数的梯度,并利⽤梯度值来更新权值以最⼩化损失函数。在介绍BP算法前,我们先看下前向传播与链式法则的内
容。
7.2.1前向传播与反向传播
前向传播指的是数据输⼊的神经⽹络中,逐层向前传输,⼀直到运算到输出层为⽌。
在⽹络的训练过程中经过前向传播后得到的最终结果跟训练样本的真实值总是存在⼀定误差,这个误差便是损失函数。想要减⼩这个误差,就⽤损失函数ERROR,从后往前,依次求各个参数的偏导,这就是反向传播(Back Propagation)。在介绍反向传播算法前,我们必须先要了解链式法则。
7.2.2链式法则
就是我们学的链式法则求导过程。
反向传播算法是利⽤链式法则进⾏梯度求解及权重更新的。对于复杂的复合函数,我们将其拆分为⼀系列的加减乘除或指数,对数,三⻆函数等初等函数,通过链式法则完成复合函数的求导。为简单起⻅,这⾥以⼀个神经⽹络中常⻅的复合函数的例⼦来说明 这个过程. 令复合函数 𝑓(𝑥; 𝑤, 𝑏)为:
其中x是输⼊数据,w是权重,b是偏置。我们可以将该复合函数分解为:
我们用图像表示,如下:
整个复合函数 𝑓(𝑥; 𝑤, 𝑏) 关于参数 𝑤 和 𝑏 的导数可以通过 𝑓(𝑥; 𝑤, 𝑏) 与参数 𝑤 和 𝑏 之间路径上所有的导数连乘来得到,即:
以w为例,当 𝑥 = 1, 𝑤 = 0, 𝑏 = 0 时,可以得到:
7.2.3反向传播算法
反向传播算法利⽤链式法则对神经⽹络中的各个节点的权重进⾏更新。我们通过⼀个例⼦来给⼤家介绍整个流程,假设当前前向传播的过程如下图:
计算损失函数,并进⾏反向传播:
计算梯度值:
输出梯度值:
隐藏层梯度值:
偏置的梯度值:
接下来就是参数的更新了:
1.输出层权重:
2.隐藏层权重:
3.偏置更新
重复上述过程完成模型的训练,整个流程如下表所示:
我们来举个例子:
如下图是⼀个简单的神经⽹络⽤来举例:激活函数为sigmoid
前向传播运算:
接下来我们进行反向传播过程:
我们先来求最简单的,求误差E对w5的导数。⾸先明确这是⼀个“链式法则”的求导过程,要求误差E对w5的导数,需要先求误差E对out o1的导数,再求out o1对net o1的导数,最后再求net o1对w5的导数,经过这个链式法则,我们就可以求出误差E对w5的导数(偏导),如下图所示:
导数(梯度)已经计算出来了,下⾯就是反向传播与参数更新过程:
如果要想求误差E对w1的导数,误差E对w1的求导路径不⽌⼀条,这会稍微复杂⼀点,但换汤不换药,计算过程如下所示:
还是很复杂的,但是我们是不需要进行计算的,计算机会帮我算出来,这就是深度学习难解释的原因。
7.3梯度下降优化方法
梯度下降算法在进⾏⽹络训练时,会遇到鞍点,局部极⼩值这些问题,那我们怎么改进SGD呢?在这⾥我们介绍⼏个⽐较常⽤的
7.3.1动量算法(Momentum,解决鞍点问题)
动量算法主要解决鞍点问题。在介绍动量法之前,我们先来看下指数加权平均数的计算⽅法。
指数加权平均:
假设给定⼀个序列,例如北京⼀年每天的⽓温值,图中蓝⾊的点代表真实数据
这时温度值波动⽐较⼤,那我们就使⽤加权平均值来进⾏平滑,如下图红线就是平滑后的结果:
计算方法如下图所示:
其中Yt为 t 时刻时的真实值,St为t加权平均后的值,β为权重值。红线即是指数加权平均后的结果。
上图中β设为0.9,那么指数加权平均的计算结果为:
那么第100天的结果就可以表示为:
动量梯度下降算法:
动量梯度下降(Gradient Descent with Momentum)计算梯度的指数加权平均数,并利⽤该值来更新参数值。动量梯度下降法的整个过程为,其中β通常设置为0.9:
与原始的梯度下降算法相⽐,它的下降趋势更平滑。
在tf.keras中使⽤Momentum算法仍使⽤功能SGD⽅法,但要设置momentum参数,实现过程如下:
import tensorflow as tf
#实例化
opt = tf.keras.optimizers.SGD(learning_rate=0.1, momentum=0.9)
#定义要更新的参数
var = tf.Variable(1.0)
val0 = var.value()
#定义损失函数
loss = lambda:(var**2)/2.0
#第一次更新结果
opt.minimize(loss, [var]).numpy()
val1 = var.value()
#第二次更新结果
opt.minimize(loss, [var]).numpy()
val2 = var.value()
print(val1.numpy())
print(val2.numpy())
print("第⼀次更新步⻓={}".format((val0 - val1).numpy()))
print("第⼆次更新步⻓={}".format((val1 - val2).numpy()))
另外还有⼀种动量算法Nesterov accelerated gradient(NAG),使⽤了根据动量项预先估计的参数,在Momentum的基础上进⼀步加快收敛,提⾼响应性,该算法实现依然使⽤SGD⽅法,要设置nesterov设置为true
7.3.2AdaGrad(对学习率进行修正)
在一般的优化算法中,目标函数自变量的每一个元素在相同时间步都使用同一个学习率来自我迭代。
例如,假设目标函数为f ,自变量为一个二维向量[ x 1 , x 2 ] ⊤ ,该向量中每一个元素在迭代时都使用相同的学习率。
例如,在学习率为η 的梯度下降中,元素x1 和x2都使用相同的学习率η \etaη来自我迭代:
在(动量法)里我们看到当x1和x2 的梯度值有较大差别时,需要选择足够小的学习率使得自变量在梯度值较大的维度上不发散。但这样会导致自变量在梯度值较小的维度上迭代过慢。动量法依赖指数加权移动平均使得自变量的更新方向更加一致,从而降低发散的可能。
AdaGrad算法会使⽤⼀个⼩批量随机梯度g_t按元素平⽅的累加变量st。在⾸次迭代时,AdaGrad将s0中每个元素初始化为0。在t次迭代,⾸先将⼩批量随机梯度gt按元素平⽅后累加到变量st:
在tf.keras中的实现方法为:
tf.keras.optimizers.Adagrad(
learning_rate=0.001, initial_accumulator_value=0.1, epsilon=1e-07
)
示例:
import tensorflow as tf
#实例化
opt = tf.keras.optimizers.Adagrad(learning_rate=0.1, initial_accumulator_value=0.1, epsilon=1e-6)
#定义要更新的参数
var = tf.Variable(1.0)
#定义损失函数
def loss():
return (var**2)/2.0
#进行梯度更新
opt.minimize(loss, [var]).numpy()
print(var.numpy())
adagrad算法是使用累加平方和修正学习率,使随着更新次数的增加,学习率减小。
7.3.2RMSprop
AdaGrad算法在迭代后期由于学习率过⼩,能较难找到最优解。为了解决这⼀问题,RMSProp算法对AdaGrad算法做了⼀点⼩⼩的修改。不同于AdaGrad算法⾥状态变量st是截⾄时间步t所有⼩批量随机梯度gt按元素平⽅和,RMSProp(Root Mean Square Prop)算法将这些梯度按元素平⽅做指数加权移动平均
其中ϵ是⼀样为了维持数值稳定⼀个常数。最终⾃变量每个元素的学习率在迭代过程中就不再⼀直降低。RMSProp 有助于减少抵达最⼩值路径上的摆动,并允许使⽤⼀个更⼤的学习率 α,从⽽加快算法学习速度。
在tf.keras中实现时,使用的方法为:
tf.keras.optimizers.RMSprop(
learning_rate=0.001, rho=0.9, momentum=0.0, epsilon=1e-07,centered=False,
name='RMSprop', **kwargs
)
rho会影响梯度下降的步幅。
示例:
import tensorflow as tf
#实例化
opt = tf.keras.optimizers.RMSprop(learning_rate=0.1, rho=0.9)
#定义要更新的参数
var = tf.Variable(1.0)
#定义损失函数
def loss():
return (var**2)/2.0
#进行梯度更新
opt.minimize(loss, [var]).numpy()
print(var.numpy())
7.3.4Adam(重点常用,容易造成梯度爆炸)
Adam 优化算法(Adaptive Moment Estimation,⾃适应矩估计)将Momentum 和 RMSProp 算法结合在⼀起。Adam算法在RMSProp算法基础上对⼩批量随机梯度也做了指数加权移动平均。
假设⽤每⼀个 mini-batch 计算 dW、db,第t次迭代时:
其中l为某一层,t为移动平均次数的值。
Adam算法的参数更新:
建议的参数设置的值:
- 学习率α:需要尝试⼀系列的值,来寻找⽐较合适的
- β1:常⽤的缺省值为 0.9
- β2:建议为 0.999
- ϵ:默认值1e-8
在tf.keras中实现的⽅法是:
tf.keras.optimizers.Adam(
learning_rate=0.001, beta_1=0.9, beta_2=0.999, epsilon=1e-7
)
示例:
import tensorflow as tf
#实例化
opt = tf.keras.optimizers.Adam(learning_rate=0.1) #其他参数使用默认即可
#定义要更新的参数
var = tf.Variable(1.0)
#定义损失函数
def loss():
return (var**2)/2.0
#进行梯度更新
opt.minimize(loss, [var]).numpy()
print(var.numpy())
7.4学习率退火
在训练神经⽹络时,⼀般情况下学习率都会随着训练⽽变化,这主要是由于,在神经⽹络训练的后期,如果学习率过⾼,会造成loss的振荡,但是如果学习率减⼩的过快,⼜会造成收敛变慢的情况
7.4.1分段常数衰减
分段常数衰减是在事先定义好的训练次数区间上,设置不同的学习率常数。刚开始学习率⼤⼀些,之后越来越⼩,区间的设置需要根据样本量调整,⼀般样本量越⼤区间间隔应该越⼩
在tf.keras中对应的⽅法是:
tf.keras.optimizers.schedules.PiecewiseConstantDecay(boundaries, values)
参数解释如下:
- Boundaries: 设置分段更新的step值
- Values: 针对不⽤分段的学习率值
示例:对于前100000步,学习率为1.0,对于接下来的100000-110000步,学习率为0.5,之后的步骤学习率为0.1
# 设置的分段的step值
boundaries = [100000, 110000]
# 不同的step对应的学习率
values = [1.0, 0.5, 0.1]
# 实例化进⾏学习的更新
learning_rate_fn = keras.optimizers.schedules.PiecewiseConstantDecay(boundaries, values)
7.4.2指数衰减
指数衰减可以⽤如下的数学公式表示:
其中,t表示迭代次数,a0和k为超参数。
在tf.keras中的实现是:
tf.keras.optimizers.schedules.ExponentialDecay(initial_learning_rate, decay_steps,decay_rate)
具体的实现是:
def decayed_learning_rate(step):
return initial_learning_rate * decay_rate ^ (step / decay_rate)
参数解释如下:
- Initial_learning_rate: 初始学习率,α0
- decay_steps: k值
- decay_rate: 指数的底
7.4.3 1/t衰减
1/t衰减可以⽤如下的数学公式表示:
其中,t表示迭代次数,a0和k是超参数。
在tf.keras中的实现是:
tf.keras.optimizers.schedules.InverseTimeDecay(initial_learning_rate,decay_rate)
具体的实现是:
def decayed_learning_rate(step):
return initial_learning_rate / (1 + decay_rate * step / decay_rate)
参数解释如下:
- Initial_learning_rate: 初始学习率,α0
- decay_step/decay_steps: k值
8.深度学习的正则化
能够缓解网络过拟合的策略就叫做正则化。
在设计机器学习算法时不仅要求在训练集上误差⼩,⽽且希望在新样本上的泛化能⼒强。许多机器学习算法都采⽤相关的策略来减⼩测试误差,这些策略被统称为正则化。因为神经⽹络的强⼤的表示能⼒经常遇到过拟合,所以需要使⽤不同形式的正则化策略。正则化通过对算法的修改来减少泛化误差,⽬前在深度学习中使⽤较多的策略有参数范数惩罚,提前终⽌,DropOut等,接下来我们对其进⾏详细的介绍。
8.1 L1和L2正则化
L1和L2是最常⻅的正则化⽅法。它们在损失函数(cost function)中增加⼀个正则项,由于添加了这个正则化项,权重矩阵的值减⼩,因为它假定具有更⼩权重矩阵的神经⽹络导致更简单的模型。 因此,它也会在⼀定程度上减少过拟合。然⽽,这个正则化项在L1和L2中是不同的。
L2正则化
这⾥的λ是正则化参数,它是⼀个需要优化的超参数。L2正则化⼜称为权重衰减,因为其导致权重趋向于0(但不全是0)
L1正则化
这⾥,我们惩罚权重矩阵的绝对值。其中,λ 为正则化参数,是超参数,不同于L2,权重值可能被减少到0。因此,L1对于压缩模型很有⽤。其它情况下,⼀般选择优先选择L2正则化。(使不太重要的权重置为0)
在tf.keras中实现使⽤的⽅法是:
1.L1正则化
tf.keras.regularizers.L1(l1=0.01)
2.L2正则化
tf.keras.regularizers.L2(l2=0.01)
3.L1 L2正则化
tf.keras.regularizers.L1L2(
l1=0.0, l2=0.0
)
示例:我们直接在某⼀层的layers中指明正则化类型和超参数即可
8.2Dropout正则化(随机失活)
dropout是在深度学习领域最常⽤的正则化技术。Dropout的原理很简单:假设我们的神经⽹络结构如下所示,在每个迭代过程中,随机选择某些节点,并且删除前向和后向连接。
因此,每个迭代过程都会有不同的节点组合,从⽽导致不同的输出,这可以看成机器学习中的集成⽅法(ensemble technique)。集成模型⼀般优于单⼀模型,因为它们可以捕获更多的随机性。相似地,dropout使得神经⽹络模型优于正常的模型。
在tf.keras中实现使⽤的⽅法是dropout:
tf.keras.layers.Dropout(rate)
rate: 每⼀个神经元被丢弃的概率
示例:
注意:一般dropout我们会放到全连接层的Dense后面,被失活的神经元输出变为0,其他的按照(1/1-rate)倍放大。dropout只在网络训练时有效,在预测时是不起作用的。
8.3提前停止正则化
提前停⽌(early stopping)是将⼀部分训练集作为验证集(validationset)。 当验证集的性能越来越差时或者性能不再提升,则⽴即停⽌对该模型的训练。 这被称为提前停⽌。
在上图中,在虚线处停⽌模型的训练,此时模型开始在训练数据上过拟合。
在tf.keras中,我们可以使⽤callbacks函数实现早期停⽌:
tf.keras.callbacks.EarlyStopping(
monitor='val_loss', patience=5
)
上⾯,monitor参数表示监测量,这⾥val_loss表示验证集损失。⽽patience参数epochs数量,当在这个过程性能⽆提升时会停⽌训练。为了更好地理解,让我们再看看上⾯的图⽚。 在虚线之后,每个epoch都会导致更⾼的验证集误差。 因此,虚线后patience个epoch,模型将停⽌训练,因为没有进⼀步的改善。
示例:
补充,在这里我们再来介绍几个名词:
- epoch:在训练一个模型时所用到的全部数据(一般在训练时都要使用多于一个的epoch,因为在神经网络中传递完整的数据集仅仅一次是不够的,只有将完整的数据集在同样的神经网络中传递多次,才会得到比较优秀的训练效果,当然也不行,容易过拟合,所以要根据实验选择自己最合适的。)
- epochs:epochs被定义为向前和向后传播中所有批次的单次训练迭代。这意味着一个周期是整个数据的单次向前和向后传递。简单说,epochs指的就是训练过程中数据将被“轮”了多少次。
- batch:Keras中参数更新是按批进行的,就是小批的梯度下降算法。如果准备跑模型的数据量太大,此时自己的电脑可能承受不住,所以可以适当的将数据分成几块,就是分成几个batch。
- batch_size:一个batch中的数据量大小即为batch_size,一般为2n,比如32,64,128等等。
- iterations(迭代):每一次迭代都是一次权重更新,每一次权重更新需要batch_size个数据进行Forward运算得到损失函数,在BP算法更新参数。1个iteration等于使用batch_size个样本训练一次。(其实对于一个epoch来说,batch和iterations数值上是相等的)
8.4批标准化(BN层)正则化
批标准化(BN层,Batch Normalization)是2015年提出的⼀种⽅法,在进⾏深度⽹络训练时,⼤多会采取这种算法,与全连接层⼀样,BN层也是属于⽹络中的⼀层。BN层是针对单个神经元进⾏,利⽤⽹络训练时⼀个 mini-batch 的数据来计算该神经元xi 的均值和⽅差,归⼀化后并重构,因⽽称为 BatchNormalization。在每⼀层输⼊之前,将数据进⾏BN,然后再送⼊后续⽹络中进⾏学习:
⾸先我们对某⼀批次的数据的神经元的输出进⾏标准化:
然后在使⽤ 变换重构 ,引⼊了可学习参数γ、β,如果各隐藏层的输⼊均值在靠近0的区域,即处于激活函数的线性区域,不利于训练⾮线性神经⽹络,从⽽得到效果较差的模型。(BN层一般用在线性层和卷积层后面,而不是放在非线性单元后)因此,需要⽤ γ 和 β 对标准化后的结果做进⼀步处理(尺度的调整和评议):
这就是BN层最后的结果。整体流程如下图所示:
在tf.keras中实现使⽤:
# 直接将其放⼊构建神经⽹络的结构中即可
tf.keras.layers.BatchNormalization(
epsilon=0.001, center=True, scale=True,
beta_initializer='zeros', gamma_initializer='ones',
)