从西瓜数据集实战线性判别分析:原理推导与Python代码实现
2026/9/12 4:15:51 网站建设 项目流程

简介:本资源是一份面向机器学习初学者与实践者的线性判别分析(LDA)教学实践包,聚焦特征降维与多类分类任务,以经典西瓜数据集3.0为载体,完整呈现LDA在Python中的建模、训练与评估全流程。压缩包共10个文件,含2个核心Python脚本(LDA.py与self_def.py)、1个CSV格式的西瓜数据集(watermelon_3a.csv)、3个XML配置文件及辅助开发文件(如.iml、.pyc、.DS_Store),总大小仅12KB,轻量易部署,适合嵌入课程实验或自学项目。已有584人下载学习,资源结构简洁清晰:src目录封装主程序逻辑,data目录存放原始数据,.idea配置支持PyCharm快速调试。读者可直接运行代码完成数据加载、训练集划分、LDA模型拟合、预测及classification_report性能评估,同时获得对高斯假设、类内/类间散度理解的实践入口,是掌握监督式降维方法的实用入门材料。

1. 项目概述:从“西瓜数据集”到线性判别分析(LDA)的实战之旅

如果你刚接触机器学习,面对一堆算法名词感到头大,那“线性判别分析”(Linear Discriminant Analysis, LDA)绝对是一个值得你花时间吃透的经典算法。它不像深度学习那样需要海量数据和算力,也不像某些黑盒模型那样难以解释。LDA的核心思想非常直观:找到一个投影方向,让不同类别的数据在这个方向上投影后,类间的距离尽可能大,类内的距离尽可能小。听起来是不是有点像在人群中找一条路,让不同队伍的人站得尽量分开,而同一队伍的人站得尽量紧凑?这个经典的“降维”兼“分类”算法,在文本主题建模(另一个LDA,Latent Dirichlet Allocation)、人脸识别、生物信息学等领域至今仍有广泛应用。

今天,我们就用一个非常接地气的“西瓜数据集”来手把手实现LDA。这个数据集在周志华老师的《机器学习》(俗称“西瓜书”)里被反复使用,数据量小、特征明确,是理解算法原理的绝佳“教具”。我们将用Python从零开始,不依赖高级的scikit-learn封装(当然最后也会对比一下),一步步推导公式、编写代码、可视化结果,让你不仅知道怎么调包,更明白包里的每一行代码在算什么。你会发现,LDA.zip里装的不仅是代码,更是一套理解监督式降维与分类的完整思维模型。

2. LDA的核心原理与数学推导拆解

2.1 问题定义:我们要解决什么?

假设我们手头有一个数据集,比如我们的“西瓜数据集”,每个西瓜样本有多个特征(如密度、含糖率),并且每个样本都有一个已知的类别标签(如好瓜、坏瓜)。LDA要解决的核心问题是:如何利用这些已知的类别信息,找到原始特征空间的一个低维投影(通常是直线或平面),使得在这个新的低维空间里,不同类别的样本能最大程度地被区分开。

这带来了两个直接的好处:1.降维:将高维数据可视化到二维或三维,便于我们观察和理解。2.分类:可以直接在这个投影后的低维空间里构建一个简单的分类器(比如找一个分类阈值)。

2.2 核心思想:类内小,类间大

LDA的目标可以用一个优化问题来精确描述。我们目标是找到一个投影向量w,将样本x投影到一维空间y = w^T x。LDA希望投影后,满足:

  1. 类内散度(Within-class scatter)最小化:同一类别样本的投影点尽可能聚集在一起,方差小。
  2. 类间散度(Between-class scatter)最大化:不同类别样本的投影点的中心尽可能远离。

如何量化“散度”呢?这里引入了散度矩阵的概念。

  • 类内散度矩阵(Sw):衡量每个类别内部样本的分散程度。计算方法是每个类别样本与其类别均值的协方差矩阵之和。Sw = Σ_i Σ_{x∈X_i} (x - m_i)(x - m_i)^T,其中m_i是第i类的均值向量。
  • 类间散度矩阵(Sb):衡量不同类别中心之间的分散程度。计算方法是每个类别的样本数乘以该类均值与总体均值的差的外积之和。Sb = Σ_i n_i (m_i - m)(m_i - m)^T,其中m是全体样本的总体均值向量,n_i是第i类的样本数。

