☰
鸢尾花大作业:PCA、LDA、GMM与刀切法全流程解析
2026/10/31 2:23:53 网站建设 项目流程

简介:这份数理统计大作业文档面向高校统计学、机器学习课程的学习者,围绕经典鸢尾花数据集展开完整分析,帮助读者理解多重变量数据的建模流程。内容涵盖马氏距离度量样本相似度、混合高斯模型聚类、主成分分析降维去噪、线性判别分析兼顾类间与类内差异,以及刀切法评估降维方法与模型泛化能力,并附有数据预处理与结果讨论。资源包共1个docx文件,约511KB,结构完整,可直接作为课程作业参考或复习提纲。已有1429人学习下载,适合需要对照案例梳理统计方法应用思路、完成大作业或准备相关考核的读者参考借鉴。

1. 从一份鸢尾花大作业说起:为什么降维和聚类要放在同一套流程里

数理统计大作业最容易翻车的地方,不是公式写错,而是把 PCA、LDA、GMM、刀切法当成四个互不相干的实验拼在一起,最后结论只能写一句“都有效果”。鸢尾花数据集只有 150 个样本、4 个属性、3 个类别,看起来简单,但它恰好能把“降维—建模—评估”这条链路完整跑通:先用主成分分析(PCA)和线性判别分析(LDA)把四维数据压到低维并顺带滤掉噪声,再用高斯混合模型(GMM)在极大似然意义下拟合分布参数,最后用刀切法(留一法)量化不同降维程度、不同降维方法之间的差异。这套方案适合正在做数理统计课程设计、需要交一份完整分析报告的学生,也适合想用一个小数据集把经典统计方法串起来讲清楚的从业者。下面按需求、设计、实现、避坑、验收的顺序,把这份作业拆成可以直接照着做的方案。

2. 需求与数据底座:150 个样本、4 个属性、3 个类别怎么组织

2.1 任务定义与符号约定

这份作业的核心任务不是“训练一个分类器拿准确率”,而是验证经典统计方法在公认数据集上的表现差异。所以第一步要把任务说清楚:输入是四维数值向量(花萼长度、花萼宽度、花瓣长度、花瓣宽度),输出是三个鸢尾花类别之一(记作 0、1、2)。样本总数 150,每类 50 个,类别均衡,这一点决定了后面刀切法的判错率可以直接比较,不需要做类别加权。

符号体系建议在报告第二章就固定下来,后面所有公式和代码都沿用同一套记号,否则写到第四章自己都会混。我一般会这样约定:样本矩阵记作 $X \in \mathbb{R}^{n \times p}$,$n=150$、$p=4$;均值向量 $\mu$、协方差矩阵 $\Sigma$、相关矩阵 $R$;线性变换方向记作 $u$;降维后的维度记作 $d$。把这些写进一张符号表,答辩时评委翻到任何一页都能对上号,这是最省事的做法。

数据来源要写清楚是公开的经典数据集,样本按类别排列,前 50 个是 Setosa,中间 50 个是 Versicolour,后 50 个是 Virginica。这个排列顺序在后面做分层刀切时非常关键,不能打乱后忘记原始标签。

2.2 数据预处理:标准化与相关矩阵的选择

鸢尾花四个属性都是厘米量纲,数值范围接近,但 PCA 对量纲敏感,所以预处理这一步不能省。通常的做法是:如果四个属性量纲一致、方差差异不大,可以直接用协方差矩阵做特征分解;如果担心量纲影响,就用相关矩阵代替协方差矩阵,等价于先对每个属性做标准化。这份作业里我建议统一走相关矩阵路线,理由有两个:一是报告里可以明确写“消除量纲影响”,逻辑闭环;二是相关矩阵的对角线恒为 1,特征值之和等于属性个数,解释方差比例时更直观。

预处理代码不需要很长,但每一步要有注释,方便报告里贴出来做说明:

