ML03数据预处理


title: 数据预处理与特征工程
date: 2021-04-11
tags:

  • ML
  • 基础
    categories:
  • dataAnalysis

typora-root-url: ./img

引言

数据挖掘的五大流程:

  1. 获取数据

  2. 数据预处理

数据预处理是从数据中检测,纠正或删除损坏,不准确或不适用于模型的记录的过程

可能面对的问题有:数据类型不同,比如有的是文字,有的是数字,有的含时间序列,有的连续,有的间断。也可能,数据的质量不行,有噪声,有异常,有缺失,数据出错,量纲不一,有重复,数据是偏态,数据量太大或太小

数据预处理的目的:让数据适应模型,匹配模型的需求

  1. 特征工程

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

特征工程的目的:1) 降低计算成本,2) 提升模型上限

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

  2. 上线,验证模型效果

数据预处理

1. 数据无量纲化

​ 在机器学习算法实践中,我们往往有着将不同规格的数据转换到同一规格,或不同分布的数据转换到某个特定分布的需求,这种需求统称为将数据“无量纲化”。譬如梯度和矩阵为核心的算法中,譬如逻辑回归,支持向量机,神经网络,无量纲化可以加快求解速度;而在距离类模型,譬如K近邻,K-Means聚类中,无量纲化可以帮我们提升模型精度,避免某一个取值范围特别大的特征对距离计算造成影响。(一个特例是决策树和树的集成算法们,对决策树我们不需要无量纲化,决策树可以把任意数据都处理得很好。)

​ 数据的无量纲化可以是线性的,也可以是非线性的。线性的无量纲化包括中心化(Zero-centered或者Meansubtraction)处理和缩放处理(Scale)。中心化的本质是让所有记录减去一个固定值,即让数据样本数据平移到某个位置。缩放的本质是通过除以一个固定值,将数据固定在某个范围之中,取对数也是一种缩放处理。

1.1 preprocessing.MinMaxScaler

from sklearn.preprocessing import MinMaxScaler
import pandas as pd

data = [[-1, 2], [-0.5, 6], [0, 10], [1, 18]]
# scaler = MinMaxScaler()  # 实例化
# scaler = scaler.fit(data)  # 在这里本质是生成min(x)和max(x)
# result = scaler.transform(data)  # 通过接口导出结果

# 上面两行可以被代替
# result = scaler.fit_transform(data)
# print(result)
# 还原归一化之前的结果
# print(scaler.inverse_transform(result))

# 使用feature_range实现将数据归一化到[0,1]以外的范围
# 比如归一化到【5,10】之间
scaler = MinMaxScaler(feature_range=[5, 10])  # 依然实例化
result = scaler.fit_transform(data)
print(result)

1.2 preprocessing.StandardScaler

from sklearn.preprocessing import StandardScaler

data = [[-1, 2], [-0.5, 6], [0, 10], [1, 18]]
scaler = StandardScaler()  # 实例化
scaler.fit(data)  # fit 本质是生成均值和方差
X_std = scaler.transform(data)  # 通过接口导出结果
# X_std是一个数组
print(X_std)
print(X_std.mean())
print(X_std.std())

对于StandardScaler和MinMaxScaler来说,空值NaN会被当做是缺失值,在fifit的时候忽略,在transform的时候保持缺失NaN的状态显示。并且,尽管去量纲化过程不是具体的算法,但在fifit接口中,依然只允许导入至少二维数组,一维数组导入会报错。通常来说,我们输入的X会是我们的特征矩阵,现实案例中特征矩阵不太可能是一维所以不会存在这个问题。

2. 缺失值

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

import pandas as pd

data = pd.read_csv("Narrativedata.csv", index_col=0)
data.head()
data.loc[:, "Age"] = data.loc[:, "Age"].fillna(data.loc[:, "Age"].median())
# .fillna 在DataFrame里面直接进行填补
data.dropna(axis=0, inplace=True)
# .dropna(axis=0)删除所有有缺失值的行
# 参数inplace,为True表示在原数据集上进行修改。为False表示生成一个复制对象,不修改原数据
print(data.info())

2.1 impote.SimpleImputer

*class* sklearn.impute.SimpleImputer (*missing_values=nan*, *strategy=’mean’*, *fifill_value=None*, *verbose=0*,*copy=True*)

在讲解随机森林的案例时,我们用这个类和随机森林回归填补了缺失值,对比了不同的缺失值填补方式对数据的影响。这个类是专门用来填补缺失值的。它包括四个重要参数:

