【技术专题】Scikit-learn Python机器学习 - 特征工程之特征降维 压缩数据

0 阅读13分钟

大家好,我是锋哥。最近连载更新《Scikit-learn Python机器学习》技术专题。 image.png 本课程主要讲解基于Scikit-learn的Python机器学习知识,包括机器学习概述,特征工程(数据集,特征抽取,特征预处理,特征降维等),分类算法(K-临近算法,朴素贝叶斯算法,决策树等),回归与聚类算法(线性回归,欠拟合,逻辑回归与二分类,K-means算法)等。 同时也配套视频教程《Scikit-learn Python机器学习 视频教程》

特征降维是机器学习中常用的技术,用于减少特征的数量,同时尽可能保留原始数据的信息。在Scikit-learn中,特征降维主要有两种方法:特征选择和特征提取。

1,特征选择:从原始特征中选择一部分特征。方法包括:移除低方差特征(VarianceThreshold),单变量统计(SelectKBest, SelectPercentile)、基于模型的特征选择(如SelectFromModel)、递归特征消除(RFE)等。,

2,特征提取:将原始特征转换到新的特征空间,从而减少特征数量。常用的方法有主成分分析(PCA)、线性判别分析(LDA)、非负矩阵分解(NMF)等。

特征选择

移除低方差特征(VarianceThreshold)

适用于移除方差低于阈值的特征,这些特征通常包含很少的信息。

VarianceThreshold 是机器学习中一个简单但实用的特征选择方法,它通过移除低方差特征来简化数据集。VarianceThreshold 的主要参数是 threshold,它决定了特征被保留与否的方差门槛。

参数名 (Parameter)说明 (Description)默认值 (Default)
threshold一个浮点数 (float)。指定要保留特征的最低方差阈值。训练集中方差低于此阈值的特征将被移除0.0

我们来看一个示例:

from sklearn.feature_selection import VarianceThreshold
from sklearn.datasets import load_iris
​
# 加载示例数据
X, y = load_iris(return_X_y=True)
# 设置阈值,移除方差低于0.8的特征
selector = VarianceThreshold(threshold=0.8)
X_new = selector.fit_transform(X)
​
print(f"原始特征数: {X.shape[1]}")
print(f"筛选后特征数: {X_new.shape[1]}")

运行结果:

原始特征数: 4
筛选后特征数: 1

数学知识:方差

方差公式是一个数学公式,是数学统计学中的重要公式,应用于生活中各种事情,方差越小,代表这组数据越稳定,方差越大,代表这组数据越不稳定

若x1,x2,x3......xn的平均数为M,则方差公式可表示为:

image.png

单变量特征选择 SelectKBest - 选择Top K个特征

基于统计检验选择最佳特征。

SelectKBest 的原理非常直观,其名称就完美概括了其工作方式: Select(选择) + K + Best(最好的)

顾名思义,它的目标是从原始特征集中选择出 K 个“最好的”特征。那么,核心问题就变成了:如何定义“最好”?

SelectKBest 的工作流程可以概括为以下三个步骤:

  1. 打分(Scoring)

    • 对于数据集中的每一个特征,都使用一个特定的评分函数 f 进行计算。
    • 这个评分函数会计算该特征与目标变量 y 之间的某种统计关系或依赖性。关系越强,得分越高。
    • 例如,它可以使用卡方检验、相关系数、互信息等作为评分标准。
  2. 排序(Ranking)

    • 得到所有特征及其对应的分数后,SelectKBest根据分数从高到低对所有特征进行排序。
  3. 选择(Selecting)

    • 最后,它简单地保留Top-K个得分最高的特征,并剔除其余的所有特征。
    • 用户指定的参数 k 就是这里需要保留的特征数量。

🧠 核心参数详解

参数名说明默认值
score_func【最重要的参数】 用于计算特征得分的函数。它决定了“最好”的标准。f_classif (用于分类)
k【核心参数】 选择要保留的 top K 个特征。可以设置为整数 ‘all’ 来保留所有特征。10

常见的 score_func 评分函数:

选择哪个评分函数取决于你的问题类型(分类还是回归)以及特征的数据类型。

评分函数适用问题说明
f_classif分类计算每个特征与目标变量之间的 ANOVA F值。适用于连续特征和分类目标。默认选项。
chi2分类卡方检验。计算每个特征与目标变量之间的卡方统计量。适用于非负的特征(如词频、布尔特征)。
mutual_info_classif分类互信息。衡量特征和目标变量之间的非线性关系。非常强大,但计算成本更高。
f_regression回归计算每个特征与目标变量之间的 F值(线性回归模型的简单线性回归)。
mutual_info_regression回归互信息的回归版本,同样用于捕捉非线性关系。

📊 工作流程示意图

image.png

我们来看一个示例:

from sklearn.feature_selection import SelectKBest, f_classif
from sklearn.datasets import load_iris
​
# 加载数据
X, y = load_iris(return_X_y=True)
​
# 选择最佳的2个特征
selector = SelectKBest(score_func=f_classif, k=3)
X_new = selector.fit_transform(X, y)
​
print(f"原始特征数: {X.shape[1]}")
print(f"筛选后特征数: {X_new.shape[1]}")
print(f"特征得分: {selector.scores_}")

运行结果:

原始特征数: 4
筛选后特征数: 3
特征得分: [ 119.26450218   49.16004009 1180.16118225  960.0071468 ]

特征提取

将原始特征转换到新的特征空间,从而减少特征数量。

主成分分析 (PCA)

PCA通过线性变换将原始特征转换为一组线性不相关的变量(主成分),按方差大小排序。

PCA的核心思想是:将原始高维特征通过线性变换映射到新的低维坐标系中,这个新坐标系的坐标轴(主成分)按照能够保留原始数据最大方差的方向依次排列。

这意味着第一个新坐标轴(第一主成分)保留了数据中最大程度的方差,第二个新坐标轴(第二主成分)在与第一个正交的前提下保留次大方差,以此类推。

🧠 直观理解:一个经典的比喻

想象你在黑暗中从不同角度观察一个三维物体(比如一个倾斜的椭圆盘子),并记录下它在二维平面上的影子。

  • 某些角度下的影子(比如正上方)可能看起来只是一个短线,丢失了大量关于盘子形状的信息。
  • 某些角度下的影子(比如从盘子侧面)则能最大程度地展现它的形状和大小(一个椭圆)。

PCA要做的就是自动找到那个 “最佳观测角度” ,使得投影后的影子(低维数据)能包含原始物体(高维数据)最多的信息。而这个“信息量”,在PCA中就用方差来衡量。方差越大,意味着数据点在新坐标轴上分布得越分散,保留的信息就越多。

📊 数学原理与计算步骤(可分步理解)

假设有一个包含 m 个样本和 n 个特征的数据集 X ),其中 X = [x_1, x_2, …, x_m] ,每个样本 x_i 是一个 n 维向量。

image.png

( 快速理解这个PCA算法原理,可以查看 视频 主成分分析 (PCA) 转载自 抖音 动画讲编程 )

⚙️ 关键参数与概念(以Scikit-learn为例)

参数/概念说明
n_components最重要的参数。指定要保留的主成分个数 k。可以设为整数(如 2),也可以设为 01 之间的浮点数(如 0.95,表示保留95%的原始方差)。
svd_solver指定求解器。通常使用默认的 'auto' 即可。对于大型数据,使用 'randomized' 的随机SVD方法会更高效。
explained_variance_属性。一个数组,表示每个主成分所捕获的方差大小(即特征值)。
explained_variance_ratio_属性。一个数组,表示每个主成分所捕获的方差占总方差的百分比。这是决定 k 取多少的关键依据。
components_属性。投影矩阵 WW,每一行是一个主成分(特征向量)。

我们来看一个示例:

from sklearn.decomposition import PCA
from sklearn.preprocessing import StandardScaler
from sklearn.datasets import load_iris
​
# 加载数据
iris = load_iris()
X, y = iris.data, iris.target
​
# 1. 标准化数据(至关重要!)
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)
​
# 2. 初始化PCA,保留2个主成分用于可视化
pca = PCA(n_components=2)
​
# 3. 训练转换数据
X_pca = pca.fit_transform(X_scaled)
​
print("原始数据形状:", X.shape)
print("降维后数据:", X_pca)

