资讯动态

PyTorch学习笔记:Tensor张量本质、核心操作与实战避坑指南

发布时间:2026/10/16 19:52:21 来源:尧图企业网站定制
这篇PyTorch学习笔记系列的第一篇聊聊Tensor张量。我在最开始接触PyTorch的时候被各种概念绕得晕头转向后来发现只要把Tensor这个“地基”彻底搞懂后面学自动求导、搭建神经网络都会顺畅很多。这篇笔记是给刚入门深度学习、或者从其他框架转过来的朋友准备的我会从张量的本质讲起结合我实际跑代码时踩过的坑把创建、属性、操作、设备迁移这些核心知识点一次说透。1. Tensor基础认知从数据结构角度理解张量1.1 为什么PyTorch的一切都是张量在深度学习里图像、文本、音频这些数据最终都要变成数字才能被模型处理。一张 ( 28 \times 28 ) 的灰度图片在 PyTorch 里就是一个形状为[1, 28, 28]的张量一句经过编码的文本可能变成[batch_size, seq_len, hidden_dim]的三维张量。可以说张量就是深度学习的“通用货币”。张量和我们熟悉的矩阵、数组有什么本质区别简单说矩阵是二维的数组是一维的而张量是n维的。标量是零维张量向量是一维张量矩阵是二维张量三维及以上的统称为高维张量。这种统一的数据结构让不同形态的数据可以走同一套计算逻辑这也是 PyTorch 把 Tensor 作为核心抽象的原因。1.2 Tensor与NumPy数组的核心差异用NumPy的朋友不陌生ndarrayTensor 和它有相似之处但有几个关键不同点必须刻在脑子里设备支持Tensor 可以在 GPU 上运行ndarray只能待在 CPU 上。深度学习训练动辄上亿次矩阵运算没有 GPU 加速基本跑不动。自动求导Tensor 自带requires_grad属性可以记录运算过程并自动计算梯度NumPy 做这个需要手写反向传播。梯度信息Tensor 可以携带grad_fn和.grad记录它是由什么操作产生的方向传播时顺着这个链条反向求导即可。注意我见过很多初学者图省事在训练循环里把 Tensor 转成 NumPy 再操作这个习惯非常不好。除了打断计算图CPU 和 GPU 之间的数据拷贝开销也比你想象的大得多。2. 创建Tensor的七种武器与适用场景2.1 直接创建torch.tensor与torch.Tensor的区别创建 Tensor 的方法看着多其实核心就是几类。先说最常用的torch.tensorimport torch # 从列表创建 a torch.tensor([1, 2, 3]) print(a) # tensor([1, 2, 3]) # 指定数据类型 b torch.tensor([1.5, 2.5], dtypetorch.float32) print(b.dtype) # torch.float32 # 从numpy创建 import numpy as np arr np.array([4, 5, 6]) c torch.tensor(arr)这里有个新手容易踩的坑torch.Tensor注意大写和torch.tensor小写看起来差不多实际上行为完全不同。torch.Tensor是默认张量类型torch.FloatTensor的别名它接收的是形状参数而torch.tensor接收的是具体数据# torch.Tensor(3) 创建的是长度为3的未初始化Tensor d torch.Tensor(3) print(d) # 输出的是未初始化的随机值看上去像垃圾数据 # torch.tensor(3) 创建的是标量3 e torch.tensor(3) print(e) # tensor(3)未初始化的torch.Tensor(3)输出值完全取决于当时内存里的残留数据属于不确定值。如果你要创建指定形状的矩阵请用torch.zeros、torch.ones这类明确初始化函数不要用torch.Tensor(shape)这种写法因为它不保证内容清零稍不注意就会引入脏数据。2.2 规则生成zeros、ones、eye与rand系列深度学习中大量场景需要特定形状的初始化张量我总结了一张常用对照表函数作用典型使用场景torch.zeros(shape)全0张量初始化偏置项、mask矩阵torch.ones(shape)全1张量初始化某些缩放参数torch.eye(n)单位矩阵计算某些距离度量torch.rand(shape)[0,1)均匀分布随机初始化torch.randn(shape)标准正态分布权重初始化配合Kaiming初始化等torch.arange(start, end, step)等差序列生成位置编码torch.linspace(start, end, steps)等间隔序列可视化坐标轴取值特别注意rand和randn的区别。torch.rand生成的是[0, 1)区间的均匀分布随机数适合概率相关的操作而torch.randn生成的是均值为0、方差为1的标准正态分布神经网络的权重初始化基本都用randn因为它更利于梯度传播的稳定性。# 一个典型的权重初始化写法 weight torch.randn(256, 128) * 0.01 # 乘0.01是让初始值足够小避免刚开始训练时激活值过大2.3 形状变换view、reshape与permute创建完张量下一步经常是调整形状。view和reshape都能改变张量形状但有个细微差别view要求张量在内存中连续contiguousreshape则更灵活如果输入不连续它会自动复制一份再变换形状。x torch.randn(2, 3, 4) print(x.shape) # torch.Size([2, 3, 4]) # 展平为 (2, 12) flat x.view(2, -1) # -1表示自动推导该维度 print(flat.shape) # torch.Size([2, 12]) # 转置维度 (3, 2, 4) transposed x.permute(1, 0, 2) print(transposed.shape) # torch.Size([3, 2, 4])这里必须提一个我实际工作中反复踩的坑做了transpose或permute之后张量在内存中不再连续如果这时候直接调用view会报错。比如把[2, 3, 4]转置成[3, 2, 4]后调用view(-1)你会得到一个 runtime error。解决方法是先调用.contiguous()让内存重新连续再调view。虽然reshape会自动处理但多一次contiguous()拷贝比不明不白地报错要好至少你知道发生了什么。3. 深入理解Tensor的核心属性shape、dtype与device3.1 这三者决定了一个Tensor的基本行为一个 Tensor 的几乎所有操作都绕不开这三个基本属性。打个比方shape决定了这个张量的“骨架”dtype决定了数据的“精度规格”device决定了数据“住在哪里”。三者缺一不可。shape返回一个torch.Size对象本质上是整数元组dtype数据精度常见的有torch.float32、torch.int64、torch.bool等device数据存储位置CPU 或 CUDA 设备x torch.randn(64, 3, 32, 32) print(x.shape) # torch.Size([64, 3, 32, 32]) print(x.dtype) # torch.float32 print(x.device) # device(typecpu)注意shape返回的torch.Size和普通元组兼容可以直接索引、相加比如x.shape[0]或者tuple(x.shape)。3.2 dtype选择背后的计算逻辑为什么要费劲区分 dtype因为不同的精度直接影响内存占用和计算速度。一张[1, 3, 224, 224]的 RGB 图片float32每个像素占4字节总占用 ( 1 \times 3 \times 224 \times 224 \times 4 602112 ) 字节约0.6MBfloat64每个像素占8字节内存直接翻倍在训练大模型时模型参数动辄几亿甚至几十亿float32的权重如果换成float64显存占用直接翻倍很多显卡直接爆掉。所以默认情况下 PyTorch 的张量都是float32这也是计算精度和资源消耗的平衡点。实际使用中的规律默认torch.tensor([1, 2, 3])得到int64默认torch.tensor([1.0, 2.0])得到float32深度学习模型的输入和权重默认float32标签分类任务的类别编号通常用int643.3 设备管理CPU与GPU之间优雅地搬迁训练深度学习模型GPU基本是标配。数据在 CPU 和 GPU 之间迁移用.to()方法# 判断是否有可用GPU device torch.device(cuda if torch.cuda.is_available() else cpu) x torch.randn(3, 3) x x.to(device) print(x.device) # device(typecuda, index0)新手最常犯的错误模型和数据不在同一个设备上。比如模型在 GPU数据还在 CPU一运行就报Expected all tensors to be on the same device。我的习惯是每次操作用.to(device)前都先确认 device 变量定义好了所有张量和模型统一调用.to(device)不要一会儿.cuda()一会儿.to(device)混用容易乱。还有一个性能细节GPU 和 CPU 切换开销不小。频繁的.cpu()再.to(cuda)会带来大量的 PCIe 数据传输影响训练速度。在一个训练循环里尽量减少这种来回切换。一般只在需要保存模型或者做可视化的时候才把数据搬回 CPU。4. 张量的索引、切片与条件筛选4.1 像切西瓜一样操作Tensor基本索引索引和切片是复制 NumPy 的操作习惯这里有几个高阶技巧x torch.arange(24).reshape(2, 3, 4) # 取第一维的第一个第二维的第二个元素 print(x[0, 1]) # tensor([4, 5, 6, 7]) # 切片取所有通道高度维取一半宽度维全部 img torch.randn(3, 224, 224) patch img[:, 0:112, :] print(patch.shape) # torch.Size([3, 112, 224])技巧1使用...省略多余的冒号。对于三维以上张量x[..., 0]表示取最后一个维度的第0个元素比写x[:, :, 0]简洁。技巧2使用bool掩码做筛选scores torch.randn(5) mask scores 0 print(mask) # tensor([True, False, True, ...]) print(scores[mask]) # 只保留大于0的分数这种布尔索引在模型输出筛选、目标检测的置信度过滤中非常实用。2020年那会儿我做目标检测的置信度过滤就用这个一句preds[conf_mask]就能把所有低置信度的框全部过滤掉不用写循环。4.2 高阶索引gather与scattergather和scatter属于知道能救命、不知道也能凑合过的操作。以gather为例它的场景是你有一个二维张量你需要按照索引矩阵沿某个维度取值# 一个实际的例子按索引挑出某个维度上的值 scores torch.randn(3, 4) indices torch.tensor([[0, 2, 3, 1], [1, 0, 2, 3]]) # 沿行方向dim1逐个取值 selected torch.gather(scores, 1, indices) print(selected.shape) # torch.Size([2, 4])在实现某些损失函数时如带类别权重的交叉熵、采样式损失gather几乎是唯一优雅的方案。我见过不少同学写两层循环慢慢取值那种写法在 batch size 大时性能血崩gather一行搞定速度提升几十倍。4.3 切片后修改的陷阱in-place操作与计算图在 PyTorch 里普通索引返回的是原张量的“视图”view修改视图会改变原数据x torch.arange(9).reshape(3, 3) view x[0, :] view[0] 999 print(x[0, 0]) # tensor(999) 原数据也被改了这既是好事省内存也是隐患容易误改数据。如果你拷贝一份独立数据使用.clone()view x[0, :].clone() view[0] -1 print(x[0, 0]) # tensor(999) 原数据不受影响这个坑在模型调试时非常隐蔽。我见过有人调试 LOSS 异常查了半天发现是某处索引返回视图后不小心改了输入数据导致后续 batch 全部用了脏数据。如果你的张量requires_gradTrue视图的修改还会影响计算图报出“leaf variable has been modified”之类的错误。5. 张量的核心运算逐元素、矩阵乘法与广播机制5.1 逐元素运算加减乘除与激活函数逐元素运算就是两个张量对应位置直接算。这点和 NumPy 完全类似a torch.tensor([1.0, 2.0, 3.0]) b torch.tensor([4.0, 5.0, 6.0]) print(a b) # tensor([5., 7., 9.]) print(a * b) # tensor([ 4., 10., 18.]) 逐元素乘法 print(a ** 2) # tensor([1., 4., 9.])注意*是逐元素乘法或者torch.matmul才是矩阵乘法两者绝不能混用。激活函数也是一类逐元素运算。PyTorch 把relu、sigmoid、tanh等都封装好了x torch.randn(3, 3) y torch.relu(x) # 等价于 max(0, x) z torch.sigmoid(x) # 将值压缩到 (0, 1)5.2 矩阵乘法torch.mm与torch.matmul的适用场景矩阵乘法在深度学习中无处不在全连接层、注意力机制、卷积实现核心都是矩阵乘法。常用的有三个函数torch.mm(a, b)只能处理二维矩阵乘法要求维度严格匹配torch.matmul(a, b)支持广播和更高维度的 batch 矩阵乘法a bmatmul的运算符写法a torch.randn(3, 4) b torch.randn(4, 5) c a b print(c.shape) # torch.Size([3, 5]) # 批量矩阵乘法batch维自动匹配 batched_a torch.randn(10, 3, 4) batched_b torch.randn(10, 4, 5) batched_c batched_a batched_b print(batched_c.shape) # torch.Size([10, 3, 5])在自注意力机制里Q K.T得到的形状是[batch, seq_len, seq_len]再乘V变成[batch, seq_len, dim]这些都是matmul的标准用法。新手容易混淆的一点是矩阵乘法的维度匹配规则(..., M, K) (..., K, N) - (..., M, N)中间那个K必须相等这个规则建议用笔写在笔记本第一页。5.3 广播机制让不同形状的张量做运算广播机制是 PyTorch 最强大也最容易出 bug 的特性。什么叫广播它允许形状不完全匹配的两个张量进行运算PyTorch 会自动扩展维度。最常见的例子a torch.randn(3, 4) b torch.randn(4) # b可以被广播成(3, 4) c a b print(c.shape) # torch.Size([3, 4])广播规则的核心逻辑是从最后一个维度开始对齐如果两个维度相等继续对齐前一个维度如果某个维度为1则该维度可以拉伸如果某个维度一个存在一个不存在存在的那个维度需要是1或者另一个可以扩展。无法匹配时直接报错。我踩过最隐蔽的坑是形状为[3, 1]和[4]的运算因为维度为1的存在两者可以广播成[3, 4]但有时候这不是你想要的。比如目标检测中做 anchor 和 gt 的 IoU 计算[num_anchor, 4]和[num_gt, 4]之间如果中间维度配错广播出来的结果会非常离谱而且完全不报错。建议在关键运算前后都打印一下.shape确认输出的形状符合预期别省这一步。5.4 归并运算sum、mean、max与dim参数归并运算把张量某个维度的信息压缩成更小的张量。dim参数是这里的关键它指定要压缩哪个维度x torch.randn(4, 3) print(x.shape) # torch.Size([4, 3]) # 按列求均值压缩第0维结果是每列的平均值 col_mean x.mean(dim0) print(col_mean.shape) # torch.Size([3]) # 按行求均值压缩第1维 row_mean x.mean(dim1) print(row_mean.shape) # torch.Size([4])dim0和dim1的区别搞混是所有新手的必经之痛。我自己的记忆技巧dim0表示从行这个维度上压缩即把每一列的多行数据合并成一个数dim1表示从列这个维度上压缩。当你把张量想象成一个 Excel 表格时dim0的操作相当于“按列汇总”dim1相当于“按行汇总”。还有一个keepdimTrue参数经常被忽略。如果设置x.mean(dim0, keepdimTrue)结果形状是[1, 3]而不是[3]这在后续做广播运算时会省掉很多维数匹配麻烦。6. 张量与NumPy的双向转换6.1 转换方法numpy()与from_numpy()深度学习的预处理通常在 NumPy 里做数据到了模型输入阶段要转成 Tensor而结果输出后为了可视化或保存又往往要转回 NumPy。双向转换代码如下import numpy as np import torch # NumPy - Tensor arr np.array([[1, 2], [3, 4]]) tensor torch.from_numpy(arr) print(tensor) # tensor([[1, 2], [3, 4]]) # Tensor - NumPy back tensor.numpy() print(type(back)) # class numpy.ndarraytorch.tensor(arr)和torch.from_numpy(arr)都能实现转换但有一个重要区别torch.from_numpy生成的新 Tensor 与原始 NumPy 数组共享内存。修改其中一个另一个也会变好处是零拷贝、速度快。6.2 共享内存带来的连锁风险共享内存有两面性。在做数据增强时如果你用from_numpy把图像数组转成 Tensor然后就地修改这个 Tensor原始 NumPy 里的图像数据也会被悄悄改掉这可能导致你的数据集被污染。下面的代码展示这个现象arr np.array([1.0, 2.0, 3.0]) tensor torch.from_numpy(arr) tensor[0] 999.0 print(arr) # [999. 2. 3.] 原数组被修改了如果不想共享内存老老实实用torch.tensor(arr)复制一份数据。这里复制开销虽然比from_numpy大但换来了数据安全尤其是在数据加载流水线里避免无意中改了原始数据。还有一个细节在 GPU 上运行的 Tensor 不能直接.numpy()因为 NumPy 只支持 CPU 数据。必须先.cpu()再.numpy()tensor_gpu torch.randn(3).to(cuda) arr tensor_gpu.cpu().numpy() # 先搬回CPU再转6.3 数据类型一致性的坑NumPy 转 Tensor 时数据格式必须提前对齐。比如你的标签数组是np.int64转成 Tensor 后就是torch.int64如果你的模型设定标签是int64交叉熵损失函数正好配套。但如果 NumPy 传进来的是int32而模型用int64接口就会报错。所以我习惯在预处理阶段显式指定 dtypearr arr.astype(np.float32) # 转成float32 tensor torch.from_numpy(arr) # 得到torch.float32的Tensor另外注意PyTorch 的float64即double在很多 GPU 算子上支持有限运算效率也低。如果发现你的 Tensor 总是torch.float64类型很可能是 NumPy 里的float64被带过来了在转 Tensor 前一定先转成float32。7. 张量的自动求导基础requires_grad与计算图7.1 叶子张量与requires_grad标记Tensor 和 NumPy 的最大差别就是自动求导。当你设置requires_gradTruePyTorch 就会开始记录这个张量上的所有操作构建一张计算图之后就可以自动反向传播计算梯度x torch.tensor([2.0], requires_gradTrue) y x ** 2 3 * x y.backward() print(x.grad) # tensor([7.]) 即 2*x 3 在 x2 时的导数这里x是叶子张量leaf tensor它没有依赖任何其他需要梯度的张量梯度最终就累积在这个叶子张量上。参数requires_grad的传播规则是如果一个 Tensor 需要梯度那么所有由它运算得到的 Tensor 也都需要梯度。7.2 计算图前向与反向的完整链路计算图是个有向无环图。在前向传播时数据从x经过x**2、3*x等操作流到y每个操作都会创建一个节点并记录实际数值调用backward()时PyTorch 从y出发利用链式法则把梯度逐层传回。举一个实际模型训练的例子import torch.nn as nn model nn.Linear(4, 1) optimizer torch.optim.SGD(model.parameters(), lr0.01) inputs torch.randn(8, 4) labels torch.randn(8, 1) outputs model(inputs) loss ((outputs - labels) ** 2).mean() optimizer.zero_grad() loss.backward() optimizer.step()这里的关键链条inputs - model - outputs - loss。loss.backward()执行完成后model里的参数weight和bias都累积了.grad梯度然后optimizer.step()根据这些梯度更新参数值。7.3 梯度累积与zero_grad的必要性上面代码中的optimizer.zero_grad()很多人不理解为什么每次训练都要调用。原因很简单PyTorch 的梯度是累积的你不清零下一次backward()得到的梯度会和上一次的叠加。如果不zero_grad()做一次彻底的清零模型参数更新会越走越偏loss 表现会异常发散。我在 Debug 时见过太多新手出现这种状况loss 一开始下降然后突然变大然后完全发散。检查半天发现是忘记zero_grad()梯度翻来覆去累加参数更新幅度爆炸。这个动作虽然不起眼却是训练稳定的基本保障。对于不需要计算梯度的场景用torch.no_grad()包住代码块则能显著提升推理速度并省去记录计算图的内存开销with torch.no_grad(): outputs model(inputs) # 这里不会构建计算图在验证集上评估模型、推理部署时这个上下文管理器几乎必用。8. 常见问题与排查技巧实录8.1 设备不一致Expected all tensors to be on the same device这个报错是新手出现频率最高的。大意是模型中某些参数在 GPU 上但你给的数据还在 CPU。解决思路很简单统一设备和风格device torch.device(cuda if torch.cuda.is_available() else cpu) model model.to(device) inputs inputs.to(device) labels labels.to(device)训练循环里每次拿到一个 batch第一行就写.to(device)形成肌肉记忆。千万不要混用.cuda()和.to(device)两种风格并存极易导致某些张量漏迁移。8.2 数据形状不匹配size mismatch报错矩阵乘法中维度不匹配会报size mismatch。排查思路很直接打印相关张量的shape检查维度口诀(..., M, K) (..., K, N)。特别注意转置后的形状变化x.T会交换最后两个维度这在矩阵乘法里很常见a torch.randn(3, 4) b torch.randn(3, 5) # 错误a b 不行因为 4 ! 3 # 正确a.T b形状变成 (4, 3) (3, 5) - (4, 5)8.3 dtype不匹配与自动类型提升的坑PyTorch 的默认规则是浮点数和整数运算会报错两个不同类型的浮点数运算可能触发自动提升类型。但神经网络大多数算子要求所有输入都是同一 dtype尤其是float32。如果报expected scalar type Float but found Long说明你传入了一个int64的张量给一个要求浮点数的算子。解决办法是在创建时指定dtypetorch.float32或者用.float()转换。8.4 梯度爆炸与梯度消失的早期信号训练时如果 loss 变成 NaN 或 inf大概率是梯度爆炸。先用下面几步定位打印每一层的权重梯度范数定位爆炸层检查学习率是否设置过大对输入做归一化数据标准化使用梯度裁剪torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm1.0)梯度消失则表现为训练很久 loss 不降这时检查是否用了不合适的激活函数或者网络初始化方式不佳。relu比sigmoid能更好缓解梯度消失权重初始化用 Kaiming 初始化而非默认的全 0 初始化。8.5 显存不足CUDA out of memory显存爆掉处理要讲究方法调小 batch_size第一步直接立竿见影确认没有把整个数据集一次性搬到 GPU训练结束时及时del大变量并调用torch.cuda.empty_cache()使用torch.no_grad()包裹验证代码如果验证集仍然爆显存可以把验证逻辑拆成多个小 batch这个问题的根源很多人都没意识到显存里存的不仅仅是你当前的 batch还有整个计算图的信息因为反向传播需要每个节点的中间激活值。模型越深、batch 越大缓存激活值占用显存就越多。9. 效率优化与实操心得9.1 多写向量化代码少写显式循环用循环逐步操作张量性能和代码优雅度都会输得很惨。以计算一组向量两两之间的欧氏距离为例# 不推荐双重循环 def pairwise_distance_loop(x): n x.shape[0] dist torch.zeros(n, n) for i in range(n): for j in range(n): dist[i, j] ((x[i] - x[j]) ** 2).sum() ** 0.5 return dist # 推荐利用广播和矩阵运算 def pairwise_distance_vec(x): # (n, d) - (n, 1, d) 广播到 (n, n, d) diff x[:, None, :] - x[None, :, :] dist (diff ** 2).sum(dim-1) ** 0.5 return dist显式循环在 CPU 上慢得让人无法忍受向量化之后可以利用底层的高效矩阵运算库并行计算快几个数量级。9.2 善用TorchScript和JIT提升推理性能如果你把模型的推理阶段用于生产可以考虑用torch.jit.script或torch.jit.trace把模型转为 TorchScript。Script 后的模型不依赖 Python 解释器可以序列化保存加载在 C 环境部署时非常有用。不过要注意脚本化之后部分动态控制流可能需要额外处理建议先用trace试试不行再上script。9.3 把关键检查写成断言在训练脚本开头和关键节点加上断言可以快速定位问题assert inputs.shape[1] 3, f期望3通道输入实际 {inputs.shape[1]} 通道 assert inputs.dtype torch.float32, f输入dtype为 {inputs.dtype}应为float32 assert labels.max() num_classes, 标签值超出类别数这些断言在初期调试期会帮你挡住大量隐蔽问题。等确定了流水线稳定可以在训练脚本里加一个--check参数只有开启时才断言避免生产推理时的额外开销。9.4 一个完整的Tensor练习Demo把所有知识点串起来这里给一个可以直接跑的练习脚本import torch import torch.nn as nn def demo(): # 1. 创建设备 device torch.device(cuda if torch.cuda.is_available() else cpu) # 2. 创建数据模拟一个batch的32张28x28灰度图 images torch.randn(32, 1, 28, 28).to(device) labels torch.randint(0, 10, (32,)).to(device) # 3. 标签做one-hot编码示例 one_hot torch.zeros(32, 10).scatter_(1, labels.unsqueeze(1), 1) # 4. 展平特征做线性分类 flat_images images.view(32, -1) linear nn.Linear(28 * 28, 10).to(device) outputs linear(flat_images) print(输出形状:, outputs.shape) # 5. 计算损失并反向传播 loss ((outputs - one_hot) ** 2).mean() loss.backward() print(梯度形状:, linear.weight.grad.shape) if __name__ __main__: demo()这个 demo 涵盖了设备迁移、形状变换、随机种子创建、one-hot 编码、线性层、损失计算和反向传播基本把 Tensor 的核心知识点全串起来了。10. 后续学习路线建议Tensor 的笔记到这里就告一段落。我个人觉得掌握 Tensor 有三个阶段可以自我对照第一阶段是会用能创建张量、改 shape、做运算遇到报错知道怎么回事。这个阶段基本熟悉了操作 API。第二阶段是懂内存知道哪些操作是视图、哪些是拷贝、怎么避免不必要的内存复制、广播操作的真正开销在哪里。这个阶段写出来的代码性能会有明显提升。第三阶段是懂计算图理解requires_grad传播、梯度累积、反向传播的链路。这个阶段就可以开始探索自定义autograd.Function、自定义复杂的训练逻辑。从 Tensor 到完整训练一个模型下一站通常是torch.nn.Module、数据集加载DataLoader、优化器选择、训练循环编写。建议学习路径是Tensor - 自动求导autograd-nn.Module搭建网络 - DataLoader 加载数据 - 训练循环 - 模型保存和加载。每一步都要亲手敲代码光看笔记永远学不会。我自己带过几个实习的同学他们进步最快的时候往往不是看资料的时候而是被 bug 卡住后自己翻文档排查的时候。最后分享一个实战中的小经验把常用的 Tensor 操作整理成自己的速查表写在一个笔记文件里遇到不确定的 API 先查自己的速查表比翻官方文档快很多。等用熟了你自然会形成一套自己的常用操作库。希望这篇 Tensor 学习笔记对你有帮助下一篇笔记我们聊自动求导把梯度传播的机制彻底讲透。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