文章目录
1 为什么使用深层网络
对于人脸识别等应用,神经网络的第一层从原始图片中提取人脸的轮廓和边缘,每个神经元学习到不同边缘的信息;网络的第二层将第一层学得的边缘信息组合起来,形成人脸的一些局部的特征,例如眼睛、嘴巴等;后面的几层逐步将上一层的特征组合起来,形成人脸的模样。随着神经网络层数的增加,特征也从原来的边缘逐步扩展为人脸的整体,由整体到局部,由简单到复杂。层数越多,那么模型学习的效果也就越精确。
随着神经网络的深度加深,模型能学习到更加复杂的问题,功能也更加强大。
2 四层网络的前向传播与反向传播
在这里首先对每层的符号进行一个确定,我们设置L为第几层,n为每一层的个数,L=[L1, L2, L3, L4]
,n=[5, 5, 3, 1]
2.1 前向传播
首先还是以单个样本来进行表示,每层经过线性计算和激活函数两步计算
z
[
1
]
=
W
[
1
]
x
+
b
[
1
]
,
a
[
1
]
=
g
[
1
]
(
z
[
1
]
)
,
输
入
x
,
输
出
a
[
1
]
z^{[1]} = W^{[1]}x+b^{[1]},a^{[1]}=g^{[1]}(z^{[1]}),输入x,输出a^{[1]}
z[1]=W[1]x+b[1],a[1]=g[1](z[1]),输入x,输出a[1]
z
[
2
]
=
W
[
2
]
a
[
1
]
+
b
[
2
]
,
a
[
2
]
=
g
[
2
]
(
z
[
2
]
)
,
输
入
a
[
1
]
,
输
出
a
[
2
]
z^{[2]} = W^{[2]}a^{[1]}+b^{[2]},a^{[2]}=g^{[2]}(z^{[2]}),输入a^{[1]},输出a^{[2]}
z[2]=W[2]a[1]+b[2],a[2]=g[2](z[2]),输入a[1],输出a[2]
z
[
3
]
=
W
[
3
]
a
[
2
]
+
b
[
3
]
,
a
[
3
]
=
g
[
3
]
(
z
[
3
]
)
,
输
入
a
[
2
]
,
输
出
a
[
3
]
z^{[3]} = W^{[3]}a^{[2]}+b^{[3]},a^{[3]}=g^{[3]}(z^{[3]}),输入a^{[2]},输出a^{[3]}
z[3]=W[3]a[2]+b[3],a[3]=g[3](z[3]),输入a[2],输出a[3]
z
[
4
]
=
W
[
4
]
a
[
3
]
+
b
[
4
]
,
a
[
4
]
=
σ
(
z
[
4
]
)
,
输
入
a
[
3
]
,
输
出
a
[
4
]
z^{[4]} = W^{[4]}a^{[3]}+b^{[4]},a^{[4]}=\sigma(z^{[4]}),输入a^{[3]},输出a^{[4]}
z[4]=W[4]a[3]+b[4],a[4]=σ(z[4]),输入a[3],输出a[4]
我们将上式简单的用通用公式表达出来,x = a[0]
z
[
L
]
=
W
[
L
]
a
[
L
−
1
]
+
b
[
L
]
,
a
[
L
]
=
g
[
L
]
(
z
[
L
]
)
,
输
入
a
[
L
−
1
]
,
输
出
a
[
L
]
z^{[L]} = W^{[L]}a^{[L-1]}+b^{[L]},a^{[L]}=g^{[L]}(z^{[L]}),输入a^{[L-1]},输出a^{[L]}
z[L]=W[L]a[L−1]+b[L],a[L]=g[L](z[L]),输入a[L−1],输出a[L]
那么m个样本的向量表示:
Z
[
L
]
=
W
[
L
]
A
[
L
−
1
]
+
b
[
L
]
,
A
[
L
]
=
g
[
L
]
(
Z
[
L
]
)
,
输
入
A
[
L
−
1
]
,
输
出
A
[
L
]
Z^{[L]} = W^{[L]}A^{[L-1]}+b^{[L]},A^{[L]}=g^{[L]}(Z^{[L]}),输入A^{[L-1]},输出A^{[L]}
Z[L]=W[L]A[L−1]+b[L],A[L]=g[L](Z[L]),输入A[L−1],输出A[L]
2.2 反向传播
因为涉及到的层数较多,所以我们通过一个图来表示反向的过程
- 反向传播的结果
单个样本的反向传播:
d Z [ l ] = d J d a [ l ] d a [ l ] d Z [ l ] = d a [ l ] ∗ g [ l ] ′ ( Z [ l ] ) dZ^{[l]}=\frac{dJ}{da^{[l]}}\frac{da^{[l]}}{dZ^{[l]}}=da^{[l]}*g^{[l]}{'}(Z^{[l]}) dZ[l]=da[l]dJdZ[l]da[l]=da[l]∗g[l]′(Z[l]) d W [ l ] = d J d Z [ l ] d Z [ l ] d W [ l ] = d Z [ l ] ⋅ a [ l − 1 ] dW^{[l]}=\frac{dJ}{dZ^{[l]}}\frac{dZ^{[l]}}{dW^{[l]}}=dZ^{[l]}\cdot a^{[l-1]} dW[l]=dZ[l]dJdW[l]dZ[l]=dZ[l]⋅a[l−1] d b [ l ] = d J d Z [ l ] d Z [ l ] d b [ l ] = d Z [ l ] db^{[l]}=\frac{dJ}{dZ^{[l]}}\frac{dZ^{[l]}}{db^{[l]}}=dZ^{[l]} db[l]=dZ[l]dJdb[l]dZ[l]=dZ[l] d a [ l − 1 ] = W [ l ] T ⋅ d Z [ l ] da^{[l-1]}=W^{[l]T}\cdot dZ^{[l]} da[l−1]=W[l]T⋅dZ[l]
多个样本的反向传播
d Z [ l ] = d A [ l ] ∗ g [ l ] ′ ( Z [ l ] ) dZ^{[l]}=dA^{[l]}*g^{[l]}{'}(Z^{[l]}) dZ[l]=dA[l]∗g[l]′(Z[l]) d W [ l ] = 1 m d Z [ l ] ⋅ A [ l − 1 ] T dW^{[l]}=\frac{1}{m}dZ^{[l]}\cdot {A^{[l-1]}}^{T} dW[l]=m1dZ[l]⋅A[l−1]T d b [ l ] = 1 m n p . s u m ( d Z [ l ] , a x i s = 1 ) db^{[l]}=\frac{1}{m}np.sum(dZ^{[l]},axis=1) db[l]=m1np.sum(dZ[l],axis=1) d A [ l ] = W [ l + 1 ] T ⋅ d Z [ l + 1 ] dA^{[l]}=W^{[l+1]T}\cdot dZ^{[l+1]} dA[l]=W[l+1]T⋅dZ[l+1]
3 参数与超参数
3.1 参数
参数即是我们在过程中想要模型学习到的信息(模型自己能计算出来的),例如 W[l]W[l],b[l]b[l]。而超参数(hyper parameters)即为控制参数的输出值的一些网络信息(需要人经验判断)。超参数的改变会导致最终得到的参数 W[l],b[l] 的改变。
2.5.3.2 超参数
典型的超参数有:
- 学习速率:α
- 迭代次数:N
- 隐藏层的层数:L
- 每一层的神经元个数:n[1],n[2],…
- 激活函数 g(z) 的选择
当开发新应用时,预先很难准确知道超参数的最优值应该是什么。因此,通常需要尝试很多不同的值。应用深度学习领域是一个很大程度基于经验的过程。
深度学习难以在大数据领域发挥最大效果的一个原因是,在巨大的数据集基础上进行训练速度很慢。而优化算法能够帮助我们快速训练模型,提高计算效率。接下来我么就去看有哪些方法能够解决我们刚才遇到的问题或者类似的问题
4 优化遇到的问题
4.1 梯度消失
在梯度函数上出现的以指数级递增或者递减的情况分别称为梯度爆炸或者梯度消失。
假
设
g
(
z
)
=
z
,
b
[
l
]
=
0
,
对
于
目
标
输
出
有
:
y
^
=
W
[
L
]
W
[
L
−
1
]
.
.
.
W
[
2
]
W
[
1
]
X
假设g(z) = z, b^{[l]} = 0,对于目标输出有:\hat{y} = W^{[L]}W^{[L-1]}...W^{[2]}W^{[1]}X
假设g(z)=z,b[l]=0,对于目标输出有:y^=W[L]W[L−1]...W[2]W[1]X
- 对于W[l]的值大于 1 的情况,激活函数的值将以指数级递增;
- 对于W[l]的值小于 1 的情况,激活函数的值将以指数级递减。
在计算梯度时,根据不同情况梯度函数也会以指数级递增或递减,导致训练导数难度上升,梯度下降算法的步长会变得非常小,需要训练的时间将会非常长。
4.2 局部最优
鞍点(saddle)是函数上的导数为零,但不是轴上局部极值的点。通常梯度为零的点是上图所示的鞍点,而非局部最小值。减少损失的难度也来自误差曲面中的鞍点,而不是局部最低点。
- 在训练较大的神经网络、存在大量参数,并且成本函数被定义在较高的维度空间时,困在极差的局部最优基本不会发生
- 鞍点附近的平稳段会使得学习非常缓慢,而这也是需要后面的动量梯度下降法、RMSProp 以及 Adam 优化算法能够加速学习的原因,它们能帮助尽早走出平稳段。
5 优化遇到的问题
解决办法有多种形式,通常会结合几种形式一起进行:
- 初始化参数策略
- Mini梯度下降法
- 梯度下降算法的优化
- 学习率衰减
5.1 参数初始化策略
由于在 z = w 1 x 1 + w 2 x 2 + . . . + w n x n + b z={w}_1{x}_1+{w}_2{x}_2 + ... + {w}_n{x}_n +b z=w1x1+w2x2+...+wnxn+b公式中,当输入的数量n较大时,如果每个wi的值都小一些,这样它们的和得到的z也会非常大,所以会造成我们之前在第一部分最后一节当中介绍的。所以都会初始化比较小的值(服从正态分布)。
5.2 批梯度下降算法(Batch Gradient Descent)
- 定义:批梯度下降法(btach),即同时处理整个训练集。
其在更新参数时使用所有的样本来进行更新。对整个训练集进行梯度下降法的时候,我们必须处理整个训练数据集,然后才能进行一步梯度下降,即每一步梯度下降法需要对整个训练集进行一次处理,如果训练数据集很大的时候,处理速度就会比较慢。
所以换一种方式,每次处理训练数据的一部分进行梯度下降法,则我们的算法速度会执行的更快。
5.3 小批量梯度下降法(Mini-Batch Gradient Descent)
- 定义:Mini-Batch 梯度下降法(小批量梯度下降法)每次同时处理固定大小的数据集。
- 当mini-batch 的大小为 1时,即是随机梯度下降法(Stochastic Gradient Descent)
使用 Mini-Batch 梯度下降法,对整个训练集的一次遍历(epoch)只做 mini-batch个样本的梯度下降,一直循环整个训练集。
5.4 小批量梯度下降的优势
- batch 梯度下降法:
- 对所有 m 个训练样本执行一次梯度下降,每一次迭代时间较长,训练过程慢;
- 相对噪声低一些,成本函数总是向减小的方向下降。
- 随机梯度下降法(Mini-Batch=1):
- 对每一个训练样本执行一次梯度下降,训练速度快,但丢失了向量化带来的计算加速;
- 有很多噪声,需要适当减小学习率,成本函数总体趋势向全局最小值靠近,但永远不会收敛,而是一直在最小值附近波动。
因此,选择一个合适的大小进行 Mini-batch 梯度下降,可以实现快速学习,也应用了向量化带来的好处,且成本函数的下降处于前两者之间。
5.5 Mini-Batch大小的选择
- 如果训练样本的大小比较小,如样本数量小于2000时,选择 batch 梯度下降法;
- 如果训练样本的大小比较大,选择 Mini-Batch 梯度下降法。为了和计算机的信息存储方式相适应,代码在 mini-batch 大小为 2 的幂次时运行要快一些。典型的大小为26,27,28,29,mini-batch 的大小要符合 CPU/GPU 内存。
需要根据经验快速尝试,找到能够最有效地减少成本函数的值。
那么第二种方式是通过优化梯度下降过程,会比梯度下降算法的速度更快些
6 指数加权平均
指数加权平均(Exponentially Weight Average)是一种常用的序列数据处理方式,通常用在序列场景如金融序列分析、温度变化序列分析。
指数加权的公式:
S
t
=
{
Y
1
,
t
=
1
β
S
t
−
1
+
(
1
−
β
)
Y
t
,
t
>
1
S_t = \begin{cases} Y_1, &t = 1 \\ \beta S_{t-1} + (1-\beta)Y_t, &t > 1 \end{cases}
St={Y1,βSt−1+(1−β)Yt,t=1t>1
其
中
Y
t
为
t
下
的
实
际
值
,
S
t
为
t
下
加
权
平
均
后
的
值
,
β
为
权
重
值
。
其中Y_{t}为 t 下的实际值,S_{t}为t下加权平均后的值,\beta为权重值。
其中Yt为t下的实际值,St为t下加权平均后的值,β为权重值。
7 动量梯度下降法
动量梯度下降(Gradient Descent with Momentum)是计算梯度的指数加权平均数,并利用该值来更新参数值。动量梯度下降法的整个过程为:
S
d
W
[
l
]
=
β
S
d
W
[
l
]
+
(
1
−
β
)
d
W
[
l
]
S_{dW^{[l]}} = \beta S_{dW^{[l]}} + (1 - \beta) dW^{[l]}
SdW[l]=βSdW[l]+(1−β)dW[l]
S
d
b
[
l
]
=
β
S
d
b
[
l
]
+
(
1
−
β
)
d
b
[
l
]
S_{db^{[l]}} = \beta S_{db^{[l]}} + (1 - \beta) db^{[l]}
Sdb[l]=βSdb[l]+(1−β)db[l]
W
[
l
]
:
=
W
[
l
]
−
α
S
d
W
[
l
]
W^{[l]} := W^{[l]} - \alpha S_{dW^{[l]}}
W[l]:=W[l]−αSdW[l]
b
[
l
]
:
=
b
[
l
]
−
α
S
d
b
[
l
]
b^{[l]} := b^{[l]} - \alpha S_{db^{[l]}}
b[l]:=b[l]−αSdb[l]
使用动量梯度下降时,通过累加过去的梯度值来减少抵达最小值路径上的波动,加速了收敛,因此在横轴方向下降得更快,从而得到图中红色或者紫色的曲线。当前后梯度方向一致时,动量梯度下降能够加速学习;而前后梯度方向不一致时,动量梯度下降能够抑制震荡。
8 RMSProp 算法
RMSProp(Root Mean Square Prop)算法是在对梯度进行指数加权平均的基础上,引入平方和平方根。
s
d
w
=
β
s
d
w
+
(
1
−
β
)
(
d
w
)
2
s_{dw} = \beta s_{dw} + (1 - \beta)(dw)^2
sdw=βsdw+(1−β)(dw)2
s
d
b
=
β
s
d
b
+
(
1
−
β
)
(
d
b
)
2
s_{db} = \beta s_{db}+ (1 - \beta)(db)^2
sdb=βsdb+(1−β)(db)2
w
:
=
w
−
α
d
w
s
d
w
+
ϵ
w := w - \alpha \frac{dw}{\sqrt{s_{dw} + \epsilon}}
w:=w−αsdw+ϵdw
b
:
=
b
−
α
d
b
s
d
b
+
ϵ
b := b - \alpha \frac{db}{\sqrt{s_{db} + \epsilon}}
b:=b−αsdb+ϵdb
其
中
ϵ
是
一
个
非
常
小
的
数
,
防
止
分
母
太
小
导
致
不
稳
定
,
当
d
w
或
d
b
较
大
时
,
(
d
w
)
2
,
(
d
b
)
2
会
较
大
,
其中\epsilon是一个非常小的数,防止分母太小导致不稳定,当 dw 或 db 较大时,(dw)^{2}, (db)^{2}会较大,
其中ϵ是一个非常小的数,防止分母太小导致不稳定,当dw或db较大时,(dw)2,(db)2会较大,
进
而
s
d
w
也
会
较
大
,
最
终
使
得
d
b
s
d
b
+
ϵ
等
结
果
变
得
非
常
小
。
进而s_dw也会较大,最终使得\frac{db}{\sqrt{s_{db} + \epsilon}}等结果变得非常小。
进而sdw也会较大,最终使得sdb+ϵdb等结果变得非常小。
最终RMSProp 有助于减少抵达最小值路径上的摆动,并允许使用一个更大的学习率 α,从而加快算法学习速度。
9 Adam算法
Adam 优化算法(Adaptive Moment Estimation,自适应矩估计)将 Momentum 和 RMSProp 算法结合在一起。
假设用每一个 mini-batch 计算 dW、db,第 t 次迭代时:
v d W = β 1 v d W + ( 1 − β 1 ) d W v_{dW} = \beta_1 v_{dW} + (1 - \beta_1) dW vdW=β1vdW+(1−β1)dW
v d b = β 1 v d b + ( 1 − β 1 ) d b v_{db} = \beta_1 v_{db} + (1 - \beta_1) db vdb=β1vdb+(1−β1)db
v d W [ l ] c o r r e c t e d = v d W [ l ] 1 − ( β 1 ) t v^{corrected}_{dW^{[l]}} = \frac{v_{dW^{[l]}}}{1 - (\beta_1)^t} vdW[l]corrected=1−(β1)tvdW[l]
s d W = β 2 s d W + ( 1 − β 2 ) ( d W ) 2 s_{dW} = \beta_2 s_{dW} + (1 - \beta_2) {(dW)}^2 sdW=β2sdW+(1−β2)(dW)2
s d b = β 2 s d b + ( 1 − β 2 ) ( d b ) 2 s_{db} = \beta_2 s_{db} + (1 - \beta_2) {(db)}^2 sdb=β2sdb+(1−β2)(db)2
s
d
W
[
l
]
c
o
r
r
e
c
t
e
d
=
s
d
W
[
l
]
1
−
(
β
1
)
t
s^{corrected}_{dW^{[l]}} = \frac{s_{dW^{[l]}}}{1 - (\beta_1)^t}
sdW[l]corrected=1−(β1)tsdW[l]
其中l为某一层,t为移动平均第次的值
- Adam 算法的参数更新:
10 TensorFlow Adam算法API
- tf.train.AdamOptimizer(learning_rate=0.001, beta1=0.9, beta2=0.999,epsilon=1e-08,name=‘Adam’)
Adam 优化算法有很多的超参数: - 学习率:需要尝试一系列的值,来寻找比较合适的
- β1:常用的缺省值为 0.9
- β2:Adam 算法的作者建议为 0.999
- ϵ:Adam 算法的作者建议为epsilon的默认值1e-8
注:β1、β2、ϵ 通常不需要调试
11 学习率衰减
如果设置一个固定的学习率 α
- 在最小值点附近,由于不同的 batch 中存在一定的噪声,因此不会精确收敛,而是始终在最小值周围一个较大的范围内波动。
- 如果随着时间慢慢减少学习率 α 的大小,在初期 α 较大时,下降的步长较大,能以较快的速度进行梯度下降;而后期逐步减小 α 的值,即减小步长,有助于算法的收敛,更容易接近最优解。
最常用的学习率衰减方法: α = 1 1 + d e c a y _ r a t e ∗ e p o c h _ n u m ∗ α 0 \alpha = \frac{1}{1 + decay\_rate * epoch\_num} * \alpha_0 α=1+decay_rate∗epoch_num1∗α0
其中,decay_rate为衰减率(超参数),epoch_num为将所有的训练样本完整过一遍的次数。
还有一种指数衰减
- α = 0.9 5 e p o c h _ n u m ∗ α 0 \alpha = 0.95^{epoch\_num} * \alpha_0 α=0.95epoch_num∗α0
对于大型的数据模型,需要使用这些方式去自动进行学习率衰减。而一些小型网络可以直接手动进行调整
12 其它非算法优化的方式-标准化输入
对网络输入的特征进行标准化,能够缓解梯度消失或者梯度爆炸
- 标准化公式:
x = x − μ σ x = \frac{x - \mu}{\sigma} x=σx−μ
这个公式其实与特征工程中的处理是一样的,μ为平均值,σ为标准差。标准化的目的是所有特征的平均值为0,标准差为1。这属于机器学习基本的内容不过多进行叙述。
那么这种有什么好处?主要是对于损失函数带来的好处
这样的话,对于梯度下降无论从哪个位置开始迭代,都能以相对较少的迭代次数找到全局最优解。可以加速网络的学习。
理解这个原理,其实还是最初的这样的公式:w1x1+w2x2+w3x3+w4x4…
如果激活函数的输入X近似设置成均值为 0,标准方差为 1,神经元输出 z 的方差就正则化到1了。虽然没有解决梯度消失和爆炸的问题,但其在一定程度上确实减缓了梯度消失和爆炸的速度。
示例代码:深度神经网络实现时装分类
import os
import numpy as np
import tensorflow as tf
from tensorflow.python import keras
class SingleNN(object):
model = keras.models.Sequential([
keras.layers.Flatten(input_shape=(28, 28)),
keras.layers.Dense(128, activation=tf.nn.relu),
keras.layers.Dense(10, activation=tf.nn.softmax)
]) # 1、建立神经网络模型(类属性)
def __init__(self): # 2、获取数据集,进行数据的归一化(关键一步)
(self.x_train, self.y_train), (self.x_test, self.y_test) = keras.datasets.fashion_mnist.load_data()
self.x_train = self.x_train / 255.0
self.x_test = self.x_test / 255.0
def compile(self): # 3、编译模型优化器、损失、准确率
SingleNN.model.compile(optimizer=keras.optimizers.Adam(),
loss=keras.losses.sparse_categorical_crossentropy,
metrics=['accuracy'])
return None
def fit(self): # 4、模型训练
# 调用TensorBoard回调函数
tensorboard = keras.callbacks.TensorBoard(log_dir='./graph', write_graph=True, write_images=True)
SingleNN.model.fit(self.x_train, self.y_train, epochs=1, batch_size=32, callbacks=[tensorboard])
return None
def evaluate(self): # 5、模型评估
test_loss, test_acc = SingleNN.model.evaluate(self.x_test, self.y_test)
print("损失评估结果:", test_loss)
print("准确率评估结果:", test_acc)
return None
def predict(self): # 6、使用训练好的模型,进行预测
if os.path.exists("./ckpt/SingleNN.h5"):
SingleNN.model.load_weights("./ckpt/SingleNN.h5")
predictions = SingleNN.model.predict(self.x_test)
# 对预测结果进行处理
print(predictions)
print(np.argmax(predictions, axis=1))
return
if __name__ == '__main__':
snn = SingleNN()
snn.compile()
snn.fit()
snn.evaluate()
# SingleNN.model.save_weights("./ckpt/SingleNN.h5")
# snn.predict()
===运行结果:====================================================================
Epoch 1/1
32/60000 [..............................] - ETA: 6:02 - loss: 2.5283 - acc: 0.0312
96/60000 [..............................] - ETA: 2:38 - loss: 2.3321 - acc: 0.0625
160/60000 [..............................] - ETA: 2:01 - loss: 2.2127 - acc: 0.1000
...
59872/60000 [============================>.] - ETA: 0s - loss: 0.4981 - acc: 0.8253
59968/60000 [============================>.] - ETA: 0s - loss: 0.4981 - acc: 0.8253
60000/60000 [==============================] - 60s 996us/step - loss: 0.4980 - acc: 0.8254
32/10000 [..............................] - ETA: 15s
2176/10000 [=====>........................] - ETA: 0s
4736/10000 [=============>................] - ETA: 0s
7584/10000 [=====================>........] - ETA: 0s
10000/10000 [==============================] - 0s 25us/step
损失评估结果: 0.44231134095191954
准确率评估结果: 0.8441