机器学习的特征选择
在搭建机器学习模型时,通过特征选择选择高质量的特征,不仅可以减少干扰,还可以降低计算量
什么是特征?
- 对象本身会有许多属性。所谓特征,即能在某方面最能表征对象的一个或者一组属性
机器学习中,特征一般存在哪些关系?
- 相关特征:对于特定的任务和场景具有一定帮助的属性,这些属性通常能有效提升算法性能
- 无关特征:在特定的任务和场景下完全无用的属性,这些属性对对象在本目标环境下完全无用
- 冗余特征:同样是在特定的任务和场景下具有一定帮助的属性,但这类属性已过多的存在,不具有产生任何新的信息的能力
什么是特征选择?
- 对原始特征集合进行分析,挖掘离散程度高且与目标的相关性强的特征,以提高估计器的准确度,或提高其在高维数据集上的性能。
为什么要进行特征选择?
- 减少特征数量、降维,使模型泛化能力更强,减少过拟合,减少训练时间:越少的数据,训练模型所需要的时间越少
- 增强对特征和特征值之间的理解
- 提高算法精度:较少的误导数据,能够提高算法的准确度
常用的特征选择方式有那些?
- 过滤法 按照发散性或者相关性对各个特征进行评分,设定阈值或者待选择阈值的个数,从而选择特征;常用方法包括去除低方差的特征、相关系数法、卡方检验、互信息法等
- 包装法 根据目标函数(通常是预测效果评分),每次选择若干特征或者排除若干特征;常用方法主要是递归特征选择
- 嵌入法 先使用某些机器学习的算法和模型进行训练,得到各个特征的权重系数,根据系数从大到小选择特征;常用方法主要是基于惩罚项的特征选择
在 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
5clf = 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)