深层神经网络(三)神经网络的优化

梯度下降、随机梯度下降、mini-batch

众所周知,梯度下降算法进行一轮参数的更新需要遍历一遍训练集。显然,这是很费时的。于是,后来产生了随机梯度下降的方法(SGD),我们用一条数据去代替整个训练集进行一轮参数的更新。但是,一条数据损失更小不代表整个数据集的损失就小了。所以我们通常采用两种方法的折中:mini-batch。即每次从训练集中选取batch条数据来进行参数的更新。

实现代码:

batch_size = n

x = tf.placeholder(tf.float32, shape=(batch_size, 2), name="x-input")
y = tf.placeholder(tf.float32, shape=(batch_size, 1), name="y-input")

loss=...
train_step = tf.train.AdamOptimizer(0.001).minimize(loss)

with tf.Session() as sess:
    for i in range(STEPS):
        current_X, current_Y = ...
        sess.run(train_step, feed_dict={x: current_X, y: current_Y})

学习率的设置

学习率过高,可能会导致参数左右横跳,而学习率过低又会造成参数收敛较慢的问题。所以学习率的选取非常的重要。

TensorFlow提供了一种更加灵活的学习率设置方法——指数衰减法

以下代码实现了该功能:

decayed_learning_rate = learning_rate *decay_rate ^ (global_step / decay_steps)

换成数学公式就是:

decayed_learning_rate:当前学习速率

learning_rate:初始学习速率

decay_rate:衰减系数

global_step:当前参数更新次数

decay_steps:衰减速度(类似参数每更新多少次进行衰减)

其中learning_rate,decay_rate,decay_steps是可以自己设置的。global_step初始设置为0,每更新一次参数+1。由此计算出当前的学习速率。

从公式可以看出:

  • decay_rate越大,衰减越快。
  • decay_steps越大,指数部分增加的就越慢,衰减越慢

TensorFlow提供的api及代码实现:

global_step = tf.Variable(0)

#初始学习率为0.1,global_step随参数更新次数+1而+1,decay_steps=100,decay_rate=0.96
learning_rate = tf.train.exponential_decay(0.1, global_step, 100, 0.96, staircase=True)

learning_step = tf.train.GradientDescentOptimizer(learning_rate).minimize(loss, global_step=global_step)

值得注意的是global_step要单独声明一个变量,初始为0。

参数staircase=True时,指的是采用阶梯下降方式。意思是指数部分(global_step / decay_steps)取整数,每当进行了decay_steps轮的参数更新之后,学习率才会进行改变。

正则化

在实际过程中,我们可能会出现过拟合的情况,解决这种问题的方法就是正则化。

我们之前优化的对象为,现加入正则项,去优化,其中为权值矩阵。

刻画的模型复杂度,刻画的是模型复杂度的损失程度。

通常情况有两种计算方式:

正则化:

正则化:

正则化和正则化有以下区别:

  • 正则化会让参数变的更加稀疏
  • 正则化可导,而1正则化则不可导,所以正则化更容易优化

在实践中,我们也可以一起使用:

采用正则化实现的代码:

w = tf.Variable(tf.random_normal([2, 1], stddev=1, seed=1))
y = tf.matmul(x, w)
loss = tf.reduce_mean(tf.square(y_ - y)) +
    tf.contrib.layers.l2_regularizer(lambda)(w)

当神经网络有很多层时,我们可以通过集合来管理正则化项,提高代码可读性:

import tensorflow as tf

#得到权值矩阵,并将其l2正则化添加到‘losses’的集合当中
def get_weight(shape, lambda)
    var = tf.Variable(tf.random_normal(shape), dtype = tf.float32)
    tf.add_to_collection('losses', tf.contrib.layers.l2_regularizer(lambda)(var))
    return var

x = tf.placeholder(tf.float32, shape=(None, 2))
y_ = tf.placeholder(tf.float32, shape=(None, 2))

batch_size = 8
#每层结点数
layer_dimension = [2, 10, 10, 10, 1]
#层数
n_layers = len(layer_dimension)
#当前层
cur_layer = x
#输入层的结点数
in_dimension = layer_dimension[0]

#网络全连接
for i in range(1, n_layers):
    out_dimension = layer_dimension[i]
    weight = get_weight([in_dimension, out_dimension], 0.001)
    bias = tf.Variable(tf.constant(0.1, shape=[out_dimension]))
    cur_layer = tf.nn.relu(tf.matmul(cur_layer, weight) + bias)
    in_dimension = layer_dimension[i]

#定义mse损失
mse_loss = tf.reduce_mean(tf.square(y_ - cur_layer))
#将mse添加到‘losses’集合
tf.add_to_collection('losses', mse_loss)
#把‘losses’集合的所有项加起来作为最终的loss函数
loss = tf.add_n(tf.get_collection('losses'))

滑动平均模型

在更新参数上,我们也可以通过滑动平均模型来提高最终模型在测试数据上的表现。TensorFlow提供了tf.train.ExponentialMovingAverage来实现了滑动平均模型。

滑动模型会保存一个变量的影子变量,影子变量的初始值为该参数的初始值,影子变量更新方式如下:

shadow_variable = decay * shadow_variable + (1-decay) * variable

shadow_variable:影子变量

decay:衰减率

variable:待更新的变量

decay决定了参数更新的速度,通常我们设置为一个比较大的值,例如:0.99。但是这在刚开始时就取这么的值会使得参数收敛的很,于是ExponentialMovingAverage还提供了num_updates参数来提高更新的速度:

num_updates:为更新参数的轮数,我们一般初始化为0,然后在运行优化函数时作为global_step的参数传入,它会随着更新轮数+1而+1,在上文指数衰减时也用到过。

我们可以看到,随着num_updates增加,后一项逐渐增大趋近于1。

下面代码模拟了训练时滑动平均模型的使用:

import tensorflow as tf

#v1为采用滑动平均模型更新的参数,参数必须是实数才能用这个模型
v1 = tf.Variable(0, dtype=tf.float)
#用来模拟参数更新的轮数
step = tf.Variable(0, trainable=False)

#声明一个ExponentialMovingAverage类,decay=0.99,num_updates=step
ema = tf.train.ExponentialMovingAverage(0.99, step)
#将要用这个模型的参数加入,要一次性传入而不能分次添加,因为每次调用apply()都会更新这个参数。通常采用
#tf.trainable_variables()将要训练的参数全部传入
maintain_averages_op = ema.apply([v1])

with tf.Session() as sess:
    #初始化变量
    init_op = tf.global_variables_initializer()
    sess.run(init_op)
    
    #ema.average(v1)获得v1活动平均之后的取值
    print(sess.run([v1, ema.average(v1)]))
    #[0.0, 0.0]
    
    #v1从0更新为5
    sess.run(tf.assign(v1, 5))
    sess.run(maintain_averages_op)
    print(sess.run([v1, ema.average(v1)]))
    #[5.0, 4.5]
    
    #v1更新为10,注意此时的v1的shadow为4.5,step模拟为10000
    sess.run(tf.assign(step, 10000))
    sess.run(tf.assign(v1, 10))
    sess.run(maintain_averages_op)
    print(sess.run([v1, ema.average(v1)]))
    #[10.0, 4.5549998]
    
    #继续滑动
    sess.run(maintain_averages_op)
    print(sess.run([v1, ema.average(v1)]))
    #[10.0, 4.6094499]

由此可见,滑动平均更新参数时,参数不会马上变成要更新的参数,而是通过decay控制更新速度来逐渐更新,向要更新的参数靠近。

你可能感兴趣的:(深层神经网络(三)神经网络的优化)