7.8. 张量 I/O

引擎在输入侧接受单个张量,在输出侧产生一个或多个张量。这些张量是 ndarray 对象,具有 numpy 章节所介绍的形状、dtype 和描述符词汇。它们的形状和 dtype 来自模型文件,并通过 input_shape / output_shapeinput_dtype / output_dtype 报告。

7.8.1. 量化

摄像头运行的大多数网络都对量化的整数张量 —— int8uint8 —— 进行运算,以便适应摄像头的 RAM 和计算预算。量化张量携带整数值,这些整数值通过逐张量的缩放和零点来表示实数值:

\[\text{real} = \text{scale} \times (q - \text{zero_point})\]
\[q = \mathrm{round}(\text{real} / \text{scale}) + \text{zero_point}\]

缩放和零点来自模型训练时的校准,并存储在模型文件中。它们被暴露为 input_scaleinput_zero_pointoutput_scaleoutput_zero_point —— 每一个都是一个列表,每个输入或输出张量对应一项。

ml.utils.quantize()ml.utils.dequantize() 针对指定的输出索引应用这些公式:

import ml.utils

real_tensor = ml.utils.dequantize(model, q_tensor, index=0)
q_tensor    = ml.utils.quantize(model, real_tensor, index=0)

当给定索引处的输出 dtype 已经是浮点时,这两个函数都会原样返回该值,因此无论模型的量化状态如何,调用都是安全的。

7.8.2. 脚本在输出侧看到的内容

predict() 返回什么取决于是否注册了后处理器。

在没有后处理器的情况下,引擎的原始整数输出会被自动反量化为浮点,并以浮点 ndarray 对象列表的形式返回。脚本收到可直接读取的实数值。这对于分类网络是正确的路径,分类网络的单个输出张量本身就是应用程序据以迭代的逐类别置信度分数列表 —— 无需解码步骤。它也是快速让一个未知模型跑起来或从 REPL 进行临时检视的简便路径。

在注册了后处理器的情况下(通过构造函数上的 postprocess= 或 predict 调用上的 callback=),原始的量化张量会被直接交给后处理器的可调用对象。后处理器接收原始的量化张量,并负责它所需要的任何反量化。

这种区分是一个性能选择。自动反量化会为每个输出分配一个新的浮点张量并遍历每个元素。一个只需从每个张量取几个值的后处理器 —— 对置信度分数应用阈值,然后只为通过的那些解码边界框 —— 可以省去反量化其余部分的开销。ml.postprocessing 下发布的边界框解码器都走这条路线,而 ml.utils.threshold() 正是为这种情形而构建的:它接受一个量化的分数张量,返回其反量化值通过实数阈值的那些索引,而不反量化整个张量。