第 P5 周:PyTorch 实现运动鞋识别(动态学习率)
·
第 P5 周:PyTorch 实现运动鞋识别(动态学习率)
- 本文为 365 天深度学习训练营 中的学习记录博客
- 原作者:K同学啊
- 本篇为个人在 P5 关卡上的学习与实践记录
一、本周学习内容概览
本周 P5 的核心任务是:使用 PyTorch 搭建 CNN 对 adidas / nike 运动鞋图片进行二分类识别,并重点学习和实践动态学习率调度策略。
我这次主要做了以下几件事:
- 数据组织:数据集
46-data.zip解压后为5-data/,内部已按train/和test/划分好,每个子目录下有adidas/和nike/两类图片。 - 数据加载与预处理:使用
datasets.ImageFolder分别读取训练集和测试集,通过transforms将图片统一缩放到224×224并按 ImageNet 常用的mean/std做标准化。 - 模型构建:搭建了带有 BatchNorm 和 Dropout 的 CNN(
Model),包含四层卷积 + 两次池化 + 全连接层,输出 2 维 logits。 - 动态学习率(重点):使用
torch.optim.lr_scheduler.StepLR,每 5 个 epoch 将学习率衰减为原来的 0.5 倍,替代了手动写adjust_learning_rate的方式。 - 训练与评估:训练 40 个 epoch,记录每轮的
Train/Test Acc/Loss和当前学习率,并在训练过程中保存最佳模型权重。 - 最终结果:训练集准确率约 99.6%,测试集最佳准确率约 82.9%。
二、必要代码与关键点说明
1. 环境与设备检查
import torch
import torch.nn as nn
import torchvision
import torchvision.transforms as transforms
from torchvision import datasets
import os, PIL, pathlib
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
device
2. 数据集目录结构与路径处理
数据集解压后结构如下(已按 train/test 划分好):
5-data/
train/
adidas/
img001.jpg ...
nike/
img101.jpg ...
test/
adidas/
img201.jpg ...
nike/
img301.jpg ...
使用 pathlib.Path 处理路径,并从 ImageFolder 直接获取类别名:
from pathlib import Path
data_dir = Path("5-data")
data_paths = list(data_dir.glob("*"))
print([p.name for p in data_paths]) # ['test', 'train']
3. 数据加载与预处理
train_transforms = transforms.Compose([
transforms.Resize([224, 224]),
transforms.ToTensor(),
transforms.Normalize(mean=[0.485, 0.456, 0.406],
std=[0.229, 0.224, 0.225]),
])
test_transform = transforms.Compose([
transforms.Resize([224, 224]),
transforms.ToTensor(),
transforms.Normalize(mean=[0.485, 0.456, 0.406],
std=[0.229, 0.224, 0.225]),
])
train_dataset = datasets.ImageFolder("5-data/train", transform=train_transforms)
test_dataset = datasets.ImageFolder("5-data/test", transform=test_transform)
classNames = train_dataset.classes # ['adidas', 'nike']
train_dataset.class_to_idx # {'adidas': 0, 'nike': 1}
batch_size = 32
train_dl = torch.utils.data.DataLoader(train_dataset, batch_size=batch_size, shuffle=True, num_workers=1)
test_dl = torch.utils.data.DataLoader(test_dataset, batch_size=batch_size, shuffle=False, num_workers=1)
4. CNN 模型结构
import torch.nn.functional as F
class Model(nn.Module):
def __init__(self, num_classes):
super(Model, self).__init__()
self.conv1 = nn.Sequential(nn.Conv2d(3, 12, 5), nn.BatchNorm2d(12), nn.ReLU()) # -> 12×220×220
self.conv2 = nn.Sequential(nn.Conv2d(12, 12, 5), nn.BatchNorm2d(12), nn.ReLU()) # -> 12×216×216
self.pool3 = nn.Sequential(nn.MaxPool2d(2)) # -> 12×108×108
self.conv4 = nn.Sequential(nn.Conv2d(12, 24, 5), nn.BatchNorm2d(24), nn.ReLU()) # -> 24×104×104
self.conv5 = nn.Sequential(nn.Conv2d(24, 24, 5), nn.BatchNorm2d(24), nn.ReLU()) # -> 24×100×100
self.pool6 = nn.Sequential(nn.MaxPool2d(2)) # -> 24×50×50
self.dropout = nn.Sequential(nn.Dropout(0.2))
self.fc = nn.Sequential(nn.Linear(24 * 50 * 50, num_classes))
def forward(self, x):
batch_size = x.size(0)
x = self.conv1(x)
x = self.conv2(x)
x = self.pool3(x)
x = self.conv4(x)
x = self.conv5(x)
x = self.pool6(x)
x = self.dropout(x)
x = x.view(batch_size, -1)
x = self.fc(x)
return x
model = Model(num_classes=len(train_dataset.classes)).to(device)
5. 损失函数、优化器与动态学习率(重点)
loss_fn = nn.CrossEntropyLoss()
learn_rate = 1e-3
optimizer = torch.optim.SGD(model.parameters(), lr=learn_rate, momentum=0.9, weight_decay=1e-4)
scheduler = torch.optim.lr_scheduler.StepLR(optimizer, step_size=5, gamma=0.5)
StepLR:每 5 个 epoch 学习率乘以gamma=0.5(即减半)。- 学习率变化轨迹:
1e-3 → 5e-4 → 2.5e-4 → 1.25e-4 → ...
三、训练与测试流程
1. 训练函数
def train(dataloader, model, loss_fn, optimizer):
size = len(dataloader.dataset)
num_batches = len(dataloader)
train_loss, train_acc = 0.0, 0.0
for X, y in dataloader:
X, y = X.to(device), y.to(device)
pred = model(X)
loss = loss_fn(pred, y)
optimizer.zero_grad()
loss.backward()
optimizer.step()
train_acc += (pred.argmax(1) == y).type(torch.float).sum().item()
train_loss += loss.item()
return train_acc / size, train_loss / num_batches
2. 测试函数
def test(dataloader, model, loss_fn):
size = len(dataloader.dataset)
num_batches = len(dataloader)
test_loss, test_acc = 0.0, 0.0
with torch.no_grad():
for imgs, target in dataloader:
imgs, target = imgs.to(device), target.to(device)
target_pred = model(imgs)
loss = loss_fn(target_pred, target)
test_loss += loss.item()
test_acc += (target_pred.argmax(1) == target).type(torch.float).sum().item()
return test_acc / size, test_loss / num_batches
3. 主训练循环(含最佳模型保存)
epochs = 40
train_loss, train_acc = [], []
test_loss, test_acc = [], []
best_acc = 0.0
best_path = "best_model_p5.pth"
for epoch in range(epochs):
model.train()
epoch_train_acc, epoch_train_loss = train(train_dl, model, loss_fn, optimizer)
model.eval()
epoch_test_acc, epoch_test_loss = test(test_dl, model, loss_fn)
scheduler.step()
train_acc.append(epoch_train_acc)
train_loss.append(epoch_train_loss)
test_acc.append(epoch_test_acc)
test_loss.append(epoch_test_loss)
if epoch_test_acc > best_acc:
best_acc = epoch_test_acc
torch.save(model.state_dict(), best_path)
lr = optimizer.param_groups[0]['lr']
template = "Epoch:{:2d}, Train_acc:{:.1f}%, Train_loss:{:.3f}, Test_acc:{:.1f}%, Test_loss:{:.3f}, Lr:{:.2E}"
print(template.format(epoch+1, epoch_train_acc*100, epoch_train_loss,
epoch_test_acc*100, epoch_test_loss, lr))
print("Done")
print(f"Best Test Acc: {best_acc*100:.2f}% | Saved to: {best_path}")
在本次实验中,40 个 epoch 后的代表性结果为:

