sklearn数据预处理与特征工程

数据预处理与特征工程

1、什么是数据预处理

真实世界的数据通常包含噪声、缺失值,并且可能采用无法直接用于机器学习模型的不可用格式。数据预处理是清理数据并使其适用于机器学习模型的必要任务,这也提高了机器学习模型的准确性和效率。
一般步骤:
① 获取数据
② 数据预处理:从数据中检测,纠正或删除损坏,不准确或不适用于模型的记录的过程 可能面对的问题有:数据类型不同,比如有的是文字,有的是数字,有的含时间序列,有的连续,有的间断。也可能,数据的质量不行,有噪声,有异常,有缺失,数据出错,量纲不一,有重复,数据是偏态,数据量太大或太小
数据预处理的目的:让数据适应模型,匹配模型的需求
③ 特征工程 : 特征工程是将原始数据转换为更能代表预测模型的潜在问题的特征的过程,可以通过挑选最相关的特征,提取特征以及创造特征来实现。其中创造特征又经常以降维算法的方式实现。可能面对的问题有:特征之间有相关性,特征和标签无关,特征太多或太小,或者干脆就无法表现出应有的数据现象或无法展示数据的真实面貌
特征工程的目的:1) 降低计算成本,2) 提升模型上限
④ 建模,测试模型并预测出结果
⑤ 上线,验证模型效果

2、sklearn中的数据预处理和特征工程

模块preprocessing:几乎包含数据预处理的所有内容
模块Impute:填补缺失值专用
模块feature_selection:包含特征选择的各种方法的实践
模块decomposition:包含降维算法

3、数据预处理 Preprocessing & Impute

3.1 数据无量纲化

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

3.1.1 数据归一化(preprocessing.MinMaxScaler)

当数据(x)按照最小值中心化后,再按极差(最大值 - 最小值)缩放,数据移动了最小值个单位,并且会被收敛到[0,1]之间,而这个过程,就叫做数据归一化(Normalization,又称Min-Max Scaling)。注意,Normalization是归一化,不是正则化,真正的正则化是regularization,不是数据预处理的一种手段。归一化之后的数据服从正态分布,公式如下:
在这里插入图片描述
步骤:
① 导包并查看数据格式

from sklearn.preprocessing import MinMaxScaler
data = [[-1,2],[-0.5,6],[0,10],[1,18]]
import pandas as pd
pd.DataFrame(data)

在这里插入图片描述
② 实现归一化

scaler = MinMaxScaler()
scaler = scaler.fit(data)
result = scaler.transform(data)
# 归一化也能一步到位:result = scaler.fit_transform(data)
result

在这里插入图片描述

③ 使用inverse_transform将归一化结果逆转

scaler.inverse_transform(result)

在这里插入图片描述

④ 如果希望把归一化结果压缩在某个范围,则使用feature_range参数

scaler = MinMaxScaler(feature_range = (5,10))
result = scaler.fit_transform(data)
result

在这里插入图片描述

# 当data中的特征数量非常多的时候,fit就会报错并表示,数据量太大我计算不了
# 此时使用partial_fit作为训练接口
# scaler = scaler.partial_fit(data)

3.1.2 数据标准化(preprocessing.StandardScaler)

定义:当数据(x)按均值(μ)中心化后,再按标准差(o)缩放, 数据就会服从为均值为0,差为1的正态分布(即标准正态分布),这个过程,就叫做数据标准化(Standardization, 又称Z-score normalization),公式如下: .
在这里插入图片描述
步骤和归一化一样:
① 导包

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

② 查看属性

scaler.mean_ # 查看均值的属性mean_

在这里插入图片描述

scaler.var_ # 查看方差的属性var_

在这里插入图片描述

③ 实现标准化

x_std = scaler.transform(data) # 通过接口导出结果
x_std

在这里插入图片描述
④ 查看均值和方差
在这里插入图片描述
在这里插入图片描述
⑤ 使用fit_transform(data)一步达成结果

scaler.fit_transform(data)

在这里插入图片描述
⑥ 将标准化逆转

scaler.inverse_transform(x_std)

在这里插入图片描述

3.1.3 标准化和归一化对比

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

3.1.4 StandardScaler和MinMaxScaler选哪个?

看情况。大多数机器学习算法中,会选择StandardScaler来进行特征缩放,因为MinMaxScaler对异常值非常敏
感。在PCA,聚类,逻辑回归,支持向量机,神经网络这些算法中,StandardScaler往往是最好的选择。

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

建议先试试看StandardScaler,效果不好换MinMaxScaler。

