使用TF_SessionRun在C(而不是C ++)中运行TensorFlow图时出现分段错误

时间:2017-06-01 10:55:10

标签: c tensorflow

我尝试使用C API加载和运行TensorFlow图形(我需要在TensorFlow项目之外构建,最好不使用Bazel,因此不能使用C ++)。

该图是3层LSTM-RNN,它将3个元素的特征向量分类为9个类中的一个。该图是用Python构建和训练的,我已经在Python和C ++中对它进行了测试。

到目前为止,我已经加载了图表,但是一旦图表加载,我就无法运行会话。我已经做了很多挖掘,但我只找到了一个使用C API的示例(here),并且不包括运行图表。

我设法将以下内容组合在一起,但它产生了一个分段错误(如果我注释掉TF_SessionRun()调用,我可以成功运行代码,但是当包含TF_SessionRun()时我得到了seg错误)。这是代码:

#include "tensorflow/c/c_api.h"
#include <stdio.h>
#include <stdlib.h>
#include <memory.h>
#include <string.h>
#include <assert.h>
#include <vector>
#include <algorithm>
#include <iterator>


TF_Buffer* read_file(const char* file);

void free_buffer(void* data, size_t length) {
        free(data);
}

static void Deallocator(void* data, size_t length, void* arg) {
        free(data);
}

int main() {
  // Use read_file to get graph_def as TF_Buffer*
  TF_Buffer* graph_def = read_file("tensorflow_model/constant_graph_weights.pb");
  TF_Graph* graph = TF_NewGraph();

  // Import graph_def into graph
  TF_Status* status = TF_NewStatus();
  TF_ImportGraphDefOptions* graph_opts = TF_NewImportGraphDefOptions();
  TF_GraphImportGraphDef(graph, graph_def, graph_opts, status);
  if (TF_GetCode(status) != TF_OK) {
          fprintf(stderr, "ERROR: Unable to import graph %s", TF_Message(status));
          return 1;
  }
  else {
          fprintf(stdout, "Successfully imported graph\n");
  }

  // Configure input & provide dummy values
  const int num_bytes = 3 * sizeof(float);
  const int num_bytes_out = 9 * sizeof(int);
  int64_t dims[] = {3};
  int64_t out_dims[] = {9};

  float values[3] = {-1.04585315e+03,   1.25702492e+02,   1.11165466e+02};


  // Setup graph inputs
  std::vector<TF_Tensor*> input_values;
  TF_Operation* input_op = TF_GraphOperationByName(graph, "lstm_1_input");
  TF_Output inputs = {input_op, 0};
  TF_Tensor* input = TF_NewTensor(TF_FLOAT, dims, 1, &values, num_bytes, &Deallocator, 0);
  input_values.push_back(input);

  // Setup graph outputs
  TF_Operation* output_op = TF_GraphOperationByName(graph, "output_node0");
  TF_Output outputs = {output_op, 0};
  std::vector<TF_Tensor*> output_values(9, nullptr);

  // Run graph
  fprintf(stdout, "Running session...\n");
  TF_SessionOptions* sess_opts = TF_NewSessionOptions();
  TF_Session* session = TF_NewSession(graph, sess_opts, status);
  assert(TF_GetCode(status) == TF_OK);
  TF_SessionRun(session, nullptr,
                &inputs, &input_values[0], 3,
                &outputs, &output_values[0], 9,
                nullptr, 0, nullptr, status);

  fprintf(stdout, "Successfully run session\n");

  TF_CloseSession(session, status);
  TF_DeleteSession(session, status);
  TF_DeleteSessionOptions(sess_opts);
  TF_DeleteImportGraphDefOptions(graph_opts);
  TF_DeleteGraph(graph);
  TF_DeleteStatus(status);
  return 0;
}

TF_Buffer* read_file(const char* file) {
  FILE *f = fopen(file, "rb");
  fseek(f, 0, SEEK_END);
  long fsize = ftell(f);
  fseek(f, 0, SEEK_SET);

  void* data = malloc(fsize);
  fread(data, fsize, 1, f);
  fclose(f);

  TF_Buffer* buf = TF_NewBuffer();
  buf->data = data;
  buf->length = fsize;
  buf->data_deallocator = free_buffer;
  return buf;
}

