机器学习入门必懂:从形状到轴的张量核心概念与实践指南
2026/9/7 9:48:29 网站建设 项目流程

我第一次在深度学习代码里遇到shape不匹配的报错时,整整折腾了一下午。报错信息其实写得很明白:某个张量的形状是[32, 28, 28, 1],另一个是[32, 784],两者没有办法直接相乘。真正让我卡住的,不是数学,而是我说不太清那个四维形状里的四个数字分别代表什么,也不理解图像数据是怎么从四维变成二维的。后来我才慢慢意识到,问题不在神经网络原理,而在一个更底层的事情上:我没有真正理解张量。

如果把机器学习训练过程比作物流系统,张量就是统一规格的集装箱。不管原始数据是图片、表格、文本还是音频,都要先按规则装进这个容器里,模型才能批量搬运、并行计算、自动求导。这篇文章会从“张量到底是什么”讲到“在实际代码里怎么和它相处”,中间也会把我自己踩过的坑一并交代清楚。

1. 为什么一个“数字容器”会让这么多学习者反复卡壳

1.1 表面是在调 bug,实际是在补一个抽象概念

很多人第一次接触张量,是从报错开始的。比如我上面提到的shape mismatch。这类报错看起来像配置问题,于是新手会先去改学习率、改网络层数,甚至把整个模型换掉,结果没有任何变化。真实原因通常不是参数,而是你把一个二维矩阵传给了期望三维输入的接口,或者你把[C, H, W]传成了[H, W, C]

这些错误的背后,是“张量是有形状的,而且每个轴都有固定含义”这件事没有形成体感。你不缺代码能力,缺的是对数据容器的一种直觉。这种直觉没法靠背诵获得,只能靠一次次观察和修正建立。

1.2 同一个词,在三个地方有三种含义

困难之处在于,“张量”并不是一个含义单一的词。在线性代数里,它像多维数组的延伸;在编程语言里,它看起来就是带维度的数组;在 PyTorch、TensorFlow 这类框架里,它又不只是数据容器,还是计算图里的节点,带着dtypedevice、是否需要梯度等属性。

这三种视角叠在一起,很容易让人混乱。你学的时候以为在看数学,查资料时发现是数据结构,写代码时又碰到设备和内存问题。其实这三个视角都成立,只是你不需要在一开始全部掌握。最务实的做法,是先以“工程里的数据容器”来理解它,等用到再往底层补。

1.3 机器学习框架里的张量,和数学里的张量不完全是一回事

这里需要做一个区分。数学上的张量理论是一套严格的多重线性代数体系,涉及张量积、协变、逆变等概念,想真正弄懂需要不少时间。但机器学习工程里说的 Tensor,绝大多数时候可以理解成“任意维度的数据数组”,再额外带上形状、数据类型、所在设备这些工程属性。

我见过不少人因为觉得“张量”这个词有数学门槛,就把入门资料放了好几个月。实际上,工程日常用到的只是张量的一个子集:看得懂维数、形状、轴,会做 reshape、transpose、拼接和广播,基本就够用了。想继续深入当然可以,但真的不必把它当成前置条件。

2. 从标量到 N 维张量,先画一张认知地图

2.1 四个台阶:标量、向量、矩阵,然后才到张量

可以从已经熟悉的对象往上搭楼梯。

名称维数(rank / ndim)形状示例典型含义
标量0()一个单独的数,比如损失值
向量1(3,)一条样本的 3 个特征
矩阵2(32, 3)32 条样本,每条 3 个特征
张量3 及以上(8, 3, 32, 32)8 张彩色图片,3 个通道,高和宽都是 32

严格来说,标量、向量、矩阵都可以看成张量的特例。但在机器学习代码里,说“张量”通常默认至少是 2 维以上,因为框架内部的数据几乎总是带着批量维。理解这一点,再去看各种官方文档里的Input Shape,就会轻松很多。

2.2 三个核心属性:维数、形状、轴