import numpy as np def standardize(X): # 按列(属性)计算均值和标准差 mu = X.mean(axis=0) sigma = X.std(axis=0, ddof=1) # 样本标准差,ddof=1 return (X - mu) / sigma, mu, sigma def corr_matrix(X): # 标准化后的协方差矩阵即相关矩阵 Xs, _, _ = standardize(X) return np.cov(Xs, rowvar=False)

这里ddof=1是血泪经验:用总体标准差(ddof=0)算出来的相关矩阵和统计教材上的样本相关矩阵会对不上,报告里数值复核时容易被挑出来。rowvar=False表示每一列是一个变量,和“4 个属性”的直觉一致。标准化后的数据要保留均值和标准差,因为后面把新样本投影到主成分上时还要用同一套参数,不能重新算。

2.3 数据组织与实验分组

实验分组要提前定好,不然后面刀切法跑起来会乱。我一般会设计三组对照:第一组不降维,直接用四维原始数据;第二组用 PCA 降到 2 维;第三组用 LDA 降到 2 维。每组都接同一个 GMM 分类流程,最后用刀切法算判错率。这样“降维程度”和“降维方法”两个变量就被拆开了,结论才有说服力。

数据读入后建议先做一次描述性统计,把每类的均值、标准差列成表,报告里放在“数据及符号说明”一章。这一步看起来是凑篇幅,实际上是后面解释 GMM 均值向量时的参照系——如果某类拟合出来的均值偏离样本均值太远,多半是初始化或迭代出了问题。

3. 算法链路设计:PCA、LDA、GMM 与马氏距离怎么串

3.1 主成分分析:从相关矩阵特征分解到投影

PCA 的目标是找一组正交方向,让投影后的方差最大。具体到实现,就是对相关矩阵做特征分解,把特征值从大到小排序,取前 $d$ 个特征值对应的特征向量组成投影矩阵。这里有一个容易被忽略的点:特征向量要单位化,而且符号可以任意(特征向量乘 -1 仍是特征向量),所以不同库跑出来的主成分方向可能整体反号,比较结果时要看绝对值或统一符号,不能直接逐元素比。

def pca_fit(X, n_components): R = corr_matrix(X) eigvals, eigvecs = np.linalg.eigh(R) # 对称矩阵用 eigh 更稳 idx = np.argsort(eigvals)[::-1] # 特征值降序 eigvals, eigvecs = eigvals[idx], eigvecs[:, idx] components = eigvecs[:, :n_components] # 取前 d 个方向 ratio = eigvals[:n_components] / eigvals.sum() return components, ratio def pca_transform(X, components, mu, sigma): Xs = (X - mu) / sigma # 用训练集参数标准化 return Xs @ components

np.linalg.eigh专门用于对称矩阵,比通用eig数值更稳定,这是踩坑之后养成的习惯。ratio是解释方差比例,报告里要写清楚前两个主成分累计解释了多少方差,通常鸢尾花数据前两个主成分能到 95% 以上,这就是“降到 2 维损失不大”的量化依据。注意pca_transform里的mu、sigma必须来自训练集,测试样本不能重新标准化,否则就是数据泄漏。

3.2 线性判别分析:类间散度与类内散度的广义特征分解

LDA 和 PCA 的本质区别在于它用了标签:投影后希望同类样本尽量聚拢、异类样本尽量分开。二分类时目标函数是类间散度比类内散度,多分类时推广为广义瑞利商,解是 $\Sigma_w^{-1}\Sigma_b$ 的广义特征向量。鸢尾花是三分类,所以取前 2 个判别方向(类别数减一),正好和 PCA 的 2 维对照。