3.2 处理缺失值

3.2.1 impute.SimpleImputer填补缺失值

在这里插入图片描述
其中四个重要参数的含义:
在这里插入图片描述

在这里,我们使用从泰坦尼克号提取出来的数据,这个数据有三个特征,一个数值型,两个字符型,标签也是字符型。
在这里插入图片描述
在这里插入图片描述
从图中可以看出Age字段缺失177个值,Embarked字段缺失两个值。
我们首先填补Age字段的缺失值

#填补年龄
Age = data.loc[:,"Age"].values.reshape(-1,1) #sklearn当中特征矩阵必须是二维

from sklearn.impute import SimpleImputer
imp_mean = SimpleImputer() #实例化,默认均值填补
imp_median = SimpleImputer(strategy="median") #用中位数填补
imp_0 = SimpleImputer(strategy="constant",fill_value=0) #用0填补

imp_mean = imp_mean.fit_transform(Age) #fit_transform一步完成调取结果
imp_median = imp_median.fit_transform(Age)
imp_0 = imp_0.fit_transform(Age)

#在这里我们使用中位数填补Age
data.loc[:,"Age"] = imp_median
data.info()

在这里插入图片描述

3.2.2 删除缺失值所在的行或列

pandas中dropna(axis=0, how=‘any’, thresh=None, subset=None, inplace=False))

参数:

axis:轴。0或'index',表示按行删除;1或'columns',表示按列删除。

how:筛选方式。‘any’,表示该行/列只要有一个以上的空值,就删除该行/列;
             ‘all’,表示该行/列全部都为空值,就删除该行/列。

thresh:非空元素最低数量。int型,默认为None。如果该行/列中,非空元素数量小于这个值,就删除该行/列。

subset:子集。列表,元素为行或者列的索引。如果axis=0或者‘index’,subset中元素为列的索引;如果axis=1或
者‘column’,subset中元素为行的索引。由subset限制的子区域,是判断是否删除该行/列的条件判断区域。

inplace:是否原地替换。布尔值,默认为False。如果为True,则在原DataFrame上进行操作,返回值为None。
data.dropna(inplace = True, axis = 0)

在这里插入图片描述

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

就是将文字型数据转化为数值型数据

3.3.1 preprocessing.LabelEncoder(标签专用)

① 导包

from sklearn.preprocessing import LabelEncoder
y = data.iloc[:,-1]
y

在这里插入图片描述
② 实例化并调取接口

le = LabelEncoder()
le = le.fit(y)
label = le.transform(y)
# 也可以一步到位:le.fit_transform(y)

③ 查看标签中有多少个类别

le.classes_

在这里插入图片描述
④ label中的值与③一 一对应
0代表No,1代表UnKnown,2代表Yes
在这里插入图片描述
⑤ 使用inverse_transform可以逆转

le.inverse_transform(label)

在这里插入图片描述
⑥ 把转化出来的值赋给原数据

data.iloc[:,-1] = label
data.head()

在这里插入图片描述
⑦ 也可以一步到位

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

3.3.2 preprocessing.OrdinalEncoder(特征专用)

① 导包

from sklearn.preprocessing import OrdinalEncoder
data_ = data.copy()
data_

在这里插入图片描述
② 查看所有文字型特征的类别

OrdinalEncoder().fit(data_.iloc[:,1:-1]).categories_

在这里插入图片描述
③ 转换文字型特征并赋值

data_.iloc[:,1:-1] = OrdinalEncoder().fit_transform(data_.iloc[:,1:-1])
data_.head(10)

在这里插入图片描述

3.3.3 preprocessing.OneHotEncoder(独热编码,创建哑变量)

有三种不同性质的分类数据:

1) 舱门(S,C,Q)
三种取值S,C,Q是相互独立的,彼此之间完全没有联系,表达的是S≠C≠Q的概念。这是名义变量
2) 学历(小学,初中,高中)
三种取值不是完全独立的,我们可以明显看出,在性质上可以有高中>初中>小学这样的联系,学历有高低,但是学历取值之间却不是可以计算的,我们不能说小学 + 某个取值 = 初中。这是有序变量
3) 体重(>45kg,>90kg,>135kg)
各个取值之间有联系,且是可以互相计算的,比如120kg - 45kg = 90kg,分类之间可以通过数学计算互相转换。这是有距变量

