Skip to content

什么是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/NPU

2. 什么叫 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     255

G 通道:

0     255
0     255

B 通道:

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 的输入就顺了。

最近更新