Hands On Machine Learning with Scikit Learn and TensorFlow(第十一章)

Vanishing/Exploding Gradients Problems

梯度消失的定义:在反向传播过程中,梯度的值会越来越小,这样导致:使用梯度下降更新参数时,更新的参数基本没有变,训练也不会收敛,无法达到最优解。

梯度爆炸的定义:在反向传播过程中,梯度的值越来越大,这样导致:权重很大,造成算法发散,在RNN中常见。

Xavier and He Initialization

我们不想让信号消失,也不想让信号过大或者饱和。为了让信号正确的流动,信号不管在前向传播还是反向传播过程中,都需要使输入时的方差等于输出时的方差。不可能同时保证这样,除非这一层的输入和输出的连接相等。在实际中,我们使用Xavier初始化权重也能达到满意的效果。n为输入的数量。

 在tf.layers.dense()函数中,默认使用正态分布的Xavier作为初始化。假如使用He初始化则如下代码:

he_init = tf.contrib.layers.variance_scaling_initializer()
hidden1 = tf.layers.dense(X, n_hidden1, activation=tf.nn.relu,
                          kernel_initializer=he_init, name="hidden1")

He初始化不像Xavier初始化一样考虑输入和输出的数量,He只考虑输入的数量。

Nonsaturating Activation Functions

 梯度消失/爆炸的很大程度的原因在于:糟糕的激活函数的选择。

Leaky ReLU基本好过普通的ReLU,定义为LeakyReLUα(z) = max(αz, z)

 最近论文提出exponential linear unit (ELU)比所有的ReLU效果都要好,但是会有过拟合风险。

Elu的优势:

  1. 值小于0,也能有梯度,避免单元死亡(输出为0)。
  2. 处处平滑,加速梯度下降。

劣势:速度慢,因为有指数函数。在训练过程中,可以被快速的收敛所抵消,但是测试中比ReLU慢

在Tensorflow中使用如下,ELU

hidden1 = tf.layers.dense(X, n_hidden1, activation=tf.nn.elu, name="hidden1")

leaky_relu

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

在激活函数之前添加Batch Normalization。在进行训练时,使用的均值以及方差都是在Mini-batch上进行测试的。

在测试阶段,使用全体训练集的均值以及方差。

使用了这种技术,不仅可以避免梯度爆炸/消失,还可以使用大的学习速率。并且带有一点正则的效果(不是正则),减少了例如dropout的使用。

输入量不需要Normalize,因为第一层的隐藏层会进行Batch normalize。

Implementing Batch Normalization with TensorFlow

 用如下代码进行