投影后,类内散度变为w^T Sw w,类间散度变为w^T Sb w。我们的优化目标就是最大化它们的比值,即寻找w使得下式最大:J(w) = (w^T Sb w) / (w^T Sw w)这个J(w)就是著名的“广义瑞利商”(Generalized Rayleigh Quotient)。

2.3 求解:特征值分解的登场

如何求解使J(w)最大的w呢?在数学上,可以证明,最优的投影方向w满足方程:Sb w = λ Sw w这被称为广义特征值问题。通常,Sw是可逆的,上式可以转化为标准特征值问题:Sw^{-1} Sb w = λ w也就是说,我们要求解矩阵Sw^{-1} Sb的特征值和特征向量。其中,最大特征值对应的特征向量,就是我们要找的、使得类间区分度最大的投影方向。如果我们想投影到多维(比如二维),就取前k个最大特征值对应的特征向量,组成投影矩阵W

注意:这里有一个非常重要的前提,就是Sw必须是可逆的。这就要求样本数量必须大于特征维度,且特征之间不能存在完全的线性相关性。在实际应用中,如果Sw奇异(不可逆),需要进行正则化处理(如加上一个很小的单位矩阵,Sw + εI),或者先使用PCA进行降维。

2.4 LDA与PCA的本质区别

很多人容易混淆LDA和主成分分析(PCA)。这里务必厘清:

  • PCA(无监督):寻找数据方差最大的投影方向,目标是保留最多的数据信息(方差),完全无视样本的类别标签。
  • LDA(有监督):寻找能最好区分已知类别的投影方向,目标是最大化分类潜力,强烈依赖类别标签。

用一个比喻:PCA是给你一堆混在一起的彩色珠子,它帮你找到一条线,让所有珠子在这条线上铺得最开(方差最大),不管颜色。LDA是已知哪些珠子是红色、哪些是蓝色,它帮你找到一条线,让红色珠子和蓝色珠子在这条线上分别聚成两团,且两团离得尽量远。

3. 西瓜数据集解析与Python环境准备

3.1 西瓜数据集3.0α 深度解析

我们采用“西瓜书”第3章最经典的“西瓜数据集3.0α”。这个数据集虽然小,但涵盖了连续值和离散值,为了聚焦LDA核心,我们通常只选取两个连续特征:密度含糖率,以及一个二分类标签:好瓜(是)坏瓜(否)

原始数据可能如下表所示:

编号密度含糖率好瓜
10.6970.460
20.7740.376
............
170.5930.042

这个数据集非常适合教学:

  1. 维度低:只有2个特征,投影结果可以轻松在二维平面上可视化,直观理解LDA的投影直线。
  2. 样本少:总共17个样本,手动计算或调试代码都非常方便。
  3. 线性可分性:数据在二维平面上大致是线性可分的,LDA能发挥出很好的效果。

在实际代码中,我们会将“是”标记为类别1,“否”标记为类别0,并将特征数据整理成一个(17, 2)的矩阵X,标签整理成一个(17,)的向量y

3.2 Python环境与工具库清单

我们将使用最基础的科学计算库来实现LDA,确保每一步都清晰可见。

# 环境准备:所需库 import numpy as np import matplotlib.pyplot as plt from sklearn.discriminant_analysis import LinearDiscriminantAnalysis as SKLDA # 用于对比 # 确保可复现性 np.random.seed(42)

核心库说明:

  • NumPy:整个项目的基石。所有矩阵运算(均值、协方差、特征值分解)都依赖它。务必理解其数组广播和矩阵乘法。
  • Matplotlib:用于可视化。我们将绘制原始数据散点图、LDA投影方向、以及投影后的数据分布直方图。
  • scikit-learn:仅用于最后的结果验证和对比。我们会用它的LDA实现来检验我们自己编写的算法是否正确。

一个关键的实操心得:在开始编码前,我强烈建议你在Jupyter Notebook或支持交互的Python环境中进行。这样你可以随时打印中间变量(如Sw,Sb, 特征值等)的形状和值,与手算或逻辑推导进行比对,这是理解算法、调试代码最有效的方式。

4. 从零手撕LDA代码实现

4.1 数据加载与预处理

首先,我们手动输入西瓜数据集,并进行标准化处理。虽然LDA本身不受量纲影响(因为其优化目标是一个比值),但良好的数据习惯是从标准化开始。

def load_watermelon_data(): """加载西瓜数据集3.0α (密度, 含糖率)""" # 特征X: 密度, 含糖率 X = np.array([ [0.697, 0.460], [0.774, 0.376], [0.634, 0.264], [0.608, 0.318], [0.556, 0.215], [0.403, 0.237], [0.481, 0.149], [0.437, 0.211], [0.666, 0.091], [0.243, 0.267], [0.245, 0.057], [0.343, 0.099], [0.639, 0.161], [0.657, 0.198], [0.360, 0.370], [0.593, 0.042], [0.719, 0.103] ]) # 标签y: 1为好瓜(是), 0为坏瓜(否) y = np.array([1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0]) return X, y X, y = load_watermelon_data() print(f"数据形状: X{X.shape}, y{y.shape}") print(f"类别分布: 好瓜{sum(y==1)}个, 坏瓜{sum(y==0)}个") # 数据标准化 (Z-score标准化) def standardize(X): mean = np.mean(X, axis=0) std = np.std(X, axis=0) return (X - mean) / std, mean, std X_std, X_mean, X_std_dev = standardize(X) print("标准化后的前5个样本:\n", X_std[:5])

注意:这里标准化存储了均值和标准差,这是一个好习惯。在后续将新样本投影到LDA空间时,你需要用同样的均值和标准差先对新样本进行标准化,然后再乘以我们求得的投影矩阵W。很多初学者会忘记这一步,导致预测错误。

4.2 核心计算:类内与类间散度矩阵

这是LDA算法的核心步骤。我们需要分别计算每个类别的均值、总体均值,然后套用公式计算SwSb

def compute_lda_components(X, y): """ 计算LDA所需的散度矩阵和投影向量 参数: X: 标准化后的特征矩阵 (n_samples, n_features) y: 标签向量 (n_samples,) 返回: W: 投影矩阵 (n_features, n_components) eigenvalues: 特征值 """ n_features = X.shape[1] labels = np.unique(y) # 计算总体均值 overall_mean = np.mean(X, axis=0) # 初始化 Sw 和 Sb 为零矩阵 Sw = np.zeros((n_features, n_features)) Sb = np.zeros((n_features, n_features)) for label in labels: # 获取当前类别的样本 X_i = X[y == label] # 计算当前类别的均值 mean_i = np.mean(X_i, axis=0) # 计算当前类别的类内散度矩阵并累加到 Sw # X_i - mean_i 是中心化后的矩阵 Sw += (X_i - mean_i).T @ (X_i - mean_i) # 等价于 np.cov(X_i.T, bias=True) * (len(X_i)-1) # 计算当前类别的类间散度矩阵贡献并累加到 Sb n_i = len(X_i) mean_diff = (mean_i - overall_mean).reshape(-1, 1) # 转换为列向量 Sb += n_i * (mean_diff @ mean_diff.T) # 求解广义特征值问题: Sw^{-1} Sb w = λ w # 使用 np.linalg.eig 求解,但更稳定的方式是使用 np.linalg.pinv(Sw) @ Sb # 因为Sw可能接近奇异,使用伪逆更稳健 Sw_inv = np.linalg.pinv(Sw) # 使用伪逆代替逆,提高数值稳定性 M = Sw_inv @ Sb # 计算特征值和特征向量 eigenvalues, eigenvectors = np.linalg.eig(M) # 特征值和特征向量是复数(由于数值计算),我们取实部 eigenvalues = np.real(eigenvalues) eigenvectors = np.real(eigenvectors) # 按特征值降序排序 idx = eigenvalues.argsort()[::-1] eigenvalues = eigenvalues[idx] eigenvectors = eigenvectors[:, idx] # 我们通常选择非零特征值对应的特征向量。 # 对于二分类问题,Sb的秩最大为1,因此最多只有一个非零特征值,对应一个投影方向。 # 我们选取前 min(n_features, len(labels)-1) 个特征向量,对于二分类就是1个。 n_components = min(n_features, len(labels) - 1) W = eigenvectors[:, :n_components] return W, eigenvalues W, eig_vals = compute_lda_components(X_std, y) print("投影向量 W (列向量):\n", W) print("对应的特征值:\n", eig_vals) print("投影向量形状:", W.shape)

关键点解析:

  1. Sw的计算:(X_i - mean_i).T @ (X_i - mean_i)是协方差矩阵的(n-1)倍。这是计算类内散度最直接的方式。
  2. Sb的计算:mean_diff被重塑为列向量,mean_diff @ mean_diff.T得到一个矩阵,乘以样本数n_i后累加。
  3. 使用伪逆np.linalg.pinv:这是实现中的关键技巧。直接求逆np.linalg.inv(Sw)Sw接近奇异(即特征间相关性很强或样本数少于特征数)时会报错。伪逆提供了数值上更稳定的解。
  4. 特征值分解结果np.linalg.eig返回的特征值和特征向量可能是复数,这是浮点数计算中的微小误差导致的,我们取实部即可。
  5. 投影维度选择:LDA能降维到的最大维度是min(n_features, n_classes-1)。对于二分类问题,无论原始特征有多少维,投影后最多只有一维(一条直线)。我们的西瓜数据是二维特征二分类,所以W是一个(2, 1)的矩阵,即一个投影方向。

4.3 数据投影与结果可视化

得到投影方向W后,我们就可以将原始数据投影到这条直线上,并直观地看到分类效果。

def project_and_visualize(X, y, W): """将数据投影到LDA方向并可视化""" # 投影: Y = X * W X_projected = X @ W # 创建画布 fig, axes = plt.subplots(1, 2, figsize=(14, 5)) # 子图1: 原始数据及LDA投影方向 ax1 = axes[0] # 绘制原始数据点 ax1.scatter(X[y==1, 0], X[y==1, 1], c='red', marker='o', label='好瓜', alpha=0.7, s=80) ax1.scatter(X[y==0, 0], X[y==0, 1], c='blue', marker='s', label='坏瓜', alpha=0.7, s=80) # 绘制LDA投影方向(直线) # 直线的方向由W决定,我们过原点画一条线(因为数据已标准化,中心在原点) # 生成直线上的点 line_x = np.linspace(X[:,0].min()-0.5, X[:,0].max()+0.5, 100) # W是(2,1)矩阵,W[1]/W[0]是斜率 if abs(W[0]) > 1e-10: # 避免除零 line_y = (W[1]/W[0]) * line_x ax1.plot(line_x, line_y, 'k--', linewidth=2, label=f'LDA投影方向 (斜率={W[1]/W[0]:.3f})') else: # 如果W[0]接近0,说明投影方向接近y轴 ax1.axvline(x=0, color='k', linestyle='--', linewidth=2, label='LDA投影方向 (垂直)') # 绘制样本点到投影方向的垂线(可选,更直观) for i in range(len(X)): x_point = X[i] # 计算投影点坐标 # 投影到方向向量W上: proj = (x·w)/(w·w) * w w_vec = W.flatten() proj_coeff = np.dot(x_point, w_vec) / np.dot(w_vec, w_vec) proj_point = proj_coeff * w_vec ax1.plot([x_point[0], proj_point[0]], [x_point[1], proj_point[1]], 'gray', alpha=0.3, linewidth=0.5) ax1.set_xlabel('密度 (标准化后)') ax1.set_ylabel('含糖率 (标准化后)') ax1.set_title('原始数据与LDA投影方向') ax1.legend() ax1.grid(True, linestyle='--', alpha=0.5) ax1.axis('equal') # 子图2: 投影后的数据分布(直方图) ax2 = axes[1] # 获取两类投影后的值 proj_good = X_projected[y==1].flatten() proj_bad = X_projected[y==0].flatten() # 绘制直方图 ax2.hist(proj_good, bins=8, alpha=0.7, color='red', label='好瓜', edgecolor='black') ax2.hist(proj_bad, bins=8, alpha=0.7, color='blue', label='坏瓜', edgecolor='black') # 计算并绘制两类投影中心的垂线 mean_good = np.mean(proj_good) mean_bad = np.mean(proj_bad) ax2.axvline(x=mean_good, color='darkred', linestyle='-', linewidth=2, label=f'好瓜中心 ({mean_good:.3f})') ax2.axvline(x=mean_bad, color='darkblue', linestyle='-', linewidth=2, label=f'坏瓜中心 ({mean_bad:.3f})') # 计算一个简单的分类阈值(两类中心的平均值) simple_threshold = (mean_good + mean_bad) / 2 ax2.axvline(x=simple_threshold, color='green', linestyle='--', linewidth=3, label=f'分类阈值 ({simple_threshold:.3f})') ax2.set_xlabel('投影值') ax2.set_ylabel('频数') ax2.set_title('投影后数据分布直方图') ax2.legend() ax2.grid(True, linestyle='--', alpha=0.5) plt.tight_layout() plt.show() return X_projected, simple_threshold X_proj, threshold = project_and_visualize(X_std, y, W) print(f"投影后数据形状: {X_proj.shape}") print(f"建议的分类阈值 (两类中心均值): {threshold:.4f}")