运行结果:

image.png

线性判别分析 (LDA)

LDA是一种有监督的降维方法,旨在最大化类间距离,最小化类内距离。

线性判别分析(Linear Discriminant Analysis, LDA)是一种经典的有监督学习算法,主要用于分类降维。它的核心思想与主成分分析(PCA)不同:PCA追求的是数据方差最大化,是一种无监督的降维方法;而LDA追求的是最大化类间散度(类与类之间的距离)的同时最小化类内散度(同一类内的数据离散程度) ,即寻找能够最好地将不同类别区分开来的特征子空间。

1. 核心目标

给定数据集,LDA的目标是找到一个投影方向(对于多类问题则是投影平面),使得:

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

通过优化这两个目标,LDA能使得投影后的数据具有最好的分类效果。

2.数学定义与推导

线性鉴别分析的基本思想是将高维的模式样本投影到最佳鉴别矢量空间,以达到抽取分类信息和压缩特征空间维数的效果,投影后保证模式样本在新的子空间有最大的类间距离和最小的类内距离,即模式在该空间中有最佳的可分离性。因此,它是一种有效的特征抽取方法。使用这种方法能够使投影后模式样本的类间散布矩阵最大,并且同时类内散布矩阵最小。就是说,它能够保证投影后模式样本在新的空间中有最小的类内距离和最大的类间距离,即模式在该空间中有最佳的可分离性。

image.png

scikit-learn 库中 LinearDiscriminantAnalysis 类来实现LDA。

sklearn.discriminant_analysis 模块中,LinearDiscriminantAnalysis 的构造函数如下:

LinearDiscriminantAnalysis(solver='svd', 
                           shrinkage=None, 
                           priors=None, 
                           n_components=None,
                           store_covariance=False, 
                           tol=0.0001, 
                           covariance_estimator=None)

参数详解

  1. solver : str, {'svd', 'lsqr', 'eigen'}, default='svd'

求解器算法。这是最重要的参数,它决定了LDA内部采用何种数学算法来求解,并且它会限制其他一些参数是否可用

  • 'svd' (奇异值分解):

    • 原理: 不直接计算散度矩阵 SwS_w,而是通过SVD(奇异值分解)来求解。这是一种数值上最稳定、最精确的方法。

    • 优点:

      • 无需计算 SwS_wSbS_b,节省内存,尤其适用于特征数量非常多(甚至多于样本数)的场景。
      • 不会因为 SwS_w 是奇异矩阵(不可逆)而出现问题。
    • 限制: 不能使用 shrinkage(收缩)参数。

    • 适用场景: 默认选择。在大多数情况下都是最佳选择,特别是当特征维数高或担心数值稳定性时。

  • 'lsqr' (最小二乘解):

    • 原理: 通过最小化平方误差来求解。此算法可以执行收缩。
    • 优点: 支持 shrinkage
    • 要求: 需要计算协方差矩阵,因此当 n_features 很大时可能效率不高。
    • 适用场景: 当你明确需要使用收缩,并且特征维度不是极高时。
  • 'eigen' (特征值分解):

    • 原理: 通过求解广义特征值问题 Sbw=λSwwS_b w = \lambda S_w w 来求解(即我们原理部分推导的方法)。此算法也可以执行收缩。
    • 优点: 支持 shrinkage
    • 要求: 需要计算协方差矩阵,同样不适用于特征维度极高的场景。
    • 适用场景: 与 'lsqr' 类似,当你需要收缩且特征数不多时。'eigen''lsqr' 的结果通常非常相似。

总结选择指南

  • 默认或特征数很多 -> 'svd'
  • 需要收缩(Shrinkage) -> 'lsqr''eigen'

  1. shrinkage : float or 'auto', default=None

收缩参数。用于估计协方差矩阵的正则化方法,主要用于解决当样本数量少于特征数量时,SwS_w 矩阵奇异(不可逆) 的问题,或者改善协方差矩阵的估计。

  • 原理: 收缩通过将类内散度矩阵 SwS_w 向一个对角矩阵(或单位矩阵)进行“缩小”来正则化它: Σ^=(1shrinkage)Sw+shrinkageσ2I\hat{\Sigma} = (1 - \text{shrinkage}) * S_w + \text{shrinkage} * \sigma^2 I

  • 取值:

    • None0: 不进行任何收缩。
    • 'auto': 使用Ledoit-Wolf引理自动确定最优的收缩强度。这是非常实用的一个选项
    • float between 0 and 1: 手动指定固定的收缩强度。例如,0.5 表示一半是原始协方差,一半是对角矩阵。
  • 依赖关系: 仅当 solver 为 'lsqr' 或 'eigen' 时有效'svd' 求解器不需要也不支持收缩。

  • 适用场景: 当训练样本数较少特征数较多导致模型过拟合或 SwS_w 奇异时,设置 shrinkage='auto' 通常能显著提升模型的泛化能力。


  1. priors : array-like of shape (n_classes,), default=None

类的先验概率

  • 原理: 在贝叶斯框架下,LDA可以融入关于类别分布的先验知识。如果你知道你的数据中各类别的出现概率(例如,在医学诊断中,健康人群远多于患病人群),可以通过此参数指定。
  • 取值: 一个长度等于类别数的数组,数组元素之和应为1.0。例如,对于三分类问题,可以设置为 [0.3, 0.3, 0.4]
  • 默认 None: 模型将直接从训练数据中计算每个类的先验概率,即 priors = np.bincount(y) / len(y)
  • 影响: 这个参数会直接影响决策边界的位置。如果你设置的先验概率与数据中的真实分布差异很大,决策边界会向先验概率较小的类别方向移动。

  1. n_components : int, default=None

降维后希望保留的维度数

  • 原理: LDA降维后的最大维度是 min(n_features, n_classes - 1)。此参数用于指定最终保留的维度。

  • 取值:

    • None: 默认值,会自动设置为 n_classes - 1
    • int: 一个小于 n_classes - 1 的整数。例如,对于4分类问题,最多可降到3维,你可以设置 n_components=2 来只取前两个线性判别式。
  • 注意: 此参数主要用于降维。即使你将其用作分类器,transform 方法也会使用这个维度。


  1. store_covariance : bool, default=False

是否计算并存储每个类的协方差矩阵

  • 原理: 为 True 时,模型拟合后会有 covariance_ 属性,它是一个数组,包含了每个类的协方差矩阵。

  • 取值:

    • False (默认): 不存储。节省内存。
    • True: 存储。主要用于调试和可视化,或者你需要查看类的协方差结构时。
  • 依赖关系: 仅当 solver='svd' 时不可用,因为 'svd' 求解器根本不计算协方差矩阵。


  1. tol : float, default=1e-4

用于秩估计的阈值

  • 原理: 当 solver='svd' 时,用于判断矩阵秩的容差值。任何奇异值小于 tol 的维度都会被丢弃。这是一个非常技术性的参数,通常不需要调整。
  • 适用场景: 除非你非常了解数值线性代数,并且发现默认值导致了一些问题,否则保持默认即可。

  1. covariance_estimator : estimator, default=None

协方差估计器

  • 原理: 这是一个实验性参数(截至 scikit-learn 1.2版本)。它允许你传入一个自定义的协方差估计器对象(例如 sklearn.covariance.ShrunkCovariance)来替代标准的极大似然估计。
  • 取值: 一个实现了 fit 方法的协方差估计器对象。
  • 依赖关系: 仅当 solver='lsqr''eigen' 时可用。
  • 适用场景: 为高级用户提供极大的灵活性,可以尝试各种不同的协方差矩阵正则化方法。绝大多数用户不需要使用此参数。

我们来看一个示例:

from sklearn.discriminant_analysis import LinearDiscriminantAnalysis
from sklearn.preprocessing import StandardScaler
from sklearn.datasets import load_iris
​
# 加载数据
iris = load_iris()
X, y = iris.data, iris.target
​
# 1. 标准化数据(至关重要!)
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)
​
# 2. 初始化LAD,保留2个维度
lda = LinearDiscriminantAnalysis(n_components=2)
​
# 3. 训练转换数据
X_lda = lad.fit_transform(X_scaled, y)
​
print("原始数据形状:", X.shape)
print("降维后数据:", X_lda)

运行结果:

image.png