简介
在介绍比较复杂的数据类型比如图像和文本数据类似之前,我们首先从最简单的数据类似开始,即:数值类型。我们收集到的数据中,数值类型数据占据了大部分。比如商品价格、人口数量、物品编号、传感器的数据、交通流量等等,几乎都是数值类型。数值类型数据可以直接输入到大部分模型中,但是这并不意味着对于数值类型的特征工程就不重要,好的特征提取方式是可以深挖隐藏在数据背后更深层次的信息的。其次,数值类型数据也并不是直观看上去那么简单易用,因为不同的数值类型的计量单位不一样,比如个数、公里、千克、DB、百分比之类,同样数值的大小也可能横跨好几个量级,比如小到头发丝直径约为0.00004米, 大到热门视频播放次数成千上万次。那么如何处理数值类型数据量纲以及分布的差异呢?这便是本文要重点讨论的地方。
数据尺度
输入数据的尺度差异是我们需要首先关注的问题,因为输入变量的尺度差异可能会增加模型拟合的困难程度。我们一般需要考虑输入数据中的最大值是多少?最小值是多少?它们之间差了几个数量级?这些问题很重要,因为某些模型对数据的尺度很敏感。举个例子,是一个简单的线性模型,其输出直接依赖于输入变量,很显然,输入越大,输出也就越大。
较大的输入会导致模型学习到更大的权重参数,而具有较大参数的模型通常是不稳定的,一般会导致较大的泛化误差,这也就是为什么训练的时候一般需要加正则化的原因之一。输入数据尺度之间的差异也并不是会影响到所有的模型,一般来说,利用输出数据的权重和来拟合模型的算法一般会受到影响,比如线性回归,逻辑回归以及深度神经网络。同样,计算样本之间距离的算法也会收到影响,比如KNN算法、SVM等等。但是对于决策树模型则并不适用,以C4.5为例,决策树在进行节点分裂时主要依据数据集D关于特征x的信息增益比,而信息增益比跟特征的尺度是无关的。
上面只是讨论了输入数据的尺度对输出的影响,实际上目标值的尺度也会一定程度上影响模型整体的性能,尤其是在深度神经网络中更加明显。具有大范围值的目标标量可能会导致更大的误差梯度值,这会导致神经网络在反向传播时候梯度发生剧烈的变化,从而导致权重更新的也会比较剧烈,使得网络学习很不稳定。因此在深度神经网络中,对输入值和目标值的尺度缩放也非常关键。
数据归一化
数据归一化操作是指将特征的数据都统一到一个大致相同的数值区间(比如0~1)内。其目的是为了消除数据特征之间的量纲影响。归一化操作的前提是你可以准确估计数据的最大最小值。归一化的公式如下:
MinMaxScaler
专门来做这种处理,我们来实践一下:
import numpy as np
from sklearn.preprocessing import MinMaxScaler
#define data
data = np.asarray([[100, 0.001],
[8, 0.05],
[50, 0.005],
[88, 0.07],
[4, 0.1]])
# define min max scaler
scaler = MinMaxScaler()
# transform data
scaled = scaler.fit_transform(data)
print("scaled data:\n", scaled)
注意这里的MinMaxScaler是应用在每一列数据上的。
数据标准化
数据标准化是指通过改变数据的分布得到均值为0,标准差为1的服从标准正态分布的数据。主要目的是为了让不同特征之间具有相同的尺度(Scale),这样更有理化模型训练收敛。总之,当原始数据不同维度上的特征尺度不一致时,需要标准化步骤对数据进行预处理。下图展示了原始数据、中心化后的数据、标准化后的数据的差异:
标准化的计算方式如下:
StandardScaler
函数来帮助我们对数据进行标准化处理。还是以上面的数据为例,代码如下:
from sklearn.preprocessing import StandardScaler
# define standard scaler
scaler = StandardScaler()
# transform data
scaled = scaler.fit_transform(data)
print(scaled)
L1、L2规范化
规范化的过程就是将每个样本缩放到单位范数,主要思想是对每个样本计算其p-范数,然后将该样本每个元素除以该范数。L1、L2规范化其实就是L1、L2范数。L2归一化的具体计算公式如下:
from sklearn.preprocessing import normalize
#define data
data = np.asarray([[ 1., -1., 2.],
[ 2., 0., 0.],
[ 0., 1., -1.]
])
l1 = normalize(data, norm='l1')
print(l1)
l2 = normalize(data, norm='l2')
print(l2)
对数转换
log函数的定义为,其中a是log函数的底数,是一个正常数,可以是任何正数。由于,我们有。这意味着log函数可以将一些介于0~1之间小范围的数字映射到范围内。比如当a=10时,函数可以将[1,10]映射到[0,1],将[1,100]映射到[1,2]。换句话说,log函数压缩了大数的范围,扩大了小数的范围。x越大,log(x)增量越慢。log(x)函数的图像如下:
- 缩小数据的绝对数值,方便计算
比如,每个数据项都非常大,许多这样的值可能超过常用数据类型的最大值,取对数可以将这些数据都放缩到一定范围之内,方便计算。 - 取对数后,可以将乘法计算转换成加法计算
- 某些情况下,在数据的整个值域中不同区间的差异带来的影响不同
数值小的部分差异的敏感程度比数值大的差异敏感程度更高。比如对于价格这个特征来说,如果你买家电,价格差几百块可能会影响你做决策。但是如果你买汽车,差了几百块你也会忽略不计了。 - 取对数后不会改变数据的性质和相关关系,但压缩了变量的尺度。
- 得到的数据易消除异方差问题
下面用一个例子来说明使用对数转换之后对数据的影响,使用的是Online News Popularity dataset数据集,我们取其中文章字数这一特征,来对其做对数转换。代码如下:
import pandas as pd
import matplotlib.pyplot as plt
df = pd.read_csv("./OnlineNewsPopularity.csv")
df['log_n_tokens_content'] = np.log10(df[' n_tokens_content'] + 1)
fig2, (ax1, ax2) = plt.subplots(2,1)
ax1.scatter(df[' n_tokens_content'], df[' shares'])
ax1.tick_params(labelsize=14)
ax1.set_xlabel('Number of Words in Article', fontsize=14)
ax1.set_ylabel('Number of Shares', fontsize=14)
ax2.scatter(df['log_n_tokens_content'], df[' shares'])
ax2.tick_params(labelsize=14)
ax2.set_xlabel('Log of the Number of Words in Article', fontsize=14)
ax2.set_ylabel('Number of Shares', fontsize=14)
两张图对比可以发现,在未使用对数转换之前,即使输入数据之间的变化很小,但是对应的目标值变化很剧烈,这造成模型很难去拟合这种强烈的变化。使用对数转换之后,可以发现数据变得更加集中,输入对应的输出变化相对来说没有那么大。
Log函数可以极大压缩数值的范围,相对而言就扩展了小数字的范围。该转换方法适用于长尾分布且值域范围很大的特征,变换后的特征趋向于正态分布。
鲁棒归一化
有的时候,不可避免地,我们的输入数据中会出现一些离群值。这些处于分布边缘的值,出现的频率也很低,但是由于一般是较大或者较小的值,如果不作处理,可能会被模型当做特征学习进去。异常离群值通常会使概率分布倾斜,并且使得数据标准化处理变得困难,因为由于离群值的出现,会使得均值和标准差的计算出现倾斜。
在离群值存在的情况下,我们可以使用对离群值具有鲁棒性的归一化方法来进行处理数据。其原理就是在计算均值方差的时候,忽略掉数据中出现的离群值。它使用四分位间距而不是最大值和最小值,因此它对异常值具有鲁棒性。其计算公式如下:
其中称为四分位距,是中位数又称中位数,等于该样本中所有数值由小到大排列后第50%的数字,又称较小四分位数,等于该样本中所有数值由小到大排列后第25%的数字。又称较大四分位数,等于该样本中所有数值由小到大排列后第75%的数字。举个例子说明的计算,如下:
待计算数据为:
sklearn库中提供了RobustScaler
函数来实现这一算法。例子如下:
from sklearn.preprocessing import RobustScaler
X = [[ 1., -2., 2.],
[ -2., 5., 3.],
[ 4., 6., -2.]]
transformer = RobustScaler().fit(X)
transformer.transform(X)
参考
- 《Feature Engineering For Machine Learning》
- 《Data Preparation For Machine Learning》
- https://www.zhihu.com/question/37069477
- https://www.zhihu.com/question/22012482