1. 为什么要从 Tensor 开始认识 PyTorch
1.1 Tensor 是什么
如果你接触过深度学习,大概率听过这样一句话:PyTorch 的一切操作都是围绕 Tensor(张量)展开的。
Tensor 的中文翻译是“张量”。用一句话概括:Tensor 是 PyTorch 中最基础的数据结构,它用来存放数据,既可以是标量、向量、矩阵,也可以是更高维度的数组。你可以把 Tensor 简单理解为一个“支持 GPU 加速、能自动记录梯度、专门为深度学习设计的 NumPy 数组”。
举个例子:
- 单个数字是 0 维张量(标量);
- 一列数字是 1 维张量(向量);
- 一个二维表格是 2 维张量(矩阵);
- 一张彩色图片通常用 3 维张量表示,比如
(通道数, 高度, 宽度); - 一批彩色图片则是 4 维张量,比如
(批量大小, 通道数, 高度, 宽度)。
在 PyTorch 中,输入数据、网络权重、中间特征、梯度信息,全部以 Tensor 的形式存储和流转。所以,不管以后你是做 CV、NLP、推荐系统还是强化学习,Tensor 都是绕不开的第一课。
1.2 Tensor 与 NumPy 数组、Python 列表的区别
很多刚入门的读者会问:Python 有 list,NumPy 有 ndarray,为什么 PyTorch 还要设计一个 Tensor?
区别主要体现在三个层面:
- 设备支持:NumPy 数组只能跑在 CPU 上,Tensor 可以轻松地在 CPU 和 GPU 之间迁移。深度学习训练时数据量巨大,GPU 并行计算能带来几十倍的加速,这是 NumPy 无法直接提供的。
- 自动求导:Tensor 有一个关键属性
requires_grad,打开后 PyTorch 会自动记录张量上的所有运算,并在反向传播时自动计算梯度。NumPy 没有这个能力。 - 深度集成:PyTorch 的神经网络层(如
nn.Linear、nn.Conv2d)输入输出都要求是 Tensor,整个生态都以 Tensor 为统一接口。
当然,Tensor 也保留了 NumPy 的很多优点,比如支持向量化运算、广播机制、切片索引等。掌握 Tensor 之后,你会发现在处理数据时比直接用 Python 列表高效得多。
1.3 本文你能学到什么
本文是 PyTorch 系列教程的第 1 课,内容保持“概念 + 代码 + 经验”的结构,读完你应该掌握:
- Tensor 是什么,适合什么场景;
- 如何在自己的电脑上搭建 PyTorch 运行环境;
- 创建 Tensor 的 6 种常用方式;
- Tensor 的属性如何查看和使用;
- Tensor 的索引、切片、形状变换、数学运算等基本操作;
- 一个包含数据生成、预处理、特征构建的完整小案例;
- 新手最容易踩的坑和工程实践建议。
接下来,我们先把环境准备好,然后用大量可以直接复制的代码带你一步步认识 Tensor。
2. 环境准备:搭建 PyTorch 运行环境
2.1 安装方式选择
PyTorch 的安装方式很灵活,你可以根据习惯选择:
- 使用 conda / miniforge 创建虚拟环境(推荐);
- 直接使用 pip 安装;
- 使用 Docker 镜像。
对于初学者,我最推荐用虚拟环境来隔离项目依赖。因为深度学习项目往往需要不同的 PyTorch 版本和 Python 版本,如果全部装在系统 Python 里,很容易出现依赖冲突。
如果你使用的是 Anaconda 或 Miniforge,可以用下面的命令创建环境:
conda create -n pytorch_env python=3.10 -y conda activate pytorch_env这里以 Python 3.10 为例,具体版本可以根据你的项目需求调整。PyTorch 对 Python 版本的兼容性比较好,3.8 到 3.12 都能覆盖大部分稳定版本。
2.2 安装 PyTorch
安装 PyTorch 最核心的一点是:一定要到 PyTorch 官网的安装页生成你的专属命令,因为官网会根据你的操作系统、包管理器、CUDA 版本实时给出推荐命令。
需要注意,PyTorch 安装命令中的 CUDA 版本和你电脑实际安装的显卡驱动不是一回事。简单理解:
- 如果你有 NVIDIA 显卡并希望用 GPU 训练,需要安装对应 CUDA 版本的 PyTorch;
- 如果你没有 NVIDIA 显卡,或者暂时只做 CPU 学习,选择 CPU 版本即可;
- AMD 显卡和 Apple Silicon 芯片也有对应的支持方式,同样以官网获取的安装命令为准。
下面给出的是一个没有 CUDA 的 CPU 版安装示例:
pip install torch torchvision torchaudio如果你需要用 GPU,推荐到官网安装页选择自己的环境后复制命令,这样可以避免版本不匹配。比如选择 Linux + pip + CUDA 12.1 时,命令会类似于:
pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu121由于 PyTorch 版本更新较快,且不同版本对应的 wheel 链接可能变化,不建议硬记命令,每次安装都以官网生成的结果为准。
2.3 验证安装是否成功
在终端激活环境并启动 Python 交互界面,输入以下代码:
import torch print(torch.__version__) print(torch.cuda.is_available())如果输出类似下面的内容,说明 PyTorch 已经安装成功:
2.3.1 Falsetorch.__version__输出版本号,torch.cuda.is_available()返回True表示当前环境可以使用 GPU,返回False表示当前是 CPU 环境或 CUDA 配置不对。
注意:就算输出False也不要着急,CPU 环境完全够用来学习本文的 Tensor 操作。真正需要 GPU 的时候,再回头检查驱动和 CUDA 配置即可。
3. 创建 Tensor 的六种核心方式
创建 Tensor 是 PyTorch 中最频繁的操作之一。下面我按使用频率从高到低,把常用的创建方法完整梳理一遍。
3.1 从 Python 列表或 NumPy 数组创建
最直接的方式是用torch.tensor()把已有的 Python 列表转换成一个 Tensor:
import torch # 从列表创建 a = torch.tensor([1, 2, 3]) print(a) # 从嵌套列表创建矩阵 b = torch.tensor([[1, 2], [3, 4]]) print(b) # 指定数据类型 c = torch.tensor([1.2, 3.4], dtype=torch.float32) print(c, c.dtype)输出结果:
tensor([1, 2, 3]) tensor([[1, 2], [3, 4]]) tensor([1.2000, 3.4000]) torch.float32需要注意,torch.tensor()会复制数据,生成的新 Tensor 和原始数据不再共享内存。如果你希望 Tensor 和 NumPy 数组共享内存,可以用torch.from_numpy():
import numpy as np arr = np.array([1, 2, 3]) t = torch.from_numpy(arr) t[0] = 100 print(arr) # 输出 [100 2 3],arr 被同步修改了这个特性在数据处理时很有用,可以避免不必要的内存拷贝,但也容易引起“数据被偷偷改了”的困惑,需要留心。
3.2 全 0、全 1 与单位矩阵
在深度学习中,初始化权重、创建掩码、填充数据时经常需要全 0 或全 1 的矩阵。
# 全 0 矩阵 zeros = torch.zeros(2, 3) print(zeros) # 全 1 矩阵 ones = torch.ones(3, 2) print(ones) # 单位矩阵 eye = torch.eye(3) print(eye)输出结果:
tensor([[0., 0., 0.], [0., 0., 0.]]) tensor([[1., 1.], [1., 1.], [1., 1.]]) tensor([[1., 0., 0.], [0., 1., 0.], [0., 0., 1.]])torch.zeros和torch.ones的第一个参数是形状shape,可以传一个元组,也可以传多个整数作为维度的尺寸。torch.eye只接受一个参数 n,生成 n 行 n 列的单位矩阵。
3.3 随机张量:均匀分布与正态分布
随机初始化是神经网络训练的基础操作,PyTorch 提供了多种随机张量生成函数。
# 均匀分布 [0, 1) uniform = torch.rand(2, 3) print(uniform) # 标准正态分布 normal = torch.randn(2, 3) print(normal) # 指定上下限的均匀分布 rand_int = torch.randint(0, 10, (3, 3)) print(rand_int)输出结果(每次运行会略有不同):
tensor([[0.2338, 0.6932, 0.9821], [0.7621, 0.1348, 0.8755]]) tensor([[ 0.3904, -0.1280, 1.0035], [ 0.0231, -0.8912, 0.5761]]) tensor([[3, 7, 0], [9, 2, 5], [4, 8, 1]])几个函数的记忆方法:
torch.rand:random 的缩写,生成 0 到 1 之间的均匀随机数;torch.randn:normal 的缩写,生成均值为 0、方差为 1 的标准正态分布随机数;torch.randint:生成指定范围内的随机整数。
随机种子在机器学习实验中非常重要,后面第 8 节我会专门说如何设置随机种子保证结果可复现。
3.4 等差数列与等间隔序列
生成序列数据时,torch.arange和torch.linspace非常常用。
# 类似 range,生成等差数列 a = torch.arange(0, 10, 2) print(a) # 在 [0, 1] 区间生成 5 个等间隔数 b = torch.linspace(0, 1, 5) print(b)输出结果:
tensor([0, 2, 4, 6, 8]) tensor([0.0000, 0.2500, 0.5000, 0.7500, 1.0000])torch.arange(start, end, step)生成从start开始,到end结束(不包含)的等差数列,步长为step。
torch.linspace(start, end, steps)则在start和end之间均匀生成steps个数,注意这里包含end本身。
两者最直观的区别是:arange关注步长,linspace关注数量。
3.5 指定形状与填充值
有时候需要把一个固定值填充到指定形状的张量中,比如初始化偏置为 0 或者常数。
# 填充 7 full = torch.full((2, 3), 7) print(full) # 生成一个未初始化的张量,内容为内存中的随机值 empty = torch.empty(2, 2) print(empty)输出结果:
tensor([[7, 7, 7], [7, 7, 7]]) tensor([[6.5738e-33, 4.5746e-41], [0.0000e+00, 0.0000e+00]])torch.full的用法很直观:第一个参数是形状,第二个参数是填充值。
torch.empty不会对内存做初始化,所以它的默认内容是什么都不确定。在实际项目中,empty往往用于后续马上被覆盖赋值的情景,避免多余的初始化开销。初学者不建议频繁使用,容易产生莫名其妙的数值问题。
3.6 创建张量时的设备与梯度设置
上面创建的 Tensor 默认都在 CPU 上,且关闭了梯度记录。但为了后续训练模型,我们需要掌握两个额外参数。
# 指定设备为 GPU(如果可用) device = torch.device("cuda" if torch.cuda.is_available() else "cpu") a = torch.tensor([1, 2, 3], device=device) print(a.device) # 开启梯度记录 b = torch.tensor([1.0, 2.0], requires_grad=True) print(b.requires_grad)输出结果:
cuda:0 Truedevice决定数据在哪个设备上存储和计算,requires_grad决定是否在后续运算中自动构建计算图。这两者会在训练神经网络时频繁出现,现在先建立概念即可。
4. Tensor 的核心属性详解
创建好 Tensor 之后,我们需要学会查看它的各种属性,否则后续处理数据时会一头雾水。
4.1 shape:形状
shape是 Tensor 最常用的属性,表示每个维度的大小。
x = torch.rand(2, 3, 4) print(x.shape)输出结果:
torch.Size([2, 3, 4])这里的torch.Size([2, 3, 4])本质是一个元组,表示这个 Tensor 是 3 维的,第一维有 2 个元素,第二维有 3 个元素,第三维有 4 个元素。
在深度学习框架中,我们经常会看到一个叫“维度”的概念。比如:
- 全连接层的输入通常是 2 维张量:
(batch_size, feature_dim); - 卷积层的输入通常是 4 维张量:
(batch_size, channel, height, width); - Transformer 的输入通常是 3 维张量:
(batch_size, seq_len, hidden_dim)。
理解shape是理解一切张量操作的基础。
4.2 ndim、size()、numel()
除了shape,还有几个高频属性需要掌握。
x = torch.rand(2, 3, 4) print(x.ndim) # 维度数量,输出 3 print(x.size()) # 等价于 x.shape print(x.size(0)) # 第 0 维大小,输出 2 print(x.numel()) # 元素总数 2*3*4 = 24输出结果:
3 torch.Size([2, 3, 4]) 2 24ndim返回维度数,你可以把它理解为“这个张量有几层方括号”;size()和shape等价;numel()返回所有元素的数量,常用于统计参数量或计算损失时进行归一化。
4.3 dtype:数据类型
dtype表示 Tensor 中元素的类型。常见的类型包括:
torch.float32(默认浮点类型,深度学习中最常用)torch.float64(双精度浮点,精度高但更慢、更占内存)torch.int32、torch.int64(整数类型)torch.bool(布尔类型)
a = torch.tensor([1, 2, 3]) b = torch.tensor([1.0, 2.0, 3.0]) print(a.dtype) # torch.int64 print(b.dtype) # torch.float32PyTorch 的默认整数类型是int64,默认浮点类型是float32。这也是深度学习中最常见的配置。GPU 对float32的优化最好,内存占用也更小。
新手经常犯的一个错误是:把整型 Tensor 和浮点型 Tensor 混在一起计算,导致类型不匹配报错。建议在创建 Tensor 时就显式指定dtype,或者通过.float()、.int()、.long()等方法转换。
4.4 device:设备
device表示 Tensor 存储在 CPU 还是 GPU 上。
cpu_tensor = torch.rand(2, 2) gpu_tensor = cpu_tensor.to("cuda") # 如果 GPU 可用 print(cpu_tensor.device) # cpu print(gpu_tensor.device) # cuda:0在训练过程中,最常见的报错之一就是“device mismatch”:一个 Tensor 在 CPU 上,另一个在 GPU 上,直接运算就会报错。后面排错章节会更详细展开。
4.5 requires_grad:自动梯度开关
requires_grad是 PyTorch 自动求导的开关。当它为True时,PyTorch 会记录这个张量上的所有操作,并构建一个计算图,之后可以通过反向传播计算梯度。
x = torch.tensor([2.0], requires_grad=True) y = x ** 2 y.backward() print(x.grad) # tensor([4.])上面代码中,y = x^2,对x求导结果是2x = 4,PyTorch 正确计算出了梯度。
这里我们只需要记住:训练神经网络时,网络权重.weight和.bias默认requires_grad=True;普通数据默认是False,不需要手动修改,除非你要实现自定义梯度逻辑。
4.6 stride 与内存布局(进阶)
stride是 Tensor 内存布局的重要概念,它表示在每个维度上,移动到下一个元素需要跨越的内存步长。
x = torch.rand(3, 4) print(x.stride()) # (4, 1)对于这样连续的二维张量,第 0 维的步长是 4(即跳过一行 4 个元素),第 1 维的步长是 1。
stride对张量性能影响很大,因为非连续张量在运算时可能触发额外的拷贝。不过对初学者来说,更重要的是知道transpose、permute等操作会改变内存布局,导致张量变成“非连续”状态,如果后面要强制改变形状,需要用contiguous()先转回连续内存布局。
5. Tensor 的基本操作入门
5.1 索引与切片
Tensor 的索引和切片与 NumPy 非常像,甚至和 Python 列表也有相似之处。
x = torch.arange(12).reshape(3, 4) print(x)输出结果:
tensor([[ 0, 1, 2, 3], [ 4, 5, 6, 7], [ 8, 9, 10, 11]])接下来看几个常用操作:
print(x[0]) # 第 0 行 print(x[:, 1]) # 所有行的第 1 列 print(x[1:, :2]) # 从第 1 行开始,前 2 列 print(x[-1]) # 最后一行输出结果:
tensor([0, 1, 2, 3]) tensor([1, 5, 9]) tensor([[4, 5], [8, 9]]) tensor([ 8, 9, 10, 11])切片返回的是原始 Tensor 的视图,不是拷贝。也就是说,修改切片结果会同步影响原始数据。如果需要独立副本,要调用.clone()。
5.2 形状变换:view、reshape、transpose
形状变换在实际开发中非常高频,因为数据和网络层之间的形状常常需要对齐。
x = torch.arange(12) # view 改变形状 y = x.view(3, 4) print(y) # reshape 改变形状 z = x.reshape(2, 6) print(z)输出结果:
tensor([[ 0, 1, 2, 3], [ 4, 5, 6, 7], [ 8, 9, 10, 11]]) tensor([[ 0, 1, 2, 3, 4, 5], [ 6, 7, 8, 9, 10, 11]])view只适用于内存连续的张量,reshape更通用。如果view遇到不连续的张量会直接报错,此时可以先调用.contiguous()再view。在新手阶段,可以优先用reshape,减少踩坑。
再看一个转置操作:
x = torch.rand(2, 3) print(x.shape) # torch.Size([2, 3]) y = x.transpose(0, 1) print(y.shape) # torch.Size([3, 2])transpose(0, 1)交换第 0 维和第 1 维。需要注意,transpose得到的是视图,内存布局会变成非连续状态。如果之后要view改形状,需要先.contiguous()。
5.3 数学运算与广播机制
Tensor 支持所有常见数学运算,比如加减乘除、幂运算、指数和对数等。
a = torch.tensor([1, 2, 3], dtype=torch.float32) b = torch.tensor([4, 5, 6], dtype=torch.float32) print(a + b) # tensor([5., 7., 9.]) print(a - b) # tensor([-3., -3., -3.]) print(a * b) # tensor([ 4., 10., 18.]) print(a / b) # tensor([0.2500, 0.4000, 0.5000]) print(torch.pow(a, 2)) # tensor([1., 4., 9.]) print(torch.sqrt(a)) # tensor([1.0000, 1.4142, 1.7321])还有一个非常重要的机制叫“广播”(Broadcasting)。当两个张量形状不一致时,PyTorch 会尝试自动扩展维度使它们可以运算。
a = torch.tensor([1, 2, 3]) b = torch.tensor(10) print(a + b) # tensor([11, 12, 13])标量10被广播到和[1, 2, 3]相同形状后再相加。再举个矩阵和向量的例子:
m = torch.rand(2, 3) v = torch.tensor([1, 2, 3]) # m 的形状是 (2, 3),v 的形状是 (3,),可以广播 print(m + v)广播机制让代码更简洁,但也容易让新手误判形状。如果两个张量形状完全不兼容,PyTorch 会抛出 RuntimeError,此时优先去检查维度是否对齐。
5.4 拼接与拆分
在处理批量数据或合并特征时,拼接操作非常常见。
a = torch.tensor([[1, 2], [3, 4]]) b = torch.tensor([[5, 6], [7, 8]]) # 按第 0 维拼接,相当于上下拼接 c = torch.cat([a, b], dim=0) print(c.shape) # torch.Size([4, 2]) # 按第 1 维拼接,相当于左右拼接 d = torch.cat([a, b], dim=1) print(d.shape) # torch.Size([2, 4])torch.stack和cat不同,它会创建一个新的维度:
e = torch.stack([a, b], dim=0) print(e.shape) # torch.Size([2, 2, 2])cat拼接时不会增加维度,stack会在指定位置新增一个维度。在实际项目里,cat用于合并特征,stack用于把多个样本堆叠成批次。
拆分操作可以使用torch.chunk或torch.split:
x = torch.rand(4, 6) # 平均分成 2 块 chunks = torch.chunk(x, chunks=2, dim=0) print(len(chunks)) # 2 # 按指定大小切分 splits = torch.split(x, split_size_or_sections=2, dim=1) print(len(splits)) # 35.5 与 NumPy 互转及深浅拷贝
PyTorch 和 NumPy 的互转非常方便。
import numpy as np # Tensor 转 NumPy t = torch.tensor([1, 2, 3]) arr = t.numpy() print(arr, arr.dtype) # NumPy 转 Tensor arr2 = np.array([4, 5, 6]) t2 = torch.from_numpy(arr2) print(t2)输出结果:
[1 2 3] int64 tensor([4, 5, 6])需要特别注意的是:t.numpy()和torch.from_numpy()生成的转换结果,默认与原始对象共享内存。如果修改其中一个,另一个也会变化。如果希望完全独立,需要用.clone()或.copy()。
t = torch.tensor([1, 2, 3]) arr = t.numpy() arr[0] = 100 print(t) # tensor([100, 2, 3])6. 实战:从数据到特征的完整 Tensor 流程
6.1 场景描述
学完基本操作后,我们通过一个小案例把它们串起来。
假设我们有一个身高和体重的数据列表,需要计算 BMI 指数,然后把数据归一化到 0 到 1 之间,最后从张量中筛选出 BMI 正常范围(18.5 到 24.9)的样本,并转换成 NumPy 数组用于后续存储。
这个场景覆盖了:创建 Tensor、类型转换、数学运算、索引筛选、NumPy 互转等高频操作。
6.2 完整代码
import torch # 1. 原始数据:身高(m),体重(kg) heights = [1.65, 1.75, 1.80, 1.60, 1.70] weights = [55.0, 70.0, 82.0, 50.0, 68.0] # 2. 创建浮点 Tensor h = torch.tensor(heights, dtype=torch.float32) w = torch.tensor(weights, dtype=torch.float32) # 3. 计算 BMI = 体重 / 身高^2 bmi = w / (h ** 2) print("BMI:", bmi) # 4. 手动归一化: (x - min) / (max - min) bmi_min = bmi.min() bmi_max = bmi.max() bmi_norm = (bmi - bmi_min) / (bmi_max - bmi_min) print("归一化 BMI:", bmi_norm) # 5. 筛选 BMI 正常范围样本 mask = (bmi >= 18.5) & (bmi <= 24.9) print("正常掩码:", mask) normal_bmi = bmi[mask] print("正常 BMI 值:", normal_bmi) # 6. 转成 NumPy 数组 normal_bmi_np = normal_bmi.numpy() print("NumPy 结果:", normal_bmi_np)6.3 运行结果与说明
输出结果:
BMI: tensor([20.2020, 22.8571, 25.3086, 19.5312, 23.5294]) 归一化 BMI: tensor([0.1315, 0.6151, 1.0000, 0.0000, 0.7387]) 正常掩码: tensor([ True, True, False, True, True]) 正常 BMI 值: tensor([20.2020, 22.8571, 19.5312, 23.5294]) NumPy 结果: [20.20202 22.857143 19.53125 23.52941 ]这个例子虽然简单,但你已经用上了 Tensor 的创建、计算、统计、掩码索引和 NumPy 转换。在真实项目中,类似的数据预处理流程还会配合torch.utils.data.Dataset来完成,那是后续教程要讲的内容。
7. 常见问题与排查思路
刚接触 PyTorch,遇到报错是很正常的。把下面这些高频问题收藏起来,能帮你减少很多排查时间。
| 问题现象 | 常见原因 | 解决思路 |
|---|---|---|
RuntimeError: Expected object of scalar type Long but got scalar type Float | 数据类型不匹配,比如索引需要整数类型,你传了浮点类型 | 用.long()、.int()、.float()显式转换 |
RuntimeError: Expected all tensors to be on the same device | 一个 Tensor 在 CPU,另一个在 GPU,直接参与运算 | 将所有张量统一移动到同一设备,使用.to(device) |
RuntimeError: view size is not compatible with input tensor's size and stride | 对非连续张量使用view,且新形状与内存布局冲突 | 先调用.contiguous()再view,或直接改用reshape |
Tensor在训练过程中梯度不断累积 | 每个 batch 未清空梯度 | 在每轮反向传播前调用optimizer.zero_grad() |
.numpy()报错 | Tensor 的requires_grad=True,或当前在 GPU 上 | 先.detach().cpu()再转 NumPy |
| 改了切片结果后原始 Tensor 也变了 | 切片返回的是视图,不是副本 | 如果需要独立数据,使用.clone() |
CUDA out of memory | GPU 显存不足 | 减小 batch size、降低输入尺寸、清理不需要的中间变量、使用torch.no_grad()推理 |
下面重点说两个最常见的问题。
7.1 设备不一致报错
在 GPU 环境下,最常见的错误就是设备不一致:
# 模拟错误:一个张量在 CPU,一个在 GPU cpu_tensor = torch.rand(3, 3) gpu_tensor = torch.rand(3, 3, device="cuda") # 下面这行会报错 # result = cpu_tensor + gpu_tensor正确的做法是统一设备:
device = torch.device("cuda" if torch.cuda.is_available() else "cpu") cpu_tensor = cpu_tensor.to(device) result = cpu_tensor + gpu_tensor7.2 非连续张量的 view 报错
# 创建一个连续张量 x = torch.arange(12).reshape(3, 4) # 转置后变成非连续 y = x.transpose(0, 1) # 下面这行可能报错 # y.view(12) # 正确做法 y = y.contiguous() print(y.view(12))在编写模型时,transpose、permute、squeeze、unsqueeze都可能改变内存布局,遇到view报错时优先用.contiguous()修复。
8. 最佳实践与工程建议
8.1 明确 dtype 和 device
在创建 Tensor 时,尽量显式指定dtype和device。特别是在处理来自不同来源的数据时,类型和设备的隐式转换可能带来性能损耗或直接报错。
推荐统一使用浮点类型torch.float32,这是 PyTorch 在深度学习中默认且优化最好的类型。整数做索引时用torch.int64即可。
实际项目里建议封装一个设备工具函数:
import torch device = torch.device("cuda" if torch.cuda.is_available() else "cpu") def to_device(data): if isinstance(data, torch.Tensor): return data.to(device) return data8.2 正确处理梯度和原地操作
训练神经网络时,有几个习惯可以尽早养成:
- 每轮反向传播前调用
optimizer.zero_grad(),否则梯度会累加; - 推理阶段用
torch.no_grad()包裹,既省内存又提高速度; - 对不需要梯度的数据,可以设置
requires_grad=False,或者用.detach()把张量从计算图中分离。
另外,在开启requires_grad的 Tensor 上尽量避免使用原地操作,比如t += 1或t.add_(1)。因为原地操作会修改变量的值,而计算图记录的是操作历史,这可能导致梯度计算错误。用非原地写法t = t + 1更安全。
8.3 可复现性设置
深度学习实验非常依赖随机初始化。为了结果可复现,最好在脚本开头统一设置随机种子:
import random import numpy as np import torch def set_seed(seed=42): random.seed(seed) np.random.seed(seed) torch.manual_seed(seed) if torch.cuda.is_available(): torch.cuda.manual_seed_all(seed)注意:即使设置了随机种子,由于 GPU 并行计算本身存在不确定性,完全复现仍然有难度,但设置种子能大幅降低实验之间的随机差异。
8.4 内存管理与性能优化
对于大张量,有几个实用建议:
- 用
.clone()或tensor.copy_()时要注意是否真的需要副本,不必要的拷贝会浪费内存; - 大批量数据不要一次性全部加载到内存,推荐配合
torch.utils.data.DataLoader分批读取; - 中间变量如果不再使用,Python 的垃圾回收会处理,但在循环中显式删除大变量也能帮助释放显存;
- 如果显存不够,优先减小 batch size,而不是降低模型复杂度。
8.5 编写可读代码
Tensor 操作很容易写出一长串难以阅读的链式调用。推荐把复杂操作拆成小函数,并加上类型注释。比如:
def normalize_tensor(x: torch.Tensor) -> torch.Tensor: """将张量归一化到 [0, 1] 区间。""" return (x - x.min()) / (x.max() - x.min())这样既方便复用,也方便测试。
9. 下一步学习路线
到这里,第 1 课的内容就算完整结束了。回顾一下,你已经掌握了 PyTorch 中最核心的数据结构 Tensor:如何创建、如何查看属性、如何做基本操作,以及常见的坑和工程建议。
但 Tensor 只是 PyTorch 的起点。要真正开始训练神经网络,还有几个关键知识需要继续学习:
- 自动求导机制:
autograd是如何工作的,计算图是什么,梯度如何流动; torch.nn模块:如何搭建全连接网络、卷积网络、循环网络;- 数据集与数据加载:
Dataset、DataLoader、数据增强; - 训练循环:损失函数、优化器、评估指标、模型保存与加载;
- GPU 训练与分布式训练:多卡训练常用技巧。
如果你有自己的数据,建议先拿一批小数据,把“数据预处理 → 模型搭建 → 训练 → 评估”的完整流程跑通,再逐步加深理论。亲手敲代码比只看教程有效得多,遇到报错也可以去官方文档和社区搜索,结合本文第 7 节的排错思路,大部分问题都能很快定位。
后续我会继续更新这个 PyTorch 系列教程,下一课计划讲自动求导与线性回归实战,帮助你完全理解梯度下降是如何在 PyTorch 中自动完成的。如果你在跟着实操时遇到问题,欢迎在评论区留言,我会根据大家反馈调整后续内容的侧重点。