使用scikit-learn进行特征选择
参考文章:《特征工程入门与实践》——第5章 特征选择:对坏属性说不
信用卡逾期数据集:credit card clients Data Set
本文代码开源链接: FeatureSelection
本文主要以信用卡逾期分类任务作为案例,讲解如何使用sklearn进行特征选择。
特征选择(Feature Selection)是特征工程中的一个重要步骤,指的是从原始数据中选择对于预测模型最好的特征的过程。给定n个特征,搜索其中k(小于等于n)个特征的子集来改善机器学习的性能。
上面这个定义包括两个需要解决的问题:
- 对机器学习性能的评估;
- 找到k个特征子集的方法。
1 机器学习模型的评估方法
在机器学习中,我们的目标是实现更好的预测性能,因此可以用评估预测性能的指标来衡量。例如分类任务的准确率和回归任务的均方根误差。
除此之外,也可以测量模型的元指标,元指标是指不直接与模型预测性能相关的指标,包括:
- 模型拟合/训练所需的时间;
- 拟合后的模型预测新实例的时间;
- 需要持久化(永久保存)的数据大小。
这些元指标补充了机器学习模型的评估方法。
下面定义一个函数,作为机器学习模型性能的评估标准,后面都以这个函数对模型进行评估:
from sklearn.model_selection import GridSearchCV # 导入网格搜索模块
# 定义一个函数搜索给定的参数,同时输出评估指标
def get_best_model_and_accuracy(model, params, X, y):
grid = GridSearchCV(
model, # 要搜索的模型
params, # 要尝试的参数
error_score=0. # 如果报错,结果是0
)
grid.fit(X, y) # 拟合模型和参数
# 经典的性能指标
print("Best Accuracy: {}".format(grid.best_score_)) # 最佳准确率
print("Best Parameters: {}".format(grid.best_params_)) # 最佳参数
print("Average Time to Fit (s): {}".format(round(grid.cv_results_['mean_fit_time'].mean(), 3))) # 拟合的平均时间(秒)
print("Average Time to Score (s): {}".format(round(grid.cv_results_['mean_score_time'].mean(), 3))) # 预测的平均时间(秒),从该指标可以看出模型在真实世界的性能
特征选择算法可以智能地从数据中提取最重要的信号,并且实现以下两个效果:
- 提升模型性能;
- 减少训练和预测时间。
接下来从数据网站上下载信用卡逾期数据集并读取导入:
import pandas as pd
import numpy as np
# 用随机数种子保证随机数永远一致
np.random.seed(123)
# 导入数据集
credit_card_default = pd.read_excel('../datasets/default of credit card clients.xls', skiprows=1)
# 描述性统计,转置方便观察
credit_card_default.describe().T
数据有30000行和24列(除去1列ID),其中23列为属性值,1列为标签(default payment next month,下个月逾期)。
接下来将数据集按照特征值和标签值进行切分:
# 特征矩阵
X = credit_card_default.drop(['ID', 'default payment next month'], axis=1)
# 标签
y = credit_card_default['default payment next month']
查看一下标签值的分布情况:
y.value_counts(normalize=True)
由于是分类任务,所以取一个空准确率作为基准,确保机器学习的性能比基准更好。
空准确率:指的是模型只预测为1类(不做任何预测),也能达到的一个准确率,在本例中,模型把所有类别都预测为0,准确率也就是77.88%,所以准确率需要超过77.88%才有意义。
2 创建基准机器学习流水线
下面以决策树作为我们的Baseline,同时运行一下评估函数评估其性能:
from sklearn.tree import DecisionTreeClassifier
# 设置模型参数
tree_params = {
'max_depth': [None, 1, 3, 5, 7]
}
d_tree = DecisionTreeClassifier()
# 运行模型评估函数
get_best_model_and_accuracy(d_tree, tree_params, X, y)
输出结果:
Best Accuracy: 0.8206333333333333
Best Parameters: {'max_depth': 3}
Average Time to Fit (s): 0.156
Average Time to Score (s): 0.002
从结果看出Baseline的决策树模型准确率为0.8206,后面我们需要击败的基线准确率就以此作为标准。
特征选择器 | 分类器 | 准确率 | 平均拟合时间 | 平均预测时间 |
---|---|---|---|---|
Baseline(选取所有特征) | 决策树 | 0.8206 | 0.156 | 0.002 |
3 两类特征选择方法
这里介绍两种类型的方法进行特征选择:
- 基于统计的特征选择:依赖于统计测试;
- 基于模型的特征选择:依赖于预处理步骤,需要训练一个辅助的机器学习模型,并利用其预测能力来选择特征(简单来说就是训练一个模型来筛选特征)。
3.1 基于统计的特征选择
基于统计主要介绍两个方法帮助选择特征:
- 皮尔逊相关系数(pearson correlations);
- 假设检验。
这两个方法都是单变量方法,也就是用于选择单一特征。
3.1.1 使用皮尔逊相关系数
皮尔逊相关系数会测量列(属性)之间的线性关系,在-1~1之间变化,0代表没有线性关系,-1/1代表线性关系很强。
皮尔逊相关系数要求每列是正态分布的,但是因为数据集很大(超过500的阈值),所以可以忽略这个要求。
注:根据中心极限定理,当数据量足够大时,可以认为数据是近似正态分布的。
接下来计算每一列特征之间的相关系数,同时用热力图进行可视化:
import seaborn as sns
from matplotlib import style
# 选用一个干净的主题
style.use('fivethirtyeight')
sns.heatmap(credit_card_default.corr())
接下来过滤出相关系数超过正负0.2的特征,重新训练模型:
# 过滤出相关系数超过正负0.2的特征
highly_correlated_features = credit_card_default.columns[credit_card_default.corr()['default payment next month'].abs() > .2]
# 删除标签列
highly_correlated_features = highly_correlated_features.drop('default payment next month')
# 只选取5个高度关联的特征值,训练模型
X_subsetted = X[highly_correlated_features]
get_best_model_and_accuracy(d_tree, tree_params, X_subsetted, y)
结果如下:
Best Accuracy: 0.8213333333333332
Best Parameters: {'max_depth': 3}
Average Time to Fit (s): 0.009
Average Time to Score (s): 0.002
特征选择器 | 分类器 | 准确率 | 平均拟合时间 | 平均预测时间 |
---|---|---|---|---|
Baseline(选取所有特征) | 决策树 | 0.8206 | 0.156 | 0.002 |
相关系数 | 决策树 | 0.8213 | 0.009 | 0.002 |
从结果可以看出,模型准确率提升了一点点,但是拟合时间快了将近20倍。
3.1.1 使用假设检验
假设检验是一种统计学方法,可以对单个特征进行复杂的统计检验。
零假设 \(H_0\)为:特征与标签值没有关系。p值越低,拒绝零假设的概率越大,即特征与标签有关联的概率就越大,我们应该保留这个特征。
以假设检验作为特征选择器,训练模型:
from sklearn.feature_selection import SelectKBest # 在给定目标函数后选择k个最高分
from sklearn.feature_selection import f_classif # ANOVA测试
k_best = SelectKBest(f_classif)
# 用SelectKBest建立流水线
select_k_pipe = Pipeline([
('k_best', k_best), # 特征选择器
('classifier', d_tree) # 决策树分类器
])
# 流水线参数
select_k_best_pipe_params = ({
'k_best__k': list(range(1, 23)) + ['all'], # 特征选择器参数,all表示选择全部
'classifier__max_depth': [None, 1, 3, 5, 7, 9, 11, 17, 15, 17, 19, 21] # 决策树参数
})
get_best_model_and_accuracy(select_k_pipe, select_k_best_pipe_params, X, y)
模型评估结果:
Best Accuracy: 0.8213333333333332
Best Parameters: {'classifier__max_depth': 3, 'k_best__k': 5}
Average Time to Fit (s): 0.105
Average Time to Score (s): 0.003
查看选择了哪些特征:
# 把列名以及对应的p值组成DataFrame,按照p值进行排序
p_values = pd.DataFrame({
'columns': X.columns,
'p_value': k_best.pvalues_
}).sort_values('p_value')
# 假设检验最终选择了5个特征
p_values.head(5)
假设检验最终选择的特征值是和相关系数选择的一样的。可以看出前5个特征的p值极小,几乎为0。
p值的一个常见阈值时0.05,意思是p值小于0.05的特征是显著的。
特征选择器 | 分类器 | 准确率 | 平均拟合时间 | 平均预测时间 |
---|---|---|---|---|
Baseline(选取所有特征) | 决策树 | 0.8206 | 0.156 | 0.002 |
相关系数 | 决策树 | 0.8213 | 0.009 | 0.002 |
假设检验 | 决策树 | 0.8213 | 0.105 | 0.003 |
3.2 基于模型的特征选择
3.2.1 使用决策树选择特征
下面引入SelectFromModel
,和SelectKBest
一样会选取最重要的前k特征,但是它使用的是机器学习模型的内部指标来评估特征的重要性。
from sklearn.feature_selection import SelectFromModel
# 在流水线中使用 SelectFromModel 这个方法
# 创建基于 DecisionTreeClassifier 的 SelectFromModel
select = SelectFromModel(DecisionTreeClassifier())
select_from_pipe = Pipeline([
('select', select), # 特征选择器
('classifier', d_tree) # 分类器
])
select_from_pipe_params = {
# 特征选择器参数
'select__threshold': [.01, .05, .1, .2, .25, .3, .4, .5, .6, "mean", "median", "2.*mean"],
'select__estimator__max_depth': [None, 1, 3, 5, 7],
# 分类器参数
'classifier__max_depth': [1, 3, 5, 7],
}
get_best_model_and_accuracy(select_from_pipe, select_from_pipe_params, X, y)
评估结果:
Best Accuracy: 0.8206333333333333
Best Parameters: {'classifier__max_depth': 3, 'select__estimator__max_depth': 1, 'select__threshold': 'median'}
Average Time to Fit (s): 0.176
Average Time to Score (s): 0.002
可以看到结果没有比原来的好。
特征选择器 | 分类器 | 准确率 | 平均拟合时间 | 平均预测时间 |
---|---|---|---|---|
Baseline(选取所有特征) | 决策树 | 0.8206 | 0.156 | 0.002 |
相关系数 | 决策树 | 0.8213 | 0.009 | 0.002 |
假设检验 | 决策树 | 0.8213 | 0.105 | 0.003 |
决策树 | 决策树 | 0.8206 | 0.176 | 0.002 |
列出模型选择的特征:
# 使用SelectFromModel的get_support()方法,列出选择的特征
# 设置流水线最佳参数
select_from_pipe.set_params(**{
'classifier__max_depth': 3,
'select__estimator__max_depth': 1,
'select__threshold': 'median'
})
# 拟合数据
select_from_pipe.steps[0][1].fit(X, y)
# 列出选择的列
X.columns[select_from_pipe.steps[0][1].get_support()]
Index(['LIMIT_BAL', 'SEX', 'EDUCATION', 'MARRIAGE', 'AGE', 'PAY_0', 'PAY_2',
'PAY_3', 'PAY_4', 'PAY_5', 'PAY_6', 'BILL_AMT1', 'BILL_AMT2',
'BILL_AMT3', 'BILL_AMT4', 'BILL_AMT5', 'BILL_AMT6', 'PAY_AMT1',
'PAY_AMT2', 'PAY_AMT3', 'PAY_AMT4', 'PAY_AMT5', 'PAY_AMT6'],
dtype='object')
这颗树选择了除了两个特征外的所有其他特征,但是和特征较少的树性能没什么区别。
3.2.2 使用逻辑回归作为选择器
用逻辑回归模型作为选择器,在L1和L2范数上进行网格搜索。
# 用正则化后的逻辑回归进行选择
logistic_selector = SelectFromModel(LogisticRegression(
solver='liblinear' # 注意:这里必须设置这个solver,否则不支持l1正则
))
# 新流水线,用逻辑回归作为参数选择器
regularization_pipe = Pipeline([
('select', logistic_selector),
('classifier', tree)
])
# 流水线参数
regularization_pipe_params = {
# 逻辑回归特征选择器参数
'select__threshold': [.01, .05, .1, "mean", "median", "2.*mean"],
'select__estimator__penalty': ['l1', 'l2'], # L1和L2正则化
# 分类器参数
'classifier__max_depth': [1, 3, 5, 7],
}
get_best_model_and_accuracy(regularization_pipe, regularization_pipe_params, X, y)
评估结果:
Best Accuracy: 0.8212666666666667
Best Parameters: {'classifier__max_depth': 3, 'select__estimator__penalty': 'l1', 'select__threshold': 0.05}
Average Time to Fit (s): 0.53
Average Time to Score (s): 0.002
列出模型选择的特征:
# 使用SelectFromModel的get_support()方法,列出选择的特征
# 设置流水线最佳参数
regularization_pipe.set_params(**{
'classifier__max_depth': 3,
'select__estimator__penalty': 'l1',
'select__threshold': 0.05
})
# 拟合数据
regularization_pipe.steps[0][1].fit(X, y)
# 列出选择的列
X.columns[regularization_pipe.steps[0][1].get_support()]
Index(['SEX', 'EDUCATION', 'MARRIAGE', 'PAY_0', 'PAY_2', 'PAY_3'], dtype='object')
特征选择器 | 分类器 | 准确率 | 平均拟合时间 | 平均预测时间 |
---|---|---|---|---|
Baseline(选取所有特征) | 决策树 | 0.8206 | 0.156 | 0.002 |
相关系数 | 决策树 | 0.8213 | 0.009 | 0.002 |
假设检验 | 决策树 | 0.8213 | 0.105 | 0.003 |
决策树 | 决策树 | 0.8206 | 0.176 | 0.002 |
逻辑回归 | 决策树 | 0.8212 | 0.53 | 0.002 |
3.2.3 使用SVM作为选择器
以支持向量机作为特征选择器,构建机器学习流水线:
from sklearn.svm import LinearSVC # SVC是线性模型,只能分割二分类数据
# 用SVC选择特征
svc_selector = SelectFromModel(LinearSVC())
svc_pipe = Pipeline([
('select', svc_selector),
('classifier', tree)
])
svc_pipe_params = {
# SVC特征选择器参数
'select__threshold': [.01, .05, .1, "mean", "median", "2.*mean"],
'select__estimator__penalty': ['l1', 'l2'], # L1和L2正则化
'select__estimator__loss': ['squared_hinge', 'hinge'],
'select__estimator__dual': [True, False],
# 分类器参数
'classifier__max_depth': [1, 3, 5, 7],
}
get_best_model_and_accuracy(svc_pipe, svc_pipe_params, X, y)
模型评估结果:
Best Accuracy: 0.8212666666666667
Best Parameters: {'classifier__max_depth': 3, 'select__estimator__dual': False, 'select__estimator__loss': 'squared_hinge', 'select__estimator__penalty': 'l1', 'select__threshold': 'mean'}
Average Time to Fit (s): 0.97
Average Time to Score (s): 0.001
查看一下选择器最终选择了哪些特征:
# 看一下选择器选择了哪些特征
# 设置流水线最佳参数
svc_pipe.set_params(**{
'classifier__max_depth': 3,
'select__estimator__dual': False,
'select__estimator__loss': 'squared_hinge',
'select__estimator__penalty': 'l1',
'select__threshold': 'mean'
})
# 拟合数据
svc_pipe.steps[0][1].fit(X, y)
# 列出选择的列
X.columns[svc_pipe.steps[0][1].get_support()]
输出结果:
Index(['SEX', 'EDUCATION', 'MARRIAGE', 'PAY_0', 'PAY_2', 'PAY_3'], dtype='object')
特征选择器 | 分类器 | 准确率 | 平均拟合时间 | 平均预测时间 |
---|---|---|---|---|
Baseline | 决策树 | 0.8206 | 0.156 | 0.002 |
相关系数 | 决策树 | 0.8213 | 0.009 | 0.002 |
假设检验 | 决策树 | 0.8213 | 0.105 | 0.003 |
决策树 | 决策树 | 0.8206 | 0.176 | 0.002 |
逻辑回归 | 决策树 | 0.8212 | 0.53 | 0.002 |
支持向量机 | 决策树 | 0.8212 | 0.97 | 0.001 |
4.小结
本文主要介绍了两大类特征选择方法:
- 基于统计
- 相关系数
- 假设检验
- 方差分析
- ...
- 基于模型
- 决策树
- 逻辑回归
- SVC
- ...
下面是一些关于如何选用特征选择方法的经验:
- 如果特征是分类的,那么从SelectKBest开始,用卡方或基于树的选择器。
- 如果特征基本是定量的(例如本例),用线性模型和基于相关性的选择器一般效果更好。
- 如果是二元分类问题,考虑使用SelectFromModel和SVC,因为SVC会查找优化二元分类任务的系数。
- 在手动选择前,探索性数据分析会很有益处。不能低估领域知识的重要性。