一文弄懂Pytorch的DataLoader, DataSet, Sampler之间的关系
作者:marsggbo 发布时间:2022-06-27 14:21:53
以下内容都是针对Pytorch 1.0-1.1介绍。
很多文章都是从Dataset等对象自下往上进行介绍,但是对于初学者而言,其实这并不好理解,因为有的时候会不自觉地陷入到一些细枝末节中去,而不能把握重点,所以本文将会自上而下地对Pytorch数据读取方法进行介绍。
自上而下理解三者关系
首先我们看一下DataLoader.next的源代码长什么样,为方便理解我只选取了num_works为0的情况(num_works简单理解就是能够并行化地读取数据)。
class DataLoader(object):
...
def __next__(self):
if self.num_workers == 0:
indices = next(self.sample_iter) # Sampler
batch = self.collate_fn([self.dataset[i] for i in indices]) # Dataset
if self.pin_memory:
batch = _utils.pin_memory.pin_memory_batch(batch)
return batch
在阅读上面代码前,我们可以假设我们的数据是一组图像,每一张图像对应一个index,那么如果我们要读取数据就只需要对应的index即可,即上面代码中的indices
,而选取index的方式有多种,有按顺序的,也有乱序的,所以这个工作需要Sampler
完成,现在你不需要具体的细节,后面会介绍,你只需要知道DataLoader和Sampler在这里产生关系。
那么Dataset和DataLoader在什么时候产生关系呢?没错就是下面一行。我们已经拿到了indices,那么下一步我们只需要根据index对数据进行读取即可了。
再下面的if
语句的作用简单理解就是,如果pin_memory=True
,那么Pytorch会采取一系列操作把数据拷贝到GPU,总之就是为了加速。
综上可以知道DataLoader,Sampler和Dataset三者关系如下:
在阅读后文的过程中,你始终需要将上面的关系记在心里,这样能帮助你更好地理解。
Sampler
参数传递
要更加细致地理解Sampler原理,我们需要先阅读一下DataLoader 的源代码,如下:
class DataLoader(object):
def __init__(self, dataset, batch_size=1, shuffle=False, sampler=None,
batch_sampler=None, num_workers=0, collate_fn=default_collate,
pin_memory=False, drop_last=False, timeout=0,
worker_init_fn=None)
可以看到初始化参数里有两种sampler:sampler
和batch_sampler
,都默认为None
。前者的作用是生成一系列的index,而batch_sampler则是将sampler生成的indices打包分组,得到一个又一个batch的index。例如下面示例中,BatchSampler
将SequentialSampler
生成的index按照指定的batch size分组。
>>>in : list(BatchSampler(SequentialSampler(range(10)), batch_size=3, drop_last=False))
>>>out: [[0, 1, 2], [3, 4, 5], [6, 7, 8], [9]]
Pytorch中已经实现的Sampler
有如下几种:
SequentialSampler
RandomSampler
WeightedSampler
SubsetRandomSampler
需要注意的是DataLoader的部分初始化参数之间存在互斥关系,这个你可以通过阅读源码更深地理解,这里只做总结:
如果你自定义了batch_sampler,那么这些参数都必须使用默认值:batch_size, shuffle,sampler,drop_last.
如果你自定义了sampler,那么shuffle需要设置为False
如果sampler和batch_sampler都为None,那么batch_sampler使用Pytorch已经实现好的BatchSampler,而sampler分两种情况:
若shuffle=True,则sampler=RandomSampler(dataset)
若shuffle=False,则sampler=SequentialSampler(dataset)
如何自定义Sampler和BatchSampler?
仔细查看源代码其实可以发现,所有采样器其实都继承自同一个父类,即Sampler
,其代码定义如下:
class Sampler(object):
r"""Base class for all Samplers.
Every Sampler subclass has to provide an :meth:`__iter__` method, providing a
way to iterate over indices of dataset elements, and a :meth:`__len__` method
that returns the length of the returned iterators.
.. note:: The :meth:`__len__` method isn't strictly required by
:class:`~torch.utils.data.DataLoader`, but is expected in any
calculation involving the length of a :class:`~torch.utils.data.DataLoader`.
"""
def __init__(self, data_source):
pass
def __iter__(self):
raise NotImplementedError
def __len__(self):
return len(self.data_source)
所以你要做的就是定义好__iter__(self)
函数,不过要注意的是该函数的返回值需要是可迭代的。例如SequentialSampler
返回的是iter(range(len(self.data_source)))
。
另外BatchSampler
与其他Sampler的主要区别是它需要将Sampler作为参数进行打包,进而每次迭代返回以batch size为大小的index列表。也就是说在后面的读取数据过程中使用的都是batch sampler。
Dataset
Dataset定义方式如下:
class Dataset(object):
def __init__(self):
...
def __getitem__(self, index):
return ...
def __len__(self):
return ...
上面三个方法是最基本的,其中__getitem__
是最主要的方法,它规定了如何读取数据。但是它又不同于一般的方法,因为它是python built-in方法,其主要作用是能让该类可以像list一样通过索引值对数据进行访问。假如你定义好了一个dataset,那么你可以直接通过dataset[0]
来访问第一个数据。在此之前我一直没弄清楚__getitem__
是什么作用,所以一直不知道该怎么进入到这个函数进行调试。现在如果你想对__getitem__
方法进行调试,你可以写一个for循环遍历dataset来进行调试了,而不用构建dataloader等一大堆东西了,建议学会使用ipdb
这个库,非常实用!!!以后有时间再写一篇ipdb的使用教程。另外,其实我们通过最前面的Dataloader的__next__
函数可以看到DataLoader对数据的读取其实就是用了for循环来遍历数据,不用往上翻了,我直接复制了一遍,如下:
class DataLoader(object):
...
def __next__(self):
if self.num_workers == 0:
indices = next(self.sample_iter)
batch = self.collate_fn([self.dataset[i] for i in indices]) # this line
if self.pin_memory:
batch = _utils.pin_memory.pin_memory_batch(batch)
return batch
我们仔细看可以发现,前面还有一个self.collate_fn
方法,这个是干嘛用的呢?在介绍前我们需要知道每个参数的意义:
indices
: 表示每一个iteration,sampler返回的indices,即一个batch size大小的索引列表self.dataset[i]
: 前面已经介绍了,这里就是对第i个数据进行读取操作,一般来说self.dataset[i]=(img, label)
看到这不难猜出collate_fn
的作用就是将一个batch的数据进行合并操作。默认的collate_fn
是将img和label分别合并成imgs和labels,所以如果你的__getitem__
方法只是返回 img, label
,那么你可以使用默认的collate_fn
方法,但是如果你每次读取的数据有img, box, label
等等,那么你就需要自定义collate_fn
来将对应的数据合并成一个batch数据,这样方便后续的训练步骤。
来源:https://www.cnblogs.com/marsggbo/p/11308889.html
猜你喜欢
- Null模式我想每个人都有一种经历,为了获取某属性,但是有时候属性是None,那么需要你做异常处理, 而假如你想节省这样的条件过滤的代码,可
- 权限全局配置:REST_FRAMEWORK = { 'DEFAULT_PERMISSION_CLASSES':
- 本篇内容通过GitHub仓库创建过程以及创建连接后的上传与下载,详细介绍了Git版本控制器使用情况,来看下。首先介绍一下什么是Git:git
- 本文实例为大家分享了python实现图片转字符画的具体代码,供大家参考,具体内容如下源码(注释很详细):# -*- coding=utf-8
- 我就废话不多说了,大家还是直接看代码吧~</pre><pre code_snippet_id="1947416&
- 带农历的JavaScript日期时间,增加了农历使这款代码更具实用性,很不错,不但有农历,而且还可以显示民国计年,可以判断显示闰月、闰年等,
- golang并没有像C语言一样提供三元表达式。三元表达式的好处是可以用一行代码解决原本需要多行代码才能完成的功能,让冗长的代码瞬间变得简洁。
- 最近,在搞这个东西,网上也找不到asp相关的,如果有人很早弄出来了,也不要笑话偶;费了好久,总算搞定;原理:用stream对象预读文件的头两
- asp之家注:为什么要防止访客频繁刷新页面呢?也许你会说他想刷新就让他刷新吧,没什么关系,而且还增加了网页的PV,呵呵。但是有的页面我们可能
- sort() 函数用于对数组单元从低到高进行排序。rsort() 函数用于对数组单元从高到低进行排序。asort() 函数用于对数组单元从低
- 我们的每期话题,团队在内部都会通过邮件进行一番讨论,随着讨论的激烈,往往能碰撞出很多有意义的观点,因此,将讨论内容分享出来,有兴趣的朋友可以
- pyecharts 是一个用于生成 Echarts 图表的类库。Echarts 是百度开源的一个数据可视化 JS 库。这篇文章重点给大家介绍
- 大多数卷积神经网络都是直接通过写一个Model类来定义的,这样写的代码其实是比较好懂的,特别是在魔改网络的时候也很方便。然后也有一些会通过c
- 之前有教大家制作日历,很多小伙伴都兴奋的告诉小编自己成功了。那么我们在日历中怎么去找到我们想要的那个时间点呢?其实有点类似于查询的功能,但是
- 字符串是Python中最常用的数据类型1、创建字符串1.1 使用 ’ ’ 或 " &quo
- 业务需求 识别验证码图片中的数字信息,用pyt
- 有几个原因促使我们使用自定义的select控件来代替原生的select控件:在ie6下select是一个窗口级的元素,绝对定位的层会被sel
- 本文实例讲述了Python实现批量转换文件编码的方法。分享给大家供大家参考。具体如下:这里将某个目录下的所有文件从一种编码转换为另一种编码,
- 1 任务需求  首先,我们来明确一下本文所需实现的需求。  现有一个
- 前言: 在刚解决jupyter notebook打不开网页的问题后,我迫不及待地写了P