PyTorch实战:从零开始掌握ResNet18图像分类全流程

在计算机视觉领域,ResNet18作为轻量级残差网络的代表,凭借其优异的性能和适中的计算复杂度,成为入门深度学习的最佳选择之一。本文将带您完整走通使用PyTorch实现ResNet18图像分类的每个环节,从环境搭建到模型推理,特别针对初学者容易遇到的坑点提供解决方案。

1. 开发环境配置与工具准备

搭建合适的开发环境是项目成功的第一步。推荐使用Anaconda创建独立的Python环境,避免包版本冲突:

conda create -n pytorch_resnet python=3.8
conda activate pytorch_resnet

PyTorch的安装需要根据CUDA版本进行选择。对于没有NVIDIA显卡的用户,可以使用CPU版本:

pip install torch torchvision torchaudio --extra-index-url https://download.pytorch.org/whl/cu113

验证安装是否成功:

import torch
print(torch.__version__)  # 应输出1.12.0及以上版本
print(torch.cuda.is_available())  # 检查CUDA是否可用

必备的辅助工具包包括:

pip install opencv-python matplotlib tqdm numpy pillow

提示:如果在Jupyter Notebook中运行代码,建议安装ipykernel并将新环境添加到Notebook选项:

python -m ipykernel install --user --name=pytorch_resnet

2. ResNet18模型加载与解析

PyTorch的torchvision.models模块提供了预定义的ResNet18实现,我们可以直接调用:

import torchvision.models as models

# 加载预训练模型(自动下载权重文件)
model = models.resnet18(pretrained=True)
model.eval()  # 设置为评估模式

理解模型结构对后续调试至关重要。ResNet18的基本构成如下表所示:

层级名称输出尺寸组成模块
conv1112x1127x7卷积, stride=2
maxpool56x563x3最大池化
layer156x562个基础残差块
layer228x282个基础残差块
layer314x142个基础残差块
layer47x72个基础残差块
avgpool1x1全局平均池化
fc1000全连接层(ImageNet分类)

查看模型各层参数的示例代码:

for name, param in model.named_parameters():
    print(f"{name}: {param.shape}")

当需要自定义输入输出时(如处理非ImageNet数据集),可以修改最后的全连接层:

import torch.nn as nn

num_classes = 10  # 假设我们的数据集有10个类别
model.fc = nn.Linear(model.fc.in_features, num_classes)

3. 数据预处理与增强策略

图像数据的标准化处理对模型性能至关重要。ImageNet的标准化参数如下:

from torchvision import transforms

# 标准ImageNet预处理流程
transform = transforms.Compose([
    transforms.Resize(256),          # 缩放至256x256
    transforms.CenterCrop(224),      # 中心裁剪224x224
    transforms.ToTensor(),           # 转为Tensor
    transforms.Normalize(            # 标准化
        mean=[0.485, 0.456, 0.406], 
        std=[0.229, 0.224, 0.225]
    )
])

数据增强能有效提升模型泛化能力。训练时可添加以下增强操作:

train_transform = transforms.Compose([
    transforms.RandomResizedCrop(224),
    transforms.RandomHorizontalFlip(),
    transforms.ColorJitter(brightness=0.2, contrast=0.2, saturation=0.2),
    transforms.RandomRotation(15),
    transforms.ToTensor(),
    transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
])

使用torchvision.datasets加载自定义数据集的示例:

from torchvision.datasets import ImageFolder

dataset = ImageFolder('path/to/your/dataset', transform=transform)
dataloader = torch.utils.data.DataLoader(
    dataset, 
    batch_size=32, 
    shuffle=True,
    num_workers=4
)

注意:Windows系统下多进程数据加载(num_workers>0)可能存在问题,建议在if name == 'main'块中运行或设置为0

4. 模型训练与验证实现

完整的训练流程需要定义损失函数和优化器:

import torch.optim as optim

criterion = nn.CrossEntropyLoss()
optimizer = optim.SGD(model.parameters(), lr=0.001, momentum=0.9)

训练循环的基本框架:

for epoch in range(num_epochs):
    model.train()
    running_loss = 0.0
    
    for inputs, labels in train_loader:
        optimizer.zero_grad()
        outputs = model(inputs)
        loss = criterion(outputs, labels)
        loss.backward()
        optimizer.step()
        running_loss += loss.item()
    
    # 验证阶段
    model.eval()
    correct = 0
    total = 0
    with torch.no_grad():
        for inputs, labels in val_loader:
            outputs = model(inputs)
            _, predicted = torch.max(outputs.data, 1)
            total += labels.size(0)
            correct += (predicted == labels).sum().item()
    
    print(f'Epoch {epoch+1}, Loss: {running_loss/len(train_loader):.4f}, Accuracy: {100*correct/total:.2f}%')

学习率调度策略可以提升训练效果:

scheduler = optim.lr_scheduler.StepLR(optimizer, step_size=7, gamma=0.1)
# 在每个epoch后调用 scheduler.step()

5. 模型推理与结果可视化

单张图片的预测示例:

from PIL import Image

def predict_image(image_path):
    img = Image.open(image_path)
    img_t = transform(img)
    batch_t = torch.unsqueeze(img_t, 0)
    
    with torch.no_grad():
        out = model(batch_t)
    
    _, index = torch.max(out, 1)
    percentage = torch.nn.functional.softmax(out, dim=1)[0] * 100
    return index.item(), percentage[index].item()

可视化模型关注区域的Grad-CAM实现:

import cv2
import numpy as np

def apply_grad_cam(model, img_tensor):
    # 获取最后一层卷积层的输出和梯度
    features = model.layer4
    features.register_forward_hook(lambda m, i, o: features.__setattr__('output', o))
    features.register_backward_hook(lambda m, gi, go: features.__setattr__('gradient', go[0]))
    
    # 前向传播
    output = model(img_tensor.unsqueeze(0))
    pred_idx = output.argmax().item()
    
    # 反向传播
    model.zero_grad()
    one_hot = torch.zeros_like(output)
    one_hot[0][pred_idx] = 1
    output.backward(gradient=one_hot)
    
    # 计算权重并生成热力图
    gradients = features.gradient[0].mean(dim=(1,2), keepdim=True)
    activations = features.output[0]
    cam = (activations * gradients).sum(0).relu().cpu().numpy()
    
    # 归一化处理
    cam = cv2.resize(cam, (224,224))
    cam = (cam - cam.min()) / (cam.max() - cam.min())
    return cam

在实际项目中,我经常遇到显存不足的问题。这时可以采用梯度累积技巧,通过多次小批量计算模拟大批量训练:

accumulation_steps = 4
optimizer.zero_grad()

for i, (inputs, labels) in enumerate(train_loader):
    outputs = model(inputs)
    loss = criterion(outputs, labels) / accumulation_steps
    loss.backward()
    
    if (i+1) % accumulation_steps == 0:
        optimizer.step()
        optimizer.zero_grad()
Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