参数 含义
missing_values 告诉SimpleImpute,数据中的缺失值长什么样,默认是np.nan
strategy 填补缺失值的策略,默认均值。输入mean表示使用均值填补,输入median表示使用中值填补,输入most_frequent表示用众数填补。输入constant表示要输入fill_value.
fill_value 当strategy为constant的时候可用,可输入字符或数字表示要填充的值,常用0
copy 默认为True,将创建特征矩阵的副本,不然就会把缺失值填补到原来的特征矩阵中去

3. 处理分类型特征

​ 在机器学习中,大多数算法,譬如逻辑回归,支持向量机SVM,k近邻算法等都只能够处理数值型数据,不能处理文字,在sklearn当中,除了专用来处理文字的算法,其他算法在fifit的时候全部要求输入数组或矩阵,也不能够导入文字型数据(其实手写决策树和普斯贝叶斯可以处理文字,但是sklearn中规定必须导入数值型)。然而在现实中,许多标签和特征在数据收集完毕的时候,都不是以数字来表现的。比如说,学历的取值可以是[“小 学”,“初中”,“高中”,“大学”],付费方式可能包含[“支付宝”,“现金”,“微信”]等等。在这种情况下,为了让数据适应算法和库,我们必须将数据进行编码,即是说,将文字型数据转换为数值型

3.1 preprocessing.LabelEncoder把分类转换为分类数值

from sklearn.preprocessing import LabelEncoder
data.iloc[:,-1] = LabelEncoder().fit_transform(data.iloc[:,-1])

3.2 preprocessing.OrdinalEncoder把分类特征转换为分类数值

from sklearn.preprocessing import OrdinalEncoder
import pandas as pd

# 要输入的是特征矩阵,所以不允许一维
data = pd.read_csv("Narrativedata.csv", index_col=0)

print(data.head())
# print(OrdinalEncoder().fit(data.iloc[:, 1:-1]).categories_)
data.iloc[:, 1: 3] = OrdinalEncoder().fit_transform(data.iloc[:, 1:3])
print(data.head())

3.3 preprocessing.OneHotEncoder独热编码。创建哑变量

列中,我们使用[0,1,2]代表了三个不同的舱门,然而这种转换是正确的吗?

我们来思考三种不同性质的分类数据:

1) 舱门(S,C,Q)

三种取值S,C,Q是相互独立的,彼此之间完全没有联系,表达的是S≠C≠Q的概念。这是名义变量。

2) 学历(小学,初中,高中)

三种取值不是完全独立的,我们可以明显看出,在性质上可以有高中>初中>小学这样的联系,学历有高低,但是学历取值之间却不是可以计算的,我们不能说小学 + 某个取值 = 初中。这是有序变量。

3) 体重(>45kg,>90kg,>135kg)各个取值之间有联系,且是可以互相计算的,比如120kg - 45kg = 90kg,分类之间可以通过数学计算互相转换。这是有距变量。然而在对特征进行编码的时候,这三种分类数据都会被我们转换为[0,1,2],这三个数字在算法看来,是连续且可以计算的,这三个数字相互不等,有大小,并且有着可以相加相乘的联系。所以算法会把舱门,学历这样的分类特征,都误会成是体重这样的分类特征。这是说,我们把分类转换成数字的时候,忽略了数字中自带的数学性质,所以给算法传达了一些不准确的信息,而这会影响我们的建模。类别OrdinalEncoder可以用来处理有序变量,但对于名义变量,我们只有使用哑变量的方式来处理,才能够尽量向算法传达最准确的信息:

"S":[0,
"Q"  1,
"C"  2]

"S":[[1,0,0,],
"Q"   [0,1,0],
"C"   [0,0,1]]
from sklearn.preprocessing import OneHotEncoder
import pandas as pd

# 要输入的是特征矩阵,所以不允许一维
data = pd.read_csv("Narrativedata.csv", index_col=0)
X = data.iloc[:, 1:3]
# print(X)
enc = OneHotEncoder(categories='auto').fit(X)
print(enc.get_feature_names())
result = enc.transform(X).toarray()
# print(result)# 性别两列,舱门3列
newdata = pd.concat([data, pd.DataFrame(result)], axis=1)
print(newdata.head())
newdata.drop(['Sex', "Embarked"], axis=1, inplace=True)
# print(newdata.head())
newdata.columns = ["Age", "Survived", "Female", "Male", "Embarked_C", "Embarked_Q", "Embarked_S", "Embarked_nan"]
print(newdata.head())