import tensorflow as tf
n_inputs = 28 * 28
n_hidden1 = 300
n_hidden2 = 100
n_outputs = 10
X = tf.placeholder(tf.float32, shape=(None, n_inputs), name="X")
training = tf.placeholder_with_default(False, shape=(), name='training')
hidden1 = tf.layers.dense(X, n_hidden1, name="hidden1")
bn1 = tf.layers.batch_normalization(hidden1, training=training, momentum=0.9)
bn1_act = tf.nn.elu(bn1)
hidden2 = tf.layers.dense(bn1_act, n_hidden2, name="hidden2")
bn2 = tf.layers.batch_normalization(hidden2, training=training, momentum=0.9)
bn2_act = tf.nn.elu(bn2)
logits_before_bn = tf.layers.dense(bn2_act, n_outputs, name="outputs")
logits = tf.layers.batch_normalization(logits_before_bn, training=training,
momentum=0.9

training 这个placeholder:在训练的时候设置为True,测试时候为False,这个placeholder告诉tf.layers.batch_normalization()在True的时候使用当前mini-batch的mean和标准差(训练阶段),False的时候使用整个训练集的均值以及标准差(测试阶段)。
因为BN使用exponential decay来计算running averages(计算方差和均值),这就是它需要momentum参数的原因。对于一个新值v,则running averages \hat{}\hat{}\hat{v}

一个好的momentum 值为接近1,0.9,0.99,0.999,数据集越大,mini-batch越小,那么需要的9也就越多(越接近1)。

使用python自带的partial函数可以自动设置默认值。

from functools import partial
my_batch_norm_layer = partial(tf.layers.batch_normalization,
training=training, momentum=0.9)
hidden1 = tf.layers.dense(X, n_hidden1, name="hidden1")
bn1 = my_batch_norm_layer(hidden1)
bn1_act = tf.nn.elu(bn1)
hidden2 = tf.layers.dense(bn1_act, n_hidden2, name="hidden2")
bn2 = my_batch_norm_layer(hidden2)
bn2_act = tf.nn.elu(bn2)
logits_before_bn = tf.layers.dense(bn2_act, n_outputs, name="outputs")
logits = my_batch_norm_layer(logits_before_bn)

batch_normalization()创建了一些少量操作,必须在训练的每一步都评估,来更新moving averages,moving averages用来评估训练集的均值以及标准差。这些操作自动被放进UPDATE_OPS。我们只需要把这些操作放在一个list里取出来,运行就可以。

extra_update_ops = tf.get_collection(tf.GraphKeys.UPDATE_OPS)
with tf.Session() as sess:
    init.run()
    for epoch in range(n_epochs):
        for iteration in range(mnist.train.num_examples // batch_size):
            X_batch, y_batch = mnist.train.next_batch(batch_size)
            sess.run([training_op, extra_update_ops],
                      feed_dict={training: True, X: X_batch, y: y_batch})
        accuracy_val = accuracy.eval(feed_dict={X: mnist.test.images,
                                            y: mnist.test.labels})
        print(epoch, "Test accuracy:", accuracy_val)
    save_path = saver.save(sess, "./my_model_final.ckpt")

Gradient Clipping

优化器的minimize()函数同时 计算梯度并且把梯度应用于变量中进行更新,首先使用compute_gradients()计算梯度,随后使用clip_by_value()函数把梯度限制在规定值的范围中,最后利用apply_gradients()把算得得梯度应用于变量中,

threshold = 1.0
optimizer = tf.train.GradientDescentOptimizer(learning_rate)
grads_and_vars = optimizer.compute_gradients(loss)
capped_gvs = [(tf.clip_by_value(grad, -threshold, threshold), var)
               for grad, var in grads_and_vars]
training_op = optimizer.apply_gradients(capped_gvs)

grads_and_variables里面是一个列表,包括梯度以及对应得变量。 

Reusing Pretrained Layers

迁移学习只有两个任务得低阶特征相似的情况下才能取得良好得效果 

Reusing a TensorFlow Model

首先使用import_meta_graph()函数把operations引入进默认的图表。

随后为了取出用于训练用的operations和tensors,你需要get_tensor_by_name()以及get_operation_by_name()。

tensor的名字为   输出tensor的operation的名字,后面在加一个:0,代表第一个输出,(:1代表第二个输出。)

X = tf.get_default_graph().get_tensor_by_name("X:0")
y = tf.get_default_graph().get_tensor_by_name("y:0")
accuracy = tf.get_default_graph().get_tensor_by_name("eval/accuracy:0")
training_op = tf.get_default_graph().get_operation_by_name("GradientDescent")

要想知道tensor的名字,可以使用tensorboard,要想使用tensorboard,必须使用FileWriter来把graph输出至本地。或者使用get_operations()的方法来列出所有的operations

for op in tf.get_default_graph().get_operations():
    print(op.name)

假如你是这个模型的作者,除了清楚的给出operations的名字,还可以创建一个collection,来把所有重要的operations都放在里面

for op in (X, y, accuracy, training_op):
    tf.add_to_collection("my_important_ops", op)

然后使用你模型的人,可以很快速的调用你的模型,

X, y, accuracy, training_op = tf.get_collection("my_important_ops")

然后你就可以加载权重,并且用你的数据来训练。

with tf.Session() as sess:
    saver.restore(sess, "./my_model_final.ckpt")

    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})
        accuracy_val = accuracy.eval(feed_dict={X: X_valid, y: y_valid})
        print(epoch, "Validation accuracy:", accuracy_val)

    save_path = saver.save(sess, "./my_new_model_final.ckpt")    

假如你有构建graph图的代码,那么就不需要使用import_meta_graph()方法,即上面的都不需要做,直接加载权重就可以,前提是图的构建要和原始的图一样,多添加层或者修改层会报错。

使用import_meta_graph()加载的是完整的整个图,但是你可以忽略某些层,在上面进行修改,随意添加层(如文章开头的那样)。

reset_graph()

n_hidden4 = 20  # new layer
n_outputs = 10  # new layer

saver = tf.train.import_meta_graph("./my_model_final.ckpt.meta")

X = tf.get_default_graph().get_tensor_by_name("X:0")
y = tf.get_default_graph().get_tensor_by_name("y:0")

hidden3 = tf.get_default_graph().get_tensor_by_name("dnn/hidden4/Relu:0")

