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

时间:2017-09-13 15:13:25

标签: 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])

在iOS上的C ++中,我尝试模仿这个工作的snippit,如下所示:

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()!

方法FilePathForResourceNamePortableReadFileToProto取自here找到的TensorFlow iOS样本。

有什么问题?我注意到无论模型有多简单(see my issue report on GitHub),都会发生这种情况,这意味着问题不在于模型的具体细节。

2 个答案:

答案 0 :(得分:3)

这里的主要问题是您将图表导出到Python中的SavedModel,然后在C ++中将其作为GraphDef读取。虽然两者都有.pb扩展名并且相似,但它们并不相同。

您正在使用SavedModel阅读PortableReadFileToProto()并且失败,将空指针(model_graph)留给GraphDef对象。因此,在执行PortableReadFileToProto()之后,model_graph仍为空,但有效,GraphDef,这就是为什么错误显示会话未在Run()之前使用图表创建的原因< / em>的。 session->Create()成功,因为您使用空图成功创建了一个会话。

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

虽然您可以使用TensorFlow的C API通过TF_LoadSessionFromSavedModel()TF_SessionRun()SavedModel进行推理,但推荐的方法是使用{{3将图表导出到冻结模型或使用freeze_graph.py写入GraphDef。我将使用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
    )

在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();

这种通用方法可行,但您可能会遇到所构建的TensorFlow库中缺少所需操作的问题,因此推理仍然会失败。要解决此问题,请首先确保已通过克隆计算机上的repo并从根tensorflow-1.3.0目录运行tf.train.write_graph()来构建最新的TensorFlow 1.3。如果您像示例一样使用tensorflow/contrib/makefile/build_all_ios.sh Pod,则推理不太适用于自定义的非预制模型。使用build_all_ios.sh构建静态库后,您需要按照TensorFlow-experimental的说明在.xcconfig中将其链接起来。

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

  1.   

    未知操作的OpKernel('op:“[operation]”device_type:“CPU”'):   [操作]

  2.   

    没有注册OpKernel来支持Op'[operation]'   ATTRS。已注册的设备:[CPU],已注册的内核:[...]

  3. 错误#1表示来自.cctensorflow/core/ops的{​​{1}}文件对应的操作(或密切相关的操作)不在here文件的tf_op_files.txt文件中1}}。您必须找到包含tensorflow/core/kernels的{​​{1}}并将其添加到tensorflow/contrib/makefile。您必须通过再次运行.cc来重建。

    错误#2表示相应操作的REGISTER_OP("YourOperation")文件位于tf_op_files.txt文件中,但您已为操作提供了(a)不支持的数据类型或( b)被剥离以减小构造的尺寸。

    一个“问题”是,如果您在模型的实现中使用tensorflow/contrib/makefile/build_all_ios.sh,则会在.cc文件中将其导出为tf_op_files.txt,大多数操作都不支持此操作。使用tf.float64代替TF_DOUBLE,然后使用.pb重新保存模型。

    如果在检查为操作提供正确的数据类型后仍然收到错误#2,则需要删除位于tf.float32的makefile中的tf.float64或将其替换为{{ 1}}然后重建。

    在收到错误#1和#2后,您可能会成功推断。

答案 1 :(得分:0)

上述非常全面的解释的一个补充:

@ jshapy8正确地说&#34;你必须找到包含REGISTER_OP(&#34; YourOperation&#34;)的.cc并将其添加到tf_op_files.txt&#34;并且有一个过程可以简化一点:

## build the print_selective_register_header tool. Run from tensorflow root
bazel build tensorflow/python/tools:print_selective_registration_header
bazel-bin/tensorflow/python/tools/print_selective_registration_header \
--graphs=<path to your frozen model file here>/model_frozen.pb > ops_to_register.h

这将创建一个.h文件,该文件仅列出特定模型所需的操作。

现在编译静态库时,请按照手工构建说明进行操作here

指示说要执行以下操作:

make -f tensorflow/contrib/makefile/Makefile \
TARGET=IOS \
IOS_ARCH=ARM64

但是你可以将很多内容传递给特定于你需求的makefile,我发现以下是你最好的选择:

make -f tensorflow/contrib/makefile/Makefile \
TARGET=IOS IOS_ARCH=ARM64,x86_64 OPTFLAGS="-O3 -DANDROID_TYPES=ANDROID_TYPES_FULL -DSELECTIVE_REGISTRATION -DSUPPORT_SELECTIVE_REGISTRATION"

特别是你在这里告诉它只编译5个架构中的两个以加快编译时间(完整列表是:i386 x86_64 armv7 armv7s arm64,显然需要更长时间) - IOS_ARCH = ARM64,x86_64 - 然后你就是告诉它不要编译ANDROID_TYPES_SLIM(这将给你上面提到的Float / Int转换问题),然后最后你告诉它拉出所有必要的ops内核文件并将它们包含在make过程中。

更新。不知道为什么昨天这对我不起作用,但这可能是一种更清洁,更安全的方法:

build_all_ios.sh OPTFLAGS="-O3 -DANDROID_TYPES=ANDROID_TYPES_FULL -DSELECTIVE_REGISTRATION -DSUPPORT_SELECTIVE_REGISTRATION"

如果您想加快速度,请编辑/ Makefile目录中的compile_ios_tensorflow.sh。查找以下行:

BUILD_TARGET="i386 x86_64 armv7 armv7s arm64"

并将其更改为:

BUILD_TARGET="x86_64 arm64"