tf.estimator


轉自 https://zhuanlan.zhihu.com/p/77248274

Inside tf.estimator(2) 使用記錄

0. 前言

  • 基本使用請參考:Inside tf.estimator(1) 基本使用
  • 目標:記錄使用 tf.estimator 時碰到的問題。
  • 感想:
    • 近來在努力搬磚,終於像個真正的算法工程師了。
    • 使用的解決方案不一定最優,但都能解決自己的問題。
    • 如果有更好的解決方案,請告訴我……
  • 本文內容
    • tf.keras.layers.BatchNormalization 采坑
      • tf.keras.layers.BatchNormalization 不會將修改mean/var/beta等操作自動添加到UPDATE_OPS中。
      • 使用 model.updates 也存在問題,需要使用 model.get_updates_for 才行。
    • 單機多卡訓練/預測/評估
      • 通過 MirroredStrategy 實現。
    • 導入 fine-tune 模型
      • 導入fine-tune模型,不能使用 model.load_weights 實現,真的坑。
      • 可以通過 tf.train.init_from_checkpoints / tf.train.Scaffold / tf.estimator.WarmStartSettings 實現。本文介紹后兩種。
    • 保存驗證集誤差最小的模型
      • 通過 tf.train.SessionRunHook 實現。

1. tf.keras.layers.BatchNormalization 采坑

  • 問題描述:
    • 使用 tf.keras 構建模型,通過自定義 tf.estimator.EstimatorSpec,構建了 tf.estimator.Estimator 對象。
    • 在創建 train_op 時,在optimizer.minimize之前使用了 with tf.control_dependencies(update_ops)
    • 當使用 vgg16 作為backend時,模型能夠正常;當使用 resnet50, xception 等作為backend時,效果賊差。
  • 發現問題:
    • 檢查保存下來的ckpt文件時發現,所有bn的mean為0,var為1,也就是說UPDATE_OPS里沒有添加BN的更新操作。
    • 也就是說 tf.keras.layers.BatchNormalization 沒有默認向 UPDATE_OPS 中添加默認操作,而tensorflow其他的bn實現都這么做了……
  • 解決:
    • 獲取所有BN的OPS,添加到tf.GraphKeys.UPDATE_OPS
      • 方法一(參考issue):
        • 又采坑(這個方法不行):在通過keras的方法獲取,即model.updates,是不行的。
        • 應該使用 model.get_updates_for().
      • 方法二:參考這篇博客,通過OPS的名稱尋找所有符合要求的OPS。
ops = tf.get_default_graph().get_operations() update_ops = [x for x in ops if ("AssignMovingAvg" in x.name and x.type=="AssignSubVariableOp")] tf.add_to_collection(tf.GraphKeys.UPDATE_OPS, update_ops)
  • 感想
    • ………………………………………………………………
    • 查了下issue,一年多前就有人問了……大哥,好歹文檔里說明下。啊…………………………………………
    • 我一直比較抵觸使用 tf.keras,但官方就是推薦使用,那就用吧,還能咋辦呢。但這個BUG表明,官方的意思是使用全套keras……
    • keras的作者還在 issue 19643 中宣稱:
      • 就是這么設計的。
      • 想解決,要么自己手動處理,想要方便就直接用全套 keras API好啦(言下之意,要官方修復是不可能的)……
That is by design. Global collections (and global states in general) are prone to various issues (like most global variables in software engineering) and should be avoided.
...
If you are writing your own custom training loops, you will have to run these updates as part of the call to sess.run(). But do note that tf.keras provides a built-in training loop in the form of model.fit(), as well as primitive methods for building your own custom training loops (in particular model.train_on_batch() and model.predict_on_batch()). If you are using Keras you should generally never have to manually manage your model's updates.

2. 單機多卡

  • 目標:單機多卡訓練/預測/評估。

2.1. 多GPU訓練的第一種實現方式

  • 最基本、底層的實現方式,不推薦使用
  • 思路:
    • 使用 tf.variable_scpetf.device等對同一個計算圖使用多個gpu創建。
    • 分別計算每個GPU中的梯度值,最終匯總求平均,最為最終目標梯度值。
  • 實例:官方cifar10示例

