69

我对 PyTorch 中执行的数据增强有点困惑。现在,据我所知,当我们执行数据增强时,我们会保留我们的原始数据集,然后添加它的其他版本(翻转、裁剪......等)。但这似乎不会在 PyTorch 中发生。据我从参考资料中了解到,当我们data.transforms在 PyTorch 中使用时,它会一一应用它们。例如:

data_transforms = {
    'train': transforms.Compose([
        transforms.RandomResizedCrop(224),
        transforms.RandomHorizontalFlip(),
        transforms.ToTensor(),
        transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
    ]),
    'val': transforms.Compose([
        transforms.Resize(256),
        transforms.CenterCrop(224),
        transforms.ToTensor(),
        transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
    ]),
}

在这里,对于训练,我们首先随机裁剪图像并将其调整为 shape (224,224)。然后我们拍摄这些(224,224)图像并水平翻转它们。因此,我们的数据集现在只包含水平翻转的图像,因此在这种情况下我们的原始图像丢失了。

我对吗?这种理解正确吗?如果不是,那么我们在上面的代码中(取自官方文档)在哪里告诉 PyTorch 保留原始图像并将它们调整为预期的形状(224,224)

谢谢

4

6 回答 6

74

我假设您在问这些数据增强转换(例如 RandomHorizo​​ntalFlip)是否实际上也增加了数据集的大小,或者它们是否一一应用于数据集中的每个项目而不增加数据集的大小

运行以下简单的代码片段,我们可以观察到后者是正确的,即如果您有一个包含 8 个图像的数据集,并在您遍历数据集时为该数据集创建一个 PyTorch 数据集对象,则在每个数据点上调用转换,并返回转换后的数据点。例如,如果你有随机翻转,一些数据点作为原始返回,一些作为翻转返回(例如 4 个翻转和 4 个原始)。换句话说,通过数据集项的一次迭代,你得到 8 个数据点(一些翻转,一些没有)。[这与增强数据集的传统理解不一致(例如,在这种情况下,增强数据集中有 16 个数据点)]

class experimental_dataset(Dataset):

    def __init__(self, data, transform):
        self.data = data
        self.transform = transform

    def __len__(self):
        return len(self.data.shape[0])

    def __getitem__(self, idx):
        item = self.data[idx]
        item = self.transform(item)
        return item

    transform = transforms.Compose([
        transforms.ToPILImage(),
        transforms.RandomHorizontalFlip(),
        transforms.ToTensor()
    ])

x = torch.rand(8, 1, 2, 2)
print(x)

dataset = experimental_dataset(x,transform)

for item in dataset:
    print(item)

结果:(浮点数的微小差异是由于转换为pil图像并返回)

原始虚拟数据集:

tensor([[[[0.1872, 0.5518],
          [0.5733, 0.6593]]],


    [[[0.6570, 0.6487],
      [0.4415, 0.5883]]],


    [[[0.5682, 0.3294],
      [0.9346, 0.1243]]],


    [[[0.1829, 0.5607],
      [0.3661, 0.6277]]],


    [[[0.1201, 0.1574],
      [0.4224, 0.6146]]],


    [[[0.9301, 0.3369],
      [0.9210, 0.9616]]],


    [[[0.8567, 0.2297],
      [0.1789, 0.8954]]],


    [[[0.0068, 0.8932],
      [0.9971, 0.3548]]]])

转换后的数据集:

tensor([[[0.1843, 0.5490],
     [0.5725, 0.6588]]])
tensor([[[0.6549, 0.6471],
     [0.4392, 0.5882]]])
tensor([[[0.5647, 0.3255],
         [0.9333, 0.1216]]])
tensor([[[0.5569, 0.1804],
         [0.6275, 0.3647]]])
tensor([[[0.1569, 0.1176],
         [0.6118, 0.4196]]])
tensor([[[0.9294, 0.3333],
         [0.9176, 0.9608]]])
tensor([[[0.8549, 0.2275],
         [0.1765, 0.8941]]])
tensor([[[0.8902, 0.0039],
         [0.3529, 0.9961]]])
于 2019-01-31T12:07:57.823 回答
49

这些transforms操作会在每个批次生成时应用于您的原始图像。因此,您的数据集保持不变,每次迭代只复制和转换批量图像。

混淆可能来自这样一个事实,就像在您的示例中一样,transforms通常用于数据准备(调整/裁剪到预期尺寸,标准化值等)和数据增强(随机调整大小/裁剪,随机翻转图像, ETC。)。


data_transforms['train']所做的是:

  • 随机调整提供的图像大小并随机裁剪它以获得(224, 224)补丁
  • 对这个补丁应用或不随机水平翻转,有 50/50 的机会
  • 将其转换为Tensor
  • Tensor给定您提供的平均值和偏差值,对结果进行归一化

data_transforms['val']所做的是:

  • 将图像大小调整为(256, 256)
  • 居中裁剪调整大小的图像以获得(224, 224)补丁
  • 将其转换为Tensor
  • Tensor给定您提供的平均值和偏差值,对结果进行归一化

(即训练数据的随机调整大小/裁剪被验证的固定操作代替,以获得可靠的验证结果)


如果您不希望您的训练图像以 50/50 的机会水平翻转,只需删除该transforms.RandomHorizontalFlip()线即可。

同样,如果您希望图像始终居中裁剪,请替换transforms.RandomResizedCroptransforms.Resizetransforms.CenterCrop,就像data_transforms['val'].

于 2018-08-03T18:19:53.537 回答
9

是的,数据集大小在转换后不会改变。每个图像都被传递给转换并返回,因此大小保持不变。

如果您希望将原始数据集与转换后的数据集结合使用。

例如increased_dataset = torch.utils.data.ConcatDataset([transformed_dataset,original])

于 2020-09-11T18:20:38.597 回答
1

TLDR:

  • 变换操作以一定的概率对循环中的输入批次应用一堆变换。因此,该模型现在在多个时期的过程中暴露于更多示例。

  • 就个人而言,当我在自己的数据集上训练音频分类模型时,在增强之前,我的模型似乎总是以 72% 的准确率收敛。我使用了增强以及增加了训练 epoch 的数量,这将测试集中的验证准确率提高到了 89%。

于 2021-03-07T07:06:26.697 回答
0

数据增强的目的是增加训练数据集的多样性。

尽管data.transforms不会改变数据集的大小,但是,我们每次调用数据集的 epoch,都会执行转换操作,然后得到不同的数据。

我稍微更改了@Ashkan372 代码以输出多个时期的数据:</p>

import torch
from torchvision import transforms
from torch.utils.data import TensorDataset as Dataset
from torch.utils.data import DataLoader

class experimental_dataset(Dataset):
  def __init__(self, data, transform):
    self.data = data
    self.transform = transform

  def __len__(self):
    return self.data.shape[0]

  def __getitem__(self, idx):
    item = self.data[idx]
    item = self.transform(item)
    return item

transform = transforms.Compose([
  transforms.ToPILImage(),
  transforms.RandomHorizontalFlip(),
  transforms.ToTensor()
])

x = torch.rand(8, 1, 2, 2)
print('the original data: \n', x)

epoch_size = 3
batch_size = 4

dataset = experimental_dataset(x,transform)
for i in range(epoch_size):
  print('----------------------------------------------')
  print('the epoch', i, 'data: \n')
  for item in DataLoader(dataset, batch_size, shuffle=False):
    print(item)

输出是:

the original data: 
 tensor([[[[0.5993, 0.5898],
          [0.7365, 0.5472]]],


        [[[0.1878, 0.3546],
          [0.2124, 0.8324]]],


        [[[0.9321, 0.0795],
          [0.4090, 0.9513]]],


        [[[0.2825, 0.6954],
          [0.3737, 0.0869]]],


        [[[0.2123, 0.7024],
          [0.6270, 0.5923]]],


        [[[0.9997, 0.9825],
          [0.0267, 0.2910]]],


        [[[0.2323, 0.1768],
          [0.4646, 0.4487]]],


        [[[0.2368, 0.0262],
          [0.2423, 0.9593]]]])
----------------------------------------------
the epoch 0 data: 

tensor([[[[0.5882, 0.5961],
          [0.5451, 0.7333]]],


        [[[0.3529, 0.1843],
          [0.8314, 0.2118]]],


        [[[0.9294, 0.0784],
          [0.4078, 0.9490]]],


        [[[0.6941, 0.2824],
          [0.0863, 0.3725]]]])
tensor([[[[0.7020, 0.2118],
          [0.5922, 0.6235]]],


        [[[0.9804, 0.9961],
          [0.2902, 0.0235]]],


        [[[0.2314, 0.1765],
          [0.4627, 0.4471]]],


        [[[0.0235, 0.2353],
          [0.9569, 0.2392]]]])
----------------------------------------------
the epoch 1 data: 

tensor([[[[0.5882, 0.5961],
          [0.5451, 0.7333]]],


        [[[0.1843, 0.3529],
          [0.2118, 0.8314]]],


        [[[0.0784, 0.9294],
          [0.9490, 0.4078]]],


        [[[0.2824, 0.6941],
          [0.3725, 0.0863]]]])
tensor([[[[0.2118, 0.7020],
          [0.6235, 0.5922]]],


        [[[0.9804, 0.9961],
          [0.2902, 0.0235]]],


        [[[0.2314, 0.1765],
          [0.4627, 0.4471]]],


        [[[0.0235, 0.2353],
          [0.9569, 0.2392]]]])
----------------------------------------------
the epoch 2 data: 

tensor([[[[0.5882, 0.5961],
          [0.5451, 0.7333]]],


        [[[0.3529, 0.1843],
          [0.8314, 0.2118]]],


        [[[0.0784, 0.9294],
          [0.9490, 0.4078]]],


        [[[0.6941, 0.2824],
          [0.0863, 0.3725]]]])
tensor([[[[0.2118, 0.7020],
          [0.6235, 0.5922]]],


        [[[0.9961, 0.9804],
          [0.0235, 0.2902]]],


        [[[0.2314, 0.1765],
          [0.4627, 0.4471]]],


        [[[0.0235, 0.2353],
          [0.9569, 0.2392]]]])

不同的时期我们得到不同的输出!

于 2022-03-02T08:22:09.517 回答
0

在 PyTorch 中,有一些裁剪类型会改变数据集的大小。这些是FiveCropTenCrop

类 torchvision.transforms.FiveCrop(大小)

将给定的图像裁剪成四个角和中央裁剪。

此转换返回图像元组,并且您的 Dataset 返回的输入和目标数量可能不匹配。有关如何处理此问题的示例,请参见下文。

例子:

>>> transform = Compose([
>>>    TenCrop(size), # this is a list of PIL Images
>>>    Lambda(lambda crops: torch.stack([ToTensor()(crop) for crop in crops])) # returns a 4D tensor
>>> ])
>>> #In your test loop you can do the following:
>>> input, target = batch # input is a 5d tensor, target is 2d
>>> bs, ncrops, c, h, w = input.size()
>>> result = model(input.view(-1, c, h, w)) # fuse batch size and ncrops
>>> result_avg = result.view(bs, ncrops, -1).mean(1) # avg over crops

TenCrop是相同的加上五个补丁的翻转版本(默认使用水平翻转)。

于 2021-06-25T13:05:33.557 回答