Pytorch入门教程09-数据加载器

王 茂南 2020年10月8日07:02:34
评论
2 7834字阅读26分6秒
摘要之前我们完成线性回归模型的时候, 在训练的时候是一下子把所有的数据都输入模型的, 但是在深度学习的时候, 数据量一般都是极大的, 我们无法一次性将数据加载到内存中, 于是就有了数据加载器. 所以这一篇就会介绍Pytorch中的dataloader.

简介

之前我们完成线性回归模型的时候,在训练的时候是一下子把所有的数据都输入模型的,但是在深度学习的时候,数据量一般都是极大的,我们无法一次性将数据加载到内存中,于是就有了数据加载器。

关于 Pytorch 数据加载,之前曾经写过一篇 Pytorch训练时候导入大量数据(How to load large data)。关于重写重写 Dataset 类的方法,我们也会在这一篇中重新进行说明。

在具体的内容之前,先简单说一下在 Pytorch 中,数据加载器的一个总的关系:

  • 原始数据集;
  • 创建 dataset 类(这里包含重写 dataset 类);
  • 将 dataset 传入 dataloader,并告诉 batchsize 等系数;
  • 每一个 batch 进行正向传播,梯度下降;

 

数据的分批加载

由于深度学习中的数据量一般都是极大的, 我们无法一次性将所有数据全部加载到内存中. 因此, 在模型训练之前, 一般我们都会对训练数据进行分批. 将数据随机分成等量的几份, 每次迭代只将一份作为输入.

所以在训练的时候, 我们会按照下面的形式: 每一个epoch中, 有不同的batch (这个就是将一个数据集分成不同的batch)

  1. # training loop
  2. for epoch in range(num_epochs):
  3.     # 遍历所有的批次
  4.     for i in range(total_batches):
  5.         batch_x, batch_y = ..

那么如何对数据集进行分批呢 (也就是分成不同的batch),  PyTorch 为我们提供了torch.utils.data.DataLoader加载器, 该加载器可以自动的将传入的数据进行打乱和分批.

DataLoader的输入如下:

  • dataset: 需要打乱的数据集(这里的dataset也是需要Pytorch中的dataset, 下面看一下详细的例子).
  • batch_size: 每一批的数据条数.
  • shuffle: True或者False,表示是否将数据打乱后再分批.

关于显示dataloader中的内容, 我们可以使用next(iter(dataloader))来进行. 详细的可以查看下面"torchvision.datasets.ImageFolder"的介绍部分和最后一部分.

 

MNIST分批

首先看一下Pytorch中自带的数据集, 该数据集合存在于torchvision.datasets中, 可以直接利用torchvision.datasets.MNIST获得.

  1. import torch
  2. import torchvision
  3. # 将数据集合下载到指定目录下
  4. train_dataset = torchvision.datasets.MNIST(root='./data',
  5.                                            train=True,
  6.                                            transform=torchvision.transforms.ToTensor(),
  7.                                            download=True)
  8. train_dataset

接着我们将其放入dataloader中, 之后训练的时候可以分批加载.

  1. from torch.utils.data import DataLoader
  2. train_loader = DataLoader(dataset=train_dataset,
  3.                           batch_size=500,
  4.                           shuffle=True)
  5. num_epochs = 2  # 迭代次数
  6. total_step = len(train_dataset)/500
  7. for epoch in range(num_epochs):
  8.     for i, (inputs, labels) in enumerate(train_loader):
  9.         # 这里可以放具体的模型训练
  10.         # 每 10 个批次展示一次
  11.         if (i+1) % 10 == 0:
  12.             print('Epoch: {}/{},Step {}/{}| Inputs {} | Labels {}'.format(epoch+1, num_epochs, i+1, total_step, inputs.shape, labels.shape))
  13. """
  14. Epoch: 1/2,Step 10/120.0| Inputs torch.Size([500, 1, 28, 28]) | Labels torch.Size([500])
  15. Epoch: 1/2,Step 20/120.0| Inputs torch.Size([500, 1, 28, 28]) | Labels torch.Size([500])
  16. Epoch: 1/2,Step 110/120.0| Inputs torch.Size([500, 1, 28, 28]) | Labels torch.Size([500])
  17. Epoch: 1/2,Step 120/120.0| Inputs torch.Size([500, 1, 28, 28]) | Labels torch.Size([500])
  18. Epoch: 2/2,Step 10/120.0| Inputs torch.Size([500, 1, 28, 28]) | Labels torch.Size([500])
  19. """

在实际操作中, 我们只需要将循环内的代码换成模型训练的代码即可.

 

重写 Dataset 类