我不确定TF_SessionRun到底出了什么问题,所以非常感谢任何帮助!

更新:我已经在gdb中的TF_SessionRun调用中设置了一个断点,当我逐步完成它时,我首先得到: Thread 1 received signal SIGSEGV, Segmentation fault. 0x0000000100097650 in ?? () 其次是: "Cannot find bounds of current function" 我最初认为这是因为TensorFlow库没有使用调试符号编译,但是后来用调试符号编译它并在gdb中获得相同的输出。

自从我的原帖后,我找到了TensorFlow C示例here(但作者指出它未经测试)。因此,我根据他们的例子重新编写了我的代码,并使用TensorFlow的c_api.h头文件对所有内容进行了双重检查。我现在也在用C ++文件调用C API(就像上面的例子中所做的那样)。尽管如此,我仍然从gdb获得相同的输出。

更新2:为了确保我的图表正确加载,我已经使用了C API中的一些TF_Operation函数(TF_GraphNextOperation()和TF_OperationName())来检查图形操作,并将这些与在Python中加载图形时的操作进行了比较。输出看起来正确,我可以从操作中检索属性(例如使用TF_OperationNumOutputs()),因此看起来图形肯定是正确加载的。

有使用TensorFlow C API经验的人的建议将不胜感激。

2 个答案:

答案 0 :(得分:13)

经过多次尝试C api中的函数并密切关注占位符的维度,我设法解决了这个问题。我的原始seg错误是由错误的操作名称字符串传递给TF_GraphOperationByName()引起的,但是seg错误只发生在TF_SeesionRun(),因为这是它尝试访问该操作的第一个地方。以下是我解决问题的方法,对于遇到同样问题的人:

首先,检查您的操作以确保它们被正确分配。在我的情况下,由于在Python中获取操作名称时出错,我提供给input_op的操作名称不正确。我从Python得到的错误操作名称是'lstm_4_input'。通过使用C API在加载的图上运行以下命令,我发现这是不正确的:

  n_ops = 700
  for (int i=0; i<n_ops; i++)
  {
    size_t pos = i;
    std::cout << "Input: " << TF_OperationName(TF_GraphNextOperation(graph, &pos)) << "\n";
  }

其中n_ops是图表中的操作数。这将打印出您的操作名称;在这种情况下,我可以看到没有'lstm_4_input',但是有一个'lstm_1_input',所以我相应地更改了值。此外,它验证了我的输出操作'output_node0'是正确的。

一旦我解决了seg故障,还有一些其他问题变得清晰了,所以这里有完整的工作代码,详细的评论,面对类似问题的任何人:

#include "tensorflow/c/c_api.h"

#include <stdio.h>
#include <stdlib.h>
#include <memory.h>
#include <string.h>
#include <assert.h>
#include <vector>
#include <algorithm>
#include <iterator>
#include <iostream>


TF_Buffer* read_file(const char* file);

void free_buffer(void* data, size_t length) {
        free(data);
}

static void Deallocator(void* data, size_t length, void* arg) {
        free(data);
        // *reinterpret_cast<bool*>(arg) = true;
}

