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

Posted

技术标签:

【中文标题】可视化 TFLite 图并获取特定节点的中间值?【英文标题】:Visualize TFLite graph and get intermediate values of a particular node? 【发布时间】:2019-11-15 00:32:24 【问题描述】:

我想知道是否有办法知道 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()

最后 3 个命令基本上给了我似乎没有必要信息的字典。

所以我想知道是否有办法知道每个节点输出的去向? Interpreter 肯定知道这一点。我们可以吗?谢谢。

【问题讨论】:

【参考方案1】:

正如@FalconUA 所指出的,我们不能直接从 TFlite 模型中获取中间输入和输出。但是,我们可以通过修改模型缓冲区来获得层的输入和输出。 This repo 展示了它是如何完成的。我们需要修改平面缓冲区模式才能使其工作。修改后的 TFlite 架构(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

【讨论】:

【参考方案2】:

注意:此答案是为 Tensorflow 1.x 编写的,虽然概念和核心思想在 TensorFlow 2.x 中保持不变,但此答案中的命令可能已被弃用。

TF-Lite 的机制使得检查图和获取内部节点中间值的整个过程有点棘手。另一个答案建议的get_tensor(...) 方法不起作用。

如何可视化 TF-Lite 推理图?

TensorFlow Lite 模型可以使用TensorFlow Lite repository 中的visualize.py 脚本进行可视化。你只需要:

Clone the TensorFlow repository

使用 bazel 运行 visualize.py 脚本:

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

我的 TF 模型中的节点是否在 TF-Lite 中具有等效的节点?

不! 事实上,TF-Lite 可以修改您的图表,使其变得更加优化。以下是来自TF-Lite documentation 的一些话:

TensorFlow Lite 可以处理许多 TensorFlow 操作,即使它们没有直接等效项。对于可以从图中简单地删除 (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 个缓冲区,让我们来看看如何。

首先,通过应用dense 运算,使用tensor1 计算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(...),您将得到不正确的值。 documentation of this method 甚至声明:

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

如何解决这个问题?

简单但有限的方法。您可以指定节点的名称,在转换过程中要获取其值的输出张量:

  tflite_convert \
      -- # other options of your model
      --output_arrays="output_node,intermediate/node/n1,intermediate/node/n2"

困难但灵活的方式。您可以使用 Bazel 编译 TF-Lite(使用 this instruction)。然后,您实际上可以在文件tensorflow/lite/interpreter.cc 中向Interpreter::Invoke() 注入一些日志记录代码。一个丑陋的黑客,但它有效。

【讨论】:

这是一个很好的解释!太感谢了。我相信许多其他人会发现它很有用。我接受它 感谢您的解释。我能够运行,但您知道我如何才能真正看到可视化模型吗?

以上是关于可视化 TFLite 图并获取特定节点的中间值?的主要内容,如果未能解决你的问题,请参考以下文章

Boost库,如何获取相邻节点?

ggplot2可视化水平箱图并使用fct_reorder排序数据使用na.rm处理缺失值(reorder boxplot with fct_reorder)按照箱图的中位数从小到大排序水平箱图

如何使用 jQuery 选择具有特定文本的中间节点:包含选择器?

ggplot2可视化水平箱图并使用fct_reorder排序数据使用na.rm处理缺失值(reorder boxplot with fct_reorder)按照箱图的中位数从大到小排序水平箱图

ggplot2可视化水平箱图并使用fct_reorder排序数据使用na.rm处理缺失值(reorder boxplot with fct_reorder)按照箱图的中位数从大到小排序水平箱图

运行TensorFlow