机器学习的特征选择

在搭建机器学习模型时,通过特征选择选择高质量的特征,不仅可以减少干扰,还可以降低计算量

什么是特征?

  • 对象本身会有许多属性。所谓特征,即能在某方面最能表征对象的一个或者一组属性

机器学习中,特征一般存在哪些关系?

  • 相关特征:对于特定的任务和场景具有一定帮助的属性,这些属性通常能有效提升算法性能
  • 无关特征:在特定的任务和场景下完全无用的属性,这些属性对对象在本目标环境下完全无用
  • 冗余特征:同样是在特定的任务和场景下具有一定帮助的属性,但这类属性已过多的存在,不具有产生任何新的信息的能力

什么是特征选择?

  • 对原始特征集合进行分析,挖掘离散程度高且与目标的相关性强的特征,以提高估计器的准确度,或提高其在高维数据集上的性能。

为什么要进行特征选择?

  • 减少特征数量、降维,使模型泛化能力更强,减少过拟合,减少训练时间:越少的数据,训练模型所需要的时间越少
  • 增强对特征和特征值之间的理解
  • 提高算法精度:较少的误导数据,能够提高算法的准确度

常用的特征选择方式有那些?

  • 过滤法 按照发散性或者相关性对各个特征进行评分,设定阈值或者待选择阈值的个数,从而选择特征;常用方法包括去除低方差的特征、相关系数法、卡方检验、互信息法等
  • 包装法 根据目标函数(通常是预测效果评分),每次选择若干特征或者排除若干特征;常用方法主要是递归特征选择
  • 嵌入法 先使用某些机器学习的算法和模型进行训练,得到各个特征的权重系数,根据系数从大到小选择特征;常用方法主要是基于惩罚项的特征选择

在 scikit-learn 的特征选择中,如何去除低方差的特征?

  • VarianceThreshold 是一种简单的特征选择基线方法。它删除了所有方差不符合某个阈值的特征。默认情况下,它删除所有零方差的特征,即在所有样本中具有相同值的特征
  • 例子:假设特征只取值 0,1 的情况,如何筛选出去掉 80% 以上样本均为 0 或 1 的特征
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    # .8 * (1 - .8)为特征方差阈值上限
    >>> from sklearn.feature_selection import VarianceThreshold
    >>> X = [[0, 0, 1], [0, 1, 0], [1, 0, 0], [0, 1, 1], [0, 1, 0], [0, 1, 1]]
    >>> sel = VarianceThreshold(threshold=(.8 * (1 - .8)))
    >>> sel.fit_transform(X)
    array([[0, 1],
    [1, 0],
    [0, 0],
    [1, 1],
    [1, 0],
    [1, 1]])

