在iOS上使用TensorFlow C ++进行推理错误:“参数无效:未在Run()之前使用图形创建会话!”

jsh*_*py8 5 c++ ios tensorflow

我正在尝试使用TensorFlow的C ++ API在iOS上运行我的模型。该模型SavedModel另存为.pb文件。但是,Session::Run()导致错误的调用:

“无效的参数:在Run()之前没有使用图形创建会话!”

在Python中,我可以使用以下代码在模型上成功运行推理:

with tf.Session() as sess:
    tf.saved_model.loader.load(sess, ['serve'], '/path/to/model/export')
    result = sess.run(['OutputTensorA:0', 'OutputTensorB:0'], feed_dict={
        'InputTensorA:0': np.array([5000.00] * 1000).reshape(1, 1000),
        'InputTensorB:0': np.array([300.00] * 1000).reshape(1, 1000)
    })
    print(result[0])
    print(result[1])
Run Code Online (Sandbox Code Playgroud)

在iOS上的C ++中,我尝试如下模仿该工作片段:

tensorflow::Input::Initializer input_a(5000.00, tensorflow::TensorShape({1, 1000}));
tensorflow::Input::Initializer input_b(300.00, tensorflow::TensorShape({1, 1000}));

tensorflow::Session* session_pointer = nullptr;

tensorflow::SessionOptions options;
tensorflow::Status session_status = tensorflow::NewSession(options, &session_pointer);

std::cout << session_status.ToString() << std::endl; // prints OK

std::unique_ptr<tensorflow::Session> session(session_pointer);

tensorflow::GraphDef model_graph;

NSString* model_path = FilePathForResourceName(@"saved_model", @"pb");
PortableReadFileToProto([model_path UTF8String], &model_graph);

tensorflow::Status session_init = session->Create(model_graph);

std::cout << session_init.ToString() << std::endl; // prints OK

std::vector<tensorflow::Tensor> outputs;
tensorflow::Status session_run = session->Run({{"InputTensorA:0", input_a.tensor}, {"InputTensorB:0", input_b.tensor}}, {"OutputTensorA:0", "OutputTensorB:0"}, {}, &outputs);

std::cout << session_run.ToString() << std::endl; // Invalid argument: Session was not created with a graph before Run()!
Run Code Online (Sandbox Code Playgroud)

方法FilePathForResourceNamePortableReadFileToProto来自此处的TensorFlow iOS示例。

问题是什么?我注意到,无论模型多么简单,都会发生这种情况(请参阅我在GitHub上的问题报告),这意味着问题不在于模型的细节。

jsh*_*py8 4

这里的主要问题是您将图形导出到SavedModelPython 中的 a,然后将其作为GraphDefC++ 中的 a 读取。虽然两者都有.pb扩展并且相似,但它们并不等同。

发生的情况是,您正在读取SavedModelwith PortableReadFileToProto(),但它失败了,留下了一个指向对象的空指针 ( model_graph) GraphDef。因此,在执行 后PortableReadFileToProto()model_graph仍然是一个空但有效的GraphDef,这就是为什么错误显示Session 不是在 Run() 之前使用图形创建的session->Create()成功是因为您成功创建了带有空图的会话。

检查是否失败的方法PortableReadFileToProto()是检查其返回值。它返回一个 bool,如果读取图表失败,则该值为 0。如果您希望在此处获得描述性错误,请使用ReadBinaryProto()。判断读取图表是否失败的另一种方法是检查 的值model_graph.node_size()。如果这是 0,那么您有一个空图并且读取失败。

虽然您可以使用 TensorFlow 的 C APISavedModel通过使用TF_LoadSessionFromSavedModel()和来对 a 执行推理TF_SessionRun(),但推荐的方法是使用 来将图形导出到冻结模型freeze_graph.py或写入到GraphDefusing tf.train.write_graph()。我将演示使用以下方法导出的模型的成功推理tf.train.write_graph()

在Python中:

# Build graph, call it g
g = tf.Graph()

with g.as_default():
    input_tensor_a = tf.placeholder(dtype=tf.int32, name="InputTensorA")
    input_tensor_b = tf.placeholder(dtype=tf.int32, name="InputTensorB")
    output_tensor_a = tf.stack([input_tensor_a], name="OutputTensorA")
    output_tensor_b = tf.stack([input_tensor_b], name="OutputTensorB")

