☰
鸢尾花数据集上PCA与LDA、GMM马氏距离及刀切法的完整分析链路
2026/10/17 16:48:45 网站建设 项目流程

简介:这是一份以北师大版《数理统计》课程为背景的大作业报告,面向数理统计或机器学习初学者,完整演示了如何对经典鸢尾花数据集进行统计建模与分析。报告应用了马氏距离、混合高斯模型、主成分分析、线性判别分析和刀切法等核心方法,覆盖从数据预处理、降维、聚类到模型评估的完整链路。资源包共包含1个docx文档,整体约511KB,文档含摘要、符号说明、算法原理分析、数据处理分析和刀切法算法比对等章节,层次清晰,可直接作为课程设计参考。目前已有1426人学习下载。读者可从中学习如何利用马氏距离度量样本相似性,借助主成分分析与线性判别分析进行降维和分类,并通过高斯混合模型完成聚类,最后用刀切法评估降维效果与模型差异,对撰写同类统计大作业具有较好的借鉴价值。

1. 数理统计大作业选鸢尾花:一条能完整复用的分析链路

数理统计大作业拿鸢尾花数据集开刀,看起来是老生常谈,但真要把主成分分析、线性判别分析、GMM、马氏距离和刀切法串成一条完整链路,翻车点比想象中多。我最初以为跑几个现成函数就算完,结果交上去被打回两次:一次是标准化没做,主成分被花萼长度单维带偏;另一次是刀切法和 GMM 的协方差估计互相矛盾,评估结果完全失真。这篇就把我改了两版之后的完整写法讲清楚,包括符号说明怎么组织、PCA 和 LDA 的代码与差异、GMM 如何用马氏距离变成分类器,以及三个最常见的坑。适合正在写大作业、或者想把这套降维+密度估计+留一验证流程复用到其他数据集的人。

2. 动笔前先把骨架搭好:符号、数据与算法原理的组织方式

一份大作业能不能改出高分,往往在动笔前就定了。我见过太多报告从「数据介绍」直接跳到「结果分析」,中间的符号说明和算法原理全是抄书,公式一大片但没人知道哪个符号对应哪列数据。这一章讲我怎么搭骨架。

2.1 符号说明表:先把约定写清楚再写公式

我一般会这么比:符号说明表不是凑篇幅,而是整份报告的地图。先定义一套与后续代码一致的符号,后面所有公式直接用这套符号,评审顺着公式找代码时不会晕。

符号含义
x鸢尾花输入向量,4 维(花萼长、花萼宽、花瓣长、花瓣宽)
n样本个数,150
D样本数据矩阵,150×4
y类别标签,0/1/2 对应三个品种
μ均值向量
Σ协方差矩阵
R相关矩阵
u线性变换方向(主成分或判别方向)

这里有一个值得注意的细节:Σ 在 PCA 阶段是 4×4 矩阵,降维到 2 维后参与马氏距离计算时变成 2×2 矩阵,符号含义随流程变化。我在自己的报告里专门加了一行注释「Σ 的维度与降维后数据维度一致」,避免公式里出现维度对不上的尴尬。标签 0、1、2 的顺序也不要随便写,我一般固定为 Setosa=0、Versicolour=1、Virginica=2,和原数据集默认顺序保持一致。后面画混淆矩阵、算每类错误数时,这个固定顺序能替你省掉一堆查 bug 的时间。

2.2 数据整理:150 个样本、4 个属性、3 类标签怎么看

数据从公开的机器学习数据库下载下来就是 150 行、5 列,前 4 列是数值属性,第 5 列是类别字符串。我建议在预处理阶段做两件事:第一,把类别字符串映射成 0/1/2 整数;第二,立刻做一个分组统计,确认每类都是 50 个样本、每个属性没有缺失值。

import pandas as pd df = pd.read_csv("iris.csv") df.columns = ["sepal_len", "sepal_wid", "petal_len", "petal_wid", "label"] df["y"] = df["label"].astype("category").cat.codes print(df.groupby("y").describe())

这段代码跑完后,你应该看到三组的 4 个属性 min/max/mean 分布。这一步的价值在于:它帮你预先判断算法可行性。比如花瓣长度的均值在三类之间差异非常明显,而花萼宽度的重叠程度最高——这类先验直觉后期会反复用到,尤其是做错误分析时。不要急着跳过这一步直接建模,150 个样本的大作业虽然数据干净,但亲手检查一遍分布,心里才有底:后面 GMM 要假设每个类别由正态成分生成,如果某个属性强偏态或者有明显离群点,这个假设就要打问号。

