第 P6 周:VGG-16 算法 — PyTorch 实现人脸识别
👉声明
- 本文为 365 天深度学习训练营 内部参考文章的个人学习记录,代码与实验在本地完成。
- 数据集来源训练营提供的
48-data.zip(本地解压/整理为6-data/);数据集请勿对外公开传播。 - 原作者:K同学啊
- 本篇为个人在 P6 关卡上的学习与实践记录
第 P6 周:VGG-16 算法 — PyTorch 实现人脸识别
🏡 我的环境(参考)
- 语言:Python 3.8+ / 3.11(以本机为准)
- 编译器:Jupyter Lab
- 深度学习:PyTorch、
torchvision(需支持torchvision.models.vgg16) - 数据:
6-data/(由训练营人脸数据整理而来,目录内为「每位名人一个子文件夹」)
🍺 本周要求
- 保存训练过程中测试集上最佳模型权重(
state_dict)。 - 调用官方 VGG-16(
torchvision.models.vgg16),并将分类头改为本数据集类别数。
🍻 拔高(可选)
- 将测试集准确率推到约 60%(小数据多分类 + 从头训练 VGG 往往只在十几个百分点,更现实的做法是使用 ImageNet 预训练权重 + 数据增强 + 微调,见下文与
p6.ipynb)。 - 手动搭建 VGG-16 框架(与预训练对照),notebook 第八节附有
ManualVGG16示例。
一、本周学习内容概览
本周任务是在人脸图像多分类数据上,使用 VGG-16 完成特征提取与分类:
- 数据:
6-data/下按人物姓名为子文件夹(如Brad Pitt/、Tom Hanks/等),共 17 类;当前样本量在各类间大致平衡(多数类 100 张,个别类略多)。 - 划分:无现成
train/test子目录时,在代码中对全部样本做 8:2 分层随机划分(优先sklearn.model_selection.train_test_split(..., stratify=)),训练集使用 Resize + RandomCrop + 水平翻转,测试集仅 Resize 到 224,再配合 ImageNet 均值方差标准化。 - 模型:使用 官方
vgg16,加载 ImageNet 预训练权重(兼容VGG16_Weights.IMAGENET1K_V1与旧版pretrained=True),将classifier最后一层改为Linear(..., num_classes)。 - 优化:
CrossEntropyLoss+SGD(momentum=0.9, weight_decay=1e-4),初始学习率1e-4;动态学习率采用LambdaLR:lambda epoch: 0.92 ** (epoch // 4)。 - 最佳权重:每个 epoch 在测试集上评估,若优于历史最佳则保存为
best_model_p6.pth。
具体 Loss/Accuracy 曲线与最终 Best Test Acc 以本机运行 p6.ipynb 为准。
二、必要代码与关键点说明
1. 设备
import torch
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
device
2. 数据:按索引划分 + 不同变换
核心思路:ImageFolder 根目录读入后得到 base_dataset.samples / targets,对索引做划分,再用包装类在 __getitem__ 里施加 train_transforms 或 test_transform(避免「训练集和测试集共用一种增强」)。
分层划分示例:
from pathlib import Path
from torchvision import datasets, transforms
import numpy as np
try:
from sklearn.model_selection import train_test_split
except ImportError:
train_test_split = None
data_root = Path("6-data")
base_dataset = datasets.ImageFolder(str(data_root), transform=None)
num_classes = len(base_dataset.classes)
train_transforms = transforms.Compose([
transforms.Resize((256, 256)),
transforms.RandomCrop(224),
transforms.RandomHorizontalFlip(),
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]),
])
IndexedImageFolder 详见 p6.ipynb 数据单元。
3. 官方 VGG-16 与替换分类头
import torch.nn as nn
import torchvision
def build_vgg16(num_classes: int, pretrained: bool = True):
model = None
if pretrained:
try:
from torchvision.models import vgg16, VGG16_Weights
model = vgg16(weights=VGG16_Weights.IMAGENET1K_V1)
except Exception:
model = torchvision.models.vgg16(pretrained=True)
else:
try:
from torchvision.models import vgg16
model = vgg16(weights=None)
except Exception:
model = torchvision.models.vgg16(pretrained=False)
in_features = model.classifier[-1].in_features
model.classifier[-1] = nn.Linear(in_features, num_classes)
return model
4. 训练与测试(单份定义即可)
训练函数内在 model.train() 下前向与反传;测试在 model.eval() + torch.no_grad() 下只做前向。

