Tensorflow中的指数学习率,指数滑动平均,global_step,num_updates,staircase参数等

TensorFlow笔记:指数衰减学习率

学习率决定了参数更新的幅度。通常我们希望在学习开始阶段提供一个较大的学习率,使得参数快速更新,达到最优解附近。然后随着训练的进行,我们希望在学习率随着训练次数的增加而减少,即在接近最优解的时候能够以较小的学习率逼近最优解
TensorFlow为我们提供了tf.train.exponential_decay()函数实现这个功能

tf.train.exponential_decay()函数

定义
tf.train.exponential_decay(
learning_rate,
global_step,
decay_steps,
decay_rate,
staircase=False,
name=None
)

解释
指数衰减学习率的计算方法如下
在这里插入图片描述
learning_rate 参数为初始的学习率, global_step 参数为当前的训练步数, decay_steps 参数设置了学习率衰减的速度,经过 decay_steps 后会进行一次衰减, decay_rate 参数则是衰减的比例, staircse 参数为真时会对 global_step/decay_step进行取整,从而学习率会呈阶梯式下降.

更多关于学习率的指数衰减请参见我的另外两篇博客:
https://blog.csdn.net/edward_zcl/article/details/89930262
https://blog.csdn.net/zxyhhjs2017/article/details/82383723


指数滑动平均

作用

