Tensorflow冻结图Protobuf无法使用C API进行预测

时间:2019-07-19 09:28:19

标签: python c tensorflow keras

我已经训练过使用this repo进行语义分割的模型,取得了不错的效果,并尝试在使用tensorflow c API编写的小型库中使用此网络。我使用this repo将keras模型转换为protobuf文件,并使用以下代码运行会话:

NSURlSessionConfiguration

问题在于预测结果总是相同的。我尝试传递随机数据和真实图像,但结果是相同的。但是,不同的训练模型给出的预测结果不同,但是对于每个模型,它总是相同的。如您在代码段中所见,我检查了每次传递不同的数据并获得相同的预测

typedef struct model_t {
    TF_Graph* graph;
    TF_Session* session;
    TF_Status* status;

    TF_Output input, target, output;

    TF_Operation *init_op, *train_op, *save_op, *restore_op;
    TF_Output checkpoint_file;
} model_t;

typedef struct NetProperties {
    int width;
    int height;
    int border;
    int classes;
    int inputSize;
} NetProperties;

static model_t * model;
static NetProperties * properties;

extern "C" EXPORT int ModelCreate(const char* nnFilename, const char* inputName, const char* outputName, int pictureWidth, int pictureHeight, int border, int classes) {
    ModelDestroy();
    model = (model_t*)malloc(sizeof(model_t));;
    model->status = TF_NewStatus();
    model->graph = TF_NewGraph();
    properties = (NetProperties*)malloc(sizeof(NetProperties));
    properties->width = pictureWidth;
    properties->height = pictureHeight;
    properties->border = border;
    properties->classes = classes;
    properties->inputSize = (pictureWidth + border * 2) * (pictureHeight + border * 2) * 3;
    {
        // Create the session.
        TF_SessionOptions* opts = TF_NewSessionOptions();
        model->session = TF_NewSession(model->graph, opts, model->status);
        TF_DeleteSessionOptions(opts);
        if (!Okay(model->status)) return 0;
    }

    TF_Graph* g = model->graph;

    {
        // Import the graph.
        TF_Buffer* graph_def = read_file(nnFilename);
        if (graph_def == NULL) return 0;
        printf("Read GraphDef of %zu bytes\n", graph_def->length);
        TF_ImportGraphDefOptions* opts = TF_NewImportGraphDefOptions();
        TF_GraphImportGraphDef(g, graph_def, opts, model->status);
        TF_DeleteImportGraphDefOptions(opts);
        TF_DeleteBuffer(graph_def);
        if (!Okay(model->status)) return 0;
    }

    // Handles to the interesting operations in the graph.
    model->input.oper = TF_GraphOperationByName(g, inputName);
    model->input.index = 0;
    model->target.oper = TF_GraphOperationByName(g, "target");
    model->target.index = 0;
    model->output.oper = TF_GraphOperationByName(g, outputName);
    model->output.index = 0;


    model->init_op = TF_GraphOperationByName(g, "init");
    model->train_op = TF_GraphOperationByName(g, "train");
    model->save_op = TF_GraphOperationByName(g, "save/control_dependency");
    model->restore_op = TF_GraphOperationByName(g, "save/restore_all");

    model->checkpoint_file.oper = TF_GraphOperationByName(g, "save/Const");
    model->checkpoint_file.index = 0;
    // first prediction is slow
    unsigned char * randomData = (unsigned char*)malloc(properties->inputSize * sizeof(unsigned char));
    for (int i = 0; i < properties->inputSize; i++) {
        randomData[i] = (unsigned char)100;
    }
    ModelPredict(randomData);
    free(randomData);
    return 1;
}

extern "C" EXPORT void ModelDestroy() {
    if (model == nullptr) return;
    TF_DeleteSession(model->session, model->status);
    Okay(model->status);
    TF_DeleteGraph(model->graph);
    TF_DeleteStatus(model->status);
    free(model);
}

