想象你搬进一栋会变形的公寓楼。物业说:这栋楼共有 3 层,每层 4 个房间;过了两天,又说:咱们重新隔一下,改成 2 层,每层 6 个房间。房间总数还是 12 间,变的只是“怎么数”。深度学习里的张量,就是这种会变形的公寓楼。张量的“阶”,是楼有几个方向——0 阶是一间屋,1 阶是一条走廊,2 阶是一层楼面,3 阶以上是一整栋楼甚至一片小区。张量的“形状”则是楼书里的户型图,它清楚写着每个方向各有多少间房。这篇文章就用“看楼”的思路,带你拆解标量、向量、矩阵、高阶张量、shape、reshape、转置,以及绕不开的 batch、channel、height、width 和内存布局。读完你会发现,一串 shape 摆在眼前,你就能立刻在脑中画出那栋清晰的楼。
职业价值:张量和 shape 是深度学习框架里最基础也最容易翻车的概念。把阶与形状彻底搞懂,读模型源码、调试维度报错、设计网络结构时都能省下大量时间,是从“会调接口”走向“真正懂框架”的关键一步。
目录
可视化演示
展示0D到4D张量的阶与形状结构,动态演示Reshape形状变换与转置维度交换,交互切换模式理解张量维度操作。
本节配有交互动效(进入视口后自动加载,约 3KB)。
张量阶:从标量到高阶张量 青色为0D标量(单一数值,如loss) 浅青为1D向量(特征序列) 橙色为2D矩阵(权重矩阵) Order = len(shape), 0D:() 1D:(n) 2D:(m,n) 3D:(d,h,w) 4D:(N,C,H,W) 张量阶是形状元组的长度,0D标量到4D批次张量逐级递增,每增加一阶多一层嵌套
一、张量阶层:从单间到高楼
万物有基础单位,数据也一样。一个单独的数,好比一栋公寓楼里最孤单的单间——它没有走廊,没有楼层,自己就是整栋“楼”。在张量世界里,我们叫它 0 阶张量(标量)。继续把很多单间排成一排,就出现了走廊,这是一阶张量(向量)。再把这些走廊一格格铺开成一整层楼面,就是二阶张量(矩阵)。如果再把多层楼叠起来,就自然进入了 3 阶、4 阶直至更高阶。
所谓“阶”(rank),指的就是张量有多少个轴(axis),也就是它的“方向数”。判断阶非常简单:看它的形状(shape)这个元组里装了几个数字。数字个数是几,阶就是几。官方文档管这个叫“张量的维度数”,本质就是那串 shape 的长度。
框架返回的 shape 永远是一个 tuple(元组)。例如 shape 为 (3, 4) 代表 2 阶(矩阵),shape 为 (2, 3, 4) 代表 3 阶(三个轴分别是 2、3、4)。注意:单个数字的向量 shape 是 (n,),而不是 (n)——不要忘记这个逗号,它表示“这是一个一维向量”,而不是一个 1 行 n 列的矩阵。
所以,以后看到一个张量,先在脑海里问三个问题:这栋“楼”有几个方向(几阶)?每个方向上有几间房(轴长)?总共多少间房(元素总数)?三个问题答上来,这栋楼的结构就彻底清楚了。
- $\boldsymbol{s}$(形状元组)像公寓楼的户型图,用圆括号包起来的数字序列,例如 (2, 3, 4) 表示三个方向的房间数。
- $\operatorname{len}(\boldsymbol{s})$(长度函数)数这个元组里有几个数字,这个数字就是阶。
- $d_i$(轴长)第 i 个方向上有多少个格子,也就是第 i 个轴的长度。
二、形状解读:给维度当翻译官
物业发你一张户型图,上面写:共 2 个分段,每段 3 层,每层 4 个房间。这行字就是 shape:(2, 3, 4)。读 shape 就像翻译楼书,把数字按照顺序还原成“第一个方向多少、第二个方向多少”,而不是把它们当成两个毫不相干的随机数。
在代码里,shape 是一个 tuple,每个位置叫一个轴(axis),左起第 0 个、第 1 个……对应下标从 0 开始。比如 shape[0] 是第一个轴长度,shape[1] 是第二个。元素总数通过各轴长连乘得到,框架里通常叫 numel 或 size。
动态维度是另一个常见话题:训练时 batch size 常常留空或用 -1 占位,运行时才知道。例如 (None, 3, 224, 224) 在代码里常写成 (batch, 3, 224, 224)。虽然实际值运行时才定,但语义已经明确:第 0 轴表示一批有多少个样本。
把 shape 读成一句话是最实用的习惯:“第 0 轴 2,第 1 轴 3,第 2 轴 4”。只要把每个轴的含义钉死在脑中,任何报错信息里出现的 (2, 3, 4) 都能立刻具象成那栋熟悉的楼。
- $N$(元素总数)张量里一共有多少个数,也就是整栋楼里所有房间的数量。
- $\prod_{i=1}^{r}$(连乘符号)把从第 1 个到第 r 个轴的长度依次相乘。
- $d_i$(轴长)第 i 个维度的大小,例如 shape=(2,3,4) 时,$d_1=2, d_2=3, d_3=4$。
三、变形容器:reshape 和 view 怎么改形状
物业说:咱们把每层 4 间房改成每层 2 间套房,房间总数不变。这正是一个 reshape 的动作:重新划分房间格局,总数守恒。记住 reshape 的唯一规则——新形状的各轴长度乘积必须等于旧形状的各轴长度乘积。所以 (2, 3, 4) 可以变成 (6, 4)、(12, 2)、(3, 8)……因为 2×3×4 = 24。反之,如果新形状的乘积不等于 24,框架会直接报错。
PyTorch 的 reshape 很智能:能复用现有内存就复用(不拷贝),不能复用就悄悄拷贝。它的兄弟 view 更严格,只能在内存连续时复用内存,否则抛错。这就是为什么常见的报错是:“view size is not compatible with input size“。此外,unsqueeze 给张量增加一个长度为 1 的轴,squeeze 去掉长度为 1 的轴——相当于在楼里加或去掉”只有一间的杂物层”,数据一字未动。
想快速把整栋楼拉成一条长走廊,交给 flatten。它等价于把房间按顺序拆下来排成一排,让全连接层一口气数完所有房间。记住:reshape 改“格局”,view 改“视角”,unsqueeze/squeeze 改“楼层语义”,三者都不是对数据施暴。
- $d_1 \times \cdots \times d_r$(原形状的积)reshape 之前,每个轴长度依次相乘,得到旧元素总数。
- $e_1 \times \cdots \times e_s$(新形状的积)reshape 之后,每个轴长度依次相乘,得到新元素总数。
- 等号(守恒律)两者必须相等,否则 reshape 无意义。
四、转置操作:换个方向看世界
物业说:从明天起,“层”改为“列”来编号。原本第 2 层第 5 间房,现在记成第 5 列第 2 层。房间没变,坐标系转了 90°。这就是转置。矩阵转置是最简单的版本:把 A 的行、列互换,公式说明一切。
高阶张量用 transpose(dim0, dim1) 交换任意两个轴,或用 permute(……) 重排全部轴。注意:转置不是 reshape!reshape 只是“重新分组”,转置是“换坐标系”。比如 shape (2,3,4) 转置第 1、2 轴后得到 (2,4,3),元素位置完全不同。转置后的张量通常是“非连续视图”:数据没有真的搬,只是换了个“看”的方式,所以后续 view 可能失效,需要 contiguous() 重新整理内存。
老话重提:转置是转图纸,contiguous 是请搬家公司。使用高维张量时,别一看 shape 对就放心大胆 view——先问一句:这栋楼的“房产证”上,门牌号真的连续吗?
- $A^{\top}$(转置矩阵)把矩阵 A 的行和列对调后得到的新矩阵,就像把户型图旋转 90°。
- $(A^{\top})_{ij}$(新矩阵的元素)转置后第 i 行第 j 列的元素。
- $A_{ji}$(原矩阵的元素)它来自原矩阵第 j 行第 i 列,位置正好相反。
五、深度维度:深度学习里的常见张量形状
深度学习的训练数据,就像一个小区的几栋楼:一栋楼是一张样本图(batch),楼的颜色属性是通道(channel),高度是楼层数(height),宽度是每层的房间数(width)。于是图像张量在多数框架里长成四个轴。
两种主流约定并存:NCHW 和 NHWC。PyTorch 默认 NCHW,早期 TensorFlow 常推荐 NHWC。两者只是轴顺序不同,数据含义完全一样——就像同一栋楼从不同方向画设计图。
为什么要关心顺序?因为内存布局会跟着顺序变。NCHW 下通道在空间维度之前,有利于某些矩阵运算库;NHWC 下空间维度连续,对部分 CPU 实现更友好。实际项目里两者互换只靠一行 permute(0, 2, 3, 1) 或 transpose。给模型喂数据前,永远先念一遍 shape 再动手。看到 (32, 3, 224, 224),要能立刻说出来:“32 张图、3 个通道、高 224、宽 224”。能把 shape 说出口,代码就不会迷路。
- $N$(批量样本)一批处理多少张图,相当于小区里有多少栋楼。
- $C$(通道数量)图像有几个颜色通道,例如 RGB 的 C=3。
- $H$(图像高度)图像纵向有多少像素,相当于楼有几层。
- $W$(图像宽度)图像横向有多少像素,相当于每层几间房。
六、内存布局:房间里真实住址
最后一个问题与“看楼”无关,而是“怎么走楼”。门牌号先发完整个一层,再上第二层,叫行优先(row-major);先发 1 号房,接着跑遍所有楼层发 2 号房,叫列优先(column-major)。PyTorch 默认行优先存储。对于形状为 $(d_1, d_2, \ldots, d_r)$、索引为 $(i_1, i_2, \ldots, i_r)$ 的元素,行优先的内存偏移为:
框架内部用 stride(步长)表示每个轴前进一个单位需要跨过多少内存单元。连续张量的 stride 由上式算出;转置后,stride 的顺序被打乱,访问性能下降。GPU/CPU 计算时,连续内存能充分利用缓存,同一批操作可能快出数倍。所以发现张量“不连续”时,及时调用 contiguous() 是在给硬件省时间。
门牌顺不顺,直接决定你找房快不快。这就是为什么转置之后要记得把数据“搬顺”——懂内存布局的人,写出来的网络不仅能跑,还会跑得特别快。
- $i_k$(第 k 个索引)当前元素在第 k 个轴上的坐标,比如 (1, 2, 0) 里的 1、2、0。
- $\prod_{j=k+1}^{r} d_j$(后缀连乘)排在当前轴之后的所有轴长度连乘,代表“在这个轴上走一步要跨过多少房间”。
- $\sum_{k=1}^{r}$(求和)把每个轴的贡献累加起来,就是最终内存位置。
AI 应用场景
场景1:图像数据预处理通道换位
阶段:数据预处理
解决的问题:OpenCV 读图天然得到 H×W×C 的数组(高、宽、通道),而 PyTorch 模型期望 N×C×H×W。直接喂会报维度错误,或者形状对不上但语义错乱。传统方式要写一堆 for 循环转数据,低效且易错。
具体案例:图像分类项目里加载 1000 张图片,每张 (224, 224, 3)。先 permute(2, 0, 1) 把通道挪到第 0 轴,变成 (3, 224, 224);再 unsqueeze(0) 补上 batch 轴,最终得到 (1, 3, 224, 224),一次性输给 ResNet。就像把楼从“平面图”翻成“立面图”,一句代码就能完成。
场景2:推理引擎里保持连续内存
阶段:模型部署与推理优化
解决的问题:网络里多次 transpose 后,张量成为非连续视图。某些算子(如 view/flatten)报错或变慢,推理延迟升高。传统做法是到处加 clone(),浪费内存且容易破坏梯度。
具体案例:Transformer 推理中,多头注意力频繁 transpose(0, 1),后续 reshape 视图时崩溃。工程师在关键层前加一行 contiguous(),显式整理内存,推理延迟下降 30%。这就是把打乱的门牌号重新理顺,让硬件少走弯路。
场景3:特征图展平接全连接层
阶段:模型结构设计
解决的问题:CNN 输出 (B, C, H, W) 的特征图,全连接层需要 (B, feature) 的二维输入。新手常把 batch 维弄丢,或者忘记元素总数匹配。
具体案例:图像分类网络最后一层卷积输出 (32, 512, 7, 7),用 reshape(32, -1) 展平成 (32, 25088),再送入分类头。-1 让框架自动计算该维大小,bulletproof。就像把整栋楼的房间拆成一条长走廊,一个动作搞定。
Python 演示
import torch x = torch.arange(24)print("原始张量共有元素:", x.numel()) a = x.reshape(2, 3, 4)print("reshape(2,3,4):", a.shape, "元素总数:", a.numel()) b = a.transpose(1, 2)print("transpose(1,2):", b.shape, "stride:", b.stride()) try: b.view(6, 4)except RuntimeError as e: print("非连续视图 view 直接报错:", e) c = b.contiguous().view(6, 4)print("contiguous 后 view(6,4):", c.shape) img = torch.randn(4, 3, 32, 32)print("图像张量 NCHW:", img.shape) img_nhwc = img.permute(0, 2, 3, 1)print("permute 后 NHWC:", img_nhwc.shape) y = x.view(1, 24).squeeze(0)z = y.unsqueeze(0).unsqueeze(-1)print("增删维度:", y.shape, "->", z.shape)
x = torch.arange(24)造出 0 到 23 共 24 个“房间”,作为实验楼。a = x.reshape(2, 3, 4)24 个房间重排成 2 段、3 行、4 列,总数不变。b = a.transpose(1, 2)交换第 1、2 个轴,门牌顺序被打乱,stride 变化。print("非连续视图 view 直接报错:", e)展示不连续时 view 的经典翻车现场。c = b.contiguous().view(6, 4)先请“搬家公司”理顺内存,再放心 view。img.permute(0, 2, 3, 1)一行代码把 NCHW 改成 NHWC,换轴不换数据。x.view(1, 24).squeeze(0)演示加/去长度为 1 的轴,数据一个字都不变。
常见误区
误区我以为 reshape 就是 view,view 就是 reshape,反正输出 shape 都一样,随便用哪个都行。
正解两者差别在“是否复制数据”。view 是站在同一栋楼里换一个视角,不搬任何房间;reshape 像物业重新隔墙,必要时会悄悄复制一份数据。当张量由转置产生、内存不连续时,view 会直接报错,而 reshape 能自动兜底。
为何常见:两个方法的输出看起来一模一样,平时看不出区别,一到非连续张量上就翻车。
误区所谓转置,不就把 shape 从 (2,3) 改成 (3,2) 吗?和 reshape 差不多。
正解转置不是简单换 shape 数字,而是交换元素位置,让原来 (i,j) 位置的元素去新矩阵的 (j,i) 位置。reshape 只是保留原排列顺序重新分组。两者结果可能形状一样,但内容排列完全不同。
为何常见:PyTorch 打印出来都是同一组数字,不深入看排列顺序就看不出差别。
误区一听到 4 维、5 维张量就觉得那是四维空间里的东西,普通人根本搞不懂。
正解在深度学习里,维度大多是“方便的编号”。比如 (batch, seq_len, embedding) 只是把“句子、词位置、词向量”三个语义装进三个轴。本质上还是同一套公寓楼编号规则,和抽象的“高维空间”没有必然关系。
为何常见:线性代数课把高维空间讲得太玄,容易让人把它想象成某种神秘数学。
误区反正都是改形状,顺序无所谓,最后 shape 对就行。
正解顺序会直接影响元素排列和内存布局。例如 x.reshape(6,4).transpose(0,1) 与 x.transpose(0,1).reshape(4,6) 的内容顺序完全不同。前者先按行重排再交换轴,后者先交换轴再重新分组,每一步的输入都是上一步的输出。
为何常见:看到形状都是 (4,6) 就觉得等价,没去看里面的元素到底怎么排的。
面试题
Q1. 在 PyTorch 中,标量张量的 shape 是什么?
- A.
()(空元组)← 正确答案 - B.
(1,)(单元素) - C.
(0,)(零长度) - D.
(None,)(未知维度)
答案:A。标量只有一个数,没有轴,所以 shape 是空元组 (),阶为 0。可以把 () 想象成“没有楼层的独栋小楼”,只有一间房。B 是含 1 个元素的向量,C 是长度 0 的向量,D 是动态形状占位,都不对标量。
Q2. 张量的“阶”指的是什么?
- A.元素的个数
- B.数值的范围
- C.维度轴的数量← 正确答案
- D.存储的字节数
答案:C。阶(rank)是张量有多少个轴,也就是形状元组里数字的个数。元素个数是“房间总数”,数值范围是数据本身的量纲,存储字节数取决于数据类型。这就像楼层数是“有几层”,跟每层家具多少、用什么材料无关。
Q3. shape=(2, 3, 4) 的张量里有多少个元素?
- A.9 个
- B.12 个
- C.24 个← 正确答案
- D.48 个
答案:C。元素总数等于各轴长度连乘:2×3×4=24。这本质上就是“全楼房间数 = 每层间数 × 层数 × 栋数”的乘法。
Q4. 把 shape=(6,) 的张量 reshape 成 (2, 3),总元素个数是多少?
- A.2 个
- B.3 个
- C.6 个← 正确答案
- D.12 个
答案:C。reshape 只能改变“房间怎么分组”,不能改变房间总数。原张量 6 个元素,reshape 成 (2,3) 后仍然是 6 个。记住守恒律:新形状连乘必须等于旧形状连乘,2×3=6。
Q5. 关于矩阵转置,下面哪些说法正确?
- A.转置后下标交换位置← 正确答案
- B.两次转置返回原矩阵← 正确答案
- C.转置后形状保持相同
- D.转置不改变元素总个数← 正确答案
答案:ABD。A 对,转置的定义就是 $(A^T)_{ij}=A_{ji}$;B 对,转置两次回到原矩阵,像把图纸转回来;C 错,A 是 m×n 时 A^T 是 n×m,形状通常不同;D 对,元素总量永远是 mn,房间数不变。
Q6. 图像张量使用 NCHW 格式时,中间两个维度表示什么?
- A.批次和通道
- B.通道和高度← 正确答案
- C.高度和宽度
- D.批次和宽度
答案:B。NCHW 对应 batch、channel、height、width,中间两位是 C 和 H,即通道数和高度。把四个字母逐个念出来,答案就清楚了;这正是“先念 shape 再动手”的好习惯。
Q7. 一个 shape=(5, 4, 3) 的张量执行 permute(2,1,0) 后,新形状是?
- A.(3, 4, 5)← 正确答案
- B.(4, 5, 3)
- C.(5, 3, 4)
- D.(3, 5, 4)
答案:A。permute(2,1,0) 表示把原第 2 个轴放到最前,第 1 个轴保持中间,第 0 个轴放最后。原轴长是 5、4、3,所以新形状是 (3, 4, 5)。就像把楼的正视图、侧视图、俯视图调换顺序看,楼本身没变,但“第几轴代表什么”变了。
Q8. 关于 reshape、view 和 transpose,下面哪些说法正确?
- A.reshape 前后元素总数一定不变← 正确答案
- B.transpose 操作会立刻复制底层内存
- C.转置后的张量一定是连续视图
- D.view 操作一般不会拷贝数据← 正确答案
答案:AD。A 对,reshape 守恒律永远成立;B 错,transpose 通常返回视图,不立即拷贝;C 错,转置通常得到非连续视图;D 对,view 共享内存、不复制数据。可以理解为:view 只是换双眼睛看同一栋楼。
Q9. shape=(2, 3, 4) 的行优先存储中,索引 (1, 2, 0) 对应的内存偏移量是?
- A.12
- B.16
- C.20← 正确答案
- D.24
答案:C。行优先偏移 = 1×(3×4) + 2×4 + 0 = 12+8+0 = 20。这就是“每走一步跨过多少房间”的连乘规则:第 0 轴步长 12,第 1 轴步长 4,第 2 轴步长 1。记住公式,别靠猜。
Q10. 关于非连续张量,下面哪些说法正确?
- A.转置经常产生非连续视图← 正确答案
- B.非连续张量调用 view 可能失败← 正确答案
- C.非连续张量永远无法变回连续
- D.非连续张量可调用
contiguous()修正← 正确答案
答案:ABD。A 对,transpose 通常生成 stride 打乱的非连续视图;B 对,view 要求内存连续,非连续时直接报错;C 错,contiguous() 就是“搬家公司”,能把数据重新落成连续;D 对,这正是修正非连续布局的标准手段。