2.2. 多GPU訓練的第二種方式

        • 如果CUDA版本高於9.2,則可以下載 O/S agnostic local installer,可以直接下載后解壓、將相關文件復制到/usr/local/cuda的對應文件夾中,方便很多,更多請參考上面參考資料中的鏈接。
        • 如果是cuda9,只能下載deb安裝包,並下載。
      • 下載deb包后,要進行安裝:
        • 如果是local版,則通過sudo dpkg -i nccl-repo-<version>.deb安裝。
        • 如果是network版,則通過sudo dpkg -i nvidia-machine-learning-repo-<version>.deb安裝。
      • 更新APT數據庫:sudo apt update
      • 通過apt安裝libnccl2庫,命令在下載nccl文件的網址中有,大概形式就是:sudo apt install libnccl2=2.4.7-1+cuda9.0 libnccl-dev=2.4.7-1+cuda9.0
  • 實現介紹
NUM_GPUS = 2 strategy = tf.contrib.distribute.MirroredStrategy(num_gpus=NUM_GPUS) config = tf.estimator.RunConfig(train_distribute=strategy) estimator = tf.keras.estimator.model_to_estimator(model, config=config)

2.3. 方法二的一些采坑以及使用記錄

  • 使用多GPU時調用train/predict/evaluate方法時,init_fn 參數注意事項
    • 返回的必須是tf.data.Dataset對象。
    • 創建tf.data.Dataset對象的過程必須在init_fn函數中。
      • 否則會報類似ValueError: Tensor(xxx) must be from the same graph as Tensor(xxx).的錯誤
      • 參考資料:issue 20784,官方回復是說:One of the key features of estimator is that it performs graph and session management for you.,大概意思是,所有的創建計算圖的過程,最好都封裝在init_fnmodel_fn中,等Estimator調用時在創建計算圖。
    • 其他:
      • 假設創建tf.data.Dataset中有指定batch_size,且確定GPU數量為 num_gpus,則多GPU訓練時,數據集實際batch size為batch_size * num_gpus
      • 建議在創建tf.data.Dataset對象時引入prefetch
      • 在未使用MirroredStrategy時,可以在init_fn外創建好tf.data.Dataset,而在init_fn中只創建iterator並調用get_next函數。
  • 若使用 slim 構建模型,很可能會報錯
    • 根據issue 27392,問題可能出在ExponentialMovingAverage操作上。
    • 根據issue 20874,當模型中使用 slim.batch_norm 時會與 MirroredStrategy 沖突。
    • 個人看法:
      • 如果沒有 batch_norm,可能模型可以正常運行。但有幾個模型是完全不包括batch_norm的……
      • 建議使用 tf.keras 構建模型;如果硬要使用 slim 模型,可以將slim.batch_norm改為tf.keras.layers.BatchNormalization
      • slim模型 + tf.estimator + MirroredStrategy 這個組合目前來看應該必須放棄,因為tensorflow的Member已經表態 issue23770 I don't think we have any current effort to add distribute strategy support to slim, and it seems unlikely to become a priority due to the general TF move away from contrib.

3. 導入 fine-tune 模型

  • 目標:在訓練之前,導入部分模型的pre-trained model。

3.1. 使用 slim 搭建模型時

  • 基本步驟:
    • 第一步:定義init_fn函數,用於構建tf.train.Scaffold對象。
      • 函數基本形式:init_fn(scaffold, session),用於初始化模型參數。
      • 構建 init_fn 可以用到 slim.assign_from_checkpoint_fn,細節就不描述了。
    • 第二步:將 tf.train.Scaffold 對象,作為 mode == tf.estimator.ModeKeys.PREDICTtf.estimator.EstimatorSpec 對象的初始化參數。
  • 采坑,曾經想用 tf.train.SessionRunHook 來實現這個功能:
    • 實現方法(最終失敗):構建子類,在 def after_create_session(session, coord) 方法中將finetune模型參數導入,並將該hook添加到tf.estimator.EstimatorSpec中。
    • 問題:
      • 如果訓練中止、再啟動時,需要導入訓練過程中保存的模型文件,即導入model_dir中最新的模型參數。
      • 導入fine-tune模型參數的操作在每次重起訓練時都會調用,而且調用順序在導入model_dir模型參數之后……

