什么是Tensor
Tensor 这个词先不要想复杂。
你现在可以直接把它理解成:
Tensor = 一块有“形状”的多维数据。
比如你写 C/C++ 时:
float a = 3.14;这是一个数。
float a[3] = {1, 2, 3};这是一维数组。
float a[2][3] = {
{1, 2, 3},
{4, 5, 6}
};这是二维数组。
深度学习里把这些统一叫做 Tensor(张量)。
1. 从数组理解 Tensor
可以这样对应:
| 数据 | C/C++感觉 | Tensor维数 |
|---|---|---|
3.14 | 一个变量 | 0维 Tensor |
[1,2,3] | 一维数组 | 1维 Tensor |
[[1,2],[3,4]] | 二维数组 | 2维 Tensor |
float a[3][640][640] | 三维数组 | 3维 Tensor |
float a[1][3][640][640] | 四维数组 | 4维 Tensor |
所以:
Tensor并不是什么神秘的 AI 专属玩意。
你目前完全可以认为:
Tensor ≈ 多维数组只是 Tensor 通常还会带一些额外属性:
数据
shape
dtype
所在设备 CPU/GPU/NPU2. 什么叫 shape?
例如:
Tensor shape = [3, 640, 640]意思就是:
float image[3][640][640];它有三个维度:
3
640
640这个 [3,640,640] 就叫:
Tensor 的 shape,形状。
3. YOLO 为什么经常是 [1,3,640,640]?
比如:
input tensor:
shape = [1, 3, 640, 640]你就可以把它想成:
float input[1][3][640][640];每一维分别有意义:
[ Batch, Channel, Height, Width ]
[ 1, 3, 640, 640 ]也就是:
1 一次输入1张图片
3 RGB三个颜色通道
640 图片高度
640 图片宽度所以:
[1,3,640,640]本质就是一大堆数字。
总共有:
1 × 3 × 640 × 640
= 1,228,800 个数字模型实际上根本“不认识图片”。
模型看到的只是:
0.23
0.71
0.45
0.03
0.92
...这一百多万个数字。
4. 图片为什么可以变成 Tensor?
比如有一张极小的 RGB 图片:
2 × 2图片:
┌───────┬───────┐
│ 红色 │ 绿色 │
├───────┼───────┤
│ 蓝色 │ 白色 │
└───────┴───────┘电脑实际上不会存:
"红色"
"绿色"而是存 RGB 数字。
例如:
红色 = [255, 0, 0]
绿色 = [0, 255, 0]
蓝色 = [0, 0, 255]
白色 = [255, 255, 255]于是整张图片其实就是很多数字。
因此可以组织成 Tensor:
shape = [3, 2, 2]比如 R 通道:
255 0
0 255G 通道:
0 255
0 255B 通道:
0 0
255 255三个二维数组叠起来:
R
┌─────┐
│ │
└─────┘
G
┌─────┐
│ │
└─────┘
B
┌─────┐
│ │
└─────┘就成为:
[3,H,W]的三维 Tensor。
5. 为什么还要多一个 1?
图片本来:
[3,640,640]YOLO 为什么要:
[1,3,640,640]因为 YOLO 支持:
一次处理多张图片。
这个最前面的数字就是:
Batch例如:
[1,3,640,640]一次一张。
[4,3,640,640]一次四张。
可以想象成:
Batch 0
RGB
640×640
Batch 1
RGB
640×640
Batch 2
RGB
640×640
Batch 3
RGB
640×640所以四张图片摞在一起,就变成:
[4,3,640,640]6. Tensor 不只是用来表示输入图片
这个特别重要。
YOLO 的:
输入是 Tensor。
YOLO 的:
输出还是 Tensor。
中间每一层:
Conv
C2f
SPPF
Detect传递的也都是 Tensor。
你可以把整个神经网络想象成:
Tensor
↓
函数
↓
Tensor
↓
函数
↓
Tensor
↓
函数
↓
Tensor就像普通程序:
int a;
a = func1(a);
a = func2(a);
a = func3(a);只不过神经网络处理的不是一个 int,
而是巨大的多维数组。
7. YOLO 输出 Tensor 又是什么意思?
比如我之前说:
output shape = [1,25200,85]现在你应该稍微能理解了。
你可以把它粗暴想成:
float output[1][25200][85];忽略最前面的 batch:
float output[25200][85];也就是一张超级大的二维表:
85个数字
┌────────────────────────────┐
候选框0 │ x y w h obj cls0 cls1 ... │
候选框1 │ x y w h obj cls0 cls1 ... │
候选框2 │ x y w h obj cls0 cls1 ... │
候选框3 │ x y w h obj cls0 cls1 ... │
│ ... │
│ ... │
候选框 │ │
25200 │ │
└────────────────────────────┘所以:
[1,25200,85]其实一点都不抽象。
本质就是:
YOLO 给了你一张 25200 行、85 列的大表。
然后你的后处理代码就在读这张表。
例如:
for (int i = 0; i < 25200; i++)
{
float x = output[i][0];
float y = output[i][1];
float w = output[i][2];
float h = output[i][3];
float obj = output[i][4];
// 后面继续找类别
}这一下应该就比较接地气了。
8. Tensor 最重要的三个属性
你现阶段看到 Tensor,先只检查三个东西。
① shape
例如:
[1,3,640,640]回答:
这些数字是怎么排列的?
② dtype
回答:
每一个数字是什么类型?
例如:
float32
float16
uint8
int8类似 C++:
float
uint8_t
int8_t比如:
Tensor:
shape = [1,3,640,640]
dtype = float32相当于:
float input[1][3][640][640];如果:
dtype = uint8大致就是:
uint8_t input[1][3][640][640];③ layout
也就是各个维度代表什么。
例如:
[1,3,640,640]可能是:
NCHW也就是:
N = Batch
C = Channel
H = Height
W = Width而另一个模型可能是:
[1,640,640,3]它叫:
NHWC即:
N = Batch
H = Height
W = Width
C = Channel虽然都是同一张图片,
但是数据在内存里的排列是不一样的。
9. 这和 OpenCV 的 Mat 有什么关系?
这也是你以后非常重要的一个连接点。
OpenCV:
cv::Mat img;里面也是一堆像素数据。
例如:
1920×1080
3通道
uint8
BGR可以理解成:
OpenCV Mat
↓ 预处理
Tensor
↓
YOLO所以你以后的代码基本就是:
RTSP
↓
MPP解码
↓
NV12
↓
RGA
↓
RGB图像
↓
Tensor
↓
RKNN模型
↓
输出Tensor
↓
后处理Tensor 就是其中:
模型和你的应用程序交换数据的载体。
10. 你现在可以这样记
先不要背数学定义。
你现在的应用层定义就是:
Tensor = 带 shape 和数据类型的多维数组。
例如:
Tensor A
shape = [1,3,640,640]
dtype = float32就理解为:
float A[1][3][640][640];而:
Tensor B
shape = [1,25200,85]
dtype = float32就理解为:
float B[1][25200][85];至于为什么数学里叫“张量”、一阶张量二阶张量、线性代数怎么定义——
你现在完全可以不管。
你现在应该先形成这一条认知链
图片
其实就是
↓
很多像素
像素
其实就是
↓
很多数字
很多数字
按照一定维度排列
↓
Tensor
Tensor
送给 YOLO
↓
YOLO 输出另一个 Tensor
输出 Tensor
↓
你的 C++ 代码读取这些数字
读取数字
↓
得到 bbox / class / confidence
最后
↓
画框所以我们刚才说“看 YOLO 输入输出”,实际上就是在干一件事:
搞清楚输入 Tensor 每个维度代表什么,输出 Tensor 每个维度和每个数字又代表什么。
下一步最适合接着学的其实不是 NMS,而是先彻底搞懂:
[1, 3, 640, 640] 里面的数据在内存中到底是怎么摆的,以及 NCHW / NHWC / HWC / CHW 到底是什么。
这个理解以后,你看 ONNX 和 RKNN 的输入就顺了。
