数据加载Dataset和DataLoader的使用

一、模型中使用数据加载器的目的

在前面的线性回归模型中,我们使用的数据很少,所以直接把全部数据放到模型中去使用。但是在深度学习中,数据量通常是都非常多,非常大的,如此大量的数据,不可能一次性的在模型中进行向前的计算和反向传播,经常我们会对整个数据进行随机的打乱顺序,把数据处理成个个的batch,同时还会对数据进行预处理

二、数据集类

2.1 Dataset基类介绍:

在torch中提供了数据集的基类torch.utils.data.Dataset, 继承这个基类,我们能够非常快速的实现对数据的加载。

torch.utils.data.Dataset的源码如下:

from torch.utils.data import Dataset

class Dataset(object):

	def __getitem__(self, index):
		raise NotImplementedError
	def __len__(se1f):
		raise NotImp lementedError
	def __add__(se1f, other):
		return ConcatDataset([self, other])

可知:我们需要在自定义的数据集类中继承Dataset类,同时还需要实现两个方法:

  1. __1en__方法, 能够实现通过全局的len()方法获取其中的元素个数;
  2. __getitem__ 方法,能够通过传入索引的方式获取数据,例如通过dataset[i]获取其中的第 i i i条数据。

2.2 数据加载案例:

下面通过一个例子来看看如何使用Dataset来加载数据:

数据来源: 我的数据是甘肃省的气温数据,是文本数据,你可以随便找数据,都可以练习。

将数据利用pandas读取进来,然后实现自定义的数据集类,其实就是实现上面说的__1en__方法和__getitem__ 方法,下面是代码:

from torch.utils.data import Dataset, DataLoader
import torch
import pandas as pd

data_path = r"./data/wendu_8_4_9_2.csv"

# 完成数据集类
class MyDataset(Dataset):
    def __init__(self):
        self.data = pd.read_csv(data_path).values # DataFrame类型,通过values转换成numpy类型

    def __getitem__(self, index):
        """ 必须实现,作用是:获取索引对应位置的一条数据 :param index: :return: """
        return MyDataset.to_tensor(self.data[index])

    def __len__(self):
        """ 必须实现,作用是得到数据集的大小 :return: """
        return len(self.data)

    @staticmethod
    def to_tensor(data):
        """ 将ndarray转换成tensor :param data: :return: """
        return torch.from_numpy(data)

if __name__ == "__main__":
    data = MyDataset() # 实例化对象
    print(data[0]) # 取第1条数据
    print(len(data)) # 获取长度

 

三、迭代数据集

使用上述的方法能够进行数据的读取,但是其中还有很多内容没有实现:

  • 批处理数据(Batching the data)
  • 打乱数据(Shuffling the data)
  • 使用多线程multiprocessing并行加载数据

在PyTorch中torch.utils.data.DataLoader提供了上述的所有方法

DataLoader使用示例:

from torch.utils.data import DataLoader
import torch
import pandas as pd


data = MyDataset() # 实例化对象,前面自定义的数据集类

# DataLoader就这一行,其实就是直接调用即可
data_loader = DataLoader(dataset=data, batch_size=2, shuffle=True, num_workers=2)


if __name__ == "__main__":
    for i in data_loader: # 可以迭代
        print(i)
        print('*'*50)

其中参数的含义:

  • 1、dataset:提前定义的dataset的实例;
  • 2、batch_size:传入数据的batch大小,常常是32、64、128、256’
  • 3、shuffle:bool类型,表示是否在每次获取数据的时候提前打乱数据;
  • 4、num_workers:加载数据的线程数。
  • 5、drop_last:bool类型,为真,表示最后的数据不足一个batch,就删掉

数据迭代器返回的结果如下:

数据加载Dataset和DataLoader的使用_第1张图片

这里有一点需要注意,如果我们同时获取我们自定义的数据集类MyDataset对象data的长度和 DataLoader对象data_loader的长度,我们会发现:
data_loader的长度是data的长度除以batch_size。如下面,我将batch_size设置为2,则如下:

print(len(data))
print(len(data_loader))

# 输出:
53280
26640

同时,需要注意,要是除不完,则向上取整,也就是说:如果我们的batch_zize=16,但是
最后的数据只有1条,那这一条就算作一个batch,这个就是len(data_loader)的输出。