处理连续型特征

比如,是代表1,否代表0.

1. sklearn.preprocessing.Binarizer

根据阈值将数据二值化(将特征值设置为0或1),用于处理连续型变量。大于阈值的值映射为1,而小于或等于阈值的值映射为0。默认阈值为0时,特征中所有的正值都映射到1。二值化是对文本计数数据的常见操作,分析人员可以决定仅考虑某种现象的存在与否。它还可以用作考虑布尔随机变量的估计器的预处理步骤(例如,使用贝叶斯设置中的伯努利建模)

from sklearn.preprocessing import Binarizer
import pandas as pd


# 要输入的是特征矩阵,所以不允许一维
data = pd.read_csv("Narrativedata.csv", index_col=0)
X = data.iloc[:, 0].values.reshape(-1, 1)
transformer = Binarizer(threshold=30).fit_transform(X)
print(transformer)
data.iloc[:, 0] = transformer
print(data.head())

2. preprocessing.KBinsDiscretizer

这是将连续型变量划分为分类变量的类,能够将连续型变量排序后按顺序分箱后编码。总共包含三个重要参数:

n_bins

每个特征中分箱的个数,默认5,一次会被运用到所有导入的特征

encode

编码的方式,默认“onehot”

“onehot”:做哑变量,之后返回一个稀疏矩阵,每一列是一个特征中的一个类别,含有该

类别的样本表示为1,不含的表示为0

“ordinal”:每个特征的每个箱都被编码为一个整数,返回每一列是一个特征,每个特征下含

有不同整数编码的箱的矩阵

“onehot-dense”:做哑变量,之后返回一个密集数组。

strategy

用来定义箱宽的方式,默认"quantile"

“uniform”:表示等宽分箱,即每个特征中的每个箱的最大值之间的差为

(特征.max() - 特征.min())/(n_bins)

“quantile”:表示等位分箱,即每个特征中的每个箱内的样本数量都相同

“kmeans”:表示按聚类分箱,每个箱中的值到最近的一维k均值聚类的簇心得距离都相同

from sklearn.preprocessing import KBinsDiscretizer
import pandas as pd

data = pd.read_csv("Narrativedata.csv", index_col=0)
X = data.iloc[:, 0].values.reshape(-1, 1)
est = KBinsDiscretizer(n_bins=3, encode='ordinal', strategy="uniform")
est = est.fit_transform(X)
print(est)

特征选择

1. Filter过滤法

1.1 方差过滤 VarianceThreshold

这是通过特征本身的方差来筛选特征的类。比如一个特征本身的方差很小,就表示样本在这个特征上基本没有差异,可能特征中的大多数值都一样,甚至整个特征的取值都相同,那这个特征对于样本区分没有什么作用。所以无论接下来的特征工程要做什么,都要优先消除方差为0的特征。VarianceThreshold有重要参数threshold,表示方差的阈值,表示舍弃所有方差小于threshold的特征,不填默认为0,即删除所有的记录都相同的特征。

import pandas as pd
from sklearn.feature_selection import VarianceThreshold

data = pd.read_csv("../digit recognizor.csv")
X = data.iloc[:, 1:]
print(X.shape)  # 42000个样本。,784个特征
selector = VarianceThreshold()  # 实例化,不填写默认方差为0
X_var0 = selector.fit_transform(X)  # 获取删除不合格特征之后的特征矩阵
print(X_var0.shape)  # (42000, 708),已经删除了方差为0的特征

可以看见,我们已经删除了方差为0的特征。但是依然剩下了708多个特征,明显还需要进一步的特征选择。然而,如果我们知道我们需要多少个特征,方差也可以帮助我们将特征选择一步到位。比如说,我们希望留下一半的特征,那可以设定一个让特征总数减半的方差阈值,只要找到特征方差的中位数,再将这个中位数作为参数threshold的值输入就好了:

# ==============================通过寻找方差中位数来进行过滤,剩下一半的特征值
import numpy as np

print(np.median(X.var().values))
X_fsvar = VarianceThreshold(np.median(X.var().values)).fit_transform(X)
print(X_fsvar.shape)  # (42000, 392)已经筛选了一半的特征

# ==================通过筛选掉二分类特征中,有一类占到80%以上的特征
# 若特征是伯努利随机变量,假设p=0.8,即二分类特征中某种分类占到80%以上的时候删除特征
X_bvar = VarianceThreshold(.8 * (1 - .8)).fit_transform(X)
print(X_bvar.shape)  # (42000, 685)

