目录
2.4 使用sklearn.preprocessing实现无量纲化
1. 学习目标
1. 无量纲化:最值归一化,均值方差标准化,sklearn中的preprocessing
2. 缺失值处理
3. 处理分类型特征:编码与哑变量
4. 处理连续型特征:二值化与分段
2. 无量纲化
2.1 为什么要做无量纲化
不同的特征可能会有不同的计量单位,这些有单位的特征在计算距离或者相似度的时候会对结果造成不良影响。例如:在两个样本中肿瘤大小的分别为1cm和5cm,发现时间分别为100天和200天,那么在求距离时,时间差为100、大小差为4,那么其结果会被时间所主导,因为肿瘤大小的差距太小了。但是如果我们把时间用年做单位,0.27年与0.55年的差距又远小于肿瘤大小的差距,结果又会被大小主导了[1]。因此,我们希望的是,每一个特征都可以用“相对公平”的方式参与到距离计算和相似度计算中。即使不同的特征存在各自的权重,我们也希望可以先将所有特征拉到同一个水平线上,然后再调整他们各自的权重。
因此,对于不能反映样本中每一个特征的重要程度的情况,这时就需要数据无量纲化或者特征缩放来进行处理了。
无量纲化还有另一个好处。那就是在使用梯度下降的方法求解最优化问题时, 无量纲化的处理可以加快梯度下降的求解速度,即提升模型的收敛速度。如左图所示,无量纲化时形成的等高线偏椭圆,迭代时很有可能走“之”字型路线(垂直长轴),从而导致迭代很多次才能收敛。而如右图对两个特征进行了无量纲化,对应的等高线就会变圆,在梯度下降进行求解时能较快的收敛[2][4][5]。

2.2 常用的无量纲化方法
无量纲化的方法有很多种,不过最为常用的是归一化方法和标准化方法。而这两种方法中最常用的分别是线性归一化和标准差标准化。
2.2.1 线性归一化(最值归一化)
先将任意区间的数据进行同距离的平移(减去最小值),再将数据进行同比例的缩放(除以极差)。这样,所有数据就都被映射到[0, 1]区间中。
线性归一化的适用情况是特征的分布具有明显边界。不过归一化很容易受到离群值(数据中存在的与其他值相差较大的值)的影响[1]。
另外,也可以不对数据进行平移而只将数据同比例缩放(除以最大值的绝对值)。这样,所有数据就都被映射到[-1, 1]区间中。
2.2.2 标准差标准化(Z-score标准化)
先将任意区间的数据进行同距离的平移(减去平均值),再将数据进行同比例的缩放(除以样本标准差)。由于具体处理不同于线性归一,所有的数据被映射到了(-∞, +∞)而非[0, 1]中。不过,此时的数据是服从标准正态分布的。
标准差标准化的适用情况是数据中没有明显的边界,以及存在极端数据。
2.2.3 正则化
在了解什么是正则化之前,首先需要了解“范数”的概念。
假设有向量x
,
则它的p-范数为
因此向量x的1-范数(L1范数)为各元素的绝对值之和,而2-范数(L2范数)为各元素绝对值的平方和的算术平方根。
在无量纲化中,正则化指的是用数据除以它的某个范数,即
2.3 手动实现无量纲化
import numpy as np
import matplotlib.pyplot as plt
np.random.seed(2020)
X = np.random.randint(0, 10000, 100)
X.astype(np.float)
fig, axs = plt.subplots(3, 1)
# 线性归一化
X1 = (X - X.min()) / (X.max() - X.min())
axs[0].scatter(X1, X1)
# 标准差归一化
X2 = (X - X.mean()) / X.std()
axs[1].scatter(X2, X2)
# L2正则化
X3 = X / np.linalg.norm(X, ord = 2)
axs[2].scatter(X3, X3)
plt.show()
2.4 使用sklearn.preprocessing实现无量纲化
import numpy as np
import matplotlib.pyplot as plt
from sklearn.preprocessing import MinMaxScaler
from sklearn.preprocessing import StandardScaler
from sklearn.preprocessing import Normalizer
np.random.seed(2020)
X = np.random.randint(0, 10000, 100)
X.astype(np.float)
fig, axs = plt.subplots(3, 1)
# 线性归一化(默认计算列向量)
minmaxscaler = MinMaxScaler()
minmaxscaler.fit(X.reshape(-1, 1))
X1 = minmaxscaler.transform(X.reshape(-1, 1))
axs[0].scatter(X1, X1)
# 标准差归一化(默认计算列向量)
stdscaler = StandardScaler()
stdscaler.fit(X.reshape(-1, 1))
X2 = stdscaler.transform(X.reshape(-1, 1))
axs[1].scatter(X2, X2)
# L2正则化(默认计算行向量)
normalizer = Normalizer(norm = 'l2')
normalizer.fit(X.reshape(1, -1))
X3 = normalizer.transform(X.reshape(1, -1))
axs[2].scatter(X3, X3)
plt.show()
借助sklearn.preprocessing,我们还可以实现其他的无量纲化方法。具体如下表所示。