然而在对特征进行编码的时候,这三种分类数据都会被我们转换为[0,1,2],这三个数字在算法看来,是连续且可以
计算的,这三个数字相互不等,有大小,并且有着可以相加相乘的联系。所以算法会把舱门,学历这样的分类特
征,都误会成是体重这样的分类特征。这是说,我们把分类转换成数字的时候,忽略了数字中自带的数学性质,所
以给算法传达了一些不准确的信息,而这会影响我们的建模

类别OrdinalEncoder可以用来处理有序变量,但对于名义变量,我们只有使用哑变量的方式来处理,才能够尽量向算法传达最准确的信息:
在这里插入图片描述
这样的变化,让算法能够彻底领悟,原来三个取值是没有可计算性质的,是“有你就没有我”的不等概念。在我们的数据中,性别和舱门,都是这样的名义变量。因此我们需要使用独热编码,将两个特征都转换为哑变量。

① 导包

from sklearn.preprocessing import OneHotEncoder
x = data.iloc[:,1:-1]
enc = OneHotEncoder(categories='auto').fit(x)
result = enc.transform(x).toarray()
result

在这里插入图片描述
② 还原

pd.DataFrame(enc.inverse_transform(result))

在这里插入图片描述
③ 查看结果和对应的名称

enc.get_feature_names_out()

在这里插入图片描述
可以看到result五列的名称与下面一 一对应

④ 拼接result至原数据,并删除“Sex”、“Embarked”两列

#axis=1,表示跨行进行合并,也就是将量表左右相连,如果是axis=0,就是将量表上下相连
newdata = pd.concat([data, pd.DataFrame(result)],axis=1)
newdata.drop(["Sex","Embarked"],axis=1,inplace=True)
newdata

在这里插入图片描述
⑤ 修改列名称

newdata.columns = ['Age','Survived','Sex_female', 'Sex_male', 'Embarked_C', 'Embarked_Q', 'Embarked_S']
newdata

在这里插入图片描述

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

3.4.1 sklearn.preprocessing.Binarizer(二值化)

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

下面将年龄二值化:

data2 = data.copy()

from sklearn.preprocessing import Binarizer
x = data2.iloc[:,0].values.reshape(-1,1)
transform = Binarizer(threshold = 30).fit_transform(x)

data2.iloc[:,0] = transform
data2.head(30)

在这里插入图片描述

3.4.2 preprocessing.KBinsDiscretizer(分段)

将连续型变划分为分类变量的类(即分段),能够将连续型变量排序后按顺序分箱后编码。总共包含三个重要参数:
在这里插入图片描述

将年龄分段:

from sklearn.preprocessing import KBinsDiscretizer
X = data.iloc[:,0].values.reshape(-1,1)
est = KBinsDiscretizer(n_bins=3, encode='ordinal', strategy='uniform')
#查看转换后分的箱:变成了一列中的三箱
set(est.fit_transform(X).ravel())

在这里插入图片描述

把编码方式encode设置为“onehot”:

est = KBinsDiscretizer(n_bins=3, encode='onehot', strategy='uniform')
#查看转换后分的箱:变成了哑变量
est.fit_transform(X).toarray()

在这里插入图片描述

4、特征工程

特征提取(feature extraction)特征创造(feature creation)特征选择(feature selection)
从文字、图像、声音等其他非结构化数据中提取新信息作为特征。比如:从淘宝宝贝的名称中提取出产品类别、产品颜色、是否是网红产品等等把现有特征进行组合,或互相计算,得到新的特征。比如:我们有一列特征是速度,一列特征是距离,我们就可以通过让两列相除,创造新的特征:通过距离所花的时间。从所有特征中选择出有意义、对模型有帮助的特征,以避免必须将所有特征都导入模型中训练的情况

接下来,我们来看一下完整版的Titanic的数据特征:
在这里插入图片描述

其中Survived是我们的标签。很明显,以判断“Survived”为目的,Ticket(票号)、PassengerId(乘客编号)、Cabin(登船的舱门)明显是无关特征,可以直接删除。Name(姓名)、Pdass(舱位等级),也基本可以判断是相关性比较低的特征。Sex(性别)、Age(年龄)、船上的亲人数量,这些特征是相关性比较高的特征

所以,特征工程第一步就是:理解业务
我们有四种方法可以用来选择特征:过滤法、嵌入法、包装法、降维算法。

我们导入数据:
在这里插入图片描述
可以看到第一列是标签,其他列是特征,我们分别取出来
在这里插入图片描述
可以看到总共有42000条数据,784个维度。这个数据量非常夸张,如果使用支持向量机和神经网络,很可能直接跑不出来。如果使用KNN跑一次大概需要半个小时。
用这个数据举例,更能够体现特征工程的重要性。