这三点是理解张量的钥匙。

  • 维数,也叫 rank 或 ndim,表示张量有多少个轴。一个(2, 3)的张量,rank 是 2。
  • 形状,也就是 shape,表示每个轴上元素的数量。(2, 3)表示第一轴长度是 2,第二轴长度是 3。
  • 轴,也叫 axis 或 dimension,表示沿着哪个方向看数据。轴 0 通常对应第一个维度,轴 1 对应第二个维度,依次类推。

“维度”这个词很容易产生歧义。有人问“这个张量是多少维的”,可能指 rank,也可能指某个轴的长度。所以我自己在沟通和排查问题时,会尽量把词换成“形状”:不说“这是一个三维数据”,而是说“它的 shape 是[3, 224, 224]”。一个简单的用词变化,能省掉不少无谓的误解。

2.3 图像进入张量之后,数据到底怎么排的

以图像为例。一张灰度图,本质是一个“高乘宽”的矩阵,每个位置是一个像素值,所以形状可以是[高度, 宽度]。一张彩色图片,每个位置有三个颜色通道,于是变成[高度, 宽度, 通道数],也就是[H, W, C]。如果一次处理一个批量,再在最前面加上批量维,就得到[批量数, 通道数, 高度, 宽度],也就是深度学习里常见的[B, C, H, W]

注意,同样是表示一批图像,[B, C, H, W][B, H, W, C]是不同的布局。PyTorch 的卷积层默认输入是前者,而一些旧代码或 TensorFlow 传统布局会用后者。这不是谁对谁错的问题,而是框架契约的问题。你要做的不是背下每种布局,而是清楚自己正在使用的框架和 API 到底要求什么。

3. 机器学习为什么偏要把所有数据都装进张量

3.1 统一容器,才能统一并行计算

深度学习训练非常依赖并行计算。显卡的核数远多于 CPU,但它擅长的是“同一条指令处理大量数据”,这就要求输入格式整齐划一。张量就是一种天然的整齐格式:它有明确的形状、一致的数据类型、相对可控的内存布局。这样底层才能用高效的数值计算库去批量做矩阵乘法和卷积,数据也才能在 CPU 和 GPU 之间高效搬运。

假如每次数据都长得不一样,比如第一张图片是32x32,第二张是64x64,训练引擎就得为每个样本单独处理,效率会低很多。所以框架层面会要求你先统一到张量,再做缩放、填充、归一化之类预处理。

3.2 张量在模型里不是静态数组,而是数据流

在 PyTorch、TensorFlow 这类框架里,张量同时扮演两个角色:它是数据容器,也是计算图中的节点。你定义一个卷积层,输入张量经过它得到输出张量,框架会自动记录这个变换关系,以便在反向传播时计算梯度。

这带来一个结果:训练模型时,你不一定需要关心张量底层是怎么存储的,但必须关心它在每一层前后的 shape 变化。也正因为多了“自动求导”这层职责,框架里的张量和普通静态数组区分开了。普通数组只负责存储,张量还要负责“记住自己是怎么来的”。所以调试模型时,看着张量 shape 一步一步变化,比盯着数学公式更容易理解网络实际做了什么。

3.3 别被术语吓住:工程层只需要理解一件事

很多人问我,学机器学习是不是得先把张量分析啃完。我的判断是:不用。数学里的张量理论适合专门做研究的人,工程实践中大多数时候需要的只是三件事:看见张量的形状、理解每个轴的意义、知道一次操作之后形状会怎么变。

另外,自制数据加载器时,“内存与张量对齐”很容易变成一个隐患。比如 OpenCV 读图默认返回[H, W, C],而训练管线要求[C, H, W],中间漏掉一次轴交换,报错会一直拖到网络第一层才出现。问题埋得越深,越难定位。所以最稳妥的做法,是在数据加载入口就统一张量布局,同时打印出shapedtype和取值范围做验证。还有一点很多人忽略:张量的dtype会影响算子行为。同样的矩阵乘法,float32float64在 GPU 上的性能差别可能很大,整数张量甚至根本不能参与某些卷积运算。所谓统一容器,不只是在形状上统一,数据类型也要统一。