2.5 无量纲化在实际使用时的注意事项
2.5.1 如何选取合适的方式
大多数机器学习算法会选择标准化来进行特征缩放,因为归一化对异常值非常敏感。在PCA,聚类,逻辑回归,SVM和神经网络等算法中,标准化往往是最好的选择[3]。如果数据存在异常值和较多噪音,用标准化还可以间接通过中心化避免异常值和极端值的影响[2]。
归一化在不涉及距离度量、梯度、协方差计算以及数据需要被压缩到特定区间时使用广泛,比如数字图像处理中量化像素强度时,都会使用归一化将数据压缩于固定的区间之中[3]。在不涉及距离度量、协方差计算、数据不符合正太分布的时候,可以使用归一化方法[2]。
基于树的方法不需要进行特征的归一化。例如随机森林,bagging与boosting等方法[2]。
从经验上来说,优先尝试标准化,然后再尝试归一化。
2.5.2 无量纲化会破坏数据的可解释性
虽然无量纲化具有上述的好处,但是它的缺陷也是显而易见的。那就是数据原有的含义消失了从而使得数据失去了可解释性。因此在有的时候,我们需要将数据还原回它们原来的值。除了逆用无量纲化的公式外,也可以借助sklearn中的相关语句来实现此功能。
import numpy as np
import matplotlib.pyplot as plt
from sklearn.preprocessing import MinMaxScaler
from sklearn.preprocessing import StandardScaler
np.random.seed(2020)
X = np.random.randint(0, 10000, 100)
X.astype(np.float)
# 线性归一化(默认计算列向量)
minmaxscaler = MinMaxScaler()
minmaxscaler.fit(X.reshape(-1, 1))
X1 = minmaxscaler.transform(X.reshape(-1, 1))
print(minmaxscaler.inverse_transform(X1))
# 标准差归一化(默认计算列向量)
stdscaler = StandardScaler()
stdscaler.fit(X.reshape(-1, 1))
X2 = stdscaler.transform(X.reshape(-1, 1))
print(stdscaler.inverse_transform(X2))
# L2正则化没有封装好的还原方法
2.5.3 无量纲化训练集和测试集的误区
在实际的模型训练中,我们需要将数据划分成训练集和测试集。假如我们需要对数据进行无量纲化,唯一正确的操作应该是:先将数据划分为训练集和测试集,然后对训练集进行无量纲化,接着用训练集无量纲化用到的参数对测试集进行无量纲化。
举个例子,假设使用标准化处理训练集。那么测试集数据减去的样本均值和除以的标准差全部是训练集的样本均值和标准差而非自己的。这是因为测试数据是模拟的真实环境,真实环境中可能无法得到均值和方差,因此我们要保存训练数据集中得到的均值和方差[1]。
3. 缺失值处理
无论是大数据、机器学习还是深度学习,它们所用的数据在内容上并非是完美无缺的。最常见的情况就是数据中某些字段值会出现缺失。
如果某个特征并不重要或者缺失值出现次数实在过多,可以考虑将整个特征所在列直接删除。但是,如果不满足这个条件就要想办法对缺失值进行填补。
3.1 用sklearn.impute进行填补
sklearn.impute是专门用来填补缺失值的模块。它的SimpleImpute类可以对numpy数组和pandas表格和序列实现该功能。其参数如下表所示。

