1. 程式人生 > >tensorflow1.4 c++編譯以及API使用

tensorflow1.4 c++編譯以及API使用

摘要: 最近在研究如何使用tensorflow c++ API呼叫tensorflow python環境下訓練得到的網路模型檔案。參考了很多部落格,文件,一路上踩了很多坑,現將自己的方法步驟記錄下來,希望能夠幫到有需要的人!(本文預設讀者對python環境下tensorflow的使用已經比較熟悉了)

方法簡要梳理如下:

  1. 安裝bazel,然後使用bazel編譯tensorflow原始碼,產生我們需要的庫檔案。
  2. 在python環境下,使用tensorflow訓練一個深度神經網路,本文以mnist為例。將訓練好的模型和引數凍結在一個pb檔案中。
  3. 在C++環境下,呼叫pb檔案,對圖片進行預測。最終結果如下圖所示,程式成功識別到圖片中的數字為1,且概率為0.95。

具體程式參考專案:

1.安裝bazel
echo "deb [arch=amd64] http://storage.googleapis.com/bazel-apt stable jdk1.8" | sudo tee /etc/apt/sources.list.d/bazel.list
curl https://bazel.build/bazel-release.pub.gpg | sudo apt-key add -
sudo apt-get update
sudo apt-get install bazel

2.tensorflow的下載。

本博文使用的tensorflow版本為1.4,其他版本的c++編譯可能會有一些不一樣。

git clone https://github.com/tensorflow/tensorflow.git
3.tensorflow的c++編譯。

3.1 進入tensorflow資料夾中,首先進行專案配置。

./configure

下面我貼出在我的機器上各選項的選擇:值得注意的是,如果我們要使用cuda和cudnn的話,一定要搞清楚自己機器上使用的cuda和cudnn的版本(尤其是cudnn),例如我使用的是cuda8.0和cudnn6.0.21。

3.2 使用bazel命令進行編譯。編譯的時間比較長,我在i3-4150cpu上編譯了一個小時左右的時間。

bazel build --config=opt --config=cuda //tensorflow:libtensorflow_cc.so

如果沒有顯示卡則使用如下命令進行編譯
bazel build --config=opt //tensorflow:libtensorflow_cc.so

編譯完成後,在bazel-bin/tensorflow中會生成兩個我們需要的庫檔案:libtensorflow_cc.so 和 libtensorflow_framework.so。

在後面我們用C++呼叫tensorflow時需要連結這兩個庫檔案。

4. 使用tensorflow C++ api呼叫圖模型(.pb檔案)。

tensorflow 編譯好之後,我們使用tensorflow c++ api呼叫一個已經凍結的圖模型(.pb檔案)

具體程式參考專案:

4.1 在python環境下生成一個圖模型(.pb檔案)

對於tensorflow,在Python環境下的使用是最方便的,tensorflow的python api也是最多最全面的。因此我們在python環境下,訓練了一個深度神經網路模型,並將模型和引數都凍結在一個pb檔案中。為後面使用C++ API呼叫這個pb檔案做好準備。我們以經典的mnist為例。

資料處理與模型的訓練,這裡就不多說了(預設讀者對python環境下tensorflow的使用已經比較熟悉)。這裡要說的是pb檔案的生成,使用一下程式碼:

from tensorflow.python.framework.graph_util import convert_variables_to_constants
graph = convert_variables_to_constants(sess, sess.graph_def, ["softmax"])
tf.train.write_graph(graph,'models','model.pb',as_text=False)
其中,convert_variables_to_constants()函式將引數變數凍結在圖模型中,其中第三個引數為網路輸出tensor的名字(name)。因為我的網路輸出是這樣定義的:y_conv = tf.nn.softmax(logits,name='softmax'),所以我的第三個引數設定為['softmax']。

write_graph()函式生成.pb檔案,第二個引數為生成pb檔案的資料夾,第三個引數為pb檔案的名字。

將上面三行程式碼加入到你的模型訓練的python腳步中,最後便可以得到我們需要的pb檔案。

4.2 c++環境下呼叫pb檔案。

第一步,載入模型

  Session* session;
  Status status = NewSession(SessionOptions(), &session);//建立新會話Session


  string model_path="model.pb";
  GraphDef graphdef; //Graph Definition for current model


  Status status_load = ReadBinaryProto(Env::Default(), model_path, &graphdef); //從pb檔案中讀取圖模型;
  if (!status_load.ok()) {
      std::cout << "ERROR: Loading model failed..." << model_path << std::endl;
      std::cout << status_load.ToString() << "\n";
      return -1;
  }
  Status status_create = session->Create(graphdef); //將模型匯入會話Session中;
  if (!status_create.ok()) {
      std::cout << "ERROR: Creating graph in session failed..." << status_create.ToString() << std::endl;
      return -1;
  }
  cout << "Session successfully created."<< endl;

第二步,使用tensorflow API讀取圖片。

這裡定義了兩個函式用於從jpg檔案中讀取圖片:

Status ReadTensorFromImageFile(const string& file_name, const int input_height,
                               const int input_width, const float input_mean,
                               const float input_std,
                               std::vector<Tensor>* out_tensors)
static Status ReadEntireFile(tensorflow::Env* env, const string& filename,
                             Tensor* output)
ReadEntireFile()函式讀取檔案內容,並將其賦值給其第三個輸入引數 Tensor* output,但是這個tensor並不能直接輸入給剛才我們載入的模型,需要經過一定的預處理。

