Python数据分析之特征处理笔记三——特征预处理(特征选择)

书接上文,进行数据清洗过程后,我们得到了没有空值、异常值、错误值的数据,但想要用海量的数据来建立我们所需要的算法模型,仅仅是数据清洗的过程是不够的,因为有的数据类型是数值,有的是字符,怎样将不同类型的数据联系起来?以及在保证最大化信息量的前提下,怎样得到便于分析的数据?这就是特征预处理要做的工作。

一、理论基础

1. 基本概念

特征预处理是数据预处理过程的重要步骤,是对数据的一个的标准的处理,几乎所有的数据处理过程都会涉及该步骤。在进行特征预处理之前需要确定标注,即标签(label)。标注的选择主要与我们的目的相契合,比如要探究不同种族、不同地区的人们长寿是否与性别相关,那么性别就可以作为标注来表明我们的目的。

2. 一般步骤

2.1 特征选择

目标是寻找最优特征子集。特征选择能剔除与标注不相关(irrelevant)或冗余(redundant )的特征,从而达到减少特征个数,提高模型精确度,减少运行时间的目的。另一方面,选取出真正相关的特征简化模型,协助理解数据产生的过程。

之所以要考虑特征选择,是因为机器学习经常面临过拟合的问题。 过拟合的表现是模型参数太贴合训练集以及验证集数据,在训练集上效果很好而在测试集上表现不好。简言之模型的泛化能力差。过拟合的原因是模型对于训练集数据来说太复杂,要解决过拟合问题,一般考虑用特征选择的方法对数据进行降维。

特征选择的思想主要有三种:

  • 过滤思想:在n个特征中,对每个特征 x(i),分别计算 x(i) 相对于标注 y 的信息量 S(i) ,得到 n 个结果。然后将 n 个 S(i) 按照从大到小排序,输出前 k 个特征。显然,这样数据的复杂度大大降低。那么关键的问题就是使用什么样的方法来度量 S(i) ,我们的目标是选取与 y 关联最密切的一些 特征x(i)。
  • 包裹思想:基于留出法(hold-out),将特征全集X划分为两个互斥的集合,其中一个集合作为训练集S,另外一个作为测试集T,即X=S∪T,S∩T=0。在S上训练出模型后,用T来评估其测试误差,作为对泛化误差的评估。将训练集根据特征子集选择方法选出特征子集,对于每一个待选的特征子集,都在训练集上训练一遍模型,然后在测试集上根据误差大小选择出特征子集。需要先选定特定算法,通常选用普遍效果较好的算法, 例如Random Forest, SVM, kNN等等。
  • 嵌入思想:先使用某些机器学习的简单回归模型进行训练,得到各个特征的权值系数,根据系数从大到小选择特征(采用这个方法有比较大的风险,要慎重选择用哪个回归模型)。

2.1.1 过滤思想

 2.1.1.1 皮尔逊(pearson)相关系数

特征选择与提取最全总结之过滤法 - 腾讯云开发者社区-腾讯云 (tencent.com)

相关系数是衡量特征与标注相关性最直接简单的方法,取值范围在[-1,1],当相关系数接近或等于0时,说明该特征与标注的相关性很小或不相关。皮尔逊相关系数可以用来探究任何数据类型之间的相关性,即使有离散非二值的数据,经过定序处理后也可用相关系数的方法。

以下代码为相关系数的输出,若要遍历特征全集根据相关系数筛选特征,还需要通过函数或方程等方法遍历全集。

import pandas as pd
s1=pd.Series([0.1,0.2,1.1,2.4,1.3,0.3,0.5])
s2=pd.Series([0.5,0.4,1.2,2.5,1.1,0.7,0.1])
s1.corr(s2,method='pearson ') #输出s1、s2之间的相关系数

Pearson相关系数的一个明显缺陷是,它只对线性关系敏感。如果关系是非线性的,即便两个变量具有一一对应的关系,Pearson相关性也可能会接近 0 。 

  2.1.1.2 卡方检验

