1.数据预处理
概念:数据预处理是从数据中检测,纠正或删除损坏,不准确或不适用模型的记录的过程
可能面对的问题:
- 数据类型不同:比如有的是文字,有的是数字,有的含时间序列,有的连续,有的离散
- 数据的质量不行:有噪声,有异常,有缺失,数据出错,量纲不一,有重复,数据是偏态,数据量太大或太小
数据预处理的目的:让数据使用模型,匹配模型的需求
1.1 数据无量纲化
数据的无量纲化是指将不同规格的数据转换到统一规格,或者讲不通分布的数据转换到某个特定分布的需求,这种需求统称为数据的无量纲化
1.1.1 数据归一化preprocessing.MinMaxScaler
将数据压缩到指定的范围,默认为[0,1],也可以通过参数feature_range
把数据压缩到其他范围
from sklearn.preprocessing import MinMaxScaler
data = [[-1,2],[-0.5,6],[0,10],[1,18]]
scaler = MinMaxScaler().fit(data) # fit本质是生成最大值和最小值
result = scaler.transform(data)
result
#MinMaxScaler().fit_transform(data) 也可以一步到位
array([[0. , 0. ],
[0.25, 0.25],
[0.5 , 0.5 ],
[1. , 1. ]])
通过方法inverse_transform
,可以将归一化之后的结果逆转
result_ = inverse_transform(result)
array([[-1. , 2. ],
[-0.5, 6. ],
[ 0. , 10. ],
[ 1. , 18. ]])
通过参数feature_range
把数据压缩到其他范围
data = [[-1,2],[-0.5,6],[0,10],[1,18]]
scaler = MinMaxScaler(feature_range=[5,10]) # 通过参数feature_range把数据压缩到[5,10]
result = scaler.fit_transform(data)
result
array([[ 5. , 5. ],
[ 6.25, 6.25],
[ 7.5 , 7.5 ],
[10. , 10. ]])
1.1.2 数据标准化preprocessing.StandardScaler
通过标准化处理,将数据为标准正态分布
from sklearn.preprocessing import StandardScaler
data = [[-1,2],[-0.5,6],[0,10],[1,18]]
scaler = StandardScaler()
scaler = scaler.fit(data) #fit,本质是生成均值和方差
scaler.mean_ # 返回各列的平均值 array([-0.125, 9. ])
scaler.var_ # 返回各列的方差 array([ 0.546875, 35. ])
result = scaler.transform(data)
result
array([[-1.18321596, -1.18321596],
[-0.50709255, -0.50709255],
[ 0.16903085, 0.16903085],
[ 1.52127766, 1.52127766]])
查看标准化之后的平均值和方差,看是否符合标准正态分布
result.mean() # 返回值1.0
result.var() # 返回值0.0
preprocessing.MinMaxScaler
和preprocessing.StandardScaler
的选择问题
因为preprocessing.MinMaxScaler
对异常值非常敏感,所以在PCA,聚类,逻辑回归,支持向量机,神经网络这些算法中,StandardScaler
往往是最好的选择
MinMaxScaler
在不涉及距离度量,梯度,方差,协方差计算以及数据需要被压缩到特定区间时使用广泛。
综上,可以优先选择StandardScaler
当效果不好时可以考虑选择MinMaxScaler
1.2 缺失值处理
运用impute.SimpleImpute(missing_values=np.nan,strategy=,fill_value=,copy)
模块对缺失值进行填充处理
参数 | 含义 |
---|---|
missing_values |
告诉Simpleimpute 缺失值的类型 |
startegy |
用于填补缺失值的策略,mean ,median ,most_frequent ,constant
|
fill_value |
当参数strategy=constant ,可输入字符串或数字,表示要填充的值 |
copy |
默认为True ,将创建特征矩阵的副本,反之则会将缺失值填补到原本的特征矩阵中去 |
(1)读取修改之后的带有缺失数据的泰坦尼克号数据集
import pandas as pd
df = pd.read_csv('Narrativedata.csv',index_col=0)
df.head()
Age | Sex | Embarked | Survived | |
---|---|---|---|---|
0 | 22.0 | male | S | No |
1 | 38.0 | female | C | Yes |
2 | 26.0 | female | S | Yes |
3 | 35.0 | female | S | Yes |
4 | 35.0 | male | S | No |
(2) 查看数据集的情况
df.info()
<class 'pandas.core.frame.DataFrame'>
Int64Index: 891 entries, 0 to 890
Data columns (total 4 columns):
Age 714 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+ KB
(3)对Age
分别用平均值,0,中位数进行缺失值填充
Age = df.loc[:,'Age'].values.reshape(-1,1) # sklearn当中,特征矩阵必须是二维数组
from sklearn.impute import SimpleImputer
imp_mean = SimpleImputer(missing_values=np.nan,strategy='mean').fit_transform(Age)
imp_median = SimpleImputer(missing_values=np.nan,strategy='median').fit_transform(Age)
imp_0 = SimpleImputer(missing_values=np.nan,strategy='constant',fill_value=0).fit_transform(Age)
imp_mean[:20]
array([[22. ],
[38. ],
[26. ],
[35. ],
[35. ],
[29.69911765],
[54. ],
[ 2. ],
[27. ],
[14. ],
[ 4. ],
[58. ],
[20. ],
[39. ],
[14. ],
[55. ],
[ 2. ],
[29.69911765],
[31. ],
[29.69911765]])
# 在这里我们使用中位数对Age进行填补,因为mean有小数,年龄是没有小数的
df.loc[:,'Age'] = imp_median
df.info() # 再次查看数据集,可以发现Age已经全部被填充完毕了
<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+ KB
同理使用同样的方法对Embarked
进行缺失值填充处理
我们也可以直接使用pandas对缺失数据进行填充
import pandas as pd
import numpy as np
data = pd.read_csv('Narrativedata.csv',index_col=0)
data.loc[:,'Age'] = data.loc[:,'Age'].fillna(data.loc[:,'Age'].median())
data.dropna(axis=0,inplace=True) # axis=0,在列方向上对行进行操作
1.3 处理分类型特征:编码与哑变量
1.3.1 编码
在实际生活中我们所收集到的特征信息,并不是以数字表示的,而是以文字表示的,比如收款方式,支付宝或者微信,学历,高中,大学,硕士,这些文字变量,机器学习是无法fit的,所以在建立模型之前要事先对这些变量进行处理,将文字转化为数字变量,这一过程称之为编码,而这些文字本质上代表类别,所以具有分类型数据的特征
标签编码preprocessing.LabelEncoder
from sklearn.preprocessing import LabelEncoder
y = df.iloc[:,-1] # 此处fit的是标签,所以可以是一维的
le = LabelEncoder().fit(y) # 实例化导入数据
label = le.transform(y) # transform接口调取结果
le.classes_ # 通过classes_属性,查看标签中究竟有多少类别
#[*set(y)] 也可以直接查看标签中的不重复类别
array(['No', 'Unknown', 'Yes'], dtype=object)
df.iloc[:,-1] = label #使用编码后的标签替换原标签
df.head()
Age | Sex | Embarked | Survived | |
---|---|---|---|---|
0 | 22.0 | male | S | 0 |
1 | 38.0 | female | C | 2 |
2 | 26.0 | female | S | 2 |
3 | 35.0 | female | S | 2 |
4 | 35.0 | male | S | 0 |
特征编码preprocessing.OrdinalEncode
from sklearn.preprocessing import OrdinalEncoder
df_ = df.copy()
# 把Sex和Embarked转化为数值变量
df_.iloc[:,1:3] = OrdinalEncoder().fit_transform(df_.iloc[:,1:3])
df_head()
Age | Sex | Embarked | Survived | |
---|---|---|---|---|
0 | 22.0 | male | 2.0 | 0 |
1 | 38.0 | female | 0.0 | 2 |
2 | 26.0 | female | 2.0 | 2 |
3 | 35.0 | female | 2.0 | 2 |
4 | 35.0 | male | 2.0 | 0 |
独热编码:创建特征哑变量preprocessing.OneHotEncode
- 名义变量:指变量之间没有完全没有任何联系,相互独立,比如舱门(S,C,Q)
- 有序变量:变量不是完全独立,存在一定的顺序联系,但是各个取值之间不能进行运算,例如(小学,初中,高中,大学)
- 有距变量:指分类变量之间可以通过运算来相互转换,例如(体重>45kg,>90kg,>135kg)
所以在上面特征编码的方式里,是存在错误的,舱门和性别并不具备可运算关系,所以我们要运用独热编码,将其转化为哑变量
from sklearn.preprocessing import OneHotEncoder
X = df.iloc[:,1:3]
enc = OneHotEncoder(categories='auto').fit(X)
result = enc.transform(X).toarray()
result
#OneHotEncoder(categories='auti').fit_transform(X).toarray() 代码一步到位
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.]])
enc.get_feature_names() # 得到每一列的哑变量名称
array(['x0_female', 'x0_male', 'x1_C', 'x1_Q', 'x1_S'], dtype=object)
result = pd.DataFrame(result)
df.drop(['Sex','Embarked'],axis=1,inplace=True)
newdata = pd.concat([df,result],axis=1,columns=['Age','Survived','female','male','Embarked_C','Embarked_Q','Embarked_S'])
newdata.head()
Age | Survived | female | male | Embarked_C | Embarked_Q | Embarked_S | |
---|---|---|---|---|---|---|---|
0 | 22.0 | 0 | 0.0 | 1.0 | 0.0 | 0.0 | 1.0 |
1 | 38.0 | 2 | 1.0 | 0.0 | 1.0 | 0.0 | 0.0 |
2 | 26.0 | 2 | 1.0 | 0.0 | 0.0 | 0.0 | 1.0 |
3 | 35.0 | 2 | 1.0 | 0.0 | 0.0 | 0.0 | 1.0 |
4 | 35.0 | 0 | 0.0 | 1.0 | 0.0 | 0.0 | 1.0 |
同样的标签也可以做哑变量,使用的模块为preprocessing.LabelBinarizer
1.4处理连续型特征:二值化与分类变量
1.4.1 二值化preprocessing.Binarizer
根据阈值将数值二值化(将特征值设置为0或1),用于处理连续型变量。大于阈值的值映射为1,而小于或等于阈值的值映射为0,默认阈值为0是,特征中所有的正值为1,负值为0,二值化是对文本计数数据的常见操作,可以决定仅考虑某种现象的存在与否,还可以用作考虑布尔随机变量的估计器的预处理步骤
# 将年龄二值化
data_2 = newdata.copy()
from sklearn.preprocessing import Binarizer
x = data_2.loc[:,'Age'].values.reshape(-1,1) # 特征必须是二维的
transformer = Binarizer(threshold=30).fit_transform(x)
transformer
array([[0.],
[1.],
[0.],
[1.],
[1.],
[0.],
[1.],
[0.],
[0.],
……
1.4.2 分类变量preprocessing.KBinsDiscretizer
这是将连续型变量划分为分类变量的类,能够将连续型变量排序之后按顺序分箱后编码
参数 | 含义&输入 |
---|---|
n_bins |
每个特征中分箱的个数,默认为5,一次会被运用到所有导入的特征 |
encode |
编码的方式,默认onehot 哑变量,返回稀疏矩阵,ordinal 每个特征的每个箱都被编码为一个整数,返回一列是一个特征,每个特征下含有不同整数编码的箱的矩阵 |
strategy |
用来定义箱宽的方式,默认quantile 表示等位分箱,uniform 表示等宽分箱,kmeans 表示聚类分箱 |
# 将年龄进行分箱
data_3 = newdata.copy()
x = data_3.loc[:,'Age'].values.reshape(-1,1) # 特征必须是二维的
from sklearn.preprocessing import KBinsDiscretizer
est = KBinsDiscretizer(n_bins=3,encode='ordinal',strategy='quantile')
result = est.fit_transform(x)
set(result.ravel()) # 返回值{0.0, 1.0, 2.0}
# 设置编码方式为独热编码
est = KBinsDiscretizer(n_bins=3,encode='onehot',strategy='uniform')
result = est.fit_transform(x).toarray()
set(result.ravel()) # 返回值{0.0, 1.0}
2.特征选择
特征提取 (feature extraction) |
特征创造 (feature creation) |
特征选取 (feature selection) |
---|---|---|
从图像,文字,声音等其他非结构化数据中提取新信息作为特征 | 把现有特征进行组合,或相互计算得到新的特征 | 从所有的特征中选出有意义,对模型有帮助的特征,以避免把所有特征都导入模型取训练结果 |
import pandas as pd
data = pd.read_csv('digit recognizor.csv')
x = data.iloc[:,1:]
y = data.iloc[:,0]
x.shape
(42000, 784)
可见x具有相当多的特征,如果将所有的特征都导入矩阵,无疑会给矩阵的运行增加负担,所以我们要事先对齐进行筛选
2.1 Filter过滤法
过滤方法通常做数据的预处理步骤,特征选择完全独立于任何机器学习算法,它是根据各种统计检验中的分数以及相关性的各项指标来选取特征
类 | 说明 | 超参数的选择 |
---|---|---|
VarianceThreshold |
方差过滤,可输入方差阈值,返回方差大于阈值的新特征矩阵 | 一般选取阈值为0过滤掉特征值差异小的特征 |
SelectKBest |
用来选取K个统计量结果最佳的特征,生成符合统计量要求的新特征矩阵 | 与卡方检验,F检验和互信息法配合使用 |
chi2 |
卡方检验,用于分类算法,捕捉相关性 | 追求p值小于显著性水平的特征 |
f_classif |
F检验分类,只能捕捉线性相关,要求数据服从正态分布 | 追求P值小于显著性水平的特征 |
f_regression |
F检验回归,只能捕捉线性相关,要求数据服从正太分布 | 追求p值小于显著性水平的特征 |
mutual_info_classif |
互信息分类,可以捕捉任何相关性,不能用于稀疏矩阵 | 追求互信息估计大于0的特征 |
mutual_info_regression |
互信息分类,可以捕捉任何相关性,不能用于稀疏矩阵 | 追求互信息估计大于0的特征 |
2.1.1 方差过滤VarianceThreshold
通过特征本身的方差来筛选特征的类,比如一个特征本身的方差很小,就表示样本在这个特征上基本没有差异,可能是特征中的大多数值都一样,甚至整个特征的取值都相同,那这个特征对于样本区别就没有作用,所以要优先消除方差为0的特征
VarianceThreshold有重要参数threshold,表示方差的阈值,表示舍弃所有方差小于阈值的特征,默认为0,即删除所有记录都相同的特征
from sklearn.feature_selection import VarianceThreshold
x_var0 = VarianceThreshold().fit_transform(x)
x_var0.shape
(42000, 708)
可以看到,方差过滤之后剩余的特征还有708个,比之前的784减少了72个,如果我们知道我们需要多少个特征,方差也可以直接帮助我们一次性筛选到位,比如我们希望留下一半的特征,那么我们就把阈值设定为中位数
x_median = VarianceThreshold(x.var().median()).fit_transform(x)
x_median.shape
(42000, 392)
可以看到使用中位数之后,特征值减少了一半
2.1.2方差过滤对模型的影响
使用KNN和随机森林对方差过滤前后的模型分别进行建模分析,查看过滤前后的准确度
(1)导入模块并准备数据
from sklearn.ensemble import RandomForestClassifier
from sklearn.neighbors import KNeighborsClassifier as KNN
from sklearn.model_selection import cross_val_score
x = data.iloc[:,1:]
y = data.iloc[:,0]
x_median = VarianceThreshold(x.var().median()).fit_transform(x)
(2)KNN方差过滤前
cross_val_score(knn(),x,y,cv=5).mean()
0.9658569700264943
%%timeit #python中的魔法命令,可以计算运行该cell所需要的时间,运行7次求平均,会严重影响运行时间
cross_val_score(knn(),x,y,cv=5).mean()
33min 58s ± 43.9 s per loop (mean ± std. dev. of 7 runs, 1 lppo each)
(3)KNN方差过滤后
cross_val_score(knn(),x_median,y,cv=5).mean()
0.9659997478150573
%%timeit #python中的魔法命令,可以计算运行该cell所需要的时间,运行7次求平均,会严重影响运行时间
cross_val_score(knn(),x_median,y,cv=5).mean()
20min ± 4min 55s per per loop (mean ± std. dev. of 7 runs, 1 lppo each)
可以看出,对于KNN过滤之后的效果十分明显,准确率稍有提升,单平均运行时间减少了10分钟,特征选择过后算法的效率上升了1/3
(4)随机森林方差过滤前
cross_val_score(RandomForestClassifier(n_estimators=10,random_state=0),x,y,cv=5).mean()
0.9380003861799541
%%timeit
cross_val_score(RandomForestClassifier(n_estimators=10,random_state=0),x,y,cv=5).mean()
18.9 s ± 2.2 s per loop (mean ± std. dev. of 7 runs, 1 loop each)
(5)随机森林方差过滤之后
cross_val_score(RandomForestClassifier(n_estimators=10,random_state=0),x,y,cv=5).mean()
0.9388098166696807
%%timeit
cross_val_score(RandomForestClassifier(n_estimators=10,random_state=0),x_median,y,cv=5).mean()
17.5 s ± 597 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
可以看到方差过滤前后对随机森林预测的准确率影响不大,对运行时间的影响也不大
这是因为最近邻算法KNN,单棵决策树,支持向量机SVM,神经网络,归回算法,都需要对所有特征进行遍历或升维来进行运算,而随机森林本身就不需要遍历所有的随机变量,只需要选取固定数量的特征就可以进行建模分析,所以方差过滤对其影响不大,
过滤法的主要对象,就是需要遍历特征或升维的算法们,而过滤法的主要目的就是在维持算法表现的前提下,帮助算法们降低运算的成本
2.2 相关性过滤
2.2.1卡方过滤
卡方过滤是专门针对离散型标签(分类问题)的相关过滤,卡方检验feature_selection.chi2
计算每非负特征和标签之间的卡方统计量,并依照卡方统计量由高到低的为特征排名,再结合feature_selection.SelectKBest
这个可以输入“评分标准”来选出前K个分数最高的类,去除独立标签,即与我们分类目的无关的标签
卡方检验检测到某个特征中所有的值都相同,会提示我们进行方差过滤,同时卡方计算的是非负标签,所以我们可以优先对标签进行归一化处理
from sklearn.ensemble import RandomForestClassifier as rfc
from sklearn.model_selection import cross_val_score
from sklearn.feature_selection import chi2
from sklearn.feature_selection import SelectKBest
# 假设在这里我们需要300个特征
x_kbest = SelectKBest(chi2,k=300).fit_transform(x_median,y)
x_kbest.shape # 返回值(42000, 300)
cross_val_score(rfc(n_estimators=10,random_state=0),x_kbest,y,cv=5).mean()
0.9333098667649198
我们可以看到,模型的效果降低了,说明我们在设置k=300时删除了一些与模型相关且有效的特征,可以通过绘制超参数曲线来找到最佳k值
%matplotlib inline
import matplotlib.pyplot as plt
score = []
for i in range(390,200,-10):
x_kbest = SelectKBest(chi2,k=i).fit_transform(x_median,y)
once = cross_val_score(rfc(n_estimators=10,random_state=0),x_kbest,y,cv=5).mean()
score.append(once)
plt.plot(range(390,200,-10),score)
plt.show()
通过这条曲线,我们可以看到随着K值得不断增加,模型的表现不断上升,这说明K越大越好,即数据中的特征与标签均相关,
卡方检验的本质是推测两组数组之间的差异,其检验的原假设是“两组数据是相互独立的”,卡方检验返回卡防值和P值两个统计量,一般用p值作为有效性的范围界定,即当P值小于0.01或0.05时,我们认为两组数据是相关的,拒绝原假设
P值 | <=0.05或0.01 | >=0.05或0.01 |
---|---|---|
数据差异 | 差异不是自然形成的 | 差异是很自然的样本误差 |
相关性 | 两组数据是相关的 | 两组数据是相互独立的 |
原假设 | 拒绝原假设接受备择假设 | 接受原假设 |
从特征工程角度,我们希望选取卡方值很大,p值小于0.05的特征
chivalue,pvalue_chi = chi2(x_median,y)
k = chivalue.shape[0]-(pvalue_chi > 0.05).sum()
k # 返回值392
可以观察到,所有特征的p值都是0,说明对于digit recognizon这个数据集来说。方差验证已经把所有和标签无关的特征剔除掉了,在这种情况下,舍弃任何一个特征,都会舍弃对模型有用信息,从而使模型表现下降
2.2.2 F检验
F检验,又称齐方差检验,是用来捕捉每个特征和标签之间的线性关系的过滤方法,包含feature_selection.f_classif
F分类检验和feature_selection.f_regression
F回归检验
和卡方检验相同,这两个类需要和SelectKBest
连用,F检验在数据服从正态分布的时候非常稳定,所以在使用F检验之前,我们可以先对数据进行标准正态化,然后再进行F检验
F检验的本质是寻找两组数据之间的线性关系,其原假设是“数据不存在显著的线性关系”。它返回F值和p值两个统计量。当p值小于0.01或0.05时我们认为两个变量之间存在线性关系,即我们要舍弃p值大于0.01或0.05的值
from sklearn.feature_selection import f_classif
F,pvalues_f = f_classif(x_median,y)
k = F.shape[0]-(pvalues_f>0.05).sum()
k # 返回值392
得到的结果和卡方过滤得到的结论保持一致,没有任何值得特征大于0.01,所有的特征都是和标签相关的,因此我们不需要相关性过滤
2.2.3互信息法
互信息是用来捕捉每个特征与标签之间的任意关系(包括线性和非线性关系)的过滤方法,包含feature_selection.mutual_info_classif
(互信息分类)和feature_selection.mutual_info_regression
(互信息回归)。互信息法返回“每个特征与目标之间的互信息量的估计”,这个估计量在[0,1]之间取值,为0表示两个变量独立,为1表示两个变量完全相关
from sklearn.feature_selection import mutual_info_classif as MIC
result = MIC(x_median,y)
k = result.shape[0]-sum(result<=0)
k # 返回值392
#X_KBest_mutual = SelectKBest(MIC,k=392).fit_transform(x_median,y)
#cross_val_score(RFC(n_estimators=10,random_state=0),X_KBest_mutual,y,cv=10)
2.3 嵌入法Embedded
嵌入法是一种方算法自己决定使用那些特征的方法,即特征选择和算法训练同时进行,在使用嵌入法时,先使用某些机器学习的算法和模型进行训练,得到每个特征的权值系数,根据权值系数从大到小选择特征,这些权值系数代表特征对标签的重要性程度,比如决策树和树的集成模型中的feature_importances_
属性,可以列出各个特征对树的建立贡献,因此相对于过滤法,嵌入法的结果会更加精确到模型的效用本身,对于提高模型效力有更好的效果
但是嵌入法返回的是权值系数,我们无法和卡方检验和F检验,通过筛选P值的方式来进行筛选,我们并不知道权值系数处于一个什么样的范围是最好的,因此我们可以使用学习曲线的方式来选取最优权值参数
feature_selection.SelectFromModel(estimator,threshold=None)
SelectModel
是一个元变换器,可以与任何在拟合后具有coef_
,feature_importances_
属性或参数中可选惩罚项的评估器一起使用,比如随机森林和树模型就有属性feature_importances_
,逻辑回归就带有l1
和l2
惩罚项,线性支持向量机也支持l2
惩罚项
参数 | 说明 |
---|---|
estimator |
使用的模型评估器,只要是带feature_importances_和coef_的都可以使用 |
threshold |
特征重要性的阈值,重要性低于这个阈值的特征都将被删除 |
from sklearn.ensemble import RandomForestClassifier as RFC
from sklearn.feature_selection import SelectFromModel
RFC_ = RFC(n_estimators=10,random_state=0)
X_embedded = SelectFromModel(RFC_,threshold=0.005).fit_transform(x,y)
X_embedded.shape # 返回值(42000, 47)
可以看到特征数降为47,但是这样设定阈值是不准确的,我们可以通过绘制学习曲线来找到最优的阈值
#==========【TIME WARNING:10 MINS】============#
import numpy as pd
import matplotlib.pyplot as plt
RFC_.fit(x,y).feature_importances_
threshold = np.linspace(0,(RFC_.fit(x,y).feature_importances_).max(),20)
score = []
for i in threshold:
X_embedded = SelectFromModel(RFC_,threshold=i).fit_transform(x,y)
once = cross_val_score(RFC_,X_embedded,y,cv=5).mean()
score.append(once)
plt.figure(figsize=(20,5))
plt.plot(threshold,score)
plt.xticks(threshold)
plt.show()
从图像上来看,随着阈值越来越高,模型的效果越来越差,被删除的特征值越来越多,信息损失也越来越大,但是在0.00134之前,模型的效果都可以维持在0.93以上,因此我们可以继续选定一个范围,细化学习曲线来找到最佳值
score2 = []
for i in np.linspace(0,0.00134,20):
X_embedded = SelectFromModel(RFC_,threshold=i).fit_transform(x,y)
once = cross_val_score(RFC_,X_embedded,y,cv=5).mean()
score2.append(once)
plt.figure(figsize=(20,5))
plt.plot(np.linspace(0,0.00134,20),score2)
plt.xticks(np.linspace(0,0.00134,20))
plt.show()
从图像上可以看到阈值为0.000564时模型的效果是最好的,我们可以把0.000564带入模型看一下效果
from sklearn.feature_selection import SelectFromModel
X_embedded = SelectFromModel(RFC_,threshold=0.000564).fit_transform(x,y)
cross_val_score(RFC_,X_embedded,y,cv=5).mean()
0.9408335415056387
可以看到模型的准确率升高为94%点多,因此,比起要考虑很多统计量的过滤法来说,前复发可能是更有效的一种方法,然而,在算法本身复杂的时候,过滤法的计算量远远比嵌入法快,所以在大型数据中,还是要优先考虑过滤法
2.4 包装法Wrapper
包装法也是特征选择和算法同时进行的方法,与嵌入法相同的是包装法也是通过模型训练之后的feature_importantances_
或者coef_
来进行特征的选择,与嵌入法不同的是包装法不需要我们指定阈值,包装法通过coef_
属性或feature_importantances_
属性获得每个特征的重要性,然后从当前的一组特征中修剪最不重要的特征。在修剪的集合上地柜地重复该过程,直到最终到达所需数量的要选择的特征
包装法是最能保证模型效果的特征选择方法,但是包装法要使用特征子集进行多次训练,所以包装法需要的计算成本是最高的
feature_selection.RFE(estimator,n_features_to_select=None,step=1,verbose=0)
feature_selection.RFECV(estimator,n_features_to_select=None,step=1,verbose=0,cv=5)
参数estimator
是需要填写的实例化后的评估器,n_features_to_select
是想要选择的特征个数,step
表示每次迭代中希望移除的特征个数。除此之外,RFE类有两个很重要的属性,support_
返回所有的特征的是否最后被选中的布尔矩阵,以及ranking_
返回特征的按数次迭代中综合重要性的排名。类feature_selection.RFECV会在交叉验证循环中执行RFE以找到最佳数量的特征,增加参数cv,其他用法都和RFE一模一样。
from sklearn.feature_selection import RFE
RFC_ = RFC(n_estimators=10,random_state=0)
selector = RFE(RFC_,n_features_to_select=50,step=50).fit(x,y)
#selector.support_.sum() support_属性返回特征选择的布尔矩阵
#selector.ranking_ ranking_属性返回特征的按次数迭代中综合重要性的排名
X_wrapper = selector.transform(x)
cross_val_score(RFC_,X_wrapper,y,cv=5).mean()
0.9056911999667074
使用超参数曲线寻找最优的特征保留数目
score =[]
for i in range(1,751,50):
X_wrapper = RFE(RFC_,n_features_to_select=i,step=50).fit_transform(x,y)
once = cross_val_score(RFC_,X_wrapper,y,cv=5).mean()
score.append(once)
plt.figure(figsize=(20,5))
plt.plot(range(1,751,50),score)
plt.xticks(range(1,751,50))
plt.show()
明显可以看出,在包装法下,应用50个特征时,模型的表现就已经达到了90%,比嵌入法和过滤法都高效很多,在特征数相同的情况下,包装法在效果上匹敌嵌入法,同样我们也可以进一步细化超参数曲线来找到最优的特征数。