深层DNN可能出现的问题:
- 梯度消失、梯度爆炸问题,影响深度神经网络,导致底层训练困难
- 网络庞大,训练缓慢
- 容易overfitting
本章将会探讨梯度消失的流行解决方案,训练大模型明显提速的优化器(相对于平坦梯度下降 ),浏览针对大型神经网络的正则化技术。
Vanishing/Exploding Gradients Problems
第十章中讲到:反向传播算法再输出层反向作用到输入层的过程中传播误差梯度,一旦算法计算出成本函数梯度,就会根据梯度修正每一个参数。
DNN受制于不稳定梯度,不同层可能以完全不同速度来学习。
- 梯度消失:梯度随着算法进展到更底层越来越小,导致梯度下降在更底层网络连接权值更新上基本没有改变,训练收敛不到好的结果。
- 梯度爆炸:梯度随着算法进展到更底层越来越大,导致很多层的权值疯狂增大,使得算法发散(常出现在循环神经网络)
梯度消失的解决:用逻辑S激活函数和权重初始化技术结合(均值为0,方差为1正态分布随机初始化/均值为0的双曲正切函数):每一层输出方差比输入方差大很多,依层增加到激活函数最高层饱和。
如上图逻辑激活函数:输入增大(正或负方向),函数在0或1饱和,导数接近0——反向传播起作用时,并没有梯度通过网络反向作用,同时反向传播至顶层几乎没有梯度被稀释,所以也基本上没给低层留下什么。
Xavier and He Initialization
缓和梯度消失:让信号在两个方向都正确流动,预测保持正向,反向传播梯度保持反方向。不希望信号消亡、稀释、爆炸。要保持每一层输入输出方差一直,反向流动方差也要一致(很难实现),折中方案:连接权重按如下公式随机初始化。
如图所示为Xavier(Glorot)初始化:
ReLU激活函数及其变种(ELU函数)的初始化称为He初始化。
TF用tf.layers.dense()代替tensorflow.contrib.layers.fully_connected(),默认He初始化:
import tensorflow as tf
reset_graph()
n_inputs = 28 * 28 # MNIST
n_hidden1 = 300
X = tf.placeholder(tf.float32, shape=(None, n_inputs), name="X")
he_init = tf.variance_scaling_initializer()
hidden1 = tf.layers.dense(X, n_hidden1, activation=tf.nn.relu,
kernel_initializer=he_init, name="hidden1")
Nonsaturating Activation Functions(非饱和激活函数)
ReLU在DNN表现好的原因是计算快,同时并不稀释正值,但会出现dying ReLU问题:当神经元权重更新到神经元输入的总权重为负值时,神经元开始输出0,除非ReLU的梯度为0且输入为负,则神经元不会开始工作。
解决此问题就要使用ReLU变种——如leaky ReLU(带泄露性整流函数):LeakyReLU_α=max(αz,z)。α表示函数泄露程度,即z<0的坡度,默认设为0.01(既保证不会死,又保证能醒过来)。(其实设0.2结果更好,但默认为0.01)
列举几个ReLU变种的激活函数:
RReLU:在训练中α为给定区间随机值,测试为固定平均值,可做正则降低overfitting风险
PReLU:α在训练中学习(作为反向传播参数),在大图片集表现好,小数据集容易overfitting。
ELU(加速线性单元)优于ReLU所有变种(训练时间更短,测试集表现更好),定义如下:
与ReLU不同之处:
- z<0为负,允许单元平均输出接近0,缓解梯度消失,α通常设为1(用来z为极大负数接近本值)
- z<0有一非零梯度,避免单元消失
- 函数整体平滑,z=0处没有抖动,提高GB
ELU主要缺陷为,计算速度比ReLU和其变种慢(即使用更快收敛弥补)
DNN隐藏层使用激活函数优先顺序:ELU>leaky ReLU>ReLU>tanh>S(逻辑函数)
关注性能:leaky ReLU;不想改变超参数,默认α(leaky ReLU:0.01,ELU:1);时间性能充沛,交叉验证评估;过拟合:RReLU;大训练集:PReLU
TF调用:
reset_graph()
X = tf.placeholder(tf.float32, shape=(None, n_inputs), name="X")
def leaky_relu(z, name=None):
return tf.maximum(0.01 * z, z, name=name)
hidden1 = tf.layers.dense(X, n_hidden1, activation=leaky_relu, name="hidden1")
Batch Normalization(批量一体化)
He初始化+ELU可以降低梯度消失/爆炸,但不能保证,使用批量一体化(BN)解决:在每一层激活函数之前在模型里加简单零中心化和归一化输入,再通过每层两个新参数,一个用来缩放结果,一个用来移动结果——让模型学会最佳规模和每层输入平均值。
零中心化和归一化输入需要评估输入平均值和标准方差,操作如下:
测试期间可以 用整个训练集平均值和方差替代经验平均值和经验标准方差。γ(scale), β (offset), μ (mean), and σ (standard deviation)为每一批量归一化层来学习的。
使用饱和激活函数(如tanh)和逻辑激活函数有效的改善了梯度消失问题,也可以使用更高的学习率,不仅减少了训练步骤而且效果很好,BN还可以同时正则化,降低其他正则化技术需求。
BN同时增加了复杂度,预测运算速度变慢。
一旦找到合适的γ(scale), β (offset),训练速度会迅速提升,否则很慢。
TF应用ELU
建图
reset_graph()
n_inputs = 28 * 28 # MNIST
n_hidden1 = 300
n_hidden2 = 100
n_outputs = 10
定义X,y:
X = tf.placeholder(tf.float32, shape=(None, n_inputs), name="X")
y = tf.placeholder(tf.int32, shape=(None), name="y")
定义DNN:
with tf.name_scope("dnn"):
hidden1 = tf.layers.dense(X, n_hidden1, activation=leaky_relu, name="hidden1")
hidden2 = tf.layers.dense(hidden1, n_hidden2, activation=leaky_relu, name="hidden2")
logits = tf.layers.dense(hidden2, n_outputs, name="outputs")
定义成本函数loss:
with tf.name_scope("loss"):
xentropy = tf.nn.sparse_softmax_cross_entropy_with_logits(labels=y, logits=logits)
loss = tf.reduce_mean(xentropy, name="loss")
定义optimizer:
learning_rate = 0.01
with tf.name_scope("train"):
optimizer = tf.train.GradientDescentOptimizer(learning_rate)
training_op = optimizer.minimize(loss)
定义eval评估:
with tf.name_scope("eval"):
correct = tf.nn.in_top_k(logits, y, 1)
accuracy = tf.reduce_mean(tf.cast(correct, tf.float32))
初始变量并保存:
init = tf.global_variables_initializer()
saver = tf.train.Saver()
加载数据(下载到本地),划分选集:
from keras.datasets import mnist
(X_train, y_train), (X_test, y_test) =mnist.load_data('mnist/mnist.npz')
X_train = X_train.astype(np.float32).reshape(-1, 28*28) / 255.0
X_test = X_test.astype(np.float32).reshape(-1, 28*28) / 255.0
y_train = y_train.astype(np.int32)
y_test = y_test.astype(np.int32)
X_valid, X_train = X_train[:5000], X_train[5000:]
y_valid, y_train = y_train[:5000], y_train[5000:]
定义取mini-batch算法:
def shuffle_batch(X, y, batch_size):
rnd_idx = np.random.permutation(len(X))
n_batches = len(X) // batch_size
for batch_idx in np.array_split(rnd_idx, n_batches):
X_batch, y_batch = X[batch_idx], y[batch_idx]
yield X_batch, y_batch
小批量训练:
n_epochs = 40
batch_size = 50
with tf.Session(