new_hidden4 = tf.layers.dense(hidden3, n_hidden4, activation=tf.nn.relu, name="new_hidden4")
new_logits = tf.layers.dense(new_hidden4, n_outputs, name="new_outputs")

with tf.name_scope("new_loss"):
    xentropy = tf.nn.sparse_softmax_cross_entropy_with_logits(labels=y, logits=new_logits)
    loss = tf.reduce_mean(xentropy, name="loss")

with tf.name_scope("new_eval"):
    correct = tf.nn.in_top_k(new_logits, y, 1)
    accuracy = tf.reduce_mean(tf.cast(correct, tf.float32), name="accuracy")

with tf.name_scope("new_train"):
    optimizer = tf.train.GradientDescentOptimizer(learning_rate)
    training_op = optimizer.minimize(loss)

init = tf.global_variables_initializer()
new_saver = tf.train.Saver()


with tf.Session() as sess:
    init.run()
    saver.restore(sess, "./my_model_final.ckpt")

    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})
        accuracy_val = accuracy.eval(feed_dict={X: X_valid, y: y_valid})
        print(epoch, "Validation accuracy:", accuracy_val)

    save_path = new_saver.save(sess, "./my_new_model_final.ckpt")

在使用Saver取回图的过程中,必须指定你需要恢复的变量,否则tensorflow会提示图不匹配。

假如有原始的架构,也可以直接在原始架构上修改。只不过多了一步,在图中定义Saver时,需要把恢复的变量传入。

reset_graph()

n_inputs = 28 * 28  # MNIST
n_hidden1 = 300 # reused
n_hidden2 = 50  # reused
n_hidden3 = 50  # reused
n_hidden4 = 20  # new!
n_outputs = 10  # new!

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=tf.nn.relu, name="hidden1")       # reused
    hidden2 = tf.layers.dense(hidden1, n_hidden2, activation=tf.nn.relu, name="hidden2") # reused
    hidden3 = tf.layers.dense(hidden2, n_hidden3, activation=tf.nn.relu, name="hidden3") # reused
    hidden4 = tf.layers.dense(hidden3, n_hidden4, activation=tf.nn.relu, name="hidden4") # new!
    logits = tf.layers.dense(hidden4, n_outputs, name="outputs")                         # new!

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")

with tf.name_scope("eval"):
    correct = tf.nn.in_top_k(logits, y, 1)
    accuracy = tf.reduce_mean(tf.cast(correct, tf.float32), name="accuracy")

with tf.name_scope("train"):
    optimizer = tf.train.GradientDescentOptimizer(learning_rate)
    training_op = optimizer.minimize(loss)



reuse_vars = tf.get_collection(tf.GraphKeys.GLOBAL_VARIABLES,
                               scope="hidden[123]") # regular expression
restore_saver = tf.train.Saver(reuse_vars) # to restore layers 1-3

init = tf.global_variables_initializer()
saver = tf.train.Saver()

with tf.Session() as sess:
    init.run()
    restore_saver.restore(sess, "./my_model_final.ckpt")

    for epoch in range(n_epochs):                                            # not shown in the book
        for X_batch, y_batch in shuffle_batch(X_train, y_train, batch_size): # not shown
            sess.run(training_op, feed_dict={X: X_batch, y: y_batch})        # not shown
        accuracy_val = accuracy.eval(feed_dict={X: X_valid, y: y_valid})     # not shown
        print(epoch, "Validation accuracy:", accuracy_val)                   # not shown

    save_path = saver.save(sess, "./my_new_model_final.ckpt")

Reusing Models from Other Frameworks

首先取出我们需要重用的W和b。和常规不同,每一个tensorflow变量都有一个关联分配操作(associated assignment operation
),此操作用来初始化。通过得到这些分配操作的handle(和变量拥有相同的名字,后面加上/Assign)来开始。我们还需要得到每个分配操作的第二个输入量的handle,在这个例子中,第二个输入量对应一个值,这个值用来初始化变量。只需要在run init中把需要初始化的值输入就可以。

original_w = [[1., 2., 3.], [4., 5., 6.]] # Load the weights from the other framework
original_b = [7., 8., 9.]                 # Load the biases from the other framework

X = tf.placeholder(tf.float32, shape=(None, n_inputs), name="X")
hidden1 = tf.layers.dense(X, n_hidden1, activation=tf.nn.relu, name="hidden1")
# [...] Build the rest of the model