四、概念

  1. torch.utils.data.dataset这样的抽象类可以用来创建数据集。学过面向对象的应该清楚,抽象类不能实例化,因此我们需要构造这个抽象类的子类来创建数据集,并且我们还可以定义自己的继承和重写方法。

  2. 这其中最重要的就是**lengetitem这两个函数,前者给出数据集的大小**,后者是用于查找数据和标签

  3. torch.utils.data.DataLoader是一个迭代器,方便我们去多线程地读取数据,并且可以实现batch以及shuffle的读取等。

五、Dataset的创建和使用

  1. 首先我们需要引入dataset这个抽象类,当然我们还需要引入Numpy:
import torch.utils.data.dataset as Dataset
import numpy as np
  1. 我们创建Dataset的一个子类:
    (1)初始化,定义数据内容和标签:
#初始化,定义数据内容和标签
def __init__(self, Data, Label):
    self.Data = Data
    self.Label = Label

(2)返回数据集大小:

#返回数据集大小
def __len__(self):
    return len(self.Data)

(3)得到数据内容和标签:

#得到数据内容和标签
def __getitem__(self, index):
    data = torch.Tensor(self.Data[index])
    label = torch.Tensor(self.Label[index])
    return data, label

(4)最终这个子类定义为:

import torch
import torch.utils.data.dataset as Dataset
import numpy as np

#创建子类
class subDataset(Dataset.Dataset):
    #初始化,定义数据内容和标签
    def __init__(self, Data, Label):
        self.Data = Data
        self.Label = Label
    #返回数据集大小
    def __len__(self):
        return len(self.Data)
    #得到数据内容和标签
    def __getitem__(self, index):
        data = torch.Tensor(self.Data[index])
        label = torch.Tensor(self.Label[index])
        return data, label

值得注意的地方是:

class subDataset(Dataset.Dataset):

如果只写了Dataset而不是Dataset.Dataset,则会报错:module.init() takes at most 2 arguments (3 given)

因为Dataset是module模块,不是class类,所以需要调用module里的class才行,因此是Dataset.Dataset!

  1. 在类外对Data和Label赋值:

Data = np.asarray([[1, 2], [3, 4],[5, 6], [7, 8]])
Label = np.asarray([[0], [1], [0], [2]])
4. 声明主函数,主函数创建一个子类的对象,传入Data和Label参数:

if __name__ == '__main__':
    dataset = subDataset(Data, Label)
  1. 输出数据集大小和数据:
    print(dataset)
    print('dataset大小为:', dataset.__len__())
    print(dataset.__getitem__(0))
    print(dataset[0])

代码变为;

import torch
import torch.utils.data.dataset as Dataset
import numpy as np
 
Data = np.asarray([[1, 2], [3, 4],[5, 6], [7, 8]])
Label = np.asarray([[0], [1], [0], [2]])
#创建子类
class subDataset(Dataset.Dataset):
    #初始化,定义数据内容和标签
    def __init__(self, Data, Label):
        self.Data = Data
        self.Label = Label
    #返回数据集大小
    def __len__(self):
        return len(self.Data)
    #得到数据内容和标签
    def __getitem__(self, index):
        data = torch.Tensor(self.Data[index])
        label = torch.IntTensor(self.Label[index])
        return data, label
 
if __name__ == '__main__':
    dataset = subDataset(Data, Label)
    print(dataset)
    print('dataset大小为:', dataset.__len__())
    print(dataset.__getitem__(0))
    print(dataset[0])

结果为:

数据加载Dataset和DataLoader的使用_第2张图片

六、DataLoader的创建和使用

  1. 引入DataLoader:
import torch.utils.data.dataloader as DataLoader
  1. 创建DataLoader,batch_size设置为2,shuffle=False不打乱数据顺序,num_workers= 4使用4个子进程:
    #创建DataLoader迭代器
    dataloader = DataLoader.DataLoader(dataset,batch_size= 2, shuffle = False, num_workers= 4)
  1. 使用enumerate访问可遍历的数组对象:
    for step, (data, label) in enumerate(dataloader):
        print('step is :', step)
        # data, label = item
        print('data is {}, label is {}'.format(data, label))
    for i, item in enumerate(dataloader):
        print('i:', i)
        data, label = item
        print('data:', data)
        print('label:', label)
  1. 最终代码如下:
