转载自:菜菜的sklearn课堂
使用sklearn做单机特征工程
数据不给力,再高级的算法都没有用。
现实中的数据…
数据挖掘的五大流程:
若所用指标的值越大越好(正向指标:)
x i j ′ = x j − x min x max − x min x'_{ij}=\frac{x_j-x_{\min}}{x_{\max}-x_{\min}} xij′=xmax−xminxj−xmin
若所用指标的值越小越好(逆向指标:)
x i j ′ = x max − x j x max − x min x'_{ij}=\frac{x_{\max}-x_j}{x_{\max}-x_{\min}} xij′=xmax−xminxmax−xj
在sklearn当中,使用 preprocessing.MinMaxScaler 来实现这个功能。MinMaxScaler有一个重要参数,feature_range,控制数据压缩到的范围,默认是[0,1]。
from sklearn.preprocessing import MinMaxScaler
data = [[-1, 2], [-0.5, 6], [0, 10], [1, 18]]
import pandas as pd
pd.DataFrame(data)
'''
0 1
0 -1.0 2
1 -0.5 6
2 0.0 10
3 1.0 18
'''
'''
第一列 最大值 1 最小值-1
归一化
data[0,0] = -1-(-1)/[1-(-1)] =0
data[1,0] = -0.5-(-1)/2 = 0.25
data[2,0] = 1/2 = 0.5
data[3,0] = 2/2 = 1
'''
#实现归一化
scaler = MinMaxScaler() #实例化
scaler = scaler.fit(data) #fit,在这里本质是生成min(x)和max(x)
result = scaler.transform(data) #通过接口导出结果
result
'''
array([[0. , 0. ],
[0.25, 0.25],
[0.5 , 0.5 ],
[1. , 1. ]])
'''
result_ = scaler.fit_transform(data) #训练和导出结果一步达成
result_
scaler.inverse_transform(result) #将归一化后的结果逆转 还原
'''
array([[-1. , 2. ],
[-0.5, 6. ],
[ 0. , 10. ],
[ 1. , 18. ]])
'''
#使用MinMaxScaler的参数feature_range实现将数据归一化到[0,1]以外的范围中
data = [[-1, 2], [-0.5, 6], [0, 10], [1, 18]]
scaler = MinMaxScaler(feature_range=[5,10]) #依然实例化
result = scaler.fit_transform(data) #fit_transform一步导出结果
result
'''
array([[ 5. , 5. ],
[ 6.25, 6.25],
[ 7.5 , 7.5 ],
[10. , 10. ]])
'''
当X中的特征数量非常多的时候,fit会报错并表示,数据量太大了我计算不了,此时使用partial_fit作为训练接口
#scaler = scaler.partial_fit(data)
当然,也可以根据数学公式,编写程序计算
import numpy as np
X = np.array([[-1, 2], [-0.5, 6], [0, 10], [1, 18]])
#归一化
# 正向指标
X_nor = (X - X.min(axis=0)) / (X.max(axis=0) - X.min(axis=0))
X_nor
# 负向指标
X_neg = (X.max(axis=0)- X) / (X.max(axis=0) - X.min(axis=0))
X_neg
#逆转归一化 还原
X_returned = X_nor * (X.max(axis=0) - X.min(axis=0)) + X.min(axis=0)
X_returned
补充:
在实际应用中,通过梯度下降法求解的模型通常是需要归一化的,包括线性回归、逻辑回归、支持向量机、神经网络等模型。但对于决策树模型则并不适用,以C4.5为例,决策树在进行节点分裂时主要依据数据集D关于特征x的信息增益比,而信息增益比跟特征是否经过归一化是无关的,因为归一化并不会改变样本在特征x上的信息增益。
引用自《百面机器学习》
数据就会服从为均值为0,方差为1的正态分布(即标准正态分布)
x ∗ = x − μ σ x^*=\frac{x-\mu}{\sigma} x∗=σx−μ
from sklearn.preprocessing import StandardScaler
data = [[-1, 2], [-0.5, 6], [0, 10], [1, 18]]
scaler = StandardScaler() #实例化
scaler.fit(data) #fit,本质是生成均值和方差
scaler.mean_ #查看均值的属性mean_
scaler.var_ #查看方差的属性var_
x_std = scaler.transform(data) #通过接口导出结果
'''
array([[-1.18321596, -1.18321596],
[-0.50709255, -0.50709255],
[ 0.16903085, 0.16903085],
[ 1.52127766, 1.52127766]])
'''
x_std.mean() #导出的结果是一个数组,用mean()查看均值
# 0.0
x_std.std() #用std()查看方差
# 1.0
scaler.fit_transform(data) #使用fit_transform(data)一步达成结果
scaler.inverse_transform(x_std) #使用inverse_transform逆转标准化
对于StandardScaler和MinMaxScaler来说,空值NaN会被当做是缺失值,在fit的时候忽略,在transform的时候保持缺失NaN的状态显示。
标准化 和 归一化 选哪个?
看情况。大多数机器学习算法中,会选择标准化来进行特征缩放,因为归一化对异常值非常敏感。在PCA,聚类,逻辑回归,支持向量机,神经网络这些算法中,标准化往往是最好的选择。
归一化在不涉及距离度量、梯度、协方差计算以及数据需要被压缩到特定区间时使用广泛,比如数字图像处理中量化像素强度时,都会使用归一化将数据压缩于[0,1]区间之中。
建议先试试看标准化,效果不好换归一化
在sklearn当中,使用 impute.SimpleImputerr 来处理 缺失值
sklearn.impute.SimpleImputer (
missing_values=nan,
strategy=’mean’,
fill_value=None,
verbose=0,
copy=True)
import pandas as pd
data = pd.read_csv(r"C:\Narrativedata.csv",index_col=0)
data.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
'''
data.info()
'''
Int64Index: 891 entries, 0 to 890
Data columns (total 4 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 Age 714 non-null float64
1 Sex 891 non-null object
2 Embarked 889 non-null object
3 Survived 891 non-null object
dtypes: float64(1), object(3)
memory usage: 34.8+ KB
'''
#填补年龄
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)
imp_mean[:20]
imp_median[:20]
imp_0[:20]
#在这里我们使用中位数填补Age
data.loc[:,"Age"] = imp_median
data.info()
'''
Int64Index: 891 entries, 0 to 890
Data columns (total 4 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 Age 891 non-null float64
1 Sex 891 non-null object
2 Embarked 889 non-null object
3 Survived 891 non-null object
dtypes: float64(1), object(3)
memory usage: 34.8+ KB
'''
#使用众数填补Embarked
Embarked = data.loc[:,"Embarked"].values.reshape(-1,1) # 把有缺失的列取出,sklearn当中特征矩阵必须是二维
imp_mode = SimpleImputer(strategy = "most_frequent")
data.loc[:,"Embarked"] = imp_mode.fit_transform(Embarked)
data.info()
'''
Int64Index: 891 entries, 0 to 890
Data columns (total 4 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 Age 891 non-null float64
1 Sex 891 non-null object
2 Embarked 891 non-null object
3 Survived 891 non-null object
dtypes: float64(1), object(3)
memory usage: 34.8+ KB
'''
用Pandas和Numpy进行填补其实更加简单
import pandas as pd
data = pd.read_csv(r"C:\Narrativedata.csv",index_col=0)
data.info()
'''
Int64Index: 891 entries, 0 to 890
Data columns (total 4 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 Age 714 non-null float64
1 Sex 891 non-null object
2 Embarked 889 non-null object
3 Survived 891 non-null object
dtypes: float64(1), object(3)
memory usage: 34.8+ KB
'''
# 用中位数填充
data.loc[:,"Age"] = data.loc[:,"Age"].fillna(data.loc[:,"Age"].median())
data.info()
'''
Int64Index: 891 entries, 0 to 890
Data columns (total 4 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 Age 891 non-null float64
1 Sex 891 non-null object
2 Embarked 889 non-null object
3 Survived 891 non-null object
dtypes: float64(1), object(3)
memory usage: 34.8+ KB
'''
#.fillna 在DataFrame里面直接进行填补
data.dropna(axis=0,inplace=True)
#.dropna(axis=0)删除所有有缺失值的行,.dropna(axis=1)删除所有有缺失值的列
#参数inplace,为True表示在原数据集上进行修改,为False表示生成一个复制对象,不修改原数据,默认False
分类型型特征原始输入通常是字符串形式,除了决策树等少数模型能直接处理字符串形式的输入,对于逻辑回归、支持向量机等模型来说,分类型型特征必须经过处理转换成数值型特征才能正确工作。
比如说,学历的取值可以是[“小学”,“初中”,“高中”,“大学”],付费方式可能包含[“支付宝”,“现金”,“微信”]等等
将文字型数据转换为数值型
preprocessing.LabelEncoder:标签专用,能够将分类转换为分类数值
from sklearn.preprocessing import LabelEncoder
y = data.iloc[:,-1] #要输入的是标签,不是特征矩阵,所以允许一维
'''
0 No
1 Yes
2 Yes
3 Yes
4 No
...
886 No
887 Yes
888 No
889 Unknown
890 No
Name: Survived, Length: 891, dtype: object
'''
le = LabelEncoder() #实例化
le = le.fit(y) #导入数据
label = le.transform(y) # transform接口调取结果
label #查看获取的结果label
'''
array([0, 2, 2, 2, 0, 0, 0, 0, 2,...,2, 0, 1, 0])
'''
le.classes_ #属性.classes_查看标签中究竟有多少类别
'''
array(['No', 'Unknown', 'Yes'], dtype=object)
'''
data.iloc[:,-1] = label #让标签等于我们运行出来的结果
data.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
'''
le.fit_transform(y) #也可以直接fit_transform一步到位
le.inverse_transform(label) #使用inverse_transform可以逆转
等价
from sklearn.preprocessing import LabelEncoder
data.iloc[:,-1] = LabelEncoder().fit_transform(data.iloc[:,-1])
preprocessing.OrdinalEncoder:特征专用,能够将分类特征转换为分类数值
from sklearn.preprocessing import OrdinalEncoder
data_ = data.copy()
data_.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
'''
data_.info()
'''
Int64Index: 891 entries, 0 to 890
Data columns (total 4 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 Age 891 non-null float64
1 Sex 891 non-null object
2 Embarked 889 non-null object
3 Survived 891 non-null int32
dtypes: float64(1), int32(1), object(2)
memory usage: 31.3+ KB
'''
# 先删除有缺失值的行
data_.dropna(axis=0,inplace=True)
# 处理 Sex 和 Embarked
OrdinalEncoder().fit(data_.iloc[:,1:-1]).categories_
'''
[array(['female', 'male'], dtype=object), array(['C', 'Q', 'S'], dtype=object)]
'''
data_.iloc[:,1:-1] = OrdinalEncoder().fit_transform(data_.iloc[:,1:-1])
data_.head()
'''
Age Sex Embarked Survived
0 22.0 1.0 2.0 0
1 38.0 0.0 0.0 2
2 26.0 0.0 2.0 2
3 35.0 0.0 2.0 2
4 35.0 1.0 2.0 0
'''
OrdinalEncoder().fit(data_.iloc[:,1:-1]).categories_
'''
[array([0., 1.]), array([0., 1., 2.])]
'''
Sex 和 Embarked 已经转换为 分类数值型
独热编码通常用于处理类别间不具有大小关系的特征。
三种不同性质的分类数据:
然而在对特征进行编码的时候,这三种分类数据都会被我们转换为 [ 0, 1, 2],这三个数字在算法看来,是连续且可以计算的,这三个数字相互不等,有大小,并且有着可以相加相乘的联系。所以算法会把舱门,学历这样的分类特征,都误会成是体重这样的分类特征。这是说,我们把分类转换成数字的时候,忽略了数字中自带的数学性质,所以给算法传达了一些不准确的信息,而这会影响我们的建模。
类别OrdinalEncoder可以用来处理有序变量,但对于名义变量,只有使用哑变量的方式来处理,才能够尽量向算法传达最准确的信息:
性别和舱门等这样的名义变量。需要使用独热编码,将两个特征都转换为哑变量
preprocessing.OneHotEncoder:独热编码,创建哑变量
from sklearn.preprocessing import OneHotEncoder
data_ = data.copy()
# 先删除有缺失值的行,才不会报错
data_.dropna(axis=0,inplace=True)
X = data_.iloc[:,1:-1]
enc = OneHotEncoder(categories='auto').fit(X)
result = enc.transform(X).toarray()
result
'''
--------------↓-------------
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.]])
'''
# OneHotEncoder(categories='auto').fit_transform(X).toarray()
# 还原
pd.DataFrame(enc.inverse_transform(result))
enc.get_feature_names()
'''
array(['x0_female', 'x0_male', 'x1_C', 'x1_Q', 'x1_S'], dtype=object)
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.]])
第一列对应x0_female,第二列对应x0_male ...
'''
#左右合并data result
newdata = pd.concat([data_,pd.DataFrame(result)],axis=1)
newdata.head()
'''
Age Sex Embarked Survived 0 1 2 3 4
0 22.0 male S 0 0.0 1.0 0.0 0.0 1.0
1 38.0 female C 2 1.0 0.0 1.0 0.0 0.0
2 26.0 female S 2 1.0 0.0 0.0 0.0 1.0
3 35.0 female S 2 1.0 0.0 0.0 0.0 1.0
4 35.0 male S 0 0.0 1.0 0.0 0.0 1.0
'''
# 删除原来的
newdata.drop(["Sex","Embarked"],axis=1,inplace=True)
'''
Age Survived 0 1 2 3 4
0 22.0 0.0 0.0 1.0 0.0 0.0 1.0
1 38.0 2.0 1.0 0.0 1.0 0.0 0.0
2 26.0 2.0 1.0 0.0 0.0 0.0 1.0
3 35.0 2.0 1.0 0.0 0.0 0.0 1.0
4 35.0 0.0 0.0 1.0 0.0 0.0 1.0
'''
newdata.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.0 1.0 0.0 0.0 1.0
1 38.0 2.0 1.0 0.0 1.0 0.0 0.0
2 26.0 2.0 1.0 0.0 0.0 0.0 1.0
3 35.0 2.0 1.0 0.0 0.0 0.0 1.0
4 35.0 0.0 0.0 1.0 0.0 0.0 1.0
'''
对于类别取值较多的情况下使用独热编码需要注意以下问题。
(1)使用稀疏向量来节省空间。在独热编码下,特征向量只有某一维取值为1,其他位置取值均为0。因此可以利用向量的稀疏表示有效地节省空间,并且目前大部分的算法均接受稀疏向量形式的输入。
(2)配合特征选择来降低维度。高维度特征会带来几方面的问题。
一是在K近邻算法中,高维空间下两点之间的距离很难得到有效的衡量;二是在逻辑回归模型中,参数的数量会随着维度的增高而增加,容易引起过拟合问题;三是通常只有部分维度是对分类、预测有帮助,因此可以考虑配合特征选择来降低维度。
引用《百面机器学习》
引用《百面机器学习》
序号编码通常用于处理类别间具有大小关系的数据。例如成绩,可以分为低、中、高三档,并且存在“高>中>低”的排序关系。序号编码会按照大小关系对类别型特征赋予一个数值ID,例如高表示为3、中表示为2、低表示为1,转换后依然保留了大小关系。
二进制编码主要分为两步,先用序号编码给每个类别赋予一个类别ID,然后将类别ID对应的二进制编码作为结果。
二进制编码本质上是利用二进制对ID进行哈希映射,最终得到0/1特征向量,且维数少于独热编码,节省了存储空间。
根据阈值将数据二值化(将特征值设置为0或1),用于处理连续型变量。
大于阈值的值映射为1,而小于或等于阈值的值映射为0。
默认阈值为0时,特征中所有的正值都映射到1。二值化是对文本计数数据的常见操作,分析人员可以决定仅考虑某种现象的存在与否。它还可以用作考虑布尔随机变量的估计器的预处理步骤(例如,使用贝叶斯设置中的伯努利分布建模)。
转换前
import pandas as pd
data = pd.read_csv(r"C:\Narrativedata.csv",index_col=0)
data.head()
data.info()
data_1 = data.copy()
data_1 = pd.DataFrame(data_1)
# 先删除有缺失值的行,才不会报错
data_1 = data_1.dropna(axis=0)
data_1.reset_index(drop=True, inplace=True)
'''
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
.. ... ... ... ...
707 39.0 female Q No
708 27.0 male S No
709 19.0 female S Yes
710 26.0 male C Unknown
711 32.0 male Q No
[712 rows x 4 columns]
'''
转换后
#将年龄二值化
from sklearn.preprocessing import Binarizer
# 取出要二值化的特征——Age
X = data_1.iloc[:,0].values.reshape(-1,1) #类为特征专用,所以不能使用一维数组
# 小于或者等于30的为0,大于30的为1
transformer = Binarizer(threshold=30).fit_transform(X) # threshold=30为阈值
# 替换
data_1.iloc[:,0] = transformer
data_1
'''
Age Sex Embarked Survived
0 0.0 male S No
1 1.0 female C Yes
2 0.0 female S Yes
3 1.0 female S Yes
4 1.0 male S No
.. ... ... ... ...
707 1.0 female Q No
708 0.0 male S No
709 0.0 female S Yes
710 0.0 male C Unknown
711 1.0 male Q No
[712 rows x 4 columns]
'''
这是将连续型变量划分为分类变量的类,能够将连续型变量排序后按顺序分箱后编码。总共包含三个重要参数:
分为3箱 等宽分箱 每个特征的每个箱都被编码为一个整数
from sklearn.preprocessing import KBinsDiscretizer
import pandas as pd
data = pd.read_csv(r"C:\Narrativedata.csv",index_col=0)
data.head()
data.info()
data_1 = data.copy()
data_1 = pd.DataFrame(data_1)
# 先删除有缺失值的行,才不会报错
data_1 = data_1.dropna(axis=0)
data_1.reset_index(drop=True, inplace=True)
data_1
'''
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
.. ... ... ... ...
707 39.0 female Q No
708 27.0 male S No
709 19.0 female S Yes
710 26.0 male C Unknown
711 32.0 male Q No
[712 rows x 4 columns]
'''
#类为特征专用,所以不能使用一维数组
X = data_1.iloc[:,0].values.reshape(-1,1)
# 分为3箱 等宽分箱 每个特征的每个箱都被编码为一个整数
est = KBinsDiscretizer(n_bins=3, encode='ordinal', strategy='uniform')
est = est.fit_transform(X)
# 替换
data_1.iloc[:,0] = est
data_1
'''
Age Sex Embarked Survived
0 0.0 male S No
1 1.0 female C Yes
2 0.0 female S Yes
3 1.0 female S Yes
4 1.0 male S No
.. ... ... ... ...
707 1.0 female Q No
708 1.0 male S No
709 0.0 female S Yes
710 0.0 male C Unknown
711 1.0 male Q No
[712 rows x 4 columns]
'''
#查看转换后分的箱:变成了一列中的三箱
set(data_1.iloc[:,0]) # {0.0, 1.0, 2.0}
分为3箱 等宽分箱 独热编码
est = KBinsDiscretizer(n_bins=3, encode='onehot', strategy='uniform')
#查看转换后分的箱:变成了哑变量
est.fit_transform(X).toarray()
'''
array([[1., 0., 0.],
[0., 1., 0.],
[1., 0., 0.],
...,
[1., 0., 0.],
[1., 0., 0.],
[0., 1., 0.]])
'''
参考:《美团机器学习实战》
这里提到,对于带噪声的特征数据,去掉后效果可能反而不好,因此,我们在进行数据去噪时,应该分清楚哪些噪声可以去除或者必须去除,哪些又该保留。
除此之外,还提到,数据过滤方法有一个过滤曝光无效数据,或者过滤大部分曝光无效数据,保留少数未曝光数据,如上图。
引用机器学习python实战
特征选择是一个流程,能够选择有助于提高预测结果准确度的特征数据,或者有助于发现我们感兴趣的输出结果的特征数据。如果数据中包含无关的特征属性,会降低算法的准确度,对预测新数据造成干扰,尤其是线性相关算法(如线性回归算法和逻辑回归算法)。因此,在开始建立模型之前,执行特征选定有助于:
开始之前
是理解业务和数据含义,根据我们的目标,用业务常识来选择特征
确定判断目的,辨别无关特征,判断相关性比较低的特征、相关性比较高的特征
所以,特征工程的第一步是:理解业务。
若无法依赖对业务的理解来选择特征,有四种方法可以用来选择特征:过滤法,嵌入法,包装法,和降维算法。
准备数据
import pandas as pd
data = pd.read_csv(r"C:\digit recognizor.csv")
data.head()
'''
label pixel0 pixel1 pixel2 ... pixel780 pixel781 pixel782 pixel783
0 1 0 0 0 ... 0 0 0 0
1 0 0 0 0 ... 0 0 0 0
2 1 0 0 0 ... 0 0 0 0
3 4 0 0 0 ... 0 0 0 0
4 0 0 0 0 ... 0 0 0 0
[5 rows x 785 columns]
'''
# 有785个特征
X = data.iloc[:,1:] # 特征
y = data.iloc[:,0] # 标签
X.shape # (42000, 784)
过滤方法通常用作预处理步骤,特征选择完全独立于任何机器学习算法。它是根据各种统计检验中的分数以及相关性的各项指标来选择特征。
这是通过特征本身的方差来筛选特征的类。
比如一个特征本身的方差很小,就表示样本在这个特征上基本没有差异,可能特征中的大多数值都一样,甚至整个特征的取值都相同,那这个特征对于样本区分没有什么作用。所以无论接下来的特征工程要做什么,都要优先消除方差为0的特征。
VarianceThreshold有重要参数threshold,表示方差的阈值,表示舍弃所有方差小于threshold的特征,不填默认为0,即删除所有的记录都相同的特征。
from sklearn.feature_selection import VarianceThreshold
selector = VarianceThreshold() #实例化,不填参数默认方差为0
X_var0 = selector.fit_transform(X) #获取删除不合格特征之后的新特征矩阵
#也可以直接写成 X = VairanceThreshold().fit_transform(X)
X_var0.shape
(42000, 708)
若要删除一般的特征,可以用特征方差的中位数作为参数 threshold 的值
import numpy as np
# 特征方差中位数
yuzhi = np.median(X.var().values) # 1352.286703180131
X_fsvar = VarianceThreshold(yuzhi).fit_transform(X)
X_fsvar.shape
(42000, 392)
# 当特征是二分类时,特征的取值就是伯努利随机变量
#若特征是伯努利随机变量,假设p=0.8,即二分类特征中某种分类占到80%以上的时候删除特征
X_bvar = VarianceThreshold(.8 * (1 - .8)).fit_transform(X)
X_bvar.shape
方差过滤这样做了以后,对模型效果会有怎样的影响呢?
KNN vs 随机森林在不同方差过滤效果下的对比
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方差过滤前
cross_val_score(KNN(),X,y,cv=5).mean() # 0.9658569780264943
KNN方差过滤后
cross_val_score(KNN(),X_fsvar,y,cv=5).mean() # 0.9659997478150573
对于KNN,过滤后的效果十分明显:准确率稍有提升
随机森林方差过滤前
cross_val_score(RFC(n_estimators=10,random_state=0),X,y,cv=5).mean() # e.9380003861799541
随机森林方差过滤后
cross_val_score(RFC(n_estimators=10,random_state=0),X_fsvar,y,cv=5).mean() # e.9388098166696807
最近邻算法KNN,单棵决策树,支持向量机SVM,神经网络,回归算法,都需要遍历特征或升维来进行运算,所以他们本身的运算量就很大,需要的时间就很长,因此方差过滤这样的特征选择对他们来说就尤为重要。
过滤法的主要对象是:需要遍历特征或升维的算法
过滤法的主要目的是:在维持算法表现的前提下,帮助算法们降低计算成本。
对于不需要遍历特征的算法,比如随机森林,它随机选取特征进行分枝,本身运算就非常快速,因此特征选择对它来说效果平平
过滤法对随机森林无效,却对树模型有效?
从算法原理上来说,传统决策树需要遍历所有特征,计算不纯度后进行分枝,而随机森林却是随机选择特征进行计算和分枝,因此随机森林的运算更快,过滤法对随机森林无用,对决策树却有用
在sklearn中,决策树和随机森林都是随机选择特征进行分枝,但决策树在建模过程中随机抽取的特征数目却远远超过随机森林当中每棵树随机抽取的特征数目(比如说对于这个780维的数据,随机森林每棵树只会抽取10~20个特征,而决策树可能会抽取300~400个特征),因此,过滤法对随机森林无用,却对决策树有用
也因此,在sklearn中,随机森林中的每棵树都比单独的一棵决策树简单得多,高维数据下的随机森林的计算比决策树快很多。
如果过滤之后模型的效果反而变差了,我们就可以认为,被我们过滤掉的特征中有很多都有有效特征,那我们就放弃过滤,使用其他手段来进行特征选择。
现实中,一般只会使用阈值为0或者阈值很小的方差过滤,来优先消除一些明显用不到的特征,然后再选择更优的特征选择方法继续削减特征数量。
方差挑选完毕之后,要考虑下一个问题:相关性了。我们希望选出与标签相关且有意义的特征,因为这样的特征能够为我们提供大量信息。
评判特征与标签之间的相关性:卡方,F检验,互信息
卡方过滤是专门针对离散型标签(即分类问题)的相关性过滤。卡方检验类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 SelectKBest
from sklearn.feature_selection import chi2
#假设在这里我一直我需要300个特征
X_fschi = SelectKBest(chi2, k=300).fit_transform(X_fsvar, y)
X_fschi.shape # (42000, 392)
使用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 # (42000, 300)
# 验证一下模型的效果如何:
cross_val_score(RFC(n_estimators=10,random_state=0),X_fschi,y,cv=5).mean()
# 0.9344761904761905
如何设置一个最佳的K值?
(1)学习曲线
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(350,200,-10),score)
plt.show()
(2)p值选择k
卡方检验的本质是推测两组数据之间的差异,其检验的原假设是”两组数据是相互独立的”。
p值小于0.05的特征,即和标签是相关联的特征。
chivalue, pvalues_chi = chi2(X_fsvar,y)
chivalue
pvalues_chi
#k取多少?我们想要消除所有p值大于设定值,比如0.05或0.01的特征:
k = chivalue.shape[0] - (pvalues_chi > 0.05).sum()
X_fschi = SelectKBest(chi2, k).fit_transform(X_fsvar, y)
cross_val_score(RFC(n_estimators=10,random_state=0),X_fschi,y,cv=5).mean()
若所有特征的p值都是0,说明对于这个数据集来说,方差验证已经把所有和标签无关的特征都剔除了,或者这个数据集本身就不含与标签无关的特征。在这种情况下,舍弃任何一个特征,都会舍弃对模型有用的信息,而使模型表现下降。
F检验,又称ANOVA(方差分析),方差齐性检验,是用来捕捉每个特征与标签之间的线性关系的过滤方法。它即可以做回归也可以做分类,因此包含feature_selection.f_classif(F检验分类)和feature_selection.f_regression(F检验回归)两个类。其中F检验分类用于标签是离散型变量的数据,而F检验回归用于标签是连续型变量的数据。
F检验在数据服从正态分布时效果会非常稳定,因此如果使用F检验过滤,会先将数据转换成服从正态分布的方式。
F检验的本质是寻找两组数据之间的线性关系
选取p值小于0.05或0.01的特征,这些特征与标签时显著线性相关的
from sklearn.feature_selection import f_classif
F, pvalues_f = f_classif(X_fsvar,y)
F
pvalues_f
k = F.shape[0] - (pvalues_f > 0.05).sum()
X_fsF = SelectKBest(f_classif, k).fit_transform(X_fsvar, y)
cross_val_score(RFC(n_estimators=10,random_state=0),X_fsF,y,cv=5).mean()
互信息法是用来捕捉每个特征与标签之间的任意关系(包括线性和非线性关系)的过滤方法。
和F检验相似,它既可以做回归也可以做分类,并且包含两个类feature_selection.mutual_info_classif(互信息分类)和
feature_selection.mutual_info_regression(互信息回归)。
互信息法比F检验更加强大,F检验只能够找出线性关系,而互信息法可以找出任意关系。
互信息法不返回p值或F值类似的统计量,它返回“每个特征与目标之间的互信息量的估计”,这个估计量在[0,1]之间取值,为0则表示两个变量独立,为1则表示两个变量完全相关。
from sklearn.feature_selection import mutual_info_classif as MIC
result = MIC(X_fsvar,y)
k = result.shape[0] - sum(result <= 0)
X_fsmic = SelectKBest(MIC, k).fit_transform(X_fsvar, y)
cross_val_score(RFC(n_estimators=10,random_state=0),X_fsmic,y,cv=5).mean()
过滤法总结
嵌入法是一种让算法自己决定使用哪些特征的方法,即特征选择和算法训练同时进行。在使用嵌入法时,我们先使用某些机器学习的算法和模型进行训练,得到各个特征的权值系数,根据权值系数从大到小选择特征。这些权值系数往往代表了特征对于模型的某种贡献或某种重要性,找出对模型建立最有用的特征。
因此相比于过滤法,嵌入法的结果会更加精确到模型的效用本身,对于提高模型效力有更好的效果。并且,由于考虑特征对模型的贡献,因此无关的特征(需要相关性过滤的特征)和无区分度的特征(需要方差过滤的特征)都会因为缺乏对模型的贡献而被删除掉,可谓是过滤法的进化版。
sklearn.feature_selection.SelectFromModel (estimator, threshold=None, prefit=False, norm_order=1,max_features=None)
SelectFromModel是一个元变换器,可以与任何在拟合后具有coef_,feature_importances_属性或参数中可选惩罚项的评估器一起使用
对于有feature_importances_的模型来说,若重要性低于提供的阈值参数,则认为这些特征不重要并被移除。feature_importances_的取值范围是[0,1],如果设置阈值很小,比如0.001,就可以删除那些对标签预测完全没贡献的特征。如果设置得很接近1,可能只有一两个特征能够被留下。
使用随机森林为例,则需要学习曲线来帮助我们寻找最佳特征值。
# 导包
from sklearn.feature_selection import SelectFromModel
from sklearn.ensemble import RandomForestClassifier as RFC
# 建模
# 实例化
RFC_ = RFC(n_estimators =10,random_state=0)
# threshold 阈值
X_embedded = SelectFromModel(RFC_,threshold=0.005).fit_transform(X,y)
#在这里我只想取出来有限的特征。0.005这个阈值对于有780个特征的数据来说,是非常高的阈值,因为平均每个特征
#只能够分到大约0.001的feature_importances_
X_embedded.shape # (42000, 47)
#模型的维度明显被降低了
#同样的,我们也可以画学习曲线来找最佳阈值
import numpy as np
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.plot(threshold,score)
plt.show()
在嵌入法下,很容易就能够实现特征选择的目标:减少计算量,提升模型表现。
因此,比起要思考很多统计量的过滤法来说,嵌入法可能是更有效的一种方法。然而,在算法本身很复杂的时候,过滤法的计算远远比嵌入法要快,所以大型数据中,我们还是会优先考虑过滤法。
包装法也是一个特征选择和算法训练同时进行的方法,与嵌入法十分相似,它也是依赖于算法自身的选择,比如coef_属性或feature_importances_属性来完成特征选择。
往往使用一个目标函数作为黑盒来帮助我们选取特征,而不是自己输入某个评估指标或统计量的阈值。包装法在初始特征集上训练评估器,并且通过coef_属性或通过feature_importances_属性获得每个特征的重要性。然后,从当前的一组特征中修剪最不重要的特征。在修剪的集合上递归地重复该过程,直到最终到达所需数量的要选择的特征。
图中的“算法”,指的不是我们最终用来导入数据的分类或回归算法(即不是随机森林),而是专业的数据挖掘算法,即我们的目标函数。这些数据挖掘算法的核心功能就是选取最佳特征子集。
最典型的目标函数是递归特征消除法,是一种贪婪的优化算法,旨在找到性能最佳的特征子集。
包装法的效果是所有特征选择方法中最利于提升模型表现的,它可以使用很少的特征达到很优秀的效果。
包装法是最能保证模型效果的特征选择方法。不适用于太大型的数据
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
RFC_ = RFC(n_estimators =10,random_state=0)
selector = RFE(RFC_, n_features_to_select=340, step=50).fit(X, y)
selector.support_.sum() # .support_返回所有的特征的是否最后被选中的布尔矩阵
selector.ranking_ # 返回特征的按数次迭代中综合重要性的排名
X_wrapper = selector.transform(X)
cross_val_score(RFC_,X_wrapper,y,cv=5).mean()
经验来说,
过滤法更快速,但更粗糙。
包装法和嵌入法更精确,比较适合具体到算法去调整,但计算量比较大,运行时间长。
当数据量很大的时候,优先使用方差过滤和互信息法调整,再上其他特征选择方法。使用逻辑回归时,优先使用嵌入法。使用支持向量机时,优先使用包装法。迷茫的时候,从过滤法走起,看具体数据具体分析。
特征选择只是特征工程中的第一步。真正的高手,往往使用特征创造或特征提取来寻找高级特征。
引用:《数据竞赛入门》
在做数据竞赛的过程中,过滤式选择一般用在初期进行特征筛选,比如从原始数据集中筛选合适的特征子集;包裹式选择一般和模型的训练一起进行,通过模型的性能来筛选特征;嵌入式选择一般用于模型训练后进行特征筛选。
需要注意的是特性筛选具有一定的随机性,搜索空间非常大,所以并没有一个完美的解决方案。在做比赛过程中,验证特征是否有效的方式是包裹式选择,因为模型精度最终的目标,同时包裹式选择的流程可以根据模型来筛选特征,这也是我们需要的。
这里补充:《机器学习python实战》
实际上,前面已经做过,可学习前面的
统计分析可以用来分析选择对结果影响最大的数据特征。在scikit-learn中提供了SelectKBest
类,可以使用一系列统计方法来选定数据特征,是对卡方检验的实现。
import os
os.getcwd() # 查看当前工作目录
os.chdir(r"F:\MachineLearning-master\MachineLearning-master\chapter09") # 改变目录,注意双引号和反斜杠
# 通过卡方检验选定数据特征
from pandas import read_csv
from numpy import set_printoptions
from sklearn.feature_selection import SelectKBest
from sklearn.feature_selection import chi2
# 导入数据
filename = 'pima_data.csv'
names = ['preg', 'plas', 'pres', 'skin', 'test', 'mass', 'pedi', 'age', 'class']
data = read_csv(filename, names=names)
# 将数据分为输入数据和输出结果
array = data.values
X = array[:, 0:8]
Y = array[:, 8]
'''
array([[ 6. , 148. , 72. , ..., 33.6 , 0.627, 50. ],
[ 1. , 85. , 66. , ..., 26.6 , 0.351, 31. ],
[ 8. , 183. , 64. , ..., 23.3 , 0.672, 32. ],
...,
[ 5. , 121. , 72. , ..., 26.2 , 0.245, 30. ],
[ 1. , 126. , 60. , ..., 30.1 , 0.349, 47. ],
[ 1. , 93. , 70. , ..., 30.4 , 0.315, 23. ]])
'''
# 特征选定
test = SelectKBest(score_func=chi2, k=4)
fit = test.fit(X, Y)
set_printoptions(precision=3)
print(fit.scores_)
features = fit.transform(X)
print(features)
'''
[[148. 0. 33.6 50. ]
[ 85. 0. 26.6 31. ]
[183. 0. 23.3 32. ]
...
[121. 112. 26.2 30. ]
[126. 0. 30.1 47. ]
[ 93. 0. 30.4 23. ]]
'''
递归特征消除(RFE)使用一个基模型来进行多轮训练,每轮训练后消除若干权值系数的特征,再基于新的特征集进行下一轮训练。通过每一个基模型的精度,找到对最终的预测结果影响最大的数据特征。
在scikit-learn文档中有更多的关于递归特征消除(RFE)的描述。
下面的例子是以逻辑回归算法为基模型,通过递归特征消除来选定对预测结果影响最大的三个数据特征。代码如下:
import os
os.getcwd() # 查看当前工作目录
os.chdir(r"F:\MachineLearning-master\MachineLearning-master\chapter09") # 改变目录,注意双引号和反斜杠
# 通过递归消除来选定特征
from pandas import read_csv
from sklearn.feature_selection import RFE
from sklearn.linear_model import LogisticRegression
# 导入数据
filename = 'pima_data.csv'
names = ['preg', 'plas', 'pres', 'skin', 'test', 'mass', 'pedi', 'age', 'class']
data = read_csv(filename, names=names)
# 将数据分为输入数据和输出结果
array = data.values
X = array[:, 0:8]
Y = array[:, 8]
'''
array([[ 6. , 148. , 72. , ..., 33.6 , 0.627, 50. ],
[ 1. , 85. , 66. , ..., 26.6 , 0.351, 31. ],
[ 8. , 183. , 64. , ..., 23.3 , 0.672, 32. ],
...,
[ 5. , 121. , 72. , ..., 26.2 , 0.245, 30. ],
[ 1. , 126. , 60. , ..., 30.1 , 0.349, 47. ],
[ 1. , 93. , 70. , ..., 30.4 , 0.315, 23. ]])
'''
# 特征选定
model = LogisticRegression()
rfe = RFE(model, 3)
fit = rfe.fit(X, Y)
print("特征个数:")
print(fit.n_features_) # 3
print("被选定的特征:") # [ True False False False False True True False]
print(fit.support_)
print("特征排名:")
print(fit.ranking_) # [1 2 4 5 6 1 1 3]
RFE选定了preg、mass和pedi三个数据特征,它们在support_中被标记为True,在ranking_中被标记为1。
import os
os.getcwd() # 查看当前工作目录
os.chdir(r"F:\MachineLearning-master\MachineLearning-master\chapter09") # 改变目录,注意双引号和反斜杠
# 通过主要成分分析选定数据特征
from pandas import read_csv
from sklearn.decomposition import PCA
# 导入数据
filename = 'pima_data.csv'
names = ['preg', 'plas', 'pres', 'skin', 'test', 'mass', 'pedi', 'age', 'class']
data = read_csv(filename, names=names)
# 将数据分为输入数据和输出结果
array = data.values
X = array[:, 0:8]
'''
array([[ 6. , 148. , 72. , ..., 33.6 , 0.627, 50. ],
[ 1. , 85. , 66. , ..., 26.6 , 0.351, 31. ],
[ 8. , 183. , 64. , ..., 23.3 , 0.672, 32. ],
...,
[ 5. , 121. , 72. , ..., 26.2 , 0.245, 30. ],
[ 1. , 126. , 60. , ..., 30.1 , 0.349, 47. ],
[ 1. , 93. , 70. , ..., 30.4 , 0.315, 23. ]])
'''
Y = array[:, 8]
# 特征选定
pca = PCA(n_components=3)
fit = pca.fit(X)
print("解释方差:%s" % fit.explained_variance_ratio_) # 解释方差:[0.889 0.062 0.026]
print(fit.components_)
'''
[[-2.022e-03 9.781e-02 1.609e-02 6.076e-02 9.931e-01 1.401e-02
5.372e-04 -3.565e-03]
[-2.265e-02 -9.722e-01 -1.419e-01 5.786e-02 9.463e-02 -4.697e-02
-8.168e-04 -1.402e-01]
[-2.246e-02 1.434e-01 -9.225e-01 -3.070e-01 2.098e-02 -1.324e-01
-6.400e-04 -1.255e-01]]
'''
袋装决策树算法(Bagged Decision Tress)、随机森林算法和极端随机树算法都可以用来计算数据特征的重要性。
import os
os.getcwd() # 查看当前工作目录
os.chdir(r"F:\MachineLearning-master\MachineLearning-master\chapter09") # 改变目录,注意双引号和反斜杠
# 通过决策树计算特征的重要性
from pandas import read_csv
from sklearn.ensemble import ExtraTreesClassifier
# 导入数据
filename = 'pima_data.csv'
names = ['preg', 'plas', 'pres', 'skin', 'test', 'mass', 'pedi', 'age', 'class']
data = read_csv(filename, names=names)
# 将数据分为输入数据和输出结果
array = data.values
X = array[:, 0:8]
Y = array[:, 8]
# 特征选定
model = ExtraTreesClassifier()
fit = model.fit(X, Y)
print(fit.feature_importances_) # [0.111 0.237 0.1 0.079 0.074 0.136 0.116 0.146]
lst_dict = dict(zip(names,[0.111 ,0.237 ,0.1 , 0.079 ,0.074, 0.136, 0.116, 0.146]))
print(lst_dict)
# {'preg': 0.111, 'plas': 0.237, 'pres': 0.1, 'skin': 0.079, 'test': 0.074, 'mass': 0.136, 'pedi': 0.116, 'age': 0.146}
参考:《美团机器学习实战》