在 scikit-learn 的特征选择中,如何进行单变量的特征选择?

  • 方法 1: 卡方检验来选择特征
    1
    2
    3
    4
    5
    6
    7
    8
    9
     >>> from sklearn.datasets import load_digits
    >>> from sklearn.feature_selection import SelectKBest, chi2
    >>> X, y = load_digits(return_X_y=True)
    >>> X.shape
    (1797, 64)
    # 不同任务选择不同的指标,这里chi2用于分类任务
    >>> X_new = SelectKBest(chi2, k=20).fit_transform(X, y)
    >>> X_new.shape
    (1797, 20)
  • 方法 2: SelectPercentile 除了用户指定的最高得分百分比的特征外,其他的都被删除了,相当于 SelectKBest 方法的相对值
    1
    2
    3
    4
    5
    6
    7
    8
     >>> from sklearn.datasets import load_digits
    >>> from sklearn.feature_selection import SelectPercentile, chi2
    >>> X, y = load_digits(return_X_y=True)
    >>> X.shape
    (1797, 64)
    >>> X_new = SelectPercentile(chi2, percentile=10).fit_transform(X, y)
    >>> X_new.shape
    (1797, 7)
  • 方法 3: 对每个特征使用常见的单变量统计测试:假阳性率 SelectFpr,假发现率 SelectFdr,或家族明智的错误 SelectFwe
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
     >>> from sklearn.datasets import load_breast_cancer
    >>> from sklearn.feature_selection import SelectFpr, chi2
    >>> X, y = load_breast_cancer(return_X_y=True)
    >>> X.shape
    (569, 30)
    >>> X_new = SelectFpr(chi2, alpha=0.01).fit_transform(X, y)
    >>> X_new.shape
    (569, 16)
    >>> X_new = SelectFdr(chi2, alpha=0.01).fit_transform(X, y)
    >>> X_new.shape
    (569, 16)
    >>> X_new = SelectFwe(chi2, alpha=0.01).fit_transform(X, y)
    >>> X_new.shape
    (569, 15)
  • 方法 4: GenericUnivariateSelect 允许以可配置的策略进行单变量特征选择。这允许用超参数搜索估计器选择最佳单变量选择策略
    1
    2
    3
    4
    5
    6
    7
    8
    9
     >>> from sklearn.datasets import load_breast_cancer
    >>> from sklearn.feature_selection import GenericUnivariateSelect, chi2
    >>> X, y = load_breast_cancer(return_X_y=True)
    >>> X.shape
    (569, 30)
    >>> transformer = GenericUnivariateSelect(chi2, mode='k_best', param=20)
    >>> X_new = transformer.fit_transform(X, y)
    >>> X_new.shape
    (569, 20)

在 scikit-learn 的特征选择中,如何进行递归特征选择?

  • 给定一个为特征分配权重的外部估计器(例如,线性模型的系数),递归特征消除(RFE)的目标是通过递归考虑越来越小的特征集来选择特征。 首先,估计器在初始特征集上进行训练,每个特征的重要性是通过任何特定的属性(如 coef_,feature_importances_)或可调用的方式获得。然后,将最不重要的特征从当前的特征集中修剪出来。这个过程在修剪后的集合上递归重复,直到最终达到需要选择的特征数量
  • RFECV 在交叉验证循环中执行 RFE,以找到最佳的特征数量
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
     >>> from sklearn.datasets import make_friedman1
    >>> from sklearn.feature_selection import RFECV
    >>> from sklearn.svm import SVR
    >>> X, y = make_friedman1(n_samples=50, n_features=10, random_state=0)
    >>> estimator = SVR(kernel="linear")
    >>> selector = RFECV(estimator, step=1, cv=5)
    >>> selector = selector.fit(X, y)
    >>> selector.support_
    array([ True, True, True, True, True, False, False, False, False,
    False])
    >>> selector.ranking_
    array([1, 1, 1, 1, 1, 6, 4, 3, 2, 5])

在 scikit-learn 的特征选择中,特征选择器有哪些选择原则?

  • SelectFromModel 特征选择器通过监控特定属性(如 coef_、feature_importances_)或通过拟合后的 importance_getter 可调用的估计器,结合给定的阈值参数,筛选特定的特征
  • 原则 1:用 L1 准则惩罚的线性模型有稀疏的解决方案:它们的许多估计系数是零。当目标是减少数据的维度以用于另一个分类器时,它们可以和 SelectFromModel 一起使用,以选择非零系数
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
     >>> from sklearn.svm import LinearSVC
    >>> from sklearn.datasets import load_iris
    >>> from sklearn.feature_selection import SelectFromModel
    >>> X, y = load_iris(return_X_y=True)
    >>> X.shape
    (150, 4)
    >>> lsvc = LinearSVC(C=0.01, penalty="l1", dual=False).fit(X, y)
    >>> model = SelectFromModel(lsvc, prefit=True)
    >>> X_new = model.transform(X)
    >>> X_new.shape
    (150, 3)
  • 原则 2:基于树的估计器(见 sklearn.tree 模块和 sklearn.ensemble 模块中的树的森林)可以用来计算基于杂质的特征导入,这反过来又可以用来丢弃不相关的特征
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
     >>> from sklearn.ensemble import ExtraTreesClassifier
    >>> from sklearn.datasets import load_iris
    >>> from sklearn.feature_selection import SelectFromModel
    >>> X, y = load_iris(return_X_y=True)
    >>> X.shape
    (150, 4)
    >>> clf = ExtraTreesClassifier(n_estimators=50)
    >>> clf = clf.fit(X, y)
    >>> clf.feature_importances_
    array([ 0.04..., 0.05..., 0.4..., 0.4...])
    >>> model = SelectFromModel(clf, prefit=True)
    >>> X_new = model.transform(X)
    >>> X_new.shape
    (150, 2)

