如果你是刚接触机器学习的开发者,第一节课很可能不是数学模型,而是先见到一个高频词:张量(Tensor)。很多人在这一步就开始犯迷糊——它和数组什么关系?和矩阵又有什么区别?为什么 PyTorch 里一切操作都叫 Tensor,连框架本身都用 Tensor 命名?
更让人头疼的是,刚学完 Python 的 list 和 NumPy 的 ndarray,打开 PyTorch 文档又看到torch.tensor(...),很快就分不清这些概念到底谁是谁。结果写代码的时候,今天报shape mismatch,明天报device mismatch,后天报dtype不一致,每一步都在跟“维度”作斗争。
我的判断是:张量不是什么高深莫测的数学对象,它是机器学习框架为了训练神经网络而重新设计的一种数据结构——把多维数组和现代硬件加速、自动求导绑定在一起。你只有把这一层看透,后面看模型源码、写训练循环、调 GPU 显存,才不会被各种形状问题卡住。
这篇文章会围绕张量的本质展开,讲清楚它到底解决了什么问题,与数组、矩阵、数据容器的边界在哪里,再用 PyTorch 从创建、运算、广播到自动求导做一遍完整演示。读完之后,你能理解张量的核心概念,也能独立排查开发中最常见的形状、类型、设备错误。
1. 这篇文章真正要解决的问题
本文不打算堆概念,先回答三个读者普遍关心的问题。
第一个问题:张量到底和数组、矩阵有什么区别?这个问题如果只用“张量是高维矩阵”来回答,等于没回答。因为矩阵已经能表示二维数据,而真实世界的数据往往是更高维的:一张彩色图片是三维,一个批量的小批量图片是四维,一条带 batch 的文本序列是二维到三维之间的结构。更重要的是,矩阵本身没有“设备”“梯度”“计算图”这些概念,张量有。
第二个问题:为什么从机器学习入门开始,就绕不开张量?因为机器学习的整个数据流转过程——原始数据输入、特征变换、模型权重、前向传播、损失函数、反向传播——全部以张量运算的形式存在。你训练一个神经网络,本质上就是在不断调整一组张量的数值,让损失函数变小。
第三个问题:新手最容易在哪些地方踩坑?从我见过的初学者代码来看,集中在三处:一是维度对不上,二是数据类型不一致,三是 CPU 和 GPU 上的张量不能混算。这三类问题在刚接触张量时几乎都会出现,本文会在第 7 章统一梳理排查思路。
什么样的人最适合读这篇文章?包括但不限于:
- 刚学完 Python,准备进入机器学习或深度学习开发的人;
- 已经写过一些 NumPy,想搞懂 PyTorch 底层数据结构的开发者;
- 在做机器学习课程作业或实验时,被 shape 报错反复折磨的学生;
- 想从原理层面弄懂自动求导、计算图,为后续看 Transformer、CNN 源码打基础的人。
一句话总结:读懂了张量,就拿到了阅读一切深度学习源码的入场券。读不懂张量,后面的模型代码都会变成“现象级编程”——照着抄能跑,改一行就崩。
2. 张量的核心概念:从标量到多维数据容器
张量的英文是 Tensor,词典里常翻译为“张量”,听起来很学术。但在机器学习的语境下,你可以先把它理解为:带有统一数据类型、固定形状、并能参与自动求导的多维数组容器。
2.1 标量、向量、矩阵与张量的关系
我们从数据维度的角度来梳理,这比直接背数学定义更直观。
| 数学对象 | 维度 | 形状示例 | 生活中的类比 |
|---|---|---|---|
| 标量 Scalar | 0 维 | () | 一个数,比如气温 26 度 |
| 向量 Vector | 1 维 | (3,) | 一列数,比如三天的气温观测值 |
| 矩阵 Matrix | 2 维 | (3, 4) | 一张二维表格,比如学生的成绩单 |
| 张量 Tensor | 3 维及以上 | (2, 3, 4) | 一批二维表格叠在一起,或一段视频的连续帧 |
你会发现,标量、向量、矩阵其实都是张量的特例。严格来说,张量是“多维数组”的统称,标量是 0 维张量,向量是 1 维张量,矩阵是 2 维张量。
但在日常交流中,我们用“张量”时通常默认指三维及以上的高维数据。这种表达上的细微差别,也正是很多初学者困惑的来源:文档说“Tensor is a multidimensional matrix”,你打开代码却发现一个一维的torch.tensor([1,2,3])也被称为 Tensor。其实这不矛盾,只是一维数据被当作“秩为 1 的张量”来看待而已。
2.2 张量的四个关键属性
要真正掌握张量,只需要抓住四个属性。
第一个是形状(shape)。它描述每个维度的大小,是排错时最常看的属性。x.shape输出(3, 4),表示这个张量有三行四列。
第二个是维度数(ndim),也叫秩(rank)。x.ndim输出 2,说明它是二维数据结构。注意区分shape和ndim,一个是具体每维的长度,一个是总共有多少维。
第三个是数据类型(dtype)。张量里的所有元素必须是同一种类型,常见的有torch.float32、torch.float64(即 double)、torch.int64(即 long)等。这一点和 NumPy 的 ndarray 很像,本质上都是“同质数组”。
第四个是设备(device)。张量可以存在于 CPU 上,也可以存在于 GPU 上。CPU 和 GPU 上的张量不能直接参与混合运算。这个概念是机器学习框架特有的,NumPy 数组只有一个“内存里”的存在方式,而张量还多了“显存里”的存在方式。
2.3 一个容易忽略的事实:张量比多维数组多了一层“计算引擎”
如果只是看存储结构,张量和 NumPy 数组几乎一样,都是同质多维数组。但张量多了两个特殊能力。
第一个是 GPU 加速。把数据转换成张量后,可以搬运到显卡上,利用 GPU 的并行计算能力做大规模矩阵乘法,这是现代深度学习训练能够跑起来的物理基础。
第二个是自动求导。PyTorch 的张量在构建运算关系时,会记录一个计算图(computational graph)。当你对最终结果调用backward()时,框架会自动沿计算图回传梯度。这意味着你可以定义任意复杂的前向运算,然后让框架替你完成链式求导,而不需要手动推导梯度公式。
这才是“张量”和“数组”最本质的分界线:数组只是数据的容器,而张量是“数据容器 + 计算引擎”的组合体。
3. 为什么机器学习离不开张量
理解了张量的定义,再看它在机器学习中的位置,会清晰很多。简而言之,整个深度学习生命周期都建立在张量之上。
3.1 数据表示:一切数据都可以编码成张量
图像是典型的高维张量。一张 RGB 彩色图片,理论上可以表示为(通道数, 高度, 宽度),比如(3, 32, 32)。如果一次处理 64 张图片,数据就变成(64, 3, 32, 32),这就是一个四维张量。中间那个 64 就是批量大小(batch size)。
文本数据经过分词和词向量映射后,也会变成张量。一个 batch 的句子,通常表示为(batch_size, sequence_length, hidden_size)。例如(32, 128, 768),意思是 32 个句子,每个句子 128 个 token,每个 token 用一个 768 维的向量表示。
表格数据、音频波形、时间序列,也都可以在预处理阶段转换成不同形状的张量。可以说,机器学习开发的第一步,就是把真实世界的数据“张量化”。
3.2 模型参数与中间计算:权重是张量,激活值也是张量
一个线性层y = Wx + b中,权重矩阵 W 和偏置 b 都是张量。神经网络中间每一层的输出,也叫激活值,仍然是张量。模型训练的目标,就是不断调整这些权重张量的数值。
如果你去看 PyTorch 里预训练模型的state_dict(),会发现它就是一个由大量张量组成的字典。每个 key 对应网络层的名称,每个 value 就是那一层的权重张量或偏置张量。这一设计贯穿所有深度学习框架。
3.3 批量计算:现代硬件需要“成块”的数据
CPU 和 GPU 都是高度并行的设备,它们喜欢一次处理一批数据,而不是一条一条地处理。为了充分利用硬件,我们通常把多个样本堆叠成 batch,让矩阵运算在一大块数据上完成。
batch 维是额外加上的张量维度。这也是为什么你会反复看到“四维张量”出现。没有 batch 的概念,GPU 的并行能力无法充分发挥,训练速度会慢到不可接受。
3.4 自动求导:张量承载了计算图信息
训练神经网络时,损失函数对每个参数的梯度决定了权重如何更新。如果让你手动计算每一层梯度的链式求导,深度模型基本无法落地,因为层数一旦加深,推导复杂度会指数级增长。
PyTorch 的方案是让张量自带“梯度记录”能力。只要你设置requires_grad=True,框架就会在前向传播时记录运算轨迹,在调用backward()时自动完成反向传播。这时候张量就不只是一个数据容器,还附带了一个关于“数据从哪来、经过哪些运算”的图结构。
4. 环境准备与前置条件
动手操作之前,先搭好环境。由于本文用 PyTorch 做演示,你需要准备 Python、PyTorch 和 NumPy。以目前的主流实践来看,使用 conda 或 venv 创建独立的虚拟环境是较好的做法,可以避免不同项目之间依赖冲突。
4.1 安装 Python 与创建虚拟环境
建议使用 Python 3.10 或更高版本。如果你已经安装了 Anaconda,可以执行下面的命令创建环境:
conda create -n tensor-basics python=3.10 -y conda activate tensor-basics如果你更习惯用 venv,也可以:
python3 -m venv tensor-basics source tensor-basics/bin/activate虚拟环境的意义在于:不同的机器学习项目可能依赖不同版本的 PyTorch,将环境隔离可以避免“升级了 PyTorch 导致老项目跑不起来”这类问题。
4.2 安装 PyTorch
PyTorch 的安装命令与操作系统、是否使用 GPU 有关,最稳妥的方式是打开 PyTorch 官方网站,在首页选择对应的系统、包管理工具和 CUDA 版本,复制生成的命令执行。如果你只是入门学习,CPU 版本完全够用,因为本文所有示例在小规模张量上运行,CPU 就能瞬间完成。
一个最小安装示例是:
pip install torch numpy装完后运行下面的命令,验证环境是否正常:
python -c "import torch; print(torch.__version__); print(torch.cuda.is_available())"如果看到类似于2.x.x的版本号,并且torch.cuda.is_available()返回False,也是正常的——说明当前安装的是 CPU 版本,或者当前机器没有可用的 NVIDIA GPU。入门阶段不用强求 GPU。
5. 张量基础操作:完整示例代码实现
这一节会从创建、查询、形状操作、自动求导四个维度,把张量最常用操作串起来写一遍。建议你打开环境,边读边运行。
5.1 创建张量
创建张量是使用 PyTorch 的第一步。可以直接从数据创建,也可以用初始化函数生成指定形状的张量。
# 文件路径:tensor_basics.py import torch import numpy as np # 从 Python 列表直接创建 a = torch.tensor([[1.0, 2.0], [3.0, 4.0]]) print("从列表创建:") print(a) print("dtype:", a.dtype) # 创建全 0 张量,形状为 (2, 3) zeros = torch.zeros((2, 3)) print("\n全 0 张量:") print(zeros) # 创建全 1 张量,形状为 (4,) ones = torch.ones(4) print("\n全 1 张量:") print(ones) # 创建单位矩阵,形状为 (3, 3) eye = torch.eye(3) print("\n单位矩阵:") print(eye) # 创建服从标准正态分布的随机张量,形状为 (2, 2) randn = torch.randn(2, 2) print("\n随机张量:") print(randn) # 从 NumPy 数组转换 numpy_array = np.arange(6).reshape(2, 3) from_numpy = torch.from_numpy(numpy_array) print("\n从 NumPy 转换:") print(from_numpy)这段代码的关键在最后一行。torch.from_numpy()创建的张量和原 NumPy 数组共享底层内存,修改其中一个会影响到另一个。如果你希望复制出一份独立数据,应该使用torch.tensor(numpy_array)或torch.as_tensor()再配合克隆。
5.2 查看张量的形状、维度、设备
拿到一个不熟悉的张量时,第一件事应该是打印它的形状和类型,而不是直接往模型里塞。
# 文件路径:inspect_tensor.py import torch x = torch.randn(2, 3, 4) print("shape:", x.shape) print("ndim:", x.ndim) print("dtype:", x.dtype) print("device:", x.device) print("元素个数:", x.numel())输出示例:
shape: torch.Size([2, 3, 4]) ndim: 3 dtype: torch.float32 device: cpu 元素个数: 24torch.Size([2, 3, 4])是 PyTorch 专用的形状对象,本质上是一个 tuple,因此可以直接用索引访问,比如x.shape[0]得到 2。numel()是 number of elements 的缩写,返回张量中元素总数。
5.3 形状操作:reshape、view、transpose 与 contiguous
形状操作是学习张量时最需要花时间的部分。很多开发者的 shape 报错,本质上都是不清楚张量底层如何组织。
先看三个最常用的方法:reshape、view、transpose。
# 文件路径:shape_ops.py import torch x = torch.arange(24) print("原始形状:", x.shape) # reshape:改变逻辑形状 a = x.reshape(2, 3, 4) print("reshape 后:", a.shape) # view:要求张量在内存中是连续的,在可以复用内存时使用 b = x.view(4, 6) print("view 后:", b.shape) # transpose:交换两个维度 c = torch.randn(3, 4) d = c.transpose(0, 1) print("transpose 后:", d.shape) # 输出 torch.Size([4, 3])这里最容易混淆的是reshape和view。简单理解:如果原始张量在内存中是连续存储的,两者都可以用;如果原始张量已经经过了转置等操作,变得不连续,那么view可能直接报错,而reshape会优先尝试复用内存,不行时再复制一份数据。
transpose的返回值只是“换了逻辑上的解读方式”,并没有真的在内存里把数据搬动。所以经过transpose的张量通常是非连续的。如果你接下来要调用一些要求连续内存的底层算子,可能会遇到报错,解决办法是调用.contiguous()把数据重新排布成连续内存。
d_contiguous = d.contiguous()在实际项目中,一个比较推荐的写法是:先统一用reshape,当你确认某个张量不会有非连续风险,并且关心性能时,再考虑view。这样能少踩很多坑。
5.4 自动求导:张量的核心优势
接下来用一个小例子演示自动求导。设 y = x² + 3x,在 x=2 处,dy/dx = 2x + 3 = 7。
# 文件路径:autograd_demo.py import torch # requires_grad=True 表示需要计算梯度 x = torch.tensor([2.0], requires_grad=True) y = x ** 2 + 3 * x # 反向传播 y.backward() # 查看梯度 print("梯度:", x.grad)输出:
梯度: tensor([7.])这个例子虽然简单,但它包含了自动求导的全部核心机制:构建计算图、前向传播、反向传播、梯度累积到叶子张量。在设计上,PyTorch 对叶子张量(即用户创建的、requires_grad=True的张量)会保存.grad属性,中间计算得到的张量默认不会保留历史梯度数据和图信息,除非你显式调用retain_grad()。
需要提醒的是:requires_grad是“传染”的。如果输入张量需要梯度,那么由它计算出来的新张量也会默认需要梯度。这在模型训练中是合理行为,但如果你只做推理(inference),应该用torch.no_grad()上下文管理器临时关闭梯度计算,以节省显存和加速。
with torch.no_grad(): y_pred = model(x)6. 张量运算与广播机制:维度对齐的艺术
创建张量只是第一步,真正进入机器学习后会面对大量张量运算。这一节讲两个核心点:基础运算和广播机制。
6.1 基础运算
PyTorch 支持逐元素运算和矩阵乘法两大类。逐元素运算包括加减乘除、比较、幂运算等;矩阵乘法用@运算符或torch.matmul。
# 文件路径:tensor_math.py import torch a = torch.tensor([[1.0, 2.0], [3.0, 4.0]]) b = torch.tensor([[5.0, 6.0], [7.0, 8.0]]) # 逐元素加法 print("逐元素加法:\n", a + b) # 矩阵乘法 print("矩阵乘法:\n", a @ b) # 逐元素乘法(Hadamard 积) print("逐元素乘法:\n", a * b)注意区分a * b和a @ b。前者是逐元素相乘,要求两个张量形状相同;后者是矩阵乘法,要求 a 的最后一维等于 b 的倒数第二维。这两者在机器学习代码里都极其常见,写混了会出现维度错误或完全错误的结果。
6.2 广播机制:小张量如何自动扩展到大形状
广播(broadcasting)是 PyTorch 自动把不同形状的张量对齐到同一形状再进行运算的机制,极大减少了手动复制数据的代码量。理解广播是理解“内存与张量对齐”的关键。
广播的规则一句话概括:从最后一个维度开始向前比较,每一维度要么长度相等,要么其中一个长度是 1,否则无法广播。
看一个经典例子:
# 文件路径:broadcast_demo.py import torch # 形状 (3, 1) a = torch.ones(3, 1) # 形状 (1, 4) b = torch.ones(1, 4) c = a * b print(c.shape) # torch.Size([3, 4])为什么(3, 1)和(1, 4)能相乘得到(3, 4)?因为广播机制会把 a 沿着第二个维度“复制”扩展 4 份,把 b 沿着第一个维度“复制”扩展 3 份。需要注意,这里的“复制”是逻辑上的,PyTorch 在底层并不一定真的为广播分配新的内存;它通过步长(stride)技巧让运算看起来像扩展了。这也是新手理解内存对齐时报错的核心原因:你以为数据已经被复制成相同形状了,但底层可能仍然是非连续存储。
再看一个不能广播的例子:
a = torch.ones(3, 4) b = torch.ones(2, 5) # 最后一维 4 和 5 不相等,且没有一个是 1,广播失败 c = a + b # RuntimeError这里的错误信息极其常见:The size of tensor a (4) must match the size of tensor b (5) at non-singleton dimension 3。
6.3 从内存布局理解形状错位
很多真正棘手的问题不是“维度不同”,而是“维度相同但语义不同”。比如一张图片既可以表示成(batch, height, width, channel),也可以表示成(batch, channel, height, width),也就是 NHWC 与 NCHW 两种布局。同一个数据,换一种布局,内存顺序就完全变了。
如果两个张量逻辑形状相同但内存布局不同,直接运算可能不报错,但结果却与预期不符。排查这类问题,可以打印张量的stride()属性,看看每个维度在内存中占用的步长。我在实际代码里见到的多数“看起来没毛病但结果不对”,往往就是这种暗中的布局不一致导致的。
建议在项目开始阶段,用注释为数据张量明确标注布局约定,例如:
# image_tensor: (batch, channel, height, width), dtype=float32这样团队成员在后续开发中就不会随意换布局。
7. 张量开发中的常见问题与排查思路
从运行报错来看,初学者遇到最多的问题集中在维度、类型、设备、原地修改这四个方面。
| 问题现象 | 可能原因 | 排查方式 | 解决方案 |
|---|---|---|---|
| 运行时报 shape mismatch | 两个张量形状无法广播,或矩阵乘法维度不匹配 | 在运算前打印x.shape和y.shape,肉眼对齐最后几个维度 | 用reshape、unsqueeze、squeeze调整形状 |
| 报 dtype 不一致或无法转换 | 整型张量和浮点张量混算,或模型权重是 float32 输入是 int64 | 打印x.dtype、y.dtype,观察模型输入要求 | 统一使用.float()、.long()或.to(torch.float32) |
| GPU 与 CPU 张量混合运算报错 | .to('cuda')只迁移了部分张量,模型和数据不在同一设备 | 打印model.device与每个输入张量的.device | 把所有输入、标签、模型都统一迁移到同一设备 |
| in-place 操作导致 autograd 报错 | 对requires_grad=True的张量使用+=、*=*或x[0] = ... | 查看报错信息中指向的行 | 改成x = x + 1,或先clone()再修改 |
| backward 得到 None | 某个中间张量没有参与损失计算,或requires_grad未打开 | 检查计算图中的叶子张量是否都设置了requires_grad | 给需要求导的参数设置requires_grad=True |
| 结果差异很大,但不报错 | 数据布局混乱,或 float32 与 float64 精度差异被放大 | 打印张量stride()和dtype,小范围对比中间结果 | 统一布局约定和全局 dtype |
7.1 遇到 shape mismatch 的第一步
不要盯着完整错误栈逐行读。先找到报错所在的那一行,然后立刻打印参与运算的所有张量的shape,再用广播规则逐个维度对齐。大部分 shape mismatch 问题都能在几十秒内定位。
a = torch.randn(2, 3, 4) b = torch.randn(3, 4) print("a.shape:", a.shape) print("b.shape:", b.shape) # RuntimeError 时先看这两个输出 c = a + b7.2 关于 dtype 的一条实用经验
PyTorch 默认浮点类型是 float32,默认整型是 int64。在模型训练中,推荐统一切到 float32。如果你从 NumPy 读入了一个 float64 数组,再用它构造张量,参与模型计算时可能会因为类型不匹配报错,或者触发额外的类型转换,降低训练效率。
x = torch.tensor(numpy_array, dtype=torch.float32)8. 最佳实践与工程建议
张量操作的工程化,比想象中更重要。下面几条是我认为对新手最有价值的建议。
8.1 为项目制定数据形状约定
项目里最好有一份简单的数据形状文档,明确输入格式。例如:
- 图像:
(batch, channel, height, width),简称 NCHW; - 文本:
(batch, sequence_length),id 类型为 int; - 表格:
(batch, feature_dim),浮点类型为 float32。
团队协作时,这些约定能避免大量无意义的 shape 错误。即使个人开发,固定约定也能减少心智负担。
8.2 梯度清零
PyTorch 的梯度是累积的,也就是说,每次backward()会把新的梯度加到已有梯度上。训练循环里如果不手动清零,梯度会越积越大,导致权重更新异常。
推荐在训练循环开头统一调用优化器的清零方法:
# 每个 batch 开始前 optimizer.zero_grad()如果你的模型有自定义的中间张量需要保留梯度,也可以调用model.zero_grad()。效果类似,但optimizer.zero_grad()更精细,只影响优化器管理和更新的参数。
8.3 用torch.no_grad()做推理
模型推理阶段不需要梯度。显式关闭梯度可以明显减少显存占用,并提升速度。更重要的是,能避免不小心让验证数据也进入计算图,造成内存泄漏式的增长。
model.eval() with torch.no_grad(): predictions = model(batch_data)注意,model.eval()和torch.no_grad()是两个不同概念。前者是切换模型的 dropout 和 batch norm 行为,后者是关闭梯度记录,两者经常一起使用,但关系并不等价。
8.4 固定随机种子保证实验可复现
机器学习实验的可复现性非常关键。如果不固定随机种子,每次运行结果都可能不同,排查问题时很难判断是代码 bug 还是随机波动。
# 文件路径:fix_seed.py import random import numpy as np import torch def set_seed(seed: int = 42): random.seed(seed) np.random.seed(seed) torch.manual_seed(seed) if torch.cuda.is_available(): torch.cuda.manual_seed(seed) torch.cuda.manual_seed_all(seed)8.5 控制数据在 CPU 和 GPU 之间的搬运
GPU 显存昂贵,数据搬运也有成本。尽量避免在训练循环中频繁调用.cpu()或.cuda()。正确做法是:在循环外部把数据加载器设置成目标设备,或每次取 batch 后统一迁移一次。
一个低效示例:
for x, y in dataloader: x = x.cuda() y = y.cuda() # ...一个更可控的做法是在数据加载后统一处理,并尽量保持 batch 的传输粒度。如果你做的是超大批量分布式训练,还会涉及更复杂的分片和张量并行,但那已经属于分布式训练的高级话题了。
8.6 优先使用torch.utils.data.Dataset和DataLoader
从工程化角度,不建议手动写死数据切片。PyTorch 提供了标准化接口Dataset和DataLoader,它们天然处理了张量转换、batch 堆叠、shuffle 和并行加载。
# 文件路径:simple_dataset.py import torch from torch.utils.data import Dataset, DataLoader class SimpleDataset(Dataset): def __init__(self, x_tensor, y_tensor): self.x = x_tensor self.y = y_tensor def __len__(self): return len(self.x) def __getitem__(self, index): return self.x[index], self.y[index] x = torch.randn(20, 10) y = torch.randint(0, 2, (20,)) dataset = SimpleDataset(x, y) loader = DataLoader(dataset, batch_size=4, shuffle=True) for batch_x, batch_y in loader: print(batch_x.shape, batch_y.shape)这段代码展示了张量如何与数据加载流程配合。写数据流的时候多用框架自带的类,后续维护和扩展都会轻松很多。
9. 总结与后续学习方向
张量是机器学习框架中最基础、也最容易被低估的概念。它不是数组的另一个名字,而是把多维数组、统一数据类型、GPU 加速、自动求导和计算图融为一体的专门设计。理解张量,意味着你不再把 PyTorch 当成一个“神奇的 API 集合”,而是能看懂每一步运算背后的数据怎么流动、形状怎么对齐、梯度怎么回来。
本文真正讲透的点包括:张量与数组矩阵的关系、决定张量的四个核心属性、机器学习离不开张量的原因、用 PyTorch 创建和操作张量的完整示例、广播机制与内存布局对齐、以及六类常见报错的排查思路。你已经可以拿着这些内容,去分析一个最简单的线性回归模型里每一步的输入和输出形状了。
下一步建议你按这样的路径继续实践:先用torch.randn创建几个不同形状的张量,练习 reshape、transpose、squeeze、unsqueeze 操作,把所有操作之后的 shape 变化手写一遍;接着用自动求导实现一个最小二乘法的梯度下降;然后阅读torch.nn.Linear的源码或文档,观察它的权重张量形状是如何与输入输出对齐的。
如果你正在刷机器学习课程,或者准备机器学习期末复习,建议把张量这一章真正当成“地基”来对待,不要跳过。后面的 CNN 卷积核是四维张量,Transformer 的注意力矩阵是二维到三维张量的组合,分布式训练里的张量并行则完全建立在张量切分之上。地基如果没打牢,越往后越容易坍塌。遇到第一个 shape mismatch 的时候,不用沮丧,打开终端把x.shape和y.shape打出来,一步一步对齐,这本身就是掌握张量最好的方式。