当特征是二分类时,特征的取值就是伯努利随机变量,这些变量的方差可以计算为:

其中X是特征矩阵,p是二分类特征中的一类在这个特征中所占的概率。

#若特征是伯努利随机变量,假设p=0.8,即二分类特征中某种分类占到80%以上的时候删除特征
X_bvar = VarianceThreshold(.8 * (1 - .8)).fit_transform(X)
X_bvar.shape
  • 方差过滤对KNN和随机森林的影响
from sklearn.ensemble import RandomForestClassifier as RFC
from sklearn.neighbors import KNeighborsClassifier as KNN
from sklearn.model_selection import cross_val_score
from sklearn.feature_selection import VarianceThreshold
import numpy as np
import pandas as pd

data = pd.read_csv("../digit recognizor.csv")
X = data.iloc[:, 1:]  # 特征矩阵
y = data.iloc[:, 0]  # 标签
# print(data)
# print(X)
# print(y)
# 经过中位数的方差过滤后的
X_fsvar = VarianceThreshold(np.median(X.var().values)).fit_transform(X)

# ======【TIME WARNING:35mins +】======#
# KNN过滤前
cross_val_score(KNN(), X, y, cv=5).mean()
# KNN过滤后
cross_val_score(KNN(), X_fsvar, y, cv=5).mean()
# python中的魔法命令,可以直接使用%%timeit来计算运行这个cell中的代码所需的时间
# 为了计算所需的时间,需要将这个cell中的代码运行很多次(通常是7次)后求平均值,因此运行%%timeit的时间会
# 远远超过cell中的代码单独运行的时间
# ======【TIME WARNING:4 hours】======#
# %%timeit
cross_val_score(KNN(), X, y, cv=5).mean()

# =========随机森林过滤前
cross_val_score(RFC(n_estimators=10, random_state=0), X, y, cv=5).mean()
# =========随机森林过滤后
cross_val_score(RFC(n_estimators=10, random_state=0), X_fsvar, y, cv=5).mean()

# ====经过运行发现。过滤方差对KNN算法有很大的影响,对随机森林没有太大影响
# 这是由于两种算法的原理中涉及到的
# 计算量不同。最近邻算法KNN,单棵决策树,支持向量机SVM,神经网络,回归算法,都需要遍历特征或升维来进
# 行运算,所以他们本身的运算量就很大,需要的时间就很长,因此方差过滤这样的特征选择对他们来说就尤为重
# 要。但对于不需要遍历特征的算法,比如随机森林,它随机选取特征进行分枝,本身运算就非常快速,因此特征选
# 择对它来说效果平平。这其实很容易理解,无论过滤法如何降低特征的数量,随机森林也只会选取固定数量的特征
# 来建模;而最近邻算法就不同了,特征越少,距离计算的维度就越少,模型明显会随着特征的减少变得轻量。因
# 此,过滤法的主要对象是:需要遍历特征或升维的算法们,而过滤法的主要目的是:在维持算法表现的前提下,帮
# 帮助算法们降低计算成本

# 思考:过滤法对随机森林无效,却对树模型有效?
# 从算法原理上来说,传统决策树需要遍历所有特征,计算不纯度后进行分枝,而随机森林却是随机选择特征进行计算和分枝,因此随机森林的运算更快,过滤法对随机森林无用,对决策树却有用.在sklearn中,决策树和随机森林都是随机选择特征进行分枝.参数random_state),但决策树在建模过程中随机抽取的特征数目却远远超过随机森林当中每棵树随机抽取
# 的特征数目(比如说对于这个780维的数据,随机森林每棵树只会抽取10~20个特征,而决策树可能会抽取(300~400个特征),因此,过滤法对随机森林无用,却对决策树有用
  • 选取超参数threshold

过滤后模型到底会变好还是会变坏呢?

答案是:每个数据集不一样,只能自己去尝试。这里的方差阈值,其实相当于是一个超参数,要选定最优的超参数,我们可以画学习曲线,找模型效果最好的点。但现实中,我们往往不会这样去做,因为这样会耗费大量的时间。我们只会使用阈值为0或者阈值很小的方差过滤,来为我们优先消除一些明显用不到的特征,然后我们会选择更优的特征选择方法继续削减特征数量。

1.2 相关性过滤