卡方检验用在属性相关性检验上,一般所探查的数据类型为类别性数据。卡方检验的本质是推测两组数据之间的差异,其检验的原假设是”两组数据是相互独立的”。卡方检验返回卡方值和P值两个统计量,其中卡方值很难界定有效的范围,而p值,我们一般使用0.01或0.05作为显著性水平,即p值判断的边界。卡方检验的具体介绍见:

卡方检验_百度百科 (baidu.com)

from sklearn.datasets import load_iris
from sklearn.feature_selection import SelectKBest #常用的特征选择的函数
from sklearn.feature_selection import chi2
iris = load_iris()
X, y = iris.data, iris.target  #iris数据集,y取值有0,1,2,为标签化数据
#选择K=2个最好的特征,返回选择特征后的数据
X_new = SelectKBest(chi2, k=2).fit_transform(X, y) #chi2为指定检验为卡方检验

先看X的前5行数据

X[0:5,:]

Python数据分析之特征处理笔记三——特征预处理(特征选择)_第1张图片

 y的前5行数据

y[:5]

 最后提取出的新特征的前5行,可以看到结果为提取X的最后两列特征。

Python数据分析之特征处理笔记三——特征预处理(特征选择)_第2张图片

  2.1.1.3 互信息(熵增益)和最大信息系数

互信息法是用来捕捉每个特征与标注之间的任意关系(包括线性和非线性关系)的过滤方法,适用于特征和标注都是离散二值的研究。互信息从字面上理解为相互包含的信息,互信息法不返回p值或F值类似的统计量,它返回“每个特征与标注之间的互信息量的估计”,这个估计量在[0,1]之间取值,为0则表示两个变量独立,为1则表示两个变量完全相关。互信息的计算公式为:

I (X, Y) = H(Y) - H(Y|X) = H(X) - H(X|Y) = \sum_ {x\in X }^{} \sum_{y\in Y }^{}p(x,y)log\tfrac{p(x,y)}{p(x)p(y)}

其中:H()表示求熵,例如:H(x) = -\sum_{}^{} p_{i}^{}log(p_{i}^{})

关于熵的定义在此不再赘述,不理解的话可去搜一下资料。

想把互信息直接用于特征选择其实不是太方便:

  1. 它不属于度量方式,也没有办法归一化,在不同数据及上的结果无法做比较
  2. 对于连续变量的计算不是很方便,通常变量需要先离散化,而互信息的结果对离散化的方式很敏感。

最大信息系数(MIC)克服了这两个问题。它首先寻找一种最优的离散化方式,然后把互信息取值转换成一种度量方式,取值区间在 [0,1] 。minepy提供了MIC功能。

简单来说,最大信息系数就是将两个变量在二维空间中进行离散化处理,然后将二维空间按照行列数规定分成一定数量的区间块,并计算这些区间块的互信息,这就是一种分割下的互信息值。接着计算按照其他分割方法下的区块的互信息值,最后在这些互信息中选出最大的一个互信息进行归一化处理后(除以log(最小互信息))得到的就是最大信息系数。python中有包可以直接计算MIC,这是另外一位博主的代码,仅供参考:

from minepy import MINE

m = MINE()
x = np.random.uniform(-1, 1, 10000) #创建10000个取值在-1到1之间的随机数
m.compute_score(x, x**2) #取x的平方
print(m.mic())


from sklearn.feature_selection import SelectKBest
#由于MINE的设计不是函数式的,定义mic方法将其为函数式的,返回一个二元组,二元组的第2项设置成固定的P值0.5
def mic(x, y):
    m = MINE()
    m.compute_score(x, y)
    return (m.mic(), 0.5)
# 选择K个最好的特征,返回特征选择后的数据
SelectKBest(lambda X, Y: array(map(lambda x:mic(x, Y), X.T)).T,k=2).fit_transform(iris.data, iris.target)

 2.1.1.4 方差选择法

