刚接触PyTorch时,我被它的灵活性和直观性所吸引。作为一个从NumPy过渡到深度学习框架的开发者,PyTorch的张量操作和自动微分机制让我能够快速实现想法。这篇笔记将记录我从零开始学习PyTorch的过程,特别适合那些有Python基础但尚未深入深度学习框架的朋友。
PyTorch的核心优势在于它的动态计算图和Pythonic的设计哲学。与静态图框架不同,PyTorch允许你在运行时定义和修改计算流程,这为调试和实验提供了极大便利。在计算机视觉、自然语言处理等领域,PyTorch已经成为许多研究者和工程师的首选工具。
PyTorch的安装过程相当简单,官方提供了针对不同系统和硬件配置的安装命令。对于大多数用户,使用pip安装CPU版本即可开始学习:
bash复制pip install torch torchvision
安装完成后,可以通过以下代码验证是否成功:
python复制import torch
print(torch.__version__) # 应输出安装的版本号
print(torch.cuda.is_available()) # 检查CUDA是否可用
注意:如果计划使用GPU加速,需要确保系统已安装对应版本的CUDA驱动。初学者可以先从CPU版本开始,待基础概念掌握后再考虑GPU加速。
PyTorch的核心数据结构是张量(Tensor),可以看作是多维数组的扩展。与NumPy的ndarray类似,但增加了GPU加速和自动微分支持:
python复制# 创建张量的多种方式
x = torch.empty(5, 3) # 未初始化的5x3矩阵
y = torch.rand(5, 3) # 随机初始化的5x3矩阵
z = torch.zeros(5, 3, dtype=torch.long) # 全零长整型矩阵
# 从Python列表创建
data = [[1, 2], [3, 4]]
t = torch.tensor(data)
张量支持丰富的数学运算,语法与NumPy非常相似:
python复制x = torch.rand(5, 3)
y = torch.ones(5, 3)
z = x + y # 逐元素相加
m = torch.mm(x, y.t()) # 矩阵乘法
PyTorch的自动微分系统(autograd)是其核心特性之一。通过跟踪张量上的所有操作,它可以自动计算梯度:
python复制x = torch.ones(2, 2, requires_grad=True) # 启用梯度跟踪
y = x + 2
z = y * y * 3
out = z.mean()
out.backward() # 反向传播
print(x.grad) # 输出梯度
实操心得:requires_grad=True会显著增加内存消耗,在不需要计算梯度时应及时关闭。可以用x.detach()或with torch.no_grad():来暂时禁用梯度计算。
PyTorch提供了torch.nn模块来简化神经网络构建。下面是一个完整的全连接网络示例:
python复制import torch.nn as nn
import torch.nn.functional as F
class Net(nn.Module):
def __init__(self):
super(Net, self).__init__()
self.fc1 = nn.Linear(784, 128) # 输入层到隐藏层
self.fc2 = nn.Linear(128, 10) # 隐藏层到输出层
def forward(self, x):
x = x.view(-1, 784) # 展平输入
x = F.relu(self.fc1(x))
x = self.fc2(x)
return x
net = Net()
print(net)
训练神经网络需要定义损失函数和优化器:
python复制import torch.optim as optim
criterion = nn.CrossEntropyLoss() # 交叉熵损失
optimizer = optim.SGD(net.parameters(), lr=0.01) # 随机梯度下降
# 训练循环基本结构
for epoch in range(10): # 训练10轮
running_loss = 0.0
for i, data in enumerate(trainloader, 0):
inputs, labels = data
optimizer.zero_grad() # 梯度清零
outputs = net(inputs) # 前向传播
loss = criterion(outputs, labels) # 计算损失
loss.backward() # 反向传播
optimizer.step() # 更新参数
running_loss += loss.item()
print(f'Epoch {epoch+1}, loss: {running_loss/len(trainloader)}')
PyTorch的torch.utils.data模块提供了高效的数据加载工具:
python复制from torchvision import datasets, transforms
# 定义数据转换
transform = transforms.Compose([
transforms.ToTensor(),
transforms.Normalize((0.5,), (0.5,))
])
# 加载MNIST数据集
trainset = datasets.MNIST('data', train=True, download=True, transform=transform)
trainloader = torch.utils.data.DataLoader(trainset, batch_size=32, shuffle=True)
# 使用示例
dataiter = iter(trainloader)
images, labels = dataiter.next()
PyTorch中最常见的错误之一是维度不匹配。例如,当输入形状不符合网络预期时:
python复制# 假设网络期望输入是(batch, 784),但实际得到的是(batch, 28, 28)
# 解决方案:
x = x.view(-1, 784) # 展平操作
调试技巧:在forward()方法中添加print(x.shape)语句,跟踪数据流经各层时的形状变化。
对于深层网络,梯度可能变得极小(消失)或极大(爆炸):
python复制# 解决方案:
# 1. 使用适当的权重初始化
nn.init.xavier_uniform_(layer.weight)
# 2. 添加批归一化层
self.bn1 = nn.BatchNorm1d(128)
# 3. 使用梯度裁剪
torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0)
当模型太大或批量太大时,可能遇到CUDA内存不足错误:
python复制# 解决方案:
# 1. 减小批量大小
trainloader = DataLoader(..., batch_size=16) # 原为32
# 2. 使用梯度累积
for i, data in enumerate(trainloader):
...
loss.backward()
if (i+1) % 4 == 0: # 每4个batch更新一次
optimizer.step()
optimizer.zero_grad()
训练好的模型需要保存以备后续使用:
python复制# 保存整个模型
torch.save(net, 'model.pth')
# 仅保存模型参数(推荐方式)
torch.save(net.state_dict(), 'model_params.pth')
# 加载模型
net = Net() # 先实例化网络结构
net.load_state_dict(torch.load('model_params.pth'))
net.eval() # 设置为评估模式
注意事项:在加载模型前,必须确保网络结构与保存时完全一致。跨PyTorch版本加载模型时可能会遇到兼容性问题。
掌握了这些基础后,可以进一步探索:
在实际项目中,我发现PyTorch的灵活性既是优势也是挑战。它允许快速实验各种想法,但也要求开发者对底层原理有更深入的理解。建议初学者从简单模型开始,逐步增加复杂度,同时养成使用torch.utils.tensorboard记录训练过程的习惯,这对调试和分析模型行为非常有帮助。