使用libtensorflow和C API时的不确定性行为



我正在尝试使用libtensorflow在TensorFlow模型上运行推理用C/c++编写的程序的库. C库没有很好的文档,但我通过在网上找到的一些教程找到了如何做的方法。下面有一个最小程序重现了我的问题。它基本上是从磁盘加载模型,检查输入和输出张量的尺寸,并将一些数据输入。

当我用Python训练和测试模型时,我确切地知道给定输入的预期输出是什么。通常,对于在这个最小程序中设置的输入,输出必须是这个向量:

0.0061
0.0349
0.0843
0.7584
0.0767
0.0320
0.0073

,这是我从C/c++程序中找到的结果,…,有时候。但是这是完全不确定的. 也许5次中有1次是有效的。其他时候,我只是得到错误的值,比如:

1.000000
0.000000
0.000000
0.000000
0.000000
0.000000
0.000000

我可以理解我的程序有问题,但我很难解释为什么我得到这种随机行为。

我在不同的机器上测试了这个程序,Windows 10和Linux都有。我使用的库是可以在这里下载的,版本2.4 CPU仅. 如果代码不够,我也在这里上传了我的模型。

如果有人能指出这段代码中的错误,我将非常感激。


#include <string>
#include "tensorflow/c/c_api.h"
static void NoOpDeallocator (void* /*data*/, size_t /*a*/, void* /*b*/)
{
}
int InputIndicesToIndex (int x, int y, int z)
{
int index = x * 7 * 4 + y * 4 + z;
return index;
}
int main (void)
{
const char* path = "./Model";
const char* input_name = "serving_default_input_1";
const char* output_name = "StatefulPartitionedCall";
// Load graph
TF_Graph* graph = TF_NewGraph ();
TF_Status* status = TF_NewStatus ();
TF_Buffer* runOptions = nullptr;
TF_SessionOptions* options = TF_NewSessionOptions ();
const char* tags = "serve";
int ntags = 1;
TF_Session* session = TF_LoadSessionFromSavedModel (options, runOptions, path, &tags, ntags, graph, nullptr, status);
if (TF_GetCode (status) != TF_OK)
{
printf ("n[ERROR] While loading TensorFlow saved model, got: %s", TF_Message (status));
return -1;
}
// Check graph input and output
TF_Output input = {TF_GraphOperationByName (graph, input_name), 0};
if (input.oper == nullptr)
{
printf ("nCould not find operation name in graph, got message: "%s"", TF_Message (status));
return -1;
}
TF_Output output = {TF_GraphOperationByName (graph, output_name), 0};
if (output.oper == nullptr)
{
printf ("nCould not find operation name in graph, got message: "%s"", TF_Message (status));
return -1;
}
if (TF_OperationOutputType (input) != TF_FLOAT || TF_OperationOutputType (output) != TF_FLOAT)
{
printf ("nUnexpected data type");
return -1;
}
// Check input shape
int64_t dims [16];
int numDims = TF_GraphGetTensorNumDims (graph, input, status);
TF_GraphGetTensorShape (graph, input, dims, numDims, status);
if (TF_GetCode (status) != TF_OK) {
printf ("nCould not retrieve tensor shape, got message: "%s"", TF_Message (status));
return -1;
}
if (numDims != 4 || dims [0] != -1 || dims [1] != 6 || dims [2] != 7 || dims [3] != 4)
{
printf ("nUnexpected input tensor dimensions");
return -1;
}
// Bind data array to graph input
float inputData [1 * 6 * 7 * 4];
dims [0] = 1;
TF_Tensor* inputTensor = TF_NewTensor (TF_FLOAT, dims, numDims, inputData, sizeof (float) * 6 * 7 * 4, &NoOpDeallocator, 0);
if (inputTensor == nullptr)
{
printf ("nCould not create input tensor");
return -1;
}
// Check output shape
numDims = TF_GraphGetTensorNumDims (graph, output, status);
TF_GraphGetTensorShape (graph, output, dims, numDims, status);
if (TF_GetCode (status) != TF_OK) {
printf ("Could not retrieve tensor shape, got message: "%s"", TF_Message (status));
return -1;
}
if (numDims != 2 || dims [0] != -1 || dims [1] != 7)
{
printf ("nUnexpected output tensor dimensions");
return -1;
}
// Feed input with some data
for (int y = 0; y < 6; y ++)
{
for (int x = 0; x < 7; x ++)
{
inputData [InputIndicesToIndex (y, x, 0)] = 0.0f;
inputData [InputIndicesToIndex (y, x, 1)] = 1.0f;
inputData [InputIndicesToIndex (y, x, 2)] = 0.0f;
inputData [InputIndicesToIndex (y, x, 3)] = 0.0f;
}
}

// Run the session to compute the output
TF_Tensor* outputTensor;
TF_SessionRun (session, nullptr, &input, &inputTensor, 1, &output, &outputTensor, 1, nullptr, 0, nullptr, status);
if (TF_GetCode (status) != TF_OK)
{
printf ("n[ERROR] Could not run graph inference, got %s", TF_Message (status));
return -1;
}
// Read out the result
float* outputData = (float*) TF_TensorData (outputTensor);
for (int i = 0; i < 7; i ++)
printf ("n%f", outputData [i]);

if ((int) (outputData [3] * 1000.0f) != 758) {
printf ("n[ERROR] Unexpected inference result");
return -1;
}
else {
printf ("n[OK] Inference ran fine");
return 0;
}
}

好吧,在发布了这个问题之后,我继续调查自己,我终于可以弄清楚发生了什么。我把我的解释写在这里,希望有一天它能帮助到别人。

问题来自libtensorflow库的未记录行为(对于未记录的库来说是可能的)。在阅读包含文件时,我发现了另一种创建张量的方法,即使用TF_AllocateTensor而不是TF_NewTensor。我尝试了这个功能,然后它最终像魅力一样工作。

通过使用TF_NewTensor,你基本上要求TensorFlow创建一个新的张量,包装一个数据缓冲区提供的。但问题是:TensorFlow可以决定是否使用您的数据缓冲区或将重新分配到其他地方. 可以这样说,通过调用必须提供的解分配函数来警告您。但是它可能完全被忽视,因为我发现的所有tuto都只是提供了一个空的释放函数(参见代码中的NoOpDeallocator)。

此行为是由内部库机制中的某些内存管理机制触发的,因此您必须随时准备好发生这种情况。这意味着不能按以下顺序执行:

  1. 分配缓冲区
  2. 呼叫TF_NewTensor
  3. 用一些数据填充缓冲区,

因为TensorFlow可能不会在你填充它的时候使用你的缓冲区。因此你必须在之前填充调用TF_NewTensor。然后,如果你必须修改它,你必须调用TF_TensorData,以确保手头有好的地址。

第二个选项包括要求TensorFlow通过使用TF_AllocateTensor来进行数据分配。在我的情况下,这是一个更好的选择,因为数据缓冲区无论如何都可以移动。

相关内容

  • 没有找到相关文章

最新更新