PyTorch人脸性别识别毕设实战:LFW数据集、YutongNet与GUI全流程
简介这份资源面向计算机、人工智能相关专业的本科生与自学者提供一套基于PyTorch实现人脸性别识别的完整课程设计或毕业设计参考方案。数据集涵盖白种人、黄种人、黑种人等多种族样本并包含姿态、光照、年龄等干扰因素需按40%、10%、50%随机划分训练、验证与测试集具有一定挑战性。压缩包共18个文件以13个Python脚本为核心涵盖数据加载、网络模型定义、多版本训练与推理流程另附实验报告docx、说明文档md、依赖说明txt及许可证文件整体约1.21MB。资源已有648人学习报告部分说明了所用模型方法、测试结果与文献引用并额外提供GUI界面脚本可在界面输入图像后显示性别识别结果便于读者理解从数据处理到界面展示的完整链路适合作为毕设或大作业的参考实现。1. 从一份毕设压缩包说起PyTorch 人脸性别识别 GUI 到底能跑出什么如果你正在为课程设计或毕设找一个能跑通、能写报告、还能演示的题目人脸性别识别是个经典选择。这份资源包给的是一个完整的 PyTorch 实现包含从数据加载、模型定义、训练脚本到 GUI 界面的全套代码外加一份实验报告文档。数据集用的是 LFW 人脸库覆盖白种人、黄种人、黑种人等多种族样本人脸姿态、光照、年龄都有干扰不是那种清洗得干干净净的玩具数据。代码按版本迭代了 1.0 到 3.3能看出作者在逐步调优。适合谁适合已经装好 PyTorch 环境、想拿一个完整项目练手或交作业的人。不适合谁如果你连 Python 环境都没搭好建议先把 Anaconda 和 PyTorch 装明白再回来。2. 数据管线拆解LFW 数据集怎么切、怎么读、怎么增强2.1 为什么选 LFW 而不是自己爬图LFWLabeled Faces in the Wild是人脸识别领域最常用的公开测试集之一包含 13000 多张人脸图像每张标注了姓名。这份资源包里LfwDataset.py和LwfDataset2.py两个文件就是围绕 LFW 做数据加载的。选它的理由很直接图像已经对齐过一轮人脸区域基本居中省去了自己写人脸检测的麻烦。但要注意LFW 原始标签是姓名不是性别。所以资源包里附了male_names.txt和female_names.txt两个文件用姓名映射性别标签。这个映射逻辑是如果某个姓名出现在 male_names 里该姓名下所有图像标为男性出现在 female_names 里则标为女性。常见做法是先把这两个名单整理成字典再在 Dataset 的__getitem__里查表返回标签。2.2 数据划分40% 训练、10% 验证、50% 测试的坑作业要求随机选 40% 做训练、10% 做验证、50% 做测试。这个比例和常规的 7:2:1 差别很大测试集占了一半意味着模型评估结果波动会更大。实现时不能直接按顺序切必须先打乱索引再切。下面是一个可复现的划分脚本import os import random from sklearn.model_selection import train_test_split # 假设所有图像路径和标签已经读入 image_paths [...] # 图像路径列表 labels [...] # 对应性别标签 0/1 # 先按 40% 训练 10% 验证 50% 临时集剩下 50% 测试 X_train_val, X_test, y_train_val, y_test train_test_split( image_paths, labels, test_size0.5, random_state42, stratifylabels ) # 再从临时集里按 80%:20% 切出训练和验证即总体的 40% 和 10% X_train, X_val, y_train, y_val train_test_split( X_train_val, y_train_val, test_size0.2, random_state42, stratifyy_train_val ) print(f训练集: {len(X_train)}, 验证集: {len(X_val)}, 测试集: {len(X_test)})逻辑说明stratifylabels保证切分后各集合的性别比例一致避免某一类严重偏少。random_state42固定随机种子保证每次运行划分结果相同方便复现。参数怎么改如果你想换划分比例调整test_size和第二个train_test_split的test_size即可但注意两次切分的乘积要等于目标比例。2.3 Dataset 与 DataLoader 的写法LfwDataset.py里定义了一个继承torch.utils.data.Dataset的类核心是__getitem__返回图像张量和标签。常见做法是在__init__里读入所有路径和标签在__getitem__里用 PIL 打开图像、做 transform、返回。DataLoader.py则负责包装成批量迭代器。下面是一个精简版实现import torch from torch.utils.data import Dataset, DataLoader from torchvision import transforms from PIL import Image class LfwGenderDataset(Dataset): def __init__(self, image_paths, labels, transformNone): self.image_paths image_paths self.labels labels self.transform transform def __len__(self): return len(self.image_paths) def __getitem__(self, idx): img Image.open(self.image_paths[idx]).convert(RGB) label self.labels[idx] if self.transform: img self.transform(img) return img, label # 训练集增强验证/测试集只做归一化 train_transform transforms.Compose([ transforms.Resize((128, 128)), transforms.RandomHorizontalFlip(), transforms.RandomRotation(10), transforms.ColorJitter(brightness0.2, contrast0.2), transforms.ToTensor(), transforms.Normalize(mean[0.5, 0.5, 0.5], std[0.5, 0.5, 0.5]) ]) val_transform transforms.Compose([ transforms.Resize((128, 128)), transforms.ToTensor(), transforms.Normalize(mean[0.5, 0.5, 0.5], std[0.5, 0.5, 0.5]) ]) train_loader DataLoader(LfwGenderDataset(X_train, y_train, train_transform), batch_size32, shuffleTrue, num_workers2) val_loader DataLoader(LfwGenderDataset(X_val, y_val, val_transform), batch_size32, shuffleFalse, num_workers2)参数说明Resize((128, 128))是输入尺寸你可以改成 224 但显存占用会翻倍。RandomHorizontalFlip和RandomRotation(10)是轻量增强因为人脸左右翻转后性别不变旋转 10 度以内也不会破坏语义。ColorJitter用来模拟光照变化LFW 里光照差异大这个增强很有必要。Normalize的均值和方差设成 0.5 是常见做法如果你用 ImageNet 预训练权重要改成mean[0.485, 0.456, 0.406]。3. 模型选型与训练YutongNet 的结构、损失函数与版本迭代3.1 YutongNet 的网络结构拆解资源包里YutongNet.py和YutongNet2.py是两个版本的模型定义。从命名和常见实现来看YutongNet 是一个自定义 CNN不是直接调torchvision.models.resnet18。作业要求不允许直接用开源项目提供的已训练模型或现成代码所以自己搭网络是必须的。下面是一个结构合理的自定义 CNN 实现import torch.nn as nn class YutongNet(nn.Module): def __init__(self, num_classes2): super(YutongNet, self).__init__() self.features nn.Sequential( nn.Conv2d(3, 32, kernel_size3, padding1), nn.BatchNorm2d(32), nn.ReLU(inplaceTrue), nn.MaxPool2d(2), # 128 - 64 nn.Conv2d(32, 64, kernel_size3, padding1), nn.BatchNorm2d(64), nn.ReLU(inplaceTrue), nn.MaxPool2d(2), # 64 - 32 nn.Conv2d(64, 128, kernel_size3, padding1), nn.BatchNorm2d(128), nn.ReLU(inplaceTrue), nn.MaxPool2d(2), # 32 - 16 nn.Conv2d(128, 256, kernel_size3, padding1), nn.BatchNorm2d(256), nn.ReLU(inplaceTrue), nn.MaxPool2d(2), # 16 - 8 ) self.classifier nn.Sequential( nn.AdaptiveAvgPool2d(1), nn.Flatten(), nn.Linear(256, 128), nn.ReLU(inplaceTrue), nn.Dropout(0.5), nn.Linear(128, num_classes) ) def forward(self, x): x self.features(x) x self.classifier(x) return x逻辑说明四层卷积每层后接 BatchNorm 和 ReLU再池化。通道数从 32 逐步翻到 256。最后用AdaptiveAvgPool2d(1)把特征图压成 1x1再接全连接。Dropout(0.5)放在全连接之间防过拟合。参数怎么改如果显存不够把第一层通道数从 32 降到 16或者把输入尺寸从 128 降到 96。如果欠拟合把 Dropout 降到 0.3 或去掉。3.2 训练循环与损失函数选择二分类问题损失函数用CrossEntropyLoss就行它内部已经包含了 Softmax。优化器选Adam学习率 1e-3 起步。下面是一个完整的训练循环import torch import torch.nn as nn import torch.optim as optim device torch.device(cuda if torch.cuda.is_available() else cpu) model YutongNet(num_classes2).to(device) criterion nn.CrossEntropyLoss() optimizer optim.Adam(model.parameters(), lr1e-3, weight_decay1e-4) scheduler optim.lr_scheduler.StepLR(optimizer, step_size10, gamma0.5) best_val_acc 0.0 for epoch in range(30): model.train() running_loss 0.0 for imgs, labels in train_loader: imgs, labels imgs.to(device), labels.to(device) optimizer.zero_grad() outputs model(imgs) loss criterion(outputs, labels) loss.backward() optimizer.step() running_loss loss.item() # 验证 model.eval() correct, total 0, 0 with torch.no_grad(): for imgs, labels in val_loader: imgs, labels imgs.to(device), labels.to(device) outputs model(imgs) _, predicted torch.max(outputs, 1) total labels.size(0) correct (predicted labels).sum().item() val_acc correct / total scheduler.step() print(fEpoch {epoch1}, Loss: {running_loss/len(train_loader):.4f}, Val Acc: {val_acc:.4f}) if val_acc best_val_acc: best_val_acc val_acc torch.save(model.state_dict(), best_model.pth)参数说明weight_decay1e-4是 L2 正则防止过拟合。StepLR每 10 个 epoch 把学习率乘 0.5帮助后期收敛。best_model.pth保存验证集准确率最高的权重测试时加载这个文件。注意验证集不参与梯度更新所以用torch.no_grad()包住。3.3 版本迭代 1.0 到 3.3 在改什么从文件名看Gender_identify1.0.py到Gender_identify3.3.py是逐步迭代的。常见迭代方向有1.0 可能是最简版本直接读图训练2.0 加入数据增强3.0 引入 BatchNorm 或 Dropout3.1 调整学习率策略3.2 换更深的网络3.3 加入 GUI 或模型集成。你不需要每个版本都跑建议直接从 3.3 开始看遇到不懂的回溯到前面版本对比。Gender_identify_gui.py是带界面的版本通常是在 3.x 基础上套了一个 Tkinter 或 PyQt 的壳。4. GUI 界面集成从命令行到可视化演示的落地细节4.1 Tkinter 还是 PyQt选型理由资源包里Gender_identify_gui.py是 GUI 入口。从毕设场景看Tkinter 更常见因为它是 Python 标准库不需要额外装包代码量也少。PyQt 界面更漂亮但安装包大、学习成本高。如果你只是交作业演示Tkinter 够用。下面是一个最小可用的 Tkinter 界面import tkinter as tk from tkinter import filedialog from PIL import Image, ImageTk import torch from torchvision import transforms class GenderGUI: def __init__(self, model, device): self.model model self.device device self.transform transforms.Compose([ transforms.Resize((128, 128)), transforms.ToTensor(), transforms.Normalize(mean[0.5, 0.5, 0.5], std[0.5, 0.5, 0.5]) ]) self.root tk.Tk() self.root.title(人脸性别识别) self.root.geometry(400x500) self.btn tk.Button(self.root, text选择图片, commandself.load_image) self.btn.pack(pady10) self.label tk.Label(self.root, text等待输入...) self.label.pack(pady10) self.result tk.Label(self.root, text, font(Arial, 16)) self.result.pack(pady20) def load_image(self): path filedialog.askopenfilename(filetypes[(Image, *.jpg *.png)]) if not path: return img Image.open(path).convert(RGB) img_display img.resize((200, 200)) photo ImageTk.PhotoImage(img_display) self.label.config(imagephoto) self.label.image photo input_tensor self.transform(img).unsqueeze(0).to(self.device) self.model.eval() with torch.no_grad(): output self.model(input_tensor) _, pred torch.max(output, 1) gender 男性 if pred.item() 0 else 女性 self.result.config(textf识别结果: {gender}) def run(self): self.root.mainloop()逻辑说明filedialog.askopenfilename弹出文件选择框选完后用 PIL 打开、缩放显示、转张量、送模型推理。unsqueeze(0)是加 batch 维度因为模型期望输入是[N, C, H, W]。self.label.image photo这行必须写否则图片会被垃圾回收导致显示空白这是 Tkinter 的经典坑。4.2 模型加载与推理的衔接GUI 里加载模型时要注意训练时保存的是state_dict加载时要先实例化模型结构再load_state_dict。如果结构对不上会报错。常见做法是把模型定义单独放在YutongNet.py里GUI 文件 import 进来。推理时记得model.eval()和torch.no_grad()否则 BatchNorm 会用当前 batch 的统计量Dropout 也会随机丢弃导致结果不稳定。4.3 界面响应速度优化如果模型较大或电脑没 GPU点按钮后界面会卡住。解决办法是把推理放到单独线程里用threading.Thread跑主线程只负责更新界面。另一个技巧是提前把模型加载到内存不要每次点按钮都重新加载。如果你用 GPU第一次推理会有 CUDA 初始化开销可以在程序启动时先跑一张空白图预热。5. 避坑与排查训练不收敛、GUI 闪退、报告数据对不上5.1 损失不下降准确率卡在 50%现象训练几个 epoch 后 loss 几乎不变验证准确率在 0.5 附近波动。原因最常见的是标签映射错了male_names 和 female_names 有重叠或拼写不一致导致同一姓名既标男又标女。解决在 Dataset 初始化后打印标签分布确认 0 和 1 的数量大致均衡。如果严重偏斜检查姓名映射逻辑。另一个原因是学习率太大梯度爆炸把 lr 从 1e-3 降到 1e-4 试试。5.2 GUI 选图后图片不显示现象点“选择图片”后界面空白但控制台没报错。原因Tkinter 的Label组件不会保持对PhotoImage的引用局部变量被回收后图片就没了。解决把photo赋值给self.label.image如上面代码所示。这是 Tkinter 血泪经验新手必踩。5.3 测试集准确率远低于验证集现象验证集 85%测试集只有 70%。原因测试集占 50%样本量大且包含更多困难样本验证集只有 10%可能恰好比较简单。解决不要反复用测试集调参测试集只在最后评估一次。如果差距过大检查划分时是否真的随机打乱了train_test_split默认会打乱但如果你手动切分忘了 shuffle 就会出问题。5.4 报告里的文献引用格式不对现象实验报告要求给出学术文献引用但不知道引什么。原因作业明确要求参考学术信息。解决至少引用三篇一篇 LFW 数据集原论文Gary B. Huang 等人的 Labeled Faces in the Wild一篇性别识别相关综述一篇 CNN 基础论文如 AlexNet 或 VGG。格式用 GB/T 7714 或 APA 都行但全文要统一。5.5 CUDA out of memory现象训练时显存不够报错。原因batch size 太大或输入尺寸太大。解决把 batch_size 从 32 降到 16 或 8把输入从 128 降到 96。如果还不行用torch.cuda.empty_cache()清缓存或者换 CPU 跑慢但能跑通。6. 进阶技巧用混淆矩阵和 ROC 曲线把报告写扎实最后一章说一个能让你的实验报告脱颖而出的技巧不要只报一个准确率把混淆矩阵和 ROC 曲线画出来。准确率在类别不均衡时会有误导性混淆矩阵能看出模型是不是偏向预测某一类。下面是在测试集上评估并画图的代码import matplotlib.pyplot as plt from sklearn.metrics import confusion_matrix, roc_curve, auc import numpy as np model.eval() all_preds, all_labels, all_probs [], [], [] with torch.no_grad(): for imgs, labels in test_loader: imgs, labels imgs.to(device), labels.to(device) outputs model(imgs) probs torch.softmax(outputs, dim1)[:, 1] _, preds torch.max(outputs, 1) all_preds.extend(preds.cpu().numpy()) all_labels.extend(labels.cpu().numpy()) all_probs.extend(probs.cpu().numpy()) # 混淆矩阵 cm confusion_matrix(all_labels, all_preds) print(混淆矩阵:) print(cm) # ROC 曲线 fpr, tpr, _ roc_curve(all_labels, all_probs) roc_auc auc(fpr, tpr) plt.figure(figsize(6, 5)) plt.plot(fpr, tpr, labelfROC curve (AUC {roc_auc:.3f})) plt.plot([0, 1], [0, 1], k--) plt.xlabel(False Positive Rate) plt.ylabel(True Positive Rate) plt.title(ROC Curve) plt.legend(loclower right) plt.savefig(roc_curve.png, dpi150) plt.show()逻辑说明torch.softmax(outputs, dim1)[:, 1]取的是预测为类别 1 的概率用于画 ROC。confusion_matrix返回 2x2 矩阵对角线是正确分类数。auc越接近 1 越好0.5 等于随机猜。参数怎么改如果你把标签 0 定义为女性、1 定义为男性那 ROC 的正类就是男性报告里要写清楚。表格测试集评估指标汇总指标数值说明准确率0.87整体正确分类比例男性精确率0.85预测为男性中真正男性的比例男性召回率0.89真正男性中被正确预测的比例女性精确率0.89预测为女性中真正女性的比例女性召回率0.85真正女性中被正确预测的比例AUC0.92ROC 曲线下面积这张表直接放进报告比只写一个准确率有说服力得多。注意上面的数值是示例你要用自己的测试集跑出来替换。从那以后我每次交毕设前都会强制走一遍混淆矩阵和 ROC因为单看准确率真的会骗人。有一次验证集 90%测试集混淆矩阵显示女性样本几乎全被预测成男性准确率是靠男性样本撑起来的。希望帮到你。本文还有配套的精品资源点击获取