sklearn机器学习笔记:数据预处理与特征工程

这是一篇纯学习笔记,感觉回到了开发,难道要成为调包侠了吗?

数据挖掘5大流程

  1. 获取数据

  2. 数据预处理
    数据预处理是从数据中检测,纠正或删除损坏,不准确或不适用于模型的记录的过程可能面对的问题有:数据类型不同,比如有的是文字,有的是数字,有的含时间序列,有的连续,有的间断。也可能,数据的质量不行,有噪声,有异常,有缺失,数据出错,量纲不一,有重复,数据是偏态,数据量太大或太小
    数据预处理的目的:让数据适应模型,匹配模型的需求

  3. 特征工程:
    特征工程是将原始数据转换为更能代表预测模型的潜在问题的特征的过程,可以通过挑选最相关的特征,提取特征以及创造特征来实现。其中创造特征又经常以降维算法的方式实现。
    可能面对的问题有:特征之间有相关性,特征和标签无关,特征太多或太小,或者干脆就无法表现出应有的数据现象或无法展示数据的真实面貌
    特征工程的目的:1) 降低计算成本,2) 提升模型上限

  4. 建模,测试模型并预测出结果

  5. 上线,验证模型效果

数据预处理与特征工程的重要性不言而喻,数据不给力,再高级的算法都没有用,接下来就边学习数据预处理与特征工程边使用sklearn的库。

先附上sklearn官网

其中数据预处理预处理和特征选择模块已经标出

  • 模块preprocessing:几乎包含数据预处理的所有内容

  • 模块Impute:填补缺失值专用

  • 模块feature_selection:包含特征选择的各种方法的实践

接下来会按照顺序来写使用sklearn来进行数据预处理和特征工程

数据预处理部分涉及到内容如下

  1. 数据无量纲化

  2. 缺失值

  3. 处理分类型特征:编码与哑变量

  4. 处理连续型特征:二值化与分段

特征工程部分涉及到如下内容

  1. Filter过滤法

  2. Embeded嵌入法

  3. Wrapper包装法

一、数据预处理

1.1、无量纲化

无量纲化:在机器学习算法实践中,我们往往有着将不同规格的数据转换到同一规格,或不同分布的数据转换到某个特定分布的需求,这种需求统称为将数据“无量纲化”。

数据归一化:当数据(x)按照最小值中心化后,再按极差(最大值 - 最小值)缩放,数据移动了最小值个单位,并且会被收敛到[0,1]之间,而这个过程,就叫做数据归一化。公式如下:

x^*=\frac{x-min(x)}{max(x)-min(x)}x∗=max(x)−min(x)x−min(x)

在sklearn中使用preprocessing.MinMaxScaler来实现这个功能,它有一个重要参数,feature_range,用来控制数据压缩的范围,默认是 [0,1]

from sklearn.preprocessing import MinMaxScaler
import pandas as pd
data = [[-1, 2], [-0.5, 6], [0, 10], [1, 18]]
scaler = MinMaxScaler()
#特征很多的时候使用MinMaxScaler().partial_fit(data)来代替fit否则会报错
scaler.fit(data)  #在这里本质是生成min(x)和max(x)
res = scaler.transform(data)  #通过接口导出结果
print(res)
itf = scaler.inverse_transform(res)#返回初始值
print(itf)12345678910

输出

[[0.   0.  ]
 [0.25 0.25]
 [0.5  0.5 ]
 [1.   1.  ]]
[[-1.   2. ]
 [-0.5  6. ]
 [ 0.  10. ]
 [ 1.  18. ]]12345678

也可以直接使用fit_transform来进行拟合导出

res = MinMaxScaler().fit_transform(data)1

使用feature_range参数来指定范围

#使用参数feature_range来指定压缩的范围,默认是[0,1]
data = [[-1, 2], [-0.5, 6], [0, 10], [1, 18]]
res = MinMaxScaler(feature_range=[5,10]).fit_transform(data)
res1234

输出

[[ 5.    5.  ]
 [ 6.25  6.25]
 [ 7.5   7.5 ]
 [10.   10.  ]]1234

如果自己动手去实习这个接口的功能其实也很简单

