簡介
由于生產環境使用windows、C++,而tensorflow模型訓練使用python更為方便,因此存在需求:在windows環境使用tensorflow的c++接口載入訓練好的tensorflow模型,并進行測試。類似的文檔比較缺乏,并且由于tf本身一直在完善,相比現有的博客各個步驟都有進一步的簡化,這里針對1.2.0版本梳理對應的最簡單的一種流程:
- 利用tensorflow的python API定義、訓練自己的模型
- 利用tensorflow的python API保存模型,并進一步將模型中的變量都轉化為常量,通過這樣“freeze graph”使得模型導出為一個文件,便于c++調用
- 編譯tensorflow的源碼來使用tensorflow的c++接口
- 在tensorflow的tutorrials Image Recognition 的基礎上修改代碼,利用模型進行測試。
利用tf的python API訓練模型
這部分屬于tensorflow的基礎,官方文檔getting started有相當詳細的介紹和描述,在此不做贅述。值得注意的是tf的命名方式,在python代碼中的變量名和在tf的graph中的變量名是兩個概念,因此至少針對輸入輸出要定義tf的graph中的變量名,定義變量名的語法類似loss = tf.reduce_mean(cross_entropy, name='xentropy_mean')
。此外也可以利用tf.name_scope
來規劃命名。
導出tf模型并freeze graph
這部分有官方工具的代碼freeze_graph.py,對應的博客也很多。這里我推薦博客TensorFlow: How to freeze a model and serve it with a python API。
??freeze graph就是把原本的圖中的變量(卷積核、偏置)等都使用訓練好的模型中的值來代替,變成常量。frozen graph的意義在于(freeze_graph.py的注釋)
It's useful to do this when we need to load a single file in C++, especially in environments like mobile or embedded where we may not have access to the RestoreTensor ops and file loading calls that they rely on.
推薦的主要原因在于博客中使用方法saver = tf.train.Saver();last_chkp = saver.save(sess, 'results/graph.chkp')
是最為簡單的保存模型的方法,同時博客提供了freeze graph的代碼,核心采用graph_util.convert_variables_to_constants
方法來進行freeze graph,使得不需要使用官方工具freeze_graph.py。對應freeze_graph的代碼引用如下(其中注意到write使用參數‘wb'寫為二進制):
import os, argparse
import tensorflow as tf
from tensorflow.python.framework import graph_util
dir = os.path.dirname(os.path.realpath(__file__))
def freeze_graph(model_folder):
# We retrieve our checkpoint fullpath
checkpoint = tf.train.get_checkpoint_state(model_folder)
input_checkpoint = checkpoint.model_checkpoint_path
# We precise the file fullname of our freezed graph
absolute_model_folder = "/".join(input_checkpoint.split('/')[:-1])
output_graph = absolute_model_folder + "/frozen_model.pb"
# Before exporting our graph, we need to precise what is our output node
# This is how TF decides what part of the Graph he has to keep and what part it can dump
# NOTE: this variable is plural, because you can have multiple output nodes
output_node_names = "Accuracy/predictions"
# We clear devices to allow TensorFlow to control on which device it will load operations
clear_devices = True
# We import the meta graph and retrieve a Saver
saver = tf.train.import_meta_graph(input_checkpoint + '.meta', clear_devices=clear_devices)
# We retrieve the protobuf graph definition
graph = tf.get_default_graph()
input_graph_def = graph.as_graph_def()
# We start a session and restore the graph weights
with tf.Session() as sess:
saver.restore(sess, input_checkpoint)
# We use a built-in TF helper to export variables to constants
output_graph_def = graph_util.convert_variables_to_constants(
sess, # The session is used to retrieve the weights
input_graph_def, # The graph_def is used to retrieve the nodes
output_node_names.split(",") # The output node names are used to select the usefull nodes
)
# Finally we serialize and dump the output graph to the filesystem
with tf.gfile.GFile(output_graph, "wb") as f:
f.write(output_graph_def.SerializeToString())
print("%d ops in the final graph." % len(output_graph_def.node))
if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument("--model_folder", type=str, help="Model folder to export")
args = parser.parse_args()
freeze_graph(args.model_folder)
編譯源碼來使用tf的c++ API
編譯源碼的方式官方有文檔Installing TensorFlow from Sources,其中有段:
We don't officially support building TensorFlow on Windows; however, you may try to build TensorFlow on Windows if you don't mind using the highly experimental Bazel on Windows or TensorFlow CMake build.
在兩種方案中,我選擇采用cmake,理由是相對來說環境配置更為容易,但可能使用google自己的bazel相對支持度更高。
??參考官方readme一步一步來,值得注意的有兩點,一個是git clone的時候推薦git對應的穩定版本的分支(直接master可能會有編譯錯誤和未知bug);另一個是要用命令行進行編譯,直接采用vs2015 IDE進行編譯會出錯C1060,原因應該是默認的編譯器調用的不是native 64位的toolset,如何設置使得能夠使用IDE直接編譯調試的方法還沒有找到。
??相比于官方的項目tf_tutorials_example_trainer.vcxproj,更有參考意義的項目是tf_label_image_example.vcxproj,對應的詳盡官方教程Image Recognition,這個教程使用inception模型來進行識別,對應運行時可能需要修改圖片和文件的路徑才能正確輸出結果。
修改代碼實現自己的模型
教程源碼提供了模型讀取,圖片讀取,Label讀取等核心步驟,修改對應代碼進行編譯能夠很容易上手完成任務,下面貼一下保存圖片的代碼,總體是讀取圖片的逆向過程:
// Given an output tensor with 4d, reduce dim and output jpg image
Status SaveTensorToImageFile(const string& file_name, const Tensor* out_tensor) {
auto root = tensorflow::Scope::NewRootScope();
using namespace ::tensorflow::ops; // NOLINT(build/namespaces)
auto output_image_data = tensorflow::ops::Reshape(root, *out_tensor, { 256, 256, 3 });
auto output_image_data_cast = tensorflow::ops::Cast(root, output_image_data, tensorflow::DT_UINT8);
auto output_image = tensorflow::ops::EncodeJpeg(root, output_image_data_cast);
auto output_op = tensorflow::ops::WriteFile(root.WithOpName("output/image"), file_name/*"D:/tf_face/trained_model_fast/output.jpg"*/, output_image);
string output_name = "output/image";
// This runs the GraphDef network definition that we've just constructed, and
// returns the results in the output tensor.
tensorflow::GraphDef graph;
TF_RETURN_IF_ERROR(root.ToGraphDef(&graph));
std::unique_ptr<tensorflow::Session> session(
tensorflow::NewSession(tensorflow::SessionOptions()));
TF_RETURN_IF_ERROR(session->Create(graph));
Status writeResult = session->Run({}, {}, { output_name }, {});
return writeResult;
}
代碼中圖片的尺寸可以自行定義,其中要注意的是c++中session->Run函數傳入的參數無論是ops或是Tensor都是要使用tf定義的名字root.WithOpName("output/image")
而不是c++代碼中定義的局部變量output_op
,以上在tf的CPU版本上流程走通。
參考鏈接
Tensorflow C++ API調用預訓練模型和生產環境編譯 (unix )
TensorFlow: How to freeze a model and serve it with a python API
TensorFlow CMake build
Tensorflow Tutorial Image Recognition