Python sklearn学习之数据预处理——非线性转换
文章目录
1. 两种常见的非线性转换
1.1 分位数转换
- 基本公式
y = G − 1 ( F ( x ) ) y = G^{-1}(F(x)) y=G−1(F(x))
- 公式说明
- x:特征
- F:特征的累积分布函数
- G^(-1):期望输出值分布G的分位数函数
- 目的:将所有特征置于相同的期望分布中
- 公式成立的事实说明
- 如果X是具有连续累积分布函数F的随机变量,那么F(X)均匀分布在[0,1]
- 如果U是在[0,1]上的随机分布,那么G^(-1)(U)有分布G.
累积分布函数:是概率密度函数的积分
概率密度函数:表示瞬时幅值落在某指定范围内的概率
设累积分布函数为F,概率密度函数为f,则可表示如下:
F x ( x ) = ∫ − ∞ x f x ( t ) d t F_x(x) = \displaystyle \int^{x}_{-\infty}f_{x}(t)dt Fx(x)=∫−∞xfx(t)dt
- 分位数转换优势:平滑了异常分布,并且比缩放方法受异常值的影响更小
- 分位数转换缺点:使特征间及特征内的关联和距离失真了
1.2 幂变换
本质是一组参数变换,其目的是将数据从任意分布映射到接近高斯分布的位置
2. sklearn中非线性变换的实现
2.1 映射到均匀分布
sklearn 将数据集映射到均匀分布的方式主要是通过分位数转换的方式实现,通过类QuantileTransformer 类以及quantile_transform 函数实现。
2.1.1QuantileTransformer类
- 类定义
def __init__(self, n_quantiles=1000, output_distribution='uniform',
ignore_implicit_zeros=False, subsample=int(1e5),
random_state=None, copy=True):
-
参数说明
- n_quantiles : int,optional(默认值= 1000或n_samples)
- 要计算的分位数。它对应于用于离散累积分布函数的标记的数量。如果n_quantiles大于样本数,则n_quantiles被设置为样本数,因为较大数量的分位数不能给出累积分布函数估计器的更好近似。
- output_distribution :str,optional(default =‘uniform’)
- 转换数据的边际分布。选择是“uniform”(默认)或“normal”
- ignore_implicit_zeros :boolean,optional(默认值= False)
- 仅适用于稀疏矩阵。如果为True,则丢弃矩阵的稀疏条目以计算分位数统计。如果为False,则将这些条目视为零
- subsample : int,optional(default = 1e5)
- 用于估计计算效率的分位数的最大样本数。注意,对于值相同的稀疏矩阵和密集矩阵,子采样过程可能不同
- random_state : int,RandomState实例或None,可选(默认=None)
- 如果是int,则random_state是随机数生成器使用的种子; 如果是RandomState实例,则random_state是随机数生成器; 如果为None,则随机数生成器是np.random使用的RandomState实例。请注意,这通过二次采样和平滑噪声来使用。
- copy :略
- n_quantiles : int,optional(默认值= 1000或n_samples)
-
对象属性
-
n_quantiles_ : 用于离散累积分布函数的实际分位数
-
quantiles_: 值与参考对应之间的分位数矩阵
-
references_:
n_samples = X.shape[0] # 数组维度元组,或者说是样本数 self.n_quantiles_ = max(1, min(self.n_quantiles, n_samples)) # 得到样本数与分数数较小的值,为了防止传入空数组,所以需保证大于1 self.references_ = np.linspace(0, 1, self.n_quantiles_,endpoint=True)
-
-
例子
X_train_trans = preprocessing.QuantileTransformer(random_state=0).fit_transform(X_train)
- quantile_transform 函数
一如既往,quantile_transform 函数只是本质上也是创建QuantileTransformer 转换器
n = QuantileTransformer(n_quantiles=n_quantiles,
output_distribution=output_distribution,
subsample=subsample,
ignore_implicit_zeros=ignore_implicit_zeros,
random_state=random_state,
copy=copy)
if axis == 0:
return n.fit_transform(X)
elif axis == 1:
return n.fit_transform(X.T).T
else:
raise ValueError("axis should be either equal to 0 or 1. Got"
" axis={}".format(axis))
2.2 映射到高斯分布
幂变换是一类参数化的单调变换, 其目的是将数据从任何分布映射到尽可能接近高斯分布,以便稳定方差和最小化偏斜。
PowerTransformer 类提供了两种幂变换,Yeo-Johnson transform 和 the Box-Cox transform
2.2.1 Yeo-Johnson transform变换
2.2.2 the Box-Cox transform变换
2.2.3 类说明
def __init__(self, method='yeo-johnson', standardize=True, copy=True):
-
参数说明
- method :可选,接受一个字符串值,默认是 ‘yeo-johnson’ ,
- ’yeo-johnson’ :指明幂变换方式以 Yeo-Johnson transform 方式实现,此种方式下数据集可以含有正负值
- the Box-Cox transform :指明幂变换方式以the Box-Cox transform 方式实现,此种方式下数据集只能是正值,不允许有负值
- standardize :可选,接受一个boolean值,默认是 ‘True’ ,设置为True可将零均值单位方差归一化应用于变换后的输出
- copy :略
- method :可选,接受一个字符串值,默认是 ‘yeo-johnson’ ,
-
属性
- lambdas_ : 得到一个浮点数组,转换过程中所选择的参数
self.lambdas_ = np.array([optim_function(col) for col in X.T])
-
例子
power = preprocessing.PowerTransformer(method='box-cox', standardize=False, copy=True)
X_lognormal = np.random.RandomState(616).lognormal(size=(3, 3))
power.fit_transform(X_lognormal)
# 注意没有下面这种使用方式
# power.fit(X_lognormal).transform(X_lognormal)
- power_transform 函数
本质上是构造 PowerTransformer 对象并调用 fit_transform 实现
pt = PowerTransformer(method=method, standardize=standardize, copy=copy)
return pt.fit_transform(X)
2.2.4 拓展
实际上,利用2.1.1中的 QuantileTransformer 类也可以讲数据集转换为正态分布(通过设置 output_distribution
=‘normal’)。
3. 其他数据预处理
3.1 归一化
归一化 是 缩放单个样本以具有单位范数 的过程。
在数据集中,单个样本通常指单列数据,单位范数指的是,向量的长度(范数)为1
3.1.1 normalize函数
def normalize(X, norm='l2', axis=1, copy=True, return_norm=False):
- 参数说明
- X :需要进行归一化的数组
- norm :范式类型,默认为’l2’,即第二范式,可选’l1’,‘l2’,‘max’
- axis :维度,默认为1,即表示行,若为0,则表示列
- copy :略
- return_norm :是否返回每个样本缩放的标准,boolean值,默认为False
- 使用实例
X = [[1., -1., 2.], [2., 0., 0.], [0., 1., -1.]]
X_normalized = preprocessing.normalize(X, norm='l1') # l1范式,每一行的绝对值之和为1
# out
# array([[0.25, -0.25, 0.5 ], [1. , 0. , 0. ], [0. , 0.5 , -0.5 ]])
X_normalized = preprocessing.normalize(X, norm='l1', return_norm=True) # 返回缩放标准
# out
# (array([[ 0.25, -0.25, 0.5 ],[ 1. , 0. , 0. ],[ 0. , 0.5 , -0.5 ]]),
# array([4., 2., 2.]))
3.1.2 Normalizer 工具类
Normalizer 工具类通过使用 Transformer API实现了相同的归一化效果。但和其他转换器不一样的是,这个转换器没有状态,其fit函数并没有对转换保留状态的。
# Normalizer类中的fit函数
def fit(self, X, y=None):
check_array(X, accept_sparse='csr')
return self
fit函数只是对X进行数组校验,可见它并无状态,整个转换的过程,实际是全在 transform 函数
def transform(self, X, copy=None):
copy = copy if copy is not None else self.copy
X = check_array(X, accept_sparse='csr')
return normalize(X, norm=self.norm, axis=1, copy=copy)
实际上,该类调用的依旧是normalize 函数。
值得一提的是,Normalizer 工具类和 normalize 函数都支持稀疏矩阵的输入,并会自动转化为压缩的稀疏行形式。
4. 类别特征编码
在机器学习中,特征经常不是连续的数值型的而是标称型的(categorical)。
标称型数据:一般在有限的数据中取,而且只存在‘是’和‘否’两种不同的结果,有时可能有多个结果,但也有限
为了将标称型数据转换为数值型数据,可以使用 OrdinalEncoder 工具类和 OneHotEncoder 工具类。
4.1 OrdinalEncoder类
def __init__(self, categories='auto', dtype=np.float64):
- 实例
enc = preprocessing.OrdinalEncoder()
X = [['male', 'from US', 'uses Safari'], ['female', 'from Europe', 'uses Firefox']]
enc.fit(X)
enc.transform([['female', 'from US', 'uses Safari']])
# 此处fit保留了转码的状态,相当于保存了编码规范。
这样的整数特征表示并不能在scikit-learn的估计器中直接使用,因为这样的连续输入,估计器会认为类别之间是有序的,但实际却是无序的。
4.2 OneHotEncoder类
def __init__(self, n_values=None, categorical_features=None,
categories=None, drop=None, sparse=True, dtype=np.float64,
handle_unknown='error'):
enc = preprocessing.OneHotEncoder()
X = [['male', 'from US', 'uses Safari'], ['female', 'from Europe', 'uses Firefox']]
enc.fit(X)
enc.transform([['female', 'from US', 'uses Safari'], ['male', 'from Europe', 'uses Safari']]).toarray()
# out
# array([[1., 0., 0., 1., 0., 1.],
# [0., 1., 1., 0., 0., 1.]])
此时可通过enc.categories_参数导出数组的维数,当然可以手动设置categories参数
genders = ['female', 'male']
locations = ['from Africa', 'from Asia', 'from Europe', 'from US']
browsers = ['uses Chrome', 'uses Firefox', 'uses IE', 'uses Safari']
enc = preprocessing.OneHotEncoder(categories=[genders, locations, browsers])
X = [['male', 'from US', 'uses Safari'], ['female', 'from Europe', 'uses Firefox']]
enc.fit(X)
enc.transform([['female', 'from Asia', 'uses Chrome']]).toarray()
# array([[1., 0., 0., 1., 0., 0., 1., 0., 0., 0.]])
OneHotEncoder 类的作用在于对所有的特征进行一个占位操作,做一个假设,假设有以上10个特征,即genders、locations、browsers指定的特征,共有10个。此时对这十个特征进行排序(并不需要保证同一类别的特征放在相邻位置),例如,我们排列顺序如下(实际上fix函数的真正功能只是根据X的数据对以上10个特征进行某种排序):
将数据排序之后,我们假设一个样本 [‘female’, ‘from Asia’, ‘uses Chrome’] ,可见,里面出现的特征在上列序列中的第1,4,7位,则我们可以把该样本编码为 [1,0,0,1,0,0,1,0,0,0] ,即在一个长度为10的0向量对应位置设为1。
5 离散化
离散化 (Discretization) (有些时候叫 量化(quantization) 或 装箱(binning)) 提供了将连续特征划分为离散特征值的方法。其目的就是把具有连续属性的数据集变换成只有名义属性(nominal attributes)的数据集。
5.1 KBinsDiscretizer
- 类定义
def __init__(self, n_bins=5, encode='onehot', strategy='quantile'):
- 参数说明
参数 | 参数类型 | 可选值 | 含义 | 默认值 |
---|---|---|---|---|
n_bins | int或array | int或array | 每个特征中分箱的个数 | 5 |
encode | str | ‘onehot’, ‘onehot-dense’, ‘ordinal’ | 编码方式 | ’onehot‘ |
strategy | str | ‘uniform’,‘quantile’,‘strategy’ | 分箱的宽度 | ‘quantile’ |
encode参数中包含三个可选值:
‘onehot’:做哑变量,之后返回一个稀疏矩阵,每一列是一个特征的类别,含有该类别的样本表示为1,否则为0.
‘onehot-dense’:做哑变量,之后返回一个密集数组
‘ordinal’:每个特征的每个箱都被编码为一个整数,返回每一列是一个特征,每个特征下含有不同整数编码的箱的矩阵。
strategy参数中含有三个可选值:
‘uniform’:等宽分箱,每个特征中每个箱的最大值之间的差为(Feature.max-Feature.min)/n_bins
‘quantile’:等位分箱,每个特征中每个箱内的样本数量一致
‘strategy’:按聚类分箱,每个箱中的值到最近的一维k均值聚类的簇心的距离都相同
5.2 特征二值化
特征二值化 是将数值特征用阈值过滤得到布尔值的过程,可以通过binarize 函数实现
def binarize(X, threshold=0.0, copy=True): # 传入一个数据集,并可设定阈阈值(默认为0)
函数实现:
X = check_array(X, accept_sparse=['csr', 'csc'], copy=copy)
if sparse.issparse(X):
if threshold < 0:
raise ValueError('Cannot binarize a sparse matrix with threshold < 0')
cond = X.data > threshold
not_cond = np.logical_not(cond)
X.data[cond] = 1
X.data[not_cond] = 0
X.eliminate_zeros()
else:
cond = X > threshold
not_cond = np.logical_not(cond)
X[cond] = 1
X[not_cond] = 0
return X
可见函数只是简单的对数据集中中小于阈值与大于阈值的值进行一个划分。
Binarizer 类也可实现该功能,但其fit函数并无实际作用,只是在transform中调用了binarize 函数实现:
return binarize(X, threshold=self.threshold, copy=copy)
6. 生产多项式特征
增加一些输入数据的非线性特征来增加模型的复杂度,最直接的办法是给数据添加多项式特征。可通过 PolynomialFeatures 实现方式:
- 类定义:
def __init__(self, degree=2, interaction_only=False, include_bias=True, order='C'):
-
参数说明:
- degree :多项式的维度,默认为2,当设定为2时,若一个样本为[x1,x2],则转换后的样本变为[1,x1,x2,x12,x1x2,x22]
- interaction_only 如果为真,则只返回相互作用的特征值,例如,对于上例,返回样本是[1,x1,x2,x1x2],默认为False
- include_bias :如果为True(默认),则包括偏置列,其中所有多项式幂为零的特征(即一列1 - 充当线性模型中的截距项)
- order :密集情况下的输出数组的顺序。'F’顺序计算速度更快,但可能会降低后续估算器的速度,默认为C,可选[‘C’, ‘F’]
-
实例
X = np.arange(6).reshape(3, 2)
X
# out:
X
# array([[0, 1],
# [2, 3],
# [4, 5]])
poly = PolynomialFeatures(2)
poly.fit_transform(X)
# out:
# array([[ 1., 0., 1., 0., 0., 1.],
# [ 1., 2., 3., 4., 6., 9.],
# [ 1., 4., 5., 16., 20., 25.]])
当使用多项的 Kernel functions 时 ,多项式特征被隐式地在核函数中被调用(比如, sklearn.svm.SVC , sklearn.decomposition.KernelPCA)
7. 自定义转换器
想要将一个已有的 Python 函数转化为一个转换器来协助数据清理或处理。可以使用 FunctionTransformer 从任意函数中实现一个转换器
以下有一个自定义函数,功能是返回数组第一行:
def my_function(X):
return X[0]
接下来用其来构造一个自定义的转换器:
X = np.array([[0, 1], [2, 3]]) # 模拟数据
transformer = FunctionTransformer(my_function, validate=True)
transformer.transform(X)
结果:
array([0, 1])