当你完成了成百上千个特征创建之后,是时候挑选其中最重要的特征了。
你肯定不想创建成百上千个无用特征,同时,过多的特征也被称为维度灾难。
如果你有许多特征,你必须有大量的数据来捕获这些特征。具体的数量对应关系,需要通过恰当的模型验证和模型训练时间来检查。
最简单的特征选择方式是删除方差值很低的特征,即该特征所有数据的方差值非常低,接近 0 ,这意味着该特征对于所有数据都差不多,因此对模型的训练不起作用。删除此类特征可以避免无意义的计算,还可以减少模型复杂度。
可通过 VarianceThreshold 方法来实现。
import numpy as np
from sklearn.feature_selection import VarianceThreshold
x = np.random.randint(1, 15, size=(10, 6)).astype(float)
x[:, 2] = 1
print(x)
var_thres = VarianceThreshold(threshold=0.1)
print(var_thres.fit_transform(x))
我们也可以删除具有高相关性的特征,对于数值特征之间的相关性计算,可以使用 Pearson Correlation 完成。
import pandas as pd
import numpy as np
from sklearn.datasets import fetch_california_housing
data = fetch_california_housing()
x = data['data']
columns = data['feature_names']
y = data['target']
df = pd.DataFrame(x, columns=columns)
df.loc[:, 'MedInc_Sqrt'] = df.MedInc.apply(np.sqrt)
df.corr()
我们从 MedInc 特征中派生出 MedInc_Sqrt ,然后计算各特征的相关系数,从中可以看出,这两列相关系数非常高,因此可以从中删除一列。
现在,我们可以进一步介绍特征选择的单变量方法,Univariate Feature Selection。该方法对每列特征运行给定的得分函数,根据得分对特征排序。
Mutual Information、ANOVA F-test 和 是最著名的单变量特征选择方法的得分函数。
必须注意到,只有数据中全部都是非负值时,才可以使用 方法。在自然语言处理中很有用。
下边用代码创建一个单变量特征选择的包装方法,该方法可以用在任何新问题中。
from sklearn.feature_selection import chi2, f_classif, mutual_info_classif
from sklearn.feature_selection import f_regression, mutual_info_regression
from sklearn.feature_selection import SelectKBest, SelectPercentile
class UnivariateFeatureSelection:
def __init__(
self,
n_features,
problem_type,
scoring
):
if problem_type == 'classification':
valid_scoring = {
'chi2': chi2,
'f_classif': f_classif,
'mutual_info_classif': mutual_info_classif
}
else:
valid_scoring = {
'f_regression': f_regression,
'mutual_info_regression': mutual_info_regression
}
if scoring not in valid_scoring:
raise Exception('Invalid scoring function.')
if isinstance(n_features, int):
self.selection = SelectKBest(
valid_scoring[scoring],
k=n_features
)
elif isinstance(n_features, float):
self.selection = SelectPercentile(
valid_scoring[scoring],
percentile=int(n_features * 100)
)
else:
raise Exception('Invalid type of feature')
def fit(self, x, y):
self.selection.fit(x, y)
def transform(self, x):
return self.selection.transform(x)
def fit_transform(self, x, y):
return self.selection.fit_transform(x, y)
时刻牢记,创建较少的重要特征比创建上百个特征要好得多。
单变量特征选择的效果有时候不会很好,人们通常在训练机器学习模型的时候使用该方法。
最简单的特征选择形式是贪心特征选择 Greedy Feature Selection。在该方法中,首先要确定使用的模型,然后选择 Loss/Score 函数,最后迭代计算每一列特征,如果该列特征可以提升模型的 Loss/Score,就将该列特征添加到保留特征组里。
贪心特征选择非常简单,但是你得明白贪心二字的含义,该方法的计算耗时非常高,如果使用不当,还会导致模型过拟合。
下边用代码实现。
import pandas as pd
from sklearn import linear_model, metrics
from sklearn.datasets import make_classification
class GreedyFeatureSelection:
def evaluate_score(self, x, y):
model = linear_model.LogisticRegression()
model.fit(x, y)
return metrics.roc_auc_score(
y,
model.predict_proba(x)[:, 1]
)
def _feature_selection(self, x, y):
good_features = []
best_scores = []
num_features = x.shape[1]
while True:
this_feature = None
best_score = 0
for feature in range(num_features):
if feature in good_features:
continue
selected_features = good_features + [feature]
x_train = x[:, selected_features]
score = self.evaluate_score(x_train, y)
if score > best_score:
this_feature = feature
best_score = score
if this_feature != None:
good_features.append(this_feature)
best_scores.append(best_score)
if len(best_scores) > 2:
if best_scores[-1] < best_scores[-2]:
break
return best_scores[:-1], good_features[:-1]
def __call__(self, x, y):
scores, features = self._feature_selection(x, y)
return x[:, features], scores
X, y = make_classification(n_samples=1000, n_features=100)
X_transformed, scores = GreedyFeatureSelection()(X, y)
print(X_transformed.shape[1])
import seaborn as sns
sns.lineplot(scores)
GreedyFeatureSelection 最后返回了特征选择带来的 AUC 优化值 scores,可以将其绘制成图,直观的检查模型选择过程中,每一轮迭代增加一个特征的效果。可以看到在某个时刻得分无法提升,因此选择停止。
另一个著名的贪心方法是递归特征消除 Recursive Feature Elimination (RFE)。
在上一个方法中,我们从一个特征开始计算,然后每轮迭代添加一个新特征,最终得到的一组特征,都是可以提升模型得分的特征。
而通过 RFE,我们从所有特征开始,每轮迭代减少一个提升模型得分最少的特征。
但是,我们如何知道哪个特征提升的模型得分最少?
如果使用 SVM 或者逻辑回归,我们需要每个特征的系数,该系数决定特征的重要度。在使用基于树的模型时,我们也需要特征重要度。
度量每轮迭代中的特征重要度,然后消除最不重要的特征,持续迭代,直到特征数量到达指定值。
现在,我们可以决定保留多少个特征了。
创建 RFE 操作非常方便,scikit-learn 提供了开箱即用的 RFE。
import pandas as pd
from sklearn.feature_selection import RFE
from sklearn.linear_model import LinearRegression
from sklearn.datasets import fetch_california_housing
data = fetch_california_housing()
x = data['data']
columns = data['feature_names']
y = data['target']
model = LinearRegression()
rfe = RFE(
estimator=model,
n_features_to_select=3
)
rfe.fit(x, y)
x_transformed = rfe.transform(x)
至此,我们见识了两种特征选择贪心方式。
你也可以训练模型,然后根据模型计算特征系数或特征重要度。
如果你需要计算特征系数,你可以选择一个阈值,系数高于该阈值的特征被保留,否则被消除。
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from sklearn.ensemble import RandomForestRegressor
from sklearn.datasets import load_diabetes
data = load_diabetes()
x = data['data']
columns = data['feature_names']
y = data['target']
model = RandomForestRegressor()
model.fit(x, y)
importances = model.feature_importances_
idx = np.argsort(importances)
plt.title('Feature Importances')
plt.barh(range(len(idx)), importances[idx], align='center')
plt.yticks(range(len(idx)), [columns[i] for i in idx])
plt.xlabel('Random Forest Feature Importance')
plt.show()
结果绘制如下:
拿到了各个特征重要性排序之后,选择最优的特征很容易。
可以基于某个模型选择出特征,然后在另一个模型上训练与测试。例如,你可以使用 Logistic Regression 模型的重要系数选择特征,然后使用这些特征去训练随机森林。
import pandas as pd
from sklearn.ensemble import RandomForestRegressor
from sklearn.datasets import load_diabetes
from sklearn.feature_selection import SelectFromModel
data = load_diabetes()
x = data['data']
columns = data['feature_names']
y = data['target']
model = RandomForestRegressor()
sfm = SelectFromModel(
estimator=model
)
sfm.fit(x, y)
x_transformed = sfm.transform(x)
support = sfm.get_support()
print([
x for x, y in zip(columns, support) if y == True
])
代码输出 ['bmi', 's5'],结合重要性排序图,可以发现这些特征是重要度最高的特征。
最后我们没提到的特征选择方法是 L1 (Lasso) penalization。当我们做 L1 正则化时,大多数系数会成为 0 或接近 0,然后我们选择系数非 0 的特征。
你可以在上述代码中使用支持 L1 惩罚的模型替换随机森林模型,如 lasso regression。
所有基于树的模型都提供特征重要性度量,因此上述代码框架可以直接用在 XGBoost、LightGBM、CatBoost 模型上。特征重要度函数名称可能不一样,导致结果不同,但是使用思路是一样的。
最后提醒一下,做特征选择时必须非常小心,只在训练集上做特征选择,然后在验证集上根据选择的特征验证模型,不会导致模型过拟合。