4、PyTorch核心概念:张量(Tensor)的创建与运算、自动求导机制(Autograd)、GPU加速
好,咱们进入PyTorch最核心的部分。说实话,我刚开始接触PyTorch时,就被它的设计哲学吸引了——动态计算图,用起来特别顺手。这一章我会把张量、自动求导和GPU加速这三个基石讲透。你想想看,搞深度学习,本质上就是在跟这三样东西打交道。
4.1 张量(Tensor):深度学习的基本单元
张量是什么?说白了就是多维数组。0维是标量,1维是向量,2维是矩阵,3维以上统称张量。在PyTorch里,torch.Tensor就是一切的基础。
4.1.1 创建张量的几种方式
我个人习惯用这几种方式创建张量,你记一下:
import torch
# 从列表创建
t1 = torch.tensor([1, 2, 3])
print(t1) # tensor([1, 2, 3])
# 全零张量
t2 = torch.zeros(3, 4)
print(t2.shape) # torch.Size([3, 4])
# 随机张量
t3 = torch.randn(2, 3) # 标准正态分布
t4 = torch.rand(2, 3) # 均匀分布[0,1)
# 指定数据类型
t5 = torch.ones(2, 2, dtype=torch.float32)
t6 = torch.eye(3) # 单位矩阵
dtype。为什么?因为默认是 float32,但有时候你需要 float16 来节省显存。我在项目中遇到过因为类型不匹配导致的诡异bug,排查了半天才发现是精度问题。
4.1.2 张量的基本运算
运算这块,记住一个原则:逐元素运算和矩阵运算要分清楚。
a = torch.tensor([[1, 2], [3, 4]])
b = torch.tensor([[5, 6], [7, 8]])
# 逐元素运算
c = a + b # 加法
d = a * b # 逐元素乘法(不是矩阵乘法!)
e = torch.sigmoid(a) # 激活函数
# 矩阵运算
f = torch.mm(a, b) # 矩阵乘法
g = a @ b # 等价写法,更简洁
# 维度操作
h = a.view(4, 1) # 重塑形状
i = a.unsqueeze(0) # 增加维度
j = a.squeeze() # 压缩维度
嗯,这里要注意:view 和 reshape 的区别。我踩过这个坑——view 要求张量在内存中是连续的,而 reshape 不要求。如果你对非连续张量用了 view,会报错。所以我现在习惯用 reshape,省心。
4.2 自动求导机制(Autograd):反向传播的引擎
自动求导是PyTorch最牛的地方。你只需要定义前向计算,梯度自动帮你算好。为什么会这样?因为PyTorch会记录所有操作,构建一个计算图。
4.2.1 核心概念:requires_grad
只有设置了 requires_grad=True 的张量,才会被追踪计算历史。我刚开始学的时候,经常忘记设置这个,导致梯度为None。
x = torch.ones(2, 2, requires_grad=True)
y = x + 2
z = y * y * 3
out = z.mean()
# 反向传播
out.backward()
# 查看梯度
print(x.grad)
# tensor([[4.5000, 4.5000],
# [4.5000, 4.5000]])
backward() 只计算叶子节点(即你直接创建的张量)的梯度。中间变量的梯度默认不保留,除非你用 retain_graph=True。
4.2.2 计算图的动态特性
PyTorch的计算图是动态的。每次前向传播都会重新构建。这意味着你可以在每次迭代中改变网络结构。我在做循环神经网络时,这个特性帮了大忙——不同长度的序列可以共用同一个模型。
# 动态图的例子
for i in range(3):
if i % 2 == 0:
y = x * 2
else:
y = x + 2
y.backward()
print(x.grad)
x.grad.zero_() # 记得清零梯度!
backward(),梯度会累加。所以每次反向传播后,记得调用 optimizer.zero_grad() 或 x.grad.zero_()。
4.2.3 禁用梯度追踪
有些场景不需要梯度,比如模型推理或特征提取。用 torch.no_grad() 可以节省显存和加速。
# 模型推理时
model.eval()
with torch.no_grad():
predictions = model(test_data)
# 或者用装饰器
@torch.no_grad()
def evaluate(model, data):
return model(data)
4.3 GPU加速:让训练飞起来
没有GPU,深度学习寸步难行。PyTorch对GPU的支持非常优雅——一行代码就能把张量搬到GPU上。
4.3.1 检查GPU可用性
# 检查是否有GPU
if torch.cuda.is_available():
device = torch.device("cuda")
print(f"使用GPU: {torch.cuda.get_device_name(0)}")
else:
device = torch.device("cpu")
print("使用CPU")
# 查看GPU数量
print(f"GPU数量: {torch.cuda.device_count()}")
4.3.2 张量在CPU和GPU间迁移
# 创建张量
x = torch.randn(1000, 1000)
# 搬到GPU
x_gpu = x.to(device)
# 或者直接在GPU上创建
y = torch.randn(1000, 1000, device=device)
# 搬回CPU
x_cpu = x_gpu.cpu()
# 注意:不同设备上的张量不能直接运算
# z = x + y # 会报错!x在CPU,y在GPU
.to(device),那样会拖慢速度。我习惯在 DataLoader 的 collate_fn 中统一处理。
4.3.3 模型迁移与多GPU训练
# 定义模型
model = MyModel()
# 将模型搬到GPU
model = model.to(device)
# 多GPU训练(单机多卡)
if torch.cuda.device_count() > 1:
print(f"使用 {torch.cuda.device_count()} 个GPU")
model = torch.nn.DataParallel(model)
# 前向传播
inputs = inputs.to(device)
labels = labels.to(device)
outputs = model(inputs)
DataParallel 时,模型的参数名会多一个 module. 前缀。保存模型时,如果你用 model.state_dict(),加载时要注意处理这个前缀。我一般保存时去掉 module.,或者直接用 model.module.state_dict()。
4.4 实战小技巧:混合精度训练
最后分享一个我常用的技巧——混合精度训练。用 torch.cuda.amp 可以大幅提升训练速度,同时减少显存占用。
from torch.cuda.amp import autocast, GradScaler
scaler = GradScaler()
for data, labels in dataloader:
data, labels = data.to(device), labels.to(device)
optimizer.zero_grad()
# 自动混合精度
with autocast():
outputs = model(data)
loss = criterion(outputs, labels)
# 梯度缩放,防止下溢
scaler.scale(loss).backward()
scaler.step(optimizer)
scaler.update()
嗯,这一章的内容就到这里。张量是基础,自动求导是核心,GPU加速是保障。把这三点吃透了,后面的模型搭建就会顺畅很多。下一章我们开始搭建第一个真正的卷积神经网络。