在训练和测试数据拆分之前或之后对数据进行归一化?

Normalize data before or after split of training and testing data?

我想将我的数据分成训练集和测试集,我应该在拆分之前还是之后对数据应用归一化?在构建预测模型时有什么不同吗?

您首先需要将数据分成训练集和测试集(验证集也很有用)。

不要忘记测试数据点代表 real-world 数据。 解释(或预测)变量的特征归一化(或数据标准化)是一种通过减去均值并除以方差来对数据进行中心化和归一化的技术。如果您采用整个数据集的均值和方差,您会将未来信息引入训练解释变量(即均值和方差)。

因此,您应该对训练数据进行特征归一化。然后也对测试实例执行归一化,但这次使用训练解释变量的均值和方差。通过这种方式,我们可以测试和评估我们的模型是否可以很好地泛化到新的、未见过的数据点。

更全面的阅读,可以看我的文章Feature Scaling and Normalisation in a nutshell


举个例子,假设我们有以下数据:

>>> import numpy as np
>>> 
>>> X, y = np.arange(10).reshape((5, 2)), range(5)

其中 X 代表我们的特征:

>>> X
[[0 1]
 [2 3]
 [4 5]
 [6 7]
 [8 9]]

Y包含相应的标签

>>> list(y)
>>> [0, 1, 2, 3, 4]

第 1 步:创建 training/testing 集

>>> X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.33, random_state=42)

>>> X_train
[[4 5]
 [0 1]
 [6 7]]
>>>
>>> X_test
[[2 3]
 [8 9]]
>>>
>>> y_train
[2, 0, 3]
>>>
>>> y_test
[1, 4]

步骤 2:规范化训练数据

>>> from sklearn import preprocessing
>>> 
>>> normalizer = preprocessing.Normalizer()
>>> normalized_train_X = normalizer.fit_transform(X_train)
>>> normalized_train_X
array([[0.62469505, 0.78086881],
       [0.        , 1.        ],
       [0.65079137, 0.7592566 ]])

步骤 3:规范化测试数据

>>> normalized_test_X = normalizer.transform(X_test)
>>> normalized_test_X
array([[0.5547002 , 0.83205029],
       [0.66436384, 0.74740932]])

你可以使用 fit 然后转换 学习

normalizer = preprocessing.Normalizer().fit(xtrain)

变换

xtrainnorm = normalizer.transform(xtrain) 
xtestnorm = normalizer.transform(Xtest) 

问问你自己,你的数据是否会根据你在拆分之前或之后进行转换而有所不同。如果您正在执行 log2 转换,则顺序无关紧要,因为每个值都是独立于其他值进行转换的。如果您正在缩放和居中数据,那么顺序确实很重要,因为离群值会极大地改变最终分布。您允许测试集“溢出”并影响您的训练集,可能导致过于乐观的性能测量。

对于 R 用途,caret 包擅长处理 test/train 拆分。您可以将参数 preProcess = c("scale", "center") 添加到 train 函数,它会自动将训练数据的任何转换应用到测试数据。

Tl;dr - 如果数据不同取决于你是在拆分之前还是之后进行标准化,请在

之前进行

在一个train/test拆分的具体设置中,我们需要区分两种变换:

  1. 根据有关特征(列)的信息更改观察值(行)的转换和
  2. 根据关于那个观察的信息改变观察值的变换单独.

(1) 的两个常见示例是 mean-centering(减去特征的均值)或缩放到单位方差(除以 标准差 )。减去平均值并除以 标准偏差 是一种常见的转换。在 sklearn 中,它在 sklearn.preprocessing.StandardScaler. Importantly, this is not the same as Normalizer 中实现。 详见下文。

(2) 的一个示例是通过取对数或将每个值提升为幂(例如平方)来转换特征。

第一类变换最好应用于训练数据,保留居中和缩放值并随后应用于测试数据。这是因为使用有关测试集的信息来训练模型可能会使模型比较指标过于乐观。这可能会导致 over-fitting 并选择虚假模型。

可以在不考虑 train/test 拆分的情况下应用第二种类型的转换,因为每个观察值的修改值仅取决于关于观察值本身的数据,而不取决于任何其他数据或观察值).


这个问题得到了一些误导性的答案。此答案的其余部分致力于展示它们如何以及为何具有误导性。

“规范化”一词含糊不清,不同的作者和学科会以不同的方式使用“规范化”一词。在没有具体说明“规范化”的含义的情况下,我认为最好从最一般的意义上来处理这个问题。

在这种观点下,问题不具体是关于 sklearn.preprocessing.Normalizer 的。实际上,问题中没有提到 Normalizer class 。就此而言,也没有提及任何软件、编程语言或库。此外,即使本意是询问 Normalizer,答案仍然具有误导性,因为它们错误地描述了 Normalizer 的作用。

