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]:
Python数据挖掘入门与实践 第二章2.2 流水线在预处理中的应用

首先是第1行到第4行每隔2行取值,
然后是所有列,每隔3列取值。
我的理解是:取出来的数组的序号(从0开始),整除2或者3就取值。
这样看来,X_broken[:,::2] /= 10的意思似乎是:
所有行取值,所有列取值后每隔2列除以10?
所以书上的内容应该是:所有行的,奇数列的特征值,要比原始数据的奇数列的特征值要小10倍。
而且这个奇数列是指从1开始的奇数列,所以对应的序号就是可以被2整除的偶数。

验证一下:
print(X[0])
print(X_broken[0])
Python数据挖掘入门与实践 第二章2.2 流水线在预处理中的应用
可以发现,果然是奇数列的特征值,要比原始数据的奇数列的特征值要小10倍。
再验证一下:
print(X[1])
print(X_broken[1])

Python数据挖掘入门与实践 第二章2.2 流水线在预处理中的应用
也是奇数列的特征值,要比原始数据的奇数列的特征值要小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)

结果为:Python数据挖掘入门与实践 第二章2.2 流水线在预处理中的应用
除了都是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