方差挑选完毕之后,我们就要考虑下一个问题:相关性了。我们希望选出与标签相关且有意义的特征,因为这样的特征能够为我们提供大量信息。如果特征与标签无关,那只会白白浪费我们的计算内存,可能还会给模型带来噪音。在sklearn当中,我们有三种常用的方法来评判特征与标签之间的相关性:卡方,F检验,互信息。

1.2.1 卡方过滤

卡方过滤是专门针对离散型标签(即分类问题)的相关性过滤。卡方检验类feature_selection.chi2计算每个非负特征和标签之间的卡方统计量,并依照卡方统计量由高到低为特征排名。再结合feature_selection.SelectKBest这个可以输入”评分标准“来选出前K个分数最高的特征的类,我们可以借此除去最可能独立于标签,与我们分类目的无关的特征。另外,如果卡方检验检测到某个特征中所有的值都相同,会提示我们使用方差先进行方差过滤。并且,刚才我们已经验证过,当我们使用方差过滤筛选掉一半的特征后,模型的表现时提升的。因此在这里,我们使用threshold=中位数时完成的方差过滤的数据来做卡方检验(如果方差过滤后模型的表现反而降低了,那我们就不会使用方差过滤后的数据,而是使用原数据)

from sklearn.ensemble import RandomForestClassifier as RFC
from sklearn.model_selection import cross_val_score
from sklearn.feature_selection import SelectKBest
from sklearn.feature_selection import chi2
import pandas as pd
import numpy as np
from sklearn.feature_selection import VarianceThreshold

data = pd.read_csv("../digit recognizor.csv")
X = data.iloc[:, 1:]  # 特征矩阵
y = data.iloc[:, 0]  # 标签
# print(data)
# print(X)
# print(y)
# 经过中位数的方差过滤后的
X_fsvar = VarianceThreshold(np.median(X.var().values)).fit_transform(X)

# 选择k个分数最高的,表示评估统计量用的是chi2。选择前300个特征
X_fschi = SelectKBest(chi2, k=300).fit_transform(X_fsvar, y)
# print(X_fschi.shape)

cvs = cross_val_score(RFC(n_estimators=10, random_state=0), X_fschi, y, cv=5).mean()
# print(cvs)
# 模型的效果降低了,说明我们设置的300的时候。删除了与模型相关的且有效的特征。
# 我们的K值设置得太小,要么我们需要调整K值,要么我们必须放弃相关性过滤。当然,如果模型的表现提升,则说明我们的相关性过滤是有效的,是过滤掉了模型的噪音的,这时候我们就保留相关性过滤的结果。

1.2.2 选取超参数K

chivalue, pvalue_chi = chi2(X_fsvar, y)
# print(chivalue)
# print(pvalue_chi)  # 全部小于0.05,所以所有特征都和标签相关
# print(chivalue.shape[0])
k = chivalue.shape[0] - (pvalue_chi > 0.05).sum()  # 求出k值
print(k)
# 可以观察到,所有特征的p值都是0,这说明对于digit recognizor这个数据集来说,方差验证已经把所有和标签无
# 关的特征都剔除了,或者这个数据集本身就不含与标签无关的特征。在这种情况下,舍弃任何一个特征,都会舍弃
# 对模型有用的信息,而使模型表现下降,因此在我们对计算速度感到满意时,我们不需要使用相关性过滤来过滤我
# 们的数据。

1.2.3 F检验

​ F检验,又称ANOVA,方差齐性检验,是用来捕捉每个特征与标签之间的线性关系的过滤方法。它即可以做回归也可以做分类,因此包含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的特征则被我们认为是和标签没有显著线性关系的特征,应该被删除。

from sklearn.feature_selection import f_classif
import pandas as pd
import numpy as np
from sklearn.feature_selection import VarianceThreshold

data = pd.read_csv("../digit recognizor.csv")
X = data.iloc[:, 1:]  # 特征矩阵
y = data.iloc[:, 0]  # 标签
# print(data)
# print(X)
# print(y)
# 经过中位数的方差过滤后的
X_fsvar = VarianceThreshold(np.median(X.var().values)).fit_transform(X)

F, pvalues_f = f_classif(X_fsvar, y)
print(F)
print(pvalues_f)

k = F.shape[0] - (pvalues_f > 0.05).sum()
print(k)  # 392

1.2.4 互信息法

