量化百科

机器学习复习笔记之预处理过程

由ypyu创建,最终由ypyu 被浏览 68 用户

无论在数据分析还是机器学习中,数据预处理都是非常麻烦枯燥但又必须去做的步骤。

其实数据预处理和特征工程,两者并无明显的界限,都是为了更好的探索数据集的结构,获得更多的信息,将数据送入模型中之前进行整理。可以说数据预处理是初级的特征处理,特征工程是高级的特征处理,也可以说这里的预处理过程是广义的,包含所有的建模前的数据预处理过程。

探索数据集(探索性数据分析EDA)

1、了解数据集的背景知识

(1)领域知识

(2)训练集、验证集、测试集同分布问题

验证集和测试集要同一分布,这是一个评估指标设定问题

比如你的验证集是美国房价,测试集是中国房价,验证集的指标是无效的。

训练集和验证集要同一分布,这是一个系统设计问题

比如猫分类器中,训练集是网页上得到的高清图片,测试集是用户上传的不清晰图片。

法一:shuffle,将其混在一起打乱,再重新划分训练集和测试集。

但是,我们希望测试集能够模拟现实中的情况,但这样的话测试集是混杂网页图片和用户上传图片而且大部分是网页图片,而现实应用中我们的分类器要处理的则是大量用户上传图片。所以法一不可行。

法二:将部分用户上传图片和网页图片混杂作为训练集,另一部分用户上传图片作为验证集和测试集。

2、对数据集的宏观探索

data1.head()  #前几条记录
data1.info()  #个数和类型
data1.describe()  #数据的统计信息

(1)数据集有多少条记录(样本),多少个特征(属性)?每个特征是什么含义?标签(目标属性)是什么?

——有无缺失值,涉及缺失值的处理问题;特征过多,可能需要采用PCA进行降维等。

(2)数据的各特征值是什么类型?是字符串吗?是离散的还是连续的?

——涉及数据类型转换问题,可能需要独热编码或者数据格式转换(如时间格式)

(3)探索数据的统计信息,最值、四分位数、均值、方差

——涉及是否需要归一化或标准化

3、数据可视化

——可参考matplotlib和seaborn库笔记

如果特征不是很多,可以尝试挨个变量进行观察(可视化)和处理。

单个变量:连续性、分布情况,包括偏度(skewness)和峰度(kurtosis)

——是否需要标准化,是否是偏斜数据集。

sns.distplot(train_raw['Sales'], kde=False) #分布图
train_raw['CompetitionOpenSinceMonth'].value_counts(dropna=False) #不同取值的数量

两个变量:相关性

——一般是和标签/目标变量的相关性或者目标变量的时间变化趋势。

sns.barplot(x='DayOfWeek', y='Sales', data=train_raw)  #条形图看相关性
plt.plot(train_raw['Date'],train_raw['Sales'])  #折线图看时间趋势

多个变量:可以简单回归一下

——初步判断一下哪几个特征比较多的解释了目标变量的变化。


数据预处理、特征工程

数据预处理

数据预处理过程是完全应对上一步探索数据集而来,甚至很多时候是边探索边顺手处理。

1、缺失值(Nan)处理

有缺失值的特征会给模型带来极大的噪音,对学习造成较大的干扰。

发现缺失值的时候,首先需要理解缺失背后的原因是什么:

是数据库的技术问题还是真正业务的原因导致它缺失?

如果是后者业务原因导致缺失,再来考虑怎么处理缺失值,处理缺失值的方法有两类:删除和填充。

(1)直接删除法

先看一下这个特征的缺失率如何:

data.isnull().sum  #统计所有各列各自共有多少缺失值

如果缺失的数量很多,而又没有证据表明这个特征很重要,那么可将这列直接删除,否则会因为较大的noise对结果造成不良影响。

比如设立阈值为1%,即将缺失值数量大于99%的特征删除:

train_B_info = train_B.describe()
meaningful_col = []
for col in train_B_info.columns: 
    if train_B_info.ix[0,col] > train_B.shape[0] * 0.01:
           meaningful_col.append(col)
train_B_1 = train_B[meaningful_col].copy()
# info的第一列是count,如果特征的取值缺失,就不会count,比如某特征count为1,而train_B.shape[0]即数据点个数(行数)为4000,即此特征有3999个缺失值。

如果缺失的数量非常少,而且数据不是时间序列那种必须连续的,那么可以将缺失值对应的样本删除:

test_raw[test_raw['Open'].isnull().values==True] #因为缺失的较少,可以观察某一列的缺失值的情况