在ReadTensorFromImageFile()函式,建立一個會話session,在會話中對讀取到的tensor進行預處理,例如,對tensor進行解碼( DecodeJpeg),resize,歸一化等等。

第三步,執行模型

  const Tensor& resized_tensor = resized_tensors[0];
  vector<tensorflow::Tensor> outputs;
  string output_node = "softmax";
  Status status_run = session->Run({{"inputs", resized_tensor}}, {output_node}, {}, &outputs);
resized_tensors的型別是std::vector<Tensor>,是一個vector容器。其在程式中,是ReadTensorFromImageFile()函式的最後一個輸入引數,對讀取到的圖片tensor進行預處理後便儲存在這個容器中。

模型預測時使用的函式為session->Run({{"inputs", resized_tensor}}, {output_node}, {}, &outputs)。

值得注意的是,"inputs"是圖模型輸入tensor的名字(name),變數output_node儲存的是圖模型輸出tensor的名字(name)。這兩個名字(name)一定要與儲存的圖模型(.pb)檔案中的名字一致,否則會報錯。最後得到的輸出tensor儲存在容器outputs中。

如果你有一個pb檔案,可是不知道它的輸入輸出tensor的名字,我們可以在python環境中使用API載入這個模型,然後將模型中的所有operation打印出來,第一項便是輸入tensor,最後一項便是輸出tensor。

print(sess.graph.get_operations())
print(sess.graph.get_operations()[0])
print(sess.graph.get_operations()[1])
第四步,從模型輸出tensor中獲得各類別的概率。
  Tensor t = outputs[0];                   // Fetch the first tensor
  auto tmap = t.tensor<float, 2>();        // Tensor Shape: [batch_size, target_class_num]
  int output_dim = t.shape().dim_size(1);  // Get the target_class_num from 1st dimension

  // Argmax: Get Final Prediction Label and Probability
  int output_class_id = -1;
  double output_prob = 0.0;
  for (int j = 0; j < output_dim; j++) 
  {
        std::cout << "Class " << j << " prob:" << tmap(0, j) << "," << std::endl;
        if (tmap(0, j) >= output_prob) {
              output_class_id = j;
              output_prob = tmap(0, j);
           }
  }
  std::cout << "Final class id: " << output_class_id << std::endl;
  std::cout << "Final class prob: " << output_prob << std::endl;

4.3 使用cmake進行編譯

本例我們建立一個cmake工程,再通過make生成一個可執行檔案。首先我們建立一個資料夾取名tensorflow_mnist,在該資料夾下建立子資料夾lib,將剛才編譯tensorflow 時產生的兩個庫檔案(libtensorflow_cc.so,libtensorflow_framework.so)放入其中。呼叫pb檔案進行預測的C++檔案,取名為tf.cpp,放在tensorflow_mnist目錄下。檔案結構如下圖所示。

下面給出我的CMakeLists.txt的檔案內容

cmake_minimum_required (VERSION 2.8.8)
project (tf_example)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g -std=c++11 -W")

link_directories(./lib)
include_directories(
   /home/zwx/tensorflow
   /home/zwx/tensorflow/bazel-genfiles
   /home/zwx/tensorflow/tensorflow/contrib/makefile/downloads/nsync/public
   /usr/local/include/eigen3
   /home/zwx/tensorflow/bazel-bin/tensorflow
   /home/zwx/tensorflow/tensorflow/contrib/makefile/gen/protobuf/include
   ) 
add_executable(tf_test  tf.cpp) 
target_link_libraries(tf_test tensorflow_cc tensorflow_framework)
最後進入build資料夾,對該工程進行編譯:
cd build
cmake ..
make

不過,在make 這一步很大機率會報錯,我將我碰到的幾個問題和解決方法寫在這裡,僅供參考。

問題一: protobuf版本不對


解決方法:安裝正確版本的protobuf。在ubuntu16.04,tensorflow1.4下,應該安裝protobuf-3.4.0。

問題二:nsync_cv.h檔案缺失

正常情況下,該檔案應該在路徑tensorflow/tensorflow/contrib/makefile/downloads/nsync/public裡。如果出現這個問題,很可能是tensorflow/tensorflow/contrib/makefile/下沒有downloads資料夾,可能是編譯的時候網路不好,沒有下載這個資料夾。

解決方法: 進入 tensorflow/tensorflow/contrib/makefile/ 資料夾下,找到腳步。然後回到tensorflow資料夾下,執行該指令碼。

./tensorflow/contrib/makefile/download_dependencies.sh

下載完畢後,便會有downloads資料夾,缺失的檔案便會包含在其中。

問題三:

解決方法:這個問題的造成原因和問題二是一樣的,檢視下載好的downloads資料夾,發現其中有一個資料夾為eigen,進入eigen資料夾執行以下命令。

mkdir build
cd build
cmake ..
make
sudo make install
安裝完畢後,在usr/local/include目錄下會出現eigen3資料夾。

4.4 執行可執行程式

make成功後,在build目錄下會出現一個可執行檔案tf_test。將一張28*28的數字圖片也放在build路徑下,檔名為digit.jpg,最後執行tf_test檔案。

./tf_test digit.jpg
結果如下圖所示:

從中我們可以看到該c++程式識別到digit.jpg圖片為數字一,為1的概率為0.95。

總結:這篇博文主要介紹瞭如何從原始碼編譯tensorflow c++ API,並且使用c++ API呼叫一個在python環境下已經訓練好並凍結引數的模型檔案(.pb檔案),最終生成一個可執行檔案tf_test。通過執行該檔案,我們成功識別了手寫體數字。

具體指令碼參考專案: