数据预处理是从数据中检测,纠正或删除损坏,不准确或不适用于模型的记录的过程 可能面对的问题有:数据类型不同,比如有的是文字,有的是数字,有的含时间序列,有的连续,有的间断。也可能,数据的质量不行,有噪声,有异常,有缺失,数据出错,量纲不一,有重复,数据是偏态,数据量太
大或太小数据预处理的目的:让数据适应模型,匹配模型的需求
特征工程是将原始数据转换为更能代表预测模型的潜在问题的特征的过程,可以通过挑选最相关的特征,提取特征以及创造特征来实现。其中创造特征又经常以降维算法的方式实现。可能面对的问题有:特征之间有相关性,特征和标签无关,特征太多或太小,或者干脆就无法表现出应有的数据现象或无法展示数据的真实面貌
特征工程的目的:1) 降低计算成本,2) 提升模型上限
模块preprocessing:几乎包含数据预处理的所有内容
模块Impute:填补缺失值专用
模块feature_selection:包含特征选择的各种方法的实践
模块decomposition:包含降维算法
在机器学习算法实践中,我们往往有着将不同规格的数据转换到同一规格,或不同分布的数据转换到某个特定分布 的需求,这种需求统称为将数据“无量纲化”。数据的无量纲化可以是线性的,也可以是非线性的。线性的无量纲化包括中心化(Zero-centered或者Meansubtraction)处理和缩放处理(Scale)。中心化的本质是让所有记录减去一个固定值,即让数据样本数据平移到 某个位置。
当数据(x)按照最小值中心化后,再按极差(最大值 - 最小值)缩放,数据移动了最小值个单位,并且会被收敛到[0,1]之间,而这个过程,就叫做数据归一化(Normalization,又称Min-Max Scaling)。注意,Normalization是归一化,不是正则化,真正的正则化是regularization,不是数据预处理的一种手段。归一化之后的数据服从正态分布,公式如下:
在sklearn当中,我们使用preprocessing.MinMaxScaler来实现这个功能。MinMaxScaler有一个重要参数, feature_range,控制我们希望把数据压缩到的范围,默认是[0,1]。
axis = 0 按行计算,得到列的性质;axis = 1 按列计算,得到行的性质。
from sklearn.preprocessing import MinMaxScaler
import numpy as np
import pandas as pd
data = [[-1, 2], [-0.5, 6], [0, 10], [1, 18]]
#实现归一化
scaler = MinMaxScaler() #实例化
scaler = scaler.fit(data) #fit,这里本质是min(x)和max(x)
result = scaler.transform(data) #通过接口导出结果
print(result)
[[0. 0. ]
[0.25 0.25]
[0.5 0.5 ]
[1. 1. ]]
data = [[-1, 2], [-0.5, 6], [0, 10], [1, 18]]
scaler = MinMaxScaler(feature_range=[5, 10])
result = scaler.fit_transform(data)
#print(result)
'''
[[ 5. 5. ]
[ 6.25 6.25]
[ 7.5 7.5 ]
[10. 10. ]]
'''
#逆转归一化
data_2 = scaler.inverse_transform(result)
当数据(x)按均值(μ)中心化后,再按标准差(σ)缩放,数据就会服从为均值为0,方差为1的正态分布(即标准正态分 布),而这个过程,就叫做数据标准化(Standardization,又称Z-score normalization),公式如下:
对于StandardScaler和MinMaxScaler来说,空值NaN会被当做是缺失值,在fit的时候忽略,在transform的时候 保持缺失NaN的状态显示。
import numpy as np
import pandas as pd
from sklearn.preprocessing import StandardScaler
data = [[-1, 2], [-0.5, 6], [0, 10], [1, 18]]
scaler = StandardScaler() #实例化的时候不输入数据
scaler = scaler.fit(data) #fit,本质是生成均值和方差
print(scaler.mean_) #[-0.125 9. ]
print(scaler.var_) #[ 0.546875 35. ]
x_std = scaler.transform(data) #输出结果
print(x_std)
'''
[[-1.18321596 -1.18321596]
[-0.50709255 -0.50709255]
[ 0.16903085 0.16903085]
[ 1.52127766 1.52127766]]
'''
print(x_std.mean())
#0.0 ,导出的结果是一个数组,用mean()查看均值
print(x_std.std()) ##用std()查看方差
#1.0
result = scaler.fit_transform(data)
print(result)
'''
[[-1.18321596 -1.18321596]
[-0.50709255 -0.50709255]
[ 0.16903085 0.16903085]
[ 1.52127766 1.52127766]]
'''
#使用inverse_transform逆转标准化
data_1 = scaler.inverse_transform(x_std)
print(data_1)
'''
[[-1. 2. ]
[-0.5 6. ]
[ 0. 10. ]
[ 1. 18. ]]
'''
并且,尽管去量纲化过程不是具体的算法,但在fit接口中,依然只允许导入至少二维数 组,一维数组导入会报错。通常来说,我们输入的X会是我们的特征矩阵,现实案例中特征矩阵不太可能是一维所以不会存在这个问题。
大多数机器学习算法中,会选择StandardScaler来进行特征缩放,因为MinMaxScaler对异常值非常敏感。在PCA,聚类,逻辑回归,支持向量机,神经网络这些算法中,StandardScaler往往是最好的选择。
import pandas as pd
import numpy as np
from sklearn.impute import SimpleImputer
from sklearn.preprocessing import LabelEncoder
from sklearn.preprocessing import OrdinalEncoder
from sklearn.preprocessing import OneHotEncoder
from sklearn.preprocessing import Binarizer
from sklearn.preprocessing import KBinsDiscretizer
data = pd.read_csv('Narrativedata.csv', index_col=0)
#print(data)
#print(data.info())
0 Unnamed: 0 891 non-null int64
1 Age 714 non-null float64 #年龄缺失过多
2 Sex 891 non-null object
3 Embarked 889 non-null object
4 Survived 891 non-null object
Age = data.loc[:, 'Age'].values.reshape(-1, 1)
#print(Age) #我们需要将age列转换为二维数组,不然sklearn会报错
#实例化
imp_mean = SimpleImputer()
imp_median = SimpleImputer(strategy='median')
imp_0 = SimpleImputer(strategy='constant', fill_value=0)
#训练+输出
imp_mean = imp_mean.fit_transform(Age)
imp_median = imp_median.fit_transform(Age)
imp_0 = imp_0.fit_transform(Age)
#呆这里我们用中位数填补Age
data.loc[:, 'Age'] = imp_median
#print(data.info())
0 Age 891 non-null float64
1 Sex 891 non-null object
2 Embarked 889 non-null object
3 Survived 891 non-null object
embark = data.loc[:, 'Embarked'].values.reshape(-1, 1)
imp_mode = SimpleImputer(strategy='most_frequent')
data.loc[:, 'Embarked'] = imp_mode.fit_transform(embark)
#print(data.info())
0 Age 891 non-null float64
1 Sex 891 non-null object
2 Embarked 891 non-null object
3 Survived 891 non-null object
#或者直接使用pandas来进行填补
data.loc[:, 'Age'] = data.loc[:, 'Age'].fillna(data.loc[:, 'Age'].median())
data.dropna(axis=0, inplace=True)
'''
标签专用,能够将分类转换为分类数值
y = data.iloc[:, -1] #设置标签, 不是特征矩阵,所以允许是一维,如果是特征要转换为二维数组
#print(y)
lable = LabelEncoder().fit_transform(y)
#print(lable)
data.iloc[:, -1] = lable
#print(data.head())
特征专用,能够将分类特征转换为分类数值
data_ = data.copy()
x = data_.iloc[:, 1:-1]
X = OrdinalEncoder().fit_transform(x)
data_.iloc[:, 1:-1] = X
print(data_)
独热编码,创建哑变量
x = data.iloc[:, 1: -1]
enc = OneHotEncoder(categories='auto').fit(x)
result = enc.transform(x).toarray()
#print(result)
x_0 = OneHotEncoder(categories='auto').fit_transform(x).toarray()
#print(x_0)
new_data = pd.concat([data, pd.DataFrame(x_0)], axis=1)
#print(new_data)
#print(enc.get_feature_names()) #['x0_female' 'x0_male' 'x1_C' 'x1_Q' 'x1_S']
new_data.drop(['Sex', 'Embarked'], axis=1, inplace=True)
#print(new_data)
new_data.columns = ['Age', 'Survived', 'Femal', 'Male', 'Embarked_C', 'Embarked_Q', 'Embarked_S']
#print(new_data.head())
根据阈值将数据二值化(将特征值设置为0或1),用于处理连续型变量。大于阈值的值映射为1,而小于或等于阈值的值映射为0。默认阈值为0时,特征中所有的正值都映射到1。
data_2 = data.copy()
x = data_2.iloc[:, 0].values.reshape(-1, 1)
#类为特征专用,所以不能使用一维数组
#print(age)
transform = Binarizer(threshold=30).fit_transform(x)
这是将连续型变量划分为分类变量的类,能够将连续型变量排序后按顺序分箱后编码。总共包含三个重要参数:
x = data.iloc[:, 0].values.reshape(-1, 1)
age = KBinsDiscretizer(n_bins=3, encode='ordinal', strategy='uniform')
est = age.fit_transform(x)
#print(est)
#print(set(est.ravel())) #{0.0, 1.0, 2.0}
est_2 = KBinsDiscretizer(n_bins=3, encode='onehot', strategy='uniform').fit_transform(x).toarray()
print(est_2) #编码成功,总之,对于onehot来说就需要一个.toarray()
在做特征选择之前,有三件非常重要的事:跟数据提供者开会!跟数据提供者开会!跟数据提供者开会!
所以特征选择的第一步,其实是根据我们的目标,用业务常识来选择特征。
无论接下来的特征工程要做什么,都要优先消除方差为0的特征。VarianceThreshold有重要参数threshold,表示方差的阈值,表示舍弃所有方差小于threshold的特征,不填默认为0,即删除所有的记录都相同的特征。
import pandas as pd
import numpy as np
from sklearn.feature_selection import VarianceThreshold
from sklearn.ensemble import RandomForestClassifier as RFC
from sklearn.neighbors import KNeighborsClassifier as KNN
data = pd.read_csv('digit recognizor.csv')
#print(data)
#print(data.info())
#特征值真的太多了,多到爆诶
x = data.iloc[:, 1:]
y = data.iloc[:, 0]
#print(y)
#print(x.shape) #(42000, 784)
###Filter过滤法
##方差过滤 variance threshold
#无论接下来的特征工程要做什么,都要优先消除方差为0的特征
selector = VarianceThreshold() #实例化,不填参数默认方差为0
x_var0 = selector.fit_transform(x) #获取删除不合格特征之后的新特征矩阵
#print(x_var0.shape)
#(42000, 708)
print(x.var()) #会列出每一列的索引和方差值
print(x.var().values) #取出每一列的方差值
print(np.median(x.var().values)) #1352.286703180131
#将方差阈值设置为中位数
x_fsvar = VarianceThreshold(np.median(x.var().values)).fit_transform(x)
print(x_fsvar.shape) #(42000, 392)
#随机森林方差过滤之前
start_time = time.time()
rfc = RFC(n_estimators=10, random_state=0)
score = cross_val_score(rfc, x, y, cv=5).mean()
print(score)
#0.9373571428571429
finall_time = time.time()
print(finall_time - start_time)
#17.109983682632446
#随机森林方差过滤之后
start_time = time.time()
rfc_0 = RFC(n_estimators=10, random_state=0)
score_0 = cross_val_score(rfc_0, x_fsvar, y, cv=5).mean()
#print(score_0) #0.9390476190476191
finall_time = time.time()
#print(finall_time - start_time) #10.776141166687012
#可以看出来计算性能有所提高,计算时间减少勒,虽然分数提升不明显
当特征是二分类时,特征的取值就是伯努利随机变量,这些变量的方差可以计算为:
#若特征是伯努利随机变量,假设p=0.8,即二分类特征中某种分类占到80%以上的时候删除特征
x_bvar = VarianceThreshold(0.8 * (1 - 0.8)).fit_transform(x)
print(x_bvar.shape) #(42000, 685)
卡方过滤是专门针对离散型标签(即分类问题)的相关性过滤。卡方检验类feature_selection.chi2计算每个非负特征和标签之间的卡方统计量,并依照卡方统计量由高到低为特征排名。再结合feature_selection.SelectKBest这个可以输入”评分标准“来选出前K个分数最高的特征的类,我们可以借此除去最可能独立于标签,与我们分类目的无关的特征。
##卡方过滤
#假设在这里我需要300个特征
from sklearn.feature_selection import chi2
import time
x_fschi = SelectKBest(chi2, k=300).fit_transform(x_fsvar, y)
#print(x_fschi.shape) #(42000, 300)
score = cross_val_score(RFC(n_estimators=10, random_state=0), x_fschi, y, cv=5).mean()
print(score) #0.9344761904761905分数降低了
#选取超参数k
scores = []
for i in range(390, 200, -10):
x_fschi = SelectKBest(chi2, k=i).fit_transform(x_fsvar, y)
score = cross_val_score(RFC(n_estimators=10, random_state=0), x_fschi, y, cv=5).mean()
scores.append(score)
plt.figure(figsize=(16, 8))
plt.plot(range(390, 200, -10), scores)
plt.show()
通过这条曲线,我们可以观察到,随着K值的不断增加,模型的表现不断上升,这说明,K越大越好,数据中所有的
特征都是与标签相关的。但是运行这条曲线的时间同样也是非常地长,接下来我们就来介绍一种更好的选择k的方
法:看p值选择k。
卡方检验返回卡方值和P值两个统计量,其中卡方值很难界定有效的范围,而p值,我们一般使用0.01或0.05作为显著性水平,即p值判断的边界,具体我们可以这样来看:
从特征工程的角度,我们希望选取卡方值很大,p值小于0.05的特征,即和标签是相关联的特征。
from sklearn.feature_selection import chi2
from sklearn.feature_selection import SelectKBest
from matplotlib import pyplot as plt
#print(chivalues)
#print(pvalues_chi) #可以看出所有的的p值都为0,这说明方差验证已经把所有和标签无关的特征剔除了,或者这个数据集本身就不含与标签无关的特征
#k取多少?我们想要消除所有p值大于设定值,比如0.05或0.01的特征:
#print(chivalues.shape[0])
k = chivalues.shape[0] - (pvalues_chi > 0.05).sum()
print(k)
F检验,它即可以做回归也可以做分类,因此包含feature_selection.f_classif(F检验分类)和feature_selection.f_regression(F检验回归)两个类。
from sklearn.feature_selection import f_classif
#和卡方检验一样,这两个类需要和类SelectKBest连用,F检验的本质是寻找两组数据之间的线性关系,其原假设是”数据不存在显著的线性关系,我们希望选取p值小于0.05或0.01的特征,这些特征与标签时显著线性相关的
F, pvalues = f_classif(x_fsvar, y)
#print(F)
#print(pvalues)
k = F.shape[0] - (pvalues > 0.05).sum()
#print(k) #392
x_fc = SelectKBest(f_classif, k=392).fit_transform(x_fsvar, y)
score = cross_val_score(RFC(n_estimators=10, random_state=0), x_fc, y, cv=5).mean()
print(score)
#0.9390476190476191
互信息法是用来捕捉每个特征与标签之间的任意关系(包括线性和非线性关系)的过滤方法。和F检验相似,它既可以做回归也可以做分类,并且包含两个类feature_selection.mutual_info_classif(互信息分类)和feature_selection.mutual_info_regression(互信息回归)。这两个类的用法和参数都和F检验一模一样,不过互信息法比F检验更加强大,F检验只能够找出线性关系,而互信息法可以找出任意关系。
from sklearn.feature_selection import mutual_info_classif as MIC
result = MIC(x_fsvar, y)
k_2 = result.shape[0] - sum((result < 0))
#392
x_fsmic = SelectKBest(MIC, 392).fit_transform(x_fsvar, y)
score_1 = cross_val_score(RFC(n_estimators=10, random_state=0), x_fsmic, y, cv=5).mean()
print(score_1) #0.9390476190476191
嵌入法是一种让算法自己决定使用哪些特征的方法,即特征选择和算法训练同时进行。在使用嵌入法时,我们先使用某些机器学习的算法和模型进行训练,得到各个特征的权值系数,根据权值系数从大到小选择特征。这些权值系数往往代表了特征对于模型的某种贡献或某种重要性,比如决策树和树的集成模型中的feature_importances_属性,可以列出各个特征对树的建立的贡献,我们就可以基于这种贡献的评估,找出对模型建立最有用的特征。
###嵌入法
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_
#print(x_embedded.shape) #(42000, 47)
rfc = RFC_.fit(x, y)
max_feature_importance = RFC_.feature_importances_.max()
#print(max_feature_importance) #0.01276360214820271
thersholds = np.linspace(0, max_feature_importance, 20)
scores = []
for i in thersholds:
x_embedded = SelectFromModel(rfc, threshold=i).fit_transform(x, y)
score = cross_val_score(rfc, x_embedded, y, cv=10).mean()
scores.append(score)
plt.figure(figsize=(16, 8))
plt.plot(thersholds, scores)
plt.show()
from sklearn.feature_selection import SelectFromModel
from sklearn.ensemble import RandomForestClassifier as RFC
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_
#print(x_embedded.shape) #(42000, 47)
rfc = RFC_.fit(x, y)
max_feature_importance = RFC_.feature_importances_.max()
#print(max_feature_importance) #0.01276360214820271
scores = []
for i in np.linspace(0, 0.002, 20):
x_embedded = SelectFromModel(rfc, threshold=i).fit_transform(x, y)
score = cross_val_score(rfc, x_embedded, y, cv=10).mean()
scores.append(score)
plt.figure(figsize=(16, 8))
plt.plot(np.linspace(0, 0.002, 20), scores)
plt.xticks(np.linspace(0, 0.002, 20))
plt.show()
RFC_ = RFC(n_estimators=10, random_state=0)
rfc = RFC_.fit(x, y)
x_embedded = SelectFromModel(RFC_, threshold=0.000421).fit_transform(x, y)
score = cross_val_score(rfc, x_embedded, y, cv=10).mean()
print(score) #0.9432619047619049
RFC_ = RFC(n_estimators=100, random_state=0) #增加评估器的数量
rfc = RFC_.fit(x, y)
x_embedded = SelectFromModel(RFC_, threshold=0.000421).fit_transform(x, y)
score = cross_val_score(rfc, x_embedded, y, cv=10).mean()
print(score) #0.9649047619047618
包装法也是一个特征选择和算法训练同时进行的方法,与嵌入法十分相似,它也是依赖于算法自身的选择,比如coef_属性或feature_importances_属性来完成特征选择。但不同的是,我们往往使用一个目标函数作为黑盒来帮助我们选取特征,而不是自己输入某个评估指标或统计量的阈值。
RFC_ = RFC(n_estimators=100, random_state=0)
selector = RFE(RFC_, n_features_to_select=340, step=50).fit(x, y)
#选择340个特征值,每次略掉50个特征
#print(selector.support_.sum()) #.support_:返回所有的特征的是否最后被选中的布尔矩阵
#print(selector.ranking_) #返回特征的按数次迭代中综合重要性的排名
x_wrapper = selector.transform(x)
score = cross_val_score(RFC_, x_wrapper, y, cv=5).mean()
print(score) #0.9637857142857144
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()