使用sklearn实现数据预处理
我们有时候发现将我们的训练数据扔到模型中,发现结果并不是很好,原因有几点,一有可能是模型不适合该类数据,而是可能数据很脏,没有经过处理。
常见的一些处理方式有标准化(Standardization)、正则化(Normalizer)、缩放等。
我们需要将数据进行一系列的处理让我们的模型可以更容易地拟合数据。
1.标准化(Standardization)
我们经常会发现一般在训练数据之前都会将数据进行标准化。
- 这是因为有些机器学习算法的假设就是我们的数据符合正态分布,但显示中我们的数据不是这样的,所以我们就要将数据进行处理,使他符合正态分布,
- 还有些原因就是统一量纲化,防止不同特征之间的量纲不统一,出现特征死掉的现象,就是那些数值很大的特征会起决定性的作用,而那些特征值小的特征就会不发挥作用,我们的学习器就不能够从所有的特征进行学习
- 第三就是将数据缩放到0-1之间,会使我们的计算更加快速。
其实标准化就是每个特征减去均值然后除以标准差。
x = x − m e a n ( x ) / s t d ( x ) x=x-mean(x)/std(x)x=x−mean(x)/std(x)
这个公式很简单,用numpy就可以实现,但是在sklearn的preprocessing中提供了实现方式,我们先用代码演示一下:
from sklearn.preprocessing import StandardScaler import numpy as np x=np.random.rand(3,4) scaler=StandardScaler().fit(x) scaler.transform(x) array([[-0.58040393, -1.2337171 , 0.93180747, 0.2577483 ], [ 1.40704952, 0.01814612, -1.38720777, 1.0753577 ], [-0.82664558, 1.21557099, 0.4554003 , -1.33310601]])
注意一点,标准化是按照列也就是特征进行的,下面我们讲的正则化是按照行进行的。
还有一点就是,上述代码fit的作用就是计算出x的均值和方差这些保存在scaler实类中,然后下面调用transform将x进行转换,有时你可以看到一些题解他们的做法是用X_train进行fit,然后将X_train和X_test,都用该类进行transform。
scaler=StandardScaler().fit(X_train) X_train=scaler.transform(X_train) X_test=scaler.transform(X_test)
就是上面的代码,有人会问为什么测试集要用训练集的,这样标准化会不会不对?
原因是这样的,因为我们首先将训练集进行标准化,然后交给模型训练,模型会拟合该分布下的数据,为了能够模型适用测试集,也需要将测试集的数据变成该分布,所以需要和训练集进行相同的处理。
在深度学习中有个BatchNorm,这个和它的原理差不多,都是将数据做成统一分布。
2.缩放(MinMaxScaler)
还有一种方法就是将数据缩放到指定的区间,MinMaxScaler就是将我们的起始数据转化成0-1之间的数。
x = x − m i n ( x ) / m a x ( x ) − m i n ( x ) x=x-min(x)/max(x)-min(x)x=x−min(x)/max(x)−min(x)
他就是让每个值减去最小值然后除以该特征下最大值和最小值的差,理解成数轴,区间内任何一个值距离最小值的距离一定小于最大值距离最小值的距离,然后做商,将它缩放到0-1之间。
但是这样也会有问题,如果某列中存在一个极值,非常大,那么在做除法时,得到的值会非常小。
有别的做法可以解决,清除异常值,或者换一种对异常值不敏感的处理方法。
代码操作一下:
from sklearn.preprocessing import MinMaxScaler x=np.array([[-1,-3,4],[2,-1,4]]) MinMaxScaler().fit_transform(x) array([[0., 0., 0.], [1., 1., 0.]])
如果我们不想将数据缩放到0-1之间,可以调整参数feature_range=(min,max)
from sklearn.preprocessing import MinMaxScaler x=np.array([[-1,-3,4],[2,-1,4]]) MinMaxScaler(feature_range=(5,10)).fit_transform(x) array([[ 5., 5., 5.], [10., 10., 5.]])
X = ( X − X . m i n ( a x i s = 0 ) ) / ( X . m a x ( a x i s = 0 ) − X . m i n ( a x i s = 0 ) ) X = (X - X.min(axis=0)) / (X.max(axis=0) - X.min(axis=0))X=(X−X.min(axis=0))/(X.max(axis=0)−X.min(axis=0))
X = X ∗ ( m a x − m i n ) + m i n X = X * (max - min) + minX=X∗(max−min)+min
3.归一化(Normalization)
其实有人很容易搞混,认为归一化就是将数据缩放到0-1这是不对的,归一化是让我们数据的每个样本具有单位范数,一般来说就是L1或者是L2范数。
注意这个不是按照特征列了,这个是按照样本,每一行进行操作。
它的原理是将每行数据求平方和开根号,然后用每个元素除以该值。
代码演示一下:
from sklearn.preprocessing import Normalizer x=np.array([[1,1,1],[-1,0,1]]) Normalizer(norm='l2').fit_transform(x) array([[ 0.57735027, 0.57735027, 0.57735027], [-0.70710678, 0. , 0.70710678]])
结果是不是很显然,按照行进行计算范数。
4.Categorical特征
在机器学习中,我们很多算法的要求是输入必须是数值型特征,但是我们遇到的很多数据都是那种文本类别的,所以我们就需要一种方式处理它,最简单就是对他进行编码,每一种类别对应一个数字,然后将该数字进行训练。
例如:[‘male’,‘female’],这种我们就可以将它编码成[0,1]
1.整数编码(OrdinalEncoder)
它的作用机制就是将每一种类别特征与另外一个数字做映射。
用代码演示一下:
from sklearn.preprocessing import OrdinalEncoder x=[['male','apple'],['female','pear'],['male','pear']] oe=OrdinalEncoder() oe.fit(x) oe.transform([['female','pear']]) array([[0., 1.]])
对于这种映射也会存在问题,你比如说原来是男和女对立的,但是现在变成了0和1,变得可运算而且存在了大小关系,也就是转化后增添了许多原来不存在的关系因素。
所以就产生了另外一种编码方式Onehot。
2.独热编码(OnehotEncoder)
它会将我们的数据变成二进制特征向量。
比如现在有苹果、香蕉、梨,那么现在苹果的编码方式就是1,0,0
香蕉就是0,1,0,梨也就对应0,0,1
显然这三个向量是非线性的,也就不存在之前说的大小和运算关系,它解决了类别独立的问题,但是他也产生了新的问题,就是如果我们某一列的类别特别多,那么产生的二进制向量维度会非常大,是我们的数据维度大大升高。同时矩阵还会变得很稀疏。
代码演示一下:
from sklearn.preprocessing import OneHotEncoder x=[['male','apple'],['female','pear'],['male','pear']] oe=OneHotEncoder() oe.fit(x) oe.transform([['female','pear']]).toarray() array([[1., 0., 0., 1.]])
注意这里有个地方转化完后要用toarray进行获取数组。
还有个问题就是如果我们transform的数据中的某些类别在fit时的数据中没有就会进行报错,说找不到该类别。这也对应如果我们的测试集和训练集的分布不同,就可能出现问题,比如说某个特征的某个类别在测试集中出现,而训练集中没有,这时转化拟合就会出现问题。
但是如果独立处理的话就会没有意义,比如训练集中有一个列是水果,有苹果、香蕉,我们用整数编码变成了0和1,而在测试集中有一个西瓜,训练集中没有,我们也同样用整数编码变成了0,那这样就违背了,两者的表达意思就不一样了,训练集中模型认为0是苹果,而现在0变成了西瓜,这就语意不对应了。
我们可以用一种新的指定方式:
from sklearn.preprocessing import OneHotEncoder sex=['male','female'] fruits=['apple','orange','pear','grape'] oe=OneHotEncoder(categories=[sex,fruits]) x=[['male','apple'],['female','pear'],['male','pear']] oe.fit(x) oe.transform([['female','grape']]).toarray() array([[0., 1., 0., 0., 0., 1.]])
就是在转化前将所有的类别指出,然后交给实类,让他们记住所有的分类,这样就不会产生上述现象了。
5.二值化(Binarization)
他就是将我们的数据根据一个阈值,进行区分成0和1,其实也是有作用的,它可以进行分类,你比如说可以根据听歌时长判断该人是不是喜欢听歌,一但时长大于1000分钟就将其置为1。
代码演示一下:
from sklearn.preprocessing import Binarizer x=np.array([[1,2,3,4,5]]) Binarizer(threshold=3).fit_transform(x) array([[0, 0, 0, 1, 1]])
可以看到他是不包括阈值的。
6.多项式特征
有时我们的特征太少,或者是说希望增加一些非线性特征增加模型复杂度是用一定作用的。简单的办法就是将各个列进行相乘。
举个例子,现在有特征x1,x2
我们设置degree=2
我们就会产生多项式特征【1,x1,x2,x12,x1*x2,x22】
代码演示一下:
from sklearn.preprocessing import PolynomialFeatures x=np.arange(6).reshape(3,2) PolynomialFeatures(2).fit_transform(x) array([[ 1., 0., 1., 0., 0., 1.], [ 1., 2., 3., 4., 6., 9.], [ 1., 4., 5., 16., 20., 25.]])
但是有时候我们希望不需要自身与自身作用的数据特征,就是不需要x1*x1这种,这时就可以设置参数interaction_only=True来实现。
from sklearn.preprocessing import PolynomialFeatures x=np.arange(6).reshape(3,2) PolynomialFeatures(degree=2,interaction_only=True).fit_transform(x) array([[ 1., 0., 1., 0.], [ 1., 2., 3., 6.], [ 1., 4., 5., 20.]])
我们发现特征少了两个维度,分别是x12,x22。