恒美微站
首页
关于我们
建站服务
主题模板
案例展示
资讯中心
联系我们
手把手用NumPy实现Fisher线性判别(FLD)
首页
资讯中心
/
手把手用NumPy实现Fisher线性判别(FLD)
手把手用NumPy实现Fisher线性判别(FLD)
发布时间:2026/10/16 14:08:21
1. 这不是“调包跑个准确率”的演示而是一次手把手还原 Fisher 判别思想的实战你可能在很多教程里见过用sklearn三行代码搞定 Iris 分类——加载数据、训练模型、打印准确率。但那只是结果不是过程是工具不是思想。今天我们要做的是回到 1936 年 R.A. Fisher 发表那篇划时代论文的现场不依赖任何封装好的判别器只用 NumPy、Matplotlib 和最基础的线性代数在 Jupyter Notebook 里从零推导、手动计算、可视化验证 Fisher 线性判别FLD的每一步。这不是为了炫技而是因为——真正理解一个算法不在于它能跑多快而在于你能否在白板上把它重新写出来并解释清楚为什么这个方向就是最优投影轴。核心关键词已经非常明确“人工智能与机器学习”是领域背景“Jupyter”是载体环境“Iris 数据集”是经典教学样本“Fisher 线性分类”是方法本质。这四个词组合起来指向一个被严重低估的教学黄金切口它足够小仅3类、4维、150个样本却足够深涵盖类内散度、类间散度、广义特征值问题、降维与分类的统一视角。我带过不少刚接触 ML 的学生或转行者他们卡在“听懂了公式但不知道怎么落地”的临界点上。而 Iris FLD 正好是一把精准的手术刀它不涉及神经网络的黑箱梯度也不需要海量算力却能把“监督式降维”“可分性度量”“几何直观”全部串成一条清晰的逻辑链。你不需要先学完《矩阵论》才能开始只需要知道向量点积、协方差矩阵长什么样、特征向量代表什么方向——这些我们会在实操中边写边讲边画边算。这篇文章适合三类人第一类是正在啃《Pattern Recognition and Machine Learning》或《统计学习方法》的读者你想把书上第4章的 Fisher 准则从符号变成可运行的代码第二类是高校某实验室的助教你需要一份能放进实验课手册、学生两小时内能复现、且能真正讲清原理的 Jupyter 实验指南第三类是从业多年但想回溯基础的工程师你发现调参越来越顺但面对新场景时总缺一层“直觉”比如“为什么这里用 LDA 而不是 PCA”“为什么两类时投影轴唯一三类时却要找前k-1个”——这些问题的答案全藏在 Iris 这个“最小可行案例”的每一行计算里。接下来的内容没有一句废话没有一个跳步。所有矩阵运算都展开所有可视化都标注物理意义所有“理所当然”的选择都告诉你背后的代价权衡。我们不用sklearn.discriminant_analysis.LinearDiscriminantAnalysis因为我们要亲手造出那个“判别轴”。2. 为什么 Fisher 判别不是“另一个分类器”而是一种可解释的降维哲学2.1 从“分类”到“投影”Fisher 的核心洞见是什么很多人一看到“Fisher 线性分类”下意识就去翻分类器接口。但 Fisher 当年根本没想做分类器——他想解决的是如何用最少的维度最大程度保留类别区分信息。想象你有一堆混在一起的红蓝黄三色弹珠它们在三维空间里分布杂乱。你没法直接用肉眼判断哪堆是哪类但如果能找到一个“最佳观察角度”把三维空间压扁成一条直线让三色弹珠在直线上自动拉开距离、各自聚成一团那分类就变得极其简单画两条竖线三个区间自然对应三类。这个“最佳观察角度”就是 Fisher 找到的投影方向w。关键来了这个“最佳”怎么定义Fisher 提出了一个极简却深刻的准则——最大化类间离散度与类内离散度的比值。用数学语言说就是找一个单位向量w使得$$ J(\mathbf{w}) \frac{\mathbf{w}^T \mathbf{S}_B \mathbf{w}}{\mathbf{w}^T \mathbf{S}_W \mathbf{w}} $$达到最大。其中$\mathbf{S}_B$ 是类间散度矩阵Between-class scatter matrix衡量各类中心点到全局中心的偏离程度$\mathbf{S}_W$ 是类内散度矩阵Within-class scatter matrix衡量每个类内部样本围绕自身中心的离散程度。提示这个公式不是凭空来的。它本质是 Fisher 对“可分性”的量化类间越远分子大、类内越紧分母小投影后就越容易分开。它和 PCA 的目标函数最大化投影后方差有本质区别——PCA 是无监督的只看整体散布Fisher 是有监督的必须利用标签信息来引导投影方向。2.2 为什么 Iris 是验证 Fisher 思想的“完美沙盒”Iris 数据集绝非偶然成为经典。它的结构天然契合 Fisher 判别的设计前提恰好三类Setosa/Versicolor/Virginica让我们能完整展示多类 FLD 的推广形式投影到 k-12 维平面同时又不至于因类别过多而掩盖核心逻辑四维特征萼片长/宽、花瓣长/宽维度足够高能体现“降维必要性”人类无法直接可视化4D又足够低所有矩阵运算都能手算验证比如 $\mathbf{S}_W$ 是 4×4 矩阵求其逆完全可行数据质量极高无缺失值、无异常点、三类在原始空间已有一定可分性尤其 Setosa 完全分离这保证了我们手动计算出的投影方向能在可视化中得到清晰、可信的验证。我试过用 MNIST 或 CIFAR 做同样推导——光是 $\mathbf{S}_W$ 就是 784×784 的矩阵求广义特征向量需要 SVD 分解数值不稳定且结果无法直观检验。而 Iris 的 4 维特征让我们能把整个流程拆解为“可触摸”的步骤计算每个类的均值向量 → 手动拼出 $\mathbf{S}_W$ → 验证其正定性 → 求解广义特征值问题 → 取前两个特征向量构成投影矩阵。每一步你都能在 Jupyter cell 里 print 出中间结果亲眼看到数字如何一步步导向最终的判别轴。2.3 Fisher 判别 vs. 其他方法一张表看清本质差异特性Fisher 线性判别 (FLD)主成分分析 (PCA)支持向量机 (SVM)逻辑回归 (Logistic Regression)监督性有监督必须用标签无监督不用标签有监督有监督核心目标最大化类间/类内散度比最大化投影后总方差最大化分类间隔margin最小化对数损失log loss输出本质投影方向降维 分类边界若需投影方向降维分类超平面分类分类超平面分类是否可解释极高每个坐标轴有明确物理意义判别能力中等主成分是方差最大方向但未必与类别相关低核技巧后更难解释中等系数可解释为特征重要性Iris 上典型表现将4D压缩为2D后三类在平面上几乎线性可分压缩为2D后Versicolor 和 Virginica 仍有大量重叠在4D原始空间上可达到98%准确率但决策边界复杂在4D上可达到95%准确率但对特征尺度敏感这张表不是为了贬低其他方法而是帮你建立一个坐标系当你下次看到“LDA”这个词立刻能反应出——它首先是一种有监督的降维技术其次才是分类器通过在降维后的子空间上做最近邻或线性分类。这也是为什么在 Jupyter 里实现它重点不在“预测”而在“看见”看见 $\mathbf{S}_W$ 如何描述类内“拥挤度”看见 $\mathbf{S}_B$ 如何描述类间“距离感”看见特征向量w如何像一把尺子精准地量出哪个方向最能拉开三类。3. 手动推导全过程从数据加载到判别轴可视化每一步都可验证3.1 环境准备与数据加载确保零歧义的起点我们从最干净的起点开始。在 Jupyter Notebook 新建一个 cell执行以下代码import numpy as np import matplotlib.pyplot as plt import seaborn as sns from sklearn import datasets # 设置中文字体和图形风格避免中文乱码 plt.rcParams[font.sans-serif] [SimHei, Arial Unicode MS] plt.rcParams[axes.unicode_minus] False sns.set_style(whitegrid)接着加载 Iris 数据# 加载数据注意我们只取特征X标签y不使用任何预处理 iris datasets.load_iris() X iris.data # shape: (150, 4)四维特征 y iris.target # shape: (150,)整数标签 [0, 1, 2] class_names iris.target_names # [setosa versicolor virginica]注意这里刻意不使用train_test_split。Fisher 判别在小数据集上是“一次性”学习它不涉及训练/测试划分而是基于全部样本计算散度矩阵。所以我们的 $\mathbf{S}_W$ 和 $\mathbf{S}_B$ 是用全部150个样本算出来的。这是与 SVM、Logistic Regression 的关键区别——后者必须划分数据集来评估泛化能力而 FLD 的“泛化”体现在其投影方向对新样本的鲁棒性上。验证数据形状和基本统计print(f数据形状: X{X.shape}, y{y.shape}) print(f各类样本数: Setosa{np.sum(y0)}, Versicolor{np.sum(y1)}, Virginica{np.sum(y2)}) print(f特征均值:\n{np.mean(X, axis0)}) # [5.84333333 3.05733333 3.758 1.19933333]这个均值向量就是全局均值 $\mathbf{m}$后续所有类中心都会相对于它计算。3.2 计算类内散度矩阵 $\mathbf{S}_W$量化“类内有多挤”类内散度矩阵 $\mathbf{S}_W$ 的定义是$$ \mathbf{S}W \sum{c1}^{C} \mathbf{S}_c, \quad \text{其中} \quad \mathbf{S}c \sum{\mathbf{x}_i \in \mathcal{C}_c} (\mathbf{x}_i - \mathbf{m}_c)(\mathbf{x}_i - \mathbf{m}_c)^T $$即对每个类 $c$计算该类所有样本到其自身类中心 $\mathbf{m}_c$ 的离差平方和外积再将三类的结果相加。手动实现不调用np.cov因为我们要看清每一步# 初始化 S_W 为 4x4 零矩阵 S_W np.zeros((4, 4)) # 遍历三类 for c in range(3): # 提取第c类的所有样本 X_c X[y c] # shape: (50, 4) # 计算第c类的均值向量 m_c m_c np.mean(X_c, axis0) # shape: (4,) print(f类 {class_names[c]} 均值: {m_c.round(3)}) # 对该类每个样本计算离差向量 (x_i - m_c)并累加外积 for x_i in X_c: diff x_i - m_c # shape: (4,) S_W np.outer(diff, diff) # outer product: (4,1) (1,4) - (4,4) print(f\nS_W 形状: {S_W.shape}) print(fS_W 前两行:\n{S_W[:2, :]})运行后你会看到三类均值例如 Setosa 的均值约 [5.006, 3.418, 1.464, 0.244]以及 $\mathbf{S}_W$ 的具体数值。这个矩阵是对称正定的它编码了所有类内部的“拥挤模式”。比如如果 $\mathbf{S}_W$ 的 (1,1) 元素萼片长的方差很大说明即使在同一类里萼片长度也变化剧烈如果 (3,4) 元素花瓣长与宽的协方差接近零说明这两个特征在类内几乎不相关。实操心得我最初写这段代码时曾错误地用了np.cov(X_c, rowvarFalse)结果发现它默认除以(n-1)而不是n。Fisher 原始推导中$\mathbf{S}_c$ 是未归一化的离差平方和所以必须用np.outer手动累加不能依赖np.cov的默认行为。这是一个极易踩的坑务必用print(S_W)和np.trace(S_W)迹总类内方差来交叉验证。3.3 计算类间散度矩阵 $\mathbf{S}_B$量化“类间有多远”类间散度矩阵 $\mathbf{S}_B$ 的定义是$$ \mathbf{S}B \sum{c1}^{C} N_c (\mathbf{m}_c - \mathbf{m})(\mathbf{m}_c - \mathbf{m})^T $$其中 $N_c$ 是第 $c$ 类的样本数Iris 中均为50$\mathbf{m}$ 是全局均值。继续在 notebook 中执行# 全局均值 m m np.mean(X, axis0) # shape: (4,) print(f全局均值 m: {m.round(3)}) # 初始化 S_B S_B np.zeros((4, 4)) # 遍历三类 for c in range(3): X_c X[y c] N_c X_c.shape[0] # 50 m_c np.mean(X_c, axis0) diff m_c - m # shape: (4,) # 注意这里是 N_c * 外积 S_B N_c * np.outer(diff, diff) print(f\nS_B 形状: {S_B.shape}) print(fS_B 前两行:\n{S_B[:2, :]})你会发现 $\mathbf{S}_B$ 的数值远小于 $\mathbf{S}_W$因为类间差异通常小于类内差异但它决定了“方向”。比如$\mathbf{S}_B$ 的 (3,3) 元素花瓣长的类间方差会特别大因为 Setosa 的花瓣长平均只有1.46而 Virginica 达到5.55这个差距是 Iris 分类的关键。提示此时可以做一个快速验证计算 $\mathbf{S}_T \mathbf{S}_W \mathbf{S}_B$总散度矩阵它应该等于np.cov(X, rowvarFalse) * (X.shape[0]-1)。这是散度矩阵的基本性质能帮你确认 $\mathbf{S}_W$ 和 $\mathbf{S}_B$ 的计算无误。3.4 求解广义特征值问题找到最优投影方向wFisher 的核心数学结论是使 $J(\mathbf{w})$ 最大的w是广义特征值问题$$ \mathbf{S}_B \mathbf{w} \lambda \mathbf{S}_W \mathbf{w} $$的解。当 $\mathbf{S}_W$ 可逆时等价于求解$$ \mathbf{S}_W^{-1} \mathbf{S}_B \mathbf{w} \lambda \mathbf{w} $$即求矩阵 $\mathbf{S}_W^{-1} \mathbf{S}_B$ 的特征向量。在 Jupyter 中实现# 检查 S_W 是否可逆计算条件数 cond_num np.linalg.cond(S_W) print(fS_W 条件数: {cond_num:.2f} ( 1e12 表示良态)) # 计算 S_W 的逆 S_W_inv np.linalg.inv(S_W) # 计算 S_W^{-1} S_B M S_W_inv S_B print(fM 形状: {M.shape}) # 求 M 的特征值和特征向量 eigvals, eigvecs np.linalg.eig(M) # 特征向量是列向量所以转置便于索引 eigvecs eigvecs.T # 按特征值从大到小排序 idx np.argsort(eigvals)[::-1] eigvals eigvals[idx] eigvecs eigvecs[idx] print(f特征值 (降序): {eigvals.real.round(4)}) print(f前两个特征向量 (已归一化):\n{eigvecs[0].round(3)}\n{eigvecs[1].round(3)})运行后你会看到三个实特征值因为 $\mathbf{S}_B$ 秩为2所以第三个特征值应接近0以及对应的两个主特征向量。这就是我们要的前两个判别轴它们张成了一个二维子空间我们把所有4D样本都投影到这个平面上。注意np.linalg.eig返回的特征向量默认是单位向量但我们需要确保它们是正交的在 $\mathbf{S}_W$ 度量下。由于 $\mathbf{S}_W$ 是对称正定的$\mathbf{S}_W^{-1}\mathbf{S}_B$ 是对称矩阵其特征向量天然正交。你可以用np.dot(eigvecs[0], eigvecs[1])验证点积是否接近0。3.5 投影与可视化让 Fisher 思想“看得见”现在用前两个特征向量构成投影矩阵 $\mathbf{W} [\mathbf{w}_1, \mathbf{w}_2]$将原始数据 $X$ 投影到2D# 构建投影矩阵 W (4x2) W np.column_stack((eigvecs[0], eigvecs[1])) # shape: (4, 2) # 投影X_proj X W X_proj X W # shape: (150, 2) # 可视化投影结果 plt.figure(figsize(10, 8)) colors [red, blue, green] for i, (color, name) in enumerate(zip(colors, class_names)): idx (y i) plt.scatter(X_proj[idx, 0], X_proj[idx, 1], ccolor, labelname, alpha0.7, s50) plt.xlabel(第一判别轴 (w1), fontsize12) plt.ylabel(第二判别轴 (w2), fontsize12) plt.title(Fisher 线性判别 (FLD) 在 Iris 数据集上的投影结果, fontsize14) plt.legend() plt.grid(True, alpha0.3) plt.show()这才是最震撼的一步。你会看到原本在4D中纠缠的三类在这个2D平面上奇迹般地分开了Setosa 完全聚集在左上角Versicolor 和 Virginica 虽有少量重叠但主体区域清晰可辨。这个图就是 Fisher 思想最直观的证明——它不是一个抽象公式而是一个能让你“一眼看出可分性”的几何操作。实操心得我第一次跑出这个图时特意对比了 PCA 的投影图。用同样的代码把W换成PCA(n_components2).fit(X).components_.T你会发现 PCA 图中 Versicolor 和 Virginica 严重重叠而 FLD 图中它们被明显拉开。这个对比胜过千言万语的公式推导。它告诉你监督信息的价值就体现在这个“拉开”的动作上。4. 从投影到分类构建完整的 Fisher 分类流水线4.1 在判别子空间上定义分类规则投影只是第一步。有了2D表示 $X_{proj}$我们还需要一个规则把一个新的2D点分到哪一类。Fisher 原始方案是计算该点到每个类在判别子空间中的中心的距离分到最近的类。这本质上是“最近中心分类器Nearest Centroid”。在 Jupyter 中实现# 计算每个类在判别子空间中的中心 centroids_proj np.zeros((3, 2)) for i in range(3): centroids_proj[i] np.mean(X_proj[y i], axis0) print(各类在判别子空间中的中心:) for i, name in enumerate(class_names): print(f{name}: {centroids_proj[i].round(3)}) # 定义预测函数 def predict_fld(x_proj, centroids): x_proj: (2,) 投影后的点; centroids: (3, 2) 各类中心 distances np.sqrt(np.sum((centroids - x_proj)**2, axis1)) return np.argmin(distances) # 对所有样本进行预测用于评估 y_pred np.array([predict_fld(x, centroids_proj) for x in X_proj]) accuracy np.mean(y_pred y) print(f\nFLD 分类准确率: {accuracy*100:.2f}%)运行后你大概率会看到 96%~98% 的准确率。这已经非常优秀尤其是考虑到我们没有做任何参数调优完全是解析解。提示这个准确率不是“魔法”。它高是因为 Iris 本身线性可分性好它不是100%是因为投影是线性的而真实边界可能有轻微弯曲。但关键在于这个分类规则是完全可解释的你打开centroids_proj就能看到每个类“在判别空间里站的位置”而predict_fld就是计算欧氏距离——没有任何黑箱。4.2 可视化决策边界画出“分类地图”为了让决策过程更透明我们可以在2D投影图上画出决策边界plt.figure(figsize(10, 8)) # 先画出所有样本点 for i, (color, name) in enumerate(zip(colors, class_names)): idx (y i) plt.scatter(X_proj[idx, 0], X_proj[idx, 1], ccolor, labelname, alpha0.6, s40, edgecolorsk, linewidth0.5) # 生成网格点用于绘制决策区域 x_min, x_max X_proj[:, 0].min() - 0.5, X_proj[:, 0].max() 0.5 y_min, y_max X_proj[:, 1].min() - 0.5, X_proj[:, 1].max() 0.5 xx, yy np.meshgrid(np.arange(x_min, x_max, 0.02), np.arange(y_min, y_max, 0.02)) grid_points np.c_[xx.ravel(), yy.ravel()] # 对每个网格点预测 grid_pred np.array([predict_fld(p, centroids_proj) for p in grid_points]) grid_pred grid_pred.reshape(xx.shape) # 用等高线填充决策区域 plt.contourf(xx, yy, grid_pred, alpha0.3, levels[-0.5, 0.5, 1.5, 2.5], colors[red, blue, green], zorder1) plt.xlabel(第一判别轴 (w1), fontsize12) plt.ylabel(第二判别轴 (w2), fontsize12) plt.title(Fisher 判别分类器的决策边界, fontsize14) plt.legend() plt.show()这张图太有价值了。它清晰地展示了每个类的“势力范围”彩色区域样本点落在哪个区域内点的颜色与区域颜色一致边界线的形状这里是直线段组成的多边形因为最近中心分类器的边界是类中心的垂直平分线。注意这个决策边界是在判别子空间中定义的不是在原始4D空间。这正是 FLD 的精髓它先降维再分类把复杂的高维问题转化成一个简单的、可视化的2D问题。4.3 与 sklearn 的 LDA 结果对比验证你的手动实现最后用sklearn的标准实现来交叉验证你的手动代码from sklearn.discriminant_analysis import LinearDiscriminantAnalysis # 使用 sklearn 的 LDA lda LinearDiscriminantAnalysis(n_components2) X_lda lda.fit_transform(X, y) # 自动完成投影 # 比较投影结果应高度相似 print(手动 FLD 投影 (前5个点):) print(X_proj[:5]) print(\nsklearn LDA 投影 (前5个点):) print(X_lda[:5]) # 计算两者的相关性应接近1 corr_w1 np.corrcoef(X_proj[:, 0], X_lda[:, 0])[0, 1] corr_w2 np.corrcoef(X_proj[:, 1], X_lda[:, 1])[0, 1] print(f\n第一判别轴相关性: {corr_w1:.4f}) print(f第二判别轴相关性: {corr_w2:.4f})如果corr_w1和corr_w2都大于 0.999恭喜你你的手动推导完全正确sklearn的 LDA 内部正是这样计算的只是它做了更多工程优化如处理 $\mathbf{S}_W$ 奇异的情况。5. 常见问题与排查技巧实录那些文档里不会写的坑5.1 问题S_W不可逆np.linalg.inv报错 “LinAlgError: Singular matrix”现象在某些数据集或 Iris 的子集上S_W的行列式为0导致无法求逆。原因当某个类的样本共线如所有样本都在一条直线上或特征存在严格线性相关时$\mathbf{S}_W$ 会降秩。Iris 本身是满秩的但如果你不小心用了重复特征或做了错误归一化就可能触发。解决方案添加微小扰动推荐S_W_reg S_W 1e-8 * np.eye(S_W.shape[0])然后对S_W_reg求逆。这个1e-8是经典的 Tikhonov 正则化数值稳定且不影响结果。使用伪逆S_W_pinv np.linalg.pinv(S_W)它能处理奇异矩阵但物理意义稍弱。检查数据np.linalg.matrix_rank(S_W)应等于特征数4。如果不是说明数据有问题。我踩过的坑曾在一个自定义数据集上遇到此问题调试半天才发现自己误把标签当特征加入了X。用np.linalg.matrix_rank一行就定位了。5.2 问题特征值出现虚部eigvals.imag不为零现象np.linalg.eig返回的特征值有很小的虚部如1.234 1e-16j。原因数值计算误差。理论上 $\mathbf{S}_W^{-1}\mathbf{S}_B$ 是实对称矩阵特征值必为实数但浮点运算会引入微小虚部。解决方案直接取实部。eigvals eigvals.real。这是标准做法所有数值库都这么处理。5.3 问题投影后各类中心重合或判别轴方向“看起来不对”现象centroids_proj的三个点几乎在同一点或者可视化图中三类没有拉开。排查步骤检查 $\mathbf{S}_B$ 是否为零矩阵np.allclose(S_B, 0)。如果是说明所有类中心都等于全局中心即数据根本没有类别区分性不可能发生在 Iris 上但可能在你自己的脏数据上。检查 $\mathbf{S}_W$ 是否过大计算np.trace(S_W)和np.trace(S_B)的比值。如果前者远大于后者如 100说明类内离散度远超类间数据本身难以区分。检查特征向量是否已归一化np.linalg.norm(eigvecs[0])应为1。如果不是投影会失真。检查投影计算X_proj X W不是W X.T。矩阵乘法顺序极易搞错。5.4 问题准确率低于预期如 90%可能原因与对策数据泄露你在计算 $\mathbf{S}_W$ 和 $\mathbf{S}_B$ 时用了全部数据但预测时又用同一数据。这没问题因为 FLD 是确定性算法。但如果和 SVM 等需要划分数据的方法比较要确保公平。分类规则太简单最近中心分类器假设各类是球形分布。如果类内分布是椭球可以用马氏距离代替欧氏距离distance (x - m_c).T np.linalg.inv(S_c) (x - m_c)其中 $S_c$ 是该类在判别空间中的协方差。维度选择错误对于三类最多只能有2个非零判别轴。如果你强行取n_components3第三个轴的特征值为0投影无效。5.5 高级技巧如何用 Fisher 轴解释原始特征的重要性Fisher 向量w本身就是一个权重向量。例如第一个判别轴w1 [0.2, -0.1, 2.3, 1.8]说明在该方向上花瓣长第3个特征和花瓣宽第4个特征的贡献最大而萼片宽第2个特征贡献为负。你可以画出abs(w1)的条形图直观展示各原始特征对第一判别能力的贡献度。这是 PCA 无法提供的——PCA 的主成分是方差最大方向但不一定与类别最相关。plt.figure(figsize(8, 4)) feature_names iris.feature_names plt.bar(feature_names, np.abs(eigvecs[0])) plt.title(第一判别轴 (w1) 各特征权重绝对值) plt.ylabel(权重绝对值) plt.xticks(rotation15) plt.show()这张图会清晰显示花瓣特征主导了 Iris 的判别能力这与植物学常识完全吻合。这就是 Fisher 方法的终极价值它不仅分类还告诉你“为什么能分”。6. 项目收尾与延伸思考从 Iris 到你的真实工作流我在实际工作中很少直接用 FLD 做最终产品分类器。现代场景下XGBoost、LightGBM 或小型神经网络往往效果更好。但 FLD 从未离开我的工具箱原因有三第一它是最高效的可解释性探针。当一个黑箱模型给出奇怪预测时我会立刻用 FLD 把高维特征投影到2D看看模型认为“不可分”的样本在 Fisher 空间里是否真的重叠。如果重叠说明问题在数据如果不重叠说明模型学错了。这个诊断过程五分钟就能完成。第二它是特征工程的导航仪。abs(w)的条形图告诉我哪些原始特征是“判别核”哪些是“噪声”。我据此设计新的特征组合比如把花瓣长/宽比作为新特征再跑一次 FLD如果w的稀疏性增强就说明这个新特征抓到了本质。第三它是教学与沟通的通用语言。给非技术同事解释“为什么这个模型觉得这两类很像”一张 FLD 投影图比一百行 AUC 曲线都有说服力。它把抽象的“相似度”变成了屏幕上可触摸的“距离”。所以不要把今天的练习看作一个过时的算法复现。它是一把钥匙帮你打开“监督式降维”这扇门。门后是 LDA 的各种变体如 Penalized LDA 处理高维小样本、是流形学习LPP, NPE的思想源头、甚至是深度度量学习Deep Metric Learning的几何根基。Iris 很小但 Fisher 的思想足够支撑你走很远。最后分享一个小技巧在你的 Jupyter Notebook 里把S_W,S_B,W,X_proj这些关键变量都用# %%魔法命令隔开并加上清晰注释。