HOG+SVM人脸性别分类实践:从数据准备到模型落地
发布时间:2026/9/13 4:35:30来源:尧图网络
简介江南大学机器学习大作业“人脸图像性别分类”的完整源码与文档面向高校机器学习初学者及期末大作业、课程设计场景提供可直接运行并理解的Python实现。压缩包共16个文件包含py与ipynb主程序、csv训练与提交数据、h5权重模型、pdf与md说明文档、html预览页面等整体约50.84MB已按功能分层存放便于快速部署和对照学习。目前已有295人浏览学习。项目附带详尽代码注释与说明文档从数据预处理、模型训练到预测提交均有清晰示例新手也能看懂同时内置训练好的权重文件下载后简单配置即可直接运行适合作为满分级大作业参考或二次开发基线。文件类型涵盖训练集、预测集、提交样例及权重存档并配有实验记录xlsx与说明txt可帮助理解完整建模流程快速复用或扩展。1. 人脸图像性别分类为什么传统机器学习方案依然是最稳的性别分类看起来是个二分类问题真做起来却比想象中麻烦。同一张脸换个光照、转个角度、戴个口罩分类器就可能翻车。神经网络方案在Kaggle上能刷到98%以上但那要大量数据和GPU训练时间机器学习课程大作业的典型约束是几百到几千张图、一台普通笔记本、两周期限。所以HOG特征加SVM这个组合成了这类作业里最常见也最可靠的工程方案特征提取可解释、训练不依赖GPU、分类器参数少且容易调。本文从数据准备讲到模型落地把这条链路上每一步的参数选择和踩坑点拆开代码可以直接拿走适配自己的数据集。2. 人脸图像性别分类的数据准备目录结构、标签编码与样本平衡2.1 原始数据整理与目录结构设计性别分类大作业踩得最多的坑不是模型效果差而是数据目录混乱导致训练脚本反复改路径。建议一开始就按固定结构组织数据所有脚本统一从根目录读取。gender_ml/ ├── data/ │ ├── raw/ # 原始下载图片 │ ├── train/ # 训练集 │ │ ├── male/ │ │ └── female/ │ ├── val/ # 验证集 │ │ ├── male/ │ │ └── female/ │ └── test/ # 测试集保持未知 │ ├── male/ │ └── female/ ├── features.py ├── train_svm.py HOG特征提取脚本与SVM训练脚本分类存放中间产物如特征矩阵和模型文件也单独建目录类目录方式比单个CSV记录标签更直观训练时直接用ImageFolder或者os.listdir扫描就能拿到标签不需要额外维护标签文件。如果是自己爬或下载的数据文件名里往往带着性别信息比如ID_male_001.jpg这种命名习惯保留下来后续写解析脚本会省很多事。2.2 标签解析与数据集划分脚本拿到原始数据后第一件事是写脚本把文件名解析成结构化信息再按分层抽样划分训练、验证、测试三份。这里用train_test_split的stratify参数保证男女比例在每份数据中一致。import os import random import shutil from sklearn.model_selection import train_test_split random.seed(42) def parse_label(filename): if male in filename: return male elif female in filename: return female else: raise ValueError(f无法从文件名识别性别: {filename}) # 收集原始图片路径和对应标签 raw_dir data/raw samples [] # (图片路径, 标签) for fname in os.listdir(raw_dir): if not fname.lower().endswith((.jpg, .jpeg, .png)): continue label parse_label(fname) samples.append((os.path.join(raw_dir, fname), label)) # 先按 8:1:1 划分stratify 保证每个子集内性别比例与原数据集一致 trains, temp train_test_split( samples, test_size0.2, stratify[s[1] for s in samples], random_state42 ) vals, tests train_test_split( temp, test_size0.5, stratify[s[1] for s in temp], random_state42 ) # 复制到目标目录 for split_name, split_data in [(train, trains), (val, vals), (test, tests)]: for src_path, label in split_data: dst_dir fdata/{split_name}/{label} os.makedirs(dst_dir, exist_okTrue) dst_path os.path.join(dst_dir, os.path.basename(src_path)) shutil.copy2(src_path, dst_path) print(ftrain: {len(trains)}, val: {len(vals)}, test: {len(tests)})代码逻辑分三层解析文件名提取标签、分层划分数据、按目录复制图片。stratify参数是关键如果原始数据里男女比例是3:7不做分层抽样划分后某份子集可能只有男性样本SVM学出来的决策边界就是偏的。random_state42固定随机种子保证每次运行划分结果一致作业报告里也可以明确写清楚这个参数。2.3 样本平衡检查与扩增策略人脸性别数据集一个典型问题是女性样本数量明显少于男性尤其是从影视剧或新闻截图里爬取的数据。划分完目录后先统计一下每类数量for split_name in [train, val, test]: male_count len(os.listdir(fdata/{split_name}/male)) female_count len(os.listdir(fdata/{split_name}/female)) print(f{split_name}: male{male_count}, female{female_count})如果训练集中女性样本不足男性的三分之一分类器会倾向于把模糊样例判成男性因为这样正确率更高。大作业场景下不宜引入复杂的生成模型我一般用两种轻量手段一是对少数类做水平翻转和随机亮度抖动扩增二是调整SVM类别权重class_weightbalanced后者在后续训练脚本里一行代码就能设置。前者要注意翻转后图片仍符合真实场景人脸左右翻转不影响性别判断可以放心用。3. 用HOG提取人脸纹理特征参数怎么设、特征维度怎么算3.1 为什么选HOG而不是原始像素或深度学习特征性别分类这件事能依赖的信息是全局脸型轮廓加上局部纹理差异比如下颌宽度、眉骨突出程度、皮肤纹理细腻度。原始像素直接拉平当特征维度高且包含大量光照噪声线性分类器很难直接切开两类样本。深度学习特征如人脸识别模型中间层效果好但需要预训练模型和显存机器学习大作业里未必允许使用也偏离了“手写特征经典分类器”的考察点。HOG梯度方向直方图的核心思想是用局部区域内梯度方向的分布来描述物体形状。人脸的五官边缘处梯度响应强不同性别在这类局部梯度模式上有可区分的统计差异而且HOG对光照变化做了归一化比原始像素鲁棒得多。这也是为什么HOG在行人检测、人脸检测时代是标配特征放到性别分类任务里依然可用。3.2 HOG参数表每个参数对特征的影响以skimage.feature.hog为例常用参数表如下参数常用值调大/调小的影响orientations9方向bin数调大更细但易过拟合常取9pixels_per_cell(8, 8)每个cell的像素尺寸调大丢失细节调小维度爆炸cells_per_block(2, 2)block内cell数调大归一化范围更大但特征更平滑block_normL2-Hys归一化方式L2-Hys对局部光照变化最稳pixels_per_cell(8, 8)意味着每个8×8像素的小区域统计一个梯度直方图人脸的关键细节眼睛、嘴唇边缘通常在8到16像素尺度上这个取值能保留足够的判别信息。cells_per_block(2, 2)将相邻4个cell合并为一个block做归一化抑制光照和对比度变化。如果数据集图像分辨率偏低可以试试(16, 16)特征维度会降四倍训练更快代价是准确率略微下降。3.3 HOG特征提取代码与维度计算验证import cv2 from skimage.feature import hog IMG_SIZE (128, 128) # 统一缩放到 128x128 def extract_hog_features(image_path): # 读入为灰度图去掉颜色信息HOG本身基于梯度颜色贡献不大 img cv2.imread(image_path, cv2.IMREAD_GRAYSCALE) if img is None: raise FileNotFoundError(f图片读取失败: {image_path}) # 统一尺寸INTER_AREA 适合缩小图片保留边缘信息 img cv2.resize(img, IMG_SIZE, interpolationcv2.INTER_AREA) # 直方图均衡化增强对比度削弱光照方向的影响 img cv2.equalizeHist(img) # visualizeFalse 只返回特征向量维度 8100 features hog( img, orientations9, pixels_per_cell(8, 8), cells_per_block(2, 2), block_normL2-Hys, visualizeFalse ) return features # 测试单张图片 feat extract_hog_features(data/train/male/sample_001.jpg) print(f特征维度: {feat.shape}) # 期望输出 (8100,)HOG特征维度计算方式值得在报告里写清楚128×128图像按8×8划分得到16×16共256个cellcells_per_block(2, 2)的滑动步长是1个cell所以block数量为(16-1)×(16-1)225每个block包含2×24个cell每个cell有9个方向bin因此维度为225×4×98100。这个数字不依赖具体实现自己手写HOG也能验证。equalizeHist这一步不是必须的但对光照差异大的数据集能明显提升SVM的稳定性代价几乎为零。4. SVM分类器训练核函数选型、交叉验证与评估指标4.1 线性核还是RBF核小数据集上的工程判断拿到8100维HOG特征后分类器首选SVM。SVM在中等维度、小样本场景下有理论优势而且分类边界只由支持向量决定不容易被全局数据分布带偏。核函数选择上线性核和RBF核是主要候选。线性核只有一个参数C训练快、可解释性强HOG特征是手工设计的局部统计特征线性可分性通常不错RBF核引入gamma参数能拟合更复杂的决策边界理论上限更高但在样本量只有几千时容易过拟合。我的做法是先用线性核跑一版baseline记录准确率再用网格搜索跑RBF核如果提升不到1个百分点就用线性核因为大作业报告里线性SVM的决策函数更容易解释。实际经验是对于清晰的正脸数据集线性核能达到93%~96%准确率RBF核调好参数能到95%~98%。差距存在但要权衡调参时间。数据集模糊或带遮挡时RBF核的优势会更明显。4.2 特征标准化与训练PipelineHOG特征各维度的数值范围不是天然一致的尤其是不同block的梯度能量差异可能很大。SVM在特征尺度差异大时优化不稳定必须先做标准化。用StandardScaler对每个维度减去均值并除以标准差特征分布变成零均值单位方差。import joblib import numpy as np from sklearn.svm import SVC from sklearn.pipeline import make_pipeline from sklearn.preprocessing import StandardScaler from sklearn.model_selection import GridSearchCV from sklearn.metrics import classification_report, confusion_matrix def load_dataset(data_dir): 遍历 train/val/test 目录返回特征矩阵和标签 X, y [], [] for label in [male, female]: label_dir os.path.join(data_dir, label) for fname in os.listdir(label_dir): fpath os.path.join(label_dir, fname) X.append(extract_hog_features(fpath)) y.append(label) return np.array(X), np.array(y) # 训练/验证阶段加载训练集 X_train, y_train load_dataset(data/train) print(f训练集形状: {X_train.shape}) # (N, 8100) # StandardScaler 必须在训练集上拟合再用于验证集和测试集 pipeline make_pipeline(StandardScaler(), SVC(class_weightbalanced, random_state42)) # 网格搜索 param_grid [ {svc__kernel: [rbf], svc__C: [0.1, 1, 10, 100], svc__gamma: [scale, 0.01, 0.001, 0.0001]}, {svc__kernel: [linear], svc__C: [0.01, 0.1, 1, 10]} ] grid GridSearchCV( pipeline, param_grid, cv5, scoringaccuracy, n_jobs-1, verbose1 ) grid.fit(X_train, y_train) print(f最优参数: {grid.best_params_}) print(f交叉验证准确率: {grid.best_score_:.4f}) # 保存最优模型 joblib.dump(grid.best_estimator_, models/gender_svm.joblib)class_weightbalanced会根据各类样本数量自动放大少数类的错分代价解决男女样本不平衡问题。网格搜索里我用了5折交叉验证每组参数组合训练5次10组参数就是50次SVM训练。8100维特征、2000张训练图片普通笔记本上线性SVM单次训练只需几秒RBF核慢一些整体跑完大约10分钟属于可接受范围。4.3 验证集评估准确率不是唯一指标网格搜索选出的模型要在独立的验证集上评估验证集不参与任何训练过程。只看准确率会掩盖问题如果测试集里男性占了70%一个把所有样本都判成男性的分类器准确率就有70%看起来不差实则毫无用处。X_val, y_val load_dataset(data/val) # 注意标准化的均值和标准差已经在 pipeline 内部继承无需额外处理 y_pred grid.best_estimator_.predict(X_val) print(classification_report(y_val, y_pred)) print(confusion_matrix(y_val, y_pred))classification_report会输出每个类别的precision、recall和f1-score。对性别分类来说两个类别的recall要同时看male的recall是男性被正确识别的比例female的recall是女性被正确识别的比例。如果男性recall是0.97而女性只有0.88说明分类器偏向把模糊样本判为男性这就是数据不平衡留下的后遗症需要回头调class_weight或扩增少数类。混淆矩阵的四格数据正好对应大作业报告里的案例分析哪些男性被误判为女性、哪些女性被误判为男性从中挑出样本看图能找到共性问题比如长发男性、戴眼镜女性通常是错分重灾区。4.4 调参参数速查表参数范围调参直觉svc__C0.01~100C越大越严格拟训练集过拟合C越小决策边界越平滑svc__gamma0.0001~0.1gamma越大RBF影响范围越小易过拟合scale自动按特征数计算pixels_per_cell(8,8)或(16,16)图像分辨率低选(16,16)防特征维度过高class_weightbalanced或None样本不平衡时优先设为balanced5. 性别分类模型落地预测脚本编写与文档说明的隐藏扣分点5.1 命令行预测脚本让模型可以被复用训练完成后写一个独立的预测脚本是展示工程能力的关键一步。用argparse接受图片路径参数加载保存的模型文件直接输出性别判断和置信度。置信度用SVM的decision_function归一化得到方便在报告里展示。import argparse import joblib import numpy as np from features import extract_hog_features def predict_gender(image_path, model_pathmodels/gender_svm.joblib): model joblib.load(model_path) features extract_hog_features(image_path).reshape(1, -1) # decision_function 返回样本到超平面的带符号距离取绝对值得置信度 score model.decision_function(features)[0] gender model.predict(features)[0] confidence 1.0 / (1.0 np.exp(-abs(score))) return gender, confidence if __name__ __main__: parser argparse.ArgumentParser(description人脸图像性别分类) parser.add_argument(image, typestr, help待预测图片路径) parser.add_argument(--model, typestr, defaultmodels/gender_svm.joblib, help训练好的模型文件路径) args parser.parse_args() gender, conf predict_gender(args.image, args.model) print(f预测性别: {gender}, 置信度: {conf:.2%})decision_function的绝对值转换为Sigmoid形式的置信度是一种近似手段不能当作真实的概率使用但足以支持“分类器对这张图的判断有多确定”这种讨论。路径参数化让模型文件可以单独替换新数据到来时不需要改代码。5.2 文档说明的组织方式与图表要求大作业的文档说明部分评分点通常不在字数而在结构完整度和实验设计的严谨性。一份能拿高分的报告应该包含实验目的与问题定义、数据来源与预处理流程、特征提取原理与参数选择理由、模型选型与调参过程、实验结果对比与分析、结论与改进方向。图表方面至少要有三张数据预处理前后的样本对比图、提取HOG后的特征可视化图、SVM调参过程中的交叉验证准确率变化曲线。特征可视化可以用skimage.feature.hog的visualizeTrue返回值输出HOG叠加图直观展示轮廓和纹理信息。混淆矩阵用热力图显示这张图在答辩时几乎是必问的。5.3 容易被扣分的四个技术点第一标准化必须在训练集上拟合再应用到验证集和测试集不能对整个数据集一起做StandardScaler否则会造成信息泄漏交叉验证结果虚高。第二随机种子不固定会导致每次运行结果不同所有划分和模型初始化都要设置random_state。第三特征提取包含随机增强操作时增强只应用于训练集验证和测试集要保持原始状态。第四模型的评估指标报告里只写了准确率缺少按性别分开的recall对比一旦面试官或助教问到少数类表现就会卡住。这些细节代码层面试错成本很低但报告里写出来会显著拉高作业完成度的观感。从数据处理到预测脚本每一步都有明确的验证手段这个链路本身就是完整的一课。本文还有配套的精品资源点击获取
网站建设高端定制企业官网