# Save graph g
with tf.Session(graph=g) as sess:
    sess.run(tf.global_variables_initializer())
    tf.train.write_graph(
        graph_or_graph_def=sess.graph_def,
        logdir='/path/to/export',
        name='saved_model.pb',
        as_text=False
    )
Run Code Online (Sandbox Code Playgroud)

在 C++ (Xcode) 中:

using namespace tensorflow;
using namespace std;

NSMutableArray* predictions = [NSMutableArray array];

Input::Initializer input_tensor_a(1, TensorShape({1}));
Input::Initializer input_tensor_b(2, TensorShape({1}));

SessionOptions options;
Session* session_pointer = nullptr;
Status session_status = NewSession(options, &session_pointer);
unique_ptr<Session> session(session_pointer);

GraphDef model_graph;
string model_path = string([FilePathForResourceName(@"saved_model", @"pb") UTF8String]);

Status load_graph = ReadBinaryProto(Env::Default(), model_path, &model_graph);

Status session_init = session->Create(model_graph);

cout << "Session creation Status: " << session_init.ToString() << endl;
cout << "Number of nodes in model_graph: " << model_graph.node_size() << endl;
cout << "Load graph Status: " << load_graph.ToString() << endl;

vector<pair<string, Tensor>> feed_dict = {
    {"InputTensorA:0", input_tensor_a.tensor},
    {"InputTensorB:0", input_tensor_b.tensor}
};

vector<Tensor> outputs;
Status session_run = session->Run(feed_dict, {"OutputTensorA:0", "OutputTensorB:0"}, {}, &outputs);

[predictions addObject:outputs[0].scalar<int>()];
[predictions addObject:outputs[1].scalar<int>()];

Status session_close = session->Close();
Run Code Online (Sandbox Code Playgroud)

这种通用方法是可行的,但您可能会遇到您构建的 TensorFlow 库中缺少所需操作的问题,因此推理仍然会失败。为了解决这个问题,首先确保您已经通过在计算机上克隆存储库并从根目录运行来构建最新的TensorFlow1.3。如果您像示例一样使用 Pod,则推理不太可能适用于自定义的非固定模型。一旦您使用构建了静态库,您需要按照此处的说明将其链接到您的库中。tensorflow/contrib/makefile/build_all_ios.shtensorflow-1.3.0TensorFlow-experimentalbuild_all_ios.sh.xcconfig

一旦您成功地将使用 makefile 构建的静态库链接到 Xcode,您可能仍然会遇到阻止推理的错误。虽然您将收到的实际错误取决于您的实现,但您将收到分为两种不同形式的错误:

  1. OpKernel('op:“[操作]”device_type:“CPU”')对于未知操作:[操作]

  2. 没有注册 OpKernel 来支持具有这些属性的 Op“[操作]”。注册设备:[CPU],注册内核:[...]

错误 #1 表示相应操作(或密切相关的操作)的.cc文件不在. 您必须找到包含的并将其添加到. 您必须通过再次运行来重建。tensorflow/core/opstensorflow/core/kernelstf_op_files.txttensorflow/contrib/makefile.ccREGISTER_OP("YourOperation")tf_op_files.txttensorflow/contrib/makefile/build_all_ios.sh

错误 #2 意味着.cc相应操作的文件位于您的tf_op_files.txt文件中,但您为操作提供了 (a) 不支持的数据类型,或者 (b) 被删除以减少构建的大小。

一个“问题”是,如果您tf.float64在模型的实现中使用,则会将其导出为TF_DOUBLE您的.pb文件中,并且大多数操作不支持这一点。使用tf.float32代替tf.float64,然后使用 重新保存模型tf.train.write_graph()

如果在检查是否为操作提供了正确的数据类型后仍然收到错误#2,则需要删除__ANDROID_TYPES_SLIM__位于的 makefile 中的内容tensorflow/contrib/makefile或将其替换为__ANDROID_TYPES_FULL__,然后重建。

通过错误 #1 和 #2 后,您可能会成功推理。