5. 张量(Tensor)与数据类型:张量的维度与形状、数据类型(FLOAT32, INT32, QUANT8_ASYMM等)、创建与设置张量(ANeuralNetworksModel_addOperand)

好,我们继续往下走。上一章我们把模型的基础骨架搭起来了,但模型要真正跑起来,还得往里面塞数据。在NNAPI的世界里,所有数据都是以张量(Tensor)的形式存在的。

说白了,张量就是个多维数组。你想想看,一张图片是三维的(高、宽、颜色通道),一段音频是一维的(采样点序列),一个视频是四维的(帧、高、宽、通道)。NNAPI要处理这些数据,就得先告诉它:嘿,我要塞进来的数据长什么样,是什么类型的。

我个人习惯把张量理解成一个“数据容器”,它有固定的形状和数据类型。你定义错了,模型推理就会出幺蛾子。我曾经在一个项目里,因为把输入张量的维度顺序搞反了,结果模型跑出来的结果完全不对,排查了半天才发现是形状定义错了。

5.1 张量的维度与形状

维度(Dimension)和形状(Shape)是张量的核心属性。维度指的是张量有多少个轴(Axis),形状则是一个数组,描述了每个轴上有多少个元素。

举个例子:

  • 标量(0维张量):形状是 [],就是一个单独的数字。
  • 向量(1维张量):形状是 [n],比如 [10] 表示有10个元素的一维数组。
  • 矩阵(2维张量):形状是 [m, n],比如 [224, 224] 表示224行224列的二维数组。
  • 3维张量:形状是 [d, h, w],比如 [3, 224, 224] 表示3个通道,每个通道是224x224的矩阵。很多图像模型就用这种格式。
  • 4维张量:形状是 [n, c, h, w][n, h, w, c],这是批量处理时的常见格式。n是批次大小,c是通道数,h和w是高度和宽度。

这里有个坑,我必须要提醒你:不同框架对维度的排列顺序可能不一样。比如TensorFlow默认是 [batch, height, width, channels](NHWC),而PyTorch默认是 [batch, channels, height, width](NCHW)。NNAPI本身支持多种布局,但你在定义张量时一定要搞清楚模型训练时用的是哪种顺序,否则数据进去就是乱的。

核心要点:张量的形状必须与模型期望的输入输出完全一致。多一个维度、少一个维度,或者顺序不对,都会导致推理失败或结果错误。

5.2 数据类型(FLOAT32, INT32, QUANT8_ASYMM等)

形状说完了,接下来是数据类型。NNAPI支持多种数据类型,每种类型都有它的适用场景。我整理了一张表,方便你对照查看:

类型枚举值 描述 字节数 典型应用场景
ANEURALNETWORKS_FLOAT32 32位浮点数 4 高精度模型,训练和推理精度要求高的场景
ANEURALNETWORKS_INT32 32位有符号整数 4 索引、形状参数、标量常量
ANEURALNETWORKS_UINT32 32位无符号整数 4 某些特定硬件加速器需要的类型
ANEURALNETWORKS_TENSOR_FLOAT32 32位浮点张量 4 * 元素数 大多数浮点模型的标准输入输出
ANEURALNETWORKS_TENSOR_INT32 32位有符号整数张量 4 * 元素数 量化模型的偏置(bias)通常用这个
ANEURALNETWORKS_TENSOR_QUANT8_ASYMM 8位非对称量化张量 1 * 元素数 移动端量化模型,最常用的量化类型
ANEURALNETWORKS_TENSOR_QUANT8_SYMM 8位对称量化张量 1 * 元素数 某些硬件加速器对对称量化支持更好
ANEURALNETWORKS_TENSOR_QUANT16_SYMM 16位对称量化张量 2 * 元素数 需要更高精度的量化场景
ANEURALNETWORKS_TENSOR_FLOAT16 16位浮点张量 2 * 元素数 支持FP16的硬件加速器,兼顾精度和速度
ANEURALNETWORKS_BOOL 布尔类型 1 控制流、条件判断

嗯,这里要注意一下QUANT8_ASYMM。这是移动端量化模型里最常见的类型。为什么叫“非对称”?因为它允许零点(zero point)不在0的位置。比如一个8位量化张量,它的实际值计算公式是:

real_value = (quantized_value - zero_point) * scale