5. 动态学习率(LambdaLR)
learn_rate = 1e-4
optimizer = torch.optim.SGD(model.parameters(), lr=learn_rate, momentum=0.9, weight_decay=1e-4)
lambda1 = lambda epoch: 0.92 ** (epoch // 4)
scheduler = torch.optim.lr_scheduler.LambdaLR(optimizer, lr_lambda=lambda1)
每个 epoch 推荐顺序:train → test → scheduler.step()。
6. 正式训练并保存最佳权重
epochs = 40
best_acc = 0.0
best_path = "best_model_p6.pth"
train_loss, train_acc = [], []
test_loss, test_acc = [], []
for epoch in range(epochs):
epoch_train_acc, epoch_train_loss = train(train_dl, model, loss_fn, optimizer)
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"]
print(
f"Epoch:{epoch+1:2d}, Train_acc:{epoch_train_acc*100:.1f}%, Train_loss:{epoch_train_loss:.3f}, "
f"Test_acc:{epoch_test_acc*100:.1f}%, Test_loss:{epoch_test_loss:.3f}, Lr:{lr:.2E}"
)
print("Done")
print(f"Best Test Acc: {best_acc*100:.2f}% | Saved to: {best_path}")
与 TensorFlow 的 ModelCheckpoint 类似之处:都按验证指标保留最优;PyTorch 更常见的是只存 state_dict 而非整图+优化器(除非要做断点续训)。
三、结果可视化
使用 matplotlib 绘制 train_acc / test_acc 与对应 Loss,横轴为 epoch;若在 WSL/Linux 下没有 SimHei,中文可能显示为方框,可安装字体或改用英文字样。
四、指定图片预测与加载最佳模型
- 预测:对
test_dataset随机下标取(tensor, label),与训练一致的通道顺序在 Tensor 上unsqueeze(0)后model(x)。 - 加载:
build_vgg16(num_classes, pretrained=False)搭建同结构网络,再load_state_dict(torch.load(best_path, map_location=device)),最后在测试集上复算准确率以确认与训练时最佳 epoch 一致。
五、关于「测试集 60%」与原文 ~17% 现象
训练营原文在学习率偏小、增强不足、且相当于从零训练大型网络时,容易出现 Train/Test 只有约百分之十几 的情况。要在人脸多分类上达到 ~60% 测试准确率,实践中通常需要:
- ImageNet 预训练 backbone + 仅微调分类头或全流程小学习率微调;
- 训练端增强(随机裁剪、翻转等)与适当 weight decay;
- 若仍不足:可尝试 先冻结
features若干 epoch 再解冻、AdamW、更长epochs、或更强的随机增强策略。
若故意将 USE_PRETRAINED = False 做对照实验,则更容易复现「低准确率、难收敛」的现象,这与理论预期一致。
六、个人总结
本周系统过了一遍 VGG-16 的结构特点(重复 3×3 卷积堆叠、池化降采样、全连接分类头),并在真实人脸数据上完成 PyTorch 训练闭环:划分数据、DataLoader、官方模型替换最后一层、LambdaLR、按测试集保存最佳 best_model_p6.pth。与 P4/P5 相比,人脸识别类别更多,更依赖预训练与正则;同时再次体会到 路径与 num_classes 必须自数据集自动推断,避免 Linear(..., 0) 引发 GPU 侧断言类错误。后续若冲更高准确率,会在当前 p6.ipynb 基础上尝试两阶段冻结/解冻与更细的学习率策略。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)