在标准化时,在无数的教科书上常见如下代码:
from sklearn.preprocessing import StandardScaler
ss = StandardScaler()
X_train = ss.fit_transform(X_train)
X_test = ss.transform(X_test)
先看一些方法的作用:
fit(): Method calculates the parameters μ and σ and saves them as internal objects.
transform(): Method using these calculated parameters apply the transformation to a particular dataset.Transform(): Method using these calculated parameters apply the transformation to a particular dataset.
而fit_transform()相当于先fit()再transform(),fit_transform(trainData)对部分数据先拟合fit,找到该部分数据的整体指标,如均值、方差、最大值、最小值等等(根据具体转换的目的),然后对该trainData进行转换transform,从而实现数据的标准化等等。
问题1:最后一行代码为什么不能写成:X_test = ss.fit_transform(X_test)?
生活中我们经常看到某些例子,比如学校运动会100米冠军的成绩拿到奥运会决赛场上极有可能是最后1名。一次统考,某所学校某班学习成绩第1名的同学可能到另外一所学校的某个班,或许连前30名可能都排不上。好,现在看一个例子:
假设我们有一组数据:[10,20,30],则标准化之后的数据为:
array([[-1.22474487],
[ 0. ],
[ 1.22474487]])
这组数据的均值为:20.0,标准差为:8.16496580927726 。标准化之后的这组数据,其标准差为1,均值为0。
假设我们的分类器在这组数据上得到一个规则,如果数据 x > 0.6 则类别为class 1,否则当 x <= 0.6时类别为calss 2,所以[10,20,30]的分类结果如下:
10 ------- class 2
20 ------- class 2
30 ------- class 1
现在假设我们有了另外一组需要分类的数据[5,6,7],先调用ss.fit_transform对其进行标准化,
ss.fit_transform(np.array([5,6,7]).reshape(-1,1))
结果为:
array([[-1.22474487],
[ 0. ],
[ 1.22474487]])
则按之前分类器的规则,分类结果如下:
5 ------- class 2
6 ------- class 2
7 ------- class 1
最终我们得到一个可笑的结果,7和30被分为class1,而5,6,10,20被分为class2。
而如果我们用[10,20,30]的均值20.0 标准差8.16496580927726 作为参数μ和σ的值来对[5,6,7]进行标准化:
ss.fit_transform(np.array([10,20,30]).reshape(-1,1))
ss.transform(np.array([5,6,7]).reshape(-1,1))
则[5,6,7]标准化的结果为:
array([[-1.83711731],
[-1.71464282],
[-1.59216833]])
然后用分类器分类,[5,6,7]均被分为class 2,这样就得到比较合理的结果了。
所以你会发现,对训练集进行fit_transform后,对测试集的标准化必须要用transform,绝对不能再用fit_transform,尤其是存在数据集的数据量比较小或数据集存在数据严重偏倚情况。
问题2:能不能先做标准化,再做 train_test_split?
比如在做机器学习鸢尾化分类时,你或许会编写如下代码:
(1)获取原数据
from sklearn.datasets import load_iris
iris = load_iris()
(2)分割数据集为训练集、测试集
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(iris.data, iris.target, test_size=0.25, random_state=33)
(3) 标准化
from sklearn.preprocessing import StandardScaler
ss = StandardScaler()
X_train = ss.fit_transform(X_train)
X_test = ss.transform(X_test)
(4)定义模型、进行分类
from sklearn.neighbors import KNeighborsClassifier
knc = KNeighborsClassifier()
knc.fit(X_train, y_train)
y_predict = knc.predict(X_test)
(5)评价模型,这里选用模型的score方法
print(‘The accuracy of K-Nearest Neighbor Classifier is’, knc.score(X_test, y_test))
**结果:**The accuracy of K-Nearest Neighbor Classifier is 0.8947368421052632
那为什么第(2)步和第(3)步不换一下呢?例如将标准化过程改为:
ss = StandardScaler()
iris.data = ss.fit_transform(iris.data)
问题1中,你或许发现训练集和测试集的衡量标准一致性是解决问题的关键,那现在把训练集和测试集都标准化为均值为0,标准差为1的数据岂不是更公平?!
在iris这个例子中,经上一步所谓的“改进”,分类器的准确率仍然是0.8947368421052632。但如果你拿其它数据集测试一下,你可能会发现分类器的准确率可能会提升,那是不是那些参考书上的例子有问题呢?
现发表一下自己的拙见:
(1)测试集的理解及过拟合
测试集就是明明类标号已知,但我们假设先不知道,充当将来可能要实际预测的一批评测分类器准确率的数据。
在分割数据集为训练集、测试集时,为我们通常调用了类似于教科书一般的这行代码
X_train, X_test, y_train, y_test = train_test_split(iris.data, iris.target, test_size=0.25)。
其实测试集的类标号我们是知道的,它就是y_test。我们拿着X_train,y_train去训练得到一个分类器模型。然后用 预测X_test,得到y_predict,然后去跟所谓“准确”的 y_test比较,目的就是想看看这个分类器模型的准确率是多少。
但y_test真的是将来类标号真不知道的数据吗?如果你先做标准化,再分割数据集,那答案显然不是。先做标准化,再分割训练集和测试集,其实是一种overfitting。如果将来碰到一组之前没出现过的数据,那分类器的准确度就会显著下降了。
(2) 先做标准化再做分割数据集,其实无法体现分类的真实过程
先做标准化,把测试集和训练集合并在一块,然后标准化为均值为0,标准差为1的数据,貌似公平,实则荒谬!试想一个在训练集上得到的分类器,将来要对一个类标号未知的数据x进行分类。你能把它放到原始的训练集上,重新再做一次标准化吗?如果你愿意去做,考虑到训练集的数据量的大小吗?考虑到训练集你能获取到吗?考虑到待分类数据的输入不规律性了吗?
就像对数据机器学习训练后得到一个分类器,将来分类的时候,只要输入数据的特征,分类器就会返回分类结果,绝对不会拿输入数据到原始数据中先跑一遍标准化,再分类给结果。而X_test = ss.transform(X_test),恰恰体现了这种真实性。
最后总结一下:
先分割训练集和测试集后再做标准化;训练集用fit_transform,测试集一定要用transform。