PyTorch 中的数据增强
Data Augmentation in PyTorch
我对 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)
?
谢谢
transforms
操作会在每个批次生成时应用于您的原始图像。所以你的数据集保持不变,每次迭代只复制和转换批处理图像。
混淆可能来自这样一个事实,就像在您的示例中一样,transforms
通常用于数据准备(resizing/cropping 到预期尺寸、规范化值等)和数据增强(随机化 resizing/cropping、随机翻转图像等)。
你的data_transforms['train']
所做的是:
- 随机调整提供的图像大小并随机裁剪它以获得
(224, 224)
补丁
- 对该补丁应用或不应用随机水平翻转,有 50/50 的机会
- 将其转换为
Tensor
- 根据您提供的均值和偏差值对结果进行标准化
Tensor
你的data_transforms['val']
所做的是:
- 将图片大小调整为
(256, 256)
- 中心裁剪调整大小的图像以获得
(224, 224)
补丁
- 将其转换为
Tensor
- 根据您提供的均值和偏差值对结果进行标准化
Tensor
(即训练数据的随机 resizing/cropping 被验证数据的固定操作所取代,以获得可靠的验证结果)
如果您不希望训练图像以 50/50 的几率水平翻转,只需删除 transforms.RandomHorizontalFlip()
行。
同样,如果您希望图像始终居中裁剪,请将 transforms.RandomResizedCrop
替换为 transforms.Resize
和 transforms.CenterCrop
,就像 data_transforms['val']
所做的那样。
我假设您是在问这些数据增强变换(例如 RandomHorizontalFlip)是否实际上 也增加了数据集的大小 ,或者 它们是否应用于每个数据集数据集中的一项一项,而不是增加数据集的大小。
运行 下面的简单代码片段我们可以观察到 后者是正确的,即如果你有一个包含 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]]])
是的,数据集大小在转换后没有改变。每个图像都传递给转换并返回,因此大小保持不变。
如果您希望将原始数据集与转换后的数据集结合使用。
例如increased_dataset = torch.utils.data.ConcatDataset([transformed_dataset,original])
TLDR :
变换操作以一定的概率对循环中的输入批次应用一堆变换。因此,模型现在在多个 epoch 的过程中接触到更多示例。
就个人而言,当我在自己的数据集上训练音频分类模型时,在增强之前,我的模型似乎总是以 72% 的准确率收敛。我使用了扩充和增加的训练时期数,这将测试集中的验证准确率提高到了 89%。
在 PyTorch 中,有一些裁剪类型会改变数据集的大小。这些是 FiveCrop
and TenCrop
:
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
同上加上五个patch的翻转版本(默认使用水平翻转)。
数据增强的目的是增加训练数据集的多样性。
虽然data.transforms
并没有改变数据集的大小,但是,我们每次调用数据集的epoch,都会执行transforms操作,然后得到不同的数据。
我稍微更改了@Ashkan372 代码以输出多个时期的数据:
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]]]])
不同的epoch我们得到不同的输出!
我对 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)
?
谢谢
transforms
操作会在每个批次生成时应用于您的原始图像。所以你的数据集保持不变,每次迭代只复制和转换批处理图像。
混淆可能来自这样一个事实,就像在您的示例中一样,transforms
通常用于数据准备(resizing/cropping 到预期尺寸、规范化值等)和数据增强(随机化 resizing/cropping、随机翻转图像等)。
你的data_transforms['train']
所做的是:
- 随机调整提供的图像大小并随机裁剪它以获得
(224, 224)
补丁 - 对该补丁应用或不应用随机水平翻转,有 50/50 的机会
- 将其转换为
Tensor
- 根据您提供的均值和偏差值对结果进行标准化
Tensor
你的data_transforms['val']
所做的是:
- 将图片大小调整为
(256, 256)
- 中心裁剪调整大小的图像以获得
(224, 224)
补丁 - 将其转换为
Tensor
- 根据您提供的均值和偏差值对结果进行标准化
Tensor
(即训练数据的随机 resizing/cropping 被验证数据的固定操作所取代,以获得可靠的验证结果)
如果您不希望训练图像以 50/50 的几率水平翻转,只需删除 transforms.RandomHorizontalFlip()
行。
同样,如果您希望图像始终居中裁剪,请将 transforms.RandomResizedCrop
替换为 transforms.Resize
和 transforms.CenterCrop
,就像 data_transforms['val']
所做的那样。
我假设您是在问这些数据增强变换(例如 RandomHorizontalFlip)是否实际上 也增加了数据集的大小 ,或者 它们是否应用于每个数据集数据集中的一项一项,而不是增加数据集的大小。
运行 下面的简单代码片段我们可以观察到 后者是正确的,即如果你有一个包含 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]]])
是的,数据集大小在转换后没有改变。每个图像都传递给转换并返回,因此大小保持不变。
如果您希望将原始数据集与转换后的数据集结合使用。
例如increased_dataset = torch.utils.data.ConcatDataset([transformed_dataset,original])
TLDR :
变换操作以一定的概率对循环中的输入批次应用一堆变换。因此,模型现在在多个 epoch 的过程中接触到更多示例。
就个人而言,当我在自己的数据集上训练音频分类模型时,在增强之前,我的模型似乎总是以 72% 的准确率收敛。我使用了扩充和增加的训练时期数,这将测试集中的验证准确率提高到了 89%。
在 PyTorch 中,有一些裁剪类型会改变数据集的大小。这些是 FiveCrop
and TenCrop
:
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
同上加上五个patch的翻转版本(默认使用水平翻转)。
数据增强的目的是增加训练数据集的多样性。
虽然data.transforms
并没有改变数据集的大小,但是,我们每次调用数据集的epoch,都会执行transforms操作,然后得到不同的数据。
我稍微更改了@Ashkan372 代码以输出多个时期的数据:
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]]]])
不同的epoch我们得到不同的输出!