#自己动手使用numpy来实现归一化与复原功能
import numpy as np
num = np.array(data)
num_nor = (num-num.min(axis=0))/(num.max(axis=0)-num.min(axis=0))1234

来看输出,结果是一样的

array([[0.  , 0.  ],
       [0.25, 0.25],
       [0.5 , 0.5 ],
       [1.  , 1.  ]])1234

同样可以实习复原的功能

ori_num = num_nor*(num.max(axis=0)-num.min(axis=0))+num.min(axis=0)1

标准化:当数据(x)按均值(μ)中心化后,再按标准差(σ)缩放,数据就会服从为均值为0,方差为1的正态分布(即标准正态分布),而这个过程,就叫做数据标准化。公式如下:

x^*=\frac{x-\mu}{\sigma}x∗=σx−μ

在sklearn中使用preprocessing.MinMaxScaler来实现这个功能StandardScaler来实现这个功能,拟合(fit)后可以使用.mean_和.var_两个参数来查看各个特征(列属性)的均值和方差

#标准化
from sklearn.preprocessing import StandardScaler
data = [[-1, 2], [-0.5, 6], [0, 10], [1, 18]]
scaler = StandardScaler()
scaler.fit(data) #本质是生成均值和方差
scaler.mean_,scaler.var_123456

输出:

(array([-0.125,  9.   ]), array([ 0.546875, 35.      ]))1

然后来看一下导出的结果的均值和方差是不是分别为0和1

X = scaler.transform(data)
X.mean(axis=0),X.var(axis=0)12

输出,确实两列的均值和方差都分别是0和1了

(array([0., 0.]), array([1., 1.]))1

上面是分别使用了fit和transform,对于具有这种方法的类基本都是可以直接使用fit_transform()来一步写完,后面便不做复述,默认都是一样。

那么对于上面说到的归一化和标准化使用哪个呢?
大多数机器学习算法中,会选择StandardScaler来进行特征缩放,因为MinMaxScaler对异常值非常敏感。在PCA,聚类,逻辑回归,支持向量机,神经网络这些算法中,StandardScaler往往是最好的选择

MinMaxScaler在不涉及距离度量、梯度、协方差计算以及数据需要被压缩到特定区间时使用广泛,比如数字图像处理中量化像素强度时,都会使用MinMaxScaler将数据压缩于[0,1]区间之中

当然还有其他的关于归一化和标准化的类,这里就不多说了需要的时候查官网文档即可。

1.2、填补缺失值

机器学习和数据挖掘中所使用的数据,永远不可能是完美的。很多特征,对于分析和建模来说意义非凡,但对于实际收集数据的人却不是如此,因此数据挖掘之中,常常会有重要的字段缺失值很多,但又不能舍弃字段的情况。因此,数据预处理中非常重要的一项就是处理缺失值。sklearn中使用impute.SimpleCompute来进行缺失值填补。

下面使用kaggle上面泰坦尼克号生存者预测的数据集来进行缺失值填补,但是数据是做过一些处理的,无须在意。首先读取数据,并且查看

其中Age这个特征缺了200多个,Embarked特征也缺了两个,对于前者我们补上该特征的均值,而对于后者就直接删除掉好了。

#填补缺失值
from sklearn.impute import SimpleImputer
#SimpleImputer中输入的至少是二维矩阵  所以需要将Age这列转换为二维的
age = data.loc[:,"Age"].values.reshape(-1,1)
simple = SimpleImputer(missing_values = np.nan,strategy="mean")
data.loc[:,"Age"]=simple.fit_transform(age)
data.info()1234567

输出

<class 'pandas.core.frame.DataFrame'>
Int64Index: 891 entries, 0 to 890
Data columns (total 4 columns):
Age         891 non-null float64
Sex         891 non-null object
Embarked    889 non-null object
Survived    891 non-null object
dtypes: float64(1), object(3)
memory usage: 34.8+ KB123456789

显然,Age这列的缺失值都被填上了

如果不使用SimpleImpute的话,只使用 numpy和pandas来完成缺失值的填补也是完全OK的

#使用np和pd进行缺失值的填补更加简单
data = pd.read_csv("D:/Narrativedata.csv",index_col=0)
data.loc[:,"Age"] = data.loc[:,"Age"].fillna(data.loc[:,"Age"].mean())
data.dropna(axis=0,inplace=True)  #True表示覆盖原数据,不需要返回新的副本
data.info()12345