过滤特征选择法还有一种方法不需要度量特征 x(i) 和标注Y的信息量。这种方法先要计算各个特征的方差,然后根据阈值,选择方差大于阈值的特征。这是因为方差越大,该特征所包含的信息量越多,对于标注有着更强的解释含义。

VarianceThreshold是特征选择的简单基线方法。可以删除方差超过某个阈值的所有特征。默认情况下,它会删除所有零差异特征,即所有样本中具有相同值的特征。代码如下:

from sklearn.feature_selection import VarianceThreshold
X = [[0, 0, 1], [0, 1, 0], [1, 0, 0], [0, 1, 1], [0, 1, 0], [0, 1, 1]] 
# 假设X是特征全集中的三个离散二值特征子集
sel = VarianceThreshold(threshold=(.8 * (1 - .8)))
# 方差选择法,返回值为特征选择后的数据
# 参数threshold为方差的阈值,因为二项分布的方差为p(1-p),则上面代码表示当方差超过(.8 * (1 - .8)时删除特征
print(sel.fit_transform(X))

返回结果为第二、第三列。

2.1.2 包裹思想

 2.1.2.1 向前搜索

基本思想为:最初时创建一个空的集合F,在n个特征子集选出一个特征子集加入到F中,这时通过交叉验证来判断错误率,接着继续往F集合中加入特征子集计算错误率,循环上述过程直至获得n个错误率或达到我们设定的阈值,最后挑选出最小错误率的特征子集。

 2.1.2.2 向后搜索

与向前搜索往F集合里增加特征相反的是,向后搜索是刚开始时F集合就包含了若干个特征子集,得出错误率后,每次删除一个特征子集,直到达到阈值或集合为空,然后得到最小错误率的特征集合。

 2.1.2.3 递归特征消除法(RFE)

递归特征消除(RFE)就是通过递归地考虑越来越小的特征集来选择特征。首先,对初始特征集训练估计器,通过coef_属性或feature_importances_属性获得每个特征的重要性。然后,从当前的特征集中删除最不重要的特征。也就是说通过不断地建立模型,每一次删除掉差的特征,直至遍历所有的特征。这种方法的效果很依赖于我们所选择的模型,以上面iris数据集为例:

from sklearn.feature_selection import RFE
from sklearn.linear_model import LogisticRegression

#递归特征消除法,返回特征选择后的数据
#参数estimator为基模型
#参数n_features_to_select为选择的特征个数
RFE(estimator=LogisticRegression(), n_features_to_select=2).fit_transform(iris.data, iris.target)

2.1.3 嵌入法

基本思想:建立简单的回归模型,常用的模型是基于树的预测模型(SelectFromModel),能够计算特征的重要程度。基于树的方法比较易于使用,因为他们对非线性关系的建模比较好,并且不需要太多的调试,但要注意过拟合问题。参考:打牛地的博客-CSDN博客_封装式特征选择

 2.1.3.1 带惩罚项的基模型

比如LR加入正则。通过L1正则项来选择特征:L1正则方法具有稀疏解的特性,因此天然具备特征选择的特性,但是要注意,L1没有选到的特征不代表不重要,原因是两个具有高相关性的特征可能只保留了一个,如果要确定哪个特征重要应再通过L2正则方法交叉检验。

from sklearn.svm import LinearSVC
from sklearn.datasets import load_iris
from sklearn.feature_selection import SelectFromModel
iris = load_iris()
X, y = iris.data, iris.target
X.shape #查看X的规格,显示为150行4列
(150, 4)
lsvc = LinearSVC(C=0.01, penalty="l1", dual=False).fit(X, y) 
#penalty:{‘l1’, ‘l2’}, default=’l2’,正则化方法,默认为L1
#loss:{‘hinge’, ‘squared_hinge’}, default=’squared_hinge’,即hinge的平方
#dual:bool, default=True,Prefer dual=False when n_samples > n_features. 令样本数大于特征数
#C:float, default=1.0,正则化的强度与 C 成反比.
#max_iter:int, default=1000,要运行的最大迭代次数。
model = SelectFromModel(lsvc, prefit=True)
X_new = model.transform(X)
X_new.shape
(150,3) #返回三个特征

 2.1.3.2 树模型(随机森林、决策树)