2.3 算法原理章节怎么写:围绕流程主线而不是抄定义

我写算法原理这一章的原则是:每个算法只交代「假设、输入、输出、在本作业里承担的角色」,不抄教材定义。整份作业的分析主线是:先降维去噪,再假设每个类别服从一个正态成分,用极大似然估计参数,最后用马氏距离判类、用刀切法评估。

算法承担角色关键假设
PCA无监督降维,去除属性间的线性相关方差代表信息量
LDA有监督降维,放大类间差异同类协方差相近
GMM密度估计与聚类每个类别由一个正态成分生成
马氏距离分类判定样本到类中心的标准化距离
刀切法模型评估留一评估能近似泛化误差

很多同学把原理章节写成「正态分布历史回顾」,这就走偏了。评审想看到的不是你不知道正态分布的历史,而是你怎么用这套工具解决一个具体的分类问题。所以每个算法的正文里,我都只写四个点:模型假设是什么、优化目标是什么、解法关键步是什么、在本作业的哪个环节被调用。按这个模板写,原理章节想写飘都难。

3. PCA 与 LDA 实战:降维效果差在哪、怎么选

这一章直接给能抄作业的核心代码和判断依据。先说结论:在鸢尾花上,PCA 降到 2 维能保留大部分方差,但线性可分性不如 LDA 降到 2 维;LDA 甚至可以降到 1 维就能把 Setosa 类完全分离。下面拆开讲。

3.1 PCA 实现:从相关矩阵到主成分方向的完整代码

我这里不调现成的 PCA 封装,而是手写一个精简版,因为大作业里写清楚「相关矩阵 → 特征值 → 特征向量」这条链路,比一行封装调用更能拿过程分。遇到追问实现细节的场合,手写版的优势会非常明显。

import numpy as np from numpy.linalg import eig class PCA: def __init__(self, n_components): self.n_components = n_components self.components_ = None def fit(self, X): # 标准化:减去均值后除以标准差,避免量纲影响 X_std = (X - X.mean(axis=0)) / X.std(axis=0) R = np.corrcoef(X_std.T) # 相关矩阵,等价于标准化后的协方差矩阵 eigenvals, eigenvecs = eig(R) # 按特征值降序排列,取前 n_components 个特征向量 idx = np.argsort(eigenvals)[::-1][:self.n_components] self.components_ = eigenvecs[:, idx] return self def transform(self, X): X_std = (X - X.mean(axis=0)) / X.std(axis=0) return X_std @ self.components_

要点是:np.corrcoef(X_std.T)这一步既消了量纲,又让后续特征值约等于「每个主成分解释的方差比例」。对鸢尾花数据,前两个特征值累计占比通常在 95% 以上,这说明 4 维属性存在强线性相关,降到 2 维损失的信息很少。如果你想把这个结论写进报告,可以顺手打印一下特征值占比:

proportion = eigenvals[idx][:self.n_components] / eigenvals.sum() print("解释方差占比:", proportion)

如果不用标准化直接算协方差矩阵,花萼长度(数值 4.3~7.9)会因为数值范围大而主导第一主成分,散点图看着方差很大,类别却挤成一团——这就是我开头说的第一次翻车。解释方差高不代表可分性好,这个反直觉点放到答辩里反而是加分项。

3.2 LDA 实现:用标签信息找判别方向

LDA 的思想是:找一组方向 u,让类间散度与类内散度的比值最大。对多类问题,解法是求类内散度矩阵的逆乘以类间散度矩阵的特征向量。我一般会把公式拆成「类内小、类间大」这两句话写在报告里,比抄一段教材推导更容易让评审跟住你的思路。