(2)填充法

通过已有的数据对缺失值进行填补:针对数据的特点,选择用0、最大值、均值、中位数等填充。缺点是效果一般,因为等于人为增加了噪声。

# 特殊值填充
train_B_1 = train_B_1.fillna(-999)
# 中位数填充
store_df["CompetitionDistance"].fillna(store_df["CompetitionDistance"].median()) 
# 前向后向填充
data_train.fillna(method='pad')  #用前一个值填充
data_train.fillna(method='bfill')  #用后一个值填充
# 插值填补(通过两点估计中间点的值)
data_train.interpolate() 
# 可以指定方法:interpolate('linear'),线性填充

(3)建立一个模型“预测”缺失的数据

比如KNN, Matrix completion等方法,即用其他特征做预测模型来算出缺失特征。缺点是如果缺失变量与其他变量之间没有太大相关,那么仍然无法准确预测。

(4)把特征映射到高维空间(虚拟变量)

比如性别有男、女、缺失三种情况,则映射成3个变量:是否男、是否女、是否缺失。缺点是连续型变量维度过高,计算量很大,而且在数据量不足时会有稀疏性问题。

(5)选择不受缺失值影响的模型

2、异常值分析

异常值处理,既可以是一种算法(异常值检测,比如K近邻算法和DBSCAN),又可以是一个数据预处理过程。

Tukey的定义异常值的方法:一个异常阶(outlier step)被定义成1.5倍的四分位距(interquartile range,IQR)。一个数据点如果某个特征包含在该特征的IQR之外的特征,那么该数据点被认定为异常点。

发现异常值的时候,首先也需要理解异常背后的原因是什么:

可能是小小的操作失误(比如输入错误);也可能隐含着重要的意义(比如欺诈案中的可疑数据)。

异常值的处理:删除、对数转换

——异常值的寻找和判断是否移除是一个复杂的事情,这里会融入比较多的 domain knowledge,Tukey的定义只是方法之一。

扩展:不受异常值影响的算法

有的模型对异常点非常敏感,异常点的存在会影响模型的预测结果;有的模型对异常点不那么敏感。

3、格式转换

涉及对字符串类型的数值的处理,可以进行格式转换,或者独热编码。

比如字符串转换为时间格式;

data1['Date'] = pd.to_datetime(data1['Date'])

比如时间序列数据的平滑显示(按天显示变按月显示);

# 创建新数据集temp,将原数据集的时间列提取出来,作为新数据集的索引。然后对索引进行操作
index = data1['Date']
temp = data1['Sales']
temp.index = index
temp = temp.to_period('M')
temp.groupby(level=0).mean()
# level=0,以索引为依据进行分组

4、独热编码(One-Hot Encoding)

(1)直接替换(映射)

有时只需直接将非数值特征替换为数值特征:

比如标签有两种类别("<=50K"和">50K"),可以直接将他们编码成两个类0和1。

income = income_raw.replace(['<=50K','>50K'],[0,1])
# 也可以先在外面写成字典形式再传入replace函数

比如属性值之间存在序(order)的关系,高、中、低,可直接通过连续化编码为1,0.5,0。

(2)哑变量/虚拟变量(dummy variable)

将分类变量转换为“哑变量矩阵”或“指标矩阵”:

pandas.get_dummies() 函数:

dummies = pd.get_dummies(user_clu['constellation'])
user_clu = pd.merge(user_clu, dummies,left_index=True,right_index=True)
user_clu = user_clu.drop('constellation',1)

用 sklearn preprocessing 模块中的 LabelBinarizer 函数(标签二值化函数):

import numpy as np
from sklearn import preprocessing
# Example labels 示例 labels
labels = np.array([1,5,3,2,1,4,2,1,3])
# Create the encoder 创建编码器
lb = preprocessing.LabelBinarizer()
# Here the encoder finds the classes and assigns one-hot vectors  # 编码器找到类别并分配 one-hot 向量
lb.fit(labels)
# And finally, transform the labels into one-hot encoded vectors # 最后把目标(lables)转换成独热编码的(one-hot encoded)向量
lb.transform(labels)

5、特征缩放(scaling):归一化/标准化/对数转换

也就是所谓的无量纲化,使不同规格的数据转换到同一规格。

特征缩放分两种:一种是线性缩放,直接把数据压缩到0-1之间;另一种是如果数据不是正态分布的,尤其是数据的平均数和中位数相差很大的时候(表示特征取值非常歪斜),这时通常用一个非线性的缩放。(尤其是对于金融数据)