4.1 特征选择

4.1.1 Filter过滤法

4.1.1.1 方差过滤

比如一个特征本身方差很小,就表示样本在这个特征上基本上没什么差异,可能特征中的大多数值都是一样的,甚至整个特征的取值都相同,那这个特征对于样本区分没什么太大作用。所以无论接下来的特征工程要做什么,都要优先消除方差为0或很小的特征。

4.1.1.1.1 VarianceThreshold

VarianceThreshold有个重要的参数threshold,表示方差的阈值,会舍弃所有方差小于threshold的特征,默认为0,即删除所有记录都相同的特征。

from sklearn.feature_selection import VarianceThreshold
selector = VarianceThreshold() # 实例化,不填默认为0
X_var = selector.fit_transform(x) # 获取删除不合格特征之后的新特征矩阵
X_var.shape

在这里插入图片描述
可以看到我们删除了方差为0的总共78个特征
如果我们只想取一半的特征,我们可以使threshold = ‘方差中位数’,即:

import numpy as np
X_fsvar = VarianceThreshold(threshold = np.median(x.var().values)).fit_transform(x)
X_fsvar.shape

在这里插入图片描述

4.1.1.1.2 方差过滤后对模型的影响

为了检验方差过滤后对模型的影响,这里分别用KNN和随机森林两种方法分别在方差过滤前和方差过滤后运行的效果和运行时间的对比。(KNN是K近邻算法中的分类算法,其原理是利用每个样本到其它样本点的距离来判断每个样本点的相似度,然后进行样本分类。KNN必出遍历每个特征和每个样本,因而特征越多,KNN的计算越缓慢)完整代码如下:

#KNN 和 随机森林在不同方差过滤效果下的对比
from sklearn.feature_selection import VarianceThreshold
from sklearn.ensemble import RandomForestClassifier as RFC
from sklearn.neighbors import KNeighborsClassifier as KNN
from sklearn.model_selection import cross_val_score
import numpy as np
x=data.iloc[:,1:]
y=data.iloc[:,0]
x_fsvar=VarianceThreshold(np.median(x.var().values)).fit_transform(x)

KNN-方差过滤前效果与运行时间

#KNN-方差过滤前效果
cross_val_score(KNN(),x,y,cv=5).mean()
%%timeit  #计算代码平均运行时间
cross_val_score(KNN(),x,y,cv=5).mean()

在这里插入图片描述

KNN-方差过滤后效果与运行时间

#KNN-方差过滤后效果
cross_val_score(KNN(),x_fsvar,y,cv=5).mean()
%%timeit  #计算代码平均运行时间
cross_val_score(KNN(),x_fsvar,y,cv=5).mean()

在这里插入图片描述

随机森林-方差过滤前效果与运行时间

#随机森林-方差过滤前效果
cross_val_score(RFC(n_estimators=10,random_state=0),x,y,cv=5).mean()
%%timeit  #计算代码平均运行时间
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()
%%timeit  #计算代码平均运行时间
cross_val_score(RFC(n_estimators=10,random_state=0),x_fsvar,y,cv=5).mean() 

在这里插入图片描述

根据以上结果,可以观察到随机森林的准确率略逊于KNN,但运行时间比KNN短。方差过滤后,随机森林和KNN的准确率都微弱上升 ,随机森林运行时间基本无明显变化,而KNN运行时间明显减少。

Q:为什么随机森林运行如此之快?为什么方差过滤对随机森林没有很大的影响?

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

A: 从算法原理上来说,传统决策树需要遍历所有特征,计算不纯度后进行分枝,而随机森
林却是随机抽取进行计算和分枝。因此随机森林的运算更快,过滤法对随机森林无用,对决
策树却有用。

在sklearn中,决策树和随机森林都是随机选择特征进行分枝,但决策树在建模过程中随机抽
取的特征数目却远远超过随机森林当中每棵树随机抽取的特征数目(比如对于这个780维的
数据,随机森林每棵树只会抽取10-20个特征,而决策树可能会抽取200~400个特征),因
此,过滤法对随机森林无用,却对决策树有用。也因此,在sklearn中,随机森林中的每棵树
都比单独的一棵决策树简单得多,高维数据下的随机森林的计算比决策树快很多。
4.1.1.1.3 方差过滤影响总结

