文章目录
数据的预处理
数据空值填充
有时候,我们拿到手的数据会有一些空值,影响计算,所以我们要先进行空值填充。这里简单介绍以下sklearn库中的缺失值处理器Imputer的使用。
注:Imputer是对DataFrame类型的数据进行操作,且元素都得为数值类型。
Imputer实例化参数详解:
sklearn.preprocessing.Imputer(missing_values=’NaN’, strategy=’mean’, axis=0, verbose=0, copy=True)
-
missing_values: 待填充的缺失值,为integer 或 “NaN”, (default=”NaN”)
-
strategy: 填充策略
名称 含义 “mean” 均值填充 “median” 中值填充 “most_frequent” 众数填充 -
axis: axis = 0, 按列处理 ;aixs =1 , 按行处理 ;(default=0)
对数据进行特征缩放(归一化/标准化/正则化)
我们获取到的数据基本上都是多维的,每个特征对应的值量纲并不统一,为了消除量纲的影响,让这些数据都属于同一量级,我们拿到数据的第一步就是对其进行特征缩放。
由于特征缩放往往都是线性变换,所以并没有丢失数据中最根本的信息,能提高模型准确度甚至加快算法的收敛速度。
注意:在下面的代码中,归一化和标准化调用sklearn库的接口都是默认按列进行特征缩放的,这也符合我们通常使用的数据的特点,每一列属于一个特征;而正则化是按行进行处理的。
归一化
归一化有两种,一种是将数据归一到 [ 0 , 1 ] 之间,另一种是归一到 [ -1 , 1 ] 之间。这两种方法可以根据自己的需求自由选择
1.MinMaxScaler:归一到 [ 0,1 ]
from sklearn import preprocessing
import numpy as np
min_max_scaler = preprocessing.MinMaxScaler() #创建实例
data = np.array([[-25,48,152],[-1,-3,6],[0.2,0.8,0.45]])
min_max_scaler.fit(data) #拟合数据
new_data = min_max_scaler.transform(data) #将数据归一化
new_data = min_max_scaler.fit_transform(data) #上面两行代码可以直接用这行表达
print(new_data)
结果:
2.MaxAbsScaler:归一到 [ -1,1 ]
(还是用一样的数据作为样例)
from sklearn import preprocessing
import numpy as np
max_abs_scaler = preprocessing.MaxAbsScaler() #创建实例
data = np.array([[-25,48,152],[-1,-3,6],[0.2,0.8,0.45]])
max_abs_scaler.fit(data) #拟合数据
new_data = max_abs_scaler.transform(data) #将数据归一化
new_data = max_abs_scaler.fit_transform(data) #上面两行代码可以直接用这行表达
print(new_data)
结果:
]
标准化
标准化是指将原始数据变换为均值为0,标准差为1的数据,它通常用于正态分布数据(大多数情况下我们获取到的数据都大致符合正态分布)。
from sklearn import preprocessing
import numpy as np
standardizer = preprocessing.StandardScaler() #创建实例
data_1 = np.random.normal(5,0.5,6).reshape(-1,1) #创建符合正态分布的数据
data_2 = np.random.normal(4,0.1,6).reshape(-1,1) #创建符合正态分布的数据
data = np.concatenate((data_1, data_2), axis=1) #合并
print("原始数据为: \n",data)
standardizer.fit(data) #拟合数据
new_data = standardizer.transform(data) #将数据归一化
new_data = standardizer.fit_transform(data) #上面两行代码可以直接用这行表达
print("标准化后的数据为: \n",new_data)
结果:
正则化
正则化是把每个样本缩放为单位范数,可以理解为是把每个样本看成一个向量,将其变成一个单位向量。sklearn中有三种正则化方法:L1范数、L2范数和max范数,也就是调用相应函数时设置的参数norm。
sklearn库提供了正则化的两个途径:
1.和之前一样构建实例
from sklearn import preprocessing
import numpy as np
normalnizer = preprocessing.Normalizer(norm='l2') #实例化并指定范数
data = np.array([[-25,48,152],[-1,-3,6],[0.2,0.8,0.45]]) #创建数据
normalnizer.fit(data) #拟合数据
new_data = normalnizer.transform(data) #将数据归一化
new_data = normalnizer.fit_transform(data) #上面两行代码可以直接用这行表达
print(new_data)
结果:
2.直接调用一个函数
from sklearn import preprocessing
import numpy as np
data = np.array([[-25,48,152],[-1,-3,6],[0.2,0.8,0.45]]) #创建数据
new_data = preprocessing.normalize(data, norm='l2') #直接使用这个函数进行正则化
print(new_data)
结果:
特征筛选
刚开始处理数据时,我们经常会发现有很多个特征,在进行数据降维之前,我们通常会进行一些初步的特征筛选,下面我介绍几个主流的特征筛选方法、
WOE编码与IV值的计算
WOE的中文名称叫做证据权重,英文为Weight of Evidence ,是自变量的一种编码,因为它是分组计算的,所以在对一个变量进行WOE编码之前,需要确保它是离散化的,如果是连续型变量,需要对其进行离散化处理(分割)。
现在假设这个变量有n个取值,我们把第i个取值视为一组,这一组的WOE的计算公式如下:
W O E i = ln ( p y 1 p y 0 ) = ln ( B i B / G i G ) = ln ( B i G i / B G ) W O E_{i}=\ln \left(\frac{p_{y 1}}{p_{y 0}}\right)=\ln \left(\frac{B_{i}}{B} / \frac{G_{i}}{G}\right)=\ln \left(\frac{B_{i}}{G_{i}} / \frac{B}{G}\right) WOEi=ln(py0py1)=ln(BBi/GGi)=ln(GiBi/GB)
其中, p y 1 p_{y 1} py1是坏样本的分布,也就是这一组样本中的坏样本在所有坏样本中的比例; p y 0 p_{y 0} py0是好样本的分布,也就是这一组样本中的好样本在所有好样本中所占的比例。因此,计算出来的WOE值在一定程度上可以说是包含了该特征取值对于分类为坏的权重。
下一步就是IV的计算。IV全称为Information Value,也就是信息价值,对于第i组,计算公式如下:
I V i = ( p y 1 − p y 0 ) ∗ W O E i = ( p y 1 − p y 0 ) ∗ ln ( p y 1 p y 0 ) = ( B i B − G i G ) ∗ ln ( B i B / G i G ) \begin{array}{l} I V_{i}=\left(p_{y 1}-p_{y 0}\right) * W O E_{i} \\ =\left(p_{y 1}-p_{y 0}\right) * \ln \left(\frac{p_{y 1}}{p_{y 0}}\right) \\ =\left(\frac{B_{i}}{B}-\frac{G_{i}}{G}\right) * \ln \left(\frac{B_{i}}{B} / \frac{G_{i}}{G}\right) \end{array} IVi=(py1−py0)∗WOEi=(py1−py0)∗ln(py0py1)=(BBi−GGi)∗ln(BBi/GGi)
这一变量的IV值为各取值对应IV值之和: I V = ∑ i n I V i I V=\sum_{i}^{n} I V_{i} IV=∑inIVi
IV值 | 预测能力 |
---|---|
小于0.02 | 无效 |
0.02-0.10 | 弱预测力 |
0.10-0.20 | 中预测力量 |
大于0.20 | 强预测力 |
信息增益(Information Gain)
相信大家都对决策树非常熟悉,而决策树里必不可少的一步就是信息增益的计算,因为信息增益可以用来衡量该属性对于区分样本能起到的作用。
关于信息增益的计算,这里需要引入信息熵(Entropy)的概念。简单来说,可以把信息熵理解为信息的纯度或者是不确定性,信息熵越大也就证明这个变量的信息量越高,对最终的判断越有用。
以分类问题为例,我们要把某个目标分为k类。对于某一个Featrue
F
F
F,假设它有n种取值,那么第 i 种取值时对应的第 j 类的概率(占比)为
p
j
p_j
pj,则这一组的信息熵计算公式为:
E
n
t
r
o
p
y
(
F
i
)
=
∑
j
=
1
k
−
p
j
log
2
p
j
Entropy(F_{i})=\sum_{j=1}^{k}-p_{j} \log _{2} p_{j}
Entropy(Fi)=∑j=1k−pjlog2pj
接下来就是信息增益(Information Gain)的计算,这个特征F的信息增益为:
I
G
(
F
)
=
E
n
t
r
o
p
y
(
c
l
a
s
s
)
−
∑
i
=
1
n
P
F
i
E
n
t
r
o
p
y
(
F
i
)
IG(F) = Entropy(class) - \sum_{i=1}^{n}P_{F_i}Entropy(F_i)
IG(F)=Entropy(class)−∑i=1nPFiEntropy(Fi)
其中,
E
n
t
r
o
p
y
(
c
l
a
s
s
)
Entropy(class)
Entropy(class)指的是把所有样本的分类情况视为一组信息计算得到的信息熵。
最后,我们就可以根据信息增益来比较不同特征对于最终结果的贡献程度,从而进行特征筛选,例如在决策树中,每次选择节点时选择信息增益最大的节点。
数据降维
很多时候,我们面临的数据都是高维数据,很容易在深度学习时陷入“维度陷阱”,因此数据降维就显得比较重要。下面我介绍几个主流的数据降维方法和相关实现代码。
线性降维方法
PCA(主成分分析法) 无监督
原理:找到一些投影方向,数据在这些投影方向上的方差最大,且这些投影方向彼此正交,它们就是新的特征。
构建PCA实例参数详解:
sklearn.decomposition.PCA(n_components=None, copy=True, whiten=False)
- n_components: PCA算法中所要保留的主成分个数n
一般赋值为int类型,有时也可以让其根据要求自动选择,赋值为string类型(n_components=2 / n_componets=‘mle’) - copy: 是否创建原始数据的副本进行运算,若为False则直接对原始数据进行运算,原始数据会被降维
- whiten: 是否进行白化(使每个特征方差相同)
PCA的属性
名称 | 含义 |
---|---|
components_ | 具有最大方差值的成分 |
explained_variance_ratio_ | 所保留的n个成分各自的方差百分比(通常可用来观察各成分贡献,从而进一步筛选特征) |
n_components_ | 保留的成分个数n |
mean_ | 每个特征的经验平均值 |
noise_variance_ | 噪声协方差 |
n_features_ | 训练数据中原有特征个数 |
n_samples_ | 训练数据中的样本个数 |
实现代码:
import numpy as np
from sklearn.decomposition import PCA
data = np.array([[-25,48,152],[-1,-3,6],[0.2,0.8,0.45]])
pca = PCA(n_components=2)
pca.fit(data)
new_data = pca.transform(data)
print(new_data)
可以看到我们的数据已经成功从之前的三维数据变成了两维数据。
更详细的参数介绍见sklearn库的官方文档:PCA官方文档
LDA(线性判别分析法) 有监督
原理: 有监督,寻找投影方向,使得在投影后类内方差最小,类间方差最大
构建LDA实例主要参数详解:
sklearn.discriminant_analysis.LinearDiscriminantAnalysis(solver='svd',n_components=None)
-solver:取值为{‘svd’, ‘lsqr’, ‘eigen’}中的一个,'svd’为默认值,表示奇异值分解(不计算协方差矩阵,适合多特征数据),‘lsqr’为最小二乘解,可与收缩或自定义协方差估计相结合,‘eigen’表示特征值分解,也可与收缩或自定义协方差估计相结合。
注意,如果在选择‘svd’的时候想要计算类内协方差矩阵,就还需要设置另一个参数store__covariance=true。
-n_components: 降维后的组件数量,最大值为min(n_classes - 1, n_features)。
实现代码:
from sklearn.discriminant_analysis import LinearDiscriminantAnalysis
lda = LinearDiscriminantAnalysis(n_components=2)
lda.fit(X,y)
X_new = lda.transform(X)
实例的一些重要属性和PCA相同,如explained_variance_ratio_,means_,还有一个比较常用的属性是covariance_,存储了类内加权协方差矩阵。
更详细的参数介绍见sklearn库的官方文档:LDA官方文档
MDS降维
原理:希望在从高维空间映射到低维空间后,数据点的相对距离变化最小,即尽可能地保留距离关系,在MDS算法中,距离的计算一般是采用欧氏距离,是针对欧氏空间的算法。
算法步骤:
- 已知有n个数据点(即n个行向量实例),当前处于m维的特征空间中,也就是说数据对应的是一个n * m 矩阵,目标是降维到d维的特征空间Z中,如下所示:
X = [ x 1 x 2 ⋮ x n ] = [ x 11 x 12 ⋯ x 1 m x 21 x 22 ⋯ x 2 m ⋮ ⋮ ⋮ ⋮ x n 1 x n 2 ⋯ x n m ] ⇒ Z = [ z 11 z 12 ⋯ z 1 d z 21 z 22 ⋯ z 2 d ⋮ ⋮ ⋮ ⋮ z n 1 z n 2 ⋯ z n d ] = [ z 1 z 2 ⋮ z n ] X=\left[\begin{array}{c} x_{1} \\ x_{2} \\ \vdots \\ x_{n} \end{array}\right] = \left[\begin{array}{cccc} x_{11} & x_{12} & \cdots & x_{1 m} \\ x_{21} & x_{22} & \cdots & x_{2 m} \\ \vdots & \vdots & \vdots & \vdots \\ x_{n 1} & x_{n 2} & \cdots & x_{n m} \end{array}\right] \Rightarrow \\Z=\left[\begin{array}{cccc} z_{11} & z_{12} & \cdots & z_{1 d} \\ z_{21} & z_{22} & \cdots & z_{2 d} \\ \vdots & \vdots & \vdots & \vdots \\ z_{n 1} & z_{n 2} & \cdots & z_{n d} \end{array}\right]=\left[\begin{array}{c} z_{1} \\ z_{2} \\ \vdots \\ z_{n} \end{array}\right] X= x1x2⋮xn = x11x21⋮xn1x12x22⋮xn2⋯⋯⋮⋯x1mx2m⋮xnm ⇒Z= z11z21⋮zn1z12z22⋮zn2⋯⋯⋮⋯z1dz2d⋮znd = z1z2⋮zn - 然后,定义内积矩阵 B = Z Z T ∈ R n ∗ n B = ZZ^T \in R ^ {n * n} B=ZZT∈Rn∗n
- 以降维前后距离尽量保持一致为优化目标,由原空间距离矩阵D求得内积矩阵B
- 对B进行特征值分解得到矩阵Z:
B = Z Z T = V Λ V T ⇒ Z Z T = V Λ 1 2 Λ 1 2 V T ⇒ Z = V Λ 1 2 \begin{array}{l} B=Z Z^{T}=V \Lambda V^{T} \Rightarrow Z Z^{T}=V \Lambda^{\frac{1}{2}} \Lambda^{\frac{1}{2}} V^{T} \\ \Rightarrow Z=V \Lambda^{\frac{1}{2}} \end{array} B=ZZT=VΛVT⇒ZZT=VΛ21Λ21VT⇒Z=VΛ21
MDS算法的详细原理,我参考了这位博主的博客MDS原理推导,大家感兴趣的话可以去看看。
实现代码:
from sklearn.manifold import MDS
和sklearn里的其他工具一样,构建实例后调用fit方法、fit_transform方法和transform方法等,就可以实现降维。
构建该实例的参数介绍如下:
参数 | 说明 |
---|---|
n_components | 降维后的维度,默认值为2 |
metric | 布尔类型,默认为true,代表了MDS是否启用度量 |
n_init | 默认初始化的次数(进行n次不同的初始化并运行算法),默认值为4 |
max_iter | 算法的最大迭代次数,默认值为300 |
random_state | int或RandomState实例(默认值为None),控制伪随机数发生器种子,让初始化保持一致 |
更详细的参数介绍见sklearn官方文档:MDS官方文档
非线性降维方法
t-SNE降维
原理:t-SNE降维是一种基于概率的非线性降维方法,它能够保留原始数据的局部特征,即保存它们之间的关系特征,通过概率分布的相近程度来反映原本数据之间的远近关系,这里引入了KL散度来度量概率分布的差异。关于KL散度,大家可以看一下我的这一篇博客:KL散度和JS散度介绍
关于一些t-SNE算法的细节,大家可以去看看知乎一个博主的这篇文章:t-SNE降维
实现代码:
from sklearn.manifold import TSNE
注:使用t-SNE,不同的初始化会导致最终降维的结果不同。
构建该实例的参数介绍如下:
参数 | 说明 |
---|---|
n_components | 降维后的维度,默认值为2 |
perplexity | t-SNE算法考虑邻近点的多少 |
learning_rate | t-SNE算法优化过程中的学习率 |
random_state | int或RandomState实例(默认值为None),控制伪随机数发生器种子,让初始化保持一致 |
更详细的参数介绍见sklearn的官方文档:t-SNE官方文档
ISOMAP降维
前面提到过的MDS算法是一种适用于欧式空间的线性降维方法,当涉及到非线性的曲面时,就变成了流形学习的任务,因此出现了脱胎于MDS算法的ISOMAP降维方法。
原理:与之前MDS算法中的距离计算不同,这里需要计算曲线的长度,因为积分不易计算,所以这里我们使用多段线段逼近这个曲线,用这堆折线长度来近似曲线长度。
算法步骤:
- 第一步就是构建距离矩阵,假设有n个样本,那么就是n * n矩阵。对于每一个样本,我们都寻找k个最近的样本,然后基于欧式距离计算得到距离,写入距离矩阵,然后每个点与k个邻居之外的点的距离视为无穷。这样一来,原本理论上对称的距离矩阵就不对称了,可以视为一个有向图的邻接矩阵。
- 下一步就是基于图的最短路径算法来补全这个矩阵,这里常用经典的Floyd-Warshall算法或Dijkstra的算法来实现。
- 得到最后的距离矩阵后,其余步骤与MDS相同,事实上就是采用了新的距离计算方法。
实现代码:
from sklearn.manifold import Isomap
构建实例的参数介绍如下:
参数 | 说明 |
---|---|
n_components | 降维后的维度,默认值为2 |
n_neighbors | 选取的邻居个数,默认值为5 |
path_method | 取值为 ‘auto’/’FW’/‘D’,分别代表自动寻找最佳算法、Floyd-Warshall算法和Dijkstra算法 |
更详细的参数介绍请看sklearn库的官方文档:ISOMAP降维
不同类型的特征
定类变量
在实际的任务中,我们会遇到类别型的变量,这些变量通常以字符串的形式存在,可能有序也可能无序,在机器学习中我们要将这种变量转化为可计算的数值。
无序定类变量
无序定类变量是指不同类别之间不存在顺序或某种固有关系的定类变量,如性别、国家等。对于这类变量,我们有下列处理方法:
- 独热编码(One-hot encoding)
- 频率编码(Frequency encoding)和计数编码(Count encoding)
- 目标均值编码(Target mean encoding)
- 直方图编码(Histogram encoding)
- 留一法编码(Leave-one-out)
- WOE编码(Weight of Evidence)
- 独热编码
独热编码想必大家都很熟悉了,把这个变量的n种状态用n位编码表示,每种状态对应位置编码为1,其余为0,例如性别这个特征变量,有男、女两个取值,那么就用两位编码表示,男就是“10”,女就是“01”,化成向量就是 [1,0] 和 [0,1]。本质上,独热编码相当于把一个无序定类变量拉平成为多个二进制特征,用1和0表示是否存在这种特征。
代码实现:
from sklearn.preprocessing import OneHotEncoder
#因为sklearn库的这些实例常见操作大致相同,前面已经描述过,这里就不作赘述了,大家可以自己去看官方文档
缺点:当这个类别变量的取值过多时,独热编码会因为大量增加特征维度而大大扩展特征空间,同时也可能使数据变得太过稀疏。
- 频率编码和计数编码
频率编码和计数编码,顾名思义,就是把类别的取值用训练集样本中该取值出现的频率或次数替换,变成数值型变量。
优点:转化的结果是连续的数值特征,且包含了该特征不同取值的分布信息。
缺点:只包含了特征各取值的统计信息,丧失了本来的一些信息,而且可能造成两个完全不同的特征值因为出现频次相同而结果一模一样的转换。 - 目标均值编码
这种编码方法有一个特殊之处,也是它的一个大优点,就是它能够利用目标列的信息。目标均值编码的原理是,将该类别变量的每一个取值(每一类),转化为该类对应样本目标值的平均值。
下面我举两个目标列类型不同的例子,数据如下:
姓名 | 身高 | 体重 | 性别 | 体测分数 | 三好学生 |
---|---|---|---|---|---|
李华 | 175 | 60 | 男 | 80 | 是 |
赵蕾 | 168 | 55 | 女 | 85 | 是 |
胡一天 | 182 | 86 | 男 | 72 | 否 |
王红 | 160 | 50 | 女 | 76 | 否 |
(1)假设目标列为体测分数,对性别进行目标均值编码
男:(80 + 72) / 2 = 76
女:(85 + 76)/ 2 = 80.5
男这个特征值就变成76,女变成了80.5
(2)假设目标列为是否为三好学生,对性别进行目标均值编码
男:2 / 4 = 0.5
女:2 / 4 = 0.5
男这个特征值变成了0.5,女也变成了0.5
这个编码方法也存在一定的缺点,那就是可能会因为包含大量目标列的信息而出现过拟合的情况,同时可能会影响模型对特征信息的提取能力,存在对于目标列的过于依赖。
- 直方图编码
直方图编码也是目标编码的一种,适用于结果是分类任务的处理。方法为统计每种类别对应各目标标签的样本占比,最终将这一个类别特征列转化为n列特征(假设最终目标为n类),每一列可以理解为这个类别对每一种结果类标签的贡献程度,示意图如下:
- 留一法编码
留一法编码是对之前介绍过的目标均值编码方法的一种改进,能够一定程度上减轻对于目标变量的依赖,同时能够消除离群值的影响,也让编码更加多样化(同一个类别值转换出的结果编码可能不同)。
原理就是在计算目标均值的时候,忽略该行目标值,用剩下的行来计算,其他都不变,也就是”留一“版本的目标均值编码。
代码实现:
from category_encoders import LeaveOneOutEncoder
- WOE编码
关于WOE编码具体的计算方法和相关介绍,前面特征筛选处已经提及,这里就说明一下它的另一个用处,就是把离散数据转为数值,而这个数值包含了该特征取值对分类贡献度的意义。一般情况下,WOE编码适用于二分类任务的情况。
有时候,我们会看到一个处理:把数值型变量化为离散型变量,再进行WOE编码变为数值型变量,原因就是WOE编码方法可以起到把非线性特征变量变为线性特征变量的作用。
代码实现:
from category_encoders import WOEEncoder
有序定类变量
对于有序定类变量,一般采用的编码方式为序号编码(Ordinal encoding),处理方式就是把分类变量映射到数值,如[非本科学历,本科,硕士,博士]映射为[0,1,2,3],顺序关系被保留了下来。
相关实现:
from sklearn.preprocessing import OrdinalEncoder
注意,还有一个极其类似的方法叫做标签编码(Label encoding),同样也是根据分类标签直接映射为自然数,但是这个方法对应的结果是没有大小顺序的,只是存粹的把标签变成数值形式,没有大小意义(虽然其实这两种编码方法没有本质上的区别),在这里提及是避免混淆。
from sklearn.preprocessing import LabelEncoder
定量变量(数值变量)
在大部分任务中,我们遇到最多的应该就是定量变量了,从数据类型的层面来看,数值变量是唯一一种可直接用于计算的变量类型,因此在类型层面我们不需要作特殊处理。
参考的文章
[1] 降维方法之t-SNE, https://zhuanlan.zhihu.com/p/426068503
[2] 特征工程之降维(MDS), https://blog.csdn.net/qq_27388259/article/details/112168953