在 scikit-learn 的特征选择中,如何进行顺序特征选择?

  • 方法 1: Forward-SFS (前向特征选择),特征选择的集合数量为 0,不断加入最佳特征,直到特征数量满足或提升不高时,停止加入特征
  • 方法 2: Backward-SFS (后向特征选择),特征选择的集合数量为 “所有特征”,不断删除最差特征,直到特征数量满足或效果降低太多,停止删除特征
  • 一般来说,前向选择和后向选择的结果并不相等。另外,一个可能比另一个快得多,这取决于所要求的选择特征的数量:如果我们有 10 个特征,要求选择 7 个特征,正向选择需要执行 7 次迭代,而反向选择只需要执行 3 次
  • 前向或后向特征选择均通过函数 SequentialFeatureSelector 实现,通过参数 direction 控制方向
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
     >>> from sklearn.feature_selection import SequentialFeatureSelector
    >>> from sklearn.neighbors import KNeighborsClassifier
    >>> from sklearn.datasets import load_iris
    >>> X, y = load_iris(return_X_y=True)
    >>> knn = KNeighborsClassifier(n_neighbors=3)
    >>> sfs = SequentialFeatureSelector(knn, n_features_to_select=3)
    >>> sfs.fit(X, y)
    SequentialFeatureSelector(estimator=KNeighborsClassifier(n_neighbors=3),
    n_features_to_select=3)
    >>> sfs.get_support()
    array([ True, False, True, True])
    >>> sfs.transform(X).shape
    (150, 3)

在 scikit-learn 的特征选择中,与递归特征选择 相比,顺序特征选择 有什么差异?

  • 顺序特征选择不要求底层模型暴露 coef_或 feature_importances_属性。然而,考虑到需要评估更多的模型,与其他方法相比,它可能会更慢。
  • 例如,在后向选择中,使用 k-fold 交叉验证法从 m 个特征到 m-1 个特征的迭代需要拟合 m * k 个模型,而 RFE 只需要进行一次拟合,SelectFromModel 总是只进行一次拟合,不需要进行迭代

在 scikit-learn 的特征选择中,如何人构建快速的特征选择 + 分类流程?

  • 在进行实际学习之前,特征选择通常被用作预处理步骤。在 scikit-learn 中,推荐的方法是使用一个管道
  • 例子: 利用 LinearSVC 和 SelectFromModel 来评估特征的重要性并选择最相关的特征。然后,在转换后的输出上训练一个 RandomForestClassifier,即只使用相关特征
    1
    2
    3
    4
    5
    clf = Pipeline([
    ('feature_selection', SelectFromModel(LinearSVC(penalty="l1"))),
    ('classification', RandomForestClassifier())
    ])
    clf.fit(X, y)

什么是卡方检验?

  • 经典的卡方检验是检验定性自变量对定性因变量的相关性的方法。
  • 即统计样本的实际观测值与理论推断值之间的偏离程度,偏离程度决定了卡方值的大小,卡方值越大,越不符合
    1
    2
    3
    4
    5
    6
    7
    8
    9
     >>> from sklearn.datasets import load_digits
    >>> from sklearn.feature_selection import SelectKBest, chi2
    >>> X, y = load_digits(return_X_y=True)
    >>> X.shape
    (1797, 64)
    # 不同任务选择不同的指标,这里chi2用于分类任务
    >>> X_new = SelectKBest(chi2, k=20).fit_transform(X, y)
    >>> X_new.shape
    (1797, 20)