extern "C" EXPORT unsigned char* ModelPredict(unsigned char * batch1) {
    if (model == NULL) return NULL;

    const int64_t dims[4] = { 1, properties->height + properties->border * 2, properties->width + properties->border * 2, 3 };
    size_t nbytes = properties->inputSize;

    // can be faster
    float * arrayOfFloats = (float*)malloc(nbytes * sizeof(float));
    //float sumUp = 0;

    for (int i = 0; i < properties->inputSize; i++) {
        arrayOfFloats[i] = batch1[i] * (1.f / 255.f);
        //sumUp += arrayOfFloats[i];
    }
    //std::cout << sumUp << std::endl;
    // removed due to jdehesa answer
    //float ** inputFloats = (float**)malloc(nbytes * sizeof(float*));
    //inputFloats[0] = arrayOfFloats;


    // Optionally, you can check that your input_op and input tensors are correct
    //// by using some of the functions provided by the C API.
    //std::cout << "Input op info: " << TF_OperationNumOutputs(input_op) << "\n";
    //std::cout << "Input data info: " << TF_Dim(input, 0) << "\n";

    std::vector<TF_Output> inputs;
    std::vector<TF_Tensor*> input_values;
    TF_Operation* input_op = model->input.oper;
    TF_Output input_opout = { input_op, 0 };
    inputs.push_back(input_opout);
    // reworked due to jdehesa answer
    //TF_Tensor* input = TF_NewTensor(TF_FLOAT, dims, 4, (void*)inputFloats, //nbytes * sizeof(float), &Deallocator, NULL);
    TF_Tensor* input = TF_NewTensor(TF_FLOAT, dims, 4, (void*)arrayOfFloats, nbytes * sizeof(float), &Deallocator, NULL);
    input_values.push_back(input);

    int outputSize = properties->width * properties->height * properties->classes;

    int64_t out_dims[] = { 1, properties->height, properties->width, properties->classes };

    // Create vector to store graph output operations
    std::vector<TF_Output> outputs;
    TF_Operation* output_op = model->output.oper;
    TF_Output output_opout = { output_op, 0 };
    outputs.push_back(output_opout);

    // Create TF_Tensor* vector
    //std::vector<TF_Tensor*> output_values(outputs.size(), nullptr);

    // Similar to creating the input tensor, however here we don't yet have the
    // output values, so we use TF_AllocateTensor()
    TF_Tensor* output_value = TF_AllocateTensor(TF_FLOAT, out_dims, 4, outputSize * sizeof(float));
    //output_values.push_back(output_value);

    //// As with inputs, check the values for the output operation and output tensor
    //std::cout << "Output: " << TF_OperationName(output_op) << "\n";
    //std::cout << "Output info: " << TF_Dim(output_value, 0) << "\n";

    TF_SessionRun(model->session, NULL,
        &inputs[0], &input_values[0], inputs.size(),
        &outputs[0], &output_value, outputs.size(),
        /* No target operations to run */
        NULL, 0, NULL, model->status);
    if (!Okay(model->status)) return NULL;

    TF_DeleteTensor(input_values[0]);

    // memory allocations take place here
    float* prediction = (float*)TF_TensorData(output_value);
    //float* prediction = (float*)malloc(sizeof(float) * properties->inputSize / 3 * properties->classes);
    //memcpy(prediction, TF_TensorData(output_value), sizeof(float) * properties->inputSize / 3 * properties->classes);
    unsigned char * charPrediction = new unsigned char[outputSize * sizeof(unsigned char)];
    sumUp = 0;
    for (int i = 0; i < outputSize; i++) {
        charPrediction[i] = (unsigned char)((prediction[i] * 255));
        //sumUp += prediction[i];
    }
    //std::cout << sumUp << std::endl << std::endl;
    //free(prediction);
    TF_DeleteTensor(output_value);
    return charPrediction;
}

我试图将自己的keras编写到tensorflow .pb转换器,但结果相同。

