Tensorflow C++ 从训练到部署(2):简单图的保存、读取与 CMake 编译
经过了 上一篇文章,我们已经成功编译了 tensorflow c++ 的系统库文件并且安装到系统目录下了。这里我们将使用这个编译好的库进行基本的 C++ 模型加载执行等操作。
注意,在本篇文章会使用 Tensorflow 的 Python API,因为比较简单,这里不做介绍,安装详见官网教程:
https://www.tensorflow.org/install/
0、系统环境
Ubuntu 16.04
Tensorflow 1.10.1 (安装详见官网,建议使用 pip 方式安装)
1、一个简单网络的保存
只有 c = a * b 的网络:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | #!/usr/bin/env python import tensorflow as tf import numpy as np with tf.Session() as sess: a=tf.placeholder(tf.float32,shape=None, name='a') b=tf.placeholder(tf.float32,shape=None, name='b') c = tf.multiply(a, b, name='c') sess.run(tf.global_variables_initializer()) tf.train.write_graph(sess.graph_def, 'model/', 'simple.pb', as_text=False) res = sess.run(c, feed_dict={'a:0': 2.0, 'b:0': 3.0}) print("res = ", res) |
在这段代码中,我们构建了一个非常简单的“网络”:
c = a * b
并且给 a 和 b 赋予了初值。这一网络虽然简单,但是更复杂的网络也是类似的道理,我们需要保存的无非是计算图的结构,和图中的参数。这里边我们 tf.train.write_graph 保存的仅仅是图的结构。注意其中每个 placeholder 的 name 非常重要,这是我们后面输入和获取这些值的基础。
在这一例子中,有两点注意:
a)tf.train.write_graph 函数就是指定了保存图的路径,as_text=False 表明用二进制格式保存(默认是文本格式保存)。
b)res = sess.run(c, feed_dict={'a:0': 2.0, 'b:0': 3.0}) 中,我们定义的 placeholder 的名字是 a 和 b,但是对于 tensorflow 来说 shape=None 的相当于一个 1x1 的向量,所以我们还是要指定一个下标表示 a 和 b 是这个向量的第一个元素,这里 tensorflow 用 a:0 和 b:0 表示。如果 a 和 b 本来就是一个多维向量,那么可以直接取出整个向量。
我们将这段代码保存为 simple/simple_net.py 文件并执行:
1 | python simple_net.py |
正常情况下会在 model 文件夹下保存一个 simple.pb 的文件,并且输出一个测试运行的结果:
res = 6.0
PS:你也可以指定为文本格式保存,通常文本格式我们保存为 .pbtxt 后缀,例如:
1 | tf.train.write_graph(sess.graph_def, 'model/', 'simple.pbtxt', as_text=False) |
保存出来就是这样的,看起来很简单,不过通常我们还是用二进制格式比较高效:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 | node { name: "a" op: "Placeholder" attr { key: "dtype" value { type: DT_FLOAT } } attr { key: "shape" value { shape { unknown_rank: true } } } } node { name: "b" op: "Placeholder" attr { key: "dtype" value { type: DT_FLOAT } } attr { key: "shape" value { shape { unknown_rank: true } } } } node { name: "c" op: "Mul" input: "a" input: "b" attr { key: "T" value { type: DT_FLOAT } } } node { name: "init" op: "NoOp" } versions { producer: 26 } |
2、使用 Python 读取这个网络
首先我们来看下用 Python 如何读取这个网络并进行计算:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | #!/usr/bin/env python import tensorflow as tf import numpy as np import numpy as np from tensorflow.python.platform import gfile # Initialize a tensorflow session with tf.Session() as sess: # Load the protobuf graph with gfile.FastGFile("model/simple.pb",'rb') as f: graph_def = tf.GraphDef() graph_def.ParseFromString(f.read()) # Add the graph to the session tf.import_graph_def(graph_def, name='') # Get graph graph = tf.get_default_graph() # Get tensor from graph c = graph.get_tensor_by_name("c:0") # Run the session, evaluating our "c" operation from the graph res = sess.run(c, feed_dict={'a:0': 2.0, 'b:0': 3.0}) print("res = ", res) |
我们将这段代码保存为 simple/load_simple_net.py 文件并执行:
1 | python load_simple_net.py |
如果运行成功的话,可以看到输出结果:
res = 6.0
3、使用 C++ 读取这个网络
现在我们使用 C++ API 来读取这个图并使用图进行计算:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 | #include "tensorflow/core/public/session.h" #include "tensorflow/core/platform/env.h" using namespace tensorflow; /** * @brief simple model for click through rate prediction * @details [long description] * * @param argv[1] graph protobuf * * @return [description] */ int main(int argc, char* argv[]) { // Initialize a tensorflow session Session* session; Status status = NewSession(SessionOptions(), &session); if (!status.ok()) { std::cerr << status.ToString() << std::endl; return 1; } else { std::cout << "Session created successfully" << std::endl; } if (argc != 2) { std::cerr << std::endl << "Usage: ./project path_to_graph.pb" << std::endl; return 1; } // Load the protobuf graph GraphDef graph_def; std::string graph_path = argv[1]; status = ReadBinaryProto(Env::Default(), graph_path, &graph_def); if (!status.ok()) { std::cerr << status.ToString() << std::endl; return 1; } else { std::cout << "Load graph protobuf successfully" << std::endl; } // Add the graph to the session status = session->Create(graph_def); if (!status.ok()) { std::cerr << status.ToString() << std::endl; return 1; } else { std::cout << "Add graph to session successfully" << std::endl; } // Setup inputs and outputs: // Our graph doesn't require any inputs, since it specifies default values, // but we'll change an input to demonstrate. Tensor a(DT_FLOAT, TensorShape()); a.scalar<float>()() = 3.0; Tensor b(DT_FLOAT, TensorShape()); b.scalar<float>()() = 2.0; std::vector<std::pair<string, tensorflow::Tensor>> inputs = { { "a:0", a }, { "b:0", b }, }; // The session will initialize the outputs std::vector<tensorflow::Tensor> outputs; // Run the session, evaluating our "c" operation from the graph status = session->Run(inputs, {"c:0"}, {}, &outputs); if (!status.ok()) { std::cerr << status.ToString() << std::endl; return 1; } else { std::cout << "Run session successfully" << std::endl; } // Grab the first output (we only evaluated one graph node: "c") // and convert the node to a scalar representation. auto output_c = outputs[0].scalar<float>(); // (There are similar methods for vectors and matrices here: // https://github.com/tensorflow/tensorflow/blob/master/tensorflow/core/public/tensor.h) // Print the results std::cout << outputs[0].DebugString() << std::endl; // Tensor<type: float shape: [] values: 30> std::cout << "output value: " << output_c() << std::endl; // 30 // Free any resources used by the session session->Close(); return 0; } |
将这一文件保存为 simple/load_simple_net.cpp。
4、编译运行 Tensorflow C++ API
1)编写 CMakeLists.txt
首先我们编写一个 CMakeLists.txt 来编译这个文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | cmake_minimum_required(VERSION 3.5) project(tensorflow_cpp) set(CMAKE_CXX_STANDARD 11) find_package(OpenCV 3.0 QUIET) if(NOT OpenCV_FOUND) find_package(OpenCV 2.4.3 QUIET) if(NOT OpenCV_FOUND) message(FATAL_ERROR "OpenCV > 2.4.3 not found.") endif() endif() set(TENSORFLOW_INCLUDES /usr/local/include/tf/ /usr/local/include/tf/bazel-genfiles /usr/local/include/tf/tensorflow/ /usr/local/include/tf/tensorflow/third-party) set(TENSORFLOW_LIBS /usr/local/lib/libtensorflow_cc.so /usr/local/lib//libtensorflow_framework.so) include_directories( ${TENSORFLOW_INCLUDES} ${PROJECT_SOURCE_DIR}/third_party/eigen3 ) add_executable(load_simple_net simple/load_simple_net.cpp) target_link_libraries(load_simple_net ${TENSORFLOW_LIBS} ${OpenCV_LIBS} ) |
将这一文件保存为 CMakeLists.txt。
2)下载 eigen3
为了避免产生问题,我们这里要依赖自己的 eigen3 而不要用 tensorflow 下载的 eigen3,我们下载一个 eigen3 并放在 third_party/eigen3 目录,这里我使用的是 3.3.5 版本:
http://bitbucket.org/eigen/eigen/get/3.3.5.tar.bz2
现在我们将所有文件创建完毕,你的目录结构应该是这样的:
3)编译 & 运行
在工程根目录下使用如下命令编译运行:
1 2 3 4 | mkdir build cd build cmake .. make |
然后在 build 目录运行:
1 | ./load_simple_net ../simple/model/simple.pb |
如果运行成功会显示:
Session created successfully
Load graph protobuf successfully
Add graph to session successfully
Run session successfully
Tensor
output value: 6
到此你已经成功用 C++ API 加载了之前定义的图(尽管只是一个乘法)并利用这个图进行了计算。
后面我们将在此基础上尝试真正的神经网络的运算。
本文完整代码参见 Github:
https://github.com/skylook/tensorflow_cpp
参考文献
[1] https://github.com/formath/tensorflow-predictor-cpp
[2] https://github.com/zhangcliff/tensorflow-c-mnist
[3] https://blog.csdn.net/ztf312/article/details/72859075
Tensorflow C++ 从训练到部署系列文章目录
Tensorflow C++ 从训练到部署(3):使用 Keras 训练和部署 CNN |
Tensorflow C++ 从训练到部署(2):简单图的保存、读取与 CMake 编译 |
Tensorflow C++ 从训练到部署(1):环境搭建 |