梯度下降、随机梯度下降、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控制更新速度来逐渐更新,向要更新的参数靠近。