互信息法是用来捕捉每个特征与标签之间的任意关系(包括线性和非线性关系)的过滤方法。和F检验相似,它既可以做回归也可以做分类,并且包含两个类feature_selection.mutual_info_classif(互信息分类)feature_selection.mutual_info_regression**(互信息回归)。这两个类的用法和参数都和F检验一模一样,不过互信息法比F检验更加强大,F检验只能够找出线性关系,而互信息法可以找出任意关系。互信息法不返回p值或F值类似的统计量,它返回“每个特征与目标之间的互信息量的估计”,这个估计量在[0,1]之间取值,为0则表示两个变量独立,为1则表示两个变量完全相关。

import pandas as pd
import numpy as np
from sklearn.feature_selection import VarianceThreshold
from sklearn.feature_selection import mutual_info_classif as MIC

data = pd.read_csv("../digit recognizor.csv")
X = data.iloc[:, 1:]  # 特征矩阵
y = data.iloc[:, 0]  # 标签
# print(data)
# print(X)
# print(y)
# 经过中位数的方差过滤后的
X_fsvar = VarianceThreshold(np.median(X.var().values)).fit_transform(X)

result = MIC(X_fsvar, y)
k = result.shape[0] - sum(result <= 0)
print(k)

2. Embedded嵌入法

嵌入法是一种让算法自己决定使用哪些特征的方法,即特征选择和算法训练同时进行。在使用嵌入法时,我们先使用某些机器学习的算法和模型进行训练,得到各个特征的权值系数,根据权值系数从大到小选择特征。这些权值系数往往代表了特征对于模型的某种贡献或某种重要性,比如决策树和树的集成模型中的feature_importances_属性,可以列出各个特征对树的建立的贡献,我们就可以基于这种贡献的评估,找出对模型建立最有用的特征。因此相比于过滤法,嵌入法的结果会更加精确到模型的效用本身,对于提高模型效力有更好的效果。并且,由于考虑特征对模型的贡献,因此无关的特征(需要相关性过滤的特征)和无区分度的特征(需要方差过滤的特征)都会因为缺乏对模型的贡献而被删除掉,可谓是过滤法的进化版。

class sklearn.feature_selection.SelectFromModel (estimator, threshold=None, prefit=False, norm_order=1,
max_features=None)

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

from sklearn.feature_selection import SelectFromModel
from sklearn.ensemble import RandomForestClassifier as RFC
import pandas as pd
from sklearn.model_selection import cross_val_score

data = pd.read_csv("../digit recognizor.csv")
X = data.iloc[:, 1:]  # 特征矩阵
y = data.iloc[:, 0]  # 标签
RFC_ = RFC(n_estimators=10, random_state=0)  # 实例化
X_embedded = SelectFromModel(RFC_, threshold=0.005).fit_transform(X, y)
# 在这里我只想取出来有限的特征。0.005这个阈值对于有780个特征的数据来说,是非常高的阈值,因为平均每个特征
# 只能够分到大约0.001的feature_importances_
X_embedded.shape
# 模型的维度明显被降低了
# 同样的,我们也可以画学习曲线来找最佳阈值
# ======【TIME WARNING:10 mins】======#
import numpy as np
import matplotlib.pyplot as plt

print(RFC_.fit(X, y).feature_importances_)

# range()最后一个参数表示步长
# linspace最后一个参数表示选择多少个数
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.plot(threshold, score)
plt.show()

可见,在嵌入法下,我们很容易就能够实现特征选择的目标:减少计算量,提升模型表现。因此,比起要思考很多统计量的过滤法来说,嵌入法可能是更有效的一种方法。然而,在算法本身很复杂的时候,过滤法的计算远远比嵌入法要快,所以大型数据中,我们还是会优先考虑过滤法。

Wrapper包装法

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

feature_selection.RFE

class sklearn.feature_selection.RFE (estimator, n_features_to_select=None, step=1, verbose=0)

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

from sklearn.feature_selection import RFE
from sklearn.feature_selection import SelectFromModel
from sklearn.ensemble import RandomForestClassifier as RFC
import pandas as pd
from sklearn.model_selection import cross_val_score

data = pd.read_csv("../digit recognizor.csv")
X = data.iloc[:, 1:]  # 特征矩阵
y = data.iloc[:, 0]  # 标签
RFC_ = RFC(n_estimators=10, random_state=0)  # 实例化

selector = RFE(RFC_, n_features_to_select=340, step=50).fit(X, y)
selector.support_.sum()
selector.ranking_
X_wrapper = selector.transform(X)
cross_val_score(RFC_, X_wrapper, y, cv=5).mean()

参考

菜菜的sklearn课堂直播间: https://live.bilibili.com/12582510

python数据分析挖掘与可视化

你可能感兴趣的:(机器学习,机器学习)