Examples
Create and load model
To create a model that you can open with cppflow you just need to create a tf.Module
or a tf.keras.Model
and save it. Using the functional API of keras this is as easy as:
import tensorflow as tf
input = tf.keras.Input(shape=(5,))
output = tf.keras.layers.Dense(5, activation=tf.nn.relu)(input)
output = tf.keras.layers.Dense(1, activation=tf.nn.sigmoid)(output)
model = tf.keras.Model(inputs=input, outputs=output)
model.compile()
# Export the model to a SavedModel
model.save('model', save_format='tf')
Now a new directory named model
is created, and it contains the saved model. You can open it from cppflow using the model
class, to then feed it with a tensor to obtain the output.
#include <iostream>
#include "cppflow/cppflow.h"
int main() {
auto input = cppflow::fill({10, 5}, 1.0f);
cppflow::model model("../model");
auto output = model(input);
std::cout << output << std::endl;
return 0;
}
Inference on EfficientNet
For this example we use a pretrained EfficientNet network that is available in Keras applications. Running the following code will create a model
directory with the definition of the EfficientNet and its weights.
import tensorflow as tf
model = tf.keras.applications.EfficientNetB0()
# Export the model to a SavedModel
model.save('model', save_format='tf')
Now we can open the model from cppflow and perform inference with a real image.

We can load the image using cppflow::read_file
and cppflow::decode_jpeg
. Then we have to convert it to float and feed it to the network.
#include <iostream>
#include "cppflow/cppflow.h"
int main() {
auto input = cppflow::decode_jpeg(cppflow::read_file(std::string("../my_cat.jpg")));
input = cppflow::cast(input, TF_UINT8, TF_FLOAT);
input = cppflow::expand_dims(input, 0);
cppflow::model model("../model");
auto output = model(input);
std::cout << "It's a tiger cat: " << cppflow::arg_max(output, 1) << std::endl;
return 0;
}
To see the prediction of the network we apply cppflow::arg_max
to the ouput and it will show the number of the predicted class, which corresponds with a tiger cat.
Multi input/output model
For this example we will create a Keras model that takes two inputs and produce two outputs:
import tensorflow as tf
input_1 = tf.keras.Input(shape=(5,), name='my_input_1')
input_2 = tf.keras.Input(shape=(5,), name='my_input_2')
x1 = tf.keras.layers.Dense(5, activation=tf.nn.relu)(input_1)
x2 = tf.keras.layers.Dense(5, activation=tf.nn.relu)(input_2)
output_1 = tf.keras.layers.Dense(1, activation=tf.nn.sigmoid, name='my_outputs_1')(x1)
output_2 = tf.keras.layers.Dense(1, activation=tf.nn.sigmoid, name='my_outputs_2')(x2)
model = tf.keras.Model(inputs=[input_1, input_2], outputs=[output_1, output_2])
model.compile()
# Export the model to a SavedModel
model.save('model', save_format='tf')
Now, we will inspect the model with the saved_model_cli to retrieve the name of the operations, and to know how to call the model.
$ saved_model_cli show --dir model
'serve'
$ saved_model_cli show --dir model --tag_set serve
SignatureDef key: "__saved_model_init_op"
SignatureDef key: "serving_default"
$ saved_model_cli show --dir model --tag_set serve --signature_def serving_default
The given SavedModel SignatureDef contains the following input(s):
inputs['my_input_1'] tensor_info:
dtype: DT_FLOAT
shape: (-1, 5)
name: serving_default_my_input_1:0
inputs['my_input_2'] tensor_info:
dtype: DT_FLOAT
shape: (-1, 5)
name: serving_default_my_input_2:0
The given SavedModel SignatureDef contains the following output(s):
outputs['my_outputs_1'] tensor_info:
dtype: DT_FLOAT
shape: (-1, 1)
name: StatefulPartitionedCall:0
outputs['my_outputs_2'] tensor_info:
dtype: DT_FLOAT
shape: (-1, 1)
name: StatefulPartitionedCall:1
Method name is: tensorflow/serving/predict
From this output we can see that there are two inputs (serving_default_my_input_1:0
and serving_default_my_input_2:0
) and two outputs (StatefulPartitionedCall:0
and StatefulPartitionedCall:1
). You can run the model specifying multiple inputs as a vector of tuples <name of the input, input tensor
and multiple outputs as a vector with the name of the outputs:
#include <iostream>
#include "cppflow/cppflow.h"
int main() {
auto input_1 = cppflow::fill({10, 5}, 1.0f);
auto input_2 = cppflow::fill({10, 5}, -1.0f);
cppflow::model model("../model");
auto output = model({{"serving_default_my_input_1:0", input_1}, {"serving_default_my_input_2:0", input_2}}, {"StatefulPartitionedCall:0", "StatefulPartitionedCall:1"});
std::cout << "output_1: " << output[0] << std::endl;
std::cout << "output_2: " << output[1] << std::endl;
return 0;
}