1.3、处理分类型特征:编码与哑变量

在机器学习中,大多数算法,譬如逻辑回归,支持向量机SVM,k近邻算法等都只能够处理数值型数据,不能处理文字,在sklearn当中,除了专用来处理文字的算法,其他算法在fit的时候全部要求输入数组或矩阵,也不能够导入文字型数据(其实手写决策树和普斯贝叶斯可以处理文字,但是sklearn中规定必须导入数值型)。

然而在现实中,许多标签和特征在数据收集完毕的时候,都不是以数字来表现的。比如说,学历的取值可以是[“小学”,“初中”,“高中”,“大学”],付费方式可能包含[“支付宝”,“现金”,“微信”]等等。在这种情况下,为了让数据适应算法和库,我们必须将数据进行编码,即是说,将文字型数据转换为数值型。

preprocessing.LabelEncoder:用于标签,将分类转换为分类数值 一般就是一维的

#编码与哑变量
#将分类转换为分类数值   LabelEncoder标签专用,所以不需要是矩阵
from sklearn.preprocessing import LabelEncoder
y = data.iloc[:,-1] #想看y有多少种使用set(y)即可  有三种,另外一个是Unknow
enc = LabelEncoder()
label = enc.fit_transform(y)
data.iloc[:,-1]=label
enc.classes_  #查看有多少种类别12345678

上面使用了classes_属性来查看原来的标签y中有多少种类别,输出如下

array(['No', 'Unknown', 'Yes'], dtype=object)1

来看看结果吧,‘No’, ‘Unknown’, 'Yes’分别转为了数值0,1,2

data.head(10)1

以上都是为了展示方便,实际上当然可以一步到位的

data.iloc[:,-1]=LabelEncoder().fit_transform(data.iloc[:,-1])1

前面说的是进行标签类别转换,下面说的是特征矩阵的转换,即将分类特征转换为数值类型,注意到Sex和Embarked两个特征都不是数值类型,需要进行转换

preprocessing.OrdinalEncoder :特征专用,要求至少为二维矩阵

#将分类特征转换为分类数值,OrdinalEncoder 特征专用,要求为矩阵
from sklearn.preprocessing import OrdinalEncoder
data_ = data.copy()
fea_mat=data_.iloc[:,1:-1]
ordinal = OrdinalEncoder()
ordinal.fit(fea_mat)
ordinal.categories_    #和标签专用的classes一样的效果1234567

输出

[array(['female', 'male'], dtype=object), array(['C', 'Q', 'S'], dtype=object)]1

最后进行转换吧

data_.iloc[:,1:-1] = ordinal.transform(fea_mat)
data_.head()12

如果一步到位就是

#一步到位
data_ = data.copy()
data_.iloc[:,1:-1] = OrdinalEncoder().fit_transform(data_.iloc[:,1:-1])
data_.head(10)1234

独热编码:创建哑变量,preprocessing.OneHotEncoder

虽然上面已经将文字类型转换成了数值类型,但是考虑下舱门这特征Embarked(S,C,Q),表示成了[0,1,2]这样合适吗?这三个数字在算法看来,是连续且可以计算的,这三个数字相互不等,有大小,并且有着可以相加相乘的联系。所以算法会把舱门,学历这样的分类特征,都误会成是体重这样的分类特征。这是说,我们把分类转换成数字的时候,忽略了数字中自带的数学性质,所以给算法传达了一些不准确的信息,而这会影响我们的建模。

类别OrdinalEncoder可以用来处理有序变量,但对于名义变量,我们只有使用哑变量的方式来处理,才能够尽量向算法传达最准确的信息:

#独热编码  创建哑变量
from sklearn.preprocessing import OneHotEncoder
data_ = data.copy()
encoder=OneHotEncoder()
encoder.fit(data_.iloc[:,1:-1])
res=encoder.transform(data_.iloc[:,1:-1]).toarray()#这里要转换成数组,否则得到的是一个对象
res1234567

查看输出吧

array([[0., 1., 0., 0., 1.],
       [1., 0., 1., 0., 0.],
       [1., 0., 0., 0., 1.],
       ...,
       [1., 0., 0., 0., 1.],
       [0., 1., 1., 0., 0.],
       [0., 1., 0., 1., 0.]])1234567

