通常在训练时我们会将数据集分成若干小的、随机的批(batch),这个操作当然可以手动操作,但是pytorch里面为我们提供了API让我们方便地从dataset中获得batch,DataLoader就是来解决这个问题的。
DataLoader的本质是一个可迭代对象,即经过DataLoader的返回值为一个可迭代的对象,一般的操作是:1、创建一个 dataset 对象;2、创建一个DataLoader对象;3、遍历这个DataLoader对象,将data, label加载到模型中进行训练。
torch.utils.data.DataLoader(dataset, batch_size=1, shuffle=False, sampler=None, \
batch_sampler=None, num_workers=0, collate_fn=None, pin_memory=False, \
drop_last=False, timeout=0, worker_init_fn=None, multiprocessing_context=None)
-
dataset:官方文档给出的解释是 “ 从中加载数据的数据集 ” 。
-
batch_size(int,可选):每个批次要加载的样本数(默认值:1)。
-
shuffle(bool,可选):设置为“True”可在每个epoch重新排列数据(默认值:“False”)。一个epoch表示数据集的数据全部使用一遍。
-
sampler(sampler,可选):定义从数据集中提取样本的策略。如果指定,“shuffle”必须为False。batch_sampler 类似,表示一次返回一个batch的索引。
-
num_workers(int,可选):用于数据加载的子进程数,0 表示将在主进程中加载数据。(默认值:0)。换句话说,num_workers = 0 表示在主进程中加载数据而不使用任何额外的子进程;若大于0,表示开启多个进程。进程越多,处理数据的速度越快,但会使电脑性能下降,占用更多的内存。
-
collate_fn(可调用,可选):表示合并样本列表以形成小批量的Tensor对象。
-
pin_memory(bool,可选):表示要将load进来的数据是否要拷贝到pin_memory区中,其表示生成的Tensor数据是属于内存中的锁页内存区,这样将Tensor数据转义到GPU中速度就会快一些,默认为False。如果为“True”,数据加载程序将在返回张量之前将张量复制到CUDA固定内存中。通常情况下,数据在内存中要么以锁页的方式存在,要么保存在虚拟内存(磁盘)中,pin_memory设置为True后,数据直接保存在锁页内存中,后续直接传入CUDA;否则需要先从虚拟内存中传入锁页内存中,再传入CUDA,这样就比较耗时了。
-
drop_last(bool,可选):当整个数据长度不能够整除batch_size,选择是否要丢弃最后一个不完整的batch,默认为False。设置为“True”时可以删除最后一个不完整的批次(batch)。
1 处理数据集
在许多情况下,在众所周知的数据集(如 MNIST 或 CIFAR)上训练神经网络,可以实现预测的准确率超过 90%。原因是,这些数据集组织整齐且易于预处理。但是,当处理自己的数据集时,要实现高精度非常棘手且具有挑战性。
我们将快速浏览一下 PyTorch 库中包含的数据集。PyTorch 带有几个内置的数据集,所有这些都预加载在torch.datasets
类中。torch
有torchvision
,该torch
包实现神经网络所需的所有核心类和方法,torchvision
包含流行的数据集、模型架构和计算机视觉的常见图像转换。
1.1 Torchvision 中的数据集
MNIST: 经过标准化和中心裁剪的手写图像数据集。它有超过 60,000 张训练图像和 10,000 张测试图像。这是用于学习和实验的最常用的数据集之一。使用以下语法导入torchvision
要加载和使用的数据集。
torchvision.datasets.MNIST()
Fashion MNIST:该数据集与 MNIST 类似,但该数据集不是手写数字,而是 T 恤、裤子、包等服装项目。训练和测试样本的数量分别为 60,000 和 10,000。
torchvision.datasets.FashionMNIST()
CIFAR: CIFAR 数据集有两个版本,CIFAR10 和 CIFAR100。CIFAR10 由 10 个不同标签的图像组成,而 CIFAR100 有 100 个不同的类别。其中包括卡车、青蛙、船、汽车、鹿等常见图像。
torchvision.datasets.CIFAR10()
torchvision.datasets.CIFAR100()
COCO:这个数据集有超过 100,000 个日常物品,如人、瓶子、文具、书籍等。这个图像数据集广泛用于对象检测和图像描述。
torchvision.datasets.CocoCaptions()
EMNIST:是 MNIST 数据集的高级版本。包含数字和字母的图像。如果您正在处理从图像中识别文本,这个数据集很适合。
torchvision.datasets.EMNIST()
IMAGE-NET: 有超过 120 万张图像,包含 10,000 个类别。通常,此数据集加载在高端硬件系统上,因为单独的 CPU 无法处理这么大的数据集。
torchvision.datasets.ImageNet()
以上这些是在 PyTorch 中构建神经网络时最常用的数据集,还包括其他数据集如 KMNIST、QMNIST、LSUN、STL10、SVHN、PhotoTour、SBU、Cityscapes、SBD、USPS、Kinetics-400。可以从PyTorch 官方文档中了解更多信息。
还有一个名为torchtext
的包,它具有 PyTorch 自然语言处理基本的实用程序,包含文本相关的数据集。
1.2 Torchtext 中的数据集
IMDB:这是一个用于情感分类的数据集,其中包含一组用于训练的 25,000 条高度极端的电影评论,以及另外 25,000 条用于测试的评论。可以使用以下类从中加载此数据torchtext
:
torchtext.datasets.IMDB()
WikiText2:这个语言建模数据集是超过 1 亿个标记的集合。它摘自维基百科并保留了标点符号和实际的字母大小写。它广泛用于涉及长期依赖性的应用程序。
torchtext.datasets.WikiText2()
除了上述两个流行的数据集,库中还有更多可用的数据集,例如 SST、TREC、SNLI、MultiNLI、WikiText-2、WikiText103、PennTreebank、Multi30k 等。
到目前为止,我们已经看到了预定义图像和文本的数据集。如果你有自己的数据集呢?如何加载它?现在让我们学习这个ImageFolder
类,用它来加载自己的图像数据集。
1.3 ImageFolder 类
ImageFolder
是torchvision
的一个通用数据加载器类,可以加载自己的图像数据集。假设读者正在处理分类问题并构建神经网络来识别给定图像是苹果还是橙子。在 PyTorch 中第一步是在默认文件夹结构中排列图像,如下所示:
root
├── orange
│ ├── orange_image1.png
│ └── orange_image1.png
├── apple
│ └── apple_image1.png
│ └── apple_image2.png
│ └── apple_image3.png
安排数据集后,可以使用ImageLoader
该类加载所有这些图像。以下代码:
torchvision.datasets.ImageFolder(root, transform)
接下来,让我们看看如何将数据加载到我们的程序中。
2 PyTorch 中的数据加载
数据加载是构建深度学习训练模型的第一步。当数据的复杂性增加时,这项任务变得更具挑战性。接下来将了解DataLoader
加载和迭代数据集。此类DataLoader
在torch.utils.data
模块中。
from torch.utils.data import DataLoader
现在详细讨论DataLoader
类的参数。
from torch.utils.data import DataLoader
DataLoader(
dataset,
batch_size=1,
shuffle=False,
num_workers=0,
collate_fn=None,
pin_memory=False,
)
-
数据集:类中的第一个参数
dataset
是加载数据的地方。 -
对数据进行批处理:
batch_size
指在一次迭代中训练样本的数量,通常将数据分成训练集和测试集,每个数据集的批量大小可能不同。 -
重排数据:
shuffle
参数采用布尔值 (True/False)。如果 shuffle 设置为True
,则所有样本都被打乱并分批加载。否则,它们将被一个接一个地发送,而不会进行任何打乱。 -
允许多进程:由于深度学习涉及用大量数据训练模型,只运行单个进程最终会花费大量时间。在 PyTorch 中,可以通过使用参数允许增加同时运行的进程数
num_workers
。默认为0,代表只使用主进程。 -
合并数据集:
collate_fn
如果想合并数据集,则使用该参数。此参数是可选的,合并样本列表以形成小批量的Tensor
对象。 -
在 CUDA 张量上加载数据:
pin_memory
参数直接将数据集加载为 CUDA 张量。它是一个可选参数,接受一个布尔值;如果设置为True
,会在返回张量之前将张量复制到 CUDA 固定内存中。
让我们看一个示例,以更好地理解数据加载。
2.1 深入了解 MNIST 数据集
首先下载数据集并将其加载到名为data_train
. ,然后打印样本图像。
# Import MNIST
from torchvision.datasets import MNIST
# Download and Save MNIST
data_train = MNIST('~/mnist_data', train=True, download=True)
# Print Data
print(data_train)
print(data_train[12])
现在提取元组,其中第一个值对应于图像,第二个值对应于其标签。
import matplotlib.pyplot as plt
random_image = data_train[0][0]
random_image_label = data_train[0][1]
# Print the Image using Matplotlib
plt.imshow(random_image)
print("The label of the image is:", random_image_label)
大多数时候,不会访问带有索引的图像,而是将包含图像的矩阵发送到模型。当需要准备数据批次时(并且可能在每次运行之前将它们打乱),如下所示。
import torch
from torchvision import transforms
data_train = torch.utils.data.DataLoader(
MNIST(
'~/mnist_data', train=True, download=True,
transform = transforms.Compose([
transforms.ToTensor()
])),
batch_size=64,
shuffle=True
)
for batch_idx, samples in enumerate(data_train):
print(batch_idx, samples)
这就是如何加载一个DataLoader
里简单的数据集,但不能总是依赖于DataLoader
每个数据集。若处理包含非对称分辨率的图像、大型或不规则数据集,这正是 GPU 发挥重要作用的地方。
2.2 在 GPU 上加载数据
可以启用 GPU 来更快地训练模型。现在看一下加载数据时可以使用的配置。
device = "cuda" if torch.cuda.is_available() else "cpu"
kwargs = {'num_workers': 1, 'pin_memory': True} if device=='cuda' else {}
train_loader = torch.utils.data.DataLoader(
torchvision.datasets.MNIST('/files/', train=True, download=True),
batch_size=batch_size_train, **kwargs)
test_loader = torch.utils.data.DataLoader(
torchvision.datasets.MNIST('files/', train=False, download=True),
batch_size=batch_size, **kwargs)
在上面,声明了一个名为device
的新变量。接下来,编写一个简单的if
条件来检查当前的硬件配置。如果它支持GPU
,它将设置device
为cuda
,否则它将设置为cpu
。该变量num_workers
表示并行生成批处理的进程数。对于数据加载,传递pin_memory=True
给DataLoader
类会自动将获取的数据张量放入固定内存中,从而使数据能够更快地传输到支持 CUDA 的 GPU。
接下来将了解转换,它定义了加载数据的预处理步骤。
3 数据的预处理
PyTorch 转换定义了简单的图像转换技术,可将整个数据集转换为一种独特的格式。例如,考虑一个包含不同分辨率的不同汽车图片的数据集。在训练时,数据集中的所有图像都应该具有相同的分辨率大小。
如果我们手动将所有图像转换为所需的输入尺寸,则非常耗时,因此可以使用 transforms 代替;使用几行 PyTorch 代码,数据集中的所有图像都可以转换为所需的输入大小和分辨率。几个最常用的操作是:
transforms.Resize()
调整图像大小;transforms.CenterCrop()
从中心裁剪图像;transforms.RandomResizedCrop()
随机调整数据集中所有图像的大小;
现在加载 CIFAR10torchvision.datasets
并实现以下转换:
- 将所有图像调整为 32×32
- 对图像应用中心裁剪变换
- 将裁剪后的图像转换为张量
- 规范化图像
首先导入必要的模块,以及transforms
模块。NumPy 和 Matplotlib 库用于可视化数据集。
import torch
import torchvision
import torchvision.transforms as transforms
import matplotlib.pyplot as plt
import numpy as np
接下来定义一个名为 transforms
的变量,其中按顺序编写所有预处理步骤。使用Compose
该类将所有转换操作链接在一起。
transform = transforms.Compose([
# resize
transforms.Resize(32),
# center-crop
transforms.CenterCrop(32),
# to-tensor
transforms.ToTensor(),
# normalize
transforms.Normalize([0.5, 0.5, 0.5], [0.5, 0.5, 0.5])
])
resize
:将所有图像转换为定义的大小。在MNIST数据集中将所有图像的大小调整为 32×32。center-crop
:使用CenterCrop
变换裁剪图像。发送的参数也是分辨率/大小,32x32 意味着图像将从中心(垂直和水平)裁剪 32 个单位。to-tensor
: 将图像转换为Tensor
数据类型。normalize
:将张量中的所有值归一化,使它们介于 0.5 和 1 之间。
在下一步中,将转换后CIFAR
数据集加载到trainloader
。Dataloader
是一个迭代器,最基本的使用就是传入一个 Dataset
对象,它就会根据参数 batch_size 的值生成一个 batch 的数据。
trainset = torchvision.datasets.CIFAR10(root='./data', train=True,
download=True, transform=transform)
trainloader = torch.utils.data.DataLoader(trainset, batch_size=4,
shuffle=False)
下载 CIFAR 数据集torchvision.datasets
,将train
和download
参数设置为True
。接下来,将 transform 参数设置为定义的transform
变量。DataLoader
迭代对象已初始化,将trainset
作为参数传递给它。batch_size
设置为 4,shuffle
设置为 False。接下来,可以使用下面的代码片段可视化图像。
classes = ('plane', 'car', 'bird', 'cat',
'deer', 'dog', 'frog', 'horse', 'ship', 'truck')
def imshow(img):
img = img / 2 + 0.5
npimg = img.numpy()
plt.imshow(np.transpose(npimg, (1, 2, 0)))
plt.show()
dataiter = iter(trainloader)
images, labels = dataiter.next()
imshow(torchvision.utils.make_grid(images))
print(' '.join('%5s' % classes[labels[j]] for j in range(4)))
除了Resize()
、CenterCrop()
和RandomResizedCrop()
,还有各种其他Transform
可用的类。让我们看看最常用的。
3.1 RandomCrop
PyTorch 中的此类在随机位置裁剪给定的 PIL 图像。以下是RandomCrop
接受的参数:
torchvision.transforms.RandomCrop(size, padding=None, pad_if_needed=False, fill=0)
size
:此参数采用一个整数,表示随机裁剪的所需输出大小。例如,如果大小设置为 32,则输出将是大小为 32×32 的随机裁剪图像。padding
:这是一个整数参数,最初设置为None
。如果设置为整数,它会为图像添加一个额外的边框。例如,如果 padding 设置为4
,它会将左、上、右和下边框各填充 4 个单位。pad_if_needed
:这是一个可选参数,它采用布尔值。如果它被设置为True
,那么它会在图像周围填充一个较小的区域以避免最小的分辨率错误。默认情况下,此参数设置为False
。fill
:此常量值初始化所有填充像素的值。默认填充值为0
.
3.2 RandomHorizontalFlip
为了使模型在训练时具有鲁棒性,会随机翻转图像。该类RandomHorizontalFlip
用于实现这样的结果。它有一个默认参数 ,p
表示图像被翻转的概率(在 0 和 1 之间)。默认值为0.5
。
torchvision.transforms.RandomHorizontalFlip(p=0.5)
3.3 Normalize
这对图像进行归一化,将均值和标准差作为参数给出。这个类有四个参数,如下所示:
torchvision.transforms.functional.normalize(tensor, mean, std, inplace=False)
- 该
tensor
参数采用具有三个值的 Tensor:C、H 和 W。它们分别代表通道数、高度和宽度。基于给定的参数,对输入图像的所有像素值进行归一化。 mean
and参数接受关于每个通道的std
一系列均值和标准差。inplace
参数是一个布尔值。如果设置为True
,则所有操作都应就地计算。
3.4 ToTensor
此类将 PIL 图像或 NumPy n 维数组转换为张量。
torchvision.transforms.functional.to_tensor(img)
现在我们将了解加载自定义数据集背后的机制,而不是使用内置数据集。
4 在 PyTorch 中创建自定义数据集
将创建一个由数字和文本组成的简单自定义数据集。
- 该
__getitem__()
方法通过索引返回数据集中选定的样本。 - 该
__len__()
方法返回数据集的总大小。例如,如果您的数据集包含 1,00,000 个样本,则该len
方法应返回 1,00,000。
下面是一个抽象视图,解释了实现__getitem__()
和__len__()
方法:
class Dataset(object):
def __getitem__(self, index):
raise NotImplementedError
def __len__(self):
raise NotImplementedError
创建自定义数据集并不复杂,将创建一个包含数字及其平方值的新数据集。数据集被称为 SquareDataset
。返回范围内的值的平方[a,b]
。下面是相关代码:
import torch
import torchvision
from torch.utils.data import Dataset, DataLoader
from torchvision import datasets, transforms
class SquareDataset(Dataset):
def __init__(self, a=0, b=1):
super(Dataset, self).__init__()
assert a <= b
self.a = a
self.b = b
def __len__(self):
return self.b - self.a + 1
def __getitem__(self, index):
assert self.a <= index <= self.b
return index, index**2
data_train = SquareDataset(a=1,b=64)
data_train_loader = DataLoader(data_train, batch_size=64, shuffle=True)
print(len(data_train))
在上面的代码块中,创建了一个名为 SquareDataset 的 Python 类 ,它继承了 PyTorch 的 Dataset 类。接下来,调用了一个__init__()
构造函数,其中a
和b
分别被初始化为0
和1
。该类super
用于访问继承类的len
和方法。接下来使用语句来检查是否小于或等于。
然后,使用该类创建了一个数据集SquareDataset
,其中数据值介于 1 到 64 之间。将其加载到一个名为data_train
。最后,创建了一个data_train_loader
迭代器,batch_size
初始化为 64,并设置为shuffle
为True