可视化解读:

  • 左图:你可以清晰地看到一条黑色虚线,那就是LDA找到的最佳投影方向。所有样本点都沿着灰色细线“垂落”到这条黑线上。我们的目标就是让红色圆圈(好瓜)和蓝色方块(坏瓜)在这条黑线上的投影点尽可能地各自聚集,且两团离得远。
  • 右图:直方图展示了投影后的结果。红色和蓝色的柱子分别代表好瓜和坏瓜投影值的分布。两条深色竖线是它们的中心,绿色虚线是取两个中心点中点得到的简单分类阈值。可以看到,在这个一维投影空间里,两类数据基本被分开了,只有极少部分重叠。这证明了LDA投影的有效性。

4.4 分类器构建与性能评估

投影之后,我们实际上得到了一个一维特征。在这个一维空间里构建分类器就非常简单了,最常见的就是用一个阈值来划分。

def lda_classifier(X_projected, threshold): """简单的阈值分类器""" # 假设投影后,好瓜的投影值通常更大(取决于W的方向) # 如果投影值大于阈值,预测为好瓜(1),否则为坏瓜(0) return (X_projected > threshold).astype(int).flatten() # 在训练集上应用分类器 y_pred = lda_classifier(X_proj, threshold) # 计算准确率 accuracy = np.mean(y_pred == y) print(f"基于LDA投影和阈值分类的训练集准确率: {accuracy:.2%}") # 更严谨的做法:查看分类决策 print("\n样本预测详情 (编号, 真实标签, 投影值, 预测标签):") for i in range(len(X)): print(f"{i+1:2d}: {y[i]} -> {X_proj[i,0]:7.4f} -> {y_pred[i]} {'✓' if y[i]==y_pred[i] else '✗'}")

注意事项:这里我们直接在训练集上评估,准确率会偏高(过拟合)。在实际项目中,必须使用未见过的测试集或交叉验证来评估模型性能。对于这个微型教学数据集,我们主要是为了理解流程。

5. 与scikit-learn实现对比及高级话题

5.1 使用scikit-learn验证

为了验证我们手写的LDA是否正确,最好的办法是调用业界标准的scikit-learn实现进行对比。