假如我们训练模型迭代了100K,每2K步保存一个snapshot。在evaluation时, 我们可以只使用最后得到的model-100K,也可以通过cross validation选出一最佳的model,如model-98K。 但Googlers发现(https://www.tensorflow.org/versions/r1.0/api_docs/python/tf/train/ExponentialMovingAverage):

When training a model, it is often beneficial to maintain moving averages of the trained parameters. Evaluations that use averaged parameters sometimes produce significantly better results than the final trained values.

大概意思是对模型参数进行平均得到的模型往往比单个模型的结果要好很多,注意,他们使用了significantly better这个修饰, 虽然前边还有个sometimes.
于是, tensorflow里就有了tf.train.ExponentialMovingAverage这个接口。

在training时使用

文档中的例子:

# Create variables.
var0 = tf.Variable(...)
var1 = tf.Variable(...)
# ... use the variables to build a training model...

# Create an op that applies the optimizer.  This is what we usually
# would use as a training op.
opt_op = opt.minimize(my_loss, [var0, var1])

# Create an ExponentialMovingAverage object
ema = tf.train.ExponentialMovingAverage(decay=0.9999)

# Create the shadow variables, and add ops to maintain moving averages of var0 and var1.
maintain_averages_op = ema.apply([var0, var1])

# Create an op that will update the moving averages after each training step.  This is what we will use in place of the usual training op.
with tf.control_dependencies([opt_op]):
    training_op = tf.group(maintain_averages_op)

#...train the model by running training_op...

这个例子体现出使用MovingAverage的三个要素。
1. 指定decay参数创建实例:
ema = tf.train.ExponentialMovingAverage(decay=0.9999)
2. 对模型变量使用apply方法:
maintain_averages_op = ema.apply([var0, var1])
3. 在优化方法使用梯度更新模型参数后执行MovingAverage:

with tf.control_dependencies([opt_op]):
    training_op = tf.group(maintain_averages_op)

其中,tf.group将传入的操作捆绑成一个操作,详细可参考文档。

原理:影子变量与decay

apply方法会为每个变量(也可以指定特定变量)创建各自的shadow variable, 即影子变量。之所以叫影子变量,是因为它会全程跟随训练中的模型变量。影子变量会被初始化为模型变量的值,然后,每训练一个step,就更新一次。更新的方式为:

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

decay的值一般很接近于1,例如0.999, 0.9999。很明显, 这个操作会增加模型在训练过程中的稳定性。

还有一点值得指出——创建tf.train.ExponentialMovingAverage实例时还可以传入num_updates参数,一般使用global_step的值:

ema = tf.train.ExponentialMovingAverage(decay=0.9999, num_updates = tf_global_step)

它的作用让decay变成动态的,训练前期的值小,后期的值大。因为这时真实decay的计算方式为:

decay = min(decay, (1 + num_updates) / (10 + num_updates))

Restore from checkpoint

训练时若使用了ExponentialMovingAverage,在保存checkpoint时,不仅仅会保存模型参数,优化器参数(如Momentum), 还会保存ExponentialMovingAverageshadow variable
之前,我们可以直接使用以下代码restore模型参数, 但不会利用ExponentialMovingAverage的结果:

saver = tf.Saver()
saver.restore(sess, save_path)

若要使用ExponentialMovingAverage保存的参数:

variables_to_restore = ema.variables_to_restore()
saver = tf.train.Saver(variables_to_restore)
saver.restore(sess, save_path)

当然,还有其他方式可以实现同样的效果。
写了一个完整的可运行demo, tensorflow r1.1.0。
看看它的输出就一目了然了(字符串是变量名,数字是对应的变量值),两者加载的变量值是不同的:

variables in checkpoint:
    bias/ExponentialMovingAverage 0.664593
    bias/Momentum 4.12663
    weight [[ 0.01567289]
 [ 0.17180483]]
    weight/ExponentialMovingAverage [[ 0.10421171]
 [ 0.26470858]]
    weight/Momentum [[ 5.95625305]
 [ 6.24084663]]
    bias 0.602739

==============================================
variables restored not from ExponentialMovingAverage:
    weight:0 [[ 0.01567289]
 [ 0.17180483]]
    bias:0 0.602739

==============================================
variables restored from ExponentialMovingAverage:
    weight:0 [[ 0.10421171]
 [ 0.26470858]]
    bias:0 0.664593

主要记住并掌握影子变量,num_updates,以及衰减率,存储与恢复平均值与原值的方法。
以上参考自:
https://blog.csdn.net/weixin_35653315/article/details/74632110
两者指数函数其实很相似,但是用途很不一样,tensorflow其实还有一个指数损失函数,也是类似的,应用在特定领域里,这里就不多做介绍了。


这里给出几个实际例子:
可以先看看p大某老师的tensorflow的网课示例代码,很啰嗦。。
https://github.com/cj0012/AI-Practice-Tensorflow-Notes/blob/master/opt/opt4_6.py

#coding:utf-8
import tensorflow as tf

#1. 定义变量及滑动平均类
#定义一个32位浮点变量,初始值为0.0  这个代码就是不断更新w1参数,优化w1参数,滑动平均做了个w1的影子
w1 = tf.Variable(0, dtype=tf.float32)
#定义num_updates(NN的迭代轮数),初始值为0,不可被优化(训练),这个参数不训练
global_step = tf.Variable(0, trainable=False)
#实例化滑动平均类,给衰减率为0.99,当前轮数global_step
MOVING_AVERAGE_DECAY = 0.99
ema = tf.train.ExponentialMovingAverage(MOVING_AVERAGE_DECAY, global_step)
#ema.apply后的括号里是更新列表,每次运行sess.run(ema_op)时,对更新列表中的元素求滑动平均值。
#在实际应用中会使用tf.trainable_variables()自动将所有待训练的参数汇总为列表
#ema_op = ema.apply([w1])
ema_op = ema.apply(tf.trainable_variables())

#2. 查看不同迭代中变量取值的变化。
with tf.Session() as sess:
    # 初始化
    init_op = tf.global_variables_initializer()
    sess.run(init_op)
	#用ema.average(w1)获取w1滑动平均值 (要运行多个节点,作为列表中的元素列出,写在sess.run中)
	#打印出当前参数w1和w1滑动平均值
    print "current global_step:", sess.run(global_step)
    print "current w1", sess.run([w1, ema.average(w1)]) 
    
    # 参数w1的值赋为1
    sess.run(tf.assign(w1, 1))
    sess.run(ema_op)
    print "current global_step:", sess.run(global_step)
    print "current w1", sess.run([w1, ema.average(w1)]) 
    
    # 更新global_step和w1的值,模拟出轮数为100时,参数w1变为10, 以下代码global_step保持为100,每次执行滑动平均操作,影子值会更新 
    sess.run(tf.assign(global_step, 100))  
    sess.run(tf.assign(w1, 10))
    sess.run(ema_op)
    print "current global_step:", sess.run(global_step)
    print "current w1:", sess.run([w1, ema.average(w1)])       
    
    # 每次sess.run会更新一次w1的滑动平均值
    sess.run(ema_op)
    print "current global_step:" , sess.run(global_step)
    print "current w1:", sess.run([w1, ema.average(w1)])

    sess.run(ema_op)
    print "current global_step:" , sess.run(global_step)
    print "current w1:", sess.run([w1, ema.average(w1)])

    sess.run(ema_op)
    print "current global_step:" , sess.run(global_step)
    print "current w1:", sess.run([w1, ema.average(w1)])

    sess.run(ema_op)
    print "current global_step:" , sess.run(global_step)
    print "current w1:", sess.run([w1, ema.average(w1)])

    sess.run(ema_op)
    print "current global_step:" , sess.run(global_step)
    print "current w1:", sess.run([w1, ema.average(w1)])

    sess.run(ema_op)
    print "current global_step:" , sess.run(global_step)
    print "current w1:", sess.run([w1, ema.average(w1)])

#更改MOVING_AVERAGE_DECAY 为 0.1  看影子追随速度

"""
current global_step: 0
current w1 [0.0, 0.0]
current global_step: 0
current w1 [1.0, 0.9]
current global_step: 100
current w1: [10.0, 1.6445453]
current global_step: 100
current w1: [10.0, 2.3281732]
current global_step: 100
current w1: [10.0, 2.955868]
current global_step: 100
current w1: [10.0, 3.532206]
current global_step: 100
current w1: [10.0, 4.061389]
current global_step: 100
current w1: [10.0, 4.547275]
current global_step: 100
current w1: [10.0, 4.9934072]
"""

主要解释了指数滑动平均操作,通过assign以及原始值不变,不断逼近原始w1来实现变化。。需要注意,如果使用python3,请自行在print后面加上括号。
但上面的代码美中不足的是,global_step并没有发挥作用,因为实际上在指数滑动平均中会使用随着训练变化的global_step。而这个global往往在优化器中进行默认的自增更新。

参见我的博客代码中的相应部分: https://blog.csdn.net/diligent_321/article/details/53130913
或者看看下面的这个描述:

TensorFlow中global_step的简单分析

https://blog.csdn.net/leviopku/article/details/78508951


综合以上两点,我自己写了一个综合使用指数衰减学习率,指数滑动平均,以及优化器更新参数的小模型,大家可以在自己的电脑上一看究竟:

import os
print(os.path.dirname(__file__))

import tensorflow as tf
import numpy as np
 
x = tf.placeholder(tf.float32, shape=[None, 1], name='x')
y = tf.placeholder(tf.float32, shape=[None, 1], name='y')
w = tf.Variable(tf.constant(3.0))
 
global_steps = tf.Variable(0, trainable=False)

mv_decay = 0.99
ema = tf.train.ExponentialMovingAverage(mv_decay, global_steps)
ema_op = ema.apply(tf.trainable_variables())    #所有待优化参数列表求华东平均

 
 
learning_rate = tf.train.exponential_decay(0.1, global_steps, 100, 2, staircase=False)
loss = w*w*x+y
 
train_step = tf.train.GradientDescentOptimizer(learning_rate).minimize(loss,global_step=global_steps)
 
with tf.Session() as sess:
    sess.run(tf.global_variables_initializer())
    for i in range(100):
        sess.run(train_step, feed_dict={x:np.array([1]).reshape([1,1]),
            y:np.array([1]).reshape([1,1])})
        print("**********************************************")
        print(sess.run(learning_rate))
        print(sess.run(w))

        sess.run(ema_op)
        print ("当前 global_step:", sess.run(global_steps))
        print ("当前 w1:", sess.run([w, ema.average(w)]), '\n')      
    

        print(sess.run(global_steps))

说白了,总结一下就是,两个指数(指数学习率,指数滑动均值)只能使用这个global_step,虽然不能主动改变global_step的值,但是可以追踪这个global_step(如果这个值通过优化器,或者assign改变了的话,虽然设置成trainable=false,但是仍然是变量,据说某版本的tensorflow已经支持了直接创建学习率衰减器,优化器结合的训练器,以及专用的global_step类。。)。而实际上能够改变global_step的一个是优化器自身的默认自增,一个是人为的assign主动赋值。

你可能感兴趣的:(人工智能-神经网络)