# Get a handle on the assignment nodes for the hidden1 variables
graph = tf.get_default_graph()
assign_kernel = graph.get_operation_by_name("hidden1/kernel/Assign")
assign_bias = graph.get_operation_by_name("hidden1/bias/Assign")
init_kernel = assign_kernel.inputs[1]
init_bias = assign_bias.inputs[1]

init = tf.global_variables_initializer()

with tf.Session() as sess:
    sess.run(init, feed_dict={init_kernel: original_w, init_bias: original_b})
    # [...] Train the model on your new task
    print(hidden1.eval(feed_dict={X: [[10.0, 11.0]]}))  # not shown in the book
    #[[ 61.  83. 105.]]

使用tf.layers.dense()函数创建的层,weights变量在里面叫做kernel,biases变量叫做bias。

Freezing the Lower Layers

 假如把DNN中较低的层固定,那么高的层会比较容易训练。为了冻结低阶的层,其中有一种办法就是:给优化器一个列表,这个列表中包含需要训练的变量,把那些不需要训练的变量排除在外。

train_vars = tf.get_collection(tf.GraphKeys.TRAINABLE_VARIABLES,
                               scope="hidden[34]|outputs")
training_op = optimizer.minimize(loss, var_list=train_vars)

上文和下文中的trainable_variables和global_variables在这个例子中是一样的,有的例子中会有不需要训练的变量,这时候就不一样。上部分代码 第一行取出了第3层,第4层,以及输出层的所有需要训练的变量。然后把它作为参数输入minimize()中。上部分代码只训练3,4层以及输出,下部分代码取出1,2,3层权重。

reuse_vars = tf.get_collection(tf.GraphKeys.GLOBAL_VARIABLES,
                               scope="hidden[123]") # regular expression
restore_saver = tf.train.Saver(reuse_vars) # to restore layers 1-3

init = tf.global_variables_initializer()
saver = tf.train.Saver()

with tf.Session() as sess:
    init.run()
    restore_saver.restore(sess, "./my_model_final.ckpt")

    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})
        accuracy_val = accuracy.eval(feed_dict={X: X_valid, y: y_valid})
        print(epoch, "Validation accuracy:", accuracy_val)

    save_path = saver.save(sess, "./my_new_model_final.ckpt")

 

第二种方法是添加stop_gradient()层,在这个层之下的所有层都会被冻结。

with tf.name_scope("dnn"):
    hidden1 = tf.layers.dense(X, n_hidden1, activation=tf.nn.relu,
                              name="hidden1") # reused frozen
    hidden2 = tf.layers.dense(hidden1, n_hidden2, activation=tf.nn.relu,
                              name="hidden2") # reused frozen
    hidden2_stop = tf.stop_gradient(hidden2)
    hidden3 = tf.layers.dense(hidden2_stop, n_hidden3, activation=tf.nn.relu,
                              name="hidden3") # reused, not frozen
    hidden4 = tf.layers.dense(hidden3, n_hidden4, activation=tf.nn.relu,
                              name="hidden4") # new!
    logits = tf.layers.dense(hidden4, n_outputs, name="outputs") # new!

接下来的训练和上面的训练过程一样。

reuse_vars = tf.get_collection(tf.GraphKeys.GLOBAL_VARIABLES,
                               scope="hidden[123]") # regular expression
restore_saver = tf.train.Saver(reuse_vars) # to restore layers 1-3

init = tf.global_variables_initializer()
saver = tf.train.Saver()

with tf.Session() as sess:
    init.run()
    restore_saver.restore(sess, "./my_model_final.ckpt")

    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})
        accuracy_val = accuracy.eval(feed_dict={X: X_valid, y: y_valid})
        print(epoch, "Validation accuracy:", accuracy_val)

    save_path = saver.save(sess, "./my_new_model_final.ckpt")

 

Caching the Frozen Layers

 因为冻结层的权重不会改变,而每次训练,训练集需要多次经过隐藏层,因此可以让所有训练集通过冻结层,把冻结层的输出作为新的训练集。
 

import numpy as np

n_batches = len(X_train) // batch_size  ##275