过滤法的主要对象是:需要遍历特征或升维的算法
过滤法的主要目的是:在维持算法表现的前提下,帮助算法降低计算成本
在这里插入图片描述
在我们的对比中,我们使用的方差阈值是特征方差中的中位数,因此属于阈值比较大,过滤掉的特征比较多的情况。无论是KNN还是随机森林,在过滤掉一般特征之后,模型的精确度都上升了,这说明被我们过滤掉的特征在当前模式下大部分都是噪音,那我们就可以保留这个去掉了一半特征的数据,来为之后的特征选择做准备。当然,如果过滤之后模型的效果变差了,我们就可以认为,被我们过滤掉的特征中有很多都是有效特征,那就应当采取另一种方法进行特征选择。

4.1.1.2 相关性过滤

常用的来评判特征与标签之间的相关性方法有:卡方检验,F检验,互信息法

4.1.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
#假设这里需要300个特征
x_fschi=SelectKBest(chi2,k=300).fit_transform(x_fsvar,y)
x_fschi.shape

在这里插入图片描述
检验模型效果

#验证模型效果
cross_val_score(RFC(n_estimators=10,random_state=0),x_fschi,y,cv=5).mean()

在这里插入图片描述
此时模型的效果降低了,说明在设定k=300的时候删除了与模型相关且有效的特征,我们的k值设置得太小,需要重新调整k值或者放弃相关性过滤。

为了能够选择一个最优的超参数k,在这里可画出学习曲线:

%matplotlib inline
import matplotlib.pyplot as plt
score=[]
for i in range(390,200,-10):
    x_fschi=SelectKBest(chi2,k=i).fit_transform(x_fsvar,y)
    once=cross_val_score(RFC(n_estimators=10,random_state=0),x_fschi,y,cv=5).mean()
    score.append(once)
plt.plot(range(390,200,-10),score)
plt.show()

在这里插入图片描述

通过这条曲线,可以观察到,随着k值的不断增加,模型的表现不断上升,这说明,k越大越好,数据中所有的特征都是与特征相关的。

但是运行这条曲线的时间同样的也是非常长,接下来介绍一种更好的选择k的方法:看p值选择k

卡方检验的本质是推测两组数据间的差异,其检验的原假设是“两组数据是相互独立的”。卡方检验返回卡方值和p值两个统计量,其中卡方值很难界定有效的范围,而p值,一般使用0.05或0.01作为显著性特征水平,即p值判断的边界
在这里插入图片描述

chivalue,pvalues_chi=chi2(x_fsvar,y)
#print("卡方值",chivalue)
#print("p值",pvalues_chi)
#k取多少?我们相要消除所有p值大于设定值,比如0.05或0.01的特征:
#k代表我想保留的特征数量
k=chivalue.shape[0]-(pvalues_chi>0.05).sum()
print(k) #特征数量减删除的特征数量

在这里插入图片描述

k=392,说明所有p值都小于0.05,也就是方差过滤已经把所有和标签无关的特征都剔除了,或者中国数据集本身就不含与标签无关的特征。在这种情况下,舍弃任何一个特征,都会舍弃对模型有用的信息,而使模型表现下降。
接下来,继续试用其它相关性过滤的方法来验证。

4.1.1.2.2 F检验(既可分类,也可回归)

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

和卡方检验一样,这两个类需要和类SelectKBest连用,并且也可以直接通过输出的统计量来判断设置一个什么样的k合适。F检验在数据服从正态分布时效果会非常稳定,所以在使用F检验过滤时通常会先将数据转化为服从正态分布的方式

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

以F检验为例,继续在数据集上进行特征选择:

#F检验
from sklearn.feature_selection import f_classif
F,pvalues_f=f_classif(x_fsvar,y)
#print("F值",F)
#print("p值",pvalues_f)
k=F.shape[0]-(pvalues_f>0.05).sum()
print(k)

在这里插入图片描述
得到的结论和卡方过滤得到的结果一样,没有任何特征的p值大于0.05,所以有特征都与标签相关,因此不需要相关性过滤。

4.1.1.2.3 互信息法(既可分类,也可回归)

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

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

#互信息法
from sklearn.feature_selection import mutual_info_classif as MIC
result=MIC(x_fsvar,y) #得到互信息量的估计
#print(result)
(result>0).sum()

在这里插入图片描述

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

当然了,无论是F检验还是互信息法,大家也可以使用学习曲线,只是使用统计量的方法更加快速。

4.1.1.3 过滤法总结

通常先使用方差过滤,再使用互信息法来捕捉相关性。
在这里插入图片描述

4.1.2 Embedded嵌入法

