从零开始:使用 PyTorch 构建深度学习网络
从零开始:使用 PyTorch 构建深度学习网络
目录
- PyTorch 简介
- 环境配置
- PyTorch 基础
- 构建神经网络
- 训练模型
- 评估与测试
- 案例实战:手写数字识别
- 进阶技巧
- 常见问题解答
PyTorch 简介
PyTorch 是一个开源的深度学习框架,由 Facebook(现在的 Meta)的人工智能研究团队开发。它以其动态计算图和简洁的 API 而受到广泛欢迎,尤其在学术研究领域。与其他深度学习框架相比,PyTorch 的特点是直观、灵活且易于调试。
为什么选择 PyTorch?
- Python 友好:PyTorch 的设计理念非常符合 Python 的编程风格,如果你熟悉 NumPy,学习 PyTorch 将会非常顺利。
- 动态计算图:不同于 TensorFlow 1.x 的静态图,PyTorch 使用动态计算图,这意味着你可以在运行时修改网络结构,使调试和实验更加方便。
- 强大的社区支持:庞大的用户基础和丰富的学习资源。
- 良好的生态系统:包括计算机视觉(torchvision)、自然语言处理(transformers)、音频处理(torchaudio)等领域的专用库。
- 现代化 API:PyTorch 2.0+ 提供了编译器优化、更好的性能和更简洁的 API。
环境配置
开始使用 PyTorch 前,需要先配置环境。以下是基本步骤:
安装 PyTorch
推荐使用 Anaconda 或 Miniconda 创建虚拟环境:
# 创建虚拟环境
conda create -n pytorch_env python=3.11
# 激活环境
conda activate pytorch_env
# 安装 PyTorch(CPU 版本)
conda install pytorch torchvision torchaudio cpuonly -c pytorch
如果你有 NVIDIA GPU,可以安装支持 CUDA 的版本:
# 安装支持 CUDA 的 PyTorch
conda install pytorch torchvision torchaudio pytorch-cuda=12.1 -c pytorch -c nvidia
你也可以使用 pip 安装最新版本:
# CPU 版本
pip install torch torchvision torchaudio# GPU 版本 (CUDA 12.1)
pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu121
验证安装
创建一个简单的 Python 脚本来验证 PyTorch 是否正确安装:
import torch# 打印 PyTorch 版本
print(f"PyTorch 版本: {torch.__version__}")# 检查 GPU 是否可用
print(f"GPU 是否可用: {torch.cuda.is_available()}")
if torch.cuda.is_available():print(f"GPU 名称: {torch.cuda.get_device_name(0)}")# 检查 MPS(适用于 Apple Silicon)
print(f"MPS 是否可用: {getattr(torch, 'has_mps', False)}")
PyTorch 基础
在构建神经网络前,先了解 PyTorch 的核心概念。
张量(Tensor)
张量是 PyTorch 中的基本数据结构,类似于 NumPy 的多维数组,但可以在 GPU 上运行计算。
import torch# 创建张量
x = torch.tensor([[1, 2], [3, 4]])
print(x)
print(f"Shape: {x.shape}")
print(f"Data type: {x.dtype}")# 创建特定类型的张量
x_float = torch.tensor([[1, 2], [3, 4]], dtype=torch.float32)
print(x_float)# 常用的张量创建函数
zeros = torch.zeros(2, 3)
ones = torch.ones(2, 3)
rand = torch.rand(2, 3) # 均匀分布 [0,1)
randn = torch.randn(2, 3) # 标准正态分布print(f"Zeros:\n{zeros}")
print(f"Ones:\n{ones}")
print(f"Random (uniform):\n{rand}")
print(f"Random (normal):\n{randn}")
张量运算
PyTorch 提供了丰富的张量运算函数:
import torch# 创建两个张量
a = torch.tensor([[1, 2], [3, 4]], dtype=torch.float32)
b = torch.tensor([[5, 6], [7, 8]], dtype=torch.float32)# 基本算术运算
print(f"a + b = \n{a + b}")
print(f"a - b = \n{a - b}")
print(f"a * b = \n{a * b}") # 元素级乘法
print(f"a / b = \n{a / b}")# 矩阵乘法
print(f"矩阵乘法 a @ b = \n{a @ b}")
# 或使用 torch.matmul
print(f"torch.matmul(a, b) = \n{torch.matmul(a, b)}")# 其他常用运算
print(f"Sum: {a.sum()}")
print(f"Mean: {a.mean()}")
print(f"Max: {a.max()}")
print(f"Min: {a.min()}")
自动微分
PyTorch 的核心功能之一是自动计算梯度,这是深度学习训练的基础:
import torch# 创建需要梯度的张量
x = torch.tensor([[1., 2.], [3., 4.]], requires_grad=True)
print(x)# 进行一些操作
y = x * 2
z = y.sum()
print(f"z = {z}")# 反向传播计算梯度
z.backward()# 查看 x 的梯度
print(f"梯度 dz/dx = \n{x.grad}") # 应该全是 2
构建神经网络
PyTorch 提供了 nn
模块,包含构建神经网络所需的各种组件。
线性层与激活函数
最基本的神经网络由线性层和激活函数组成:
import torch
import torch.nn as nn# 定义一个简单的全连接层
linear = nn.Linear(in_features=10, out_features=5)# 创建输入数据(批量大小为3,每个样本有10个特征)
x = torch.randn(3, 10)# 前向传播
output = linear(x)
print(f"输入形状: {x.shape}")
print(f"输出形状: {output.shape}")# 激活函数
relu = nn.ReLU()
sigmoid = nn.Sigmoid()
tanh = nn.Tanh()# 应用激活函数
print(f"ReLU: {relu(output)}")
print(f"Sigmoid: {sigmoid(output)}")
print(f"Tanh: {tanh(output)}")
定义神经网络类
在 PyTorch 中,神经网络通常通过继承 nn.Module
类来定义:
import torch
import torch.nn as nnclass SimpleNN(nn.Module):def __init__(self, input_size, hidden_size, output_size):super(SimpleNN, self).__init__()# 第一个全连接层self.fc1 = nn.Linear(input_size, hidden_size)# 激活函数self.relu = nn.ReLU()# 第二个全连接层self.fc2 = nn.Linear(hidden_size, output_size)def forward(self, x):# 定义前向传播过程x = self.fc1(x)x = self.relu(x)x = self.fc2(x)return x# 创建网络实例
input_size = 10
hidden_size = 20
output_size = 5
model = SimpleNN(input_size, hidden_size, output_size)# 打印模型结构
print(model)# 创建一个输入样本
x = torch.randn(1, input_size)
# 前向传播
output = model(x)
print(f"输出: {output}")
使用 PyTorch 2.x 的新特性
PyTorch 2.x 引入了一些强大的新特性,比如 torch.compile
,可以显著提高模型性能:
import torch
import torch.nn as nnclass ModernNN(nn.Module):def __init__(self, input_size, hidden_size, output_size):super().__init__()# 使用 Sequential 简化网络定义self.network = nn.Sequential(nn.Linear(input_size, hidden_size),nn.ReLU(),nn.Linear(hidden_size, hidden_size // 2),nn.ReLU(),nn.Linear(hidden_size // 2, output_size))def forward(self, x):return self.network(x)# 创建网络实例
model = ModernNN(input_size=10, hidden_size=32, output_size=5)# 使用 torch.compile 加速模型(PyTorch 2.0+ 新特性)
# 在生产环境运行速度提升可达 2x 或更多
if hasattr(torch, 'compile'):model = torch.compile(model)# 输入数据
x = torch.randn(100, 10) # 批量大小为100的输入# 使用 @torch.inference_mode 可以在推理时降低内存使用
@torch.inference_mode()
def predict(model, x):return model(x)# 调用推理函数
outputs = predict(model, x)
print(f"输出形状: {outputs.shape}")
训练模型
训练神经网络涉及多个步骤:数据准备、定义损失函数、优化器配置和训练循环。
数据加载与预处理
PyTorch 提供了 Dataset
和 DataLoader
类来简化数据处理:
import torch
from torch.utils.data import Dataset, DataLoader
import numpy as np# 创建一个简单的数据集类
class SimpleDataset(Dataset):def __init__(self, size, input_dim, output_dim):# 生成随机数据self.x = torch.randn(size, input_dim)# 生成随机标签(分类问题)self.y = torch.randint(0, output_dim, (size,))def __len__(self):return len(self.x)def __getitem__(self, idx):return self.x[idx], self.y[idx]# 创建数据集实例
dataset = SimpleDataset(size=1000, input_dim=10, output_dim=5)# 创建数据加载器
dataloader = DataLoader(dataset, batch_size=32, shuffle=True)# 查看一个批次的数据
for inputs, labels in dataloader:print(f"Inputs shape: {inputs.shape}")print(f"Labels shape: {labels.shape}")break # 只查看第一个批次
损失函数与优化器
选择合适的损失函数和优化器对模型训练至关重要:
import torch
import torch.nn as nn
import torch.optim as optim# 假设我们已经定义了模型
model = SimpleNN(input_size=10, hidden_size=20, output_size=5)# 定义损失函数(交叉熵损失,适用于分类问题)
criterion = nn.CrossEntropyLoss()# 定义优化器(随机梯度下降)
optimizer = optim.SGD(model.parameters(), lr=0.01)# 其他常用优化器
# Adam 优化器
optimizer_adam = optim.Adam(model.parameters(), lr=0.001)
# RMSprop 优化器
optimizer_rmsprop = optim.RMSprop(model.parameters(), lr=0.001)
训练循环
下面是一个完整的训练循环示例:
import torch
import torch.nn as nn
import torch.optim as optim
from torch.utils.data import DataLoader# 假设我们已经定义了模型、数据集和数据加载器
model = SimpleNN(input_size=10, hidden_size=20, output_size=5)
dataloader = DataLoader(dataset, batch_size=32, shuffle=True)# 定义损失函数和优化器
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters(), lr=0.001)# 训练参数
num_epochs = 10
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
model.to(device)# 训练循环
for epoch in range(num_epochs):running_loss = 0.0for inputs, labels in dataloader:# 将数据移到设备(CPU/GPU)inputs, labels = inputs.to(device), labels.to(device)# 清零梯度optimizer.zero_grad()# 前向传播outputs = model(inputs)# 计算损失loss = criterion(outputs, labels)# 反向传播loss.backward()# 更新参数optimizer.step()# 累加损失running_loss += loss.item()# 打印每个 epoch 的平均损失epoch_loss = running_loss / len(dataloader)print(f"Epoch {epoch+1}/{num_epochs}, Loss: {epoch_loss:.4f}")print("训练完成!")
评估与测试
训练后,需要评估模型在测试数据上的性能:
import torch# 切换到评估模式
model.eval()# 创建测试数据集和数据加载器
test_dataset = SimpleDataset(size=200, input_dim=10, output_dim=5)
test_loader = DataLoader(test_dataset, batch_size=32)# 在测试数据上评估
correct = 0
total = 0# 不计算梯度
with torch.no_grad():for inputs, labels in test_loader:inputs, labels = inputs.to(device), labels.to(device)# 前向传播outputs = model(inputs)# 获取预测结果_, predicted = torch.max(outputs.data, 1)# 统计正确预测的数量total += labels.size(0)correct += (predicted == labels).sum().item()# 计算准确率
accuracy = 100 * correct / total
print(f"测试集准确率: {accuracy:.2f}%")# 返回训练模式
model.train()
案例实战:手写数字识别
下面将使用 MNIST 数据集实现一个完整的手写数字识别模型,并应用 PyTorch 的现代特性。
导入必要的库
import torch
import torch.nn as nn
import torch.optim as optim
import torchvision
import torchvision.transforms as transforms
from torch.utils.data import DataLoader
import torch.nn.functional as F
from torch.cuda.amp import GradScaler, autocast # 用于混合精度训练
准备数据
# 数据预处理
transform = transforms.Compose([transforms.ToTensor(),transforms.Normalize((0.1307,), (0.3081,))
])# 下载 MNIST 数据集
train_dataset = torchvision.datasets.MNIST(root='./data', train=True, transform=transform,download=True
)test_dataset = torchvision.datasets.MNIST(root='./data', train=False, transform=transform,download=True
)# 创建数据加载器,使用新的 PyTorch 2.x 的 persistent workers 特性提高数据加载效率
train_loader = DataLoader(train_dataset, batch_size=128, # 更大的批次大小shuffle=True,num_workers=4, # 多进程加载persistent_workers=True, # 保持工作进程活跃,减少启动开销pin_memory=True # 使用固定内存提高GPU传输速度
)test_loader = DataLoader(test_dataset, batch_size=1000, shuffle=False,num_workers=4,persistent_workers=True,pin_memory=True
)
定义现代化的 CNN 模型
class ModernMNISTModel(nn.Module):def __init__(self):super().__init__()# 使用更现代的网络架构self.features = nn.Sequential(nn.Conv2d(1, 32, kernel_size=3, padding=1),nn.BatchNorm2d(32),nn.ReLU(inplace=True),nn.MaxPool2d(kernel_size=2, stride=2),nn.Conv2d(32, 64, kernel_size=3, padding=1),nn.BatchNorm2d(64),nn.ReLU(inplace=True),nn.MaxPool2d(kernel_size=2, stride=2),nn.Conv2d(64, 128, kernel_size=3, padding=1),nn.BatchNorm2d(128),nn.ReLU(inplace=True),nn.MaxPool2d(kernel_size=2, stride=2, padding=1) # 确保尺寸正确)self.classifier = nn.Sequential(nn.AdaptiveAvgPool2d((1, 1)), # 自适应池化到固定尺寸nn.Flatten(),nn.Linear(128, 64),nn.ReLU(inplace=True),nn.Dropout(0.5),nn.Linear(64, 10))def forward(self, x):x = self.features(x)x = self.classifier(x)return x# 创建模型实例
model = ModernMNISTModel()# 使用 torch.compile 编译模型(PyTorch 2.0+)
if hasattr(torch, 'compile'):model = torch.compile(model)
训练与评估完整流程(带混合精度训练)
# 定义损失函数和优化器
criterion = nn.CrossEntropyLoss()
optimizer = optim.AdamW(model.parameters(), lr=0.001, weight_decay=1e-4) # 使用 AdamW 替代 Adam
scheduler = torch.optim.lr_scheduler.OneCycleLR( # 使用 OneCycleLR 调度器optimizer, max_lr=0.005, epochs=10, steps_per_epoch=len(train_loader)
)# 设备配置
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
model.to(device)# 混合精度训练
scaler = GradScaler()# 训练参数
num_epochs = 10# 训练循环
for epoch in range(num_epochs):model.train() # 设置为训练模式running_loss = 0.0for i, (images, labels) in enumerate(train_loader):images, labels = images.to(device), labels.to(device)# 清零梯度optimizer.zero_grad()# 使用混合精度训练with autocast():# 前向传播outputs = model(images)loss = criterion(outputs, labels)# 反向传播和优化scaler.scale(loss).backward()scaler.step(optimizer)scaler.update()# 更新学习率scheduler.step()running_loss += loss.item()# 每100批次打印统计信息if (i+1) % 100 == 0:print(f'Epoch [{epoch+1}/{num_epochs}], Step [{i+1}/{len(train_loader)}], Loss: {loss.item():.4f}, LR: {scheduler.get_last_lr()[0]:.6f}')# 测试模型model.eval() # 设置为评估模式with torch.no_grad():correct = 0total = 0for images, labels in test_loader:images, labels = images.to(device), labels.to(device)# 使用 torch.inference_mode() 优化推理with torch.inference_mode():outputs = model(images)_, predicted = torch.max(outputs.data, 1)total += labels.size(0)correct += (predicted == labels).sum().item()print(f'Epoch [{epoch+1}/{num_epochs}], 测试准确率: {100 * correct / total:.2f}%')# 保存模型
torch.save({'model_state_dict': model.state_dict(),'optimizer_state_dict': optimizer.state_dict(),'epoch': num_epochs
}, 'mnist_model_modern.pth')print("模型已保存!")# 测试推理性能
import timemodel.eval()
test_images, test_labels = next(iter(test_loader))
test_images = test_images.to(device)# 预热
with torch.inference_mode():for _ in range(10):_ = model(test_images[:10])# 计时
start_time = time.time()
with torch.inference_mode():for _ in range(50):_ = model(test_images)
end_time = time.time()print(f"推理性能: {50 * len(test_images) / (end_time - start_time):.2f} 图像/秒")
进阶技巧
掌握了基础知识后,这里介绍一些提高模型性能的进阶技巧和 PyTorch 的现代特性。
学习率调整
随着训练的进行,适当调整学习率可以帮助模型更好地收敛:
from torch.optim.lr_scheduler import StepLR, ReduceLROnPlateau, CosineAnnealingLR# 每10个 epoch 将学习率乘以 gamma
scheduler = StepLR(optimizer, step_size=10, gamma=0.1)# 当验证损失不再下降时减小学习率
scheduler = ReduceLROnPlateau(optimizer, mode='min', factor=0.1, patience=5)# 余弦退火学习率(在深度学习中表现优异)
scheduler = CosineAnnealingLR(optimizer, T_max=10)# 在训练循环中使用
for epoch in range(num_epochs):# 训练代码...# 更新学习率(取决于scheduler类型)if isinstance(scheduler, ReduceLROnPlateau):scheduler.step(val_loss)else:scheduler.step()
早停法(Early Stopping)
当模型在验证集上的性能不再提升时,停止训练可以防止过拟合:
best_val_loss = float('inf')
patience = 5
counter = 0
best_model_path = 'best_model.pth'for epoch in range(num_epochs):# 训练代码...# 计算验证损失val_loss = validate(model, val_loader, criterion)# 更新最佳损失和检查早停if val_loss < best_val_loss:best_val_loss = val_losscounter = 0# 保存最佳模型torch.save({'model_state_dict': model.state_dict(),'optimizer_state_dict': optimizer.state_dict(),'epoch': epoch,'loss': val_loss,}, best_model_path)print(f"保存最佳模型,验证损失: {val_loss:.4f}")else:counter += 1if counter >= patience:print(f"Early stopping at epoch {epoch+1}")break
使用预训练模型与迁移学习
对于复杂任务,使用预训练模型可以大幅提高性能,PyTorch 2.x 提供了更简洁的 API:
import torch
import torchvision.models as models# 使用新的 API 加载预训练模型
# weights 参数代替了旧的 pretrained=True
resnet = models.resnet50(weights=models.ResNet50_Weights.IMAGENET1K_V2)# 冻结预训练层
for param in resnet.parameters():param.requires_grad = False# 替换最后的全连接层,适应新任务
num_features = resnet.fc.in_features
resnet.fc = torch.nn.Linear(num_features, num_classes) # 只训练这一层# 也可以使用 HuggingFace 获取最新的预训练模型
# pip install transformers
from transformers import AutoImageProcessor, AutoModelForImageClassification# 加载预训练的视觉模型
processor = AutoImageProcessor.from_pretrained("microsoft/resnet-50")
model = AutoModelForImageClassification.from_pretrained("microsoft/resnet-50")# 加载并预处理图像
from PIL import Image
import requestsurl = "http://images.cocodataset.org/val2017/000000039769.jpg"
image = Image.open(requests.get(url, stream=True).raw)
inputs = processor(images=image, return_tensors="pt")# 使用模型进行推理
with torch.no_grad():outputs = model(**inputs)logits = outputs.logits# 获取预测结果
predicted_class_idx = logits.argmax(-1).item()
print("预测类别:", model.config.id2label[predicted_class_idx])
PyTorch 2.x 的模型加速与部署
PyTorch 2.x 提供了多种模型优化和部署技术:
使用 TorchScript 和 torch.compile
import torch# 定义模型
model = YourModel()
model.eval()# 使用 torch.compile 加速(PyTorch 2.0+)
optimized_model = torch.compile(model, mode='reduce-overhead', # 可选模式: 'default', 'reduce-overhead', 'max-autotune'fullgraph=True
)# 或使用 TorchScript 导出模型
example_input = torch.randn(1, 3, 224, 224)
scripted_model = torch.jit.script(model)
# 或 traced_model = torch.jit.trace(model, example_input)# 保存模型用于部署
scripted_model.save("model_scripted.pt")
量化模型以减小尺寸和加速推理
import torch.quantization# 准备量化
model.eval()
model.qconfig = torch.quantization.get_default_qconfig('fbgemm')
torch.quantization.prepare(model, inplace=True)# 校准模型(需要代表性数据)
with torch.no_grad():for data, _ in calibration_dataloader:model(data)# 转换到量化模型
torch.quantization.convert(model, inplace=True)# 保存量化模型
torch.jit.save(torch.jit.script(model), "quantized_model.pt")
分布式训练
PyTorch 提供了多种分布式训练方法,包括数据并行和模型并行:
import torch.distributed as dist
import torch.multiprocessing as mp
from torch.nn.parallel import DistributedDataParallel as DDPdef setup(rank, world_size):# 初始化进程组dist.init_process_group("nccl", rank=rank, world_size=world_size)def train(rank, world_size):setup(rank, world_size)# 创建模型model = YourModel().to(rank)# 将模型包装为分布式模型ddp_model = DDP(model, device_ids=[rank])# 数据加载器需要修改为分布式sampler = torch.utils.data.distributed.DistributedSampler(dataset,num_replicas=world_size,rank=rank)dataloader = DataLoader(dataset, sampler=sampler, ...)# 训练循环for epoch in range(num_epochs):sampler.set_epoch(epoch) # 确保不同进程看到不同数据for data, target in dataloader:# 常规训练步骤...# 启动多进程训练
world_size = torch.cuda.device_count()
mp.spawn(train, args=(world_size,), nprocs=world_size, join=True)
使用现代 PyTorch 特性进行移动设备部署
PyTorch 提供了 TorchMobile 功能,可以将模型部署到移动设备:
import torch# 准备模型
model = YourModel()
model.eval()# 转换为 TorchScript
example_input = torch.rand(1, 3, 224, 224)
traced_model = torch.jit.trace(model, example_input)# 优化移动设备部署
traced_model_optimized = torch.utils.mobile_optimizer.optimize_for_mobile(traced_model)# 保存为移动格式
traced_model_optimized.save("model_mobile.pt")# 可以进一步转换为适用于更多平台的格式,如 ONNX
torch.onnx.export(model,example_input,"model.onnx",export_params=True,opset_version=12,do_constant_folding=True,input_names=['input'],output_names=['output'],dynamic_axes={'input': {0: 'batch_size'},'output': {0: 'batch_size'}}
)
常见问题解答
如何处理过拟合?
过拟合是指模型在训练数据上表现良好,但在测试数据上表现不佳。解决方法包括:
- 增加数据量:更多样本通常会降低过拟合风险
- 数据增强:通过旋转、缩放等变换增加训练数据的多样性
- 正则化:使用 L1、L2 正则化或 Dropout
- 简化模型:减少模型的复杂度(层数或参数数量)
示例代码(添加 Dropout 和 权重衰减):
# 添加 Dropout
self.dropout = nn.Dropout(0.5) # 在网络中间层添加# 添加权重衰减(L2正则化)
optimizer = optim.Adam(model.parameters(), lr=0.001, weight_decay=1e-4)
GPU 内存不足怎么办?
当模型或数据集过大,GPU 内存不足时:
- 减小批量大小:这是最直接的方法
- 使用梯度累积:累积多个小批量的梯度后再更新参数
- 混合精度训练:使用 float16 代替 float32 进行部分计算
梯度累积示例:
accumulation_steps = 4 # 累积4个批次
optimizer.zero_grad()for i, (inputs, labels) in enumerate(dataloader):outputs = model(inputs)loss = criterion(outputs, labels) / accumulation_steps # 缩小损失loss.backward()if (i + 1) % accumulation_steps == 0:optimizer.step()optimizer.zero_grad()
如何解决梯度消失/爆炸问题?
- 使用 ReLU 等现代激活函数:避免 Sigmoid 和 Tanh 在饱和区的梯度消失
- 批量归一化(Batch Normalization):稳定各层的输入分布
- 梯度裁剪:防止梯度爆炸
- 合理的权重初始化:如 Xavier 或 He 初始化
梯度裁剪示例:
torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0)
以上就是 PyTorch 深度学习入门的完整指南。