import torch
import torch.utils.data.dataset as Dataset
import torch.utils.data.dataloader as DataLoader
import numpy as np
 
Data = np.asarray([[1, 2], [3, 4],[5, 6], [7, 8]])
Label = np.asarray([[0], [1], [0], [2]])
#创建子类
class subDataset(Dataset.Dataset):
    #初始化,定义数据内容和标签
    def __init__(self, Data, Label):
        self.Data = Data
        self.Label = Label
    #返回数据集大小
    def __len__(self):
        return len(self.Data)
    #得到数据内容和标签
    def __getitem__(self, index):
        data = torch.Tensor(self.Data[index])
        label = torch.IntTensor(self.Label[index])
        return data, label
 
if __name__ == '__main__':
    dataset = subDataset(Data, Label)
    print(dataset)
    print('dataset大小为:', dataset.__len__())
    print(dataset.__getitem__(0))
    print(dataset[0])
 
    #创建DataLoader迭代器
    dataloader = DataLoader.DataLoader(dataset,batch_size= 2, shuffle = False, num_workers= 4)
    for i, item in enumerate(dataloader):
        print('i:', i)
        data, label = item
        print('data:', data)
        print('label:', label)

结果为:

数据加载Dataset和DataLoader的使用_第3张图片


可以看到两个对象,因为对象数*batch_size就是数据集的大小__len__

七、将Dataset数据和标签放在GPU上(代码执行顺序出错则会有bug)

  1. 改写__getitem__函数:
        if torch.cuda.is_available():
            data = data.cuda()
            label = label.cuda()

代码变为:

    #得到数据内容和标签
    def __getitem__(self, index):
        data = torch.Tensor(self.Data[index])
        label = torch.IntTensor(self.Label[index])
        if torch.cuda.is_available():
            data = data.cuda()
            label = label.cuda()
        return data, label
  1. 报错啦:
THCudaCheck FATIHCudaCheck FAIL file=Lc:\n efwile=-builder_3\win-whce:el\\pnyteorwch-\tborucihl\cdsrec\rge_3n\weirinc\StorageSharing.cpp-w helienl\epy=t2or3ch1\ toercrhr\cosrrc=\g71e ne:r ioc\pSteorartagieSohanr niotng .cspupppo line=231 error=rt7e1d
: operProcess Process-2:
ation not supportedTraceback (most recent call last):
 
  File "D:\Anaconda3\lib\multiprocessing\process.py", line 258, in _bootstrap
    self.run()
  File "D:\Anaconda3\lib\multiprocessing\process.py", line 93, in run
    self._target(*self._args, **self._kwargs)
  File "D:\Anaconda3\lib\site-packages\torch\utils\data\dataloader.py", line 110, in _worker_loop
    data_queue.put((idx, samples))
Process Process-1:
  File "D:\Anaconda3\lib\multiprocessing\queues.py", line 341, in put
    obj = _ForkingPickler.dumps(obj)
  File "D:\Anaconda3\lib\multiprocessing\reduction.py", line 51, in dumps
    cls(buf, protocol).dump(obj)
  File "D:\Anaconda3\lib\site-packages\torch\multiprocessing\reductions.py", line 109, in reduce_tensor
    (device, handle, storage_size, storage_offset) = storage._share_cuda_()
RuntimeError: cuda runtime error (71) : operation not supported at c:\new-builder_3\win-wheel\pytorch\torch\csrc\generic\StorageSharing.cpp:231
Traceback (most recent call last):
  File "D:\Anaconda3\lib\multiprocessing\process.py", line 258, in _bootstrap
    self.run()
  File "D:\Anaconda3\lib\multiprocessing\process.py", line 93, in run
    self._target(*self._args, **self._kwargs)
  File "D:\Anaconda3\lib\site-packages\torch\utils\data\dataloader.py", line 110, in _worker_loop
    data_queue.put((idx, samples))
  File "D:\Anaconda3\lib\multiprocessing\queues.py", line 341, in put
    obj = _ForkingPickler.dumps(obj)
  File "D:\Anaconda3\lib\multiprocessing\reduction.py", line 51, in dumps
    cls(buf, protocol).dump(obj)
  File "D:\Anaconda3\lib\site-packages\torch\multiprocessing\reductions.py", line 109, in reduce_tensor
    (device, handle, storage_size, storage_offset) = storage._share_cuda_()
RuntimeError: cuda runtime error (71) : operation not supported at c:\new-builder_3\win-wheel\pytorch\torch\csrc\generic\StorageSharing.cpp:231
  1. 那怎么办呢?有两种方法:

(1)只需要将num_workers改成0即可:

dataloader = DataLoader.DataLoader(dataset,batch_size= 2, shuffle = False, num_workers= 0)

代码变为:

import torch
import torch.utils.data.dataset as Dataset
import torch.utils.data.dataloader as DataLoader
import numpy as np
 
Data = np.asarray([[1, 2], [3, 4],[5, 6], [7, 8]])
Label = np.asarray([[0], [1], [0], [2]])
#创建子类
class subDataset(Dataset.Dataset):
    #初始化,定义数据内容和标签
    def __init__(self, Data, Label):
        self.Data = Data
        self.Label = Label
    #返回数据集大小
    def __len__(self):
        return len(self.Data)
    #得到数据内容和标签
    def __getitem__(self, index):
        data = torch.Tensor(self.Data[index])
        label = torch.IntTensor(self.Label[index])
        if torch.cuda.is_available():
            data = data.cuda()
            label = label.cuda()
        return data, label
 
if __name__ == '__main__':
    dataset = subDataset(Data, Label)
    print(dataset)
    print('dataset大小为:', dataset.__len__())
    print(dataset.__getitem__(0))
    print(dataset[0][0])
 
    #创建DataLoader迭代器
    dataloader = DataLoader.DataLoader(dataset,batch_size= 2, shuffle = False, num_workers= 0)
    for i, item in enumerate(dataloader):
        print('i:', i)
        data, label = item
        print('data:', data)
        print('label:', label)

结果为:

数据加载Dataset和DataLoader的使用_第4张图片


可以看到多了一个device=‘cuda:0’

(2)把Tensor放到GPU上的操作放在DataLoader之后,即删除__getitem__函数里的下面内容

if torch.cuda.is_available():
   data = data.cuda()
   label = label.cuda()

并在主函数的for循环里添加删除的语句,代码变为

import torch
import torch.utils.data.dataset as Dataset
import torch.utils.data.dataloader as DataLoader
import numpy as np
 
Data = np.asarray([[1, 2], [3, 4],[5, 6], [7, 8]])
Label = np.asarray([[0], [1], [0], [2]])
#创建子类
class subDataset(Dataset.Dataset):
    #初始化,定义数据内容和标签
    def __init__(self, Data, Label):
        self.Data = Data
        self.Label = Label
    #返回数据集大小
    def __len__(self):
        return len(self.Data)
    #得到数据内容和标签
    def __getitem__(self, index):
        data = torch.Tensor(self.Data[index])
        label = torch.IntTensor(self.Label[index])
        return data, label
 
if __name__ == '__main__':
    dataset = subDataset(Data, Label)
    print(dataset)
    print('dataset大小为:', dataset.__len__())
    print(dataset.__getitem__(0))
    print(dataset[0][0])
 
    #创建DataLoader迭代器
    dataloader = DataLoader.DataLoader(dataset,batch_size= 2, shuffle = False, num_workers= 8)
    for i, item in enumerate(dataloader):
        print('i:', i)
        data, label = item
        if torch.cuda.is_available():
            data = data.cuda()
            label = label.cuda()
        print('data:', data)
        print('label:', label)

结果为:

数据加载Dataset和DataLoader的使用_第5张图片

 

八、Dataset和DataLoader总结

  1. Dataset是一个抽象类,需要派生一个子类构造数据集,需要改写的方法有__init__,__getitem__等。

  2. DataLoader是一个迭代器,方便我们访问Dataset里的对象,值得注意的num_workers的参数设置:如果放在cpu上跑,可以不管,但是放在GPU上则需要设置为0;或者在DataLoader操作之后将Tensor放在GPU上。

  3. 数据和标签是tuple元组的形式,使用Dataloader然后使用enumerate函数访问它们。

         

你可能感兴趣的:(Deeplearn,pytorch,python,深度学习)