def compare_with_sklearn(X_std, y): """与scikit-learn的LDA进行对比""" # 使用sklearn的LDA,指定降维到1维 sk_lda = SKLDA(n_components=1) X_proj_sk = sk_lda.fit_transform(X_std, y) # 注意:sklearn的fit_transform直接返回降维后的数据 # 获取sklearn计算出的投影向量 # sklearn的coef_是判别函数的系数,方向与我们的W有关但不直接相等。 # 更直接的是获取标量化后的特征向量。 # sklearn将投影向量存储在`scalings_`属性中 W_sk = sk_lda.scalings_[:, :1] # 取第一列 print("scikit-learn 计算出的投影向量 (scalings_):\n", W_sk) print("我们手写代码计算出的投影向量 W:\n", W) # 比较方向是否一致(可能差一个符号,因为方向相反不影响分类) # 计算两个向量的余弦相似度绝对值 cos_sim = np.abs(np.dot(W.flatten(), W_sk.flatten()) / (np.linalg.norm(W) * np.linalg.norm(W_sk))) print(f"\n投影向量方向余弦相似度 (绝对值): {cos_sim:.6f}") print("(非常接近1,说明方向一致)") # 比较投影后的数据(可能差一个缩放因子和符号) # 将我们的投影数据与sklearn的进行相关性比较 corr = np.corrcoef(X_proj.flatten(), X_proj_sk.flatten())[0,1] print(f"投影后数据的相关系数: {corr:.6f}") print("(非常接近1或-1,说明线性相关,即投影本质相同)") # 使用sklearn的模型进行预测 y_pred_sk = sk_lda.predict(X_std) accuracy_sk = np.mean(y_pred_sk == y) print(f"\nscikit-learn LDA 训练集准确率: {accuracy_sk:.2%}") print(f"我们手写LDA的准确率: {np.mean(lda_classifier(X_proj, threshold)==y):.2%}") compare_with_sklearn(X_std, y)

运行这段代码,你会发现我们的投影方向Wsklearnscalings_可能方向相反(余弦相似度接近-1),但投影后数据的相关系数绝对值接近1。这是完全正常的,因为投影直线没有正反方向之分,w-w是同一个子空间。分类准确率也应该是一致的。这个对比验证了我们手写LDA核心逻辑的正确性。

5.2 多分类LDA与维度选择

我们的例子是二分类。对于多分类问题(C>2个类别),LDA的原理完全一样。此时,Sb矩阵的秩最大为C-1。这意味着我们最多可以找到C-1个有意义的投影方向(对应C-1个非零特征值)。这些投影方向张成了一个最多C-1维的子空间。

在代码实现上,你只需要修改n_components参数,例如设置为min(n_features, n_classes-1),然后取前n_components个特征向量组成投影矩阵W。投影后的数据X_projected = X @ W将是一个(n_samples, n_components)的矩阵。

如何选择降维后的维度k一个常用的方法是观察特征值。特征值λ_i的大小代表了对应投影方向的重要性(区分能力)。你可以计算累计贡献率:累计贡献率 = 前k个特征值之和 / 所有特征值之和然后选择一个k,使得累计贡献率达到一个较高的阈值(如95%)。对于分类任务,有时直接选择k = n_classes - 1也是一个合理的默认值。

5.3 LDA的假设与局限性

没有哪个算法是万能的,LDA也不例外,它建立在几个关键假设之上:

  1. 正态性假设:每个类别的数据特征服从多元正态分布。在实际中,虽然轻微偏离此假设LDA仍可工作,但严重偏离会影响性能。
  2. 同方差性假设:所有类别的协方差矩阵相同(即Sw矩阵)。这是我们推导中Sw是各类别协方差矩阵之和的前提。如果不同类别数据形状差异很大(比如一个类是圆形散开,另一个类是扁椭圆形),LDA的效果会打折扣。
  3. 线性可分性:LDA寻找的是线性投影。如果原始数据本身是非线性可分的(比如环形分布),线性投影无法很好地区分,此时需要考虑核判别分析(KDA)或其他非线性方法。