def lda_fit(X, y, n_components): classes = np.unique(y) mu_all = X.mean(axis=0) Sw = np.zeros((X.shape[1], X.shape[1])) # 类内散度 Sb = np.zeros((X.shape[1], X.shape[1])) # 类间散度 for c in classes: Xc = X[y == c] mu_c = Xc.mean(axis=0) Sw += (Xc - mu_c).T @ (Xc - mu_c) Sb += len(Xc) * np.outer(mu_c - mu_all, mu_c - mu_all) # 广义特征分解 Sw^{-1} Sb eigvals, eigvecs = np.linalg.eig(np.linalg.inv(Sw) @ Sb) idx = np.argsort(eigvals.real)[::-1] return eigvecs[:, idx][:, :n_components].real

这里Sw加正则项是常见做法,当某个类样本很少或属性高度相关时Sw可能接近奇异,直接求逆会报错或数值爆炸。我一般会在对角线上加一个很小的数(比如1e-6 * np.eye(p)),报告里注明“为数值稳定性加入正则项”,评委不会为难。另外np.linalg.eig返回可能是复数,取.real之前要确认虚部足够小,否则说明数据或公式有问题。

3.3 高斯混合模型:极大似然估计与 EM 迭代

GMM 把每个类别的数据看成若干个高斯成分的加权混合,参数包括混合权重、均值向量和协方差矩阵。直接对似然函数求导没有闭式解,所以用 EM 算法迭代:E 步算每个样本属于各成分的后验概率,M 步用后验概率加权更新参数。鸢尾花数据每个类别内部近似单峰,所以每个类别用一个高斯成分就能跑通,但报告里要说明“成分数可以调,这里取 1 是为了和类别对应”。

def gmm_em(X, k=1, max_iter=100, tol=1e-6): n, p = X.shape # 初始化:均值随机选 k 个样本,协方差用全局协方差 mu = X[np.random.choice(n, k, replace=False)] Sigma = [np.cov(X, rowvar=False) + 1e-6 * np.eye(p) for _ in range(k)] pi = np.ones(k) / k log_lik_old = -np.inf for _ in range(max_iter): # E 步:计算后验责任度 resp = np.zeros((n, k)) for j in range(k): diff = X - mu[j] inv = np.linalg.inv(Sigma[j]) det = np.linalg.det(Sigma[j]) coef = 1.0 / np.sqrt((2 * np.pi) ** p * det) resp[:, j] = pi[j] * coef * np.exp(-0.5 * np.einsum('ni,ij,nj->n', diff, inv, diff)) resp /= resp.sum(axis=1, keepdims=True) # M 步:更新参数 Nk = resp.sum(axis=0) pi = Nk / n mu = (resp.T @ X) / Nk[:, None] for j in range(k): diff = X - mu[j] Sigma[j] = (resp[:, j][:, None] * diff).T @ diff / Nk[j] + 1e-6 * np.eye(p) log_lik = np.sum(np.log(resp.sum(axis=1) + 1e-12)) if abs(log_lik - log_lik_old) < tol: break log_lik_old = log_lik return pi, mu, Sigma

einsum那一行是马氏距离的二次型展开,比循环写起来快也更清晰。1e-12是防止log(0)的后悔药,不加的话第一次迭代就可能出-inf。EM 只保证收敛到局部最优,所以初始化很关键,报告里可以写“多次随机初始化取似然最大的一次”,这是负责任的做法。

3.4 马氏距离判别:把样本判给最近的高斯均值

GMM 拟合完之后,每个类别有一个均值向量和协方差矩阵。对新样本做判别时,最直接的方式是算它到各个类别均值的马氏距离,取距离最小的类别。马氏距离的好处是考虑了属性之间的相关性和各方向的方差差异,和 GMM 的协方差假设是自洽的。

def mahalanobis(x, mu, Sigma): diff = x - mu inv = np.linalg.inv(Sigma) return np.sqrt(diff @ inv @ diff) def predict(x, mus, Sigmas): dists = [mahalanobis(x, mus[j], Sigmas[j]) for j in range(len(mus))] return int(np.argmin(dists))