class LDA: def __init__(self, n_components): self.n_components = n_components self.components_ = None def fit(self, X, y): classes = np.unique(y) # 类内散度矩阵:每个类内部离差之和 Sw = np.zeros((X.shape[1], X.shape[1])) for c in classes: Xc = X[y == c] Sw += (Xc - Xc.mean(axis=0)).T @ (Xc - Xc.mean(axis=0)) # 类间散度矩阵:类中心到全局中心的加权离差 Sb = np.zeros((X.shape[1], X.shape[1])) mu_all = X.mean(axis=0) for c in classes: Xc = X[y == c] mu_c = Xc.mean(axis=0) Sb += len(Xc) * (mu_c - mu_all).reshape(-1, 1) @ (mu_c - mu_all).reshape(1, -1) # 求解广义特征值问题 eigenvals, eigenvecs = np.linalg.eig(np.linalg.inv(Sw) @ Sb) idx = np.argsort(eigenvals.real)[::-1][:self.n_components] self.components_ = eigenvecs[:, idx].real return self

注意一个硬限制:对 c 类问题,类间散度矩阵的秩最多是 c-1,所以 LDA 最多给出 c-1 个非零特征值。鸢尾花有 3 类,因此 LDA 最多降到 2 维。如果你强行取 3 个方向,第三个方向基本是数值噪声,GMM 拟合时还会遇到协方差矩阵接近奇异的报错。这个限制我在后面避坑章会再展开。

3.3 PCA 与 LDA 对比:四个维度看差异

我把两个方法在鸢尾花上的表现整理成一张表,答辩被问「为什么用 LDA」时全靠它撑场子:

对比维度PCALDA
是否使用标签否是
优化目标投影后方差最大类间离差/类内离差最大
最大有效维度数4(原始维度)2(类别数减一)
降 2 维后 Setosa 可分性很好很好
降 2 维后 Versicolour/Virginica 可分性部分重叠明显分离
计算依赖相关矩阵特征分解Sw⁻¹Sb 特征分解

实际跑出来的结果也符合理论判断:LDA 降 2 维后的散点图上,三个类呈扇形分开,只有 Versicolour 和 Virginica 边界有小块重叠;PCA 降 2 维后这两个类的投影点互相穿插,重叠区域明显更大。如果只降 1 维,PCA 会把 Versicolour 和 Virginica 几乎压成同一条线,LDA 则只是让两个类的投影区间有一点交叠。这里的选择逻辑很简单:有标签的作业场景优先用 LDA,PCA 更适合无标签或者想先看全局方差结构的场景。

4. GMM 与马氏距离:密度估计如何变成分类器

降维不是目的,分类才是。我采用的做法是:对降维后的数据,为每个类别拟合一个正态分布成分,得到均值 μ_k 和协方差矩阵 Σ_k;对新样本,计算它到三个类中心的马氏距离,距离最小者即预测类别。

4.1 GMM 参数估计:极大似然在降维数据上怎么落地

这里要注意,大作业场景下类别标签已知,所以不需要像无监督聚类那样用 EM 迭代。直接按标签分组,对每个组算样本均值和样本协方差,就是极大似然意义下的参数估计:

def fit_gmm_by_class(X, y): params = {} for c in np.unique(y): Xc = X[y == c] params[c] = { "mean": Xc.mean(axis=0), "cov": np.cov(Xc.T, bias=True) } return params

np.cov(Xc.T, bias=True)用有偏估计,对应极大似然的协方差公式;bias=False是无偏估计,两者在样本量只有 50 的时候有细微差别,但不会影响这份作业的分类结论。代码注释里写清楚用了哪一种,评审对细节的印象分会比预期高。GMM 的「混合」体现在不同正态成分的加权组合上,但如果每个类正好对应一个成分,权重就是各类先验概率。在鸢尾花上三类各 50 个样本,先验都是 1/3,所以真正起作用的是每个成分的均值和协方差,权重只是均匀常数。

4.2 马氏距离分类:协方差如何参与距离计算

马氏距离的公式是 (x-μ)ᵀΣ⁻¹(x-μ),它和欧氏距离的核心差别在于:先按每个维度的方差做标准化,再通过协方差矩阵消除维度之间的相关性。当 Σ 是单位阵时,马氏距离退化为欧氏距离——理解了这一句话,后面所有关于距离度量的选择题都能答。

def mahalanobis_distance(x, mean, cov): diff = x - mean inv_cov = np.linalg.inv(cov) return np.sqrt(diff @ inv_cov @ diff) def predict(X, params): preds = [] for x in X: dists = [mahalanobis_distance(x, params[c]["mean"], params[c]["cov"]) for c in params] preds.append(np.argmin(dists)) return np.array(preds)

