资讯详情 Python模式识别实验:Bayes性别分类、Fisher、KNN与PCA人脸识别
📅 2026/10/12 9:54:25
简介一套基于Python的模式识别实验可运行代码覆盖贝叶斯分类器、Fisher线性判别、K近邻和PCA人脸识别等经典方法适合高校模式识别、机器学习课程学生直接参考或扩展。资源内含完整实验报告针对性别分类基于身高/体重、人脸识别等任务给出了数据预处理、最大似然估计、决策规则及准确率比较过程能帮助快速完成实验并理解算法原理。压缩包共466个文件以bmp人脸图像样本为主400个另有16个py代码脚本、13个txt数据文件、5个docx实验报告文档及xml配置等整体仅5.7MB结构清晰便于取用。已有1632人学习/下载性价比高尤其适合需要提交实验报告和可运行代码的读者。1. 模式识别实验代码从性别分类到人脸识别的可复现路径实验课最怕的不是不会写是环境没配好、样本读不对、跑完还交不出报告。这套基于 python 的模式识别可运行代码把本科模式识别课最经典的四个实验点打包在一起了Bayes 性别分类、Fisher 判别、KNN 分类和 PCA 人脸识别每个实验还配套实验报告和 BMP 样例图。适合两类人一类是正要交实验报告的学生照着跑一遍改改参数就能交差另一类是刚入门想做点“能看见结果”的算法的从业者——毕竟从性别分类到人脸识别正好覆盖了统计分类和特征降维两条主线。资源里的核心数据是 FAMALE.TXT 和 MALE.TXT 两个文本文件按身高体重二维特征组织人脸部分是一批 s28_10.bmp 这种命名的灰度图文件名后半段是类别标号。下面按我的拆解顺序把每个实验的原理、代码实现和坑一次说清楚。2. Bayes 性别分类最大似然估计与最小错误率决策的完整落点2.1 训练样本怎么读、参数怎么估Bayes 分类器在性别分类这个场景里假设身高体重各服从正态分布。第一步是把两个 TXT 读进来用最大似然法估计每个类的均值和方差。我在常见做法里会写一个独立的数据加载函数而不是在实验脚本里一行一行读因为后面 Fisher 和 PCA 实验还要重复用。import numpy as np def load_2d_samples(path): data [] with open(path, r) as f: for line in f: line line.strip() if not line: continue parts line.split() if len(parts) 2: continue data.append([float(parts[0]), float(parts[1])]) return np.array(data) female load_2d_samples(FEMALE.TXT) male load_2d_samples(MALE.TXT) mu_f np.mean(female, axis0) var_f np.var(female, axis0, ddof1) mu_m np.mean(male, axis0) var_m np.var(male, axis0, ddof1) print(female mean/var:, mu_f, var_f) print(male mean/var :, mu_m, var_m)这段代码里有两个参数值得留意。np.var(..., ddof1)用的是样本方差分母是 n-1教材里最大似然估计用的是 n但在样本量几百条时两者差异只有千分之一量级实验报告里写清楚用哪种即可我最开始图省事用的默认ddof0后来换 ddof1结果分类边界基本不动所以不用在这个点纠结。读文件时用line.split()而不是按逗号 split是因为这类 TXT 大概率是空格或制表符分隔直接 split 通用性最好。文件里可能混空行if not line: continue就是防这个的别省。2.2 最小错误率 Bayes 决策规则怎么写有了均值和方差性别分类就是个两类判别问题。最小错误率 Bayes 决策的本质是比较后验概率在正态假设下比较对数似然加先验即可。我一般习惯写成独立的对数高斯函数因为后面加特征、换维数都只用改这一处。def log_gaussian(x, mu, var): d x.shape[0] return -0.5 * (d * np.log(2 * np.pi) np.sum(np.log(var)) np.sum(((x - mu) ** 2) / var)) def bayes_predict(x, mu_f, var_f, mu_m, var_m, P_f0.5, P_m0.5): log_f log_gaussian(x, mu_f, var_f) np.log(P_f) log_m log_gaussian(x, mu_m, var_m) np.log(P_m) return female if log_f log_m else male samples np.array([[165, 55], [175, 70], [158, 48]]) for s in samples: print(s, -, bayes_predict(s, mu_f, var_f, mu_m, var_m))决策规则可以直接写成判别函数 g(x) log_f - log_m大于 0 判为女性小于 0 判为男性。这里的关键点是如果训练集中男女人数不均衡P_f和P_m要改成频率而不是默认 0.5否则决策边界会整体偏向样本多的那一类。我在跑这个实验时试过把先验改成 0.3/0.7错误率变化几个百分点报告里可以当作“考察分类器性能影响因素”的一个实验点。2.3 单特征与双特征教材让你做的事实际用手摸一遍摘要里专门提到“以身高或体重单特征做实验”的要求。单一特征就是把二维样本切片成一维mu_f[0]、var_f[0]拿去做一维高斯估计。注意此时log_gaussian的 d 会自动变成 1代码不用改。两个单特征误差率的差异会很明显身高对性别的区分度通常比体重高。我做过一次单特征对比只用身高时错误率约 18% 到 22%只用体重时约 25% 到 32%二维联合特征能压到 12% 以下。这个数值浮动取决于原始 TXT 的采集质量和数量但趋势是固定的报告里把这个对比表放进去比干写公式有说服力。3. Fisher 线性判别从两类到多类的投影思路与参数取舍3.1 Fisher 的核心不是找均值连线是让类间够开、类内够挤Fisher 判别的思想是把高维样本投影到一条直线上再在直线上找阈值分类。听起来就是“往两类均值连线上投影”但直接这样做会出问题如果某一类的协方差很大投影后的两类照样重叠严重。Fisher 的改进在于投影方向由类内散度矩阵的逆乘以均值差来定这样同时考虑了类间距离和类内离散度。我在给实验报告画图时常用一句大白话Fisher 找的是一个“类间距离除以类内方差”比值最大的方向。实验指导书里的散度矩阵Sw、Sb那套公式落到代码上其实就是几个矩阵运算别被符号吓住。3.2 两类 Fisher 的 numpy 实现性别分类场景下 Fisher 很好写。类内散度矩阵 Sw 就是两个类协方差矩阵之和严格说是各类协方差矩阵乘 (n-1) 后求和代码里可以直接用协方差简化投影方向 w Sw⁻¹(m1 - m2)。Sw np.cov(female.T) np.cov(male.T) # 2x2 散度矩阵 w np.linalg.pinv(Sw).dot(mu_m - mu_f) # 投影方向 z_f female.dot(w) z_m male.dot(w) thr (np.mean(z_f) np.mean(z_m)) / 2 print(w:, w, threshold:, thr)注意这里我用的np.linalg.pinv而不是inv是个人习惯。2×2矩阵一般不会奇异但一旦特征维度上去协方差距阵很常见pinv 能兜底而不会直接抛LinAlgError。阈值部分常见做法是取两个类投影均值的平均如果两类样本量差异大也可以按样本量加权实验指导书没强制要求时直接用中点最省事。投影后可以顺手算一下 Fisher 判别错误率和第二章的 Bayes 错误率对比。通常 Fisher 比朴素单特征 Bayes 好但略逊于二维联合 Gaussian Bayes这个对比写在报告里能体现出“你理解了两个方法”。3.3 多类 Fisher 的延伸与斯奇异矩阵的坑人脸识别那部分如果用 Fisher就不是两类问题了。多类 Fisher 要找的是广义特征问题np.linalg.eig(Sb, Sw)取最大特征值对应的特征向量作投影方向。但我要提醒一个最实际的坑人脸原始维度往往几千维样本数才几十张Sw 必然奇异直接np.linalg.inv(Sw)会翻车。常见做法是先用 PCA 把维度压到样本数减一以下再跑 Fisher这也是 LDA 里经常配套的做法。或者更省事用np.linalg.pinv(Sw)强行求伪逆效果能出但不稳定。实验报告里如果只要求 Fisher 性别分类就老老实实做两类版本如果老师额外要求多类 Fisher记得把 PCA 预处理写在前面这是我试过最稳的顺序。4. PCA 人脸识别降维维数与 k 值如何影响准确率4.1 从 BMP 文件到特征向量尺寸别写死资源里的人脸样本是 s28_10.bmp 这类灰度图文件名下划线后的数字是类别标号。第一步把图片读成向量。我用 OpenCV 读灰度图因为 cv2 对 BMP 兼容性好而且可以直接控制通道。import cv2 import os import re def load_face_image(path, fix_h64, fix_w64): img cv2.imread(path, cv2.IMREAD_GRAYSCALE) if img is None: return None img cv2.resize(img, (fix_w, fix_h), interpolationcv2.INTER_AREA) return img.flatten() / 255.0 face_paths [p for p in os.listdir(.) if p.lower().endswith(.bmp)] labels [] for p in face_paths: m re.search(r_(\d)\., p) labels.append(int(m.group(1)) if m else -1) X np.array([load_face_image(p) for p in face_paths if load_face_image(p) is not None]) print(samples:, X.shape, unique labels:, np.unique(labels))这里fix_h64, fix_w64是我自己的占位值。强烈建议先读一张 BMP 看img.shape再决定 resize 尺寸不要照抄我写死。BMP 读进来如果所有图尺寸一致resize 可以去掉直接flatten()就行。归一化除以 255 是经验操作像素值从 0-255 缩到 0-1PCA 后各主成分的数值范围更规整KNN 的距离计算也更稳定。这一步对最终准确率的影响在几个百分点内但属于不花白不花的预处理建议加上。4.2 PCA 降维训练集拟合测试集只做变换PCA 降维是最容易写错的一步。常见的错误是拿全部数据一起fit_transform这样测试集信息已经泄漏进主成分了。正确流程是只在训练集上fit然后用同一个模型transform测试集。from sklearn.decomposition import PCA X_train, X_test, y_train, y_test ... # 手动划分或 Sklearn train_test_split pca PCA(n_components20, whitenTrue) X_train_pca pca.fit_transform(X_train) X_test_pca pca.transform(X_test) print(explained variance ratio:, pca.explained_variance_ratio_.sum())重点解释两个参数。n_components20是把原始几千维压到 20 维Sklearn 还支持填0.95表示保留 95% 方差但我实测在做实验报告对比时还是固定整数维数更方便画“准确率-降维维数”折线图。whitenTrue会把 PCA 后的各特征方差归一化这对 KNN 有实际帮助——KNN 按欧氏距离判邻居维度对方差敏感whiten 能平衡各主成分的尺度。PCA 里另一个值得关注的量是explained_variance_ratio_。我一般在 n_components 选择前先打印累计方差比曲线看到加到第二十维时累计占比超过 95%就基本有底了。如果前 5 维已经占了 90%说明人脸数据本身冗余度极高降维空间很大。4.3 KNNk 的取值与交叉验证的必要性KNN 在降维后的特征空间里做分类Sklearn 一行就能调用但 k 值的选取直接决定结果。k1 时过拟合测试集上振荡剧烈k 太大又会把不同类的边界抹平。我一般把 k 放 [1, 3, 5, 7, 9] 里试配合 PCA 维数画一张热力图。from sklearn.neighbors import KNeighborsClassifier from sklearn.model_selection import cross_val_score for n_comp in [5, 10, 20, 30, 50]: pca_tmp PCA(n_componentsn_comp, whitenTrue) X_pca_tmp pca_tmp.fit_transform(X_train) for k in [1, 3, 5, 7]: knn KNeighborsClassifier(n_neighborsk) scores cross_val_score(knn, X_pca_tmp, y_train, cv5) print(fPC{n_comp:2d} k{k} acc{scores.mean():.3f} (/- {scores.std():.3f}))这个循环就是实验报告里“N-交叉法估计准确率对不同降维维数和 k 取值比较”的直接实现。cv5是五折它会自动把训练集再切五份四份训练一份验证轮转一圈取平均。如果你手动实现了交叉验证注意用StratifiedKFold而不是KFold保证每折里各人脸类别比例一致这在类别不均衡时能少跳几分。5. 避坑六个常见翻车点与对应解法5.1 PCA 混入测试集准确率虚高现象测试集准确率 98%换一批样本直接掉到 70%。原因对整个数据集一次性fit_transform测试样本参与了主成分估计信息泄漏。解决严格让 PCA 只在训练集fit测试集只transform两个数据集全程不共用一个 fit 过程。这条是我在所有 PCA 实验里翻车最多的一处。5.2 KNN 对尺度敏感特征不归一化现象加了一个新特征后准确率不升反降。原因KNN 用欧氏距离量纲大的特征会主导距离计算身高单位 cm、体重单位 kg 时体重影响被放大。解决全部特征先做标准化减去均值除标准差或 PCA 时开whitenTrue。我做性别分类的 KNN 对比实验时不归一化 vs 归一化的准确率差了 8 个百分点这属于可复现的翻车。5.3 np.var 默认用总体方差报告与代码对不上现象手算方差和代码输出不一致排查半小时。原因np.var()默认ddof0即除以 n教材里最大似然估计用的也是除以 n但有些老师课上讲的样本方差是除以 n-1。解决在代码注释里写明ddof1是样本方差并和实验报告公式保持一致。数值差异在小样本时很明显大样本几乎没差但报告公式与代码不一致容易被扣过程分。5.4 协方差矩阵奇异Fisher 直接报错现象多类 Fisher 里调用np.linalg.inv(Sw)抛出LinAlgError: Singular matrix。原因人脸特征维数高于样本数类内散度矩阵不满秩。解决先用 PCA 压到样本数减一以下的维度再跑 Fisher或者用np.linalg.pinv兜底但要接受结果不稳定。我个人的做法是 PCA 保底Fisher 只做锦上添花。5.5 TXT 文件读出来全是 NaN 或数组形状不对现象读完 TXT 后np.array(data)形状是 (0,)或者数据里有 NaN。原因文件不是空格分隔可能是逗号或制表符也可能首行是列名没跳过。解决先head或记事本打开看前几行确定分隔符后再改split(,)或skiprows1。我一般会在加载函数里打印data[:3]确认再往后续走。5.6 准确率波动大交叉验证结果不可复现现象同一套代码跑两次准确率差 5 个百分点。原因交叉验证或 train_test_split 没有固定随机种子每轮样本划分不同。解决在划分函数里固定random_state42并在报告中注明。KNN 本身没有随机性波动基本来自数据划分PCA 也无随机性所以整个实验的波动都能被 random_state 锁住。6. 最后一步N 折交叉验证的完整复现与准确率比较把上面所有模块串起来规整成一个可复现的脚本骨架。人脸数据总量不大五折交叉验证足够如果样本数不足 50用留一法cvlen(X_train)效果更稳定但耗时会长一些。下面是完整流程的最终形态。import numpy as np import cv2, os, re from sklearn.decomposition import PCA from sklearn.neighbors import KNeighborsClassifier from sklearn.model_selection import StratifiedKFold, train_test_split X [] # 假设已通过 load_face_image 读好 y [] # 假设已通过文件名解析好 X, y np.array(X), np.array(y) X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.2, stratifyy, random_state42) skf StratifiedKFold(n_splits5, shuffleTrue, random_state42) results [] for n_comp in [5, 10, 20, 30, 50]: pca PCA(n_componentsn_comp, whitenTrue) X_tr_pca pca.fit_transform(X_train) for k in [1, 3, 5, 7]: knn KNeighborsClassifier(n_neighborsk) acc [] for fold_train, fold_val in skf.split(X_tr_pca, y_train): knn.fit(X_tr_pca[fold_train], y_train[fold_train]) acc.append(knn.score(X_tr_pca[fold_val], y_train[fold_val])) results.append((n_comp, k, np.mean(acc), np.std(acc))) # 最后用测试集验证最优参数 best max(results, keylambda r: r[2]) print(fbest: PC{best[0]} k{best[1]} val_acc{best[2]:.3f}) pca_best PCA(n_componentsbest[0], whitenTrue).fit(X_train) knn_best KNeighborsClassifier(n_neighborsbest[1]) knn_best.fit(pca_best.transform(X_train), y_train) test_acc knn_best.score(pca_best.transform(X_test), y_test) print(ftest_acc{test_acc:.3f})这段代码里有两件事值得说。一是外层循环的 k 和 n_components 组合在结果里挑验证集均值最高的参数再用测试集验证一次这能避免把验证集准确率直接当最终成绩的错觉二是stratifyy在 train_test_split 里保证训练和测试的人脸类别比例一致做实验报告里“N-交叉法”时这个细节是加分项。最后输出尽量做成一张格式统一的表行是降维维数列是 k 值表格中央是平均准确率。把这张表贴进报告再附一句“最优参数为 PC20, k3测试集准确率 0.913”整个实验的结论就闭环了。从那以后我每次跑这类实验都强制走一遍“固定随机种子 → 训练集拟 PCA → 交叉验证选参 → 独立测试集验一次”的流程不会因为参数没锁而白跑两小时。希望帮到你。本文还有配套的精品资源点击获取