具体实例可参考[6]。
3.2 利用pandas的fillna()来填补缺失值
如果数据的存储用的是pandas的表格和序列。可以使用此方法来填充。具体的参数设置及实例可参考[7]。
4. 特征编码技术
4.1 为什么要进行特征编码
机器学习模型的输入的数据一定要是数值类型,然而有些特征并非如此。因此,就要用到特征编码技术将其转化成符合输入要求的类型。另外,有的特征虽然是数值类型,但是如果该数值的大小只是为了区别不同的类别而非进行数值上的比较,那么这个特征很容易会令模型产生误解。此时仍需要特征编码来消除这种歧义。
4.2 分类型特征(标签编码和独热编码)
对于表示类别的特征,例如性别可分为“男”和“女”,由于不是数值,因此模型无法识别。此时我们会想到将“男”用“1”代替,“女”用“0”代替。这就是典型的标签编码,即用不同的整数来表示不同的类别。这种方法简单直观,不过此时的特征仍然蕴含大小关系,会对模型造成影响。因此我们需要对其进行进一步的转换。
独热编码(也叫哑编码)可以很好地解决这个问题。首先,我们统计类别特征中存在的不同类别的个数n。然后,将此特征扩展成一个n维向量。对于每一个样本数据而言,它对应的这个n维向量中有且仅有一个元素是1,其余的全是0。这样,就可以通过1出现的不同位置来对应样本数据具有的不同特征类别。例如“男”用“01”代替,“女”用“10”代替,这样模型就会发现二者并没有明确的大小关系,因此也就不会产生误导。
在sklearn.preprocessing中,实现标签编码的类有LabelEncode和OriginalEncode,它们的具体用法和区别可以参考[3][8][9]。实现独热编码的类有OneHotEncode,它的具体用法可以参考[3][10]。

另外,对于pandas的表格,还可以利用pandas.get_dummies()的方法来实现独热编码的生成。具体用法可以参考[11]。另外,pandas.get_dummies()还有个好处就是可以直接返回一个含有列名的pandas表格。而OneHotEncoder只能够返回一个numpy数组,如果想要获取每一列数据对应的列名则要调用它的get_feature_names()方法[13]。
4.3 连续型特征(二值化与分段)
在工业界,很少直接将连续值作为逻辑回归模型的特征输入,而是将连续特征离散化为一系列0、1特征交给逻辑回归模型。这样做的优势有以下几点[12]:
1. 离散特征的增加和减少都很容易,模型迭代速度会提升;
2. 稀疏向量内积乘法运算速度快,计算结果方便存储,容易扩展;
3. 离散化后的特征对异常数据有很强的鲁棒性。比如一个特征是年龄>30是1,否则0。如果特征没有离散化,一个异常数据“年龄300岁”会给模型造成很大的干扰;
4. 逻辑回归属于广义线性模型,表达能力受限。单变量离散化为多个后,每个变量有单独的权重,相当于为模型引入了非线性,能够提升模型表达能力;
5. 离散化后可以进行特征交叉,由m+n个变量变为m*n个变量,进一步引入非线性,提升表达能力;
6. 特征离散化后,模型会更稳定,比如如果对用户年龄离散化,20-30作为一个区间,不会因为一个用户年龄长了一岁就变成一个完全不同的人。当然处于区间相邻处的样本会刚好相反,所以怎么划分区间是门学问;
7. 特征离散化以后,起到了简化了逻辑回归模型的作用,降低了模型过拟合的风险。
对于连续型特征,常用的离散化处理有二值化和分段。
所谓二值化就是指对于一个连续型特征,人为规定一个阈值。凡是小于此阈值的全部记为0,否则全部记为1。在sklearn.preprocessing中,可以使用Binarizer类来实现此功能。具体实例可参考[3][14]。
而分段则是二值化的进一步推广。二值化实际上是将连续型数据分为两段,而分段则是将连续型数据分为多段。在sklearn.preprocessing中,可以使用KBinsDiscretizer类来实现此功能。KBinsDiscretizer类既可以实现对分段后的数据用数字标签进行标记,还可以实现对分段后的数据用独热编码的方式进行标记。而且,KBinsDiscretizer类既可以将数据分布范围等分若干段进行分段,还可以将分段的个数对数据进行均匀的分段。具体参数设置可以参考[14]。
5. 参考文献
1. https://mp.weixin.qq.com/s/RkenakI_DSXoMLwNNvUAAw
2. https://mp.weixin.qq.com/s/qWO9zgKyntvyWfftpGqrHQ
3. https://www.cnblogs.com/juanjiang/archive/2019/05/30/10948849.html
4. https://www.jianshu.com/p/95a8f035c86c
5. https://blog.youkuaiyun.com/pipisorry/article/details/52247379
6. https://blog.youkuaiyun.com/sdnuwjw/article/details/100655255
7. https://blog.youkuaiyun.com/haoxun05/article/details/104563911
8. https://www.bingwong.org/2018/12/26/133.html
9. https://blog.youkuaiyun.com/weixin_41798592/article/details/101344948
10. https://blog.youkuaiyun.com/accumulate_zhang/article/details/78510571
11. https://blog.youkuaiyun.com/maymay_/article/details/80198468
12. https://blog.youkuaiyun.com/u014135752/article/details/80789251
13. https://blog.youkuaiyun.com/dQCFKyQDXYm3F8rB0/article/details/82719437