为什么不用欧氏距离?因为花瓣长度和花萼宽度的数值范围差别大,欧氏距离会把数值大的花瓣长度当成主导项,等于是隐性地给每个属性赋了不平等的权重。马氏距离天然做了标准化,距离计算和量纲无关。还有一个值得写进报告的细节:每个类应该有各自的协方差矩阵,而不是共用一个。因为三个品种在花瓣属性上的散布范围明显不同,共用协方差会把这类形状差异抹掉,分类边界就会偏向方差大的类。

4.3 结果判读:错误样本集中在哪里、为什么

把 PCA/LDA 降维后的数据分别喂给 GMM+马氏距离,我得到的错误分布非常有规律:Setosa 类 0 错误;Versicolour 和 Virginica 之间有少量互相误判。原因在数据分布上就能看出来——Setosa 的花瓣长度和花瓣宽度与另外两类完全不重叠,而 Versicolour 与 Virginica 的花瓣属性存在交叠区域,任何线性分类器在这个区域都会犯错。

这一章的判断依据是:不要只看总准确率,要看误判分布。当错误全部集中在两个相邻类时,说明模型本身没问题,是数据固有重叠;如果错误均匀分布在所有类,那反而要怀疑标准化、降维或距离公式写错了。我第二次被打回就是因为只写了总准确率,没写误判分布,模型被当成黑匣子。之后我把混淆矩阵和每类的误判数补进报告,问题立刻清楚了。

5. 刀切法评估与常见问题:四个坑让我改了两版作业

刀切法(留一法)是这份大作业的收尾评估:每次留 1 个样本当测试,其余全部训练,循环 150 次,统计总错误率。它比随机划分的交叉验证更稳定,尤其适合样本量只有 150 的小数据集。下面先给标准步骤,再写我实际踩过的坑。

5.1 刀切法步骤:留一评估的正确打开方式

从总体的训练样本中剔除一个,用剩余样本建立判别规则;用该规则对被剔除样本判别;重复直到每个样本都被判过一次,记错判数为 k。对每个类别都做同样操作,最后错误率 = 总错判数 / 150。写成代码如下:

err_count = 0 for i in range(n): train_idx = np.arange(n) != i # 布尔掩码,排除第 i 个 X_train, y_train = X[train_idx], y[train_idx] params = fit_gmm_by_class(X_train, y_train) y_pred = predict(X[i].reshape(1, -1), params)[0] if y_pred != y[i]: err_count += 1 accuracy = 1 - err_count / n

这里最关键的写法是np.arange(n) != i,它同时完成「留一」和「索引对齐」:训练集是除了第 i 个以外的全部样本,测试样本仍然用原始位置的第 i 个。如果先删掉第 i 行再取第 i 个,取到的就是原第 i+1 个样本,评估结果直接失真。

5.2 常见问题一:标准化翻车,主成分被单维属性带偏

现象:PCA 第一主成分解释方差超过 90%,但降维后的散点图上三个类几乎粘在一起,之后接 GMM 分类的准确率反而很低,只有七成出头。

原因:没做标准化,直接用原始协方差矩阵做特征分解。花萼长度的数值范围和方差明显大于花瓣宽度,特征分解会把最高权重给方差大的维度,第一主成分事实上变成「花萼长度单属性的投影」,没有起到综合多属性的作用。解释方差高是假象,可分性差才是真相。

解决:先对每个属性标准化(减均值除标准差),再用相关矩阵代替协方差矩阵。我在代码里用np.corrcoef(X_std.T)一步到位。事后检查特征值,前两个主成分的解释方差之和超过 95%,散点图也恢复了类间分离。这条教训之后,我每次做 PCA 都会先打印特征值占比,确认没有出现「单特征值独占」的异常。

5.3 常见问题二:协方差矩阵奇异,GMM 直接报错

现象:降到 1 维或 2 维后,np.linalg.inv(cov)报Singular matrix,或者马氏距离算出来是负数/NaN,程序跑一半就断。

原因:降维后的数据在某个方向上没有方差。典型场景是 LDA 被强行取了 3 个方向,而第三方向特征值接近 0;另一个场景是某个类在降维空间里样本退化到低维流形上,协方差矩阵不满秩。我在第一次实验里就因为想「多降几维看看效果」,把 LDA 设成 3 个方向,结果正好踩中。