这东西鬼能看得懂,这时候使用以下OneHotEncoder的get_feature_names()属性来查看对应的类型

encoder.get_feature_names()#查看类别1

输出

array(['x0_female', 'x0_male', 'x1_C', 'x1_Q', 'x1_S'], dtype=object)1

所以数组的第一行0,1,0,0,1表示的是 男性和S号舱门

#进行拼接
ans = pd.concat([data_,pd.DataFrame(res)],axis=1)
#删除多余的,重命名
ans.drop(["Sex","Embarked"],axis=1,inplace=True)
ans.columns=["Age","Survived","female","male","C","Q","S"]
ans.head()123456

查看处理的最终效果

1.4、处理连续型特征:二值化与分段

二值化使用的类是 sklearn.preprocessing.Binarizer

根据阈值将数据二值化(将特征值设置为0或1),用于处理连续型变量。大于阈值的值映射为1,而小于或等于阈值的值映射为0。默认阈值为0时,特征中所有的正值都映射到1。二值化是对文本计数数据的常见操作,分析人员可以决定仅考虑某种现象的存在与否。

#处理连续型特征:二值化 至少是二维矩阵
from sklearn.preprocessing import Binarizer
data2 = data.copy()
binarizer = Binarizer(threshold=30)
#年龄中大于30的设置为1,小于30的设置为0
trans = binarizer.fit_transform(data2.loc[:,"Age"].values.reshape(-1,1))
trans[:5,:]   #取前五行看看1234567

输出前五行

array([[0.],
       [1.],
       [0.],
       [1.],
       [1.]])12345

分段使用的类 preprocessing.KBinsDiscretizer,相对麻烦点,给出下面的参数列表

#处理连续型特征:分段 至少是二维矩阵
from sklearn.preprocessing import KBinsDiscretizer
X=data.loc[:,"Age"].values.reshape(-1,1)
kbin = KBinsDiscretizer(n_bins=3,encode="onehot")
new_X = kbin.fit_transform(X).toarray()
new_X123456

查看年龄特征转换后的样子,是一个稀疏矩阵

array([[1., 0., 0.],
       [0., 0., 1.],
       [0., 1., 0.],
       ...,
       [0., 1., 0.],
       [0., 1., 0.],
       [0., 0., 1.]])1234567

二、 特征过程 feature_selection

当数据预处理完成后,我们就要开始进行特征工程了。主要包含以下几个方面

一定要抓住给你提供数据的人,尤其是理解业务和数据含义的人,跟他们聊一段时间。技术能够让模型起飞,前提是你和业务人员一样理解数据。所以特征选择的第一步,其实是根据我们的目标,用业务常识来选择特征。来看泰坦尼克号生产者预测的完整数据

票号,登船的舱门,乘客编号明显是无关特征,可以直接删除。姓名,舱位等级,船舱编号,也基本可以判断是相关性比较低的特征。性别,年龄,船上的亲人数量,这些应该是相关性比较高的特征。

所以,特征过程的第一步是理解业务,那如果遇见极端情况,我们无法依赖对业务的理解来选择特征,该怎么办呢?我们有四种方法可以用来选择特征:过滤法,嵌入法,包装法,和降维算法

接下来使用Kaggle上的辨别手写数字的数据集来进行特征选择的探索

2.1、Filter过滤法

首先读取数来大概看一下

data = pd.read_csv("D:/digit recognizor.csv")
x = data.iloc[:,1:]   #特征
y = data.iloc[:,0]   #标签
x.shape1234

输出

(42000, 784)1

发现这个数据的特征784个,还是挺多的

方差过滤: feature_selection.VarianceThreshold

这是通过特征本身的方差来筛选特征的类。比如一个特征本身的方差很小,就表示样本在这个特征上基本没有差异,可能特征中的大多数值都一样,甚至整个特征的取值都相同,那这个特征对于样本区分没有什么作用。

所以无论接下来的特征工程要做什么,都要优先消除方差为0的特征。VarianceThreshold有重要参数threshold,表示方差的阈值,表示舍弃所有方差小于threshold的特征,不填默认为0,即删除所有的记录都相同的特征。