with tf.Session() as sess:
    init.run()
    restore_saver.restore(sess, "./my_model_final.ckpt")
    
    h2_cache = sess.run(hidden2, feed_dict={X: X_train})
    h2_cache_valid = sess.run(hidden2, feed_dict={X: X_valid}) # not shown in the book

    for epoch in range(n_epochs):
        shuffled_idx = np.random.permutation(len(X_train))
        hidden2_batches = np.array_split(h2_cache[shuffled_idx], n_batches)
        y_batches = np.array_split(y_train[shuffled_idx], n_batches)
        for hidden2_batch, y_batch in zip(hidden2_batches, y_batches):
            sess.run(training_op, feed_dict={hidden2:hidden2_batch, y:y_batch})

        accuracy_val = accuracy.eval(feed_dict={hidden2: h2_cache_valid, # not shown
                                                y: y_valid})             # not shown
        print(epoch, "Validation accuracy:", accuracy_val)               # not shown

    save_path = saver.save(sess, "./my_new_model_final.ckpt")

注意这里的training_op为只训练3,4,以及输出层。

train_vars = tf.get_collection(tf.GraphKeys.TRAINABLE_VARIABLES,
                               scope="hidden[34]|outputs")
training_op = optimizer.minimize(loss, var_list=train_vars)

hidden2_batches为一个列表,长度为n_batches的大小,为275.列表中的每一个值都是一个numpy数组。

zip() 函数用于将可迭代的对象作为参数,将对象中对应的元素打包成一个个元组,然后返回由这些元组组成的对象,这样做的好处是节约了不少的内存。

 Tweaking, Dropping, or Replacing the Upper Layers

首先冻结所有层,训练模型,查看效果。然后试着解锁最上的一层(或者最上的两层),训练的数据越多,能解锁的层也就越多。假如没有获得好的效果,而且训练数据比较少,丢弃最上的一层或者两层,冻结余下的所有层。你可以重复这么做,直到找到合适的重用层的个数。假如训练数据比较多,那么可以替换最上层,或者在最上层以上多增添几层。

Model Zoos

tensorflow有自己model zoo https://github.com/tensorflow/models,类似VGG,inception,ResNet,里面包括代码,预训练模型,以及下载图片数据集的工具。

Unsupervised Pretraining

假如你的数据量太小,并且不能找到一个已经训练好的类似任务的模型 。可以进行无监督的预训练。使用大量的无标注的训练集,采用无监督特征检测算法,例如受限玻尔兹曼机,自编码器,一层一层的单独训练。每层的训练都以上一层的输出为基础。Each layer is trained on the output of the previously trained layers (all layers except the one being trained are frozen).所有层,除了在训练的那一层,都被冻结了。一旦所有的层都以这种方式预训练,你能用标注的数据进行微调。

直到2010,梯度消失问题缓解才渐渐使用纯粹的反向传播训练神经网络。现在更多的使用自编码器进行预训练,而不是受限玻尔兹曼机。

Pretraining on an Auxiliary Task

可以建立一个辅助任务,这个辅助任务可以得到大量的标注数据。然后使用这个辅助任务的低阶层当作特征检测器来为你的实际任务重用。

例如你想要做一个人脸识别任务recognize faces,你可以先从互联网上下载很多人的图片,可以先做一个可以鉴定两张图片是否是同一张图片的神经网络。这个神经网络的低层肯定学到了人脸的低阶特征。

又或者先产生很多训练实例,全部标注为好,然后人为破坏它,破坏之后的训练实例标注为差。例如先建立一个判断句子好坏的神经网络,差的实例通过随机改变句子中的一个单词来获得,例如the dog sleeps,变为the dog they,只要这个神经能正确判断,这个神经网络的低阶层可以用作自然语言处理。

Faster Optimizers

Momentum Optimization

常规的梯度下降不考虑之前的梯度是怎么样的,假如局部的梯度很小,那么梯度更新的会非常缓慢。

使用momentum可以使得算法快速找到局部最优

optimizer = tf.train.MomentumOptimizer(learning_rate=learning_rate,
                                        momentum=0.9)

Nesterov Accelerated Gradient

与常规的momentum不同的是,不是算当前的梯度值,而是算在 θ + βm处的梯度值。

 因为通常momentum的方向会朝着最佳值的方向前进,所以使用朝最优值更近一点距离的梯度,会更精确。收敛的更快。

optimizer = tf.train.MomentumOptimizer(learning_rate=learning_rate,
                                       momentum=0.9, use_nesterov=True)

Adam Optimization

optimizer = tf.train.AdamOptimizer(learning_rate=learning_rate)

 不需要怎么调学习率,可以设置为0.001,这是一种自适应的学习法。

大多数算法都会产生稠密的模型,意思为非0.假如你需要更快的运行速度,或者少量的内存,你需要稀疏模型。

你可以这么做,把过小的权重设置为0,或者使用L1正则,L1正则会尽可能的把权重设置为0.