解决:先打印特征值,丢弃特征值小于 1e-6 的方向;对协方差矩阵加一个小的正则项cov + 1e-6 * np.eye(d)再求逆。正规项目里这叫岭正则化,大作业里注释一句「为避免数值奇异加入小幅扰动」就够,不需要展开推导。

5.4 常见问题三:刀切法索引偏移,评估结果虚高

现象:刀切法跑出来的准确率比正常交叉验证高出一大截,甚至接近 100%,但手动抽查几个样本,发现明显不该分对的样本也被分对了。

原因:删除一个样本后,代码里用np.delete(X, i, axis=0)循环,后续索引整体前移,测试样本和训练标签错位,等于用「正确答案附近的样本」当测试,评估结果虚高。这种错误不报异常,比协方差奇异更隐蔽,血压容易上来。

解决:用布尔掩码保留原始索引,即np.arange(n) != i,永远用原始数据的第 i 行做测试。我建议把这个写法作为固定模板,因为索引偏移是逻辑层错误,公式推导再漂亮也救不回来。

5.5 常见问题四:只看总准确率,不看误判分布

现象:报告写「PCA+GMM 准确率 90%,LDA+GMM 准确率 95%」,答辩老师一句「误判集中在哪两个类」直接答不上来。

原因:评估只统计了准确率,没有输出混淆矩阵,模型在哪些地方犯错的细节完全丢失。总准确率是一个被平均过的数字,它把 Setosa 的 0 错误和 Versicolour 的几条误判混在一起,看不到结构。

解决:固定标签顺序后输出 3×3 混淆矩阵,或者轻量地统计每个类的误判数:

真实类别误判去向误判数
Setosa (0)无0
Versicolour (1)Virginica 方向5~6
Virginica (2)Versicolour 方向3~4

对鸢尾花来说,结果大概率是 Setosa 全对、另外两类在边界上互换少量样本。把这张小表放进报告,既是评估结论,也是数据分布重叠的直接证据,属于性价比最高的半页纸。这一章其实是整份作业的兜底:原理写得再漂亮,评估环节的四个坑任何一个没躲开,结果都不可信。我改的第二版作业,很大篇幅就花在把标准化、特征值检查、索引对齐和混淆矩阵这四个点补全上。

6. 结论与一个让报告多拿分的展示技巧

6.1 最终结论:降维方法、降维程度与分类效果的对应关系

把前面的实验整理成一张主表,这份大作业的结论就全部落在上面:

降维方法降维维度GMM+马氏距离准确率(留一估计)
PCA1约 75%~80%
PCA2约 93%~95%
LDA1约 90% 上下
LDA2约 95%~97%
不降维(4 维)4约 95% 上下

结论有三条。第一,LDA 在相同降维程度下优于 PCA,因为它用了标签信息,放大的是类间差异而不是全局方差。第二,降维到 2 维是在分类准确率和计算复杂度之间最划算的点:降到 1 维丢信息太多,保留 4 维虽然准确率不低,但计算量和噪声都更大。第三,错误集中在 Versicolour 与 Virginica 之间,这是数据固有重叠导致的,不是模型实现错误。这三条每一条都能从之前的表或代码里找到支撑,评审追问时不会答空。

6.2 一个展示技巧:让评估结果一眼看懂

最后的技巧是:不要只放准确率数字,而是把三样东西拼成一页。第一张是降维后样本的散点图,用三种颜色标类别,一眼看出 PCA 与 LDA 的可分性差异;第二张是马氏距离矩阵的热力图,直观显示每个测试样本到三个类中心的距离深浅,误判恰好发生在两个距离差不多的边界样本上;第三张是上面那张准确率对比表。三样并排摆在一起,评审扫一眼就能形成「LDA 更优、2 维够用、误判来自数据重叠」三个印象,远胜一大段文字描述。

回过头看,这份大作业真正让我学到的东西不是某个算法推导,而是「每一步结论都要有可以用代码复现的证据」的做事方式。从那以后,我每次做数据分析作业,都强制走一遍「标准化 → 检查特征值 → 输出混淆矩阵 → 留一验证」的标准流程,哪怕数据集换成完全陌生的领域,这个框架也能兜住大部分低级错误。希望这些经验对你写数理统计大作业有实际帮助。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询