4. 形状和轴理解到位,张量运算才不会翻车

4.1 reshape 是重排,不是换轴

最常见的错误,是拿reshapetranspose用。reshape会保留数据在内存中的排列顺序,只是把数据重新切成新形状。比如一个 1 到 6 的数组,先排成(2, 3)

import numpy as np a = np.arange(1, 7).reshape(2, 3) # array([[1, 2, 3], # [4, 5, 6]])

如果再reshape(3, 2),结果会是:

[[1, 2], [3, 4], [5, 6]]

元素顺序仍然按行扫描,只是每段长度变了。如果你想让原来的行变成列,也就是把(2, 3)转成(3, 2),并且第一列是 1、4,第二列是 2、5,就必须用转置a.T,而不是reshape

4.2 transpose 和 permute 会改变轴的语义

PyTorch 里,transpose(dim0, dim1)交换两个轴,permute可以重排多个轴。它们和reshape的本质区别是:reshape只改形状,不改轴顺序;transpose/permute改的是轴顺序。

举例说明,如果有一个形状是[B, C, H, W]的图像张量,你想转成[B, H, W, C],正确做法是:

import torch x = torch.randn(2, 3, 32, 32) # [B, C, H, W] y = x.permute(0, 2, 3, 1) # [B, H, W, C]

如果这时贸然用x.view(2, 32, 32, 3),可能不会直接报错,但结果语义是错的。因为你只是把内存里的连续数据重新切块,并没有把通道轴挪到最后一个位置。这种错误更难发现,因为网络可能还能继续训练,只是精度一直上不去。

再提醒一句:permute之后的张量在内存中可能是非连续的,某些框架里直接接view或其他算子会报错。常见做法是先调用contiguous(),再做后续操作。

先理解轴的语义,再谈运算。任何一次 reshape、transpose、拼接之前,先问自己:这会不会改变轴的顺序?有没有可能产生语义错位?

4.3 广播机制:形状不完全一致也能做运算

广播是新手容易忽略的另一个规则。它允许两个形状不完全一致的张量做加减乘除,但前提是它们的维度从后往前逐一对齐,要么长度相同,要么其中一个长度是 1,要么某个张量在这一维度根本没有轴。

比如形状(3, 1)(1, 4)相加,会得到一个(3, 4)的结果:第一组把(1, 4)沿轴 0 复制到 3 行,第二组把(3, 1)沿轴 1 复制到 4 列。这个机制很方便,但也容易掩盖错误:当你以为两个张量形状相同,实际上差了一维时,广播会默默扩张,反而不报错。所以判断运算是否符合预期,先对比两个输入的 shape 会更稳妥。

除了 reshape 和 transpose,catstacksqueezeunsqueeze也是高频操作。cat是在已有轴上拼接,要求其他轴完全一致;stack是新增一个轴,再在这个新轴方向上堆叠。这两个操作经常被搞混。使用前先想清楚:你是要新增一个批量维,还是要把两个特征矩阵在同一个轴上拼起来。操作不同,结果 shape 自然也不同。

5. 在一小段代码里建立张量的“可检查感”

5.1 先用 NumPy 熟悉基本属性

NumPy 是理解张量最轻量的环境。语法简单,属性也清晰。

import numpy as np a = np.array([[1, 2, 3], [4, 5, 6]]) print(a.ndim) # 2 print(a.shape) # (2, 3) print(a.size) # 6,元素总数 print(a.dtype) # int64 b = np.zeros((2, 3, 4)) # 创建一个形状为 [2, 3, 4] 的数组 print(b.shape) # (2, 3, 4)

当你只是需要组织数据、做预处理或检查中间结果时,NumPy 足够用了。它和深度学习框架的张量之间通常可以低成本互转。通过反复打印ndimshape,你会慢慢形成“每个操作如何改变形状”的直觉。