二者区别:一是对行处理,一是对列处理;一是线性缩放,一是非线性缩放。

扩展:不受特征缩放影响的算法

需要缩放的:logistic回归、SVM、K均值聚类

不需要缩放的:决策树、NB

5.1 归一化(normalization)(centering)

线性缩放,直接把数据压缩到0-1之间。

(1)sklearn中的最大最小值缩放器(MinMaxScaler)

from sklearn.preprocessing import MinMaxScaler
scaler = MinMaxScaler()
numerical = ['age', 'education-num', 'capital-gain', 'capital-loss', 'hours-per-week']
features_raw[numerical] = scaler.fit_transform(data[numerical])

(2)手动设置:

(x-np.min(x))/(np.max(x)-np.min(x)) 

5.2 标准化(standardization)

对于特征值的取值不均匀的特征,可将其取值分布转换为均值为 0 ,方差为1的正态分布。因为正态分布的简写也是norm,因此很多地方将数据的标准化同样叫normalization。

(1)Box-Cox变换/对数转换

将数据转换成对数,这样非常大和非常小的值不会对学习算法产生负面的影响。并且使用对数变换显著降低了由于异常值所造成的数据范围异常。

skewed = ['capital-gain', 'capital-loss']
features_raw[skewed] = data[skewed].apply(lambda x: np.log(x + 1))

——注意:因为0的对数是没有定义的,所以我们必须先将数据处理成一个比0稍微大一点的数以成功完成对数转换。

进行过对数转换的数据,预测完之后如需恢复:

np.expm1(predictions)
# 如果没有+1就是:np.exp

(2)sklearn中的方法

from sklearn.preprocessing import StandardScaler
StandardScaler().fit_transform(iris.data)

6、样本不均衡问题:倾斜数据集处理

——倾斜数据集与特征分布的偏斜不同

通过data.value_counts()函数可以看目标变量(标签)的不同取值的数量,如果两类样本,一类样本占绝大多数,另一类则占极少数,那么就是样本不均衡问题。现实中,比如处理信用卡欺诈问题(交易数据异常检测),就会面临这样一个情况。

——异常值问题,是特征或标签都可能异常,这里专指标签的取值异常。

**解决办法:**下采样和过采样。

(1)下采样(under sample)

让两类样本同样少。正例有1万条,负例有100条,那就从正例中选择100条和负例搭配。(仅针对训练过程而言,测试集还是用原来的未下采样的)

# Number of data points in the minority class
number_records_fraud = len(data[data.Class == 1])
fraud_indices = np.array(data[data.Class == 1].index)

# Picking the indices of the normal classes
normal_indices = data[data.Class == 0].index

# Out of the indices we picked, randomly select "x" number (number_records_fraud)
random_normal_indices = np.random.choice(normal_indices, number_records_fraud, replace = False)
random_normal_indices = np.array(random_normal_indices)

# Appending the 2 indices
under_sample_indices = np.concatenate([fraud_indices,random_normal_indices])

# Under sample dataset
under_sample_data = data.iloc[under_sample_indices,:]

(2)过采样(over sample):效果更好(数据一般越多越好)

让两类样本同样多。人工生成数据,比如生成1万条负例。

SMOTE(Synthetic Minority Oversampling Technique)算法:

对于每一个少数类中的样本x,按以下公式生成新样本:

$x_{new}^{(i)}=x^{(i)}+random(0,1)\times d^{(i)}$

d表示新样本到原始样本xi的距离。

# 需事先安装imblearn库
from imblearn.over_sampling import SMOTE
oversampler=SMOTE(random_state=0)
os_features,os_labels=oversampler.fit_sample(features_train,labels_train)

7、假阳性和假阴性这两种错误的重要性不同

比如边境的安检会犯两种错误,不是恐怖分子却当作恐怖分子审讯,是恐怖分子却当作正常人放行,无疑后者的错误更严重。

解决方法:赋予不同类样本不同的权重(或者使用不同的评估方法,见相关笔记)。比如赋予我们更关注的那类样本更大的权重,使其对最终结果的影响更大。比如一个算法有更高的对猫的识别率,但却会同时识别一些色情图片,也就是说它错误率低,但错误的代价比较大。解决方法是评估指标中给那些色情图片一个更大的权重,这样分错它们的惩罚就会更大。

lr=LogisticRegression(class_weight='balance')
# 如果效果不好,也可自己定义权重项
penalty={0:5,1:1}
lr=LogisticRegression(class_weight=penalty)