DataLoader 传入的参数中,我们需要注意的是dataset参数。该参数是一个 Dataset 类,即只有继承了 PyTorch 中的 Dataset 接口的类,才能够被传入DataLoader中。上面的 MNIST 是 Pytorch 中自带的,要是是一般的数据集,这个时候我们需要重写 dataset 类。关于这个部分,推荐查看下面的链接,里面有更加详细的介绍,Pytorch 基础

这一部分的代码可以参考Github仓库 (包括使用的示例数据也放在了Github仓库): 数据加载器(重写Dataset类的例子)如果想要继承了Dataset类, 我们就必须实现下面三个函数:

  • __init__(self): 用于初始化类中所需要的一些变量;
  • __len__(self): 返回数据集合的长度,即数据量大小;
  • __getitem__(self, index): 返回第 index 条数据;
下面就是一个简单的例子, 我们加载 csv 文件, 并重写 Dataset 类:
  1. class WineDataset(Dataset):
  2.     # 建立一个数据集合继承  Dataset 即可
  3.     def __init__(self):
  4.         # I初始化数据
  5.         # 以pandas的形式读入数据
  6.         winedata = pd.read_csv("./dataset/wine.csv", header=None) # 读取数据
  7.         self.n_samples = winedata.shape[0] # 数据集大小
  8.         # 将 pandas 类型的数据转换成 numpy 类型
  9.         # size [n_samples, n_features]
  10.         self.x_data = torch.from_numpy(winedata.values[:, 1:]) # 特征数据
  11.         self.y_data = torch.from_numpy(winedata.values[:, 0].reshape(-1,1))  # size [n_samples, 1]
  12.     # 返回 dataset[index]
  13.     def __getitem__(selfindex):
  14.         return self.x_data[index], self.y_data[index]
  15.     # 返回数据长度
  16.     def __len__(self):
  17.         return self.n_samples
我们看一下样例数据:
  1. dataset = WineDataset()
  2. dataset[0] # 查看样例数据
  3. """
  4. (tensor([1.4230e+01, 1.7100e+00, 2.4300e+00, 1.5600e+01, 1.2700e+02, 2.8000e+00,
  5.          3.0600e+00, 2.8000e-01, 2.2900e+00, 5.6400e+00, 1.0400e+00, 3.9200e+00,
  6.          1.0650e+03], dtype=torch.float64), tensor([1.], dtype=torch.float64))
  7. """
接下来使用 Dataloader 加载数据即可。
  1. # 放入DataLoader
  2. import math
  3. # 传入加载器
  4. train_loader = DataLoader(dataset=dataset,
  5.                           batch_size=4,
  6.                           shuffle=True)
  7. num_epochs = 2
  8. total_samples = len(dataset)
  9. n_iterations = math.ceil(total_samples/4) 
  10. print("该数据集合共有{}条数据,被分成了{}个批次".format(total_samples, n_iterations))
  11. for epoch in range(num_epochs):
  12.     for i, (inputs, labels) in enumerate(train_loader):
  13.         # 模型训练步骤
  14.         # 178 个样本, batch_size = 4, n_iters=178/4=44.5 -> 45 个批次
  15.         if (i+1) % 5 == 0:
  16.             print('Epoch: {}/{},Step {}/{}| Inputs {} | Labels {}'.format(epoch+1, num_epochs, i+1, n_iterations, inputs.shape, labels.shape))
  17. """
  18. 该数据集合共有178条数据,被分成了45个批次
  19. Epoch: 1/2,Step 5/45| Inputs torch.Size([4, 13]) | Labels torch.Size([4, 1])
  20. Epoch: 2/2,Step 45/45| Inputs torch.Size([2, 13]) | Labels torch.Size([2, 1])
  21. """

 

Data.TensorDataset介绍

对于一些特征类的数据, 可以一下全部读取内容, 就不需要重写Dataset类, 可以直接使用Data.TensorDataset来进行数据的导入. 例如上面的csv文件, 我们就不需要麻烦的重写Dataset类, 只需要使用TensorDataset即可.

这一部分的内容, 可以参考github仓库链接数据加载器(TeensorDataset的使用)

首先我们先导入数据, 并将其转换为tensor的格式.

  1. winedata = pd.read_csv("./dataset/wine.csv", header=None)
  2. x_data = torch.from_numpy(winedata.values[:, 1:]) # 特征数据
  3. y_data = torch.from_numpy(winedata.values[:, 0].reshape(-1,1))  # size [n_samples, 1]
  4. print(x_data.shape, y_data.shape)
  5. """
  6. torch.Size([178, 13]) torch.Size([178, 1])
  7. """

接着我们将其转化为dataset的格式, 这样就可以被传入dataloader中.

  1. dataset = torch.utils.data.TensorDataset(x_data, y_data) # 合并训练数据和目标数据
  2. dataset[0]
  3. """
  4. (tensor([1.4230e+01, 1.7100e+00, 2.4300e+00, 1.5600e+01, 1.2700e+02, 2.8000e+00,
  5.          3.0600e+00, 2.8000e-01, 2.2900e+00, 5.6400e+00, 1.0400e+00, 3.9200e+00,
  6.          1.0650e+03], dtype=torch.float64), tensor([1.], dtype=torch.float64))
  7. """

最后还是将其传入dataloader, 并做简单的测试.

  1. MINIBATCH_SIZE = 30
  2. train_loader = torch.utils.data.DataLoader(
  3.     dataset=dataset,
  4.     batch_size=MINIBATCH_SIZE,
  5.     shuffle=True,
  6.     num_workers=1           # set multi-work num read data
  7. )
  8. num_epochs = 2
  9. total_samples = len(dataset)
  10. n_iterations = math.ceil(total_samples/4) 
  11. print("该数据集合共有{}条数据,被分成了{}个批次".format(total_samples, n_iterations))
  12. for epoch in range(num_epochs):
  13.     for i, (inputs, labels) in enumerate(train_loader):
  14.         # 模型训练步骤
  15.         # 178 个样本, batch_size = 4, n_iters=178/4=44.5 -> 45 个批次
  16.         if (i+1) % 5 == 0:
  17.             print('Epoch: {}/{},Step {}/{}| Inputs {} | Labels {}'.format(epoch+1, num_epochs, i+1, n_iterations, inputs.shape, labels.shape))
  18. """
  19. 该数据集合共有178条数据,被分成了45个批次
  20. Epoch: 1/2,Step 5/45| Inputs torch.Size([30, 13]) | Labels torch.Size([30, 1])
  21. Epoch: 2/2,Step 5/45| Inputs torch.Size([30, 13]) | Labels torch.Size([30, 1])
  22. """

 

torchvision.datasets.ImageFolder介绍

对于图片的数据, 我们其实也是不用单独设计dataset的, 可以直接使用现成的ImageFolder. (关于train和test就可以使用两次ImageFolder)

官方文档ImageFolder介绍

下面看一个简单的例子.

  1. trans = transforms.Compose([
  2.     transforms.Resize(64),
  3.     transforms.ToTensor(),
  4.     transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))
  5. ])
  6. # 注意这里文件夹的路径
  7. dataset = datasets.ImageFolder('./data', transform=trans) # 数据路径
  8. dataloader = torch.utils.data.DataLoader(dataset,
  9.                                      batch_size=16, # 批量大小
  10.                                      shuffle=True# 乱序
  11.                                      num_workers=2 # 多进程
  12.                                      )

同时我们需要注意的是, 这个类的root路径的书写. 他的文件格式是如下所示的.

  1. root/dog/xxx.png
  2. root/dog/xxy.png
  3. root/dog/xxz.png
  4. root/cat/123.png
  5. root/cat/nsdf3.png
  6. root/cat/asd932_.png

也就是他只需要写到root目录, 然后把每一类图片分别放在对应的文件夹内. 我们不能直接将图片放在root目录下, 不然就会出现如下的报错: RuntimeError: Found 0 images in subfolders of: ./data

上面报错解决链接RuntimeError: Found 0 images in subfolders of: ./data

 

Dataloader结果展示

下面看一下如何查看定义好的数据集中的数据. 我们使用图片作为例子. 其他的就更加简单. 首先需要将tensor转换为numpy, 同时需要反归一化, 并且转换颜色通道.

  1. import matplotlib.pyplot as plt
  2. %matplotlib inline
  3. def imshow(inp, title):
  4.     """Imshow for Tensor."""
  5.     inp = inp.numpy().transpose((1, 2, 0))
  6.     inp = std * inp + mean
  7.     inp = np.clip(inp, 0, 1)
  8.     plt.imshow(inp)
  9.     plt.title(title)
  10.     plt.show()

之后从dataloader中取一个batch的数据出来, 来进行显示.

  1. # 获得训练数据中的一个批次的数据
  2. inputs, classes = next(iter(dataloaders['train'])) # 这个大小和batch size是有关的
  3. # 将图片拼成一个网格
  4. out = torchvision.utils.make_grid(inputs)
  5. # 展示图像
  6. imshow(out, title=[class_names[x] for x in classes])

 

  • 微信公众号
  • 关注微信公众号
  • weinxin
  • QQ群
  • 我们的QQ群号
  • weinxin
王 茂南
  • 本文由 发表于 2020年10月8日07:02:34
  • 转载请务必保留本文链接:https://mathpretty.com/12527.html
匿名

发表评论

匿名网友 填写信息

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: