Variable 的主要作用是維護特定節點的狀態,如深度學習模型參數
創建_基礎操作
創建 Variable 有兩種方式
tf.Variable
創建唯一變量
class VariableV1(Variable): def __init__(self, initial_value=None, # -- 變量值 trainable=None, # 該變量是否需要訓練,或者說是否能被優化器更新 collections=None, validate_shape=True, caching_device=None, name=None, # -- 變量的名字 variable_def=None, dtype=None, # -- 變量類型 expected_shape=None, import_scope=None, constraint=None, use_resource=None, synchronization=VariableSynchronization.AUTO, aggregation=VariableAggregation.NONE, shape=None): # -- 變量尺寸 pass
tf.Variable 是一個操作 (op),返回值是 Variable;
d1 = tf.Variable(2) d2 = tf.Variable(3, dtype=tf.int32, name='int') d3 = tf.Variable(4., dtype=tf.float32, name='float') d4 = tf.add(d1, d2) d5 = d1 + d2 # d6 = tf.add(d1, d3) ### 不同類型的數據不能運算 init = tf.global_variables_initializer() ### 變量必須初始化 sess1 = tf.Session() sess1.run(init) print(sess1.run(d4)) # 5 print(sess1.run(d5)) # 5 # print(sess1.run(d6)) print(type(d5)) # <class 'tensorflow.python.framework.ops.Tensor'>
tf.get_variable
獲取或者創建共享變量:獲取指定屬性(如name)的現有變量,如果該變量不存在,就新建一個變量;
d1 = tf.get_variable('d1', shape=[2, 3], initializer=tf.ones_initializer) d2 = tf.get_variable('d2', shape=[3, 2], initializer=tf.zeros_initializer) sess3 = tf.Session() sess3.run(tf.global_variables_initializer()) print(sess3.run(d1)) # [[1. 1. 1.] # [1. 1. 1.]] print(sess3.run(d2))
tf.Variable VS tf.get_variable
tf.Variable 保證了變量的唯一性:當它檢測到有命名沖突時,會自動處理沖突;
tf.get_variable 用於共享變量:當它檢測到有命名沖突時,會報錯;
### Variable w_1 = tf.Variable(3, name="w_1") w_2 = tf.Variable(1, name="w_1") print(w_1.name) # w_1:0 print(w_2.name) # w_1_1:0 系統檢測到命名沖突,會自動處理,把w_1變成w_1_1,保證了變量的唯一性 ### get_variable w_1 = tf.get_variable(name="g_1", initializer=1) # w_2 = tf.get_variable(name="w_1",initializer=2) # 這句報錯,系統檢測到命名沖突,會直接報錯 # 錯誤信息 # ValueError: Variable w_1 already exists, disallowed. Did you mean to set reuse=True in VarScope?
二者混用比較復雜,具體用法參考我的博客 變量與作用域
初始化
Variable 在參與計算之前必須初始化, 兩種方式
d1 = tf.Variable(1) print(d1) # <tf.Variable 'Variable:0' shape=() dtype=int32_ref> init = tf.global_variables_initializer() with tf.Session() as sess: sess.run(init) print(sess.run(d1)) # 1 ### d2 = tf.Variable(1) with tf.Session() as sess: tf.global_variables_initializer().run()
如果初始化后又創造了新變量,需要重新初始化
上面是初始化全部變量,還有以下方法
tf.local_variables_initializer() ### 初始化局部變量 tf.initialize_variables(var_list=, name=) ### 初始化指定變量
屬性方法
Variable 所有屬性方法如下
['SaveSliceInfo', 'aggregation', 'assign', 'assign_add', 'assign_sub', 'batch_scatter_update', 'constraint', 'count_up_to', 'device', 'dtype', 'eval', 'from_proto', 'gather_nd', 'get_shape', 'graph', 'initial_value', 'initialized_value', 'initializer', 'load', 'name', 'op', 'read_value', 'scatter_add', 'scatter_nd_add', 'scatter_nd_sub', 'scatter_nd_update', 'scatter_sub', 'scatter_update', 'set_shape', 'shape', 'sparse_read', 'synchronization', 'to_proto', 'trainable', 'value']
變量名
節點名,也是變量名,如果創建 Variable 時顯式的設置了 name,則取該 name,如果沒有,則以 Variable_1 格式遞增下標
d1 = tf.Variable(tf.zeros(2,2)) d2 = tf.Variable(2., dtype=tf.float32, name='d2') d3 = tf.Variable(3) print(d1) # <tf.Variable 'Variable:0' shape=() dtype=float32_ref> print(d1.op.name) # Variable_1 print(d2.op.name) # d2 print(d3.op.name) # Variable_2
內存機制
tf.Variable 創建的變量與張量一樣,可以作為操作的輸入和輸出,不同之處在於:
1. 張量的生命周期通常依賴計算的完成而結束,內存隨即釋放
2. 變量常駐內存,隨計算同步更新,不隨計算結束而結束
d1 = tf.Variable(2.) d2 = tf.constant(42.) print(d2) # Tensor("Const:0", shape=(), dtype=float32) d3 = tf.assign_add(d1, 1.) d4 = tf.add(d2, 1.) with tf.Session() as sess: tf.global_variables_initializer().run() for i in range(2): print(sess.run(d3)) # 3.0, 4.0 ### 循環中 variable 狀態持續更新,內存不釋放 print(sess.run(d4)) # 43.0, 43.0 ### 循環中 constant 狀態不更新,內存實時釋放 print(d1, sess.run(d1)) # <tf.Variable 'Variable:0' shape=() dtype=float32_ref> 4.0 ### 循環結束之后 variable 狀態保持,內存未釋放 print(d2, sess.run(d2)) # Tensor("Const:0", shape=(), dtype=float32) 42.0 ### 循環結束之后 constant 狀態恢復,內存釋放
Variable 的這個特性常用於模型參數的迭代;
Variable 賦值
變量的賦值不能直接用 =,有幾種方式:tf.assign、var.assign、tf.assign_add
d1 = tf.Variable(2) d2 = tf.Variable(3, dtype=tf.int32, name='int') d3 = tf.Variable(4., dtype=tf.float32, name='float') ## method1 # d4 = tf.assign(d2, d3) ### 兩個變量數據類型要一致 d5 = tf.assign(d2, d1) # d7 = tf.assign(d6, d3) ### 被賦值的變量必須事先存在 ## method2 d8 = d2.assign(100) ## method3:加個數並賦值 d9 = tf.assign_add(d2, 50) with tf.Session() as sess2: tf.global_variables_initializer().run() print(sess2.run(d5)) # 2 d5 被賦值了,等於 d2 print(sess2.run(d2)) # 2 真正的 d2 也變了 print(sess2.run(d8)) # 100 print(sess2.run(d9)) # 150 print(sess2.run(tf.assign_add(d2, 3))) # 153 print(sess2.run(tf.assign(d2, 3))) # 3 print(sess2.run(tf.assign(d2, d1))) # 2
trainable
trainable 屬性指定變量是否參與訓練,或者說是否能被優化器更新,類似於 PyTorch 中的 requires_grad;
False 代表不參與訓練,默認為 True;
trainable 為只讀屬性,只在創建 Variable 時生效,后期無法更改;
在創建優化器 Optimizer 的 minimize 張量時,tf 會把所有可訓練的 Variable 收集到 trainable_variables 中,此后增加或者刪除 可訓練的變量,trainable_variables 不會變化;
x = tf.Variable(3.0, dtype=tf.float32, trainable=False) ### x 的 trainable 為 F,不參與訓練 y = tf.Variable(13.0, dtype=tf.float32) ### 參與訓練 train_op = tf.train.AdamOptimizer(0.01).minimize(tf.abs(y - x)) with tf.Session()as sess: sess.run(tf.global_variables_initializer()) for i in range(5): _, xx, yy = sess.run([train_op, x, y]) print('epoch', i, xx, yy) # 觀察 x 和 y 的變化 # epoch 0 3.0 12.99 # epoch 1 3.0 12.98 # epoch 2 3.0 12.969999 # epoch 3 3.0 12.959999 # epoch 4 3.0 12.949999
tf.trainable_variables and tf.all_variables
tf.trainable_variables() 返回的是 所有需要訓練的變量列表
tf.all_variables() 返回的是 所有變量的列表
v1 = tf.Variable(0, name='v1') v2 = tf.Variable(tf.constant(5, shape=[1], dtype=tf.float32), name='v2') global_step = tf.Variable(6, name='global_step', trainable=False) # 聲明不是訓練變量 for ele1 in tf.trainable_variables(): print(ele1.name) # v1:0 # v2:0 for ele2 in tf.all_variables(): print(ele2.name) # v1:0 # v2:0 # global_step:0
Variable 的保存和加載
保存和加載都需要創建 Saver 對象,然后調用 save 保存 和 restore 加載
from tensorflow.core.protobuf import saver_pb2 class Saver(object): def __init__(self, var_list=None, reshape=False, sharded=False, max_to_keep=5, keep_checkpoint_every_n_hours=10000.0, name=None, restore_sequentially=False, saver_def=None, builder=None, defer_build=False, allow_empty=False, write_version=saver_pb2.SaverDef.V2, pad_step_number=False, save_relative_paths=False, filename=None): pass def save(self, sess, save_path, global_step=None, latest_filename=None, meta_graph_suffix="meta", write_meta_graph=True, write_state=True, strip_default_attrs=False, save_debug_info=False): pass def restore(self, sess, save_path): """Restores previously saved variables.""" pass
創建 Saver 對象
var_list:被保存或者加載的 variable,該參數的取值有幾種形式:
- 默認為 None,即針對所有 Variable
- list 格式,指定 variable,variable 的命名默認為 v1、v2...
- dict 格式,指定 name 和 variable
save 方法
save_path:指定存儲路徑,一般以 ckpt(checkpoint) 結尾
global_step:指定全局階段,實際上就是個標記,通常是用一個數字指定 variable 在哪個階段保存的,這個數字位於 filename 之后,具體見例子
restore 方法
save_path:注意這個路徑要看 save 時的 global_step
d1 = tf.Variable(1.) d2 = tf.Variable(2., dtype=tf.float32, name='d2') init = tf.global_variables_initializer() ### 初始化 Saver 對象 saver = tf.train.Saver() ### 保存所有變量 saver1 = tf.train.Saver([d1, d2]) ### list 指定變量,變量名默認為 v1 v2 遞增 saver2 = tf.train.Saver({'v1': d1, 'v2':d2}) ### dict 指定變量和變量名 with tf.Session() as sess: sess.run(init) ### save 方法保存變量 saver.save(sess, './var/all.ckpt') saver1.save(sess, './var/list.ckpt', global_step=0) print(saver2.save(sess, './var/dict.ckpt', global_step=1)) # ./var/dict.ckpt-1 sess.run(tf.assign_add(d2, 3.)) ### 保存之后再改變 print(sess.run(d2)) # 5.0 ### 加載變量 1:同一個 saver、sess saver2.restore(sess, './var/dict.ckpt-1') print(sess.run(d2)) # 2.0 ### 加載的是改變前的值,說明保存成功 ### 加載變量 2:同一個 saver,不同的 sess with tf.Session() as sess: saver2.restore(sess, './var/dict.ckpt-1') print(sess.run(d2)) # 2.0 ### 加載變量 3:不同的 saver,不同的 sess saver2 = tf.train.Saver({'v2':d2}) with tf.Session() as sess: saver2.restore(sess, './var/dict.ckpt-1') print(sess.run(d2)) # 2.0
可見,保存與加載相互獨立
上述代碼保存 variable 結果如下
1. global_step 被加到 filename 之后
2. save 會生成 4 個文件 data、index、meta、checkpoint
- data:存放模型參數
- meta:存放計算圖
- checkpoint:記錄模型存儲的路徑,model_checkpoint_path 代表最新的模型存儲路徑,all_model_checkpoint_paths 代表所有模型的存儲路徑
3. 最多只保存近 5 次的存儲
4. 多次保存只有一個 checkpoint
參考資料:
https://www.cnblogs.com/weiyinfu/p/9973022.html tensorflow動態設置trainable