可以使用Follow Te Regularized Leader (FTRL)技术结合L1正则使得模型更稀疏。在tensorflow,FTRLOptimizer类中。

Learning Rate Scheduling

在学习开始的时候使用大的学习速率,一旦停止快速更新,那么就减少学习速率。比直接使用一个常量学习速率好。

exponential scheduling与performance scheduling 的效果都很好,但是exponential scheduling比较容易实现,收敛的也更快。

  1. 分段预先确定常量学习率(Predetermined piecewise constant learning rate):例如首先设置学习率为0.1,然后50个epochs为0.001.尽管能工作的很好,但是需要调试以找到何时使用最佳学习率。
  2. Performance scheduling:在验证集上每N次计算误差,误差不在下降的时候,学习率乘以一个因子来降低数值。 
  3. Exponential scheduling:利用迭代次数t,建立函数式\eta _{0}10^{\frac{-t}{r}},函数值的值为学习率。能工作的很好,只是需要调整学习率。这个式子就好比每进行10次迭代,那么学习率就在原始学习率的基础上下降1/10。
  4. Power scheduling:函数式为\eta_{0}(1+\frac{t}{r})^{c},超参数c一般设置为1.比上面的指数下降的要慢。

 用tensorflow实现:

initial_learning_rate = 0.1
decay_steps = 10000
decay_rate = 1/10
global_step = tf.Variable(0, trainable=False, name="global_step")
learning_rate = tf.train.exponential_decay(initial_learning_rate, global_step,
                                           decay_steps, decay_rate)
optimizer = tf.train.MomentumOptimizer(learning_rate, momentum=0.9)
training_op = optimizer.minimize(loss, global_step=global_step)

这里面\eta _{0}为0.1,r=10000,就是说每进行10000次迭代,那么学习率就下降为原来的1/10.因为在training_op中传入了全局次数变量,在每进行一次训练都会增加它。

AdaGrad, RMSProp, and Adam这类算法在训练的时候能自动的减少学习率。所以不需要额外的学习策略。

Early Stopping

在训练过程中,当验证集的误差开始下降的时候就停止训练。 

ℓ1 and ℓ2 Regularization

L1和L2可以限制神经网络的连接权重。常规使用tensorflow如下:

n_inputs = 28 * 28  # MNIST
n_hidden1 = 300
n_outputs = 10

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=tf.nn.relu, name="hidden1")
    logits = tf.layers.dense(hidden1, n_outputs, name="outputs")
W1 = tf.get_default_graph().get_tensor_by_name("hidden1/kernel:0")
W2 = tf.get_default_graph().get_tensor_by_name("outputs/kernel:0")

scale = 0.001 # l1 regularization hyperparameter

with tf.name_scope("loss"):
    xentropy = tf.nn.sparse_softmax_cross_entropy_with_logits(labels=y,
                                                              logits=logits)
    base_loss = tf.reduce_mean(xentropy, name="avg_xentropy")
    reg_losses = tf.reduce_sum(tf.abs(W1)) + tf.reduce_sum(tf.abs(W2))
    loss = tf.add(base_loss, scale * reg_losses, name="loss")

 tensorflow中的函数get_variable() 或者 tf.layers.dense()已经集成了正则化的方法,你只需要传入需要正则的方法就行。

scale = 0.001
my_dense_layer = partial(
    tf.layers.dense, activation=tf.nn.relu,
    kernel_regularizer=tf.contrib.layers.l1_regularizer(scale))

with tf.name_scope("dnn"):
    hidden1 = my_dense_layer(X, n_hidden1, name="hidden1")
    hidden2 = my_dense_layer(hidden1, n_hidden2, name="hidden2")
    logits = my_dense_layer(hidden2, n_outputs, activation=None,
                            name="outputs")

tensorflow在图内部建立计算对应每层权重正则。tensorflow会自动的把这些节点放到一个特殊的collection中,这个collection中包含所有的正则损失。

with tf.name_scope("loss"):                                     # not shown in the book
    xentropy = tf.nn.sparse_softmax_cross_entropy_with_logits(  # not shown
        labels=y, logits=logits)                                # not shown
    base_loss = tf.reduce_mean(xentropy, name="avg_xentropy")   # not shown
    reg_losses = tf.get_collection(tf.GraphKeys.REGULARIZATION_LOSSES)
    loss = tf.add_n([base_loss] + reg_losses, name="loss")

最后需要把所有层的正则放到总的损失中。

Dropout

最流行的正则方法当属dropout,即使是state-of-art的方法,使用dropout也能获得大约2%的提升。

