尧图精选

小样本人脸性别分类实战:从数据集审计到PyTorch迁移学习

🕒 发布时间:2026/9/15 6:27:01 📁 来源:尧图网络
简介人脸性别检测与分类数据集面向计算机视觉初学者与算法工程师包含300张真实手机采集的人脸图片已按woman/man两类划分并完成标注适合用于训练人脸检测、特征提取与性别分类模型。资源包共505个文件大小约339.41MB以Python脚本、配置文件、模型定义文件为主辅以少量图片、视频、ipynb示例及训练检查点配置覆盖SSD MobileNet、Inception等常用检测网络可直接对接TensorFlow训练流程。已有58人学习下载。除基础数据集外还附带基于Faster R-CNN、Mean-shift和卡尔曼滤波的人流统计参考实现便于在性别检测基础上扩展目标跟踪与应用落地。整体结构清晰兼顾数据、模型与代码适合作为算法训练与毕设/课设的参考资料。1. 300张手机人脸图做性别分类先弄清这个数据集的边界300张图能不能跑深度学习算法能而且很适合做第一轮实验。人脸性别检测分类数据集里给出的woman/man两个分类、真实手机采集、已完成分类划分标注这三个条件叠加起来意味着训练一轮只需要几秒模型结构可以反复换真正要花时间的地方反而是数据本身。样本量小错误标注的影响会被成倍放大train/val/test划分不严谨、类间数量失衡、同一张脸跨集合重复出现都会让最终精度虚高或偏低。整篇文章按实际项目习惯的顺序推进先审计数据集的woman/man划分与标注情况再用PyTorch做迁移学习训练一个两分类模型最后用置信度把低质量样本筛出来做一轮清洗跑完这套流程后续换成你自己采集的新图片也能照用。2. 审数据再开训用脚本核对woman/man划分与标注2.1 分类数据集的标准目录结构标签直接来自路径对人脸性别检测的两分类任务常见的数据集布局是这样的dataset/ ├── train/ │ ├── woman/ │ └── man/ ├── validation/ │ ├── woman/ │ └── man/ └── test/ ├── woman/ └── man/这个结构的标注维护成本很低类别名就是目录名不需要额外生成json或txt。torchvision的ImageFolder读取时按目录名的字母序生成索引man排在woman前面所以标签0对应man标签1对应woman。相比之下检测任务用cvat标注工具拉框另存xml或json分类任务完全不用走到那一步只要目录划分严格训练代码里就不会出现“标签对不上”的问题。300张图建议按6:2:2拆参考划分如下子集图片数woman : mantrain约180张接近1:1validation约60张接近1:1test约60张接近1:1类别不平衡在性别分类里虽然不致命但会直接影响分类评估的召回率。如果woman和man各150张训练时模型会倾向于把不确定的样本判给数量更多的那一类最终accuracy看着还行其中一类的recall却掉得很明显。300张的体量不算大划分前先做一次检查是值得的。2.2 数据集健康检查脚本统计数量、查重复、查坏图我一般会先跑一个脚本把三个信息打印出来每个split下各类图片数量、重复文件组、无法解码的图片。脚本不修改任何文件只输出报告等你确认后再决定怎么处理。import hashlib from pathlib import Path from PIL import Image def audit_classification_dataset(data_root: str): root Path(data_root) stats {} duplicates {} unreadable [] for split_dir in sorted(root.iterdir()): if not split_dir.is_dir() or split_dir.name.startswith(.): continue split split_dir.name stats[split] {} for class_dir in sorted(split_dir.iterdir()): if not class_dir.is_dir(): continue images [ f for f in class_dir.rglob(*) if f.suffix.lower() in {.jpg, .jpeg, .png} ] stats[split][class_dir.name] len(images) for img in images: try: with Image.open(img) as im: im.load() except Exception as exc: unreadable.append((str(img), str(exc))) continue digest hashlib.md5(img.read_bytes()).hexdigest() duplicates.setdefault(digest, []).append(str(img)) print(f各类别数量: {stats}) dup_groups {k: v for k, v in duplicates.items() if len(v) 1} print(f疑似重复组数: {len(dup_groups)}) for paths in dup_groups.values(): print(重复组:, .join(paths)) if unreadable: print(无法解码的图片:) for path, err in unreadable: print(path, err) if __name__ __main__: audit_classification_dataset(dataset)脚本做了三件事。第一遍历根目录下每个split目录按类别文件夹统计图片数量直接看出woman/man在训练集、验证集、测试集中是否均衡第二用PIL逐张打开并执行im.load()这一步能触发真实解码防止把改了扩展名的非图片文件混进数据集第三对原始字节做md5哈希相同的文件视为重复样本。重点关注跨split重复同一张脸若同时出现在train和validation里验证精度会虚高这个虚高不是模型能力强而是它记住了图片本身。哈希基于文件原始字节同一个人在不同照片里不会被判成重复这类“内容相似但编码不同”的样本需要靠人工翻图识别。提示脚本只负责报告不负责删图。看到重复文件先确认是内容重复还是同一张原图被不同压缩等级保存前者可以保留一张后者建议把清晰度低的那张移出去。2.3 手机采集图片的人工复核缩略图网格和三类硬伤脚本把数量问题解决后剩下的是标注质量问题。手机采集的照片最常出现三类影响性别分类的硬伤美颜和浓妆把面部结构磨平模型学到的是“磨皮感”而非性别特征远距离抓拍导致人脸分辨率不足眼睛和下颌线细节丢失逆光或夜景下暗部细节几乎不可见。还有一个常见问题是标注错误本身比如长发男性被放进woman目录、戴假发的样本被标错。快速处理方式是用文件管理器的“大图标”视图或者用一小段Python把每个类别的图片拼成缩略图网格按文件名顺序翻一遍。看到明显标错的片子先移到单独的hard_cases目录不要直接在原目录里删。后面训练完模型还要靠置信度把这些样本重新审一遍保留原始文件能给复查留个依据。标注错误的样本宁可先移出训练集也别让模型去学一个错误信号。3. 用PyTorch迁移学习训练性别分类模型300张也能收敛3.1 选ResNet18而不是更重的分类网络300张训练图、二分类ResNet18是很好的起点。参数量约11.2Mbatch_size取16时显存占用不到1GB编入深度学习算法实验环境几乎没门槛。更大的ResNet50在这个数据量下容易过拟合MobileNetV3-Small则更适合后续部署到移动端时再切换。ResNet18在ImageNet上预训练过浅层已经具备边缘、纹理、眼睛位置这些通用特征人脸图片做迁移学习时只需要替换最后的全连接层。从头训练卷积层在300张图上很难收敛而迁移学习可以把训练压力压缩到一个线性层上。模型参数量batch16显存参考适用场景ResNet1811.2M约1GB小数据集分类实验首选ResNet5025.6M约2GB500张以上再考虑MobileNetV3-Small2.5M约0.4GB移动端部署换用3.2 数据加载和预处理参数分类数据集的加载用ImageFolder最省事它读目录名生成标签和前面说的目录结构完全匹配。预处理里需要区分训练集和验证集训练集加随机增强验证集只做resize和归一化不加任何随机操作否则评估结果会有波动。from torch.utils.data import DataLoader from torchvision import datasets, transforms train_transform transforms.Compose([ transforms.Resize((224, 224)), transforms.RandomHorizontalFlip(p0.5), transforms.ColorJitter(brightness0.1, contrast0.1, saturation0.1), transforms.ToTensor(), transforms.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]), ]) val_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_ds datasets.ImageFolder(dataset/train, transformtrain_transform) val_ds datasets.ImageFolder(dataset/validation, transformval_transform) train_loader DataLoader(train_ds, batch_size16, shuffleTrue, num_workers4, pin_memoryTrue) val_loader DataLoader(val_ds, batch_size32, shuffleFalse)参数含义按经验说清楚Resize到224x224是ResNet预训练权重的标准输入尺寸Normalize的三个通道均值和方差来自ImageNet统计量迁移学习场景下沿用这套数值即可自己重新统计反而破坏预训练特征分布。ColorJitter三个通道的幅度控制在0.1只模拟光线细微变化过强会把肤色本身改变导致性别特征被掩盖。batch_size取16在小数据集上已够用显存紧张的机器降到8pin_memoryTrue在GPU训练时能减少主机到设备的数据拷贝时间。3.3 冻结主干训练分类头最小可运行训练脚本第一次训练只让最后的全连接层学习其余层全部冻结。这个做法在300张数据上效果直接训练速度快并且能明显看到fc层从0到收敛的过程方便你判断数据集本身是否可分。import torch import torch.nn as nn from torchvision import models device torch.device(cuda if torch.cuda.is_available() else cpu) model models.resnet18(weightsmodels.ResNet18_Weights.IMAGENET1K_V1) for param in model.parameters(): param.requires_grad False in_features model.fc.in_features model.fc nn.Linear(in_features, 2) model model.to(device) optimizer torch.optim.Adam(model.fc.parameters(), lr1e-3) criterion nn.CrossEntropyLoss() best_acc 0.0 for epoch in range(10): model.train() for images, labels in train_loader: images, labels images.to(device), labels.to(device) loss criterion(model(images), labels) optimizer.zero_grad() loss.backward() optimizer.step() model.eval() correct total 0 with torch.no_grad(): for images, labels in val_loader: images, labels images.to(device), labels.to(device) _, preds torch.max(model(images), 1) correct (preds labels).sum().item() total labels.size(0) val_acc correct / total print(fepoch{epoch1:02d} val_acc{val_acc:.4f}) if val_acc best_acc: best_acc val_acc torch.save(model.state_dict(), gender_resnet18_best.pth)代码逻辑不复杂requires_gradFalse把除fc外的参数全部冻结反向传播不会计算和更新它们显存占用显著下降model.fc替换成输出2维的线性层对应man和woman两个类别CrossEntropyLoss内部自带softmax不需要在fc后面再接一层。优化器只接收model.fc.parameters()lr取1e-3这是线性分类头常见的起始学习率。10轮结束后如果val_acc仍在持续上升再考虑把最后两个残差块解冻用1e-5的小学习率微调20轮通常还能再提2到3个点。提示如果数据集的验证集目录叫val而不是validation把上面代码里的路径同步改掉即可ImageFolder不关心目录名本身是什么只关心它下面的类别子目录。4. 增强强度、早停与混淆矩阵把300张的性能细节看清4.1 面向小数据集的增强参数选取小数据集上增强的强度比模型结构更敏感。照搬ImageNet训练时的增强策略会把下颌线、眉骨这类性别判别特征直接抹掉完全不做增强模型又容易记住背景。人脸左右对称性好水平翻转是性价比较高的增强旋转角度要控制在10度以内太大就会产生黑边随机擦除面积限制在画面10%以内覆盖面积过大会把眼睛或下巴整个遮住。增强项推荐参数设置理由RandomHorizontalFlipp0.5左右镜像不改变性别语义ColorJitterbrightness0.1, contrast0.1, saturation0.1模拟真实手机光线变化RandomRotation5~10度容忍拍摄角度偏移RandomResizedCropscale(0.8, 1.0)模拟取景远近差异RandomErasingp0.25, scale(0.02, 0.1)防止模型依赖单一局部特征RandomResizedCrop的scale下限设0.8而不是0.08原因很具体裁剪区域过小时人脸主体被大部分裁掉剩下的背景区域没有性别信息模型只会学到噪声。RandomErasing的p值超过0.3后训练损失会明显抖动模型在验证集上的表现反倒下降。4.2 超参与早停配置表训练分类模型时我习惯把超参集中在一个表格里方便复现和调参超参取值调整方向lr1e-3fc层1e-5解冻后训练震荡时减半weight_decay1e-4只对fc层生效即可batch_size16显存不够降到8max_epoch20~30以早停结果为准early_stop_patience5验证loss连续5轮不降则停lr_schedulerReduceLROnPlateau, patience2, factor0.5验证loss停滞时降学习率早停的实现很简单但要注意监控对象是验证集loss而不是accuracy。验证集只有60张图accuracy的随机波动很大某轮多错一张就掉1.7个百分点而loss的连续性更好更能反映模型是否还在真正进步。简单写法如下patience, wait, best_loss 5, 0, float(inf) for epoch in range(30): train_loss train_one_epoch() val_loss, val_acc evaluate() if val_loss best_loss: best_loss val_loss wait 0 torch.save(model.state_dict(), gender_best.pth) else: wait 1 if wait patience: print(fearly_stop at epoch{epoch}) break这段逻辑里best_loss只在新低时更新模型权重也在同一时刻保存wait超过patience就停止训练。这样最终留下的权重永远是验证集loss最低的那一版而不是最后一轮的结果。4.3 用混淆矩阵做分类评估定位分错在哪二分类只看accuracy不够60张验证集里accuracy是72%还是80%无法告诉你错误集中在哪个方向。用sklearn的confusion_matrix直接打出来看from sklearn.metrics import confusion_matrix all_preds, all_labels [], [] model.eval() with torch.no_grad(): for images, labels in val_loader: images, labels images.to(device), labels.to(device) _, preds torch.max(model(images), 1) all_preds.extend(preds.cpu().tolist()) all_labels.extend(labels.cpu().tolist()) cm confusion_matrix(all_labels, all_preds) print(行真实标签列预测标签) print(cm)输出的2x2矩阵里第0行对应man真实样本第1行对应woman真实样本第0列是预测为man的数量第1列是预测为woman的数量。常见的失败模式有两种长发男性被预测成woman以及短发素颜女性被预测成man。如果是前者占多数说明模型把头发长度当成了性别信号需要增加中长发男性的训练样本或者加强头发区域以外的特征如果是后者通常和训练集里女性样本多为长发有关属于典型的数据偏置。把这些分错图片的文件路径定位出来回到原始目录看拍摄条件再决定是补样本还是清理标注。5. 用softmax置信度做第二轮数据清洗让数据集越用越干净训练完成的模型在训练集上准确率通常很高但softmax输出的置信度分布能暴露另一类问题某些图片模型虽然答对了概率却只有0.55说明它“记得”这张图却没有学到可泛化的特征。把这类低置信度样本筛出来人工复查比随机抽检效率高得多。现在用训练好的模型对整个train目录过一遍import torch from PIL import Image from torchvision import transforms val_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]), ]) model.eval() model.to(device) with torch.no_grad(): with open(low_confidence.txt, w, encodingutf-8) as f: for img_path, label in train_ds.samples: img Image.open(img_path).convert(RGB) tensor val_transform(img).unsqueeze(0).to(device) prob torch.softmax(model(tensor), dim1) conf, pred torch.max(prob, 1) if conf.item() 0.8: f.write(f{img_path}\tlabel{label}\tpred{pred.item()}\tconf{conf.item():.3f}\n)conf.item()取的是softmax最大概率值低于0.8才记录。0.8是经验阈值如果输出行数超过全部样本的20%说明模型对训练集本身都没学透先别急着清洗回到第3章的预处理和训练环节排查。读取图片时用convert(RGB)统一通道数避免灰度图或带alpha通道的PNG在后期处理时类型报错。拿到low_confidence.txt后按行号定位图片分四种情况处理标注与内容不符移到hard_cases目录图片模糊到人眼都无法判断性别移出训练集多张同一人的相似照片重复出现只保留画质最高的一张确实清晰且标注正确保留并观察下一轮置信度是否提升。清洗完重新训练一遍对比test集的混淆矩阵观察原先分错的样本有没有归位。把每轮清洗后的评估数字记下来下一批新的手机采集图加入时整套流程直接重跑数据质量和模型精度会一轮一轮交替上升。本文还有配套的精品资源点击获取
上一篇/下一篇内容由系统自动关联 返回资讯列表 →