嵌入法是一种让算法自己决定使用那些特征的方法,即特征选择和算法训练同时进行。在使用嵌入法时,我们使用某些机器学习的算法和模型进行训练,得到各个特征的权值系数,根据权值系数从大到小排列选择权值系数大的特征,这些权值系数往往代表了特征对于模型的某种贡献或某种重要性,比如决策树和随机森林中的feature_importances返回各个特征对树的建立的贡献,因此我们可以基于这种贡献的评估,找出对模型建立最有用的特征。相比于过滤法,嵌入法的结果会更加精确到模型的效用本身,对于提高模型效力有更好的结果。

缺点:
① 过滤法中使用的统计量可以使用统计知识和常识来查找范围(如p值应当低于显著性水平0.05),而嵌入法中使用的权值系数却没有这样的范围可找。当大量特征都对模型有贡献且贡献不一时,我们就很难去界定一个有效的临界值。这种情况下,模型权值系数就是我们的超参数,根据学习曲线或模型本身的某些性质去判断这个超参数的最佳值。

② 另外,嵌入法引入了算法来挑选特征,并且每次挑选都会使用全部特征,因此其计算速度也会和应用的算法有很大的关系。如果采用计算量很大,计算缓慢的算法,嵌入法本身也会非常耗时耗力。并且,在选择完毕之后,我们还是需要自己来评估模型。

feature_selection.SelectFromModel

sklearn.feature_selection.SelectFromModel(estimator,threshold=None,prefit=False,norm_order=1,max_features=None)
对于有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
data=pd.read_csv(r"digit recognizor.csv")
x=data.iloc[:,1:]
y=data.iloc[:,0]
x.shape  #(42000, 784)
RFC_=RFC(n_estimators=10,random_state=0) #随机森林的实例化
x_embedded=SelectFromModel(RFC_,threshold=0.005).fit_transform(x,y)
x_embedded.shape

在这里插入图片描述
可以看到,模型的维度明显降低了,但threshold具体取多少呢?我们可以画学习曲线来寻找最佳阈值

#画学习曲线来找最佳阈值
import numpy as np
from sklearn.model_selection import cross_val_score
import matplotlib.pyplot as plt
threshold=np.linspace(0,(RFC_.fit(x,y).feature_importances_).max(),20)  #linspace选取两者之间有限个数
# threshold
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()
print(score)
print(threshold)

在这里插入图片描述
根据图像可得,随着阈值的增加,模型的效果降低,被删除的特征越来越多,信息损失越来越大。当阈值在0.0134之前,模型的效果可以维持在0.93以上,因此我们可以在[0,0.0134]之间挑选一个数值验证模型效果。

x_embedded=SelectFromModel(RFC_,threshold=0.00067).fit_transform(x,y)
x_embedded.shape #(42000, 324)
cross_val_score(RFC_,x_embedded,y,cv=5).mean()

在这里插入图片描述
可以看出,特征个数瞬间缩小到324,小于特征个数的一半,且模型效果达到0.939。在第一条学习曲线后选定一个范围 [0,0.0134],在[0,0.0134]使用细化的学习来找到最佳值:

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.00007左右,模型效果超过0.94,取threshold = 0.00007,

x_embedded=SelectFromModel(RFC_,threshold=0.000070).fit_transform(x,y)
x_embedded.shape # (42000, 462)
cross_val_score(RFC_,x_embedded,y,cv=5).mean()

在这里插入图片描述
可以看到模型效果达到0.94,接下来再对随机森林的参数调参,准确率应该还可以再升高不少。可见,在嵌入法下,我们很容易就能够实现特征选择的目标:减少计算量,提升模型表现。

因此,比起要思考很多统计量的过滤法来说,嵌入法可能是更有效的一种方法。然而,过滤法的计算远远比嵌入法要快,所以大型数据中,我们还是会优先考虑过滤法,或者下面这种结合了过滤和嵌入法的方法:包装法Wrapper。

4.1.3 Wrapper包装法

包装法也是一个特征选择和算法训练同时进行的方法,与嵌入法类似,不同的是,包装法并不是自己输入某个评估指标或统计量的阈值。包装法在初始特征平台上训练评估器,并且通过coef_ 属性或feature_importances_属性获得每个特征的重要性。然后从当前的一组特征中修剪掉最不重要的特征,在修剪的集合上递归重复该过程,直到最终到达所需数量的要选择的特征。

feature_selection.RFE