在训练阶段,除了输出层以外(包括输入层),所有的层 都有一个比率p,此层中p比率的节点会被忽略。在下一次训练中,又会被激活。通常p设置为0.5.

使用dropout的神经网络更鲁棒。

使用dropout可以使得其中的一个节点不依赖旁边节点,也不会依赖一些输入节点,此节点会关注连接它的所有输入。然后就使得神经网络对于输入不敏感。

另一种理解 dropout是:使用dropout的神经网络在训练的每一步都产生一个不同的网络。dropout可以看成所有这些网络值得平均。

需要注意得是:假如不做任何操作,以p(keep probability)=0.5得情况为例,那么在测试的时候,每个神经元会连接 2倍于训练时的神经元。为避免此情况,需要在测试的时候把权重降低为原来的0.5(1-p)。还有一种替换做法,可以在训练的时候,给每个输出的神经元除以p.  这里的方法是,因为dropout使得测试的时候输出扩大了两倍,要使得输出不变,一种方法为直接使得连接的权重减小,另一个方法是在训练的时候使得上一层神经元的数值变大,迫使连接权重减小。

在tensorflow中使用

 tf.layers.dropout() 

此函数随机丢弃其中的一些节点(把他们设置为0),并且让剩余的神经元除以keep_probability,在训练结束之后,这些函数就不起作用了。

training = tf.placeholder_with_default(False, shape=(), name='training')

dropout_rate = 0.5  # == 1 - keep_prob
X_drop = tf.layers.dropout(X, dropout_rate, training=training)

with tf.name_scope("dnn"):
    hidden1 = tf.layers.dense(X_drop, n_hidden1, activation=tf.nn.relu,
                              name="hidden1")
    hidden1_drop = tf.layers.dropout(hidden1, dropout_rate, training=training)
    hidden2 = tf.layers.dense(hidden1_drop, n_hidden2, activation=tf.nn.relu,
                              name="hidden2")
    hidden2_drop = tf.layers.dropout(hidden2, dropout_rate, training=training)
    logits = tf.layers.dense(hidden2_drop, n_outputs, name="outputs")

需要注意的是此函数

tf.layers.dropout()  ###1

与以下函数不同。

tf.nn.dropout()   ###2

上面第一种那个函数在非训练阶段会自动关闭,而第二种不会

需要像前面的Batch Normalization一样,在训练阶段把dropout的training设置为true,非训练阶段设置为默认值False

过拟合就增加丢弃比率,未拟合就降低丢弃比率。

Max-Norm Regularization

这种正则的定义为:给所有权重限制,满足\left \| w \right \|_{2}\leq r,r为MAX-NORM超参数。实际中我们可以这么做,在一次训练完之后计算\left \| w \right \|_{2},然后裁剪w,通过

减少r,增加了正则的数量,帮助减少过拟合。假如不使用batch normalization的话,还可以缓解梯度消失/爆炸。 

threshold = 1.0
weights = tf.get_default_graph().get_tensor_by_name("hidden1/kernel:0")
clipped_weights = tf.clip_by_norm(weights, clip_norm=threshold, axes=1)
clip_weights = tf.assign(weights, clipped_weights)

第一行获得一个第一层隐藏层权重的handle.随后使用clip_by_norm()函数建立一个operation,此函数会沿着第2个axis裁剪weights的值,然后每一个行向量的最大范数为1.最后进行赋值操作。

threshold = 1.0
weights = tf.get_default_graph().get_tensor_by_name("hidden1/kernel:0")
clipped_weights = tf.clip_by_norm(weights, clip_norm=threshold, axes=1)
clip_weights = tf.assign(weights, clipped_weights)

第二层也进行相同的操作

weights2 = tf.get_default_graph().get_tensor_by_name("hidden2/kernel:0")
clipped_weights2 = tf.clip_by_norm(weights2, clip_norm=threshold, axes=1)
clip_weights2 = tf.assign(weights2, clipped_weights2)

随后在训练要多进行裁剪操作。如下 

with tf.Session() as sess:                                              # not shown in the book
    init.run()                                                          # not shown
    for epoch in range(n_epochs):                                       # not shown
        for X_batch, y_batch in shuffle_batch(X_train, y_train, batch_size): # not shown
            sess.run(training_op, feed_dict={X: X_batch, y: y_batch})
            clip_weights.eval()
            clip_weights2.eval()                                        # not shown
        acc_valid = accuracy.eval(feed_dict={X: X_valid, y: y_valid})   # not shown
        print(epoch, "Validation accuracy:", acc_valid)                 # not shown

    save_path = saver.save(sess, "./my_model_final.ckpt")   

