特征工程包括三个部分:
1、特征提取:从文字,图像,声音等其他非结构化数据中提取信息作为特征,建立最原始特征信息采集。
2、特征创造:把现有的一些特征进行组合*和相互计算,得到新的特征。
3:特征选择:从所有特征中挑选出最具有价值的,对模型最具有帮助的特征。
本文将全部学习特征选择。
特征选择第一步,充分理解业务和需求,理解数据特征。
拿到数据后,一些低相关性的特征在我们人眼识别下就可以轻易剔除掉的,这需要我们对业务的理解后次啊可以得出。比如上次在泰坦尼克的实验中,名字,乘客编号这些信息是跟最后是否存活是没有什么作用的,相关性低,可以剔除;而性别,年龄是相关性程度高的特征,就可以保留了。
当然实际运用中,金融、医疗、电商等,我们数据集的特征是很多很多的,业务领域很复杂,我们无法做大对业务完全理解,或者有一些特征也谈不上业务,不能完全自己手动地去剔除一些低相关性的特征,那此时怎么做呢?
做法是:过滤法,嵌入法,包装法以及之前学习过的降维算法。本文只讲前三个。
一:过滤法
主要对象是那些需要遍历特征,或者需要升维的算法,因为这个很耗资源成本。
1:方差过滤:
一个好的特征,要有足够的区分度,数值差异越大,区分度越好,这个数值就可以使用方差来衡量,比如一个特征自身方差很小,也可能绝大部分甚至全部数值是一样的,那么或者特征对我们的模型就没有什么意义,需要过滤掉这样的方差几乎为0的特征。
使用VarianceThreshold,有个参数是threshold,表示舍弃掉所有方差小于threshold的特征,threshold默认是0。
from sklearn.feature_selection import VarianceThreshold
import numpy as np
data = np.array([[1, 2, 3, 4, 5], [6, 7, 8, 9, 10], [3, 3, 3, 3, 3]]).transpose()
print(data.shape)
selector = VarianceThreshold(threshold=0.1) # 默认阈值是0,表示会舍弃掉多有方差小于该阈值的特征。
res = selector.fit_transform(data)
print(res)
print(res.shape)
如果你觉得特征还是很多,自己也不知道要把哪些方差值作为阈值,可以通过下面的方式进行查看数据的每一个特征的方差,按照结果自行选择一下方差。
from sklearn.feature_selection import VarianceThreshold
import numpy as np
data = np.array([[1, 2, 3, 4, 5], [6, 7, 8, 9, 11], [3, 3, 3, 3, 3], [3, 7, 3, 7, 3]]).transpose()
print(data.shape)
print(data.var(axis=0)) # 每一列的方差
print(np.sort(data.var(axis=0))) # 从小到大的顺序来查看每一个特征的方差。
print(np.median(data.var(axis=0))) # 方差的中位数
selector = VarianceThreshold(threshold=np.median(data.var(axis=0))) # 会把一半的特征砍掉
res = selector.fit_transform(data)
print(res)
print(res.shape)
传统的决策树按道理是全部特征都会考虑的,随机森林会随机选取一些部分的特征进行拟合,但是scikit-learn的话,都是含有随机性了。
但是总体而言,过滤法对随机森林的影响不是很大的,原因是随机森林会随机选择特征和样本。
2:相关性过滤之卡方过滤法
卡方过滤法是专门针对离散型标签,也就是分类问题的相关性过滤。
计算每个非负特征和标签之间的统计量,从高到低进行排名,保留前几个相关性较高的特征,去除与分类无关的特性。
记住,不是负数,存在负数,必须得先标准化一下。
from sklearn.feature_selection import SelectKBest # 选择出前k个分数最高的特征
from sklearn.feature_selection import chi2 # 卡方
import numpy as np
x = np.array([[1, 2, 3, 12, 5], [6, 7, 8, 9, 11], [3, 3, 3, 3, 3], [3, 7, 3, 7, 3]]).transpose()
y = np.array([1, 2, 1, 2, 1])
selector = SelectKBest(score_func=chi2, k=3) # 卡方作为评分标准,保留前k个
res = selector.fit_transform(x, y)
print(res)
3:相关性过滤之F检验
用来捕捉每个特征和标签y之间的线性相关关系的过滤方法。既可以做回归,也可以做分类。它的本质是寻找出两组数据之间的线性关系。
一般在正太分布的数据的话,效果会比较好。
sklearn.feature_selection.f_classif用于分类
sklearn.feature_selection.f_regression用于回归
from sklearn.feature_selection import f_classif # 用于分类
# from sklearn.feature_selection import f_regression # 用于回归
from sklearn.feature_selection import SelectKBest # 选择出前k个分数最高的特征
import numpy as np
x = np.array([[1, 2, 3, 12, 5], [6, 7, 8, 9, 11], [3, 3.1, 3.1, 3, 3], [4.1, 8, 4, 8, 4]]).transpose()
y = np.array([1, 2, 1, 2, 1])
# f_classif 返回的值和 卡方和卡方类似,我们希望选出p值小于0.05或者0.01的特征,这些标签是有存在显著线性关系。
# 相反,大于这些值,我们认为是没有显著线性相关关系,我们应该删除
F, p_values = f_classif(x, y)
print(F)
print(p_values)
k = F.shape[0] - (p_values > 0.05).sum()
print(k)
selecor = SelectKBest(score_func=f_classif, k=k)
res = selecor.fit_transform(x, y)
print(res)
4:相关性过滤之互信息法
互信息是用来捕获每个特征与标签之间的任意关系(包括线性和非线性)
F检验只能找出线性关系,而互信息可以找出任意关系。
互信息法它返回[0,1]之间的量,为0 表示俩组数据完全独立,为1表示完全相关。
我个人觉得或者方法很好用,能清晰直观的看出来哪些特征是有关的,哪些特征是无关的。用起来特别舒服。
from sklearn.feature_selection import SelectKBest # 选择出前k个分数最高的特征
from sklearn.feature_selection import mutual_info_classif as MIC # 用于分类
# from sklearn.feature_selection import mutual_info_regression # 用于回归
import numpy as np
x = np.array([[1, 2, 3, 12, 5], [6, 7, 8, 9, 11], [3, 3.1, 3, 3.1, 3], [4, 8, 4, 8, 4]]).transpose()
y = np.array([1, 2, 1, 2, 1])
result = MIC(x, y)
print(result) # 我个人感觉啊,这个是真的强大,很直白明了的看出俩哪个特征有关,哪个特征无关,我们最想要的不就是这个么。
selector = SelectKBest(score_func=MIC, k=2)
res = selector.fit_transform(x, y)
print(res)
至此,我们把一些常用的基于过滤法的特征选择,基于方差过滤,以及基于卡方,F检验,互信息的相关性过滤等,具体操作,我喜欢先进行方差过滤,再使用互信息来进行捕捉相关性,具体的使用方式已经列举了出来。
二:Embedded嵌入法
嵌入法是让算法自己决定使用哪些特征,即特征选择和算法训练同时进行,
数据输入后,每次都是选择一部分特征进训练和评估,反复如此执行,多次后,得到各个特征的权值系数,此数值代表特征对模型的某种重要性(某种贡献),根据权值系数从大到小来选择一些重要的(权值系数大的特征),之前我们再学习决策树和继承算法中有个feature_importances_就是这个意思。
嵌入法的结果会更加精确到模型的实际效用,对模型的提升来说是非常有效果,因此无关的特征是五区分度的特征会因为缺乏对模型的贡献而直接被删除,可谓是更高级一些。
如果我们直接上来搞嵌入法,不用过滤也是ok。
由于嵌入法本身引入了算法(请注意,这里的算法是一个评估器,是一个模型),每次都会选怎全部特征,如果我们选择的算法很复杂,那么将会带来很大的计算量。
在scikit-learn中,使用sklearn.feature_selection.SelectFromModel,它要求传入的评估器具有属性feature_importances_或者是属性coef_,
我们先来看看效果:
from sklearn.datasets import load_breast_cancer # 乳腺癌数据
from sklearn.ensemble import RandomForestClassifier
from sklearn.feature_selection import SelectFromModel # 嵌入法
# 加载乳腺癌数据,569个样本数据,30个属性特征,维度不高,数据样本也少。
breast_cancer = load_breast_cancer()
print(breast_cancer.data.shape)
rfc = RandomForestClassifier(n_estimators=30, random_state=0) # 需要自定义个算法模型来进行训练和评估吧.
x_embedded = SelectFromModel(estimator=rfc, threshold=0.008) # 嵌入法,使用我们提供的模型,把重要度数值低于0.008的特征全部删除。
res = x_embedded.fit_transform(breast_cancer.data, breast_cancer.target)
print(res.shape)
从这最后的结果可以发现,嵌入法已经帮我们删除了一些特征了都。
下面这个阈值设置多少呢?我们还是用学习曲线数据来说话,这里的思路都是通用的,单个超参数,我们今天学习游戏额特征选择的类,涉及一些超参数,我们完全可以使用学习曲线来看看哪个更合适。
# ====== 跟之前的一样,如果threshold不缺定是多少,那么我们就来画学习曲线呗 ====== #
import numpy as np
import matplotlib.pyplot as plt
from sklearn.datasets import load_breast_cancer # 乳腺癌数据
from sklearn.ensemble import RandomForestClassifier
from sklearn.feature_selection import SelectFromModel # 嵌入法
from sklearn.model_selection import cross_val_score
# 加载乳腺癌数据,569个样本数据,30个属性特征,维度不高,数据样本也少。
breast_cancer = load_breast_cancer()
rfc = RandomForestClassifier(n_estimators=30, random_state=0) # 把算法固定先,别随机。
thresholds = np.linspace(0, 0.1, 80)
score = []
for i in thresholds:
x_embedded = SelectFromModel(estimator=rfc, threshold=i).fit_transform(breast_cancer.data, breast_cancer.target)
once = cross_val_score(rfc, x_embedded, breast_cancer.target, cv=10).mean()
score.append(once)
print('最大的是:{}'.format(score.index(max(score))))
plt.plot(thresholds, score)
plt.legend()
plt.show()
效果如下图所示:
可以看到阈值在0.01左右有较好的表现。
三:包装法
包装法也是一个特征选择和算法训练同时进行的方法,于嵌入法十分相似。也是依赖_coef属性和feature_importances_属性来完成的特征选择。
不同的是呢,在每一轮迭代都会删除一些特征,直到达成了目标个数特征,选择除了最佳特征集合,因此效率要高于嵌入法,但是低于过滤法。
from sklearn.datasets import load_breast_cancer # 乳腺癌数据
from sklearn.feature_selection import RFE # 包装法
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import cross_val_score
# 加载乳腺癌数据,569个样本数据,30个属性特征,维度不高,数据样本也少。
breast_cancer = load_breast_cancer()
print(breast_cancer.data.shape)
rfc = RandomForestClassifier(n_estimators=30, random_state=0) # 把算法固定先,别随机。
selector = RFE(estimator=rfc, n_features_to_select=16, step=1) # 使用随机森林的评估,目标最后只留下16个属性,步长是1,每次迭代删除一个特征
res_embedded = selector.fit_transform(breast_cancer.data, breast_cancer.target)
print(res_embedded.shape)
print(selector.ranking_) # 所有特征按次数迭代中综合总要先排名,排名第1的都是选中的属性,而且数量恰恰就是 n_features_to_select
print(selector.support_) # 所有特征是否被选中的bool值。
score = cross_val_score(rfc, res_embedded, breast_cancer.target, cv=10)
print(score)
但是我们到底选多少个特征是合适的呢?
来来来,学习曲线来了。
import matplotlib.pyplot as plt
from sklearn.datasets import load_breast_cancer # 乳腺癌数据
from sklearn.feature_selection import RFE # 包装法
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import cross_val_score
# 加载乳腺癌数据,569个样本数据,30个属性特征,维度不高,数据样本也少。
breast_cancer = load_breast_cancer()
print(breast_cancer.data.shape)
rfc = RandomForestClassifier(n_estimators=30, random_state=0) # 把算法固定先,别随机。
scores = []
for i in range(1, 30, 1):
X_embedded = RFE(estimator=rfc, n_features_to_select=i, step=1).fit_transform(breast_cancer.data, breast_cancer.target)
once = cross_val_score(rfc, X_embedded, breast_cancer.target, cv=10).mean()
scores.append(once)
plt.plot(range(1, 30, 1), scores)
plt.xticks(range(1, 30, 1))
plt.show()
可以看到在16附近就可以达到一个较高的值了。
四:总结一下
总结一下,到现在我们学习了一些除了降维算法外的一些常用的特征选择的方法,这些代码都不难,但是每一种方法包含一些超参数需要自己调调。按照经验来看
过滤法速度快,但是粗糙,包装法和嵌入法更加精确,适合具体到算法去调整,但是计算量大,运行时间长。
数据量大的时候优先选择方差过滤和互信息去调整,其次再选择其他的特征选择方法。
当然了,要是有时间,嵌入法和包装法也是非常值得尝试的啦。
特征选择只是特征工程的第一步,高手一般还会创造和提取更高级别的特征。