最典型的目标函数是递归特征消除法(Recursive feature elimination,简写为RFE)。它是一种贪婪的优化算法,旨在找到性能最佳的特征子集。它反复创建模型,并在每次迭代时保留最佳特征或剔除最差特征,下一次迭代时,它会使用上一次建模中没有被选中的特征来构建下一个模型,直到所有特征都耗尽为止。然后,它根据保留或剔除特征的顺序进行排名,最终选出一个最佳子集。包装法的效果是所有特征选择方法中最利于提升模型表现的,它可以使用很少的特征达到很优秀的效果。

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

参数estimator是需要填写的实例化后的评估器,n_features_to_select的特征个数,step表示每次迭代中希望移除的特征个数。除此之外,RFE类有两个很重要的属性,.support_:返回所有的特征的是否最后被选中的布尔矩阵,.ranking_:返回特征的按数次迭代中综合重要性的排名。

#包装法
from sklearn.feature_selection import RFE
RFC_=RFC(n_estimators=10,random_state=0)
selector=RFE(RFC_,n_features_to_select=340,step=50).fit(x,y)
selector.support_
selector.ranking_
selector.support_.sum()
selector.ranking_
x_wrapper=selector.transform(x)
cross_val_score(RFC_,x_wrapper,y,cv=5).mean()

在这里插入图片描述

同样对包装法画学习曲线找到最佳的特征个数:

score3=[]
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()
    score3.append(once)
plt.figure(figsize=[20,5])
plt.plot(range(1,751,50),score3)
plt.xticks(range(1,751,50))
plt.show()

在这里插入图片描述

明显看出,在包装法应用50个特征时,模型的表现就可以达到90%以上,比嵌入法、过滤法高效。

4.1.4 总结

过滤法嵌入法包装法
定义过滤法(Filter Method)是一种基于特征本身的统计属性来选择特征的方法。它根据特征与目标变量之间的关联程度来进行选择。常用的过滤法有:卡方检验、相关系数、互信息等嵌入法(Embedded Method)是一种在模型训练过程中进行特征选择的方法。它根据学习器的训练过程来决定哪些特征是重要的。常用的嵌入法有:LASSO回归、岭回归、决策树等包装法(Wrapper Method)是一种基于学习器性能来选择特征的方法。它将特征选择看作是一个搜索问题,通过学习器的训练和评估来寻找最优的特征子集。常用的包装法有:递归特征消除(RFE)、前向选择(Forward Selection)、后向选择(Backward Selection)等。
优点计算简单,速度快考虑了特征之间的相互关系,能够找到最优特征子集,同时计算复杂度相对较低考虑了特征之间的相互关系,能够找到最优特征子集
缺点可能忽略特征之间的相互关系与特定的学习器相关,不具备通用性计算复杂度高,需要大量的计算资源和时间

总之,过滤法计算简单但可能不够准确;嵌入法在模型训练中完成特征选择;包装法搜索最优特征子集但计算开销大。选择哪种方法取决于具体的问题、数据特点和计算资源等因素。

4.2 特征创造

降维算法(PCA)

sklearn中降维算法都被包括在模块decomposition中,这个模块本质是一个矩阵分解模块。
在这里插入图片描述
在降维中,PCA使用的信息量衡量指标,就是样本方差,又称可解释性方差,方差越大,特征所带的信息量越多。
在这里插入图片描述

class sklearn.decomposition.PCA (n_components=None, copy=True, whiten=False, svd_solver=’auto’, tol=0.0,iterated_power=’auto’, random_state=None)

降维一般步骤:
在这里插入图片描述
在步骤3当中,我们用来找出n个新特征向量,让数据能够被压缩到少数特征上并且总信息量不损失太多的技术就是矩阵分解。PCA和SVD是两种不同的降维算法,但他们都遵从上面的过程来实现降维,只是两种算法中矩阵分解的方法不同,信息量的衡量指标不同罢了。PCA使用方差作为信息量的衡量指标,并且特征值分解来找出空间V。降维完成之后,PCA找到的每个新特征向量就叫做“主成分”,而被丢弃的特征向量被认为信息量很少,这些信息很可能就是噪音。

重要参数n_components

n_components是我们降维后需要的维度,即降维后需要保留的特征数量,降维流程中第二步里需要确认的k值,一般输入[0, min(X.shape)]范围中的整数。

一说到K,大家可能都会想到,类似于KNN中的K和随机森林中的n_estimators,这是一个需要我们人为去确认的超参数,并且我们设定的数字会影响到模型的表现。如果留下的特征太多,就达不到降维的效果,如果留下的特征太少,那新特征向量可能无法容纳原始数据集中的大部分信息,因此,n_components既不能太大也不能太小。那怎么办呢