5.2 再用 PyTorch 观察深度学习里的张量

深度学习框架里的张量多了一些属性,比如设备、是否要求梯度等。下面是一个最小示例:

import torch x = torch.tensor([[1., 2., 3.], [4., 5., 6.]]) print(x.shape) # torch.Size([2, 3]) print(x.ndim) # 2 print(x.dtype) # torch.float32 print(x.device) # cpu y = x.view(3, 2) print(y.shape) # torch.Size([3, 2]) z = x.permute(1, 0) print(z.shape) # torch.Size([3, 2])

这里view和 NumPy 的reshape类似,但更严格一些,它要求张量在内存中连续;如果遇到报错,可以换用reshape或先调用contiguous()permute则用于重排轴。两种操作在部分情况下会得到相同的形状,但语义完全不同,建议在自己机器上多打印几组结果做对比。

5.3 在训练循环里跟踪张量形状变化

实际训练中,最有用的调试手段之一,是在每一层前后打印 shape。你可以在模型的forward方法里临时加几行输出:

def forward(self, x): print("input:", x.shape) x = self.conv1(x) print("after conv1:", x.shape) x = self.pool(x) print("after pool:", x.shape) # 全连接前展平 x = x.view(x.size(0), -1) print("after flatten:", x.shape) ...

观察几次之后会看到规律:卷积层通常保持批量维,改变的是通道维;池化层会缩小高和宽;全连接层要求把多维数据展平成[批量, 特征数]。结合框架文档里给出的 Shape 说明,逐步核对每个变换,大多数 shape 问题都能被提前发现。

建议:调试时先用一个 batch,甚至一张图先跑通,不要直接上完整数据集。样本越少,越容易定位 shape 问题发生在哪一层。

还有一点容易被忽略:打印张量时,如果内容太长,默认会被省略号截断。想看完整数值可以设置torch.set_printoptions(threshold=10_000),或直接切片查看。检查中间张量时,除了 shape,还要顺手看下数值范围。NaN、全零、数值过大都是危险信号。shape 对不代表数据对,但它通常是第一道最明显的检查关卡。

6. 训练时最常见的一类报错:shape 不匹配

6.1 先判断报错类型

碰到 shape 报错,不要急着查那行代码本身。先给错误分个类,排查会有方向得多。

报错特征大概率原因排查重点
两个张量 shape 对不上输入维度和模型期望不一致数据加载、预处理、上一层输出
轴顺序错误但没有直接报错reshape 代替了 transpose / permute轴语义检查、结果精度
张量在 CPU,模型在 GPU设备不一致统一调用.to(device)
数据类型不匹配float 和 int 混用检查dtype
内存不足 OOMbatch 太大、序列太长减小 batch、降精度、优化数据加载

6.2 三步排查流程:看形状、看来源、看变换

我总结过一个顺手的排查链路:

  1. 先定位报错位置,把参与运算的所有张量形状打印出来。一行print(x.shape)就可以。
  2. 回看这些张量是从哪个前一步来的。是数据加载器生成的,还是卷积输出,还是经过了viewpermutesqueeze等操作?这决定了问题根源在哪一层。
  3. 手写一遍从输入到报错位置的 shape 变化链,再和实际输出逐项对比。不确定时,把每一步都打印出来,直到找到和预期不符的那步。

很多报错看起来发生在网络深处,实际根因却在数据读取入口。比如图片在图像库里是[H, W, C],网络要求[C, H, W],中间漏了一次轴交换,错误会延迟到卷积阶段的矩阵运算才爆发。按上面三步走,比随机改参数有效得多。

排查 shape 报错时,一次只验证一个假设。不要同时改 batch size、网络层和轴顺序,否则你会很难确认到底是哪一步修复了问题。

6.3 除了 shape,还要检查 dtype、device 和内存

