可视化TFLite图并获取特定节点的中间值?

6

我想知道在TFLite中如何获取某个节点的输入和输出列表。我知道可以获取输入/输出的详情,但这并不能让我重构Interpreter内部发生的计算过程。因此,我的做法是:

interpreter = tf.lite.Interpreter(model_path=model_path)
interpreter.allocate_tensors()

input_details = interpreter.get_input_details()
output_details = interpreter.get_output_details()
interpreter.get_tensor_details()

最后三个命令基本上给了我一些字典,但似乎没有必要的信息。

所以我想知道每个节点输出去哪里的方法?毕竟Interpreter应该知道这个。我们能知道吗?谢谢。

2个回答

21
注意:本答案是针对TensorFlow 1.x编写的,在TensorFlow 2.x中,其概念和核心思想仍然相同,但本答案中的命令可能已经过时。
TF-Lite机制使得检查图形并获取内部节点的中间值的整个过程有些棘手。另一个回答建议使用的get_tensor(...)方法不起作用。
如何可视化TF-Lite推理图?
可以使用visualize.py脚本在TensorFlow Lite repository中可视化TensorFlow Lite模型。您只需:
  • Clone the TensorFlow repository

  • Run the visualize.py script with bazel:

      bazel run //tensorflow/lite/tools:visualize \
           model.tflite \
           visualized_model.html
    

我的TF模型中的节点是否在TF-Lite中有相应的节点?

不是!实际上,TF-Lite可以修改您的图形使其更加优化。以下是TF-Lite文档中关于此的一些说明:

许多TensorFlow操作可以通过TensorFlow Lite进行处理,即使它们没有直接等效项。这适用于可以从图形中简单删除的操作(tf.identity),可以替换为张量的操作(tf.placeholder)或融合到更复杂的操作中(tf.nn.bias_add)。即使某些支持的操作有时也可以通过其中一个这些过程之一进行删除。

此外,TF-Lite API目前不允许获取节点对应关系;很难解释TF-Lite的内部格式。因此,您无法获取任何想要的节点的中间输出,即使没有下面的另一个问题...

我能获取一些TF-Lite节点的中间值吗?

不行! 在这里,我将解释为什么 get_tensor(...) 在TF-Lite中无法工作。假设在内部表示中,图包含3个张量,以及一些中间的密集操作(节点)(您可以认为tensor1是模型输入,tensor3是输出)。在推理此特定图时,TF-Lite 仅需要 2个缓冲区,让我们来展示一下。

首先,使用tensor1通过应用dense操作来计算tensor2。这只需要2个缓冲区来存储值:

           dense              dense
[tensor1] -------> [tensor2] -------> [tensor3]
 ^^^^^^^            ^^^^^^^
 bufferA            bufferB

其次,使用存储在bufferB中的tensor2的值来计算tensor3...但是等等!我们不再需要bufferA,所以让我们用它来存储tensor3的值:

           dense              dense
[tensor1] -------> [tensor2] -------> [tensor3]
                    ^^^^^^^            ^^^^^^^
                    bufferB            bufferA

现在是棘手的部分。 tensor1 的“输出值”仍将指向 bufferA,它现在保存着 tensor3 的值。因此,如果您为第一个张量调用 get_tensor(...),您将得到不正确的值。甚至该方法的文档也声明:

此函数不能用于读取中间结果。

如何解决这个问题?

  • Easy but limited way. You can specify the names of the nodes, output tensors of which you want to get the values of during conversion:

      tflite_convert \
          -- # other options of your model
          --output_arrays="output_node,intermediate/node/n1,intermediate/node/n2"
    
  • Hard but flexible way. You can compile TF-Lite with Bazel (using this instruction). Then you can actually inject some logging code to Interpreter::Invoke() in the file tensorflow/lite/interpreter.cc. An ugly hack, but it works.


1
这是一个很好的解释!非常感谢。我相信许多其他人也会发现它有用。我接受它。 - drsealks
谢谢您的解释。我已经能够运行了,但是您知道如何实际查看可视化模型吗? - Rouzbeh
在等待三个多小时来构建整个tf之后,我发现了这个链接:https://github.com/tensorflow/tensorflow/tree/master/tensorflow/lite/examples/minimal。如果你只关心使用TFLite运行推理,你可以构建这个最小化的示例(大约需要20分钟)。 - akevan
@akevan,这是个好发现!距离我上次使用TFLite已经有几年了,但如果我没记错的话,你可以cd进入正确的文件夹并且只bazel build需要的项目。你不必构建整个TF,只需构建TFLite(甚至TFLite的部分)。每次构建只需要2-3分钟,所以我可以快速迭代。 - Chan Kha Vu

1
正如@FalconUA所指出的那样,我们无法直接从TFlite模型中获取中间输入和输出。但是,通过修改模型缓冲区,我们可以获取图层的输入和输出。这个repo展示了如何实现。我们需要修改flat buffer schema才能使其工作。修改后的TFlite schema(存储在repo的tflite文件夹中)也在repo中提供。
为了回答问题的完整性,以下是相关代码:
def buffer_change_output_tensor_to(model_buffer, new_tensor_i):
    # from https://github.com/raymond-li/tflite_tensor_outputter
    # Set subgraph 0's output(s) to new_tensor_i
    # Reads model_buffer as a proper flatbuffer file and gets the offset programatically
    # It might be much more efficient if Model.subgraphs[0].outputs[] was set to a list of all the tensor indices.
    fb_model_root = tflite_model.Model.GetRootAsModel(model_buffer, 0)
    output_tensor_index_offset = fb_model_root.Subgraphs(0).OutputsOffset(0) # Custom added function to return the file offset to this vector
    # print("buffer_change_output_tensor_to. output_tensor_index_offset: ")
    # print(output_tensor_index_offset)
    # output_tensor_index_offset = 0x5ae07e0 # address offset specific to inception_v3.tflite
    # output_tensor_index_offset = 0x16C5A5c # address offset specific to inception_v3_quant.tflite
    # Flatbuffer scalars are stored in little-endian.
    new_tensor_i_bytes = bytes([
        new_tensor_i & 0x000000FF, \
        (new_tensor_i & 0x0000FF00) >> 8, \
        (new_tensor_i & 0x00FF0000) >> 16, \
        (new_tensor_i & 0xFF000000) >> 24 \
    ])
    # Replace the 4 bytes corresponding to the first output tensor index
    return model_buffer[:output_tensor_index_offset] + new_tensor_i_bytes + model_buffer[output_tensor_index_offset + 4:]

def get_tensor(path_tflite, tensor_id):
    with open(path_tflite, 'rb') as fp:
        model_buffer = fp.read()
    
    model_buffer = buffer_change_output_tensor_to(model_buffer, int(tensor_id))
    interpreter = tf.lite.Interpreter(model_content=model_buffer)
    interpreter.allocate_tensors()
    tensor_details = interpreter._get_tensor_details(tensor_id)
    tensor_name = tensor_details['name']
    
    input_details = interpreter.get_input_details()
    interpreter.set_tensor(input_details[0]['index'], input_tensor)
    interpreter.invoke()
    
    tensor = interpreter.get_tensor(tensor_id)
    return tensor

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接