训练能够对特征打分的预选模型:RandomForest和Logistic Regression等都能对模型的特征打分,通过打分获得相关性后再训练最终模型。代码以博主的文章为例:

 from sklearn.ensemble import ExtraTreesClassifier
 from sklearn.datasets import load_iris
 from sklearn.feature_selection import SelectFromModel
 iris = load_iris()
 X, y = iris.data, iris.target
 X.shape
 (150, 4)
 clf = ExtraTreesClassifier()
 clf = clf.fit(X, y)
 clf.feature_importances_  # 返回每个特征的重要性
 array([ 0.04...,  0.05...,  0.4...,  0.4...])
 model = SelectFromModel(clf, prefit=True)
 X_new = model.transform(X)
 X_new.shape               
 (150, 2)

2.1.4 编码操作

接下来用三个思想来分析一个简单的数据集

import numpy as np
import pandas as pd 
import scipy.stats as ss
df=pd.DataFrame({'A':ss.norm.rvs(size=10),'B':ss.norm.rvs(size=10),'C':ss.norm.rvs(size=10),'D':np.random.randint(low=0,high=2,size=10)})
#创建一个df数据集,其中A、B、C都是符合正态分布大小为10的随机数,D为取值为0到1、大小为10的整数。

from sklearn.svm import SVR #SVR为常用的回归模型包、回归器
from sklearn.tree import DecisionTreeRegressor #DecisionTreeRegressor为决策树回归器

X=df.loc[:,['A','B','C']] #将A、B、C设置为特征
Y=df.loc[:,['D']] #将D设置为标注

from sklearn.feature_selection import SelectKBest,RFE,SelectFromModel
#上面SelectKBest为过滤思想用到的特征选择包,RFE为包裹思想中的递归特征消除法,SelectFromModel为嵌入思想的模型选择包

#过滤思想
skb=SelectKBest(k=2) #从特征中选出两个特征
skb.fit(X,Y) #拟合模型,输出指定函数
skb.transform(X) #转化选择的两个特征

#包裹思想
rfe=RFE(estimator=SVR(kernel='linear'),n_features_to_select=2,step=1) 
#estimator为指定回归器,linear为线性回归,n_features_to_select=2表示选择两个特征,step=1表示每次删除一个特征
rfe.fit_transform(X,Y) #转化出选择的两个特征和标注

#嵌入思想
sfm=SelectFromModel(estimator=DecisionTreeRegressor(),threshold=0.1)
#threshold=0.1 表示重要性低于0.1时就删除特征
sfm.fit_transform(X,Y)

以上就是特征选择的基本方法和简单的代码操作,想要学习复杂度较高的代码的小伙伴可以去Sklearn的官网看看。其他内容待下回分解......

部分资料和代码来源于以下参考文献:

特征预处理 - 知乎 (zhihu.com)

机器学习 特征选择(Feature Selection)方法汇总 - 知乎 (zhihu.com)

数据分析3——预处理理论(特征工程、数据清洗、特征预处理)_啧啧啧@的博客-CSDN博客

机器学习 特征选择(过滤法 封装法 嵌入法)_打牛地的博客-CSDN博客_封装式特征选择

华为大佬用159小时讲完的Python数据分析-数据挖掘教程,整整600集,零基础快速入门 手把手教学,学完即可就业_哔哩哔哩_bilibili

你可能感兴趣的:(数据分析,机器学习,python,数据分析,机器学习,随机森林,决策树)