Python数据挖掘入门与实践 第二章2.2 流水线在预处理中的应用
2.2 流水线在预处理中的应用
现实中,物体不同特征的取值范围会非常广,它们的值域可能存在天壤之别。
例如材料学里面,不同材料的特征值,
单位g重之类的,可能就是1和10的差别,
但是拉伸强度之类的,需要使用log来进行处理,一个差就是几万,
显然,单纯的使用数字来进行K近邻分析的时候,
拉伸强度就会是最显著的特征,
但特征值大小实际上与该特征的分类效果没有任何关系。
不同特征的取值范围千差万别,常见的解决方法是对不同的特征进行规范化,使它们的特征值落在相同的值域或从属于某几个确定的类别,比如小、中和大。
一旦解决这个问题,不同的特征类型对算法的影响将大大降低,分类正确率就能有大幅提升。
选择最具区分度的特征、创建新特征等都属于预处理的范畴。scikit-learn的预处理工具叫作转换器(Transformer),它接受原始数据集,返回转换后的数据集。除了处理数值型特征,转换器还能用来抽取特征。
2.2.1 预处理示例
首先对Ionosphere数据集做些破坏。虽然这里的麻烦是人为制造的,但是这 些问题在很多真实数据集里都存在。
首先,为了不破坏原来的数据集,我们为其创建一个副本。
X_broken = np.array(X)
接下来,我们就要捣乱了,每隔一行,就把第二个特征的值除以10。
X_broken[:,::2] /= 10
理论上讲,这样做对结果影响应该不大。毕竟,除以10之后,各个特征相差不大。主要的问题是,数值范围变了,奇数行的第二个特征要比偶数行的大。再次计算正确率看一下效果。
书里写的奇数行的第二个特征要比偶数行的大,看了半天X_broken[:,::2] /= 10,还是没理解。。。于是乎先深究一下,np.array的切片问题。
第一个冒号是代表所有行,逗号表示分隔,第二个冒号表示所有列,
第三个冒号是什么意思呢?
直接ctrl+F搜索双冒号,看看有没有例子,结果还真有如下的例子。
y = np.arange(35).reshape(5,7)
y[1:5:2,::3]
array([[ 7, 10, 13],
[21, 24, 27]])
为了方便观察,打印一下y和切片后的y[1:5:2,::3]:
首先是第1行到第4行每隔2行取值,
然后是所有列,每隔3列取值。
我的理解是:取出来的数组的序号(从0开始),整除2或者3就取值。
这样看来,X_broken[:,::2] /= 10的意思似乎是:
所有行取值,所有列取值后每隔2列除以10?
所以书上的内容应该是:所有行的,奇数列的特征值,要比原始数据的奇数列的特征值要小10倍。
而且这个奇数列是指从1开始的奇数列,所以对应的序号就是可以被2整除的偶数。
验证一下:
print(X[0])
print(X_broken[0])
可以发现,果然是奇数列的特征值,要比原始数据的奇数列的特征值要小10倍。
再验证一下:
print(X[1])
print(X_broken[1])
也是奇数列的特征值,要比原始数据的奇数列的特征值要小10倍。
再验证一下所有的奇数列:
i_index = []
for i in range(34):
if (X[:,i:i+1] == X_broken[:,i:i+1]).all()==True:
i_index.append(i)
print(i_index)
打印的序号结果为:
[1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23, 25, 27, 29, 31, 33]
果然是序号为奇数的数列,前后不变。
X_broken[:,::2] /= 10搞懂了之后,接着看书吧~
计算破坏前后数据的分类情况:
cross_val_score的函数,默认每次的取值也是一样的,所以代码和结果如下:
import numpy as np
import csv
from sklearn.neighbors import KNeighborsClassifier
from sklearn.cross_validation import cross_val_score
data_filename = r'数据下载地址\ionosphere.data'
X = np.zeros((351, 34),dtype='float')
y = np.zeros((351,),dtype='bool')
with open(data_filename, 'r') as input_file:
reader = csv.reader(input_file)
for i, row in enumerate(reader):
data = [float(datum) for datum in row[:-1]]
X[i] = data
y[i] = row[-1] == 'g'
X_broken = np.array(X)
X_broken[:,::2] /= 10
estimator = KNeighborsClassifier()
original_scores = cross_val_score(estimator, X, y,scoring='accuracy')
print("The original average accuracy for is {0:.1f}%".format(np.mean(original_scores) * 100))
broken_scores = cross_val_score(estimator, X_broken, y,scoring='accuracy')
print("The broken average accuracy for is {0:.1f}%".format(np.mean(broken_scores) * 100))
打印的结果为:
The original average accuracy for is 82.3%
The broken average accuracy for is 71.5%
嗯,和书本的结果是对的上的~
看来,认为破坏之后的数据集,准确率下降了10多个百分点。
2.2.2 标准预处理
可以用MinMaxScaler类进行基于特征的规范化。
这个类可以把每个特征的值域规范化为0到1之间。最小值用0代替,最大值用1代替,其余值介于两者之间。
接下来,对数据集X进行预处理。我们在预处理器MinMaxScaler上调用转换函数。有些转换器要求像训练分类器那样先进行训练,
但是MinMaxScaler 不需要, 直接调用fit_transform()函数,即可完成训练和转换。
直接组装起来~在上面的代码加入下列代码:
from sklearn.preprocessing import MinMaxScaler
X_transformed = MinMaxScaler().fit_transform(X_broken)
transformed_scores = cross_val_score(estimator, X_transformed, y,scoring='accuracy')
print("The average accuracy for is {0:.1f}%".format(np.mean(transformed_scores) * 100))
得到的结果为:
The original average accuracy for is 82.3%
The broken average accuracy for is 71.5%
The average accuracy for is 82.3%
又回到了82.3%!!
MinMaxScaler可以把数值标准化到0~1的范围,那么它的原理是怎样的呢?
## The transformation is given by:
X_std = (X - X.min(axis=0)) / (X.max(axis=0) - X.min(axis=0))
X_scaled = X_std * (max - min) + min
可见,每一列的最大值和最小值找到后,使用上面的公式,计算X_std,就能全部转化为0~1的数值。
这个还是挺好理解的。
于是我们验证一下上面的公式,对比转换之后的结果:
row_max = np.max(X_broken,axis=0)
row_min = np.min(X_broken,axis=0)
row_δ = row_max-row_min
print(row_δ)
结果为,奇数列的数值的差额,比偶数列的小10倍:
[0.1 0. 0.2 2. 0.2 2. 0.2 2. 0.2 2. 0.2 2. 0.2 2. 0.2 2. 0.2 2.
0.2 2. 0.2 2. 0.2 2. 0.2 2. 0.2 2. 0.2 2. 0.2 2. 0.2 2. ]
X_broken_std = X_broken
X_broken_std = (X_broken-row_min)/(row_max-row_min)
print(X_broken_std)
print(X_transformed)
结果为:
除了都是0的一列,其他数值基本吻合!
可见,MinMaxScaler还是很方便使用的。
此外,书中还介绍了以下操作方法:
为使每条数据各特征值的和为1,
使用sklearn.preprocessing.Normalizer。
为使各特征的均值为0,方差为1,
使用sklearn.preprocessing.StandardScaler,常用作规范化的基准。
为将数值型特征的二值化,
使用sklearn.preprocessing.Binarizer,大于阈值的为1,反之为0。
跟着书本的进度,以后再看吧~
参考文献:
1.https://www.numpy.org.cn/user_guide/numpy_basics/indexing.html
2.https://scikit-learn.org/stable/modules/generated/sklearn.preprocessing.MinMaxScaler.html