
简介基于Python的Fisher线性判别模式识别资源包面向模式识别初学者、研究生及需要完成相关课程设计或大作业的学生目标是解决两类样本的线性投影与最优分类面确定问题。压缩包共含14个文件包括两个Python算法脚本数据读取与核心判别实现、多份实验数据集如sonar、iris、一份实验报告文档docx、一份PDF说明、README使用说明及LICENSE许可文件整体仅1.02MB轻量易用目录结构清晰便于离线浏览。目前已有250人学习使用适合作为模式识别课程实验的参照实现或自学案例。资源不仅提供可直接运行的代码还附有原始数据、类别标注文件和详细的原理文档能够帮助读者从样本投影、阈值选取到分类面构造完整理解Fisher线性判别的核心思想。特别是通过代码对照“类间距离尽可能大、类内距离尽可能小”的投影方向选择原则可直观掌握经典的两类判别方法配合实验报告能快速完成环境搭建、结果复现与误差分析是一份很好的模式识别实践素材。1. Fisher线性判别在模式识别里到底解决什么问题做模式识别课程设计或者实际项目时最常遇到的一个场景是手里有一堆样本每个样本带一个类别标签你想找一个规则把新来的样本分到正确的类里。Fisher线性判别Fisher Linear DiscriminantFLD是这类问题里最经典也最容易被忽略的方法——它不直接学分类边界而是先找一个投影方向把高维数据压到低维让同类样本聚拢、异类样本分开再在低维空间做分类。这个思路在特征维数高、样本量少的场景下尤其管用也是很多深度学习方案之前老师们习惯用来做baseline的方法。本文会用Python把Fisher判别的原理、实现、调参和踩坑一次讲清楚适合正在做模式识别作业、准备毕业设计或者想给分类任务找个可靠基线的从业者。2. Fisher线性判别的数学原理散度矩阵与广义特征值2.1 为什么不是直接用PCA或者最小距离分类很多人第一次接触Fisher判别时会有一个疑问PCA不也是降维吗为什么不直接用PCA这个问题的答案其实点出了Fisher判别的核心价值。PCA只关心数据整体的方差最大方向它不关心样本属于哪个类而Fisher判别关心的是“投影后类间散度尽量大、类内散度尽量小”。换句话说PCA是在无监督地描述数据分布Fisher判别是有监督地寻找判别方向。举个直观例子如果一个数据集在x方向上方差很大但两类样本在这个方向上是重叠的PCA会把主要成分放在x方向降维后两类照样分不开Fisher判别则会绕过这个方向找到那个让两类样本中心间距最大、且每个类内部又尽量紧凑的方向。最小距离分类比如直接算到各类中心的欧氏距离的问题在于它对特征的量纲非常敏感而且没有考虑类内分布的形状。如果某个类在某个方向上特别“胖”距离中心最近的判决面就会偏向那一侧导致误分类。Fisher判别的做法是把类内散布信息编码进投影向量里相当于对特征空间做了一个白化再去找判别方向这比单纯用距离更稳健。从数学上讲Fisher判别要最大化的是这样一个目标函数J(w) (w^T Sb w) / (w^T Sw w)其中 Sb 是类间散度矩阵Sw 是类内散度矩阵w 是投影方向的列向量。这个形式很直白分子衡量投影后各类中心之间的分离程度分母衡量投影后每个类内部样本的紧凑程度。我们要找一个方向 w让两个类的中心离得越远越好同时每个类的样本在w方向上越集中越好。2.2 类内散度矩阵与类间散度矩阵的定义先看两类分类的情况。假设样本矩阵 X 是 n 行 d 列n 是样本数d 是特征数。把属于类 c1 的样本记为 X1属于类 c2 的记为 X2每类的样本数分别是 n1 和 n2。类内散度矩阵 Sw 的定义是Sw Σ_{x∈X1} (x - μ1)(x - μ1)^T Σ_{x∈X2} (x - μ2)(x - μ2)^T其中 μ1、μ2 分别是两个类的均值向量。注意这里不是除以样本数而是直接累加所以样本越多的类对 Sw 的贡献越大。有时候教科书上会用协方差矩阵的加权平均来定义其实就是同一个东西差一个常数倍不影响最后求出来的投影方向。类间散度矩阵 Sb 的定义则是Sb (μ1 - μ2)(μ1 - μ2)^T对于两类问题Sb 的秩只有1因为它是一对向量外积的结果。这也意味着 Sw^{-1} Sb 这个矩阵的秩也只有1也就是说它只有一个非零特征值所以我们只需要求一个特征向量就够了。有了 Sw 和 Sb最优投影方向 w 可以通过求解广义特征值问题得到Sw^{-1} Sb w λ w取最大的特征值 λ 对应的特征向量 w就是我们要的投影方向。如果你熟悉矩阵运算会发现这等价于对 Sw^{-1} Sb 做特征值分解。由于 Sb 是秩1矩阵这个最大特征值其实也很容易求但下面我们用数值方法一锅端。2.3 扩展到多类情况的“全局散度”记法当类别数 K 大于 2 时我们需要把类内散度和类间散度的定义做推广。最常见的做法是定义总散度矩阵 StSt Σ_{i1}^{n} (x_i - μ)(x_i - μ)^T其中 μ 是所有样本的全局均值。然后 St Sw Sb 这个恒等式在多类情况下依然成立只不过 Sb 的定义变成Sb Σ_{k1}^{K} n_k (μ_k - μ)(μ_k - μ)^T也就是每一类的均值到全局均值的偏移量乘以该类样本数再外积累加。这时候 Sb 的秩最大是 K-1所以 K 类问题最多有 K-1 个非零判别方向。这就是为什么Fisher判别天然适合做降维——如果你有10类数据它最多给你9个判别分量每个分量都是按判别力从大到小排好的。多类情况下我们不再只取一个方向而是取前 m 个特征向量组成一个投影矩阵 W把原始特征从 d 维降到 m 维然后在低维空间里做近邻分类或者高斯分类。这里的 m 就是一个需要根据验证集表现来调的超参数一般来说 m 不会超过 K-1。3. 用Python从零实现Fisher线性判别动手跑通第一个版本3.1 数据准备用Iris数据集验证最小实现讲完原理直接用numpy从零写一遍Fisher判别不建议一上来就掉进sklearn的封装里——自己写一遍才能看清边界在哪里。这里用sklearn自带的Iris数据集做验证虽然它是三分类问题但可以先用其中两个类跑二分类再扩展到三类。import numpy as np import matplotlib.pyplot as plt from sklearn.datasets import load_iris # 加载数据只取前两个类别setosa 和 versicolor iris load_iris() X iris.data[:100] # 前100个样本每类50个 y iris.target[:100] # 标签0 和 1 # 只取最后两个特征花瓣宽度、花瓣长度方便二维可视化 X X[:, 2:4] print(样本形状:, X.shape, 标签:, np.unique(y, return_countsTrue))这里把数据切成两类的目的是先验证二分类版本的Fisher判别避免一开始就被多类问题干扰。取花瓣长度和宽度两个特征而不是全部四个特征是故意的——两个特征可以直接在二维平面上把投影结果画出来方便检查方向是否合理。如果这个二分类版本的结果和直觉一致再放开到全特征、全类别。3.2 四个关键矩阵均值、Sw、Sb、St接下来按部就班地算均值向量、类内散度矩阵和类间散度矩阵。这里有一个容易混淆的点类内散度矩阵是用每一类的样本减去该类的均值外积累加类间散度矩阵是用每一类的均值减去全局均值乘以样本数再外积累加。不要搞混减去的对象。def fisher_lda_fit(X, y): # 1. 获取类别信息和全局均值 classes np.unique(y) K len(classes) # 类别数 d X.shape[1] # 特征维数 global_mean np.mean(X, axis0) # 所有样本的全局均值向量 # 2. 初始化类内散度矩阵 Sw 和类间散度矩阵 Sb Sw np.zeros((d, d)) Sb np.zeros((d, d)) # 3. 遍历每一类累加散度矩阵 for c in classes: X_c X[y c] # 属于第c类的样本 n_c X_c.shape[0] # 第c类的样本数 mean_c np.mean(X_c, axis0) # 第c类的均值向量 # 类内散度该类的样本减去该类均值外积累加 diff X_c - mean_c # (n_c, d) Sw diff.T diff # 类间散度类均值与全局均值之差乘以样本数 delta (mean_c - global_mean).reshape(-1, 1) # (d, 1) Sb n_c * (delta delta.T) return Sw, Sb, classes这段代码的核心逻辑是遍历每一类分别累加 Sw 和 Sb。注意 diff.T diff 是 (d, n_c) 乘 (n_c, d)结果是 (d, d) 的方阵这正是“外积累加”的向量化写法比手写循环快得多。delta 被 reshape 成列向量是为了让 delta delta.T 得到外积而不是内积。如果忘记 reshapedelta delta 会算出一个标量整个 Sb 就错了。3.3 求解广义特征值问题得到投影向量def fisher_project(X, y, n_components1): Sw, Sb, classes fisher_lda_fit(X, y) # 求解广义特征值问题Sw^{-1} Sb 的特征值分解 # 使用 eig 而不是 eigh因为 Sw^{-1} Sb 不一定对称 eig_vals, eig_vecs np.linalg.eig(np.linalg.inv(Sw) Sb) # 特征值按从大到小排序取前 n_components 个特征向量 idx np.argsort(eig_vals)[::-1] eig_vecs eig_vecs[:, idx] # 归一化特征向量 for i in range(eig_vecs.shape[1]): eig_vecs[:, i] eig_vecs[:, i] / np.linalg.norm(eig_vecs[:, i]) W eig_vecs[:, :n_components] # (d, n_components) X_proj X W # (n, n_components) return X_proj, W, eig_vals[idx]这里的关键点是 np.linalg.inv(Sw) Sb 并不对称所以要用 np.linalg.eig 而不是 np.linalg.eigh。如果直接用 eigh它在对称矩阵假设下会用快速算法但遇到非对称输入时结果会出错。特征向量按特征值大小排列后取前几个方向就是判别力最强的方向。归一化这一步很关键因为特征值分解出来的特征向量长度是任意的不归一化的话投影坐标的量纲没有意义。跑完这步把 Iris 前两类的二维数据投影到一个方向上你会发现投影坐标在两类之间的分离度明显优于原始任一特征。这个方向本质上是在“花瓣宽度”和“花瓣长度”的二维平面里找了一条斜线方向两类样本在它上面的均值间隔最大、类内方差最小。3.4 投影后的分类决策与阈值选择投影只是第一步最终要落地到分类。一个朴素但有效的做法是在投影后的坐标轴上找一个阈值对这个阈值做线性判决。常见的选阈值方法是取两个类投影均值的中心点或者用高斯模型估计每个类的分布后找最大似然分界点。def fisher_predict(X, W, threshold): # 投影到一维空间 z X W # 二分类判决z threshold 判为类1否则判为类0 y_pred (z threshold).astype(int) return y_pred # 在训练集上计算投影均值和阈值 X_proj_train, W, _ fisher_project(X, y, n_components1) mean0 X_proj_train[y 0].mean() mean1 X_proj_train[y 1].mean() threshold (mean0 mean1) / 2 # 在测试数据上分类 X_test np.array([[5.0, 2.0], [6.0, 2.5], [4.5, 1.5]]) z_test X_test W y_test_pred (z_test threshold).astype(int) print(投影坐标:, z_test.flatten(), 预测标签:, y_test_pred)阈值取两个类投影均值的中心点是最大间隔的一个近似在高斯等方差假设下这是最优阈值。如果两个类的先验概率差别很大或者类内协方差明显不同建议把阈值调整为先验概率的log-odds来修正。另外如果用了 n_components1W 是 (d,1) 矩阵z X W 得到的是 (n,1) 列向量比较时注意先 flatten 再和标量 threshold 比对。这里值得说一下Fisher投影之后的数据也可以直接丢给任何其他分类器比如在低维空间跑KNN。不少从业者的习惯是“Fisher判别做特征提取再在外面套一个分类器”这种做法在特征维数高、样本量少的时候比直接在高维空间跑KNN稳定得多。Fisher在这里的角色更像一个带监督信息的特征压缩器。4. 避坑Fisher线性判别最常见的5个翻车现场4.1 现象1类内散度矩阵奇异np.linalg.inv直接报错现象跑 np.linalg.inv(Sw) 的时候报 LinAlgError: Singular matrix或者算出无穷大的特征值。原因Sw 奇异的场景非常常见尤其是特征维数 d 大于样本数 n 的时候。比如你有 40 个样本、每个样本 100 维Sw 是个 100x100 的矩阵但它的有效秩最多只有 40必然奇异。另一个常见场景是特征之间存在线性相关性比如把原始特征和它的两倍同时放进特征矩阵里。解决最直接的办法是把 Sw 加上一个小的单位矩阵扰动也就是用 Sw λI 替代 Sw其中 λ 是一个很小的正数比如 1e-6。这个技巧在统计里叫正则化对应sklearn里 shrinkage 参数的思想。加了扰动之后 Sw 变成可逆矩阵求出来的特征向量虽然有一点偏移但稳定性大幅提升。如果加了扰动还是奇异说明你的特征冗余太严重建议先用PCA把维度压到样本数以下再跑Fisher。# 用正则化解决奇异问题 lambda_reg 1e-6 Sw_reg Sw lambda_reg * np.eye(Sw.shape[0]) eig_vals, eig_vecs np.linalg.eig(np.linalg.inv(Sw_reg) Sb)4.2 现象2特征值出现复数或者负值现象用 np.linalg.eig 求解后特征值是复数形式比如 (1.50.02j)特征向量也跟着变成复数投影坐标出现虚部。原因Sw^{-1} Sb 在数学上不保证是对称矩阵而 np.linalg.eig 是通用解法理论上可以得到复数特征值。但 Fisher 判别的理论保证这个广义特征值问题会有实特征值所以出现复数通常意味着前面的散度矩阵算错了最常见的是 Sw 和 Sb 的公式用反了或者样本均值算成了列均值而不是行均值。解决逐项检查散度矩阵的计算。一个快速验证手段Sw 必须是对称半正定矩阵Sb 在二分类下是秩1对称矩阵。可以用 np.max(np.abs(Sw - Sw.T)) 检查对称性如果这个值大于 1e-12说明代码里某处矩阵转置写错了。另一个原因是数据里有 NaN 或者 inf导入数据后先检查 X 和 y 里是否有缺失值。# 校验散度矩阵对称性 print(Sw 对称性误差:, np.max(np.abs(Sw - Sw.T))) print(Sb 对称性误差:, np.max(np.abs(Sb - Sb.T)))4.3 现象3数据量纲不同导致投影被大数值特征淹没现象投影向量 w 里某一维的系数异常大其他维度几乎被忽略分类效果还不如直接用某个单特征分类。原因Fisher判别没有内置的量纲归一化。如果一个特征的单位是毫米、取值范围 0 到 1000另一个特征的单位是米、取值范围 0 到 1散度矩阵里那个大数值特征会完全主导 Sw 和 Sb 的计算投影方向几乎只沿着大数值特征的方向。这在模式识别项目里非常常见尤其是直接从传感器原始数据提取特征时。解决在跑Fisher之前先做标准化。最常见的是 z-score 标准化也就是每个特征减去均值再除以标准差把每个特征拉到约 N(0,1) 的分布。注意标准化要在训练集上算均值和标准差然后用同样的参数去变换测试集否则测试数据会被泄漏信息。from sklearn.preprocessing import StandardScaler scaler StandardScaler() X_train_std scaler.fit_transform(X_train) X_test_std scaler.transform(X_test) # 在标准化后的数据上跑Fisher X_proj, W, eig_vals fisher_project(X_train_std, y_train)标准化之后特征的量纲影响被消除Fisher判别方向会更均衡地利用各个特征。这里有一个值得养成的习惯不管后面用什么分类器先做标准化再看效果绝大多数情况下都不会吃亏。如果标准化之后效果反而变差再考虑是不是某些特征的方差本身就携带判别信息。4.4 现象4类别不平衡导致判别方向偏移现象数据里类A有200个样本类B只有20个Fisher投影后分类边界严重偏向类B类B的样本几乎全被误判。原因Sw 的定义里每个类内散度矩阵是直接累加样本多的类别在 Sw 里的占比更大导致计算出的投影方向被主导类拉走。同时 Sb 里每个类乘以 n_k大类的类中心位移被放大投影方向更倾向于让大类离全局均值更远而不是真正让两个类分开。解决最常见的做法是对散度矩阵做加权修正把每个类的贡献除以其样本数让每个类在Sw里的权重均等。具体来说Sw 累加时把 diff.T diff 除以 n_cSb 累加时把 n_k 权重去掉只用 (μ_k - μ)(μ_k - μ)^T。这个改动在小样本类上效果非常明显。def fisher_lda_fit_balanced(X, y): classes np.unique(y) d X.shape[1] global_mean np.mean(X, axis0) Sw np.zeros((d, d)) Sb np.zeros((d, d)) for c in classes: X_c X[y c] n_c X_c.shape[0] mean_c np.mean(X_c, axis0) diff X_c - mean_c Sw (diff.T diff) / n_c # 用均值协方差去掉样本数影响 delta (mean_c - global_mean).reshape(-1, 1) Sb delta delta.T # 不再乘以 n_c return Sw, Sb这种加权版本的Fisher判别在实际项目中比原始定义稳健很多尤其是当类别数量悬殊时先试这个版本再试原始版本看哪个在验证集上表现更好。4.5 现象5n_components 选择不当导致信息丢失现象多类数据降到一维后有两个类在投影方向上完全重叠分类准确率崩盘。原因Fisher判别最多提供 K-1 个判别方向。如果你有 5 个类最多 4 个方向。但并不是每个方向都有判别价值最后的几个方向对应的特征值可能很小几乎全是噪声。如果 n_components 取太大会把噪声方向引入分类器如果取太小又可能丢失区分某些类别的关键信息。更麻烦的是降维到一维时如果三类样本的均值恰好投影到同一个点上模型无从区分。解决不要凭经验拍脑袋定 n_components用交叉验证去选。比如从1到K-1遍历每一折训练集上拟合Fisher投影测试集上投影后用KNN分类画一条验证准确率随 n_components 变化的曲线取峰值对应的值。下面是简化的搜索代码from sklearn.neighbors import KNeighborsClassifier from sklearn.model_selection import cross_val_score def evaluate_fisher_components(X, y, max_components): scores [] for m in range(1, max_components 1): X_proj, W, _ fisher_project(X, y, n_componentsm) knn KNeighborsClassifier(n_neighbors3) s cross_val_score(knn, X_proj, y, cv5).mean() scores.append(s) best_m np.argmax(scores) 1 print(最佳分量数:, best_m, 最高准确率:, max(scores)) return best_m # 多类数据上用搜索方式选分量数 best_m evaluate_fisher_components(X_train_multi, y_train, max_componentsK-1)另外一个很容易被忽略的点投影之后每个分量的取值范围差异可能很大直接丢给KNN的话距离计算会被大数值分量主导。通常在投影之后再做一个标准化或者分量加权效果会比裸投影好。Fisher判别在这一点上面临和PCA完全一样的陷阱。5. 进阶从二分类到多类、从手写代码到工程验证5.1 多类Fisher判别的完整实现前面二分类的代码稍加改动就能支持多类。核心变化是 Sb 的定义从“每类均值与全局均值的外积”变成“多类累加”投影矩阵 W 也从一列变为多列。下面给出一个可直接替换的多类版本注意处理特征值可能是复数的问题投影时只保留实数部分def fisher_lda_multi(X, y, n_componentsNone): classes np.unique(y) K len(classes) d X.shape[1] if n_components is None: n_components K - 1 global_mean np.mean(X, axis0) Sw np.zeros((d, d)) Sb np.zeros((d, d)) for c in classes: X_c X[y c] n_c X_c.shape[0] mean_c np.mean(X_c, axis0) # 类内散度除以样本数得到平均协方差 diff X_c - mean_c Sw (diff.T diff) / n_c # 类间散度多类版本累加 delta (mean_c - global_mean).reshape(-1, 1) Sb delta delta.T # 正则化防止奇异 Sw_reg Sw 1e-8 * np.eye(d) # 求解广义特征值问题 eig_vals, eig_vecs np.linalg.eig(np.linalg.inv(Sw_reg) Sb) # 只取实数部分按特征值降序排列 eig_vals np.real(eig_vals) eig_vecs np.real(eig_vecs) idx np.argsort(eig_vals)[::-1] eig_vecs eig_vecs[:, idx] # 归一化每个特征向量 for i in range(eig_vecs.shape[1]): eig_vecs[:, i] eig_vecs[:, i] / np.linalg.norm(eig_vecs[:, i]) W eig_vecs[:, :n_components] X_proj X W return X_proj, W, eig_vals这里的两个关键改动一是 Sw 除以 n_c让类内散度成为“平均协方差”避免样本量大的类过度主导二是引入 1e-8 的正则项保证 Sw 可逆。多类场景下投影出来的维度最多 K-1 个这个上限是理论约束不是代码限制。5.2 和 sklearn 的 LinearDiscriminantAnalysis 做对比手写版本跑通之后强烈建议用 sklearn 的 LinearDiscriminantAnalysis 做一次交叉验证确保你的实现没有原则性错误。对比时关注两件事投影方向是否一致可能差一个符号或常数倍、分类准确率是否在误差范围内。from sklearn.discriminant_analysis import LinearDiscriminantAnalysis from sklearn.model_selection import train_test_split X_train, X_test, y_train, y_test train_test_split( iris.data, iris.target, test_size0.3, random_state42 ) # sklearn 版本 lda LinearDiscriminantAnalysis(n_components2) X_train_sk lda.fit_transform(X_train, y_train) acc_sk lda.fit(X_train, y_train).score(X_test, y_test) # 自写版本 X_train_my, W, eig_vals fisher_lda_multi(X_train, y_train, n_components2) acc_my 0 # 自写版本没有内置分类器用最近邻做参照 from sklearn.neighbors import KNeighborsClassifier knn KNeighborsClassifier(n_neighbors3) knn.fit(X_train_my, y_train) X_test_my X_test W acc_my knn.score(X_test_my, y_test) print(sklearn LDA 准确率:, acc_sk) print(自写Fisher KNN 准确率:, acc_my) print(投影后特征值:, eig_vals[:5])如果两个准确率差距在 1-2 个点以内说明实现正确。如果差距很大先检查 Sw 和 Sb 里有没有除以 n_c 的差别。sklearn 默认使用带 shrinkage 的求解器对奇异矩阵有更好的鲁棒性所以它在你没做正则化时也能跑出结果这不代表你的代码写错了只说明 sklearn 内部封装了更多细节。5.3 用交叉验证做可靠性检验Fisher判别调参的核心就两个n_components 和 Sw 的正则化系数 lambda。与其问“哪个值最好”不如写一段网格搜索把两个参数一起扫一遍用交叉验证选组合。下面是实操中可以直接改的搜索逻辑from sklearn.model_selection import StratifiedKFold from sklearn.pipeline import Pipeline from sklearn.preprocessing import StandardScaler def fisher_pipeline_eval(X, y, components_list, lambda_list): best_score 0 best_params None for m in components_list: for lam in lambda_list: # 手动实现带正则化的Fisher投影 score_list [] skf StratifiedKFold(n_splits5, shuffleTrue, random_state42) for train_idx, val_idx in skf.split(X, y): X_tr, y_tr X[train_idx], y[train_idx] X_val, y_val X[val_idx], y[val_idx] # 标准化与投影 scaler StandardScaler().fit(X_tr) X_tr_std scaler.transform(X_tr) X_val_std scaler.transform(X_val) # 自定义 Fisher 投影带正则项 lambda Sw, Sb compute_sw_sb(X_tr_std, y_tr) Sw_reg Sw lam * np.eye(Sw.shape[0]) eig_vals, eig_vecs np.linalg.eig(np.linalg.inv(Sw_reg) Sb) eig_vals, eig_vecs np.real(eig_vals), np.real(eig_vecs) idx np.argsort(eig_vals)[::-1] W eig_vecs[:, idx][:, :m] X_tr_proj X_tr_std W X_val_proj X_val_std W knn KNeighborsClassifier(n_neighbors5) knn.fit(X_tr_proj, y_tr) score_list.append(knn.score(X_val_proj, y_val)) avg_score np.mean(score_list) if avg_score best_score: best_score avg_score best_params (m, lam) print(最佳参数: n_components{}, lambda{}, 准确率{:.4f}.format( best_params[0], best_params[1], best_score )) return best_params # 在完整 Iris 上搜索 best_params fisher_pipeline_eval(iris.data, iris.target, components_list[1, 2], lambda_list[1e-6, 1e-4, 0.01])这段代码把标准化、Fisher投影、KNN分类串成一个完整的评估链路每一折都在训练集上拟合参数、在验证集上评估避免了信息泄漏。搜索出来的最佳参数组合就是你在正式上预测模型前应该使用的配置。我个人的习惯是在跑任何深度学习模型之前先用这条链路跑一遍如果Fisher KNN已经拿到98%的准确率那就没必要上复杂模型后续所有工作都围绕特征工程和部署展开就可以了。这也是这些年做模式识别项目时最值得的坚持——先让简单模型跑赢再决定要不要加复杂度。希望帮到你。本文还有配套的精品资源点击获取