作为2016年由Facebook推出的开源机器学习库,PyTorch凭借其直观的接口设计和动态计算图特性,已经成为学术界和工业界最受欢迎的深度学习框架之一。我仍然记得第一次接触PyTorch时的惊喜——相比其他框架,它的API设计如此符合Python开发者的直觉,就像在使用NumPy但拥有了GPU加速和自动微分的能力。
对于初学者而言,PyTorch最大的优势在于它的即时执行模式(eager execution)。这意味着你可以像编写普通Python代码一样逐行执行和调试,每个操作的结果都能立即看到。这种交互式体验对于理解深度学习中的张量操作和模型训练流程特别有帮助。
提示:虽然TensorFlow 2.x也转向了即时执行模式,但PyTorch的Pythonic设计哲学让它对新手更加友好。你几乎不需要学习任何特殊的语法规则就能上手。
在开始之前,我们需要确保环境配置正确。PyTorch官方提供了非常方便的安装命令生成器。根据你的系统配置(是否有NVIDIA GPU),访问PyTorch官网选择对应的版本。对于大多数初学者,我推荐使用CPU版本开始学习:
bash复制pip install torch torchvision torchaudio
如果你有NVIDIA显卡并想利用GPU加速,需要先安装CUDA驱动,然后选择对应的PyTorch版本。不过要注意,GPU配置可能会遇到各种兼容性问题,建议初学者先从CPU版本开始。
在编写第一个模型前,我们需要掌握几个核心概念:
让我们通过一个简单的例子感受PyTorch的张量操作:
python复制import torch
# 创建一个2x3的随机张量
x = torch.rand(2, 3)
print(x)
# 张量加法
y = torch.ones(2, 3)
z = x + y
print(z)
# 转换到GPU(如果有)
if torch.cuda.is_available():
z = z.to('cuda')
我们将构建一个用于MNIST手写数字识别的简单网络。这个网络包含两个全连接层:
python复制import torch.nn as nn
import torch.nn.functional as F
class SimpleNN(nn.Module):
def __init__(self):
super(SimpleNN, self).__init__()
self.fc1 = nn.Linear(28*28, 128) # 输入层到隐藏层
self.fc2 = nn.Linear(128, 10) # 隐藏层到输出层
def forward(self, x):
x = x.view(-1, 28*28) # 展平输入图像
x = F.relu(self.fc1(x))
x = self.fc2(x)
return x
这个网络虽然简单,但包含了神经网络的关键要素:
nn.Module是所有神经网络模块的基类。当你创建自己的模型时,必须继承这个类并实现两个方法:
__init__:定义网络层和参数forward:定义数据如何通过网络注意:永远不要直接调用forward方法!应该使用
model(input)的方式调用,这样PyTorch能正确处理钩子和自动微分。
nn.Linear实现了一个全连接层,其参数包括:
PyTorch提供了torchvision包来处理常见视觉数据集。加载MNIST非常简单:
python复制from torchvision import datasets, transforms
# 定义数据转换
transform = transforms.Compose([
transforms.ToTensor(),
transforms.Normalize((0.1307,), (0.3081,))
])
# 下载并加载训练集和测试集
train_dataset = datasets.MNIST('./data',
train=True,
download=True,
transform=transform)
test_dataset = datasets.MNIST('./data',
train=False,
transform=transform)
# 创建数据加载器
train_loader = torch.utils.data.DataLoader(train_dataset,
batch_size=64,
shuffle=True)
test_loader = torch.utils.data.DataLoader(test_dataset,
batch_size=1000,
shuffle=True)
这里有几个关键点需要注意:
transforms.ToTensor()将PIL图像转换为PyTorch张量,并自动将像素值缩放到[0,1]范围transforms.Normalize对数据进行标准化处理,这里的参数是MNIST数据集的全局平均值和标准差DataLoader负责批量加载数据和打乱顺序,这对于训练稳定性很重要在开始训练前,检查一下数据总是一个好习惯:
python复制import matplotlib.pyplot as plt
# 获取一个批次的数据
images, labels = next(iter(train_loader))
# 显示图像
fig = plt.figure(figsize=(8, 8))
for i in range(9):
plt.subplot(3, 3, i+1)
plt.imshow(images[i][0], cmap='gray')
plt.title(f"Label: {labels[i]}")
plt.axis('off')
plt.show()
这个步骤能帮助你确认数据加载和预处理是否正确,也能直观感受你要解决的问题。
现在我们已经准备好模型和数据,可以开始训练了。训练神经网络通常需要以下几个组件:
python复制# 初始化模型、损失函数和优化器
model = SimpleNN()
criterion = nn.CrossEntropyLoss()
optimizer = torch.optim.SGD(model.parameters(), lr=0.01)
# 训练函数
def train(model, device, train_loader, optimizer, criterion, epoch):
model.train()
for batch_idx, (data, target) in enumerate(train_loader):
data, target = data.to(device), target.to(device)
optimizer.zero_grad()
output = model(data)
loss = criterion(output, target)
loss.backward()
optimizer.step()
if batch_idx % 100 == 0:
print(f'Train Epoch: {epoch} [{batch_idx * len(data)}/{len(train_loader.dataset)} '
f'({100. * batch_idx / len(train_loader):.0f}%)]\tLoss: {loss.item():.6f}')
让我们分解训练循环中的每个关键操作:
model.train():将模型设置为训练模式(影响某些层如Dropout和BatchNorm的行为)optimizer.zero_grad():清除之前的梯度(PyTorch会累积梯度)output = model(data):前向传播loss = criterion(output, target):计算损失loss.backward():反向传播计算梯度optimizer.step():更新模型参数重要提示:忘记调用
optimizer.zero_grad()是初学者最常见的错误之一,这会导致梯度累积,使训练不稳定。
为了监控模型在未见数据上的表现,我们需要添加验证步骤:
python复制def test(model, device, test_loader, criterion):
model.eval()
test_loss = 0
correct = 0
with torch.no_grad():
for data, target in test_loader:
data, target = data.to(device), target.to(device)
output = model(data)
test_loss += criterion(output, target).item()
pred = output.argmax(dim=1, keepdim=True)
correct += pred.eq(target.view_as(pred)).sum().item()
test_loss /= len(test_loader.dataset)
accuracy = 100. * correct / len(test_loader.dataset)
print(f'\nTest set: Average loss: {test_loss:.4f}, Accuracy: {correct}/{len(test_loader.dataset)} ({accuracy:.0f}%)\n')
return accuracy
注意这里使用了model.eval()和torch.no_grad():
model.eval():将模型设置为评估模式torch.no_grad():禁用梯度计算,节省内存和计算资源现在我们可以将训练和测试组合起来:
python复制device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
model = SimpleNN().to(device)
for epoch in range(1, 11):
train(model, device, train_loader, optimizer, criterion, epoch)
test(model, device, test_loader, criterion)
这个简单的网络在MNIST上通常能达到约97%的准确率。虽然不算很高,但对于理解基本流程已经足够。
学习率是最重要的超参数之一。让我们尝试不同的值:
python复制learning_rates = [0.1, 0.01, 0.001, 0.0001]
results = {}
for lr in learning_rates:
print(f"\nTraining with learning rate: {lr}")
model = SimpleNN().to(device)
optimizer = torch.optim.SGD(model.parameters(), lr=lr)
for epoch in range(1, 6): # 缩短训练轮次
train(model, device, train_loader, optimizer, criterion, epoch)
acc = test(model, device, test_loader, criterion)
results[lr] = acc
通过这个实验,你会发现学习率太大(如0.1)会导致训练不稳定,太小(如0.0001)则收敛太慢。0.01通常是一个不错的起点。
除了SGD,PyTorch还提供了多种优化器。让我们试试Adam:
python复制model = SimpleNN().to(device)
optimizer = torch.optim.Adam(model.parameters(), lr=0.001)
for epoch in range(1, 11):
train(model, device, train_loader, optimizer, criterion, epoch)
test(model, device, test_loader, criterion)
Adam优化器通常对学习率不那么敏感,在很多情况下表现良好。不过对于简单问题,SGD有时也能取得不错的效果。
训练好的模型可以保存下来供以后使用:
python复制# 保存模型
torch.save(model.state_dict(), 'mnist_model.pth')
# 加载模型
loaded_model = SimpleNN().to(device)
loaded_model.load_state_dict(torch.load('mnist_model.pth'))
loaded_model.eval()
state_dict包含了模型的所有参数。保存和加载时要注意:
model.eval()如果用于推理如果发现损失不下降或变成NaN,可能是梯度问题。可以:
torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm)如果训练准确率很高但测试准确率低,可能是过拟合。解决方法包括:
遇到CUDA out of memory错误时,可以尝试:
torch.cuda.empty_cache()完成这个基础教程后,你可以继续探索:
记住,深度学习是一个实践性很强的领域。我建议你多动手实验,修改网络结构,调整超参数,观察这些变化如何影响模型性能。这是真正理解和掌握PyTorch的最佳方式。