tensorflow提供的tf.train.ExponentialMovingAverage 類利用指數衰減維持變量的滑動平均。
當訓練模型的時候,保持訓練參數的滑動平均是非常有益的。評估時使用取平均后的參數有時會產生比使用最終訓練好的參數值好很多的效果。方法apply()會添加被訓練變量的影子副本和在影子副本中維持被訓練變量的滑動平均的若干操作。該方法在創建訓練模型時使用。那些保持維持滑動平均的操作(ops)一般會在每個訓練步驟之后被執行。average()和average_name()方法分別提供了對影子變量和影子變量名字訪問的途徑。它們在建立評估模型或者從checkpoint文件恢復模型時能夠用到,主要是幫助使用滑動平均代替最終訓練結果進行評估。
滑動平均計算時使用指數衰減。當創建ExponentialMovingAverage對象時,衰減率應該被指定。影子變量和被訓練參數的初始值相同。當執行更新滑動平均的操作時,每個影子變量會按照下面的公式進行更新:
shadow_variable -= (1 - decay) * (shadow_variable - variable)
上面的公式與下面的公式相同:
shadow_variable = decay * shadow_variable + (1 - decay) * variable
decay決定了模型更新的速度,越大越趨於穩定。decay的合理取值接近1.0,所以 decay的取值一般包含多個9,如0.999、0.9999等。
創建訓練模型時的用法示例:
# 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) with tf.control_dependencies([opt_op]): # Create the shadow variables, and add ops to maintain moving averages # of var0 and var1. This also creates 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. training_op = ema.apply([var0, var1]) ...train the model by running training_op...
有兩種使用滑動平均進行評估的方法:
- 建立一個使用影子變量(shadow variables)而非變量(variables)的模型。為此,需要使用返回給定變量的影子變量的average()方法
- 創建一個正常的模型,但是使用影子變量名加載checkpoint文件進行評估。為此,需要使用average_name()方法
恢復影子變量值的示例:
# Create a Saver that loads variables from their saved shadow values. shadow_var0_name = ema.average_name(var0) shadow_var1_name = ema.average_name(var1) saver = tf.train.Saver({shadow_var0_name: var0, shadow_var1_name: var1}) saver.restore(...checkpoint filename...) # var0 and var1 now hold the moving average values
部分方法:
__init__(decay, num_updates=None, zero_debias=False, name='ExponentialMovingAverage') # 創建一個ExponentialMovingAverage對象 # 為了創建影子變量和添加維持滑動平均的操作,apply()方法必須被調用
# 可選參數num_updates允許對衰減率進行動態微調。典型的方式是通過記錄訓練次數,在每次訓練開始之前降低衰減率。這樣做可以使模型在訓練的初始階段更新
# 得更快
# zero_debias: 如果為True,Tensor objects會被初始化為無偏滑動平均
# 衰減率更新公式為:
actual_decay = min(decay, (1 + num_updates) / (10 + num_updates))
可選參數name是被添加到apply()方法中的操作名稱的前綴。
apply(var_list=None) # 維持變量的滑動平均,即對shadow variables進行計算 # var_list必須是Variable或者Tensor objects構成的列表。該方法會為列表中的所有元素創建影子變量,且變量對象的影子變量初始值和變量相同。影子變量
也會被添加到GraphKeys.MOVING_AVERAGE_VARIABLES集合中。對於Tensor objects,影子變量會被初始化為0,同時被設置為無偏。 # 影子變量被設置trainable=False,並且被添加到GraphKeys.MOVING_AVERAGE_VARIABLES集合中,它們會在調用tf.global_variables()時被返回。 # 該方法返回一個按照要求更新所有影子變量的操作。同時需要注意的是,apply()可以在不同的var_list下被多次調用。
average(var) # 返回變量的影子變量值,即讀取影子變量shadow variables
average_name(var) # 返回變量的影子變量名,即讀取影子變量名 # 在模型訓練期間計算變量的滑動平均和在評估時從計算得到的滑動平均恢復變量是ExponentialMovingAverage的典型應用。 # 為了恢復變量,必須知道影子變量名。然后影子變量名和對應的變量被傳給Saver()對象來從計算得到滑動平均值恢復變量。 # Saver=tf.train.Saver({ema.average_name(var):var}) # 不管apply()方法有沒有被調用,average_name()都可以被調用
variables_to_restore(moving_avg_variables=None) # 返回要恢復的變量的名稱映射 # moving_avg_variables : 需要使用滑動平均名進行恢復的變量構成的list;如果為None,會默認為variables.moving_average_variables() + va
riables.trainable_variables() # 如果變量有滑動平均,那么使用滑動平均變量名作為恢復時使用的名稱;否則,使用變量名。 # 例如: # variables_to_restore = ema.variables_to_restore() # saver = tf.train.Saver(variables_to_restore) # 以下是返回的一個映射的示例: # conv/batchnorm/gamma/ExponentialMovingAverage: conv/batchnorm/gamma, # conv_4/conv2d_params/ExponentialMovingAverage: conv_4/conv2d_params, # global_step: global_step
示例:參考鏈接
import os import tensorflow as tf os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2' # 創建待訓練參數 variable1 = tf.Variable(initial_value=0, trainable=True, dtype=tf.float32) # 訓練次數,不可訓練 step_var = tf.Variable(initial_value=0, trainable=False) # 創建滑動平均對象 ema = tf.train.ExponentialMovingAverage(decay=0.999, num_updates=step_var) # 計算變量variable1的滑動平均操作 maintain_average_op = ema.apply([variable1]) # 初始化操作 init_op = tf.global_variables_initializer() with tf.Session() as sess: sess.run(init_op) # 初始值輸出 # 更新影子變量 sess.run(maintain_average_op) # 輸出變量和變量的影子變量 print(sess.run([variable1, ema.average(variable1)])) # 更新變量 sess.run(tf.assign(variable1, 5)) # 更新影子變量 # decay = min(decay, (1+step_var) / (10+step_var)) # shadow_variable = decay * shadow_variable + (1 - decay) * variable sess.run(maintain_average_op) # 輸出變量和變量的影子變量 print(sess.run([variable1, ema.average(variable1)])) # 更新step_var sess.run(tf.assign(step_var, 10000)) # 更新變量 sess.run(tf.assign(variable1, 10)) # 更新影子變量 sess.run(maintain_average_op) # 輸出變量和變量的影子變量 print(sess.run([variable1, ema.average(variable1)])) # 更新影子變量 # 更新影子變量 sess.run(maintain_average_op) # 輸出變量和變量的影子變量 print(sess.run([variable1, ema.average(variable1)]))
輸出如下:
[0.0, 0.0] [5.0, 4.5] [10.0, 4.5054998] [10.0, 4.5109944]
