使用 frugally-deep 在 C++ 中轻松调用 Keras 模型
Keras 使用起来非常方便,我们有时候需要在 C++ 中调用训练好的模型,希望在 C++ 中调用 Keras 的 model.predict()
,但 Keras 并没有提供 C++ API。一种解决方法是使用 TensorFlow 的 C++ API,但编译过程非常繁琐,容易失败。这里我们使用另一种方法,使用 Github 上的 frugally-deep。
1 介绍
frugally-deep 是一个用 C++ 实现的库,它可以将 Keras 保存的 .h5
文件直接转为 C++ 中可调用的 .json
文件,经过一步转换后就可以直接调用。frugally-deep 使用起来比较简单,支持非常多常用的模型,无需编译 TensorFlow。同时它也是线程安全的,可以很方便的在多 CPU 上进行前向传播。另外,frugally-deep 不支持使用 GPU,如果不是必须要使用 GPU,frugally-deep 是一个很好的选择。
1.1 支持的模型
frugally-deep 支持大多数常用的模型
Add
,Concatenate
,Subtract
,Multiply
,Average
,Maximum
AveragePooling1D/2D
,GlobalAveragePooling1D/2D
Bidirectional
,TimeDistributed
,GRU
,LSTM
,CuDNNGRU
,CuDNNLSTM
Conv1D/2D
,SeparableConv2D
,DepthwiseConv2D
Cropping1D/2D
,ZeroPadding1D/2D
BatchNormalization
,Dense
,Flatten
Dropout
,AlphaDropout
,GaussianDropout
,GaussianNoise
SpatialDropout1D
,SpatialDropout2D
,SpatialDropout3D
MaxPooling1D/2D
,GlobalMaxPooling1D/2D
ELU
,LeakyReLU
,ReLU
,SeLU
,PReLU
Sigmoid
,Softmax
,Softplus
,Tanh
UpSampling1D/2D
Reshape
,Permute
Embedding
以及
- multiple inputs and outputs
- nested models
- residual connections
- shared layers
- variable input shapes
- arbitrary complex model architectures / computational graphs
- custom layers (by passing custom factory functions to
load_model
)
2 安装
frugally-deep 的安装很简单,可以使用官方安装教程 INSTALL.md 上的命令安装,也可以直接下载源码。使用命令安装的方法在官方的教程中已经很详细了,所以这里采用直接下载源码的方式。
使用 frugally-deep 前需要有 (截止2020年6月13日)
- 一个支持 C++14 的编译器
- Python 版本在 3.7 或以上
- TensorFlow 2.1.1
如果你正在使用 Tensorflow 1.x 的 Keras,安装 TensorFlow 2.x.x 后大多数情况下只需要把 import keras
修改为 import tensorflow.keras
即可,Keras 的改动并不大。
2.1 下载源码
分别前往frugally-deep, FunctionalPlus , Eigen 和 json 点击右侧的 Code 再点击 Download ZIP 下载这些源码。(Gitlab 直接点击下载)
2.2 解压
假设现在代码根目录下的源文件只有 main.cpp
,文件结构为:
1 | +-- main.cpp |
将 frugally-deep 源码中的 include 文件夹和 keras_export 文件夹解压到与 main.cpp
相同的目录下:
1 | +-- include |
将 FunctionalPlus 源码中 include 文件夹内的 fplus 文件夹复制到 main.cpp
相同目录下的 include 文件夹内:
1 | +-- include |
同样,将 json 源码中 include 文件夹内的 nlohmann 文件夹复制到 main.cpp
相同目录下的 include 文件夹内:
1 | +-- include |
最后将 eigen 源码中 Eigen 文件夹放在 main.cpp
相同目录下的 include 文件夹内
1 | +-- include |
需要的文件已经安装完成,下面可以开始使用了。
3 使用
总的来说,使用 frugally-deep 的步骤为:
- 在 Python 中训练好模型后,使用
model.save('....h5', include_optimizer=False)
保存模型 - 使用
keras_export/convert_model.py
将.h5
模型转换成 C++ 模型 - 在 C++ 中使用
fdeep::load_model(...)
加载模型 - 在 C++ 中使用
model.predict(...)
调用模型
下面我们以 frugally-deep 主页 上的例子来说明如何使用。假设我们在 Python 中编写了模型
1 | # create_model.py |
运行 create_model.py
后,当前目录下生成了 keras_model.h5
,接下来使用下面的命令进行转换
1 | python keras_export/convert_model.py keras_model.h5 fdeep_model.json |
看到下面这些就是转换成功了,转换成功后,当前目录下会生成 fdeep_model.json
,在 C++ 中读取 fdeep_model.json
就可以直接调用了。
1 | Forward pass took 0.091729 s. |
转换过程中,frugally-deep 会自动对模型进行测试,验证相同的输入下 ,模型在 Python 和 C++ 中的输出是否相同。若输出不同会直接报错,所以不必担心转换出错。
转换完成后,在 C++ 中进行调用
1 | // main.cpp |
这时,以 Visual Studio 为例,编译器会报错
1 | fatal error C1083: 无法打开包括文件: “fdeep/fdeep.hpp”: No such file or directory |
这是因为还没有添加附加包含目录,右键点击**”解决方案资源管理器”中的项目名称,选择属性** -> 配置属性 -> C/C++ -> 常规,在右侧的附加包含目录中填上 $(ProjectDir)include;
若使用的是 gcc 编译器,要在编译时加上参数 -Iinclude
再次运行 main.cpp
,输出:
1 | Loading json ... done. elapsed time: 0.009921 s |
成功输出了结果,调用成功。另外,model.predict()
是线程安全的,可以直接在多个线程中调用。如果想在多 CPU 上并行预测,使用 model::predict_multi
就会自动在多 CPU 上执行 model.predict()
。要注意的是,model::predict_multi
的并行是对多个输入数据的并行,并不是对一个数据的并行。
4 常见问题
4.1 model.predict() 的输入输出类型
model.predict() 的输入类型是 fdeep::tensor
,下面的例子说明了如何声明一个 fdeep::tensor
并初始化
1 | // 声明一个tensor,形状参数在fdeep::tensor_shape()中,0是初始化的值 |
有了 fdeep::tensor
我们可能会需要将其转化为 std::vector
进行后续的操作
1 | // 将tensor转为std::vector |
需要注意的是,model.predict()
返回的类型是 fdeep::tensors
而不是fdeep::tensor
。实际上 fdeep::tensors
是作者给 std::vector<tensor>
定义的别名,若想将其转为 vector
,可使用
1 | // 将vector<tensor>中的第一个tensor转为vector<float> |
除此之外,其他的方法可以参考官方 FAQ.md。要注意,frugally-deep 中必须采用 channel-last 的格式。
4.2 error C2653:
使用 Visual Studio 2019 时可能会遇到这个问题
1 | error C2653: 'FOut': is not a class or namespace name |
这是一个编译器 BUG,详见 Github issue ,微软官方称已经在 16.5 Preview 2 版本中修复,但是目前升级到最新版 16.6 后仍有很多人反应存在此问题,解决方法是使用 Visual Studio 2017 或 gcc 编译器。
4.3 fdeep::model 没有默认构造函数
当使用 fdeep::model
作为类的成员变量时,会遇到 fdeep::model 没有默认构造函数
的问题,这是作者刻意为之的,解决方法是使用std::unique_ptr<fdeep::model>
下面的例子说明了如何使用
1 | // neural_network.h |
1 | // neural_network.cpp |
4.4 运行速度比 Python 慢 100 倍
这是因为编译器没有开优化。若使用 Visual Studio ,要把”Debug”模式改为”Release”模式。 gcc 要开 -O3 优化。修改后就正常了。参考FAQ.md
5 总结
如果你需要在 C++ 中调用 model.predict()
且没有使用 GPU 的需求,frugally-deep 是一个很好的选择。
还想了解更多可以阅读官方的英文资料 frugally-deep