2、PyTorch框架入门:张量操作与自动求导、构建简单全连接网络、数据加载器与训练循环、模型保存与加载

说实话,很多初学者一上来就扎进Transformer、大模型,结果连梯度怎么回传都搞不清楚。我个人觉得,PyTorch就像一把瑞士军刀——你得先知道刀刃在哪,才能切菜。这一章,咱们就把PyTorch最核心的四个基本功打扎实。

2.1 张量操作:PyTorch的“数字积木”

张量是什么?说白了就是多维数组。标量是0维,向量是1维,矩阵是2维,再往上堆就是高维张量。我在项目中遇到过最典型的场景:图像数据进来是[batch, channel, height, width]的四维张量,你如果对维度操作不熟,后面reshape、permute的时候分分钟想砸键盘。

创建张量的几种姿势

import torch

# 从列表创建
a = torch.tensor([[1, 2], [3, 4]])
# 全零/全一
b = torch.zeros(3, 4)
c = torch.ones(2, 3)
# 随机初始化
d = torch.randn(2, 2)  # 标准正态分布
# 指定设备
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
e = torch.randn(2, 2, device=device)
我的习惯:创建张量时尽量显式指定dtype。默认是float32,但如果你做量化或混合精度训练,float16能省一半显存。我曾经因为忘记设dtype,一个模型在V100上直接OOM,排查了半天才发现是默认float32在作祟。

张量运算与广播机制

加减乘除这些基础运算我就不啰嗦了。重点说说广播——这是PyTorch里最容易踩坑的地方之一。

# 广播示例
x = torch.randn(3, 1)  # 形状 [3, 1]
y = torch.randn(1, 4)  # 形状 [1, 4]
z = x + y              # 结果形状 [3, 4]

为什么会这样?PyTorch会自动把维度为1的那一列“复制”成跟对方匹配的大小。但如果你不小心把形状搞成[3, 4][4, 3],它不会报错,而是给你一个莫名其妙的结果。嗯,这里要注意:广播是从最后一个维度开始匹配的

2.2 自动求导:反向传播的“自动驾驶”

我记得刚学深度学习那会儿,手动推导梯度公式写到崩溃。PyTorch的autograd模块就是来救命的。你只需要把张量的requires_grad设为True,它就会自动记录所有操作,然后调用.backward()就能拿到梯度。

x = torch.tensor([2.0], requires_grad=True)
y = x ** 2 + 3 * x + 1
y.backward()
print(x.grad)  # 输出: tensor([7.]) 因为 dy/dx = 2x + 3 = 7
避坑指南:我曾经在训练循环里忘记清零梯度,结果梯度不断累加,loss直接炸到NaN。记住:每次backward()之前,一定要调用optimizer.zero_grad()。这是新手最容易犯的错误,没有之一。

还有一个细节:with torch.no_grad()上下文管理器。在模型推理或计算验证集loss时,记得用这个包裹起来。它不会追踪梯度,能省显存还提速。你想想看,推理阶段你根本不需要梯度,何必让PyTorch白干活?

2.3 构建简单全连接网络

全连接网络,也叫多层感知机(MLP)。结构很简单:输入层 -> 隐藏层 -> 输出层,层与层之间全连接。咱们用nn.Module来定义,这是PyTorch里所有网络的基类。

import torch.nn as nn

class SimpleMLP(nn.Module):
    def __init__(self, input_dim, hidden_dim, output_dim):
        super().__init__()
        self.fc1 = nn.Linear(input_dim, hidden_dim)
        self.relu = nn.ReLU()
        self.fc2 = nn.Linear(hidden_dim, output_dim)
    
    def forward(self, x):
        x = self.fc1(x)
        x = self.relu(x)
        x = self.fc2(x)
        return x

# 实例化
model = SimpleMLP(784, 256, 10)
print(model)

我个人习惯把每一层都写在__init__里,forward只负责数据流。这样代码清晰,调试也方便。如果你用nn.Sequential,虽然写起来快,但想打印中间层的输出就麻烦了。

