特征选择

# 特征选择 特征选择(排序)对于数据科学家、机器学习从业者来说非常重要。好的特征选择能够提升模型的性能,更能帮助我们理解数据的特点、底层结构,这对进一步改善模型、算法都有着重要作用。 但是拿到数据集,一个特征选择方法,往往很难同时完成这两个目的。通常情况下,我们经常不管三七二十一,选择一种自己最熟悉或者最方便的特征选择方法(往往目的是降维,而忽略了对特征和数据理解的目的), 但是真的好使吗?只能说具体问题具体分析,也许会暴力出奇迹呢。 **特征选择主要有两个功能:** - 减少特征数量、降维,使模型泛化能力更强,减少过拟合 - 增强对特征和特征值之间的理解 **通常来说,从两个方面考虑来选择特征:** - 特征是否发散:如果一个类别特征不发散,也就是说样本在这个特征上基本上没有差异,这个特征对于样本的区分并没有什么用。 - 特征与目标的相关性:这点比较显见,与目标相关性高的特征,应当优选选择。 **根据特征选择的形式又可以将特征选择方法分为3种:** - Filter:过滤法,按照发散性或者相关性对各个特征进行评分,设定阈值或者待选择阈值的个数,选择特征。 - Wrapper:包装法,根据目标函数(通常是预测效果评分),每次选择若干特征,或者排除若干特征。 - Embedded:嵌入法,先使用某些机器学习的算法和模型进行训练,得到各个特征的权值系数,根据系数从大到小选择特征。类似于Filter方法,但是是通过训练来确定特征的优劣。 首先,导入之前处理备份的数据,看一下构造的特征: ![19.png](https://cos.easydoc.net/17082933/files/kf9fi1bp.png) ::: hljs-center ## 过滤式 ::: **主要思想**: 对每一维特征“打分”,即给每一维的特征赋予权重,这样的权重就代表着该特征的重要性,然后依据权重排序。先进行特征选择,然后去训练学习器,所以特征选择的过程与学习器无关。相当于先对特征进行过滤操作,然后用特征子集来训练分类器。 **主要方法:** - 移除低种类少的特征; - 相关系数排序,分别计算每个特征与输出值之间的相关系数,设定一个阈值,选择相关系数大于阈值的部分特征; - 利用假设检验得到特征与输出值之间的相关性,方法有比如卡方检验、t检验、F检验等。 - 互信息,利用互信息从信息熵的角度分析相关性。 这里,本文为大家提供一些有价值的小tricks: - 对于数值型特征,方差很小的特征可以不要,因为太小没有什么区分度,提供不了太多的信息,对于分类特征,也是同理,取值个数高度偏斜的那种可以先去掉。 - 根据与目标的相关性等选出比较相关的特征(当然有时候根据字段含义也可以选) - 卡方检验一般是检查离散变量与离散变量的相关性,当然离散变量的相关性信息增益和信息增益比也是不错的选择(可以通过决策树模型来评估来看),person系数一般是查看连续变量与连续变量的线性相关关系。 **去掉取值变化小的特征** 这应该是最简单的特征选择方法了:假设某特征的特征值只有0和1,并且在所有输入样本中,95%的实例的该特征取值都是1,那就可以认为这个特征作用不大。如果100%都是1,那这个特征就没意义了。 当特征值都是离散型变量的时候这种方法才能用,如果是连续型变量,就需要将连续变量离散化之后才能用,而且实际当中,一般不太会有95%以上都取某个值的特征存在,所以这种方法虽然简单但是不太好用。 可以把它作为特征选择的预处理,先去掉那些取值变化小的特征,然后再从接下来提到的的特征选择方法中选择合适的进行进一步的特征选择。例如,我们前面的seller和offerType特征。 ```python # 对方差的大小排序 select_data.std().sort_values() # select_data是final_data去掉了独热的那些特征 ``` 根据这个,可以把方差非常小的特征作为备选的删除特征(备选,**可别先盲目删除**) **单变量特征选择** 单变量特征选择能够对每一个特征进行测试,衡量该特征和响应变量之间的关系,根据得分扔掉不好的特征。对于**分类问题(y离散)可以采用卡方检验**等方式对特征进行测试。 这种方法比较简单,易于运行,易于理解,通常对于理解数据有较好的效果(但对特征优化、提高泛化能力来说不一定有效);这种方法有许多改进的版本、变种。 **下面重点介绍一下[Pearson相关系数](https://www.jianshu.com/p/9dec47bac5b9)**(适合y连续),皮尔森相关系数是一种最简单的,比较常用的方式。能帮助理解特征和响应变量之间关系的方法,该方法衡量的是变量之间的线性相关性,结果的取值区间为[-1,1],-1表示完全的负相关(这个变量下降,那个就会上升),+1表示完全的正相关,0表示没有线性相关。 Pearson Correlation速度快、易于计算,经常在拿到数据(经过清洗和特征提取之后的)之后第一时间就执行。Scipy的pearsonr方法能够同时计算相关系数和p-value, 当然pandas的corr也可以计算。 直接根据pearson系数画出图像: ```python corr = select_data.corr('pearson') # .corr('spearman') plt.figure(figsize=(25, 15)) corr['price'].sort_values(ascending=False)[1:].plot(kind='bar') plt.tight_layout() ``` 结果如下: ![20.png](https://cos.easydoc.net/17082933/files/kf9figtg.png) 当然,这个数据用pearson系数可能不是那么合理,可以使用spearman系数,这个被认为是排列后的变量的pearson的相关系数, 具体的可以看(Pearson)皮尔逊相关系数和spearman相关系数, 这里只整理两者的区别和使用场景, 区别如下: - **连续数据**,**正态分布,线性关系**,用pearson相关系数是最恰当,当然用spearman相关系数也可以,效率没有pearson相关系数高。 - 上述任一条件不满足,就用spearman相关系数,不能用pearson相关系数。 - 两个定序测量数据(顺序变量)之间也用spearman相关系数,不能用pearson相关系数。 - Pearson相关系数的一个明显缺陷是,作为特征排序机制,他只对线性关系敏感。如果关系是非线性的,即便两个变量具有一一对应的关系,Pearson相关性也可能会接近0。 当然还可以画出热力图来,这个不陌生了吧, 这个的目的是可以看变量之间的关系, 相关性大的,可以考虑保留其中一个: ```python # 下面看一下互相之间的关系 f, ax = plt.subplots(figsize=(50, 30)) sns.heatmap(corr, annot=True) ``` 结果如下: ![1.png](https://cos.easydoc.net/17082933/files/kf9fj3si) 从上面两个步骤中,就可以发现一些结论: - 根据与price的线性相关关系来看的话,我们可以考虑正负相关0.6以上的特征, v_std, v_12, v_0, v_8, estivalue_price_average, estivalue_price_median, estivalue_price_std, kil_bin, kilmoeter, estivalue_count, used_time, estivalue, v_3 - 某些变量之间有很强的的关联性,比如v_mean和v_sum,这俩的相关性是1,所以可以删掉其中一个。 当然,依然是备选删除选项和备选保留选项(这些都先别做), 因为我们有时候不能盲目,就比如上面的相关性,明明知道pearson的缺陷是无法捕捉非线性相关,所以得出的这个结论也是片面的结论。这些都是备选,先做个心中有数,后面再用一些别的方式看看再说(如果现在就删除了,后面的方法就不好判断了) ::: hljs-center ## 包裹式 ::: 单变量特征选择方法独立的衡量每个特征与响应变量之间的关系,另一种主流的特征选择方法是基于机器学习模型的方法。有些机器学习方法本身就具有对特征进行打分的机制,或者很容易将其运用到特征选择任务中,例如回归模型,SVM,决策树,随机森林等等。 **主要思想:** 包裹式从初始特征集合中**不断**的选择**特征子集**,训练学习器,根据学习器的性能来**对子集进行评价**,直到选择出**最佳的子集**。包裹式特征选择直接针对给定学习器进行优化。 **主要方法:** 递归特征消除算法, 基于机器学习模型的特征排序 ![image.png](https://cos.easydoc.net/17082933/files/khjyvpf4.png) 这可以使用前面提到的各种减小搜索空间的尝试。 其中最经典的是使用启发式搜索(Heuristic Search), 而概括的来说主要分为两大类: - Forward Selection: 挑出一些属性, 然后慢慢增大挑出的集合。 - Backward Elimination: 删除一些属性,然后慢慢减小保留的集合。 **优缺点:** 优点:从最终学习器的性能来看,包裹式比过滤式更好; 缺点:由于特征选择过程中需要多次训练学习器,因此包裹式特征选择的计算开销通常比过滤式特征选择要大得多。 **代码实现** ```python from sklearn.feature_selection import RFE from sklearn.linear_model import LinearRegression boston = load_boston() X = boston["data"] Y = boston["target"] names = boston["feature_names"] #use linear regression as the model lr = LinearRegression() #rank all features, i.e continue the elimination until the last one rfe = RFE(lr, n_features_to_select=1) rfe.fit(X,Y) print("Features sorted by their rank:") print(sorted(zip(map(lambda x: round(x, 4), rfe.ranking_), names))) 结果输出 Features sorted by their rank: [(1, 'NOX'), (2, 'RM'), (3, 'CHAS'), (4, 'PTRATIO'), (5, 'DIS'), (6, 'LSTAT'), (7, 'RAD'), (8, 'CRIM'), (9, 'INDUS'), (10, 'ZN'), (11, 'TAX'), (12, 'B'), (13, 'AGE')] ``` ::: hljs-center ## 嵌入式 ::: 在过滤式和包裹式特征选择方法中,特征选择过程与学习器训练过程有明显的分别。而嵌入式特征选择在学习器 训练过程中自动地进行特征选择。嵌入式选择最常用的是L1正则化与L2正则化。在对线性回归模型加入两种正则化方法后,他们分别变成了岭回归与Lasso回归。 **主要思想:** 在模型既定的情况下学习出对提高模型准确性最好的特征。也就是在确定模型的过程中,挑选出那些对模型的训练有重要意义的特征。 **主要方法:** 简单易学的机器学习算法–岭回归(Ridge Regression),就是线性回归过程加入了L2正则项。 L1正则化有助于生成一个稀疏权值矩阵,进而可以用于特征选择 L2正则化在拟合过程中通常都倾向于让权值尽可能小,最后构造一个所有参数都比较小的模型。因为一般认为参 数值小的模型比较简单,能适应不同的数据集,也在一定程度上避免了过拟合现象。可以设想一下对于一个线性 回归方程,若参数很大,那么只要数据偏移一点点,就会对结果造成很大的影响;但如果参数足够小,数据偏移 得多一点也不会对结果造成什么影响,专业一点的说法是『抗扰动能力强』 这里简单介绍一下怎么使用,其实和上面机器学习模型的使用方法一样, 所以有时候这些方法没有必要严格的区分开: ```python from sklearn.linear_model import LinearRegression, Ridge,Lasso models = [LinearRegression(), Ridge(), Lasso()] result = dict() for model in models: model_name = str(model).split('(')[0] scores = cross_val_score(model, X=train_X, y=train_y, verbose=0, cv=5, scoring='r2') result[model_name] = scores ``` --- ::: hljs-center ## 基于树模型的方法 ::: 1. **第一种方法基本思路如下**: 如果一个特征被选为分割点的次数越多,那么这个特征的重要性就越强。这个理念可以被推广到集成算法中,只要将每棵树的特征重要性进行简单的平均即可。 2. **第二种方式是训练好模型之后,用Out of Bag(或称Test)数据进行特征重要性的量化计算**。具体来说,先用训练好的模型对OOB数据进行打分,计算出AUC或其他业务定义的评估指标;接着对OOB数据中的每个特征: (1)随机shuffle当前特征的取值; (2)重新对当前数据进行打分,计算评估指标; (3)计算指标变化率 按照上面方式,对每个特征都会得到一个变化率,最后按照变化率排序来量化特征重要性。 具体原理见:[[机器学习] 树模型特征重要性原理总结](https://blog.csdn.net/zwqjoy/article/details/97259891) 这里整理基于学习模型的特征排序方法,这种方法的思路是直接使用你要用的机器学习算法,针对每个单独的特征和响应变量建立预测模型。其实Pearson相关系数等价于线性回归里的标准化回归系数。 假如某个特征和响应变量之间的关系是非线性的,可以用基于树的方法(决策树、随机森林)、或者扩展的线性模型等。基于树的方法比较易于使用,因为他们对非线性关系的建模比较好,并且不需要太多的调试。但要注意过拟合问题,因此树的深度最好不要太大,再就是运用交叉验证。 我们可以用随机森林来跑一下,看看随机森林比较喜欢特征: ```python from sklearn.model_selection import cross_val_score, ShuffleSplit from sklearn.ensemble import RandomForestRegressor from sklearn.model_selection import KFold X = select_data.iloc[:, :-1] Y = select_data['price'] names = select_data.columns rf = RandomForestRegressor(n_estimators=20, max_depth=4) kfold = KFold(n_splits=5, shuffle=True, random_state=7) scores = [] for column in X.columns: print(column) tempx = X[column].values.reshape(-1, 1) score = cross_val_score(rf, tempx, Y, scoring="r2", cv=kfold) scores.append((round(np.mean(score), 3), column)) print(sorted(scores, reverse=True)) ``` 这里对喜欢的特征排序并打分,结果如下: ![2.png](https://cos.easydoc.net/17082933/files/kf9fjjex.png) 这里就可以看出随机森林有用的特征排序,如果我们后面选择随机森林作为模型,就可以根据这个特征重要度选择特征。当然,如果你是xgboost,xgboost里面有个画特征重要性的函数,可以这样做: ```python # 下面再用xgboost跑一下 from xgboost import XGBRegressor from xgboost import plot_importance xgb = XGBRegressor() xgb.fit(X, Y) plt.figure(figsize=(20, 10)) plot_importance(xgb) plt.show() ``` 这样,直接把xgboost感兴趣的特征画出来: ![3.png](https://cos.easydoc.net/17082933/files/kf9fjvfa.png) 最后,我们把上面的这两种方式封装起来, 还可以画出边际效应: ```python from mlxtend.feature_selection import SequentialFeatureSelector as SFS from sklearn.linear_model import LinearRegression from sklearn.ensemble import RandomForestRegressor # sfs = SFS(LinearRegression(), k_features=20, forward=True, floating=False, scoring='r2', cv=0) sfs = SFS(RandomForestRegressor(n_estimators=10, max_depth=4), k_features=20, forward=True, floating=False, scoring='r2', cv=0) X = select_data.iloc[:, :-1] Y = select_data['price'] sfs.fit(X, Y) sfs.k_feature_names_ # 随机森林放这里跑太慢了,所以中断了 ``` 结果如下: ![4.png](https://cos.easydoc.net/17082933/files/kf9fk7uz.png) 画出边际效应: ```python from mlxtend.plotting import plot_sequential_feature_selection as plot_sfs fig1 = plot_sfs(sfs.get_metric_dict(), kind='std_dev') plt.grid() plt.show() ``` 这个也是看选出的特征的重要性: ![5.png](https://cos.easydoc.net/17082933/files/kf9fkish.png) 这样,根据我们使用的模型,我们可以对特征进行一个选择,综合上面的这几种方式,我们就可以把保留和删除的特征给选出来了,该删除的可以删除了。 如果真的这样尝试一下,就会发现保留的特征里面, v_std, v_3, used_time, power, kilometer, estivalue等这些特征都在,虽然我们不知道v系列特征的含义,但是汽车使用时间,发动机功率,行驶公里, 汽车使用时间的分箱特征其实对price的影响都是比较大的。 ::: hljs-center ## Permutation importance ::: **原理:** 这个原理真的很简单:依次打乱数据集中每一个特征数值的顺序,其实就是做shuffle,然后观察模型的效果,**下降的多的**说明这个特征对模型比较重要。 **使用示例** 下面示例中,参数model表示已经训练好的模型(支持sklearn中全部带有coef_ 和feature_importances_ 的模型,部分pytorch和keras训练的深度学习模型) ```python import eli5 from eli5.sklearn import PermutationImportance perm = PermutationImportance(model, random_state=20).fit(test_x, test_y) eli5.show_weights(perm, feature_names=test_x.columns.tolist()) ``` **上面代码的输出见下图:** ![image.png](https://cos.easydoc.net/17082933/files/khk4pgff.png) **备注**:该方法与树模型法的OOB法类似 --- ## 以上只是简单介绍,更多具体的代码示例、各方法优缺点等见:[特征工程———特征选择的原理和实现](https://7125messi.github.io/post/%E7%89%B9%E5%BE%81%E5%B7%A5%E7%A8%8B%E7%89%B9%E5%BE%81%E9%80%89%E6%8B%A9%E7%9A%84%E5%8E%9F%E7%90%86%E5%92%8C%E5%AE%9E%E7%8E%B0/)