其中scale是缩放因子,zero_point是零点偏移。非对称量化可以更好地覆盖数据的实际分布范围,尤其是当数据不是关于0对称的时候(比如ReLU的输出全是非负数)。

我的经验:在移动端做模型部署时,尽量用QUANT8_ASYMM。它比FLOAT32省3/4的内存,推理速度也快很多。但要注意,量化模型对数值精度敏感,如果模型原本的数值范围很宽,量化后精度可能会下降。我建议你先用少量测试数据跑一遍,对比一下量化前后的输出差异。

5.3 创建与设置张量(ANeuralNetworksModel_addOperand)

好,理论知识差不多了。接下来我们看看怎么在代码里创建和设置张量。核心API就是ANeuralNetworksModel_addOperand

这个函数的作用是向模型中添加一个操作数(Operand),也就是一个张量。你需要告诉它这个张量的类型和形状。

函数签名长这样:

int ANeuralNetworksModel_addOperand(ANeuralNetworksModel* model,
                                     const ANeuralNetworksOperandType* type);

关键在第二个参数——ANeuralNetworksOperandType。这是一个结构体,你需要先填充它,再传给上面的函数。

我们来看一个完整的例子:创建一个形状为 [1, 224, 224, 3] 的FLOAT32张量,作为模型的输入。

// 1. 定义张量类型
ANeuralNetworksOperandType inputType;
// 设置数据类型为32位浮点张量
inputType.type = ANEURALNETWORKS_TENSOR_FLOAT32;
// 设置维度数量为4
inputType.dimensionCount = 4;
// 设置每个维度的大小:batch=1, height=224, width=224, channels=3
uint32_t inputDimensions[4] = {1, 224, 224, 3};
inputType.dimensions = inputDimensions;
// 量化参数:非量化类型填0和0.0f即可
inputType.scale = 0.0f;
inputType.zeroPoint = 0;

// 2. 添加到模型中
int result = ANeuralNetworksModel_addOperand(model, &inputType);
if (result != ANEURALNETWORKS_NO_ERROR) {
    // 处理错误
    // 我曾经遇到过因为dimensions指针传了null而导致崩溃的情况,记得检查!
}

再来看一个量化张量的例子:创建一个形状为 [1, 224, 224, 3] 的QUANT8_ASYMM张量。

ANeuralNetworksOperandType quantInputType;
quantInputType.type = ANEURALNETWORKS_TENSOR_QUANT8_ASYMM;
quantInputType.dimensionCount = 4;
uint32_t quantDims[4] = {1, 224, 224, 3};
quantInputType.dimensions = quantDims;
// 量化参数:scale和zeroPoint必须与模型训练时一致
quantInputType.scale = 0.0078125f;   // 1/128
quantInputType.zeroPoint = 128;      // 零点偏移

int result = ANeuralNetworksModel_addOperand(model, &quantInputType);

重要提醒:量化张量的scalezeroPoint必须与模型训练时使用的量化参数完全一致。如果填错了,推理结果就是垃圾数据。我曾经接手过一个项目,前任工程师把scale写成了0.01,结果模型输出全是乱码,排查了整整两天才发现是这里的问题。

另外,对于标量常量(比如卷积的padding大小、步长等),你可以用ANEURALNETWORKS_INT32类型来定义。它的维度数量是0(标量),不需要传dimensions指针。

ANeuralNetworksOperandType scalarType;
scalarType.type = ANEURALNETWORKS_INT32;
scalarType.dimensionCount = 0;  // 标量
scalarType.dimensions = nullptr;
scalarType.scale = 0.0f;
scalarType.zeroPoint = 0;

ANeuralNetworksModel_addOperand(model, &scalarType);

最后,我用一张图来总结一下张量创建的核心流程:

张量创建核心流程 1. 定义OperandType 填充type/dimensions/scale等 2. 设置数据类型 FLOAT32 / QUANT8_ASYMM等 3. 设置形状 dimensionCount + dimensions 4. 调用ANeuralNetworksModel_addOperand 传入model和OperandType指针 5. 检查返回值 ANEURALNETWORKS_NO_ERROR? ✅ 张量创建成功,可用于后续操作 注意:量化张量必须正确设置scale和zeroPoint,否则推理结果会出错

嗯,到这里张量的创建和设置就讲完了。记住,张量是NNAPI模型的数据基石,形状和类型必须精确匹配。下一节我们会把这些张量连接起来,组成真正的计算图。


公众号:蓝海资料掘金营,微信deep3321