// first is float sum of passed picture, second is the float sum of answer
724306
22982.6

692004
22982.6

718490
22982.6

692004
22982.6

720861
22982.6

692004
22982.6

帮助我找出如何在c api中修复预测结果。

更新1:按照jdehesa的建议重新构造输入数组

更新2:添加了import os, argparse import tensorflow as tf from tensorflow.keras.utils import get_custom_objects from segmentation_models.losses import bce_dice_loss,dice_loss,cce_dice_loss from segmentation_models.metrics import iou_score # some custom functions from segmentation_models get_custom_objects().update({ 'dice_loss': dice_loss, 'bce_dice_loss': bce_dice_loss, 'cce_dice_loss': cce_dice_loss, 'iou_score': iou_score, }) def freeze_keras(model_name): tf.keras.backend.set_learning_phase(0) model = tf.keras.models.load_model(model_name) sess = tf.keras.backend.get_session() constant_graph = tf.graph_util.convert_variables_to_constants(sess, sess.graph.as_graph_def(), [out.op.name for out in model.outputs]) tf.train.write_graph(constant_graph, './', 'saved_model.pb', as_text=False) freeze_keras('best-weights.hdf5') model的定义

2 个答案:

答案 0 :(得分:0)

我认为您没有正确设置输入数据。让我们看看。

float * arrayOfFloats1 = (float*)malloc(nbytes * sizeof(float));
float sumUp = 0;

在这里创建arrayOfFloats1来保存所有图像数据。

for (int i = 0; i < properties->inputSize; i++) {
    arrayOfFloats1[i] = batch1[i] * (1.f / 255.f);
    sumUp += arrayOfFloats1[i];
}
std::cout << sumUp << std::endl;

在这里将arrayOfFloats1设置为图像数据。很好。

但是然后:

float ** inputFloats = (float**)malloc(nbytes * sizeof(float*));

这里有inputFloats,其中有nbytes个浮点指针的空间。首先,您可能想为浮点值而不是浮点指针(可能没有相同的大小)分配空间。然后:

inputFloats[0] = arrayOfFloats1;

在这里,您将那些nbytes指针中的第一个设置为指针arrayOfFloats1。然后inputFloats用作模型的输入。但是其余的nbytes - 1指针尚未设置为任何东西。尽管不是必需的,但它们可能都设置为零。

如果您只想使用arrayOfFloats1创建“浮点数数组”,则无需分配任何内存,只需执行以下操作即可:

float ** inputFloats = &arrayOfFloats1;

但是您实际上像这样使用inputFloats

TF_Tensor* input = TF_NewTensor(
    TF_FLOAT, dims, 4, (void*)inputFloats, nbytes * sizeof(float), &Deallocator, NULL);

因此,您在这里说inputinputFloats中的数据组成,这些数据将是指向arrayOfFloats1的指针,然后是未初始化的内存。也许您实际上想要的是这样的东西:

TF_Tensor* input = TF_NewTensor(
    TF_FLOAT, dims, 4, (void*)arrayOfFloats1, nbytes * sizeof(float), &Deallocator, NULL);

这意味着input将是由您之前复制的arrayOfFloats1中的数据组成的张量。实际上,我认为您的代码根本不需要inputFloats

否则,据我所知,其余代码似乎是正确的。您应该确保在所有情况下都正确释放了所有已分配的内存(例如,当您执行if (!Okay(model->status)) return NULL;时,可能应该在返回之前删除输入和输出张量),但这是另一个问题。

答案 1 :(得分:0)

问题出在模型中。我已经使用来自图像的非标准化数据(像素值介于0.0到255.0之间)训练了它,并尝试干扰未分割的数据(我将每个像素值划分为255 std::cin.fail(),并且得到的值介于0.0到1.0之间),所以我的模型认为每次都会得到黑色图像,并给了我类似的答案。因此我删除了归一化,模型开始进行预测。