3.2 使用 tf.keras 搭建模型時

3.2.1 問題描述:

  • 在使用 tf.keras.applications 中的模型都有對應的預訓練模型,想通過這些h5文件進進行fine-tune。
  • 采坑:不能直接使用 load_weights,原因如下:
    • load_weights的最后一步是通過 keras.backend.get_session() 執行 assign 操作,也就是說,會自動創建 tf.Session 對象。
    • tf.estimator的目標是自動管理 tf.Session 的生命周期,會創建新的 Session 對象,而不會使用 keras.backend.get_session() 中的對象。
  • TensorFlow模型中不能直接使用 h5 文件,所以需要先將h5文件轉換為ckpt。
  • tf.estimator 中導入fine-tune模型的方法有很多,可以使用以下幾種:
    • tf.train.init_from_checkpoint:改變的是變量的 initializer,看issue里有個Member提到這事推薦方法,不過我沒試過。
    • tf.train.Scaffold:定義其中的 init_fn,將scaffold對象傳入 tf.estimator.Estimator 的初始化方法或train方法。
    • 使用 tf.estimator.WarmStartSettings 對象,導入 tf.estimator.Estimator 初始化方法中。

3.2.2. h5 to ckpt

  • 參考keras issue9040,fchollet給出了方法。
  • 我自己寫的測試樣例
import tensorflow as tf import os os.environ["CUDA_DEVICE_ORDER"]="PCI_BUS_ID" # see issue #152 os.environ["CUDA_VISIBLE_DEVICES"]="1" config = tf.ConfigProto() config.gpu_options.allow_growth = True tf.keras.backend.set_session(tf.Session(config=config)) path_to_save_ckpt = '/path/to/keras-ckpt' model = tf.keras.applications.VGG16() model_name = 'vgg16' var_list = slim.get_variables_to_restore(include=None, exclude=['predictions']) saver = tf.train.Saver(var_list) saver.save(tf.keras.backend.get_session(), os.path.join(path_to_save_ckpt, model_name + '.ckpt'))

3.2.3 tf.train.Scaffold 對象導入 ckpt 文件實現思路

  • tf.train.Scaffold對象中init_fn方法的定義為:def init_fn(scaffold, session)
  • 通過 slim.get_variables_to_restore(include=[], exclude=[]) 獲取對應的變量。
  • 通過 slim.assign_from_checkpoint_fn 獲取 def init_fn(session) 方法。
    • 該方法可將ckpt文件中tensor和當前模型中實際tensor對應起來。
    • var_list 可以是list,也可以是dict。
      • dict是 ckpt name to tf.Variable。
      • list則表明ckpt文件與當前模型中tensor name是一一對應的,即 {var.op.name: var for var in var_list}
  • 有了 init_fn(session) 再自己創建一個 _init_fn(scaffold, session) wrap 一下就行了。