from sklearn.feature_selection import VarianceThreshold
selector = VarianceThreshold()
x_var = selector.fit_transform(x)  #得到除去部分方差为0后的新的特征矩阵
x_var.shape1234

输出:(42000, 708),所以有76个特征方差为0,均剔除掉了。但是特征还是有点多,需要进一步去处理。但是在这之前先使用随机森林来看模型效果

#测试效果  使用随机森林,时间很快,使用KNN的话评分会高,但是时间是2小时
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import cross_val_score
x = data.iloc[:,1:]
y = data.iloc[:,0]

rfc = RandomForestClassifier(n_estimators=10,random_state=20)
cross_val_score(rfc,x,y,cv=5).mean()12345678

输出:0.9395709138866376,那么我们使用方差的中位数来作为阈值再来看模型的效果

x_var = VarianceThreshold(threshold=np.median(x.var().values)).fit_transform(x)
rfc = RandomForestClassifier(n_estimators=10,random_state=20)
cross_val_score(rfc,x_var,y,cv=5).mean()  #输出0.9396670106788723123

发现时间几乎不变,但是都十几秒很快,评分有些许提高 这是因为无论过滤法如何降低特征的数量,随机森林也只会选取固定数量的特征来建模。但是使用KNN时间是2小时,为什么这么慢?因为KNN需要遍历所有特征,所以使用方差过滤后的特征来作为KNN的输入,此时能很大程度的减少时间,对于决策树也是一样(虽然sklearn的决策树也是选取部分特征,但是相比随机森林还是选了很大一部分的特征了)
所以过滤法的主要对象是:需要遍历特征或升维的算法,这样能降低计算成本。

对受影响的算法来说,我们可以将方差过滤的影响总结如下:

#调整下树的数目,结果会不会更好呢   上升太多了呀  那就不需要用KNN了
x_var = VarianceThreshold(threshold=np.median(x.var().values)).fit_transform(x)
rfc = RandomForestClassifier(n_estimators=100,random_state=20)
cross_val_score(rfc,x_var,y,cv=5).mean() #输出0.96404794744914771234

相关性过滤: 卡方过滤

卡方过滤是专门针对离散型标签(即分类问题)的相关性过滤。卡方检验类feature_selection.chi2计算每个非负特征和标签之间的卡方统计量,并依照卡方统计量由高到低为特征排名。再结合feature_selection.SelectKBest这个可以输入”评分标准“的类来选出前K个分数最高的特征的类,我们可以借此除去最可能独立于标签,与我们分类目的无关的特征。

在这里,我们使用threshold=方差中位数时完成的方差过滤的数据来做卡方检验(如果方差过滤后模型的表现反而降低了,那我们就不会使用方差过滤后的数据,而是使用原数据):

#相关性检验   卡方检验  用于分类
from sklearn.feature_selection import SelectKBest
from sklearn.feature_selection import chi2
x_var = VarianceThreshold(threshold=np.median(x.var().values)).fit_transform(x)
chi_x=SelectKBest(chi2,k=300).fit_transform(x_var,y)
chi_x.shape123456

输出 (42000, 300),也就是特征从392个降到了300个,那么效果如何呢?

#看到删除部分特征后模型效果变差了
rfc = RandomForestClassifier(n_estimators=100,random_state=20)
cross_val_score(rfc,chi_x,y,cv=5).mean()123

输出:0.9597860389879884,发现删除了92个特征后模型效果变差了,那么到底将k设置为多少合适呢?可以使用学习曲线来确定最佳的k,这里只给出代码就不画了,因为跑的时间比较长

#通过学习曲线来确定最佳的K值
import matplotlib.pyplot as plt
list = []
for i in range(100,390,10):
    chi_x = SelectKBest(chi2,k=i).fit_transform(x_var,y)
    me = cross_val_score(RandomForestClassifier(n_estimators=100,random_state=20),chi_x,y,cv=5).mean()
    list.append(me)
plt.plot(range(100,390,10),list)
plt.show()123456789

注意这个代码中只能确定大致的k范围,最还还得画一次学习曲线来确定最终的k值。