关键点:激活函数一定要加。没有激活函数的全连接层,堆再多也只是线性变换的叠加,跟单层没区别。ReLU是最常用的,但如果你遇到梯度消失,可以试试LeakyReLU或GELU。

2.4 数据加载器与训练循环

数据加载器DataLoader是PyTorch里被低估的神器。它帮你做三件事:打乱数据、分批次、多进程加载。我建议你永远不要手动写循环来切batch,直接用DataLoader,省心又高效。

from torch.utils.data import DataLoader, TensorDataset

# 模拟数据
X = torch.randn(1000, 784)
y = torch.randint(0, 10, (1000,))

dataset = TensorDataset(X, y)
dataloader = DataLoader(dataset, batch_size=32, shuffle=True)

# 训练循环
model = SimpleMLP(784, 256, 10)
criterion = nn.CrossEntropyLoss()
optimizer = torch.optim.Adam(model.parameters(), lr=0.001)

num_epochs = 10
for epoch in range(num_epochs):
    running_loss = 0.0
    for batch_x, batch_y in dataloader:
        # 前向传播
        outputs = model(batch_x)
        loss = criterion(outputs, batch_y)
        
        # 反向传播
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()
        
        running_loss += loss.item()
    
    print(f'Epoch [{epoch+1}/{num_epochs}], Loss: {running_loss/len(dataloader):.4f}')

这个循环模板,你以后写任何PyTorch项目都会用到。我建议你把它背下来,或者存成代码片段。注意loss.item()——它把张量转成Python标量,不然你会得到一个张量列表,打印出来全是tensor(0.1234),看着难受。

性能小技巧:如果你的数据在CPU上,模型在GPU上,记得用batch_x = batch_x.to(device)把数据搬过去。我见过有人忘了这一步,结果模型在GPU上算,数据在CPU上,PyTorch自动帮你搬,但每次迭代都搬一次,速度慢得离谱。

2.5 模型保存与加载

训练一个模型可能要几个小时甚至几天,你总不想每次重启都重头来吧?PyTorch提供了两种保存方式:保存整个模型,或者只保存状态字典。我强烈建议你只保存state_dict,原因有二:一是文件小,二是兼容性好——换了PyTorch版本也不容易报错。

# 保存
torch.save(model.state_dict(), 'model_weights.pth')

# 加载
model = SimpleMLP(784, 256, 10)
model.load_state_dict(torch.load('model_weights.pth'))
model.eval()  # 切换到评估模式
我曾经踩过的坑:加载模型后忘记调用model.eval()。如果你模型里有Dropout或BatchNorm,训练模式和评估模式的行为完全不同。不切换的话,推理结果会随机变化,排查起来特别痛苦。记住:训练用model.train(),推理用model.eval()

如果你要保存checkpoint以便恢复训练,建议把epoch、optimizer状态、loss都存下来:

checkpoint = {
    'epoch': epoch,
    'model_state_dict': model.state_dict(),
    'optimizer_state_dict': optimizer.state_dict(),
    'loss': loss,
}
torch.save(checkpoint, 'checkpoint.pth')

这样即使训练到一半断电了,你也能从断点处继续,不用从头再来。

本章知识体系

下面这张图把本章的核心逻辑串起来了。你可以把它当作一张“地图”,学完后再回来看,应该会有更清晰的认识。

PyTorch入门核心知识体系 张量操作 自动求导 全连接网络 数据加载器 创建 · 运算 · 广播 · 设备 requires_grad · backward · no_grad nn.Module · Linear · ReLU · forward Dataset · DataLoader · batch 训练循环 前向传播 反向传播 参数更新 模型保存与加载

从张量操作开始,到自动求导帮你算梯度,再到用nn.Module搭网络,最后用DataLoader和训练循环把整个流程跑通——每一步都环环相扣。模型保存与加载则是你辛苦训练的“保险柜”。

这一章的内容,说白了就是PyTorch的“四件套”。你把这四个点吃透了,后面学CNN、RNN、Transformer都会轻松很多。别急,慢慢来,代码多敲几遍,手感自然就有了。


公众号:蓝海资料掘金营,微信deep3321