数据和特征决定了机器学习的上限,而模型和算法只是逼近这个上限而已。
1 缺失值
1.1 检查是否存在缺失值
#显示表格中每一列的信息
data.info()#直接判断是否存在空值
data.isnull()
data.isnull().sum(axis=1).sort_values(ascending=False)#计算每一列的缺失率
data.apply(lambda x:sum(x.isnull())/len(x))
1.2 缺失值的过滤
#删除缺失值
data.dropna(inplace=True)#根据具体要求进行更加精准的过滤
data.dropna(subset=['记录日期'],how='any',inplace=True) #删除缺失记录日期的数据项#直接删除某一列
data.drop(['记录日期'],axis=1,inplace=True)#直接删除某一行
data.drop(drop_record_index,inplace=True)
1.3 缺失值的填充
data.fillna(value=0,inplace=True)
#具体的填充值由我们自己根据具体的情况来设计
2 重复值
2.1 检查是否存在重复值
#观察是否存在重复值
data.duplicated()#详细计算重复值的个数
data.duplicated().sum()
2.2 重复值的过滤
#删除重复值,但是不修改原数据
data.drop_duplicates()#删除重复值,并修改原数据
data.drop_duplicates(inplace=True)
3 数据的无量纲化
在机器学习算法实践中,我们往往有着将不同规格的数据转换到同一规格,或者不同分布的数据转换到同一分布的需求,这种需求统称为无量纲化。无量纲化可以帮助我们提升模型精度,避免一个取值范围特别大的特征对距离计算造成的影响。
数据的无量纲化可以是线性的,也可以是非线性的。线性的无量纲化包括去中心化处理(Zero-centered或者Mean-subtraction)和缩放处理(Scale)。去中心化的本质是让所有记录减去一个固定值,即让数据样本平移到某个位置。缩放的本质是通过除以一个固定值,将数据固定在某个范围之中,取对数也是一种缩放处理。在数据的无量纲化的过程中往往是将去中心化和缩放这两种方法结合在一起使用。
常用的一种数据无量纲化的方法是将数据按照最小值中心化后,再按照极差进行缩放,数据会收缩到0到1之间,所以这个过程也叫做数据归一化(Normalization)。归一化之后的数据服从正太分布。
#数据准备
import pandas as pddata = [[-1,2],[-0.5,6],[0,10],[1,18]]
pd.DataFrame(data)
#数据的归一化
scaler = MinMaxScaler() #实例化
scaler = scaler.fit(data) #本质上是生成min(x)与max(x)
result = scaler.transform(data) #通过接口导出结果
result
#将归一化后的结果逆转
scaler.inverse_transform(result)
#使用feature_range实现将数据缩放到[0,1]之外的其他范围。
scaler = MinMaxScaler(feature_range=[5,10])
result = scaler.fit_transform(data)
result
另一种常用的方法是将数据减去均值,再按照标准差缩放,数据就会服从标准正太分布。这个过程也被叫做数据标准化(Standardization)
#数据标准化
from sklearn.preprocessing import StandardScalerscaler = StandardScaler() #实例化
scaler.fit(data) #本质是生成均值和方差
x_std = scaler.transform(data) #通过接口导出结果
x_std
4 类别型特征的编码
pandas和sklearn都提供了对类别型特征进行编码的方法。
#one-hot编码
#以data数据集中的婚姻状况这一特征为例
marital_du = pd.get_dummies(data['marital'],prefix='marital')
marital_du.head(10)
#映射
#以data数据集中的信贷违约这一特征为例,该特征主要有三种情况:违约、未违约、未知
default = data['default'].map({'no':-1,'unknown':0,'yes':1})
default.head()
5 连续型特征的离散化:二值化与分段
二值化:根据阈值将连续型数据二值化,大于阈值的值映射为1,小于阈值的值映射为0。可以采用sklearn.preprocessing中的Binarizer方法来实现。
分段:将某些连续型的数据分为不同的区间,便于模型训练。在平常情况下,我们可以采用sklearn.preprocessing中的KBinsDiscretizer方法来实现。
这些方法的具体使用方法可以参考scikit-learn中Preprocessing模块中的实例,在这里不在做多余介绍。
6 特征选择
特征选择的方法主要有三类:
- 过滤型 Filter
- 包裹型 Wrapper
- 嵌入型 Embeded
6.1 过滤型
6.1.1 方差过滤
通过特征本身的方差来对特征进行筛选。比如一个特征本身的方差很小,就表示样本在这个特征上没有什么差异,可能不同类别的样本在这个特征上体现不出什么区别,即这个特征对于区分样本没有什么作用。消除方差小于某个阈值的特征对模型训练是很有帮助的。方差过滤可以通过sklearn.feature_selection中的VarianceThreshold方法来实现。
#数据准备
X = [[0, 0, 1], [0, 1, 0], [1, 0, 0], [0, 1, 1], [0, 1, 0], [0, 1, 1]]
#方差过滤
from sklearn.feature_selection import VarianceThreshold
selector =VarianceThreshold() #实例化,不填参数阈值默认为0
x_var = selector.fit_transform(X)
6.1.2 相关性过滤
相关性过滤主要是通过某个指标去衡量特征与标签之间的相关性程度,并以此进行特征选择,过滤掉那些相关性不高的特征。其中,衡量相关性的主要方法有:
- 相关系数
- 假设检验:卡方检验、F检验、t检验
- 互信息值(信息增益)
#数据准备
from sklearn.datasets import load_iris
X, y = load_iris(return_X_y=True)
#相关性过滤
from sklearn.feature_selection import SelectKBest
from sklearn.feature_selection import chi2
X_new = SelectKBest(chi2, k=2).fit_transform(X, y)#参数选择:参数验证曲线
#假设我们的特征集X包含400个特征
%matplotlib inline
import matplotlib.pyplot as pltscore = []
for i in range(390,200,-10):x_fschi = SelectKBest(chi2,k=i).fit_transform(X, y)once = cross_val_score(RFC(n_estimators=10, random_state=0),x_fschi,y,cv=5).mean()score.append(once)
x_inx = range(390,200,-10)
plt.plot(x_inx,score)
plt.show()
6.2 包裹型
只有拥有coef_或者feature_importances_的模型才能使用包裹法来做特征选择。递归特征消除法基于权值系数或者特征重要性递归的剔除重要性程度低的特征,保留重要的特征,直到人为设定的特征数量(这个方法需要多次迭代计算,消耗的时间比较多)。
6.2 嵌入法
只有拥有特征系数coef_或者特征重要性feature_importances_的模型才能使用嵌入法进行特征选择。直接去掉权值系数或者特征重要性小于某个阈值的特征,这样做计算较快,但是无法直接控制保留特征的数量。
#数据准备
import pandas as pd
path = r'D:DataSetdigit_recognizertrain.csv'
data = pd.read_csv(path)
x = data.iloc[:,1:]
y = data.iloc[:,0]#模型准备,以随机森林为例
from sklearn.ensemble import RandomForestClassifier as RFC
RFC_ = RFC(n_estimators=10, random_state=0)#嵌入法
from sklearn.feature_selection import SelectFromModel
x_embedded = SelectFromModel(RFC_,threshold=0.005).fit_transform(x,y)
嵌入法在使用过程中需要我们人为的设定阈值,如何选择一个合适的阈值呢?
import numpy as np
import matplotlib.pyplot as plt#所有的权重取值
RFC_IM = RFC_.fit(x,y).feature_importances_#参数验证曲线
from sklearn.model_selection import cross_val_score
score = []
for i in threshold: #模型实例化并fit,得到经过挑选选后的特征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()
基于参数曲线,我们可以选择0.0067作为我们的阈值。
7 不均衡样本
7.1 不均衡样本的定义与问题
不均衡样本是指在分类任务中,不同类别的训练样本数目相差很大。往往样本量少的一类是模型学习比较关心的一类。基于不均衡样本建立的模型会倾向于将新的样本判断为多数类,即模型分类会更加偏向于多数类样本。而在很多场景下(如精准营销、信贷风控、癌症诊断)错判少数类样本往往比错判多数类样本造成的损失更大。另外,在进行分类模型的训练时往往会比较关注两类样本的边界特性,而样本的不均衡会导致两类样本的边界特性变得模糊,不利于分类。
7.2 不均衡样本的处理方法
不均衡样本的处理可以参考imbalanced-learn库中的方法。处理不均衡数据集最常用方的几类方法:
- 过采样(over-sampling)
- 欠采样(under-sampling)
- 组合采样法(combine)
- 集成方法(Ensemble methods)
7.2.1 过采样
- 随机过采样:从样本少的类别中随时抽样,再将抽样得来的样本添加到数据集中(相当于直接复制了原样本)。这种重复采样的方法有严重的弊端,可能会导致模型严重的过拟合。
- SMOTE:基于k个近邻在少数类样本中进行插值来生成新的样本。这样做容易带来两个问题:1.如果选取的少数类样本周围都是少数类样本,则新生成的样本不会对分类提供太多有用信息;2.如果选取的少数类样本周围都是多数类样本,这类样本可能是噪声,基于这个样本合成的新样本可能会周围多数类样本大部分重叠,导致分类困难。总的来说,我们总是希望新生成的少数类样本能处于两个类别的边界附近,为分类提供足够的信息。
- Border-line SMOTE:先对少数类样本进行分类:noise(所有的k近邻个样本都属于多数类)、danger(超过一半的k近邻样本属于多数类)、safe(低于一半的k近邻样本属于多数类)。然后只从处于danger状态的样本中随机选择,并使用SMOTE算法生成新的样本。
7.2.2 欠采样
- 随机欠采样:直接从多数类样本中随机抽取部分样本
- NearMiss:从多数类样本中选取最具代表性的样本进行训练。(比如选择到K近邻个少数类样本平均距离最近的多数类样本)
- Tomek link: 找不同类别样本之间距离最近的两个样本,即这两个样本分属不同类别且互为最近邻。在这种情况下,要么其中一个是噪声,要么两个样本都在边界附近。通过移除Tomek link来清洗掉类间重叠样本,使得互为最近邻的样本皆属于同一类别,从而能够更好的进行分类。使用这个方法时要注意,这个方式会同时删除多数类和少数类样本,并且无法控制欠采样的数量。
- Edited Nearest Neighbours(ENN):对于某个多数类样本,如果其k个近邻点超过一般都不属于多数类,这个样本很可能是噪声,则这个样本就会被剔除。这个方法也无法控制欠采样的数量。
7.2.3 组合采样法
- SMOTE+Tomek link:先使用SMOTE进行过采样,在此基础上使用Tomek link进行欠采样。
- SMOTE+ENN:先使用SMOTE进行过采样,在此基础上使用ENN进行欠采样。
7.2.4 集成方法
- EasyEnsemble:将多数类样本随机划分成n个子集,每个子集的数量等于少数类样本的数量,这相当于欠采样。接着将每个子集与少数类样本结合起来分别训练一个模型,最后将n个模型集成,这样虽然每个子集的样本少于总体样本,但集成后总信息量并不减少。
- BalanceCascade: 在训练过程集成学习基学习器的过程中不断剔除被分类正确的多数类样本,并用于下一轮训练,最终再将这些基学习器集成。(相当于是基学习器自动帮助我们筛选多数类样本)