tensorflow 模型的存檔、儲存、凍結、優化
模型的儲存分三種類型
- 知道模型結構,單純儲存變數
- 不知道模型結構,儲存模型和變數
- 不需要再改變數,只要常量化的模型(“凍結”)
第一種用於訓練的存檔,並且臨時恢復,這個時候使用者是把訓練需要的網路結構在程式碼裡面構造好了的,只是在一定的時間下需要暫時儲存網路中的變數,為了在崩潰之後繼續訓練。所以自然而然會有一個問題,如果我用 Python 寫的程式碼,需要在 C++ 當中恢復,我需要知道你的模型結構,才能恢復,這個最蠢的辦法是用 C++ 把你的網路結構再構造一遍,但我們按照統一的協議(比如 Protobuf)確定網路結構,就可以直接從標準序列化的資料中解析網路結構,這就是第二種情況,獨立於語言,模型和變數一起儲存的情況。然後如果碰到我們不需要再訓練了,比如只是把這個模型進行部署,不需要改變相關的變數,那麼其實只要一個帶常量的模型就可以,這就是第三種情況,把變數凍結的正向傳播模型。接下來會依次解釋這幾種情況的工作方式。
除了這些以外,針對用於服務的模型還可以做很多的優化。
存檔
存檔只是單純的儲存變數,並且能夠恢復,可以在一定的迭代次數以後儲存變數,並且從任意一個存檔開始重新訓練。以兩個變數加減 1 為例。
import tensorflow as tf # Create some variables. v1 = tf.get_variable("v1", shape=[3], initializer = tf.zeros_initializer) v2 = tf.get_variable("v2", shape=[5], initializer = tf.zeros_initializer) inc_v1 = v1.assign(v1+1) dec_v2 = v2.assign(v2-1) # Add an op to initialize the variables. init_op = tf.global_variables_initializer() # Add ops to save and restore all the variables. saver = tf.train.Saver() # Later, launch the model, initialize the variables, do some work, and save the # variables to disk. with tf.Session() as sess: sess.run(init_op) # Do some work with the model. inc_v1.op.run() dec_v2.op.run() # Save the variables to disk. save_path = saver.save(sess, "/tmp/tf-test/model.ckpt") print("Model saved in path: %s" % save_path)
可以在/tmp/tf-test
下面看到這幾個檔案checkpointmodel.ckpt.data-00000-of-00001 model.ckpt.indexmodel.ckpt.meta
。
可以通過指令碼觀察儲存的變數python$tensorflow-src/tensorflow/python/tools/inspect_checkpoint.py --file_name=/tmp/tf-test/model.ckpt--all_tensors
得到儲存的變數的內容,注意model.ckpt
這個只是檔案字首。
tensor_name:v1 [1. 1. 1.] tensor_name:v2 [-1. -1. -1. -1. -1.]
如果要恢復的話,可以通過下面的程式碼。
import tensorflow as tf # Create some variables. v1 = tf.get_variable("v1", shape=[3]) v2 = tf.get_variable("v2", shape=[5]) # Add ops to save and restore all the variables. saver = tf.train.Saver() # Later, launch the model, use the saver to restore variables from disk, and # do some work with the model. with tf.Session() as sess: # Restore variables from disk. saver.restore(sess, "/tmp/tf-test/model.ckpt") print("Model restored.") # Check the values of the variables print("v1 : %s" % v1.eval()) print("v2 : %s" % v2.eval())
得到一樣的效果
v1 : [1. 1. 1.] v2 : [-1. -1. -1. -1. -1.]
具體來說 .meta 對應的是 MetaGraph 和 SaverGraph,.index 對應的是變數值的位置,key 是變數名,value 是變數儲存的入口定義,data 變數的值具體儲存的檔案。這是恢復程式碼中已經原樣構造出了 Graph,如果沒有構造的化,需要通過tf.train.import_meta_graph('/tmp/model.ckpt.meta')
來載入,但是存檔儲存的資訊比較單一,Tensorflow 提供了一個更豐富的 API 來使用。
儲存
SavedModelBuilder
儲存的 API 比較豐富,能夠儲存多個 MetaGraph 和 Variables 的組合,除此之外還能附帶 assets,並且要指定模型簽名,simple_saved
的方法是一個簡單版本的呼叫,適用於 Predict API。這裡要展開一下 GraphDef, MetaGraphDef, SignatureDef, tags 這些東西的概念。對於 MetaGraph,這篇文章
解釋得很清楚。SignatureDef 是對應了一種圖的輸入和輸出,可以依據這個進行 serving API 的呼叫,類似於函式簽名,相對於一個介面的定義。
tensorflow_serving 自己給了個例子
,執行python mnist_saved_model.py /tmp/tf-test-2
以後可以獲得一個目錄,下面有版本 1 的模型資料,執行saved_model_cli show --dir/tmp/tf-test-2/1
可以檢視對應的簽名。可以看到對應的層級關係,預設用於服務的模型會打上 serve 的標籤,函式簽名有兩個,分別對應了 predict 和 classify 的方法。
MetaGraphDef with tag-set: 'serve' contains the following SignatureDefs: signature_def['predict_images']: The given SavedModel SignatureDef contains the following input(s): inputs['images'] tensor_info: dtype: DT_FLOAT shape: (-1, 784) name: x:0 The given SavedModel SignatureDef contains the following output(s): outputs['scores'] tensor_info: dtype: DT_FLOAT shape: (-1, 10) name: y:0 Method nameis: tensorflow/serving/predict signature_def['serving_default']: The given SavedModel SignatureDef contains the following input(s): inputs['inputs'] tensor_info: dtype: DT_STRING shape: unknown_rank name: tf_example:0 The given SavedModel SignatureDef contains the following output(s): outputs['classes'] tensor_info: dtype: DT_STRING shape: (-1, 10) name: index_to_string_Lookup:0 outputs['scores'] tensor_info: dtype: DT_FLOAT shape: (-1, 10) name: TopKV2:0 Method nameis: tensorflow/serving/classify
可以參考 tensorflow 的REST API
,比如GET http://host:port/v1/models/${MODEL_NAME}[/versions/${MODEL_VERSION}]
其實對應這個例子就是GET http://host:port/v1/models/tf-test-2/versions/1
,然後感覺函式簽名不同的 method name,可以呼叫不同的 request,比如POST http://host:port/v1/models/${MODEL_NAME}[/versions/${MODEL_VERSION}]:predict
這個格式,如果輸入和輸出對應的是images
和scores
那麼就對應了第一個簽名。
凍結
凍結的情況就是變數不再需要修改,直接把變數轉化成常量儲存成單一的模型,方便在部署的場景下使用。
凍結模型的程式碼在這裡 ,他的主要流程如下
- 清除所有 Op 中的 device,讓原來在指定 CPU/GPU/節點 上的 Op 不再繫結。
-
通過
graph_util.convert_variables_to_constants
將所有的 Variableeval
一次,把變數的 Op 的結果拿到,替換成 constant
優化
除了凍結模型以外,還可以刪減一些多餘的節點,比如 Summary 節點或者 Identity 節點,甚者把 16bit 的浮點數權重修改為 8bit 的浮點數權重(這個在 Tensorflow Lite 裡很有用)。這篇文章
列出了詳細的優化方式,主要是靠transform_graph
這個工具,地址在這
,他有很詳細的柴剪列表,並且可以自己編寫裁剪函式,充分做到模型在部署環節的“純淨化”,呼叫方式也很簡單。
transform_graph \ --in_graph=tensorflow_inception_graph.pb \ --out_graph=optimized_inception_graph.pb \ --inputs='Mul:0'\ --outputs='softmax:0'\ --transforms=' strip_unused_nodes(type=float, shape="1,299,299,3") remove_nodes(op=Identity, op=CheckNumerics) fold_old_batch_norms '
在transforms
裡面加入你想進行優化的 transformer 和對應的引數即可。