- 训练集:
Train_acc ≈ 99.6%,Train_loss ≈ 0.04 - 测试集最佳:
Test_acc ≈ 82.9%(出现在 Epoch 10–12 附近) - 学习率变化:从
1e-3逐步衰减到3.91e-6
四、结果可视化与分析
1. 绘制训练 / 测试曲线
import matplotlib.pyplot as plt
from datetime import datetime
epochs_range = range(epochs)
plt.figure(figsize=(12, 3))
plt.subplot(1, 2, 1)
plt.plot(epochs_range, train_acc, label="Train Acc")
plt.plot(epochs_range, test_acc, label="Test Acc")
plt.legend(loc="lower right")
plt.title("Training and Validation Accuracy")
plt.xlabel(datetime.now())
plt.subplot(1, 2, 2)
plt.plot(epochs_range, train_loss, label="Train Loss")
plt.plot(epochs_range, test_loss, label="Test Loss")
plt.legend(loc="upper right")
plt.title("Training and Validation Loss")
plt.tight_layout()
plt.show()

从曲线可以看到:训练集准确率在 10 个 epoch 后就达到 95%+ 并持续上升到接近 100%,而测试集在 82% 左右就趋于平稳,说明模型存在一定过拟合。
2. 指定图片预测
from PIL import Image
from pathlib import Path
import random
idx_to_class = {v: k for k, v in train_dataset.class_to_idx.items()}
test_root = Path("5-data/test")
img_candidates = list(test_root.glob("*/*.jpg")) + list(test_root.glob("*/*.png"))
img_path = random.choice(img_candidates)
img = Image.open(img_path).convert("RGB")
x = test_transform(img)
x = torch.unsqueeze(x, dim=0).to(device)
model.eval()
with torch.no_grad():
pred_idx = model(x).argmax(dim=1).item()
print(f"图片: {img_path}")
print(f"预测类别: {idx_to_class[pred_idx]}")
五、保存并加载模型
# 保存最后一轮权重
torch.save(model.state_dict(), "last_model_p5.pth")
# 加载最佳权重
best_model = Model(num_classes=len(train_dataset.classes)).to(device)
best_model.load_state_dict(torch.load("best_model_p5.pth", map_location=device))
best_model.eval()
六、动态学习率知识总结
本次重点学习了三种官方学习率调度器:
1) StepLR(本次实际使用)
scheduler = torch.optim.lr_scheduler.StepLR(optimizer, step_size=5, gamma=0.5)
每 step_size 个 epoch,学习率乘 gamma。
2) LambdaLR(自定义衰减函数)
lambda1 = lambda epoch: 0.92 ** (epoch // 2)
scheduler = torch.optim.lr_scheduler.LambdaLR(optimizer, lr_lambda=lambda1)
3) MultiStepLR(指定里程碑衰减)
scheduler = torch.optim.lr_scheduler.MultiStepLR(
optimizer, milestones=[10, 20, 30], gamma=0.1
)
在指定的 epoch 节点上衰减,适合已知训练曲线拐点的场景。
七、个人总结
- 环境方面:本次实验在 WSL2 + Linux 环境下完成,Python 版本为
3.11.13,PyTorch 为2.5.1+cu121,Torchvision 为0.20.1+cu121,GPU 为NVIDIA GeForce RTX 4060 Ti。一开始在路径与类别数上再次踩了坑(split("\\")、全局变量classeNames拼写不一致导致out_features=0和 CUDA assert),重启内核并统一使用train_dataset.classes后解决。 - 数据与模型方面:本次使用的是 adidas vs nike 运动鞋图片数据集,数据量不大。自定义的
Model(四层卷积 + BN + Dropout + 全连接)在 40 个 epoch 后训练集准确率达到 99.6%,但测试集最高只到约 82.9%,过拟合较为明显。 - 动态学习率(本次重点):体会到了动态调整学习率的必要性——初期用较大的
lr=1e-3快速收敛,后期通过StepLR逐步减小学习率来稳定训练。相比固定lr=1e-4的 SGD,收敛速度和最终精度都有所改善。还了解了LambdaLR、MultiStepLR等更灵活的调度方式。 - 最佳模型保存(拔高):在训练循环中加入了对测试集准确率的跟踪,只有当新的
test_acc超过历史最佳时才保存权重到best_model_p5.pth,避免最终使用过拟合的最后一轮模型。 - 后续改进方向:测试集准确率要达到 84%–86%,可以尝试加入
RandomHorizontalFlip等数据增强、使用Adam优化器、或引入预训练ResNet18做迁移学习,同时适度增加 Dropout 强度来控制过拟合。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐




所有评论(0)