3.2.4 tf.estimator.WarmStartSettings 對象導入 ckpt 文件實現思路:

  • 該類就是為了實現部分導入模型參數的功能,該類的注釋中有幾個簡單的使用
  • 定義該類時有以下幾個參數,現在分別說明:
    • ckpt_to_initialize_from:必須填寫,ckpt文件路徑。
    • vars_to_warm_start:需要導入的參數。
      • 默認是使用所有 trainable 參數。
      • 如果設置為 .*,則表示所有 trainable 參數。
      • 如果設置為 [.*],則表示所有參數(包括non-trainable)。
      • 可以設置為[string1, string2],分別獲取。
      • 小技巧,如果想要設置以XXX結尾,則使用*xxx[^/]
    • var_name_to_vocab_info:不知道是啥玩意,沒仔細研究。
    • var_name_to_prev_var_name:如果ckpt name和當前模型tensor name不對應,可以在這里設置。
      • 需要注意的是,var_name_to_prev_var_name 不用於過濾參數。
      • 換句話說,如果ckpt name和當前模型的tensor name相同時,可以不在 var_name_to_prev_var_name 中進行配置。
  • 采坑:如果選擇的當前模型參數不存在於ckpt文件中,則會報錯。
    • 如所有以 Momentum 結尾的參數。
    • 解決方法:設置正則表達式,例如設置以kernelbias結尾的所有變量。
  • 舉個例子:
    • 對於 keras vgg16 網絡,若想所有卷基層的tensor name和當前模型的var name相同,但全連接層不相同,則
ws = tf.estimator.WarmStartSettings( # ckpt 文件路徑 ckpt_to_initialize_from=os.path.join(ckpt_root_path, ckpt_name), # 獲取所有block開頭、kernel/bias結尾的當前模型var # 獲取所有original_ranking/rank_fc開頭、kernel/bias結尾的當前模型var vars_to_warm_start=['block.+kernel[^/]', 'block.+bias[^/]', 'original_ranking/rank_fc.+kernel[^/]', 'original_ranking/rank_fc.+bias[^/]'], # 若當前模型var name與ckpt name不匹配,則在這里進行處理 # 當前模型 var name to ckpt name var_name_to_prev_var_name={ 'original_ranking/rank_fc1/bias': 'fc1/bias', 'original_ranking/rank_fc1/kernel': 'fc1/kernel', 'original_ranking/rank_fc2/bias': 'fc2/bias', 'original_ranking/rank_fc2/kernel': 'fc2/kernel', } )

4. 保存驗證集誤差最小的模型

  • tf.estimator.Estimator中,基本的訓練過程是類似於以下代碼
for i in range(args.num_epochs): # train estimator.train(_train_input_fn, hooks=train_hooks, steps=args.train_steps) if i % args.validation_step == 0: # val every {validation_step} steps estimator.evaluate(_val_input_fn, args.val_steps, hooks=evaluate_hooks)
  • 目標:在模型訓練過程中,保留驗證集誤差最小的模型。
    • 換句話說:希望根據evaluate的結果保存 loss 最小或 accuracy 最大的模型。
  • 實現思路:
    • 構建hook,每次evaluate結束后記錄loss/accuracy的平均數,如果loss/accuracy比之前記錄的對應數值小/大,則保存該模型。
    • 難點:由於tf.estimator.Estimator封裝了evaluate的過程,無法直接獲取loss/accuracy的平均值。
  • 解決方案:
    • 猜測evaluate中使用了類似 tf.metrics.mean 的操作,用於保存驗證集中的平均loss/accuracy。
    • 找到 loss/accuracy 平均數對應的tensor name。
      • 我使用的方法是,在tensorboard中找到該名稱。
    • 使用 tf.get_default_graph().get_tensor_by_name(tensor_name) 來獲取tf.Tensor對象。
    • 構建hook對象,實現上述思路,並將該hook添加到evaluate方法中。
  • 源碼示例(保存loss最小的模型):
class EvaluateCheckpointHook(tf.train.SessionRunHook): def __init__(self, eval_dir, # 模型保存路徑 tensor_name='mean/value:0', # 需要對比的tensor名稱 ): self._cur_min_value = 1e8 # 記錄當前最小值 self._tensor_name = tensor_name self._eval_dir = eval_dir def begin(self): self._saver = tf.train.Saver() # saver 必須在 begin 方法中創建 def end(self, session): target_tensor = tf.get_default_graph().get_tensor_by_name( self._tensor_name) # 獲取tensor cur_value = session.run(target_tensor) if self._cur_min_value > cur_value: self._cur_min_value = cur_value self._saver.save(session, self._eval_dir, global_step=tf.train.get_global_step())

 

編輯於 2019-08-22


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM