正确的MNIST数据集归一化和缩放方法

16

我已经四处寻找,但仍找不到我想要的东西。基本上,MNIST数据集中的图像具有[0,255]范围内的像素值。人们通常建议执行以下操作:

  • 将数据缩放到[0,1]范围内。
  • 对数据进行标准化,使其具有零均值和单位标准差(data-mean) / std

不幸的是,没有人展示如何同时执行这两个操作。他们都会减去0.1307的均值并除以0.3081的标准差。这些值基本上是数据集的平均值和标准差除以255得出的结果:

from torchvision.datasets import MNIST        
import torchvision.transforms as transforms 

trainset = torchvision.datasets.MNIST(root='./data', train=True, download=True)
print('Min Pixel Value: {} \nMax Pixel Value: {}'.format(trainset.data.min(), trainset.data.max()))
print('Mean Pixel Value {} \nPixel Values Std: {}'.format(trainset.data.float().mean(), trainset.data.float().std()))
print('Scaled Mean Pixel Value {} \nScaled Pixel Values Std: {}'.format(trainset.data.float().mean() / 255, trainset.data.float().std() / 255))

这将输出以下内容

Min Pixel Value: 0 
Max Pixel Value: 255
Mean Pixel Value 33.31002426147461 
Pixel Values Std: 78.56748962402344
Scaled Mean: 0.13062754273414612 
Scaled Std: 0.30810779333114624

然而,这样做显然没有实现上述任何一个目标!得到的数据 1) 不会在 [0, 1] 范围内,并且其均值不是 0 或标准差不是 1。实际上我们在做的是:

[data - (mean / 255)] / (std / 255)

与下面的方法非常不同

[(scaled_data) - (mean/255)] / (std/255)

其中 scaled_data 就是 data / 255

5个回答

22

Euler_Salter

我可能有点晚了解到这个问题,但希望我能提供一些帮助。

假设您正在使用torchvision.Transform,以下代码可用于归一化MNIST数据集。

        train_loader = torch.utils.data.DataLoader(
        datasets.MNIST('./data', train=True
        transform=transforms.Compose([
        transforms.ToTensor(),
        transforms.Normalize((0.1307,), (0.3081,))
        ])),

通常使用'transforms.ToTensor()'将输入数据从[0,255]范围转换为三维张量。 此函数会自动将输入数据缩放到[0,1]范围内。 (相当于将数据缩小到0和1之间)

因此,在“transforms.Normalize(...)”中使用的平均值和标准差分别为0.1307和0.3081是有意义的。(这相当于对零均值和单位标准差进行归一化。)

更好的解释请参考以下链接。

https://pytorch.org/vision/stable/transforms.html


2
此答案回答了OP问题中所问的关键点。 - Shikhin Mehrotra

12

我认为您误解了一个重要的概念:这是两个不同且矛盾的缩放操作。您只能选择其中之一:

  • 平均值=0,标准差=1
  • 数据范围[0,1]

考虑一下[0,1]范围:如果数据全部是小正数,最小值为0,最大值为1,则数据的总和必须是正数,给出一个正的非零均值。同样,当没有任何数据可能与平均值相差1.0时,标准差不能1

反过来,如果您有平均值=0,则其中一些数据必须为负数。


您仅使用其中一个变换。您使用哪个取决于数据集的特性以及最终为您的模型起作用的哪一个。

对于[0,1]缩放,您只需除以255。

对于平均值=0,标准差=1缩放,您执行已知的简单线性变换:

new_val = (old_val - old_mean) / old_stdev

这样解释清楚了吗?还是我完全没有理解你的困惑点?


2
它确实澄清了很多问题,但问题在于:当值保持在范围 [0, 255] 内且数据仅进行归一化(而不是缩放)时,每个人似乎都在使用错误的平均值和错误的标准差。基本上,他们使用的是如果将数据缩放到 [0, 1],你会发现的平均值和标准差。 - Euler_Salter
2
数据的均值和标准差在不进行缩放的情况下(即在范围 [0, 255] 内)分别为 33.3178.56。相反,缩放后的均值和标准差(将 33 和 78 分别除以 255)为 0.13060.3081。出于某种原因,即使他们没有将数据缩放到 [0, 1] 之间,每个人都使用这两个缩放值。他们使用了缩放后的均值/标准差,但是他们的数据并没有被缩放! - Euler_Salter

9

目的

特征缩放的两个最重要的原因如下:

  1. 你需要对特征进行缩放,使它们具有相同的数量级(即重要性或权重)。

例如

数据集包含两个特征: 年龄和体重。年龄以年为单位,体重以克为单位!现在一个20岁、体重只有60公斤的人将转换为向量=[20岁,60000g],整个数据集也是如此。在训练过程中,体重属性会占主导地位。这取决于所使用的算法类型--某些算法比其他算法更敏感:例如,神经网络的梯度下降学习率会受到神经网络 thetas(即权重) 的数量级及其与训练过程中输入(即特征)之间的相关性的影响; 同时,特征缩放可以提高收敛速度。另一个例子是K-Mean聚类算法需要具有相同数量级的特征,因为它在空间的所有方向上都是各向同性的。 有趣的列表

  1. 你需要对特征进行缩放,以加快执行时间。
这很简单:所有这些矩阵乘法和参数求和都会比使用非常大的数字(或通过将特征乘以其他参数产生的非常大的数字)更快。

类型

特征缩放器最流行的类型可以总结如下:

  1. StandardScaler:通常是您的首选,非常常用。它通过标准化数据(即将它们居中),使它们达到STD=1Mean=0的状态。但它会受到离群值的影响,只有在数据具有类似高斯分布的情况下才应使用。

enter image description here

  1. MinMaxScaler: 通常用于将所有数据点转换为特定范围(例如[0-1])。由于它使用Range它受异常值的影响很大

enter image description here

  1. RobustScaler:它对异常值具有"鲁棒性",因为它根据分位数范围来缩放数据。但是,您应该知道在缩放后的数据中仍然存在异常值。

enter image description here

  1. MaxAbsScaler:主要用于稀疏数据

enter image description here

  1. 单位标准化:它基本上是将每个样本的向量缩放为单位范数,独立于样本分布。

enter image description here


哪一个和多少个

首先,您需要了解数据集。如上所述,在此之前,有些事情需要注意,例如:数据的分布异常值的存在以及所使用的算法

无论如何,每个数据集都需要一个缩放器,除非有特定的要求,例如如果存在一种只能在数据处于某个范围内且具有零均值和标准差为1的算法 - 所有这些条件必须同时满足。然而,我从未遇到过这样的情况。


主要观点

  • 根据上述经验法则,有不同类型的特征缩放器可供使用。

  • 您应基于需求选择一个缩放器,而非随意选择。

  • 您会为了某个目的而对数据进行缩放,例如,在随机森林算法中通常不需要进行缩放(链接1)


1
也许我的问题没有表达清楚。问题在于人们使用了错误的平均值和标准差(至少看起来是这样)。 - Euler_Salter

2

0
我想补充说明,这些转换只有在被DataLoader实例访问时才会执行。
即使包括转换,OP提到的代码片段也会产生相同的结果。
transform = transforms.Compose([
    transforms.ToTensor(),
    transforms.Normalize((0.1307,), (0.3081,)) # calculated mean and std 
])

trainset = torchvision.datasets.MNIST(root='./data', train=True, download=True, transform = transform)

print('Min Pixel Value: {} \nMax Pixel Value: {}'.format(trainset.data.min(), trainset.data.max()))
print('Mean Pixel Value {} \nPixel Values Std: {}'.format(trainset.data.float().mean(), trainset.data.float().std()))
print('Scaled Mean Pixel Value {} \nScaled Pixel Values Std: {}'.format(trainset.data.float().mean() / 255, trainset.data.float().std() / 255))

生产,

Min Pixel Value: 0 
Max Pixel Value: 255
Mean Pixel Value 33.31842041015625 
Pixel Values Std: 78.56748962402344
Scaled Mean Pixel Value 0.13066047430038452 
Scaled Pixel Values Std: 0.30810779333114624

但是当被DataLoader访问时,例如,
train_loader = DataLoader(
    trainset,
    batch_size=batch_size,
)
torch.manual_seed(5576)
for (x, y) in train_loader:
    print('Min Pixel Value: {} \nMax Pixel Value: {}'.format(x.min(), x.max()))
    print('Mean Pixel Value {} \nPixel Values Std: {}'.format(x.float().mean(), x.float().std()))
    break

生成的结果是这样的,
Min Pixel Value: -0.4242129623889923 
Max Pixel Value: 2.821486711502075
Mean Pixel Value -0.012694964185357094 
Pixel Values Std: 0.9848554730415344

这意味着在数据集实例化期间存储了转换,并且在通过DataLoader访问时动态执行。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接