尧图精选

300张手机人脸图跑性别分类:迁移学习与两段式链路实战

🕒 发布时间:2026/10/1 18:06:31 📁 来源:尧图网络
简介这份资源面向计算机视觉方向的科研人员、算法工程师与深度学习入门者提供一套可直接用于性别检测与分类训练的人脸数据集。数据源自真实手机采集环境共300张高质量人脸图片按woman与man两个子集完成分类划分与标注覆盖不同光线、表情与姿态有助于提升模型泛化能力与性别分类准确率。压缩包共505个文件约339.41MB除jpg、png图片外还包含大量py脚本、config与proto配置文件、pbtxt标签映射、pb模型文件及checkpoint权重并附带ipynb笔记、md说明与少量视频素材可支撑从人脸检测到特征提取、分类训练的完整流程。资源同时涉及基于Faster R-CNN、Mean-shift与卡尔曼滤波的人流统计思路便于拓展智能监控与商业分析场景。目前已有59人学习适合希望快速搭建性别分类实验、复用检测配置与排错思路的读者参考。1. 300 张手机人脸图够不够跑一个性别分类模型先说结论够但前提是你得把它当成一个「小样本、强先验」的任务来做而不是指望它像 ImageNet 那样喂出一个通用模型。我手上这类数据集——woman / man 两类、300 张真实手机采集、已经分好类标好注——最典型的用法不是从零训练而是做迁移学习里的微调或者验证一条轻量流水线能不能跑通。手机采集意味着光照、角度、肤色、遮挡都带着真实世界的脏这恰恰是它的价值实验室里拿公开大头照训出来的模型一上真实场景就翻车而这份数据天生带这种「脏」。它适合谁做深度学习入门、想跑通第一个图像分类实战的人做毕设需要一个干净二分类起点的人以及想验证人脸性别检测这条链路检测框 → 裁剪 → 分类能不能落地的人。300 张的规模决定了它不适合当唯一数据源但非常适合当微调集和验证集。下面我按「先立住原理、再动手复现、最后讲坑」的顺序把这条链路拆开讲清楚。2. 人脸性别检测这条链路从检测框到二分类的完整拆解2.1 为什么性别分类不是「一个 CNN 就完事」很多人一上来就想拿 ResNet 直接吃整张图输出男女这在 300 张的规模下几乎必然过拟合。真实链路是两段式的先用检测把人脸框出来再对裁剪后的人脸做分类。原因很直接——整张手机照片里背景、衣服、发型占了大半像素性别信号集中在人脸区域让分类网络去学「哪里是人脸」是浪费它本就不多的容量。检测这一环常见做法是用现成的人脸检测器MTCNN、RetinaFace、YOLO 的人脸变体都行把每张图的人脸框和关键点拿到。分类这一环才是我们要训的部分。两段式还有个好处检测器可以复用你换分类头、换数据、换类别都不用重训检测。300 张数据下这个解耦能显著降低你要训的参数量。提示如果你的数据里一张图只有一张脸检测这步可以离线跑一次把裁剪结果存成新图训练时直接读裁剪图省掉每轮重复检测的开销。2.2 数据划分别用随机切分按人切300 张里如果同一个人出现多张随机切分会让同一个人的脸同时进训练集和验证集验证准确率虚高上线就露馅。正确做法是按身份切分——同一个人只出现在一个集合里。如果数据没标身份退而求其次按拍摄场景或时间切尽量让训练和验证的分布有差异这样验证分数才有参考价值。常见比例是 8:1:1 或 7:2:1。300 张的话训练 240、验证 30、测试 30 是个合理起点。类别要均衡woman 和 man 各占一半左右如果原始数据偏了训练时用加权采样或类别权重补偿。import os, random, shutil SRC faces_dataset # 原始数据结构: SRC/woman/*.jpg, SRC/man/*.jpg DST split_dataset # 输出: DST/train|val|test/{woman,man} RATIO (0.8, 0.1, 0.1) # 训练/验证/测试 SEED 42 random.seed(SEED) for cls in [woman, man]: files sorted(os.listdir(os.path.join(SRC, cls))) random.shuffle(files) n len(files) n_train int(n * RATIO[0]) n_val int(n * RATIO[1]) splits { train: files[:n_train], val: files[n_train:n_train n_val], test: files[n_train n_val:], } for split, names in splits.items(): out_dir os.path.join(DST, split, cls) os.makedirs(out_dir, exist_okTrue) for name in names: shutil.copy(os.path.join(SRC, cls, name), os.path.join(out_dir, name)) print(cls, total, n, -, {k: len(v) for k, v in splits.items()})这段脚本做的是按类别分层切分保证每个集合里两类都有。RATIO控制比例SEED固定后结果可复现。注意它只做了随机切分如果你的数据有身份信息把files换成按身份分组后的列表再切逻辑一样。跑完检查一下每个 split 下两类的数量偏差超过 10% 就调SEED或手动干预。2.3 用迁移学习把 300 张用出 3000 张的效果300 张从零训一个 CNN验证集准确率大概率在 60% 上下晃因为参数比样本多太多。迁移学习是这里的正解拿在 ImageNet 上预训练过的骨干ResNet18、MobileNetV3、EfficientNet-B0 都行换掉最后的全连接层冻结前面的卷积层只训分类头。等分类头收敛了再解冻最后几个 block 做小学习率微调。为什么有效预训练骨干已经学会了边缘、纹理、五官结构这些通用特征性别分类只需要在这些特征上再学一层线性可分边界。300 张足够学这个边界不够学底层特征。MobileNetV3-Small 这类轻量骨干在 300 张上尤其稳参数少、过拟合风险低还能部署到边缘设备。import torch, torch.nn as nn from torchvision import models, transforms, datasets from torch.utils.data import DataLoader device cuda if torch.cuda.is_available() else cpu # 手机图分辨率不一统一到 224训练集加轻量增强 train_tf transforms.Compose([ transforms.Resize((224, 224)), transforms.RandomHorizontalFlip(), transforms.ColorJitter(0.2, 0.2, 0.2), transforms.ToTensor(), transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]), ]) eval_tf transforms.Compose([ transforms.Resize((224, 224)), transforms.ToTensor(), transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]), ]) train_ds datasets.ImageFolder(split_dataset/train, train_tf) val_ds datasets.ImageFolder(split_dataset/val, eval_tf) train_ld DataLoader(train_ds, batch_size16, shuffleTrue, num_workers2) val_ld DataLoader(val_ds, batch_size16, shuffleFalse, num_workers2) model models.mobilenet_v3_small(weightsmodels.MobileNet_V3_Small_Weights.DEFAULT) for p in model.features.parameters(): p.requires_grad False # 先冻结骨干 model.classifier[3] nn.Linear(model.classifier[3].in_features, 2) model model.to(device) criterion nn.CrossEntropyLoss() optimizer torch.optim.AdamW(model.classifier.parameters(), lr1e-3, weight_decay1e-4)关键参数说明batch_size16是 300 张规模下的稳妥值太小梯度噪声大太大一轮迭代次数不够lr1e-3只作用于分类头因为骨干冻结了weight_decay1e-4抑制过拟合。增强只用了水平翻转和轻微颜色抖动手机图本身光照差异大颜色抖动幅度别开太大否则把性别相关的肤色信号也抖没了。训练循环里每轮记录验证准确率和损失验证损失连续 5 轮不降就停这是 300 张数据下防过拟合的基本操作。3. 训练、评估与部署把模型跑成能用的东西3.1 训练循环与早停的写法接着上面的代码训练循环要同时盯训练损失和验证指标。300 张数据下训练损失很快降到接近 0这时候验证损失如果开始上升就是过拟合的信号。早停不是可选项是必需品。best_acc, patience, wait 0.0, 5, 0 for epoch in range(30): model.train() for x, y in train_ld: x, y x.to(device), y.to(device) optimizer.zero_grad() loss criterion(model(x), y) loss.backward() optimizer.step() model.eval() correct total 0 with torch.no_grad(): for x, y in val_ld: x, y x.to(device), y.to(device) pred model(x).argmax(1) correct (pred y).sum().item() total y.size(0) acc correct / total print(fepoch {epoch} val_acc {acc:.3f}) if acc best_acc: best_acc, wait acc, 0 torch.save(model.state_dict(), best_gender.pt) else: wait 1 if wait patience: print(early stop) breakpatience5意味着验证准确率 5 轮没刷新就停。保存的是验证集上最好的权重不是最后一轮的这点很多人会搞错导致部署时用的是过拟合的版本。best_gender.pt就是最终产物。3.2 评估不能只看准确率300 张的测试集只有 30 张准确率一个小数点波动就是一张图的事参考价值有限。必须看混淆矩阵和每类召回率。性别分类里如果 woman 召回率明显低于 man说明模型偏向多数类或某些特征比如长发被过度依赖。这时候要回去看误判样本多半是侧脸、遮挡、光照极端的图。from sklearn.metrics import confusion_matrix, classification_report model.load_state_dict(torch.load(best_gender.pt)) model.eval() ys, ps [], [] with torch.no_grad(): for x, y in DataLoader(datasets.ImageFolder(split_dataset/test, eval_tf), batch_size16): ys y.tolist() ps model(x.to(device)).argmax(1).cpu().tolist() print(confusion_matrix(ys, ps)) print(classification_report(ys, ps, target_names[man, woman]))classification_report会给出每类的 precision、recall、f1。测试集小的时候f1 比准确率更能反映真实水平。如果某一类 f1 低于 0.8先别急着调模型去翻误判图八成是数据问题而不是模型问题。3.3 推理阶段的预处理必须和训练一致部署时最常见的翻车是预处理不一致训练用了 Normalize推理忘了训练 Resize 到 224推理直接喂原图。结果就是线下 90%、线上 60%。把预处理封装成一个函数训练和推理共用。def predict(img_path, model, tf, device): from PIL import Image img Image.open(img_path).convert(RGB) x tf(img).unsqueeze(0).to(device) # 加 batch 维度 model.eval() with torch.no_grad(): prob torch.softmax(model(x), dim1)[0] idx prob.argmax().item() return [man, woman][idx], prob[idx].item()tf必须传eval_tf不能传带增强的train_tf。返回置信度是为了让你设阈值——低于 0.7 的样本可以打上「不确定」标签交人工复核这在真实业务里比强行二分类更靠谱。4. 避坑与排查300 张数据最容易踩的五个坑4.1 验证准确率 95%上线只有 60%现象验证集分数漂亮实际用起来一塌糊涂。原因随机切分导致同一人的多张图跨了训练和验证模型记住了人脸而不是性别特征。解决按身份切分没有身份信息就按拍摄批次切确保验证集的人训练时没见过。4.2 模型把长发一律判成 woman现象混淆矩阵里 woman 召回率高得离谱但短发女性全判错。原因数据里 woman 类长发占比过高模型学到了「长发女」这个捷径。解决检查数据分布补充短发女性样本训练时加随机裁剪和遮挡增强逼模型看脸而不是看头发。4.3 训练损失降到 0.01验证损失开始涨现象训练集几乎完美验证集停滞。原因300 张数据下模型容量过剩过拟合。解决冻结更多层、加 weight_decay、加 dropout、减小学习率或者换更小的骨干。早停一定要开。4.4 推理速度慢到没法用现象单张推理几百毫秒。原因没冻结梯度、没切 eval 模式、或者骨干选太大。解决推理时torch.no_grad()model.eval()骨干换 MobileNetV3-Small必要时导出 ONNX 或 TorchScript 加速。4.5 换一批手机拍的图效果断崖下跌现象同型号手机拍的没问题换机型就崩。原因不同手机的成像风格、白平衡、锐化差异被模型当成了特征。解决训练时加颜色抖动和模糊增强模拟不同成像如果目标机型明确用该机型补拍几十张做微调比调参管用。5. 把 300 张用出花半监督与主动学习的进阶玩法300 张的天花板摆在那想再往上走靠的不是调参是扩数据。但人工标 3000 张成本高这时候半监督和主动学习就派上用场了。思路是用当前模型去预测大量未标注的人脸图挑出置信度高的伪标签加入训练集置信度低的交人工标注。这样每一轮标注预算都花在模型最不确定的样本上效率比随机标高好几倍。具体操作上先拿现有 300 张训一个基线模型然后收集一批未标注人脸图同场景、同机型最好用基线模型推理把 softmax 概率在 0.9 以上的直接当伪标签0.5 到 0.7 之间的挑出来人工标。伪标签样本训练时给一个小于 1 的损失权重比如 0.3避免错误伪标签带偏模型。每轮迭代后重新评估通常两三轮就能把有效训练集从 300 扩到 1000 以上验证准确率能再提 5 到 10 个点。验证这套流程有没有效别只看准确率看模型在「困难样本」上的表现——侧脸、遮挡、暗光这些。建一个 50 张的困难验证集每轮迭代都在这上面测这个分数涨了才说明模型真的变强了而不是在简单样本上刷分。我自己做这类小数据集项目习惯是先把困难验证集建好再开始训不然训到最后都不知道模型到底行不行。这套流程跑通一次你手上任何 300 张级别的人脸二分类数据都能复用换类别、换场景只改数据路径和类别名。希望帮到你。本文还有配套的精品资源点击获取
上一篇/下一篇内容由系统自动关联 返回资讯列表 →