# 5.19 自动特征选择

我们经常收集许多可能与监督预测任务相关的特征，但我们不知道它们中的哪一个实际上是预测性的。 为了提高可解释性，有时还提高泛化表现，我们可以使用自动特征选择来选择原始特征的子集。 有几种可用的特征选择方法，我们将按照复杂性的升序来解释。

对于给定的监督模型，最佳特征选择策略是尝试每个可能的特征子集，并使用该子集评估泛化表现。 但是，特征子集是指数级，因此这种详尽的搜索通常是不可行的。 下面讨论的策略可以被认为是这种不可行计算的替代。

## 单变量统计

选择要素的最简单方法是使用单变量统计，即通过单独查看每个特征并运行统计检验，来查看它是否与目标相关。 这种检验也称为方差分析（ANOVA）。

我们创建了一个人造数据集，其中包含乳腺癌数据和另外 50 个完全随机的特征。

```py
from sklearn.datasets import load_breast_cancer, load_digits
from sklearn.model_selection import train_test_split

cancer = load_breast_cancer()

# get deterministic random numbers
rng = np.random.RandomState(42)
noise = rng.normal(size=(len(cancer.data), 50))
# add noise features to the data
# the first 30 features are from the dataset, the next 50 are noise
X_w_noise = np.hstack([cancer.data, noise])

X_train, X_test, y_train, y_test = train_test_split(X_w_noise, cancer.target,
                                                    random_state=0, test_size=.5)
```

我们必须在统计检验的 p 值上定义一个阈值，来决定要保留多少特征。 在 scikit-learn 中实现了几种策略，一种直接的策略是`SelectPercentile`，它选择原始特征的百分位数（下面我们选择 50%）：

```py
from sklearn.feature_selection import SelectPercentile

# use f_classif (the default) and SelectPercentile to select 50% of features:
select = SelectPercentile(percentile=50)
select.fit(X_train, y_train)
# transform training set:
X_train_selected = select.transform(X_train)

print(X_train.shape)
print(X_train_selected.shape)
```

我们还可以直接使用检验统计量，来查看每个特征的相关性。 由于乳腺癌数据集是一项分类任务，我们使用`f_classif`，F 检验用于分类。 下面我们绘制 p 值，与 80 个特征中的每一个相关（30 个原始特征和 50 个噪声特征）。 低 p 值表示信息性特征。

```py
from sklearn.feature_selection import f_classif, f_regression, chi2

F, p = f_classif(X_train, y_train)

plt.figure()
plt.plot(p, 'o')
```

显然，前 30 个特征中的大多数具有非常小的 p 值。

回到`SelectPercentile`转换器，我们可以使用`get_support`方法获得所选特征：

```py
mask = select.get_support()
print(mask)
# 展示掩码。黑色是真，白色是假
plt.matshow(mask.reshape(1, -1), cmap='gray_r')
```

几乎所有最初的 30 个特征都被还原了。 我们还可以通过在数据上训练监督模型，来分析特征选择的效果。 仅在训练集上学习特征选择非常重要！

```py
from sklearn.linear_model import LogisticRegression

# 转换测试数据
X_test_selected = select.transform(X_test)

lr = LogisticRegression()
lr.fit(X_train, y_train)
print("Score with all features: %f" % lr.score(X_test, y_test))
lr.fit(X_train_selected, y_train)
print("Score with only selected features: %f" % lr.score(X_test_selected, y_test))
```

## 基于模型的特征选择

用于特征选择的稍微复杂的方法，是使用监督机器学习模型，并基于模型认为它们的重要性来选择特征。 这要求模型提供某种方法，按重要性对特征进行排名。 这适用于所有基于树的模型（实现`get_feature_importances`）和所有线性模型，系数可用于确定特征对结果的影响程度。

任何这些模型都可以制作成变换器，通过使用`SelectFromModel`类包装它，用于特征选择：

```py
from sklearn.feature_selection import SelectFromModel
from sklearn.ensemble import RandomForestClassifier
select = SelectFromModel(RandomForestClassifier(n_estimators=100, random_state=42), threshold="median")

select.fit(X_train, y_train)
X_train_rf = select.transform(X_train)
print(X_train.shape)
print(X_train_rf.shape)

mask = select.get_support()
# 展示掩码。黑色是真，白色是假
plt.matshow(mask.reshape(1, -1), cmap='gray_r')

X_test_rf = select.transform(X_test)
LogisticRegression().fit(X_train_rf, y_train).score(X_test_rf, y_test)
```

此方法构建单个模型（在本例中为随机森林）并使用此模型中的特征重要性。 我们可以通过在数据子集上训练多个模型，来进行更精细的搜索。 一种特殊的策略是递归特征消除：

## 递归特征消除

递归特征消除在整个特征集上构建模型，类似于上述方法，选择模型认为最重要的特征子集。 但是，通常只会从数据集中删除单个要素，并使用其余要素构建新模型。 重复删除特征和模型构建的过程，直到只剩下预定数量的特征：

```py
from sklearn.feature_selection import RFE
select = RFE(RandomForestClassifier(n_estimators=100, random_state=42), n_features_to_select=40)

select.fit(X_train, y_train)
# 可视化所选特征
mask = select.get_support()
plt.matshow(mask.reshape(1, -1), cmap='gray_r')

X_train_rfe = select.transform(X_train)
X_test_rfe = select.transform(X_test)

LogisticRegression().fit(X_train_rfe, y_train).score(X_test_rfe, y_test)

select.score(X_test, y_test)
```

> 练习
> 
> 创建“XOR”数据集，如下面的第一个单元格：
> 添加随机特征，并使用随机森林，在还原原始特征时，比较单变量选择与基于模型的选择。

```py
import numpy as np

rng = np.random.RandomState(1)

# 在 [0,1] 范围内生成 400 个随机整数
X = rng.randint(0, 2, (200, 2))
y = np.logical_xor(X[:, 0] > 0, X[:, 1] > 0)  # XOR creation

plt.scatter(X[:, 0], X[:, 1], c=plt.cm.tab10(y))

# %load solutions/19_univariate_vs_mb_selection.py
```