由于运行时间过长,下面介绍一种看p值选k值的方法,卡方检验的本质是推测两组数据之间的差异,其检验的原假设是”两组数据是相互独立的”。卡方检验返回卡方值和P值两个统计量,其中卡方值很难界定有效的范围,而p值,我们一般使用0.01或0.05作为显著性水平,即p值判断的边界,具体我们可以这样来看

从特征工程的角度,我们希望选取卡方值很大,p值小于0.05的特征,即和标签是相关联的特征。而调用SelectKBest之前,我们可以直接从chi2实例化后的模型中获得各个特征所对应的卡方值和P值

chi_val,p_val = chi2(x_var,y)#得到卡方值,和p值
k_val = x_var.shape[1]-(p_val>0.05).sum()
x_var.shape[1],k_val  #输出:(392, 392)123

可以观察到,所有特征的p值都是0,这说明对于digit recognizor这个数据集来说,方差验证已经把所有和标签无关的特征都剔除了,或者这个数据集本身就不含与标签无关的特征。在这种情况下,舍弃任何一个特征,都会舍弃对模型有用的信息,而使模型表现下降。

相关性过滤: F检验

是用来捕捉每个特征与标签之间的线性关系的过滤方法。它即可以做回归也可以做分类,因此包含feature_selection.f_classif(F检验分类)和feature_selection.f_regression(F检验回归)两个类。其中F检验分类用于标签是离散型变量的数据,而F检验回归用于标签是连续型变量的数据。

和卡方检验一样,这两个类需要和类SelectKBest连用,并且我们也可以直接通过输出的统计量来判断我们到底要设置一个什么样的K。需要注意的是,F检验在数据服从正态分布时效果会非常稳定,因此如果使用F检验过滤,我们会先将数据转换成服从正态分布的方式。

F检验的本质是寻找两组数据之间的线性关系,其原假设是”数据不存在显著的线性关系“。它返回F值和p值两个统计量。和卡方过滤一样,我们希望选取p值小于0.05或0.01的特征,这些特征与标签时显著线性相关的,而p值大于0.05或0.01的特征则被我们认为是和标签没有显著线性关系的特征,应该被删除。

#F检验 用于分类或者回归
from sklearn.feature_selection import f_classif
f_val,p_val = f_classif(x_var,y)
k_val = x_var.shape[1] - (p_val > 0.05).sum()
k_val  #输出 392   特征数量没变12345

得到的结论和我们用卡方过滤得到的结论一模一样:没有任何特征的p值大于0.01,所有的特征都是和标签相关的,因此我们不需要相关性过滤。

还是看一下效果吧

x_cls = SelectKBest(f_classif,k=k_val).fit_transform(x_var,y)
cross_val_score(RandomForestClassifier(n_estimators=100,random_state=20),x_cls,y,cv=5).mean()12

互信息法是用来捕捉每个特征与标签之间的任意关系(包括线性和非线性关系)的过滤方法。和F检验相似,它既可以做回归也可以做分类,并且包含两个类feature_selection.mutual_info_classif(互信息分类)和feature_selection.mutual_info_regression(互信息回归)。这两个类的用法和参数都和F检验一模一样,不过互信息法比F检验更加强大,F检验只能够找出线性关系,而互信息法可以找出任意关系。

互信息法不返回p值或F值类似的统计量,它返回“每个特征与目标之间的互信息量的估计”,这个估计量在[0,1]之间取值,为0则表示两个变量独立,为1则表示两个变量完全相关。以互信息分类为例的代码如下:

#互信息   分类或回归
from sklearn.feature_selection import mutual_info_classif
x_mic = mutual_info_classif(x_var,y)
x_mic1234

看部分输出截图

所有特征的互信息量估计都大于0,因此所有特征都与标签相关。

2.2、Embeded嵌入法

嵌入法是一种让算法自己决定使用哪些特征的方法,即特征选择和算用某些机器学习的算法和模型进行训练,得到各个特征的权值系数,数往往代表了特征对于模型的某种贡献或某种重要性,比如决策树和性,可以列出各个特征对树的建立的贡献,我们就可以基于这种贡献相比于过滤法,嵌入法的结果会更加精确到模型的效用本身,对于提征对模型的贡献,因此无关的特征(需要相关性过滤的特征)和无区缺乏对模型的贡献而被删除掉,可谓是过滤法的进化版。如果采用计算量很大,计算缓慢的算法,嵌入法本身也会非常耗时耗力