int main() {
  // Use read_file to get graph_def as TF_Buffer*
  TF_Buffer* graph_def = read_file("tensorflow_model/constant_graph_weights.pb");
  TF_Graph* graph = TF_NewGraph();

  // Import graph_def into graph
  TF_Status* status = TF_NewStatus();
  TF_ImportGraphDefOptions* graph_opts = TF_NewImportGraphDefOptions();
  TF_GraphImportGraphDef(graph, graph_def, graph_opts, status);
  if (TF_GetCode(status) != TF_OK) {
          fprintf(stderr, "ERROR: Unable to import graph %s", TF_Message(status));
          return 1;
  }
  else {
          fprintf(stdout, "Successfully imported graph\n");
  }

  // Create variables to store the size of the input and output variables
  const int num_bytes_in = 3 * sizeof(float);
  const int num_bytes_out = 9 * sizeof(float);

  // Set input dimensions - this should match the dimensionality of the input in
  // the loaded graph, in this case it's three dimensional.
  int64_t in_dims[] = {1, 1, 3};
  int64_t out_dims[] = {1, 9};

  // ######################
  // Set up graph inputs
  // ######################

  // Create a variable containing your values, in this case the input is a
  // 3-dimensional float
  float values[3] = {-1.04585315e+03,   1.25702492e+02,   1.11165466e+02};

  // Create vectors to store graph input operations and input tensors
  std::vector<TF_Output> inputs;
  std::vector<TF_Tensor*> input_values;

  // Pass the graph and a string name of your input operation
  // (make sure the operation name is correct)
  TF_Operation* input_op = TF_GraphOperationByName(graph, "lstm_1_input");
  TF_Output input_opout = {input_op, 0};
  inputs.push_back(input_opout);

  // Create the input tensor using the dimension (in_dims) and size (num_bytes_in)
  // variables created earlier
  TF_Tensor* input = TF_NewTensor(TF_FLOAT, in_dims, 3, values, num_bytes_in, &Deallocator, 0);
  input_values.push_back(input);

  // 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";

  // ######################
  // Set up graph outputs (similar to setting up graph inputs)
  // ######################

  // Create vector to store graph output operations
  std::vector<TF_Output> outputs;
  TF_Operation* output_op = TF_GraphOperationByName(graph, "output_node0");
  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, 2, num_bytes_out);
  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";

  // ######################
  // Run graph
  // ######################
  fprintf(stdout, "Running session...\n");
  TF_SessionOptions* sess_opts = TF_NewSessionOptions();
  TF_Session* session = TF_NewSession(graph, sess_opts, status);
  assert(TF_GetCode(status) == TF_OK);

  // Call TF_SessionRun
  TF_SessionRun(session, nullptr,
                &inputs[0], &input_values[0], inputs.size(),
                &outputs[0], &output_values[0], outputs.size(),
                nullptr, 0, nullptr, status);

  // Assign the values from the output tensor to a variable and iterate over them
  float* out_vals = static_cast<float*>(TF_TensorData(output_values[0]));
  for (int i = 0; i < 9; ++i)
  {
      std::cout << "Output values info: " << *out_vals++ << "\n";
  }

  fprintf(stdout, "Successfully run session\n");

  // Delete variables
  TF_CloseSession(session, status);
  TF_DeleteSession(session, status);
  TF_DeleteSessionOptions(sess_opts);
  TF_DeleteImportGraphDefOptions(graph_opts);
  TF_DeleteGraph(graph);
  TF_DeleteStatus(status);
  return 0;
}

TF_Buffer* read_file(const char* file) {
  FILE *f = fopen(file, "rb");
  fseek(f, 0, SEEK_END);
  long fsize = ftell(f);
  fseek(f, 0, SEEK_SET);  //same as rewind(f);

  void* data = malloc(fsize);
  fread(data, fsize, 1, f);
  fclose(f);

  TF_Buffer* buf = TF_NewBuffer();
  buf->data = data;
  buf->length = fsize;
  buf->data_deallocator = free_buffer;
  return buf;
}

注意:在我之前的尝试中,我使用'3'和'9'作为ninputs的{​​{1}}和noutputs参数,认为这些与输入和输出张量的长度有关(我将3维特征分类为9个类中的一个)。实际上,输入/输出张量的数量很简单,因为张量的维数在实例化时会得到更早的处理。在这里使用.size()成员函数很容易(当使用TF_SessionRun()来保存std::vector时)。

希望这是有道理的,有助于澄清将来发现自己处于类似位置的任何人的过程!

答案 1 :(得分:0)

您可以使用以下语法使用gdb执行代码:

gdb executable_name

像这样你的进程将在gdb中运行,这样你就可以在崩溃后得到回溯。崩溃后你将在gdb中有一个控制台,所以你可以使用命令bt来查看回溯。希望这应该给你足够的信息来调试问题。如果没有,您还可以将您的回溯添加到原始帖子,以便人们可以看到它。 阅读gdb中的断点可能是一个好主意。