一个重要的实操心得:在应用LDA前,快速检查数据是否大致符合这些假设是个好习惯。例如,可以分别画出每个类别特征的分布直方图或Q-Q图来检查正态性;可以计算每个类别的协方差矩阵并观察它们是否相似。当假设被严重违反时,朴素贝叶斯分类器(对特征独立性假设敏感)或二次判别分析(QDA,放松同方差假设)可能是更好的选择。

6. 常见问题、调试技巧与扩展应用

6.1 报错与问题排查速查表

问题现象可能原因解决方案
Sw矩阵奇异,无法求逆1. 样本数少于特征数。
2. 特征之间存在完全线性相关。
1. 增加样本或使用正则化(Sw + λI)。
2. 进行特征选择或先用PCA降维。
特征值/特征向量出现复数数值计算误差,Sw^{-1}Sb不是严格的对称矩阵。np.real()获取实部,通常不影响结果。或使用scipy.linalg.eigh求解广义特征值问题。
投影后分类效果很差1. LDA的假设(正态、同方差)严重不满足。
2. 数据本质是非线性可分的。
1. 尝试对数据进行变换(如对数变换)使其更接近正态。
2. 使用QDA、核方法或非线性分类器。
与sklearn结果符号相反投影方向w-w是等价的。检查投影后数据的相关系数,如果绝对值接近1,则结果一致。可通过比较分类准确率验证。
多分类时维度超过C-1试图获取多于C-1个投影方向。n_components设置为min(n_features, n_classes-1)

6.2 性能优化与数值稳定技巧

  1. 正则化(Regularization):当Sw接近奇异时,在Sw上加一个小的正则化项λ * I(单位矩阵)是标准做法。这相当于在优化目标中加入了对投影向量w的L2范数惩罚,防止过拟合并提高数值稳定性。
    lambda_reg = 1e-4 # 一个很小的正数 Sw_reg = Sw + lambda_reg * np.eye(Sw.shape[0]) Sw_inv = np.linalg.inv(Sw_reg) # 此时可以直接求逆
  2. 使用更稳定的求解器:对于广义特征值问题Sb w = λ Sw w,可以使用scipy.linalg.eigh函数,它专门用于求解厄米特(实对称)矩阵的特征值问题,并支持广义形式,数值上更稳定。
    from scipy.linalg import eigh eigenvalues, eigenvectors = eigh(Sb, Sw) # 直接求解广义特征值问题
  3. 先进行PCA预降维(白化):如果特征维数很高,可以先使用PCA将数据降到较低维度(比如保留95%方差),然后再应用LDA。这能有效缓解小样本问题,并去除噪声。

6.3 超越西瓜数据集:LDA的实际应用场景

理解了LDA在西瓜数据集上的运作后,你可以将其应用到更广泛的领域:

  1. 图像识别与计算机视觉:在人脸识别中,LDA(常被称为Fisherfaces)被用来提取最能区分不同人脸的线性特征。将每张人脸图像拉成一个高维向量,LDA可以找到区分不同人的低维子空间。
  2. 生物信息学与医学诊断:在基因表达数据分析中,样本(患者)有成百上千个基因(特征)的表达量,标签是疾病类型(如癌症亚型)。LDA可以用于寻找对区分疾病亚型最重要的基因组合,并实现降维可视化。
  3. 文本分类与主题模型:注意,这里的LDA通常是潜在狄利克雷分布,与线性判别分析同名但完全不同。不过,线性判别分析本身也可以用于文本分类,例如将文档的词袋模型向量投影到低维空间后再分类。
  4. 故障诊断与质量控制:在工业生产中,传感器会采集设备的多维运行数据。LDA可以学习正常状态和各类故障状态下的数据投影,当新数据投影点偏离正常区域时,即可预警。

最后再分享一个小技巧:当你使用LDA作为分类前的降维工具时,不妨将它与最简单的分类器(如最近邻、线性SVM)在投影后的低维空间上结合。这样构建的管道(Pipeline)往往比直接在原始高维空间使用复杂分类器更高效、更不容易过拟合,且模型可解释性更强。你可以用sklearn.pipeline.Pipeline轻松实现这一流程,并利用网格搜索来优化LDA的维度和分类器的参数。

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

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

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

立即咨询