按照以上方法可以运行,但是太繁琐,采用如下方法。定义一个max_norm_regularizer()函数

def max_norm_regularizer(threshold, axes=1, name="max_norm",
                         collection="max_norm"):
    def max_norm(weights):
        clipped = tf.clip_by_norm(weights, clip_norm=threshold, axes=axes)
        clip_weights = tf.assign(weights, clipped, name=name)
        tf.add_to_collection(collection, clip_weights)
        return None # there is no regularization loss term
    return max_norm

max-norm regularization不需要在总损失后面添加正则项。这也是max_norm()函数返回None的原因。但是还是需要在训练的每一步都运行clip_weights。这也就是把clip_weights放到一个collection里面的原因。按照如上定义可以直接在tf.layers.dense中添加正则方法。

max_norm_reg = max_norm_regularizer(threshold=1.0)

with tf.name_scope("dnn"):
    hidden1 = tf.layers.dense(X, n_hidden1, activation=tf.nn.relu,
                              kernel_regularizer=max_norm_reg, name="hidden1")
    hidden2 = tf.layers.dense(hidden1, n_hidden2, activation=tf.nn.relu,
                              kernel_regularizer=max_norm_reg, name="hidden2")
    logits = tf.layers.dense(hidden2, n_outputs, name="outputs")

训练还是和往常一样,只不过在训练之后需要多运行裁剪操作

clip_all_weights = tf.get_collection("max_norm")

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})
            sess.run(clip_all_weights)
        acc_valid = accuracy.eval(feed_dict={X: X_valid, y: y_valid}) # not shown
        print(epoch, "Validation accuracy:", acc_valid)   

Data Augmentation

数据增强也是 一种正则技术。理想情况下:人们应该无法分辨图片是增强过的还是非增强过的。增加白噪声不会帮助,数据增强的改变应该是可以学习的。 

数据增强包括:平移,旋转,尺寸变化。让模型对于位置,方向,目标尺寸很宽容。假如你想让模型对于光照很宽容,那么你可以使用不同量的光照强度来产生图片。

 在训练过程中动态产生数据增强的图片是最好的(在程序中直接数据增强),而不是浪费存储空间以及带宽(存在电脑中,或者从网上下载)。tensorflow提供image manipulation operations例如transposing (shifting), rotating, resizing, flipping,and cropping, as well as adjusting the brightness, contrast, saturation, and hue

Practical Guidelines

默认DNN设置:

 在此设置之上,还需要又一些微调:

  1. 无法找到合适学习率的情况下,例如收敛太慢,增加学习率导致收敛快了但是精度不是全局最优,需要使用指数衰减。
  2. 训练集太小,需要使用数据增强。
  3. 需要稀疏模型,可以使用L1正则,还可以把较小权重值直接归为0。假如你需要一个更稀疏的模型,那么可以使用FTRL来代替Adam优化,并上L1正则。
  4. 假如你需要一个很快的模型,可以去掉Batch-Normalization,用leaky Relu去代替ELU。还可以使用稀疏模型的方法。

Exercises

  1.  把所有值都设置为相同的随机值是不可取的,不能打破对称。
  2. 可以把bias设置为0
  3. ELU与RELU相比的优点:1.ELU考虑了输出为负的情况,对于某一层所有节点的输出平均为0,不像RELU一样,只会输出正值。缓解了梯度消失。2.在所有地方导数都不为负,避免了神经元的死亡(RELU中会出现)。3.ELU处处平滑,不像RElu一样在0处不连续,在0处的这种突变会导致梯度下降变慢。
  4. ELU一般为默认。假如你想要程序变快,那么可以使用leaky RELU,常规ReLU的简单性使得ReLU被广泛使用,虽然它的效果比不过ELU和leaky ReLU。tanh用于输出-1,1之间的值,现在很少用。logistic activation function通常用于输出概率的地方,它的输出为0,1,一般也不用于隐藏层,除了在自编码器中。softmax通常用在输出为多个独立分类的地方。
  5. momentum的值设置为0.999会导致模型收敛的更慢。
  6. 稀疏模型的获得方法,1.把过小的权重值归为0。2.使用L1正则。3.使用L1正则加上FTRL优化器。
  7. dropout会使得训练变慢,假如把p设置为0.5,那么变慢2倍。但是在测试的时候每影响。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值