特征抽取是数据挖掘任务最为重要的一个环节,一般而言,它对最终结果的影响要高过数据挖掘算法本身。
不幸的是,关于怎样选取好的特征,还没有严格、快捷的规则可循,其实这也是数据挖掘科学更像是一门艺术的所在。
创建好的规则离不开直觉,还需要专业领域知识和数据挖 掘经验,光有这些还不够,还得不停地尝试、摸索,在试错中前进,有时多少还要靠点运气。
通常特征数量很多,但我们只想选用其中一小部分。有如下几个原因。
1、降低复杂度
随着特征数量的增加,很多数据挖掘算法需要更多的时间和资源。减少特征数量,是提高算法运行速度,减少资源使用的好方法。
2、降低噪音
增加额外特征并不总会提升算法的表现。额外特征可能扰乱算法的正常工作,这些额外特征间的相关性和模式没有实际应用价值(这种情况在小数据集上很常见)。只选择合适的特征有助于减少出现没有实际意义的相关性的几率。
3、增加模型可读性
根据成千上万个特征创建的模型来解答一个问题,对计算机来说很容易,但模型对我们自己来说就晦涩无比。因此,使用更少的特征,创建我们自己可以理解的模型,就很有必要。
有些分类算法确实很强壮,能够处理噪音问题,特征再多也不在话下,但是选用干净的数据,选取更具描述性的特征,对算法效果提升很有帮助。
根据特征选择的形式又可以将特征选择方法分为三种
Filter
:过滤法,按照发散性或者相关性对各个特征进行评分,设定阈值或者待选择阈值的个数,选择特征。
Wrapper
:包装法,根据目标函数(通常是预测效果评分),每次选择若干特征,或者排除若干特征。
Embedded
:嵌入法,先使用某些机器学习的算法和模型进行训练,得到各个特征的权值系数,根据系数从大到小选择特征。类似于Filter方法,但是是通过训练来确定特征的优劣。
写在前面
为了帮助更好地理解特征选择,这里使用我们常用的股市数据作为基础数据。
由于文章较长,为方便阅读,我将特征选择与特征提取总结文章拆分为上下两篇,上篇(本文)主要内容包括如下图所示,主要介绍过滤法中常用的几种特征选择方法。
导入相关模块
代码语言:javascript复制import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import warnings
warnings.filterwarnings("ignore")
import yfinance as yf
yf.pdr_override()
获取数据
仍然用腾讯股市数据,辅助理解本文内容。
代码语言:javascript复制symbol = 'TCEHY'
start = '2016-01-01'
end = '2021-01-01'
df = yf.download(symbol, start, end)
# df = df.reset_index()
# View columns
df.head()
特征构造
代码语言:javascript复制dataset = df.copy()
['Increase_Decrease'] = np.where(dataset['Volume'].shift(-1) > dataset['Volume'],1,0)
dataset['Buy_Sell_on_Open'] = np.where(dataset['Open'].shift(-1) > dataset['Open'],1,0)
dataset['Buy_Sell'] = np.where(dataset['Adj Close'].shift(-1) > dataset['Adj Close'],1,0)
dataset['Returns'] = dataset['Adj Close'].pct_change()
dataset = dataset.dropna()
dataset.head()
数据准备
设定目标标签为收盘价,研究哪些变量对收盘价影响加大。通过一定对方法剔除几乎没有影响的特征,选出影响较多对特征。特征选择在维度较大时尤为重要。
代码语言:javascript复制features = dataset.drop(['Adj Close', 'Close', 'Returns'], axis=1)
array = features.values
X = array.astype(int)
Y = dataset['Adj Close'].values.astype(int)
过滤法
过滤方法通常用作预处理步骤,特征选择完全独立于任何机器学习算法。它是根据各种统计检验中的分数以及相关性的各项指标来选择特征。
方差过滤
这是通过特征本身的方差来筛选特征的类。比如一个特征本身的方差很小,就表示样本在这个特征上基本没有差异,可能特征中的大多数值都一样,甚至整个特征的取值都相同,那这个特征对于样本区分没有什么作用。
scikit-learn中的VarianceThreshold转换器可用来删除特征值的方差达不到最低标准的特征。
代码语言:javascript复制from sklearn.feature_selection import VarianceThreshold
vt = VarianceThreshold()
vt.fit_transform(X)
var_thd = pd.DataFrame(vt.variances_, columns = ["Variance"], index=features.columns)
var_thd = var_thd.reset_index()
var_thd.sort_values('Variance',ascending=0)
无论什么时候,拿到数据后,先做下类似简单、直接的分析,对数据集的特点做到心中有数。方差为0的特征不但对数据挖掘没有丝毫用处,相反还会拖慢算法的运行速度。
单变量选择
单变量的特征选择是通过基于一些单变量的统计度量方法来选择最好的特征。属于过滤法的一种。
scikit-learn提供了几个用于选择单变量特征的转换器,其中SelectKBest
返回
个最佳特征,SelectPercentile
返回表现最佳的前
个特征。这两个转换器都提供计算特征表现的一系列方法。都将得分函数作为输入,返回单变量的得分和p值。可作为输入的评分函数有:
- 对于回归: f_regression , mutual_info_regression, 互信息
- 对于分类: chi2 , f_classif , mutual_info_classif, 皮尔森相关系数
SelectKBest 选择出前k个与标签最相关的特征,主要有两个参数: 1、
score_func
: callable,函数取两个数组X和y,返回一对数组(scores, pvalues)或一个分数的数组。默认函数为f_classif,默认函数只适用于分类函数。 2、k
:int or "all", optional, default=10。所选择的topK个特征。“all”选项则绕过选择,用于参数搜索。
卡方
单个特征和某一类别之间相关性的计算方法有很多。最常用的有卡方检验。经典的卡方检验是检验定性自变量对定性因变量的相关性。
卡方过滤是专门针对离散型标签(即分类问题)的相关性过滤。卡方检验类 feature_selection.chi2
计算每个非负特征和标签之间的卡方统计量,并依照卡方统计量由高到低为特征排名。
再结合 feature_selection.SelectKBest
这个可以输入”评分标准“来选出前K个分数最高的特征的类,我们可以借此除去最可能独立于标签,与我们分类目的无关的特征。
from sklearn.feature_selection import SelectKBest
from sklearn.feature_selection import chi2
# 再使用SelectKBest转换器类,用卡方函数打分,初始化转换器。
test = SelectKBest(score_func=chi2, k=3)
test.fit(X, Y)
# 得分情况
np.set_printoptions(precision=3)
print(test.scores_)
代码语言:javascript复制[6.365e 03 6.416e 03 6.286e 03 8.295e 08
2.949e 01 2.240e 01 3.059e 01]
卡方检验的本质是推测两组数据之间的差异,其检验的原假设是”两组数据是相互独立的”。卡方检验返回卡方值和P值两个统计量,其中卡方值很难界定有效的范围,而p值,我们一般使用0.01或0.05作为显著性水平,即p值判断的边界。
从特征工程的角度,我们希望选取卡方值很大,p值小于0.05的特征,即和标签是相关联的特征。而调用SelectKBest之前,我们可以直接从chi2实例化后的模型中获得各个特征所对应的卡方值和P值。
F检验
另外类似的方法还有F检验,又称ANOVA,方差齐性检验,是用来捕捉每个特征与标签之间的线性关系的过滤方法。它即可以做回归也可以做分类,因此包含feature_selection.f_classif
(F检验分类)和feature_selection.f_regression
(F检验回归)两个类。其中F检验分类用于标签是离散型变量的数据,而F检验回归用于标签是连续型变量的数据。
F检验的本质是寻找两组数据之间的线性关系,其原假设是”数据不存在显著的线性关系“。它返回F值和p值两个统 计量。
和卡方过滤一样,我们希望选取p值小于0.05或0.01的特征,这些特征与标签时显著线性相关的,而p值大于 0.05或0.01的特征则被我们认为是和标签没有显著线性关系的特征,应该被删除。
互信息
互信息法是用来捕捉每个特征与标签之间的任意关系(包括线性和非线性关系)的过滤方法。和F检验相似,它既可以做回归也可以做分类,并且包含两个类 feature_selection.mutual_info_classif
(互信息分类)和feature_selection.mutual_info_regression
(互信息回归)。这两个类的用法和参数都和F检验一模一样,不过互信息法比F检验更加强大,F检验只能够找出线性关系,而互信息法可以找出任意关系。
互信息法不返回p值或F值类似的统计量,它返回“每个特征与目标之间的互信息量的估计”,这个估计量在[0,1]之间取值,为0则表示两个变量独立,为1则表示两个变量完全相关。
关于F检验和互信息,可以参见官方例子:Comparison of F-test and mutual information[1]
提取简化后的特征
代码语言:javascript复制# 调用transform()或直接使用fit_transform方法,
# 对相同的数据集进行预处理和转换。
new_features = test.transform(X)
# 打印出特征提取前后特征数
print('原始特征数:', X.shape[1])
print('简化的特征数:', new_features.shape[1])
代码语言:javascript复制原始特征数: 7
简化的特征数: 3
代码语言:javascript复制chi_sq = pd.DataFrame(fit.scores_, columns = ["Chi_Square"], index=features.columns)
chi_sq = chi_sq.reset_index()
chi_sq.sort_values('Chi_Square',ascending=0)
皮尔逊相关系数
Pearsonr函数的接口几乎与scikit-learn单变量转换器接口一致,该函数接收两个数组 (当前例子中为x和y)作为参数,返回两个数组:每个特征的皮尔逊相关系数和p值,直接把它传入到SelectKBest函数中。
SciPy的pearsonr函数参数为两个数组,但要注意的是第一个参数x为一维数组。我们来实现一个包装器函数,这样就能像前面那样处理多维数组。
代码语言:javascript复制from scipy.stats import pearsonr
def multivariate_pearsonr(X, Y):
# 创建scores和pvalues数组,遍历数据集的每一列。
scores, pvalues = [], []
for column in range(X.shape[1]):
# 只计算该列的皮尔逊相关系数和p值,并将其存储到相应数组中。
cur_score, cur_p = pearsonr(X[:,column], Y)
scores.append(abs(cur_score))
pvalues.append(cur_p)
#函数最后返回包含皮尔逊相关系数和p值的元组。
return (np.array(scores), np.array(pvalues))
该方法衡量的是变量之间的线性相关性,结果的取值区间为
, -1表示完全的负相关; 1表示完全的正相关; 0表示没有线性相关。
现在,就可以像之前那样使用转换器类,根据皮尔逊相关系数对特征进行排序。
代码语言:javascript复制m_pearsonr = SelectKBest(score_func=multivariate_pearsonr, k=3)
X_pearson = m_pearsonr.fit_transform(X, Y)
print(m_pearsonr.scores_)
代码语言:javascript复制[9.989e-01 9.992e-01 9.994e-01 3.903e-01
9.078e-03 9.203e-04 2.001e-02]
代码语言:javascript复制pearsonr = pd.DataFrame(m_pearsonr.scores_, columns = ["pearsonr"], index=features.columns)
pearsonr = pearsonr.reset_index()
pearsonr.sort_values('pearsonr',ascending=0)
多重共线性方差膨胀系数
方差膨胀系数(variance inflation factor,VIF)是衡量多元线性回归模型中复 (多重)共线性严重程度的一种度量。它表示回归系数估计量的方差与假设自变量间不线性相关时方差相比的比值。
多重共线性是指自变量之间存在线性相关关系,即一个自变量可以是其他一个或几个自变量的线性组合。
通常以10作为判断边界。 当VIF<10,不存在多重共线性; 当10<=VIF<100,存在较强的多重共线性; 当VIF>=100, 存在严重多重共线性。
代码语言:javascript复制from statsmodels.stats.outliers_influence import variance_inflation_factor
def calculate_vif(features):
vif = pd.DataFrame()
vif["index"] = features.columns
vif["VIF"] = [variance_inflation_factor(features.values, i) for i in range(features.shape[1])]
return(vif)
vif = calculate_vif(features)
while vif['VIF'][vif['VIF'] > 10].any():
remove = vif.sort_values('VIF',ascending=0)['index'][:1]
features.drop(remove,axis=1,inplace=True)
vif = calculate_vif(features)
vif
过滤法总结
最后用一张表格将过滤法做个总结,方便大家查阅学习。
类 | 说明 | 超参数的选择 |
---|---|---|
VarianceThreshold | 方差过滤,可输入方差阈值,返回方差大于阈值的新特征矩阵 | 看具体数据究竟是含有更多噪声还是更多有效特征一般就使用0或1来筛选也可以画学习曲线或取中位数跑模型来帮助确认 |
SelectKBest | 用来选取K个统计量结果最佳的特征,生成看配合使用的统计量符合统计量要求的新特征矩阵 | 看配合使用的统计量 |
chi2 | 卡方检验,专用于分类算法,捕捉相关性 | 追求p小于显著性水平的特征 |
f_classif | F检验分类,只能捕捉线性相关性 要求数据服从正态分布 | 追求p小于显著性水平的特征 |
f_regression | F检验回归,只能捕捉线性相关性 要求数据服从正态分布 | 追求p小于显著性水平的特征 |
mutual_info_classif | 互信息分类,可以捕捉任何相关性 追求互信息估计大于0的特征不能用于稀疏矩阵 | 追求互信息估计大于0的特征 |
mutual_info_regression | 互信息回归,可以捕捉任何相关性 不能用于稀疏矩阵 | 追求互信息估计大于0的特征 |
pearsonr | 皮尔逊相关系数,只能捕捉线性相关关系 | 追求p小于显著性水平的特征 |
左右滑动查看更多
参考资料
[1]
Comparison of F-test and mutual information: https://scikit-learn.org/stable/auto_examples/feature_selection/plot_f_test_vs_mi.html