文章目录
在机器学习过程中,数据预处理和特征工程十分重要。数据不给力,再高级的算法都没有用。依旧是根据菜菜的视频做的笔记。
1. 数据预处理
1.1 数据无量纲化
数据的无量纲化指的是样本特征的量纲不同时会导致不同的特征对结果的影响程度差异很大,为了减小这种差异并且加快求解速度,规范化数据范围,就得对数据进行无量纲化。
数据的无量纲化可以是线性的,也可以是非线性的。线性的无量纲化包括中心化(数据平移到某个位置)和缩放处理(数据固定在某个范围内)。常用的两种方式是数据归一化和数据标准化。
1.1.1 数据归一化
数据按最小值中心化后,再按极差(最大值-最小值)缩放,这个过程就叫数据归一化(Normalization,又称Min-Max Scaling),处理后的数据服从正态分布,计算公式如下:
x
∗
=
x
−
m
i
n
(
x
)
m
a
x
(
x
)
−
m
i
n
(
x
)
x^*=\frac {x-min(x)}{max(x)-min(x)}
x∗=max(x)−min(x)x−min(x)
sklearn实现:
from sklearn.preprocessing import MinMaxScaler
data = [[-1, 2], [-0.5, 6], [0, 10], [1, 18]]
import pandas as pd
pd.DataFrame(data)
# 实现归一化
# 默认是归一化到[0, 1]的范围内,参数可更改
scaler = MinMaxScaler()
scaler = scaler.fit(data) # 本质是生成min(x)和max(x)
result = scaler.transform(data) # 通过接口导出结果
# result_ = scaler.fit_transform(data) # 训练和导出结果一步达成
# 可以将归一化后的结果逆转还原
scaler.inverse_transform(result)
# 归一化到[0,1]以外的范围中
scaler = MinMaxScaler(feature_range=[5, 10])
result = scaler.fit_transform(data)
print(result)
# 当X中的特征数量非常多时,需要采用partial_fit训练接口
scaler = scaler.partial_fit(data)
numpy实现:
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_returned = X_nor * (X.max(axis=0) - X.min(axis=0)) + X.min(axis=0)
1.1.2 数据标准化
数据按均值中心化后,再按标准差缩放,数据就会服从均值为0,方差为1的正态分布,这就是数据标准化(又称Z-score normalization),公式如下:
x
∗
=
x
−
μ
σ
x^*=\frac {x-\mu}{\sigma}
x∗=σx−μ
sklean实现:
from sklearn.preprocessing import StandardScalar
scaler = StandardScaler()
scaler = scaler.fit(data) # 本质是生成均值和方差
scaler.mean_ # 查看均值的属性mean_
scaler.var_ # 查看均值的属性var_
x_std = scaler.transform(data) # 通过接口导出结果
# 查看均值和方差
x_std.mean() # 0
x_std.std() # 1
result = scaler.fit_transform(data) # 一步导出结果
scaler.inverse_transform(x_std)
在大多数机器学习算法中,会选择StandardScaler来进行特征缩放,因为MinMaxScaler对异常值很敏感。所以一般先试试StandardScaler,效果不好再考虑MinMaxScaler。
1.2 缺失值处理
填补缺失值包含以下方法:均值、众数、中位数、上/下条数据、KNN最邻近k个样本均值、随机森林预测填补等,以下链接总结的很好:
填补缺失值
数据分析之缺失值处理
sklearn实现:
import pandas as pd
data = pd.read_csv("csv数据集路径", index_col=0) # 告知第0列是索引,若无索引列则不需要
data.head()
# 可以查看空值的分布
data.info()
sklearn缺失值填充
# 切片提取Age特征列
Age = data.loc[:, "Age"].values.reshape(-1, 1) # sklearn中特征矩阵必须是二维,所以对于提取的一维特征需要reshape
from sklearn.impute import SimpleImputer
imp_mean = SimpleImputer() # 实例化,默认均值填补
imp_median = SimpleImputer(strategy="median") # 用中位数填补
imp_0 = SimpleImputer(strategy="constant", fill_value=0) # 用0填补
# 数字型用中位数填补
data.loc[:, "Age"] = imp_median
data.info()
# 字符型使用众数填补
Embarked = data.loc[:, "Embarked"].values.reshape(-1, 1)
imp_mode = SimpleImputer(strategy="most_frequent")
data.loc[:, "Embarked"] = imp_mode.fit_transform(Embarked)
data.info
# 有时候缺失值不是NAN而是其他的,在sklean中可设定指定值(如?)为缺失值
imp_0 = SimpleImputer(missing_value="?", strategy="mean")
sklearn还可以用随机森林来填补缺失值,这在另一篇笔记中有写,这里就不写了,可以点下面链接查看。
案例中的1.1:缺失值填补
pandas和numpy的缺失值填充和删除:
data.loc[:, "Age"] = data.loc[:, "Age"].fillna(data.loc[:,"Age"].median())
# 删除所有有缺失值的行.dropna(axis=0),删除所有有缺失值的列.dropna(axis=1)
# inplace为True表示直接在原数据上修改,False为生成一个复制对象,不修改原数据
data.dropna(axis=0, inplace=True)
根据条件删除包含缺失值的数据:
data.dropna(thresh=2, axis=1) # 删除缺失值大于2的列
# 删除全部为空值的行
data.dropna(how="all")
# 删除具体特征列中包含缺失值的样本行
data.dropna(subset=["列名"])
1.3 处理分类类型特征:编码与哑变量
在机器学习过程中,很多模型不能处理非数值型的数据,因此需要对非数值类型数据进行处理编码。
# 标签专用,能够将分类转换成分类数值,可以导入一维数组
from sklearn.preprocessing import LabelEncoder
y = data.iloc[:, -1]
# y = data.loc[:, "label"]
le = LabelEncoder()
le = le.fit(y)
label = le.transform(y)
le.classes_ # 查看标签有几类
le.fit_transform(y)
le.inverse_transform(label)
# 替换标签
data.iloc[:, -1] = label
# 特征专用,能够将分类特征转换成分类数值,不可导入一维数组,因此需要取几列非数值数据或者对一维数据进行reshape(适用于处理有联系,可以相互转换的字符特征,如:120kg这种数字特征)
from sklearn.preprocessing import OrdinalEncoder
data_ = data.copy()
print(data_.head())
OrdinalEncoder().fit(data_.iloc[:,1:-1].categories_)
data_.iloc[:,1:-1] = OrdinalEncoder().fit_transform(data_.iloc[:,1:-1])
print(data_.head())
# 对于非此即彼的或者是有序的字符特征,就需要采用独热编码
from sklearn.preprocessing import OneHotEncoder
X = data.iloc[:, 1, -1]
enc = OneHotEncoder(categories="auto").fit(X)
result = enc.transfotm(X).toarray()
# 还原
pd.DataFrame(enc.inverse_transform(result))
# 可以查看每个稀疏矩阵的特征名
enc.get_feature_names()
# axis=1表示跨行进行合并,也就是将量表左右相连,如果是axis=0就是上下相连
newdata = pd.concat([data, pd.DataFrame(result)], axis=1)
# Sex中的female和male两类被替代,以此类推
newdata.drop(["Sex", "Embarked"], axis=1, inplace=True)
newdata.columns = ["Age", "Survived", "Female", "male", "Embarked_C", "Embarked_Q", "Embarked_S"]
1.4 处理连续型特征:二值化与分段
二值化就是对连续数值进行映射。
# 将年龄二值化
data_2 = data.copy()
from sklearn.preprocessing import Binarizer
X = data_2.iloc[:,0].values.reshape(-1, 1)
transformer = Binarizer(threshold=30).fit_transform(X)
data_2.iloc[:,0] = transformer
# 分箱操作
from sklearn.preprocessing import KBinsDiscretizer
X = data.iloc[:,0].values.reshape(-1, 1)
est = KBinsDiscretizer(n_bins=3, encode="ordinal", strategy="uniform")
est.fit_transform(X)
# 查看转换后分的箱:变成了一列中的三箱
set(est.fit_transform(X).ravel())
est = KBinsDiscretizer(n_bins=3, encode="onehot", strategy="uniform")
# 查看转换后分的箱:变成了哑变量
est.fit_transform(X).toarray()
2. 特征选择
特征工程=特征提取+特征创造+特征选择。
特征选择首先需要根据对业务的理解,人为根据常识进行特征的筛选。
2.1 过滤法
过滤法通常用作预处理步骤,特征选择完全独立于任何机器学习算法。它是根据各种统计检验中的各项指标来选择特征。
2.1.1 方差过滤
方差过滤是一种预处理特征筛选,只要阈值不是设的太高,就有一定的作用。
import pandas as pd
data = pd.read_csv("csv数据集路径")
X = data.iloc[:, 1:]
X = data.iloc[:, 0]
from sklearn.feature_selection import VarianceThreshold
selector = VarianceThreshold() # 实例化,默认删除方差为0的特征
X_var0 = selector.fit_transform(X) # 获取删除不合格特征之后的新特征矩阵
print(X_var0.shape) # 查看降维后的特征708
import numpy as np
# 利用方差中位数作为阈值来删选特征
X_fsvar = VarianceThreshold(np.median(X.var().values())).fit_transform(X)
print(X_fsvar.shape) # 392
# 当特征是二分类时,特征的取值就是伯努利随机变量,这些变量的方差可以计算为:
# Var[X] = p(1-p)
# 二分类特征中某种分类占到80%以上的时候删除特征
x_bvar = VarianceThreshold(0.8*(1-0.8)).fit_transform(X)
print(x_bvar.shape) # 685
# 方差过滤后的效果对比
# KNN和随机森林对比
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_favar = VarianceThreshold(np.median(X.var().values())).fit_transform(X)
# KNN方差过滤前
cross_val_score(KNN(), X, y, cv=5).mean() # 0.9658...
cross_val_score(KNN(), X, y, cv=5).mean()
# KNN方差过滤后
cross_val_score(KNN(), x_fsvar, y, cv=5).mean() # 0.9659
cross_val_score(KNN(), X, y, cv=5).mean()
# 算法效率提高,精度也有所提高
# 随机森林方差过滤前
cross_val_score(RFC(n_estimator=10, random_state=0), X, y, cv=5).mean() # 0.9380.....
# 随机森林方差过滤后
cross_val_score(RFC(n_estimator=10, random_state=0), X, y, cv=5).mean() # 0.93880......
# 算法效率提高,精度也有所提高
# KNN运行时间很长,准确度会高一点
# 随机森林运行时间少
# 方差过滤主要对象是:需要遍历特征或升维度的算法,因此对随机森林影响不大
2.1.2 相关性过滤
统计特征同标签之间的联系,对特征进行筛选。
2.1.2.1 卡方过滤
针对离散标签,即分类问题的过滤方法。该过滤方法不可以处理负数,因此需要对特征先进行预处理。
from sklearn.feature_selection import SelectKBest
from sklearn.feature_selection import chi2
# 选前K个特征
X_fschi = SelectKBest(chi2, k=300).fit_transform(X_fsvar, y)
cross_val_score(RFC(n_estimators=10, random_state=0), X_fschi, y, cv=5).mean() # 0.93330有所降低
# 说明设定的k值太小,删除了与模型相关有效的特征,否则就是不适合卡方过滤
# 学习曲线选择k值
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()
# 若曲线为直线上升趋势,说明不需要进行卡方过滤,特征都与标签相关
# 通过p值选择k值,p<0.05说明特征和标签高度相关
# 卡方值和p值
chivalue, pvalues_chi = chi2(X_favar, y)
# 消除p值大于0.05或0.01的特征
k = chivalue.shape[0] - (pvalues_chi > 0.05).sum()
# 然后根据选定的k进行特征筛选
2.1.2.2 F检验过滤
F检验,又称ANOVA,方差齐性检验,是用来捕捉每个特征之间的线性关系的过滤方法。可以用于回归(feature_selection.f_classif),也可以用于分类(feature_selection.f_regression)。数据服从正态分布效果会比较好。同样也是选择p值小于0.05或者0.01的特征(与标签显著线性相关)。
from sklearn.feature_selection import f_classif
F, pvalues_f = f_classif(X_fsvar, y)
k = F.shape[0] - (pvalues_f > 0.05).sum()
# X_fsF = SlectKBest(f_classif, k=填写具体的k).fit_transform(X_fsvar, y)
# cross_val_score(RFC(n_estimators=10, random_state=0), X_fsF, y, cv=5).mean()
2.1.2.3 互信息法
互信息法是用来捕捉每个特征和标签之间的任意关系(包括线性关系和非线性关系)的过滤方法。可以做回归(feature_selection.mutual_info_regression)也可以做分类(feature_selection.mutual_info_classif)。不向上述两种方法,该过滤方法返回的是“每个特征和标签之间的互信息量的估计”,0表示两个变量相互独立,1表示两个变量完全相关。
from sklearn.feature_selection import mutual_info_classif as MIC
result = MIC(x_favar, y)
k = result.shape[0] - sum(result <= 0)
x_fsmic = SelectKBest(MIC, k=填写具体的k).fit_transform(X_fsvar, y)
cross_val_score(RFC(n_estimator=10, random_state=0), x_fsmic, y, cv=5).mean()
一般建议方差过滤+互信息法来进行特征选择。
2.2 Embedded嵌入法
特征选择和算法同时进行。用某些机器学习的算法和模型进行训练,得到各个特征的权值系数,根据权值系数从大到小选择特征。
随机森林嵌入为例:
from sklearn.feature_selection import SelectFromModel
from sklearn.ensemble import RandomForestClassifier as RFC
RFC_ = RFC(n_estimator=10, random_state=0)
X_embedded = SelectFromModel(RFC_, threshold=0.005).fit_transform(X, y)
# 在这里只能取出有限的特征,0.005这个阈值对于有780个特征的数据来说,是非常高的阈值,因为平均每个特征只能分到大约0.001的feature_importance_
print(X_embedded.shape)
# 绘制学习曲线寻找最佳阈值
RFC_.fit(X, y).feature_importance_
# 重要性在最大最小之间的20个
threshold = np.linspace(0, (RFC_.fit(X, y).feature_importance_).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()
# 选择一个合适的阈值计算
X_embedded = SelectFromModel(RFC_, threshold=0.00067).fit_transform(X, y)
print(X_embedded.shape)
cross_val_score(RFC_, X_embedded, y, cv=5).mean()
# 0.9399精度提升,特征减少到324个
# 因此可以进一步缩小范围绘制学习曲线,查看存在精度最高点的特征
threshold = np.linspace(0, 0.00134, 20)
score1 = []
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()
score1.append(once)
plt.figure(figsize=[20, 5])
plt.plot(threshold, score1)
plt.xticks(threshold)
plt.show()
# 选择一个据学习曲线查找到的最佳阈值计算
X_embedded = SelectFromModel(RFC_, threshold=0.000564).fit_transform(X, y)
print(X_embedded.shape)
cross_val_score(RFC_, X_embedded, y, cv=5).mean()
# 0.940精度提升,特征减少到340个
# 修改n_estimator,精度上升至0.963
cross_val_score(RFC(n_estimator=100, random_state=0), X_embedded, y, cv=5).mean()
逻辑回归的嵌入法在另一篇笔记中可以查看:
Logistic回归
2.3 包装法
包装法也是一个特征选择和算法训练同时进行的方法,与嵌入法相似。但是需要一个目标函数(最典型的是递归特征消除法,一种贪心算法)来进行特征选择,根据具体算法进行特征选择。包装法能够找到最少的特征使得模型精度较高。
from sklearn.feature_selection import RFE
RFC_ = RFC(n_estimator=10, random_state=0)
# n_features_to_select保留的特征的个数
# step是每次删选多少个特征
selector = RFE(RFC_, n_features_to_select=340, step=50).fit(X, y)
# support_返回所有特征是否被选择的bool矩阵
selector.support_.sum()
# 返回所有特征的综合重要性排名
selector.ranking_
X_wrapper = selector.transform(X)
cross_val_score(RFC_, X_wrapper, y, cv=5).mean()
# 绘制学习曲线
score = []
for i in range(1, 751, 50):
X_wrapper = RFE(RFC_, n_feature_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), score1)
plt.xticks(range(1, 751, 50))
plt.show()
使用模型为逻辑回归时,优先考虑嵌入法,使用支持向量机时,优先使用包装法。