part2_02Sklearn数据预处理

4 非线性变换

有些场景中,采集到的数据并不需要以连续性的方式呈现,而是以一些非线性的方式呈现,比如:

  • 考试分数的及格线
  • 家庭年收入划分

诸如此类,此时只需要对数据进行区间划分,或者表现数据内部间的相对关系。在非线性变换过程中,可以摒弃一部分数据的“精度”,用离散的数学表示取代连续的数学表示,数据直接转化为类别数据或者呈现与整体数据关系的数据。

4.1 二值化变换

二值化变换(Binary Transform)的实现方法,设定一个阈值,当待转换的数据大于阈值时,该数据转化为1,反之转化为0。
$$
x^{'} = \left{ \begin{array}{rcl} 1&\mbox{threshold>x} \ 0&\mbox{threshold≤x} \end{array}\right.
$$

# numpy用于生成缺失值,引用sklearn.preprocessing库,其中有绝大部分的数据预处理方法
from sklearn import preprocessing
import numpy as np
# 原始数据X
X=np.array([[3.,-2.,2.],[2.,0.,0.],[-1.,1.,3.]])
print('原始数据:\n')
print(X)
# ☆ 初始化数据预处理器,此处为二值化变换器,其中设定阈值为1
binarizer=preprocessing.Binarizer(threshold=1)
# ☆ fit_transform是将数据拟合和转化在同一个步骤完成
X_binarizer=binarizer.fit_transform(X)
print('二值化转化后的数据:\n')
print(X_binarizer)
'''
[[1. 0. 1.]
 [1. 0. 0.]
 [0. 0. 1.]]
'''

4.2 分位数变换

分位数(Quantile),亦称分位点,是指用分割点将一个随机变量的概率分布范围划分为几个具有相同概率的连续区间,分割点的数量比划分出的区间数量少1。常用的分位数有中位数(即二分位数)、四分位数、百分位数等。

以统计学中较为常用的四分位数为例,将所有数值由小到大排列并分成四等份,处于三个分割点位置的数值就是四分位数。

  • 第一四分位数(Q1)——“较小四分位数”,等于该样本从小到大排列后第25%的数字。
  • 第二四分位数(Q2)——“中位数”,等于该样本从小到大排列后第50%的数字。
  • 第三四分位数(Q3)——“较大四分位数”,等于该样本从小到大排列后第75%的数字。

在机器学习中,分位数的方法可用于数据预处理,选择这种预处理方式是由模型建立的目的所决定的。因为在实际的模型中,传统的线性回归模型的假设常常不被满足,比如数据出现尖峰或者厚尾的分布、存在显著的异方差等情况,这是的最小二乘法将不再具有优良的特性,同时稳健性也不够理想。

为了弥补普通的最小二乘法在回归分析中的缺陷,Koenker和Bassett在1978年提出了分位数回归(Quantile Regression)的思想。它依据因变量的条件分位数自变量进行回归,得到了所有分位数下的回归模型。

基于上述的场景,那么分位数变化就是分位数回归分析模型训练中必要的步骤。其将所有的特征放在相同的已知范围或者分布(由分位数决定)中。通过执行分位数变换,平滑了不寻常的分布,比缩放方法更少受到异常值的影响,因为此时将样本数值的绝对关系转化为相对关系。例如,学生考试分数是绝对的,但是排名是相对的。分位数回归可以捕捉分布的尾部特征,当自变量对不同部分的因变量的分布产生不同的影响时(如出现左偏或者右偏的情况),它能更加全面地刻画分布的特征,从而得到全面分析。

相对于线性数据变换(如一些规则化方法),分位数变化的优点主要有:

  1. 它对样本中的随机扰动项不需要做任何分布的假定,所以模型具有很强的稳健性。
  2. 分位数变换无需一个连接函数描述因变量的均值和方差的相互关系,因为分位数变换使模型拥有比较好的弹性性质。
  3. 由于分位数回归是对所有分位数进行回归,所以对样本中的异常点具有耐抗性。
  4. 分位数变换后的数据在模型中对因变量具有单调变换性。
  5. 分位数变换后的数据具有在大样本理论下的渐进优良性。

正是因为其样本数据关系进行了变换,所以扭曲了特征内部及其之间的相关性和距离。

分位数变换的基本实现

Sklearn自带的数据集包sklearn.datasets.*,收录了各种数据集,以鸢尾花数据集为例的实现过程如下。

from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
# 载入鸢尾花数据集
import pandas as pd
iris=load_iris()
X,y=iris.data,iris.target
'''
y=y.reshape(150,1)
data0=np.hstack([X,y])
data_pd=pd.DataFrame(data0)
data_pd.to_csv('iris_pd.csv')
'''
# 划分数据集
X_train,X_test,y_train,y_test=train_test_split(X,y,random_state=0)
# 初始化分位数转换器
from sklearn import preprocessing
quantile_transformer=preprocessing.QuantileTransformer(random_state=0)
# 训练并转化数据
X_train_trans=quantile_transformer.transform(X_train)
X_test_trans=quantile_transformer.transform(X_test)
import numpy as np
print('被转化训练集特征X的五分位数')
print(np.percentile(X_train[:,0],[0,25,50,75,100]))
print(np.percentile(X_train[:,1],[0,25,50,75,100]))
print(np.percentile(X_train[:,2],[0,25,50,75,100]))
print(np.percentile(X_train[:,3],[0,25,50,75,100]))
print('=================================')
print('被转化测试集特征X的五分位数')
print(np.percentile(X_test[:,0],[0,25,50,75,100]))
print(np.percentile(X_test[:,1],[0,25,50,75,100]))
print(np.percentile(X_test[:,2],[0,25,50,75,100]))
print(np.percentile(X_test[:,3],[0,25,50,75,100]))
print('=================================')
print(np.percentile(y_test[:,0],[0,25,50,75,100]))
import numpy as np
import seaborn as sns
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.preprocessing import QuantileTransformer
from sklearn.model_selection import train_test_split
# 设置特定参数
N_SAMPLES=1000
size=N_SAMPLES
# 初始化随机数发生器
rng=np.random.RandomState(0)
# 生成lognormal分布
X_lognormal=rng.lognormal(size=size)
# 生成高斯分布
loc=100
X_gaussian=rng.normal(loc=loc,size=size)
# 生成均匀分布
X_uniform=rng.uniform(low=0,high=1,size=size)
# 将要展示的分布数据
distributions=[
    ('Lognormal',X_lognormal),
    ('Gaussian',X_gaussian),
    ('Uniform',X_uniform),
    ('Lognormal after uniform',[]),
    ('Gaussian after uniform',[]),
    ('Uniform after uniform',[]),
    ('Lognormal after normal',[]),
    ('Gaussian after normal',[]),
    ('Uniform after normal',[]),
]
# 图表初始化
f,ax=plt.subplots(ncols=3,nrows=3,figsize=(12,10))
# 开始画图
for i,d in enumerate(distributions):
    # 配置图位置坐标信息
    a=int(np.floor(i/3))
    b=np.mod(i,3)
    title,data=d
    # 绘制原始数据图表
    if data!=[]:
    sns.distplot(data,kde=False,rug=True,ax=ax[a,b],color='r')
        ax[a,b].set_title(title)
    else:
        _,data=distributions[b]
        X_train,X_test=train_test_split(data,test_size=.5)
        X_train=X_train.reshape(-1,1)
        X_test=X_test.reshape(-1,1)
        # 分位数分布可采用两种分布策略,及均匀分布和正态分布
        # 其配置项为 output_distribution
        strategy=title.split()[2]	show_data=preprocessing.QuantileTransformer(output_distribution=strategy,random_state=rng).fit(X_train).transform(X_test)
        sns.distplot(show_data,kde=False,rug=True,ax=ax[a,b])
        ax[a,b].set_title(title)
# 图表展示
plt.show()

原本形态各异的分布数据,通过分位数变换后,其分布趋于统一,由此知,分位数变换在某种程度上可以扭曲原有的数据形态,而得到的新数据形态可以更好的反映数据之间的关系。

4.3 幂变换(此节无代码实现)

在统计学中,幂变换(Power Transformation)主要使用幂函数建立数据单调变换的一系列函数。是一种有用的数据转换技术,用于稳定方差,使数据更接近正态分布,提高关联度量的有效性,如变量之间的相关性及其他数据的稳定过程。

Sklearn中提供了两种变换方法:Box-Cox变换(Box-Cox Transformation)、Yeo-Johnson变换。

  1. Box-Cox变换

    Box-Cox变换是一种广义幂变换方法,用于连续响应变量不满足正态分布的情况。Box-Cox变换之后,在一定程度上减小不可观测的误差和预测变量的相关性。Box-Cox变换主要特点是引入一个参数,通过数据本身估计该参数,进而确定采取的数据变换形式。Box-Cox变换可以明显地改善数据的正态性、对称性和方差相等性。
    $$
    x_{i}^{(\lambda)}=\left{ \begin{array}{rcl} \frac{x_{i}^{\lambda}-1}{\lambda}&\mbox{λ≠0} \ ln(x_{i})&\mbox{λ=0} \end{array}\right.
    $$
    xi为原始连续因变量,且为正数;λ为变换参数,意义在于控制变换函数的形式,λ=0为对数变换,λ<0为倒数变换,λ>0为幂方变换或者开方变换。当xi出现负数时,此时xi以(xi+γ)的形式呈现,γ要大到使(xi+γ)>0。

    Box-Cox变换的一个显著优点:通过求变换参数λ确定变换形式,这个过程完全基于数据本身而不依赖任何先验信息,这比凭经验或者穷举尝试而选用对数、平方根等变换形式更加客观和精确。其目的是使数据满足线性模型的基本假定,即线性、正态性、方差齐性。

  2. Yeo-Johnson变换

    Yeo-Johnson变换在Box-Cox变换的基础上,解决了xi必须为正的严格限制,更具普适性。
    $$
    x_{i}^{(\lambda)}=\left{ \begin{array}{rcl} \frac{({x_{i}+1)}^{\lambda}-1}{\lambda}&\mbox{λ≠0,xi≥0} \ ln(x_{i}+1)&\mbox{λ=0,xi≥0} \ \frac{-[({-x_{i}+1)}^{2-\lambda}-1]}{2-\lambda}&\mbox{λ≠2,xi<0}\
    -ln(-x_{i}+1)&\mbox{λ=2,xi<0}\end{array}\right.
    $$
    Yeo-Johnson变换参数的解释很困难,但Yeo-Johnson变换函数族可用于选择线性或者正太变换的过程。

    注意:参数λ不是直接设定的,而是通过参数估计进行求解,使用的是极大似然估计和贝叶斯方法。

对于不满足正态分布的数据,幂变换可以使转化后的数据改善其正态性、对称性和方差相等性。

4.4 多项式变换

多项式变换(Polynomial Transformation)常用于线性回归场景,其本质是一种升维操作,将低维度的数据样本通过多项式变化转换为高维度的样本,但部分高维度之间仍然存在相关性。

相对于多项式线性回归描述数据的能力,一般线性回归在描述数据时存在局限性。

比如,原始样本是一组二维数据集{x1,x2},假设对其加入高次项,如x1x2、x12 、x22 ,甚至x1nx2m 等,原来原始的二维数据集{x1,x2}转化为更高维度{x1,x2,...,x1nx2m },于是从原有的回归方程:
$$
y_{(2)}=ax_{(2)}+b
$$
扩展到:
$$
y_{(2+k)}=ax_{(2+k)}+b
$$
式中,k为增加的高次项个数。

import numpy as np
from sklearn.preprocessing import PolynomialFeatures
# 生成初始数据,令其大小为 3*2
X=np.arange(15).reshape(3,5)
X
# 初始化数据预处理器,默认的次数项参数degree=2
# 交互项参数interaction_only默认为False,当其为True时,各个特征将会相乘产生交叉项
# 常数项include_bias默认为True,转化之后会多出一个常数列
poly2=PolynomialFeatures(degree=4,interaction_only=False,include_bias=True) #所有项

print('二次转化后的数据:\n')
# 从原始的(x1,x2)转化为(1,x1,x2,x1^2,x2^2,x1x2)
print(poly2.fit_transform(X))
poly2.fit_transform(X).shape

print('三次转化后且只保留交互项的数据:\n')
# 从原始的(x1,x2)转化为(1,x1,x2,x1x2)
poly3=PolynomialFeatures(degree=4,interaction_only=True,include_bias=True) 
# 交互项(每项中元素的次数≤1)+1(常数项)
print(poly3.fit_transform(X))
poly3.fit_transform(X).shape

5 自定义预处理结构

当上述方法不能完全覆盖开发者数据处理的场景时,在很多场景中需要对数据源进行特定的函数运算才能进一步提交给模型训练,比如:

  • 加密数据需要解密
  • 财务数据需要进行汇总核算
  • 水文数据需要进行加权计算
  • 市场数据需要生成新的指标

诸如此类,当现有的通用预处理方法不足以满足业务要求时,可以通过自定义预处理的方式进行处理。

import numpy as np
from sklearn.preprocessing import FunctionTransformer
# ☆ 定义自定义函数,也就是自己设定一种映射方式
def customer_function(x):
    return X**2-2*x+1

# ☆ 输入用于数据转换的自定义函数,数据验证项validate默认为 True,输入数据将被转换为nunpy矩阵
transformer=FunctionTransformer(customer_function,validate=True)
X=np.arange(4).reshape(1,4)
print('原始数据:',X)
# print(X) [[0 1 2 3]]
print('自定义转换之后的数据如下:',transformer.transform(X))
# print(transformer.transform(X)) [[1 0 1 4]]
posted @ 2022-12-18 23:09  努力生活的小林  阅读(188)  评论(0)    收藏  举报