这篇博客呢,主要记录的是自己对DNN的理解,然后呢,还记录了一下比较前沿的DNN的理论 ELU,Dropout,动量法,RMSProp。反向传播算法呢,由于CSDN上面街上的已经很多了,不在这篇的讨论范围之类,这篇博客主要参考的台大李宏毅的机器学习、以及DeepLearning 这本书吧。
我只是知识的搬运工 JXinyee,嘿嘿
在此附上李宏毅老师的个人主页,讲的非常非常好http://speech.ee.ntu.edu.tw/~tlkagk/courses_MLDS18.html
我们刚开始在使用深度学习的使用,大多数人用的都是手写数字识别这个数据集。而且这个手写数字识别大概需要3个隐藏层就能达到92%以上的准确率。但是随着时代进步,社会发展。如果你需要解决非常复杂的问题,例如语音识别中的上百种语言,无数多个单词该怎么办? 这时候就要训练更深的 DNN,也许有 10 层,每层包含数百个神经元,通过数十万个连接来连接。这就引入了梯度爆炸的问题了,如果你对此抱有怀疑,不妨看一下下面这个函数。
很长一段时间人们用sigmoid函数作为激活函数,人们认为这种连续性的函数似乎能模拟人的大脑神经元的活动,当然事实上,后来证明relu更好。
1 sigmoid 函数的y值 对应的都是正的,均值为0.5,这会让训练得到的方差随着层数递增不断加大,人们后来通过这个提出了tanh函数 均值为0,效果确实要好一些
2.如上图所示,函数上分别有1、2、3三个点当sigmoid函数的目标点是1的时候,但是你现在处于3这个位置时。你会发现从3用梯度下降多么多么的艰难,因为3的梯度接近于0,这也会出现,我们发现训练着训练着发现,越接近输入层的伸进网络的权重越发不动了,梯度似乎“消失”了,这会导致训练深层的神经网络,训练会非常缓慢,这也是一段时间内,神级网络臭名昭著的原因。
解决梯度消失的方法(Xavier初始化)
Glorot 和 Bengio 在2015年的一篇论文中认为,我们需要每层输出的方差等于其输入的方差。(这里有一个比喻:如果将麦克风放大器的旋钮设置得太接近于零,人们听不到声音,但是如果将麦克风放大器设置得太大,声音就会饱和,人们就会听不懂你在说什么。神经网络也是如此,我们既不希望权重在传递的过程中消失了,也不希望他爆炸饱和。
于是他们提出了一个公式:
他们发现随机初始化连接权重必须如公式 11-1 所描述的那样。其中n_inputs
和n_outputs
是权重正在被初始化的层(也称为扇入和扇出)的输入和输出连接的数量。具体的证明公式,请看Glorot 和 Bengio 的论文。现在好多都是这么干的。
另外Glorot 和 Bengio 在 2010 年的论文中的一个见解是,消失/爆炸的梯度问题部分是由于激活函数的选择不好造成的。 在那之前,大多数人都认为,如果大自然选择在生物神经元中使用 sigmoid 激活函数,它们必定是一个很好的选择。 但事实证明,其他激活函数在深度神经网络中表现得更好,特别是 ReLU 激活函数,主要是因为它对正值不会饱和(也因为它的计算速度很快)。而且也很简单,这也是大自然的神奇之处,越简单的函数泛性反而越好。
首先relu这个函数呢,我们可以看到求导非常的简单,而且是固定值,不会出现梯度消失的问题,在另一方面,relu能够更好的拟合函数,包括非常复杂的函数。拿李宏毅老师上课给的一个例子:
对于一个给定的函数f1,现在我们要找一个函数f2去拟合它,我们不妨假设这俩个函数相差L*||x1-x2||,这里的L是一个常数,相差越小,说明俩个函数越接近。这样我们就把损失函数最小化的问题转化了。
我们原先的问题是上面这个公式(典型的损失函数),现在我们就转化为,在一定的区间上寻找一个直线,注意一定要是直线!
让他们的差距尽可能的小。如图,怎么让ε尽可能的小呢,只需要让l足够小就可以了。
那么你会说这个函数图像和relu又有什么关系呢?嘻嘻,仔细瞅一眼下面这个函数,它其实就是由右边的函数组合而成,而右边的函数,每一条蓝色线就是俩个relu的组合了。
事实上搞定了上面这个relu的问题,我们自然而然会产生一个疑问,反正都用relu可以拟合出来,那么我们为什么要用那么多隐藏层呢,直接一层,只要有足够多的神经元让它学习不也是可以么?这样顺道似乎也能解决层次过多,梯度爆炸和梯度消失的问题了?事实上并非如此,深度学习之所以越深,是因为它能指数型减少我们需要的参数,注意!是指数型减少!
如上图所示,拟合这样一个函数,在一层大概需要8个神经元激活函数为relu函数,而在深度学习中只需要3个神经元激活函数是relu就够了,你可以把它简单理解为折纸,如果我们把一张纸对折3次,再展开那么这张纸上会留下8个痕迹,如果是直接在纸上画痕迹,大概需要画8次。这也是为什么越深的神经网络比越胖的神经网络好的原因!
事实上relu并不完美,从他的函数图像我们可以看到一旦x小于0,那么y也就相应变成0了,而且梯度为0,那么一旦我们在训练的时候不小心到了0这个区域,这就意味着这个神经元再也无法通过梯度下降活过来了,这也是神经元死亡的问题,为了解决这个问题,人们额外加了一个alpha超参。这个alpha可以自己设,也可以通过学习得到。如下图虽然x一时半会小于0,y也跟着小于0了,但还是有缓慢梯度的,这意味着有朝一日他能活过来。人们甚至还不满足加了一个指数型线性神经元ELU,唉算力强真的惹不起。其实都大同小异,这个主要是为了解决在0处不可导的问题。
hidden = tf.layers.dense(X, hidden1, activation=tf.nn.elu, name="hidden") #tensorflow 定义一个relu特别简单,定义一个leaky虽然没有现成的,直接定义也可以
def leakyrelu(z, name=None):
return tf.maximum(0.02 * z, z, name=name)
hidden = tf.layers.dense(X, hidden, activation=leakyrelu, name="hidden")
使用 Xavier初始化和 ELU(或任何 ReLU 变体)可以显著减少训练开始阶段的梯度消失/爆炸问题,但不保证在训练期间问题不会回来。
在 2015 年的一篇论文中,Sergey Ioffe 和 Christian Szegedy 提出了一种称为批量标准化(Batch Normalization,BN)的技术来解决梯度消失/爆炸问题,每层输入的分布在训练期间改变的问题,更普遍的问题是当前一层的参数改变,每层输入的分布会在训练过程中发生变化(他们称之为内部协变量偏移问题)。
该技术包括在每层的激活函数之前在模型中添加操作,简单地对输入进行zero-centering和规范化,然后每层使用两个新参数(一个用于尺度变换,另一个用于偏移)对结果进行尺度变换和偏移。 换句话说,这个操作可以让模型学习到每层输入值的最佳尺度,均值。为了对输入进行归零和归一化,算法需要估计输入的均值和标准差。 它通过评估当前小批量输入的均值和标准差(因此命名为“批量标准化”)来实现。公式如下:
z是最终的输出,虽然增加了γ和β俩个参数,而且每一次标准化训练都要计算u和δ,加大了计算负担,但是会显著减少梯度爆炸的问题。tensorflow也提供了专门的包去做这个。
现在给出俩个代码进行前后对比,一个是relu,另一个使用elu进行batch_norm方式,可能是我这里的隐藏层层数比较低,所以效果并没有明显与relu造成差异
#relu方法
# Common imports
import numpy as np
import os
import tensorflow as tf
# to make this notebook's output stable across runs
def reset_graph(seed=42):
tf.reset_default_graph()
tf.set_random_seed(seed)
np.random.seed(seed)reset_graph()
#mnist 手写数字识别为例
n_inputs = 28* 28
n_hidden1 = 300
n_hidden2 =100
n_outputs = 10
def leakyrelu(z,name =None):
return tf.maximum(0.02*z,z,name=name)
X =tf.placeholder(tf.float32,shape=(None,n_inputs), name ="X")
y= tf.placeholder(tf.int32,shape=(None), name = "y")
with tf.name_scope("dnn"):
hidden1 = tf.layers.dense(X, n_hidden1, activation=leakyrelu, name="hidden1")
hidden2 = tf.layers.dense(hidden1, n_hidden2, activation=leakyrelu, name="hidden2")
logits = tf.layers.dense(hidden2, n_outputs, name="outputs")
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")
learning_ratge = 0.01
with tf.name_scope("train"):
optimizer = tf.train.GradientDescentOptimizer(learning_rate =learning_ratge)
training_op = optimizer.minimize(loss)
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 tensorflow.examples.tutorials.mnist import input_data
mnist = input_data.read_data_sets("/tmp/data/")
X_train = mnist.train.images.astype(np.float32).reshape(-1, 28*28)
X_test = mnist.test.images.astype(np.float32).reshape(-1, 28*28)
y_test = mnist.test.labels
y_train = mnist.train.labels
y_train = y_train.astype(np.int32)
y_test = y_test.astype(np.int32)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() as sess:
init.run()
for epoch in range(n_epochs):
for X_batch,y_batch in shuffle_batch(X_train,
y_train,batch_size):
sess.run(training_op,feed_dict={X:X_batch,y:y_batch})
if(epoch%5 ==0):
acc_batch = accuracy.eval(feed_dict={X:X_batch,y:y_batch})
acc_valid = accuracy.eval(feed_dict={X:X_test,y:y_test})
print(epoch,"Batch accuracy:",acc_batch,
"Valid accuracy",acc_valid)
save_path = saver.save(sess,"./my_model_final.ckpt")
0 Batch accuracy: 0.88 Valid accuracy 0.8981
5 Batch accuracy: 0.94 Valid accuracy 0.9431
10 Batch accuracy: 0.96 Valid accuracy 0.9574
15 Batch accuracy: 0.98 Valid accuracy 0.9632
20 Batch accuracy: 0.98 Valid accuracy 0.9681
25 Batch accuracy: 1.0 Valid accuracy 0.9724
30 Batch accuracy: 0.98 Valid accuracy 0.9735
35 Batch accuracy: 1.0 Valid accuracy 0.9754
97.5%的手写数字识别准确率,还可以吧
reset_graph()
from functools import partial
import tensorflow as tf
if __name__ == '__main__':
n_inputs = 28 * 28
n_hidden1 = 300
n_hidden2 = 100
n_outputs = 10
batch_norm_momentum = 0.9
learning_rate = 0.01
X = tf.placeholder(tf.float32, shape=(None, n_inputs), name = 'X')
y = tf.placeholder(tf.int64, shape=None, name = 'y')
training = tf.placeholder_with_default(False, shape=(), name = 'training')#给Batch norm加一个placeholder
with tf.name_scope("dnn"):
he_init = tf.contrib.layers.variance_scaling_initializer()
#对权重的初始化
my_batch_norm_layer = partial(
tf.layers.batch_normalization,
training = training,
momentum = batch_norm_momentum
)
my_dense_layer = partial(
tf.layers.dense,
kernel_initializer = he_init
)
hidden1 = my_dense_layer(X ,n_hidden1 ,name = 'hidden1')
bn1 = tf.nn.elu(my_batch_norm_layer(hidden1))
hidden2 = my_dense_layer(bn1, n_hidden2, name = 'hidden2')
bn2 = tf.nn.elu(my_batch_norm_layer(hidden2))
logists_before_bn = my_dense_layer(bn2, n_outputs, name = 'outputs')
logists = my_batch_norm_layer(logists_before_bn)
with tf.name_scope('loss'):
xentropy = tf.nn.sparse_softmax_cross_entropy_with_logits(labels = y, logits= logists)
loss = tf.reduce_mean(xentropy, name = 'loss')
with tf.name_scope('train'):
optimizer = tf.train.GradientDescentOptimizer(learning_rate)
training_op = optimizer.minimize(loss)
with tf.name_scope("eval"):
correct = tf.nn.in_top_k(logists, y, 1)
accuracy = tf.reduce_mean(tf.cast(correct, tf.float32))
init = tf.global_variables_initializer()
saver = tf.train.Saver()
n_epoches = 40
batch_size = 50
# 注意:由于我们使用的是 tf.layers.batch_normalization() 而不是 tf.contrib.layers.batch_norm()(如本书所述),
# 所以我们需要明确运行批量规范化所需的额外更新操作(sess.run([ training_op,extra_update_ops], ...)。
extra_update_ops = tf.get_collection(tf.GraphKeys.UPDATE_OPS)
with tf.Session() as sess:
init.run()
for epoch in range(n_epoches):
for X_batch,y_batch in shuffle_batch(X_train,
y_train,batch_size):
sess.run(training_op,feed_dict={X:X_batch,y:y_batch})
if epoch%5 ==0:
accuracy_val = accuracy.eval(feed_dict= {X:mnist.test.images,
y:mnist.test.labels})
print(epoch, 'Test accuracy:', accuracy_val)
0 Test accuracy: 0.9055
5 Test accuracy: 0.9438
10 Test accuracy: 0.9576
15 Test accuracy: 0.9654
20 Test accuracy: 0.9676
25 Test accuracy: 0.9714
30 Test accuracy: 0.973
35 Test accuracy: 0.9728
最后效果也只是97.28%和relu半斤八两,sad!!!!!
好像晚上有点肝不动了,下一篇再写一写dropout,动量法什么的把