这里Sigmas是每个类别拟合出的协方差矩阵,不是全局协方差。如果报告里写“用马氏距离判别”,就要明确是“到类均值的马氏距离”,不能和“到样本的马氏距离”混为一谈,这是答辩时最容易被追问的细节。

4. 刀切法评估与实验实现:留一法怎么跑出可比较的判错率

4.1 刀切法的执行流程

刀切法(留一法)的逻辑很朴素:每次从训练集里剔除一个样本,用剩下的样本建模,再预测被剔除的那个样本,重复直到每个样本都被预测一次,最后统计判错个数除以总数。它的好处是不浪费样本,评估结果比简单回报法更客观;代价是运算量大,150 个样本就要跑 150 次完整流程。对鸢尾花这种规模,计算机辅助下完全可以接受。

实现时要注意分层:剔除样本后,剩下的样本里每个类别仍然要有足够样本,否则 LDA 的类内散度矩阵可能奇异。由于每类 50 个,剔除一个后仍有 49 个,问题不大,但代码里最好加一个检查,样本数少于属性数时直接报错提示。

def loo_evaluate(X, y, dim_reducer, n_components): n = len(y) errors = 0 for i in range(n): # 留出第 i 个样本 X_train = np.delete(X, i, axis=0) y_train = np.delete(y, i) x_test, y_test = X[i], y[i] # 降维:只用训练集拟合参数 if dim_reducer == 'pca': comp, _ = pca_fit(X_train, n_components) mu, sigma = X_train.mean(axis=0), X_train.std(axis=0, ddof=1) X_tr = pca_transform(X_train, comp, mu, sigma) x_te = pca_transform(x_test.reshape(1, -1), comp, mu, sigma)[0] else: comp = lda_fit(X_train, y_train, n_components) X_tr = X_train @ comp x_te = x_test @ comp # 每个类别单独拟合 GMM(k=1) mus, Sigmas = [], [] for c in np.unique(y_train): _, mu_c, Sigma_c = gmm_em(X_tr[y_train == c], k=1) mus.append(mu_c[0]); Sigmas.append(Sigma_c[0]) pred = predict(x_te, mus, Sigmas) if pred != y_test: errors += 1 return errors / n

这段代码的关键在于“降维参数只用训练集拟合”,测试样本的投影必须复用训练集的均值和投影方向。如果图省事在全体数据上先降维再切分,判错率会偏乐观,这是数据泄漏的典型翻车点,报告里如果被问到“你的评估有没有泄漏”,要能明确回答没有。

4.2 三组对照的实验组织

把三组实验跑在同一套刀切框架下,结果才有可比性。建议在报告里用一张表呈现:不降维、PCA 降 2 维、LDA 降 2 维三行,列写判错率、运行时间、备注。通常 LDA 的判错率会低于 PCA,因为它用了标签信息;PCA 又可能略低于不降维,因为去掉了部分噪声。但具体数值要以实际跑出来的为准,不能照抄教材。

实验组降维方法维度判错率说明
A无4待测基线对照
BPCA2待测无监督降维
CLDA2待测有监督降维

表格里的“待测”要换成真实结果,并且注明随机种子。GMM 初始化有随机性,同一组实验跑两次判错率可能差一两个样本,所以报告里最好写“固定随机种子后结果可复现”,或者“重复 10 次取平均”。这是让结果站得住脚的细节。

4.3 结果解释的写法

结果部分不要只贴数字,要解释数字背后的原因。比如 LDA 判错率低,是因为它最大化类间散度、最小化类内散度,投影方向直接服务于分类;PCA 不看标签,保留的是方差最大的方向,可能把区分性强的方向丢掉。再比如降到 1 维时判错率明显上升,说明两个主成分已经承载了大部分判别信息,继续压缩损失太大。这些解释要扣着前面的公式和代码写,形成“原理—实现—结果”的闭环。

5. 避坑与常见问题:这份作业最容易翻车的五个点