可以先从我们的降维目标说起:如果我们希望可视化一组数据来观察数据分布,我们往往将数据降到三维以下,很多时候是二维,即n_components的取值为2。

在这里,我们选取鸢尾花数据集进行演示。

import matplotlib.pyplot as plt
from sklearn.datasets import load_iris
from sklearn.decomposition import PCA
iris = load_iris()
y = iris.target
X = iris.data

X.shape # (150,4)

# 建模
pca = PCA(n_components = 2)
pca = pca.fit(X)
X_dr = pca.transform(X)

X_dr.shape # (150,2)

 # 对三种鸢尾花分别绘图
plt.figure()
plt.scatter(X_dr[y==0, 0], X_dr[y==0, 1], c="red", label=iris.target_names[0])
plt.scatter(X_dr[y==1, 0], X_dr[y==1, 1], c="black", label=iris.target_names[1])
plt.scatter(X_dr[y==2, 0], X_dr[y==2, 1], c="orange", label=iris.target_names[2])
plt.legend()
plt.title('PCA of IRIS dataset')
plt.show()

在这里插入图片描述
鸢尾花的分布被展现在我们眼前了,明显这是一个分簇的分布,并且每个簇之间的分布相对比较明显,也许versicolor和virginia这两种花之间会有一些分类错误,但setosa肯定不会被分错。这样的数据很容易分类,可以遇见,KNN,随机森林,神经网络,朴素贝叶斯,Adaboost这些分类器在鸢尾花数据集上,未调整的时候都可以有95%上下的准确率。

探索降维后的数据:

#属性explained_variance,查看降维后每个新特征向量上所带的信息量大小(可解释性方差的大小)
pca.explained_variance_

#属性explained_variance_ratio,查看降维后每个新特征向量所占的信息量占原始数据总信息量的百分比
#又叫做可解释方差贡献率
#大部分信息都被有效地集中在了第一个特征上
pca.explained_variance_ratio_

pca.explained_variance_ratio_.sum()

在这里插入图片描述
可以得出:
新特征第一维所携带的信息量为4.22,第二维为0.24;
第一维的信息量与原始数据的关联度为92.4%,第二维为5.3%;
新特征矩阵所携带的信息量是原始数据的97.7%,也就是说,降维转化所损失的信息量只有2.3%。

选择最好的n_ components:累积可解释方差贡献率曲线

当参数components中不填写任何值,则默认返回min(X.shape)个特征,一般来说,样本量都会大于特征数目,所以什么都不填就相当于转换了新特征空间,但没有减少特征的个数。一般来说,不会使用这种输入方式。但我们却可以使用这种输入方式来画出累计可解释方差贡献率曲线,以此选择最好的n_components的整数取值。

累积可解释方差贡献率曲线是一条以降维后保留的特征个数为横坐标,降维后新特征矩阵捕捉到的可解释方差贡献率为纵坐标的曲线,能够帮助我们决定n_components最好的取值。

import numpy as np
pca_line = PCA().fit(X)
plt.plot([1,2,3,4],np.cumsum(pca_line.explained_variance_ratio_))
plt.xticks([1,2,3,4]) #这是为了限制坐标轴显示为整数
plt.xlabel("number of components after dimension reduction")
plt.ylabel("cumulative explained variance")
plt.show()

在这里插入图片描述
可知,n_ components最好的取值为2或3

最大似然估计自选超参数n_ components

勤奋智慧的数学大神Minka, T.P.在麻省理I学院媒体实验室做研究时找出了让PCA用最大似然估计(maximum likelihoodestimation)自选超参数的方法,输入"mle"作为n_ components的参数输入,就可以调用这种方法。

pca_mle = PCA(n_components="mle")
pca_mle = pca_mle.fit(X)
X_mle = pca_mle.transform(X)

X_mle.shape # (150,3)

pca_mle.explained_variance_ratio_.sum()

在这里插入图片描述
可以发现,mle为我们自动选择了3个特征。

按信息量占比选超参数

输入[0,1]之间的浮点数,并且让参数svd_solver ==‘full’,表示希望降维后的总解释性方差占比大于n_components指定的百分比,即是说,希望保留百分之多少的信息量。比如说,如果我们希望保留97%的信息量,就可以输入n_components = 0.97,PCA会自动选出能够让保留的信息量超过97%的特征数量。

pca_f = PCA(n_components = 0.97, svd_solver = "full")
pca_f = pca_f.fit(X)
X_f = pca_f.transform(X)

X_f.shape

在这里插入图片描述
可以看到,自动选择了两个特征

  • 12
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值