1. 程式人生 > >深度學習部署--tensorflow 用c++呼叫前向

深度學習部署--tensorflow 用c++呼叫前向

目前,TensorFlow官方推薦使用Bazel編譯原始碼和安裝,但許多公司常用的構建工具是CMake。TensorFlow官方並沒有提供CMake的編譯示例,但提供了MakeFile檔案,所以可以直接使用make進行編譯安裝。另一方面,模型訓練成功後,官方提供了TensorFlow Servering進行預測的託管,但這個方案過於複雜。對於許多機器學習團隊來說,一般都有自己的一套模型託管和預測服務,如果使用TensorFlow Servering對現存業務的侵入性太大,使用TensorFlow C++ API來匯入模型並提供預測服務能方便的嵌入大部分已有業務方案,對這些團隊來說比較合適。

本文以一個簡單網路介紹從線下訓練到線上預測的整個流程,主要包括以下幾點:

  • 使用Python介面訓練模型
  • 使用make編譯TensorFlow原始碼,得到靜態庫
  • 呼叫TensorFlow C++ API編寫預測程式碼,使用CMake構建預測服務

使用Python介面訓練模型

這裡用一個簡單的網路來介紹,主要目的是儲存網路結構和引數,用於後續的預測。

123456789101112131415import tensorflow as tfimport numpy as npwith tf.Session() as sess: a = tf.Variable(5.0, name='a') b = tf.Variable(6.0, name='b') c = tf.multiply(a, b, name='c'
) sess.run(tf.global_variables_initializer()) print(a.eval()) # 5.0 print(b.eval()) # 6.0 print(c.eval()) # 30.0 tf.train.write_graph(sess.graph_def, 'simple_model/', 'graph.pb', as_text=False)

這個網路有兩個輸入,a和b,輸出是c,最後一行用來儲存模型到simple_model目錄。執行後會在simple_model目錄下生成一個graph.pb的protobuf二進位制檔案,這個檔案儲存了網路的結構,由於這個例子裡沒有模型引數,所以沒有儲存checkpoint檔案。

原始碼編譯TensorFlow

官方詳細介紹可以看這裡原始碼編譯TensorFlow。其實很簡單,以maxOS為例,只要執行以下命令即可,其他作業系統也有相應的命令。編譯過程大概需要半小時,成功後會在tensorflow/tensorflow/contrib/makefile/gen/lib下看到一個100多MB的libtensorflow-core.a庫檔案。maxOS需要使用build_all_linux.sh,並且只能用clang,因為有第三方依賴編譯時把clang寫死了。

123git clone https://github.com/tensorflow/tensorflow.gitcd tensorflowtensorflow/contrib/makefile/build_all_linux.sh

後續如果要依賴TensorFlow的標頭檔案和靜態庫做開發,tensorflow/tensorflow/contrib/makefile目錄下的幾個目錄需要注意:

  • downloads 存放第三方依賴的一些標頭檔案和靜態庫,比如nsync、Eigen等
  • gen 存放TensorFlow生成的C++ PB標頭檔案、TensorFlow的靜態庫、ProtoBuf的標頭檔案和靜態庫等等

使用TensorFlow C++ API編寫預測程式碼

預測程式碼主要包括以下幾個步驟:

  • 建立Session
  • 匯入之前生成的模型
  • 將模型設定到建立的Session裡
  • 設定模型輸入輸出,呼叫Session的Run做預測
  • 關閉Session

建立Session

1234567Session* session;Status status = NewSession(SessionOptions(), &session);if (!status.ok()) { std::cout << status.ToString() << std::endl;} else { std::cout << "Session created successfully" << std::endl;}

匯入模型

1234567GraphDef graph_def;Status status = ReadBinaryProto(Env::Default(), "../demo/simple_model/graph.pb", &graph_def);if (!status.ok()) { std::cout << status.ToString() << std::endl;} else { std::cout << "Load graph protobuf successfully" << std::endl;}

將模型設定到建立的Session裡

123456Status status = session->Create(graph_def);if (!status.ok()) { std::cout << status.ToString() << std::endl;} else { std::cout << "Add graph to session successfully" << std::endl;}

設定模型輸入

模型的輸入輸出都是Tensor或Sparse Tensor。

12345Tensor a(DT_FLOAT, TensorShape()); // input aa.scalar<float>()() = 3.0;Tensor b(DT_FLOAT, TensorShape()); // input bb.scalar<float>()() = 2.0;

預測

12345678910111213std::vector<std::pair<string, tensorflow::Tensor>> inputs = { { "a", a }, { "b", b },}; // inputstd::vector<tensorflow::Tensor> outputs; // outputStatuc status = session->Run(inputs, {"c"}, {}, &outputs);if (!status.ok()) { std::cout << status.ToString() << std::endl;} else { std::cout << "Run session successfully" << std::endl;}

檢視預測結果

12auto c = outputs[0].scalar<float>();std::cout << "output value: " << c() << std::endl;

關閉Session

1session->Close();

完整的程式碼在https://github.com/formath/tensorflow-predictor-cpp,路徑為src/simple_model.cc

使用CMake構建預測程式碼

這裡主要的問題是標頭檔案和靜態庫的路徑要正確,包括TensorFlow以及第三方依賴。 以macOS為例,其他平臺路徑會不一樣。

標頭檔案路徑

12345tensorflow // TensorFlow標頭檔案tensorflow/tensorflow/contrib/makefile/gen/proto // TensorFlow PB檔案生成的pb.h標頭檔案tensorflow/tensorflow/contrib/makefile/gen/protobuf-host/include // ProtoBuf標頭檔案tensorflow/tensorflow/contrib/makefile/downloads/eigen // eigen標頭檔案tensorflow/tensorflow/contrib/makefile/downloads/nsync/public // nsync標頭檔案

靜態庫路徑

123tensorflow/tensorflow/contrib/makefile/gen/lib // TensorFlow靜態庫/tensorflow/tensorflow/contrib/makefile/gen/protobuf-host/lib // protobuf靜態庫/tensorflow/tensorflow/contrib/makefile/downloads/nsync/builds/default.macos.c++11 // nsync靜態庫

編譯時需要這些靜態庫

1234libtensorflow-core.alibprotobuf.alibnsync.a其他: pthread m z

CMake構建

12345git clone https://github.com/formath/tensorflow-predictor-cpp.gitcd tensorflow-predictor-cppmkdir build && cd buildcmake ..make

構建完成後在bin路徑下會看到一個simple_model可執行檔案,執行./simple_model即可看到輸出output value: 6。 需要注意的時,編譯選項裡一定要加這些-undefined dynamic_lookup -all_load,否則在編譯和執行時會報錯,原因可見dynamic_lookupError issues

以上用c = a * b一個簡單的網路來介紹整個流程,只要簡單的修改即可應用到複雜模型中去,更復雜的一個例子可見src/deep_model.cc

參考