5.1 标准化参数在训练集和测试集之间混用

现象是刀切法判错率异常低,甚至接近 0。原因多半是在全体数据上先做了标准化或 PCA,再切分训练测试,测试样本的信息泄漏到了降维参数里。解决方法是把标准化、PCA、LDA 的拟合全部放进刀切循环内部,每次只用当前训练集估计参数,测试样本只做变换不做拟合。这个坑很隐蔽,代码跑起来不报错,但结论不可信。

5.2 协方差矩阵奇异导致求逆失败

现象是np.linalg.inv抛LinAlgError或结果出现极端大值。原因是某个类别样本数接近属性数,或者属性之间高度共线,协方差矩阵接近奇异。解决办法是在对角线上加一个小正则项,或者在降维后再拟合 GMM,把维度控制在样本数远大于维度的范围。报告里要写明加了正则项,这是常规操作,不丢人。

5.3 EM 迭代不收敛或似然出现 NaN

现象是似然值变成nan或-inf,或者迭代几百次还在震荡。原因通常是某个高斯成分的协方差行列式接近 0,密度计算溢出,或者初始化把两个成分放到了同一个点。解决办法是给协方差加正则项、给密度计算加极小值保护、多次随机初始化取最优。如果还不行,就减少成分数,鸢尾花每类一个成分足够。

5.4 特征向量符号不一致导致对比出错

现象是两次跑 PCA 得到的主成分方向整体反号,投影后的散点图镜像对称。原因是特征向量的符号本身不唯一。解决办法是在拟合后统一符号,比如让每个特征向量的绝对值最大的分量为正,或者在比较投影结果时只看距离和判错率,不看坐标符号。报告里如果画了投影图,要说明符号不影响方差解释比例。

5.5 刀切法运算时间失控

现象是留一法跑了很久还没结束。原因是每次循环都重新做完整的 EM 迭代,150 次乘以每次上百次迭代,计算量翻倍。解决办法是控制 EM 的最大迭代次数和收敛阈值,或者在 PCA/LDA 降维后再跑 GMM,维度低了单次迭代更快。如果时间实在紧,可以先用 5 折交叉验证做粗调,最终结果再用留一法确认,但报告里要写清楚评估协议。

6. 验收与答辩:怎么证明这套流程真的跑通了

验收这份作业,我一般不看结论写得多漂亮,而是看三件事能不能当场复现。第一,代码能不能从原始数据一路跑到判错率表格,中间不需要手动改参数;第二,报告里的每个数字能不能在代码输出里找到对应,尤其是解释方差比例和判错率;第三,被问到“为什么用马氏距离而不是欧氏距离”时,能不能结合协方差矩阵讲清楚尺度无关和相关性这两点。

答辩时最容易被追问的是评估协议。评委常问“你的留一法有没有数据泄漏”,这时候要能指着代码说降维和 GMM 拟合都在循环内部,测试样本只做变换。另一个高频问题是“LDA 比 PCA 好在哪里”,回答要落到“LDA 用了标签,最大化类间散度比类内散度”,而不是笼统说“LDA 更高级”。如果时间允许,可以准备一张对比表,把三组实验的判错率、维度、是否用标签列在一起,一目了然。

还有一个实用技巧:把关键实验固定随机种子后跑三遍,确认判错率波动在一个样本以内,再写进报告。GMM 的初始化有随机性,如果不做这一步,答辩现场重新跑一次结果对不上,会很被动。我通常会在附录里放一段“复现说明”,写清楚运行环境、依赖库、随机种子和预期输出,评委照着跑一遍就能验证。

从那以后我每次带这类统计作业,都强制要求先跑通“不降维基线”,再逐步加 PCA、LDA,每加一步记录一次判错率,确保每个模块的贡献都能被单独看到。这套习惯看起来笨,但能挡住大部分“结果好看但说不清为什么”的问题。希望帮到你。

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

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

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

立即咨询