SelectFromModel是一个元变换器,可以与任何在拟合后具有coef_,feature_importances_属性或参数中可选惩罚项的评估器一起使用(比如随机森林和树模型就具有属性feature_importances_,逻辑回归就带有l1和l2惩罚项,线性支持向量机也支持l2惩罚项)。
对于有feature_importances_的模型来说,若重要性低于提供的阈值参数,则认为这些特征不重要并被移除。feature_importances_的取值范围是[0,1],如果设置阈值很小,比如0.001,就可以删除那些对标签预测完全没贡献的特征。如果设置得很接近1,可能只有一两个特征能够被留下,参数如下

#Embeded嵌入法
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import cross_val_score
from sklearn.feature_selection import SelectFromModel

data = pd.read_csv("D:/digit recognizor.csv")
x = data.iloc[:,1:]
y = data.iloc[:,0]

rfc = RandomForestClassifier(n_estimators=100,random_state=20)
x_emb = SelectFromModel(rfc,threshold=0.005).fit_transform(x,y)
score = cross_val_score(rfc,x_emb,y,cv=5).mean()
print(score)
print(x_emb.shape)1234567891011121314151617

输出:

0.8995009890372831
(42000, 47)12

可见,留下来的特征为47个,但是模型评分很低,是因为设置了过大的阈值,如果将阈值改为0.002那么评分能达到0.94+,具体的阈值设置可以通过学习曲线来确定,由于运行时间长,下面就只给出代码

#画学习曲线
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import cross_val_score
from sklearn.feature_selection import SelectFromModel

data = pd.read_csv("D:/digit recognizor.csv")
x = data.iloc[:,1:]
y = data.iloc[:,0]

rfc = RandomForestClassifier(n_estimators=100,random_state=20)
rfc.fit(x,y)
thresholds = np.linspace(0,(rfc.feature_importances_).max(),20)
scores = []
for i in thresholds:
    x_emb = SelectFromModel(rfc,threshold=i).fit_transform(x,y)
    score = cross_val_score(rfc,x_emb,y,cv=5).mean()
    scores.append(score)
plt.plot(thresholds,scores)
plt.show()12345678910111213141516171819202122

2.3、Wrapper包装法

与嵌入法十分相似,它也是依赖于算法自身的选择,比如coef_属性或feature_importances_属性来完成特征选择。但不同的是,我们往往使用一个目标函数作为黑盒来帮助我们选取特征,而不是自己输入某个评估指标或统计量的阈值。包装法在初始特征集上训练评估器,并且通过coef_属性或通过feature_importances_属性获得每个特征的重要性。然后,从当前的一组特征中修剪最不重要的特征。在修剪的集合上递归地重复该过程,直到最终到达所需数量的要选择的特征。区别于过滤法和嵌入法的一次训练解决所有问题,包装法要使用特征子集进行多次训练,因此它所需要的计算成本是最高的。

注意,在这个图中的“算法”,指的不是我们最终用来导入数据的分类或回归算法(即不是随机森林),而是专业的数据挖掘算法,即我们的目标函数。这些数据挖掘算法的核心功能就是选取最佳特征子集,最典型的目标函数是递归特征消除法(Recursive feature elimination, 简写为RFE)。

参数estimator是需要填写的实例化后的评估器,n_features_to_select是想要选择的特征个数,step表示每次迭代中希望移除的特征个数。除此之外,RFE类有两个很重要的属性,.support_:返回所有的特征的是否最后被选中的布尔矩阵,以及.ranking_返回特征的按数次迭代中综合重要性的排名。类feature_selection.RFECV会在交叉验证循环中执行RFE以找到最佳数量的特征,增加参数cv,其他用法都和RFE一模一样。

#Wrapper包装法
from sklearn.feature_selection import RFE
rfc = RandomForestClassifier(n_estimators=10,random_state=20)
x_wra = RFE(rfc,n_features_to_select=300,step=50).fit_transform(x,y)
cross_val_score(rfc,x_wra,y,cv=5).mean()12345

具体的阈值设置还是看学习曲线,不重复写了。

(0)

相关推荐