6和7的情况,分别对应着评估指标中的查准率和查全率。当然很多情况下,这两种情况是同时存在的,比如欺诈检测问题,一方面欺诈样本很少,是倾斜数据集,另一方面识别欺诈很重要,要赋予不同的权重。


特征工程

1、特征选择:添加与删除

特征太少,不足以描述数据,造成偏差过高;特征太多,一是增大计算成本,二是造成维度灾难(方差过高导致过拟合)。

爱因斯坦:“尽量让事情简单,但不能过于简单。”机器学习算法性能的上限,取决于特征的选择。

1.1、添加特征

添加列:

train['Year'] = Date.dt.year

数据集的合并:

train = pd.merge(data1, data2, how='left', on='Store')

1.2、删除特征

(1)经验法

依据 domain knowledge 或前人的经验筛选特征。比如id值、比如预测是否通过贷款,那么贷了多少款这个特征就无用。

三种等价的drop方法:

DF= DF.drop('column_name', 1);
DF.drop('column_name',axis=1, inplace=True)
DF.drop(DF.columns[[0,1,3]], axis=1, inplace=True)   # Note: zero indexed
# inplace为True(默认为False),那么原数组直接就被替换,否则需要new_DF = 。

(2)依据特征的相关性

通过相关性分析,移除特征相关性过高的特征(功能重复而耗费运行时间)。

可视化相关性矩阵(散点图矩阵),通过观察手动删除:

pd.plotting.scatter_matrix(data, alpha = 0.3, figsize = (14,8), diagonal = 'kde')

sklearn中的方法(直接用函数SelectKBest从相关性角度选择最好的特征):

from sklearn.feature_selection import SelectKBest
from scipy.stats import pearsonr
#选择K个最好的特征,返回选择特征后的数据
#第一个参数为计算评估特征是否好的函数,该函数输入特征矩阵和目标向量,输出二元组(评分,P值)的数组,数组第i项为第i个特征的评分和P值。在此定义为计算相关系数
#参数k为选择的特征个数
SelectKBest(lambda X, Y: array(map(lambda x:pearsonr(x, Y), X.T)).T, k=2).fit_transform(iris.data, iris.target)

另外sklearn还提供卡方检验、互信息等方法判断特征之间的相关性。

(3)依据特征的发散性(离散性)

比如方差越大,特征取值的离散性越大,如果方差接近0,就是说特征的取值基本上没有差异,那这个特征对于样本的分类就没有什么用。(极端情况就是所有取值均为一个数)

from sklearn.feature_selection import VarianceThreshold
#方差选择法,返回值为特征选择后的数据
#参数threshold为方差的阈值
VarianceThreshold(threshold=3).fit_transform(iris.data)

(4)依据特征的重要性

用可以输出特征重要性的算法(随机森林、XGBoost)输出特征重要性排序,按此来进行特征选择。

model = ensemble.AdaBoostClassifier(random_state=100)
model.fit(X_train,y_train)
# 提取特征重要性
importances = model.feature_importances_
# 特征重要性的可视化
importances_v = sorted(importances.items(), key=operator.itemgetter(1))
df = pd.DataFrame(importances_v, columns=['feature', 'fscore'])
df['fscore'] = df['fscore'] / df['fscore'].sum()
featp = df.plot(kind='barh', x='feature', y='fscore', legend=False, figsize=(4,8))
plt.title('Feature Importance')
plt.xlabel('relative importance')

2、特征转换:降维

降维和前面提到的特征选择某种程度是同一类问题:都要生成新的特征空间。比如依据相关性进行特征选择可以像上面那样手动删除,也可以降维,具体问题具体分析。

特征选择只是删除不重要的特征,生成原特征空间的最优子集。

而特征转换则可以将几个特征整合起来,形成新的特征(可能是隐藏特征或潜在特征,也可能是没有现实意义的特征)。

比如对于冗余特征(有了长和宽,面积就是冗余特征)。如果特征之间过于相关,而总的特征又过多,比如降维后方差捕获为原来的0.99,0.95,说明原数据中很多特征一定是强相关的,这时可以降维。

主要成分分析(Principle Component Analysis,PCA)

——见无监督学习笔记

独立成分分析(Independent Component Analysis,ICA)

随机成分分析(Random Component Analysis,RCA)

线性判别分析(Linear Discriminant Analysis)

不同的寻找投影的方式:基于标签进行判断(所以是有监督的)


3、特征提取(构建特征)

参考论文等。

标签

数据预处理特征工程机器学习