张量之间的运算不仅要求形状符合规则,对数据类型和设备也有要求。PyTorch 中 CPU 和 GPU 上的张量不能直接混算,float32float64混用时,有些算子会隐式转换,有些直接报错。建议在加载数据和构建模型时就统一约定:图像数据常用float32,分类标签常用long;需要 GPU 训练时,把模型和输入张量都放到同一个设备上。

内存问题也值得提前看。一个[256, 3, 224, 224]float32张量,不算梯度缓存,单输入就接近 150MB。如果数据加载时多开几个进程,再叠加中间激活值,显存很快会触顶。发现 OOM 时先减 batch,再考虑关闭不需要的梯度、使用混合精度或换用更省内存的数据布局。

7. 学张量最有体感的一条路径:三个实验加一个框架

7.1 实验一:把数据的“形状”变成能默写的信息

第一个实验很简单。找一张你熟悉的图片,分别用图像库读入、用 NumPy 保存、用 PyTorch 转成张量,每次打印shapedtype和数值范围,然后手动完成[H, W, C][C, H, W]的转换。这个实验会强迫你直面轴顺序的问题,比看十遍理论都直观。

7.2 实验二:在同一个数组上分别做 reshape 和 transpose

准备好一个从 1 到 12 的整数数组,分别尝试这些操作,并打印每次结果:

  • reshape(3, 4)
  • reshape(4, 3)
  • .Ttranspose
  • PyTorch 里的viewpermute

你会发现reshape只是改变读取方式,transpose是调整轴语义。把结果写下来对比,印象会非常深。这个实验做一次,基本可以避免以后犯“拿 reshape 替代 transpose”的低级错误。

7.3 实验三:搭一个极小的神经网络,逐层打印 shape

不用复杂模型,一个卷积层、一个池化层、一个全连接层就够。输入随机张量,然后在每一层后面打印 shape,比较手动推演和实际输出。遇到不一致,就问自己:我漏掉了哪个操作?是通道维变化,还是展平方式错了?

7.4 一个可复用框架:张量五问

把下面的框架用在每次报错或每个新代码片段里,可以快速定位大多数张量相关的问题。

问题你要确认的信息
1. 这个张量的维数是多少?ndim/ rank
2. 每个轴分别代表什么语义?批量、通道、高、宽、特征数
3. 当前形状是什么?shape
4. 下一步操作会改变哪个轴?reshape、transpose、cat 等
5. 我期望的输出形状是什么?手动推演后和实际对比

实际写代码时,哪怕只在心里快速过一遍这五个问题,也能避免大多数张量失误。特别是第 4 问,很多人栽在“我以为这个操作不会改变轴”上面。

7.5 边界提醒:不是所有数据都必须用张量

最后说一句边界。传统结构化数据量不大、不需要 GPU 和自动求导时,用 DataFrame 或普通数组往往更顺手。张量是深度学习工作流里的核心容器,但不是唯一正解。只有当你要批量训练、并行计算、自动求导,或者需要把数据传入神经网络时,张量才真正发挥不可替代的作用。学会判断“这里是否需要张量”,本身也是一种工程能力。

入门阶段,张量的难点从来不是记住所有 API,而是建立对形状的敏感度。这种敏感度不是看书看出来的,而是在一次次报错和打印中训练出来的。所以遇到 shape 不匹配时,不必把它当成麻烦,可以把它当成一次刻意练习。处理过十几次后,你对轴、维度和变换的理解会明显上一个台阶。

现在再回到开头那个报错。当我终于弄懂[32, 28, 28, 1][32, 784]分别代表着什么,后面的事情就顺理成章了:图像先被读成四维张量,经过预处理展平成二维,再进入全连接层。报错解决的关键,不是找到了某个神秘参数,而是我能对自己说清楚:这个张量现在是什么形状,它应该是什么形状,中间怎么变过去的。如果下次遇到 shape 问题时,你也能这样问自己一句,说明你对张量的理解已经到位了。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询