我正在尝试使用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()!
方法FilePathForResourceName
和PortableReadFileToProto
取自here找到的TensorFlow iOS样本。
有什么问题?我注意到无论模型有多简单(see my issue report on GitHub),都会发生这种情况,这意味着问题不在于模型的具体细节。
答案 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链接后,您可能仍会收到阻止推理的错误。虽然您将获得的实际错误取决于您的实现,但您将收到分为两种不同形式的错误:
未知操作的OpKernel('op:“[operation]”device_type:“CPU”'): [操作]
没有注册OpKernel来支持Op'[operation]' ATTRS。已注册的设备:[CPU],已注册的内核:[...]
错误#1表示来自.cc
或tensorflow/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"