PyTorch中的数据增强技术

95

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

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])
    ]),
}

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

我理解得对吗?如果不是,那么我们在上面的代码中告诉PyTorch保留原始图像并将其调整为期望的形状(224,224)的位置在哪里?

谢谢

6个回答

95

我假设你是在问这些数据增强的转换(例如RandomHorizontalFlip)是否实际上增加了数据集的大小,还是逐个应用于数据集中的每个项目并且不会增加数据集的大小

运行以下简单的代码片段,我们可以观察到后者是正确的,即如果您有一个包含8张图片的数据集,并为此数据集创建一个PyTorch数据集对象,当您遍历该数据集时,将在每个数据点上调用变换,并返回转换后的数据点。因此,例如,如果您进行随机翻转,则返回一些数据点原始的,另一些数据点翻转的(例如4个翻转的和4个原始的)。换句话说,在遍历数据集项时,您会得到8个数据点(其中一些翻转,一些没有)。(这与增加数据集的传统理解不符(例如在此情况下具有16个数据点的增强数据集))。

from torch.utils.data import Dataset
from torchvision import transforms

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]]])

19
我认为这就是原帖作者真正想要的问题答案。 - Nicole Finnie
14
这意味着在每个时期,您会得到数据集的不同版本,对吗? - Alexandros
5
@Alexandros 是需要翻译成中文的内容。我可以给您提供一个简单的翻译:“@Alexandros 是指某人在发言或者回复时提到了另一个人的名字。” - Ashkan372
3
但是我如何使用所有的数据集,包括原始和转换后的数据集呢?因为增强的目的之一是增加数据集大小,对吗? - pooria
5
并不一定。数据增强的目的是为了尝试获取未见数据(测试数据)的数据分布的上限,希望神经网络能够近似于该数据分布,并且在近似训练数据的原始分布之间进行权衡(实际上测试数据很可能与训练数据不同)。没有一种通用的数据增强方法或定义,需要根据具体情况而定。 - Nicole Finnie
显示剩余5条评论

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

混淆可能是由于通常情况下,例如在您的示例中,transforms 既用于数据准备(调整大小/裁剪到预期尺寸,规范化值等),也用于数据增强(随机调整大小/裁剪图像,随机翻转图像等)。


data_transforms ['train'] 的作用是:

  • 随机调整提供的图像大小并随机裁剪以获得大小为 (224,224) 的补丁
  • 使用50/50的概率对此补丁进行随机水平翻转或不翻转
  • 将其转换为 Tensor
  • 根据提供的均值和偏差值规范化结果 Tensor

data_transforms ['val'] 的作用是:

  • 将您的图像调整大小至 (256,256)
  • 在调整大小的图像上居中裁剪以获得大小为 (224,224) 的补丁
  • 将其转换为 Tensor
  • 根据提供的均值和偏差值规范化结果 Tensor

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


如果你不想训练图片有50/50的概率被水平翻转,只需删除 transforms.RandomHorizontalFlip() 这一行。
同样地,如果你希望图像始终采用中心裁剪,只需将 transforms.RandomResizedCrop 替换为 transforms.Resizetransforms.CenterCrop,就像对data_transforms['val']所做的那样。

谢谢您的回答。这意味着CNN不会在我拥有的原始图像上进行训练,只会在水平翻转的图像上进行训练。对吗? - Fawaz
不完全正确。您的网络将在从原始数据集随机调整大小和裁剪的图像块上进行训练,并且有时会水平翻转(概率=0.5)。 - benjaminplanche
4
我仍然不清楚哪些转换会增加数据集的大小,哪些转换会改变原始图像。 - insanely_sin
7
所有的变换都会以某种方式改变图像(它们不会触及原始图像,只是返回一个改变后的副本)。给定相同的输入图像,有些方法将总是应用相同的更改(例如将其转换为“张量”,调整大小为固定形状等)。其他方法将使用随机参数应用变换,每次返回不同的结果(例如随机裁剪图像,随机改变它们的亮度或饱和度等)。因为后者从相同的原始样本中每次返回不同的图像,它们会增强数据集。 - benjaminplanche

18

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

如果您希望使用原始数据集以及转换后的数据集,请将它们连接在一起。

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


1
使用这种技术,我成功地将我的数据增加了2倍,但我仍然想知道是否有一种方法可以帮助我自动化这个任务,例如你只需要提供原始数据,然后根据所做的转换,你就可以获得增加了2倍或4倍的扩充数据? - bart-khalid

3

简述:

  • 变换操作会将一组变换以一定的概率应用于输入批次,这些批次在循环中传入。因此,在多个时期的过程中,模型现在能够接触到更多的示例。

  • 个人经验:当我在自己的数据集上训练音频分类模型时,没有使用增强技术,我的模型总是在 72% 的准确率处收敛。我使用了增强技术以及增加了训练时期的数量,这使得验证集的准确率提高到了 89%。


我认为这个答案虽然不够详细,但实际上解决了问题的困惑。关键是每个批次返回的数据是不同的(由于“随机”选项),所以从训练循环中看到的独特图像数量是增加的。你可以运行更多的训练周期而不会过拟合。 对于非随机变换,它们是一种预处理形式,不会增加训练循环中看到的选项数量,但是出于必要性(例如调整大小)而存在。 - undefined

2

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

尽管 data.transforms 不会改变数据集的大小,但是每个 epoch 我们都会重新调用数据集,进行转换操作并得到不同的数据。

我稍微修改了 @Ashkan372 的代码以输出多个 epochs 的数据:

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]]]])

不同的时代会得到不同的输出!

0
在PyTorch中,有两种裁剪方式会改变数据集的大小。它们分别是FiveCropTenCrop

CLASS torchvision.transforms.FiveCrop(size)

Crop the given image into four corners and the central crop.

This transform returns a tuple of images and there may be a mismatch in the number of inputs and targets your Dataset returns. See below for an example of how to deal with this.

Example:

>>> 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 是五个裁剪图像的水平翻转版本加上原始图像的组合(默认使用水平翻转)。


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