即使在同一个库中,术语也可能不一致。例如,PyTorch 实现规范化 torchvision.transforms.Normalizetorch.nn.functional.normalize。其中一个可用于创建均值为 0 和标准差为 1 的输出张量,而另一个可用于创建范数为 1 的输出。


Normalizer Class 的作用

Normalizer class 是 (2) 的一个例子,因为它重新调整了每个观测值(行)单独 以便 sum-of-squares每行为 1。 (在 corner-case 中,一行 sum-of-squares 等于 0,不进行重新缩放。) the documentation for the Normalizer 的第一句说

Normalize samples individually to unit norm.

这个简单的测试代码验证了这个理解:

X = np.arange(10).reshape((5, 2))
normalizer = preprocessing.Normalizer()
normalized_all_X = normalizer.transform(X)
sum_of_squares = np.square(normalized_all_X).sum(1)
print(np.allclose(sum_of_squares,np.ones_like(sum_of_squares)))

这会打印 True,因为结果是 1 的数组,如文档中所述。

标准化器实现了 fittransformfit_transform 方法,尽管其中一些只是“pass-through”方法。这是为了在预处理方法之间有一个一致的接口,不是,因为方法的行为需要区分不同的数据分区。


误导性演示文稿 1

Normalizerclass不减列的意思是

写道:

Don't forget that testing data points represent real-world data. Feature normalization (or data standardization) of the explanatory (or predictor) variables is a technique used to center and normalise the data by subtracting the mean and dividing by the variance.

好的,让我们试试这个。使用答案中的代码片段,我们有

X = np.arange(10).reshape((5, 2))

X_train = X[:3]
X_test = X[3:]

normalizer = preprocessing.Normalizer()
normalized_train_X = normalizer.fit_transform(X_train)
column_means_train_X = normalized_train_X.mean(0)

这是column_means_train_X的值。不为零!

[0.42516214 0.84670847]

如果已从列中减去列均值,则居中的列均值将为 0.0。 (这很容易证明。 n 个数字 x=[x1,x2,x3,...,xn] 的总和是 S。这些数字的平均值是 S / n。然后我们有 sum(x - S/n) = S - n * (S / n) = 0。)

我们可以编写类似的代码来显示列没有被 方差 划分。 (这些列也没有除以 标准偏差 ,这是更常见的选择)。

误导性演示文稿 2

Normalizer class应用于整个数据集不会改变结果。

If you take the mean and variance of the whole dataset you'll be introducing future information into the training explanatory variables (i.e. the mean and variance).

这个说法就目前而言是正确的,但它与 Normalizer class 完全没有关系。事实上,Giorgos Myrianthous 选择的例子实际上不受他们描述的影响。

如果 Normalizer class 确实涉及特征的均值,那么我们预计归一化结果会根据我们的哪些数据包含在训练集中而改变。

例如,样本均值是样本中每个观察值的加权和。如果我们计算列均值并减去它们,则将其应用于所有数据的结果将不同于仅将其应用于训练数据子集。但我们已经证明 Normalizer 不会减去列均值。

此外,这些测试表明,将 Normalizer 应用于所有数据或仅部分数据对结果没有影响。

如果我们单独应用这个方法,我们有

[[0.         1.        ]
 [0.5547002  0.83205029]
 [0.62469505 0.78086881]]

[[0.65079137 0.7592566 ]
 [0.66436384 0.74740932]]

如果我们一起应用它,我们有

[[0.         1.        ]
 [0.5547002  0.83205029]
 [0.62469505 0.78086881]
 [0.65079137 0.7592566 ]
 [0.66436384 0.74740932]]

唯一的区别是在第一种情况下,由于分区,我们有 2 个数组。让我们只是 double-check 组合数组是相同的:

normalized_train_X = normalizer.fit_transform(X_train)
normalized_test_X = normalizer.transform(X_test)
normalized_all_X = normalizer.transform(X)
assert np.allclose(np.vstack((normalized_train_X, normalized_test_X)),normalized_all_X )

没有出现异常;它们在数值上是相同的。

但 sklearn 的转换器有时是有状态的,所以让我们创建一个新对象以确保这不是某些 state-related 行为。

new_normalizer = preprocessing.Normalizer()
new_normalized_all_X = new_normalizer.fit_transform(X)
assert np.allclose(np.vstack((normalized_train_X, normalized_test_X)),new_normalized_all_X )

在第二种情况下,我们仍然没有抛出异常。

我们可以得出结论,对于Normalizer class,数据是否分区没有区别。