YOLO dataloader

yolo dataloader

  • 1 迭代器的简要介绍
    • 1.1 iter和next的初步理解
    • 1.2 torch.utils.data.DataLoader中的iter和next
      • 1.2.1 第一种方式
      • 1.2.2 第二种方式,这种方式就类似于`torch.utils.data.DataLoader`。
      • 1.2.3 第三种方式,这种方式就是`torch.utils.data.DataLoader`的实现方式
      • 1.2.4 第四种方式,这种方式就是YOLOv9中`InfiniteDataLoader`的实现方式
        • 1.2.4.1 while True
  • 2 YOLOv9
    • 2.1 DataLoader
      • 2.1.1 DataLoader(介绍使用`torch.utils.data.DataLoader`读取数据)
        • 2.1.1.1 for循环:生成迭代器
          • DataLoader相对于InfiniteDataLoader最大的区别
        • 2.1.1.2 for循环:生成迭代器后调用迭代器的__next__()
        • 2.1.1.3 BatchSampler
        • 2.1.1.4 sample
    • 2.2 InfiniteDataLoader
    • 2.3 YOLOv9 close_mosaic
      • 2.3.1 YOLOv9 实现close_mosaic流程
      • 2.3.2 为什么close_mosaic=15的时候,读取数据只能采取`DataLoader`?
        • `DataLoader可以`
        • `InfiniteDataLoader不可以`
    • 2.4 YOLOv8(__version__ = "8.2.31") 实现close_mosaic流程
  • 3 参考链接

不理解 看代码 看代码 看代码 重要的事情说三遍 代码最好进行调试理解 代码是最好的老师

1 迭代器的简要介绍

1.1 iter和next的初步理解

__iter__(): class 中定义的魔术方法
__next__(): class 中定义的魔术方法
iter(): 将一个定义了__iter__()的类转化为一个迭代器
next(): 读取迭代器中的数据

iter()和next()的原理图可以参考这个链接:Pytorch(三):Dataset和Dataloader的理解

注意:当一个类中同时定义了__iter__()和__next__(),这个类本身就是一个迭代器,或者如果只定义了__iter__(),需要使用iter()将类对象转化为迭代器

next()有一个点需要注意(见下面代码):

  1. 类Exmaple同时定义了__iter__()__next__() => exmaple是一个迭代器,next(example)调用的是example中的__next__()
  2. 类Exmaple只定义了__iter__() => 需要用iter(example)将exmaple转化为一个迭代器,next(example)调用的是example中的__iter__()
class Example:def __init__(self, data):self.data = datadef __iter__(self):for index in range(len(self.data)):print("打印iter")yield self.data[index]def __next__(self):print("打印next")if __name__ == "__main__":example = Example(range(100))"""不注释__next__()  Exmaple同时定义了__iter__()和__next__() => exmaple是一个迭代器当Example同时定义了__iter__()和__next__()的时候 => next(example)调用的是example中的__next__()"""next(example)  """注释__next__()  Exmaple只定义了__iter__() => 需要用iter(example)将exmaple转化为一个迭代器当Example 只定义了 __iter__()的时候 => next(example)调用的是example中的__iter__()"""# next(iter(example))  

for循环:当执行for i in obj:,Python解释器会在第一次迭代时自动调用iter(obj)生成一个迭代器Iter,然后在之后的迭代(包扩第一次迭代)会使用next()调用迭代器Iter的__iter__()方法,for语句会自动处理最后抛出的StopIteration异常,如list、tuple、dict等可迭代对象。但是当obj是一个类对象,且类中定义了__iter__()时,Python解释器会在循环开始时自动调用Iter = obj.__iter__()返回一个迭代器Iter,然后在之后的迭代(包括第一次迭代)会调用迭代器Iter的__next__()方法,for语句会自动处理最后抛出的StopIteration异常

如果obj是类似于list、tuple、dict的一个可迭代对象时,python解释器会自动为我们做以下几步操作(通义千问,重点不是for循环中这个用法,不用太过重视):

  • 调用 iter() 函数:首先,Python调用内置的iter()函数尝试从可迭代对象obj中获取一个迭代器。这相当于执行了iterator = iter(obj)。
  • 循环与 next() 方法:在for循环的每次迭代中,Python会使用next()调用迭代器的__iter__()方法来获取下一个元素。
  • 处理 StopIteration 异常:当迭代器没有更多的元素可以提供时,next()方法会抛出一个StopIteration异常。Python的for循环会捕获这个异常,并且知道这是迭代结束的信号,于是它会干净利落地结束循环,而不会将这个异常暴露给用户代码。
  • 具体的实现代码过程类似于
    for i in obj:# 循环体
    
    实际上,Python在幕后做的事情类似于下面的手动迭代过程:
    iterator = iter(obj)
    while True:try:i = next(iterator)except StopIteration:break  # 遇到StopIteration时退出循环# 循环体
    

1.2 torch.utils.data.DataLoader中的iter和next

DataLoader(torch.utils.data.DataLoader)重要的是:在数据集遍历的一般化过程中,执行for i, data in enumerate(dataLoader):,当dataLoader是一个类对象,且类中定义了__iter__()时,Python解释器会在循环开始时自动调用Iter = dataLoader.__iter__()返回一个迭代器Iter,然后在之后的迭代(包扩第一次迭代)会调用迭代器Iter的__next__()方法获取数据。

__iter__()的作用是使一个类对象成为可迭代的。在for循环中,Python调用iter(obj)时,如果类对象obj是可迭代的(即定义了__iter__),那么iter(obj)实际上等同于调用obj.__iter__()生成一个迭代器。 这个方法返回的迭代器随后被用于每次循环迭代中调用__next__()来获取下一个值,直到遇到StopIteration异常为止。

1.2.1 第一种方式

下面代码中,DataLoader的load_data方法通过yield from委托给了iter(self.sampler)的这个迭代器,从而直接迭代并输出数据。注意yeild from iter(self.sampler)中必须要使用iter(),因为self.sampler并不是一个迭代器(Sampler中只定义了__iter__()),需要使用iter(self.sampler)将self.sampler转换为迭代器。

# 打印0-99
class Sampler:def __init__(self, data):self.data = datadef __iter__(self):for index in range(len(self.data)):yield self.data[index]class DataLoader:def __init__(self, sampler):self.sampler = samplerdef load_data(self):yield from iter(self.sampler)# 使用示例
sampler = Sampler(range(100))  # 假设的采样器实例
data_loader = DataLoader(sampler)
for item in data_loader.load_data():print(item)

1.2.2 第二种方式,这种方式就类似于torch.utils.data.DataLoader

  1. for item in data_loader:中,会调用data_loader.__iter__()返回一个迭代器Iter,然后一直调用迭代器Iter的__next__()返回值。在for循环中的第一次循环调用data_loader.__iter__()生成一个迭代器Iter后,之后的循环就不再调用data_loader.__iter__()了,只调用迭代器Iter的__next__()读取数据。当然,如果再次使用for循环,它又会再次调用data_loader.__iter__()生成一个迭代器。如目标检测中,使用torch.utils.data.DataLoader读取数据,epochs=300时,每一个epoch,都会使用一次for循环,即都会调用一次__iter__()生成一个迭代器。
  2. 因为class DataLoader同时定义了__iter__()__next__(),所以DataLoader类本身就是一个迭代器,所以在其__iter__()中可以使用return self返回其本身这个迭代器Iter, 然后调用迭代器Iter的Iter.__next__()去读取数据。注意,这里不要理解错了,调用的__next__()是迭代器Iter的,如果class DataLoader中的__iter__()返回的不是self,而是其他迭代器如iter(self.sampler),那么就不会调用class DataLoader__next__(),而是调用iter(self.sampler)的__next__()
  3. 还有一个注意的地方,这个在torch.utils.data.DataLoader中也出现过,就是使用next(self.sampler)时一定要注意self.sampler必须是一个迭代器,因为sampler不是一个迭代器,所以需要在__init__()使用iter(sampler)将sampler转化成迭代器。
    # 打印0-99
    class Sampler:def __init__(self, data):self.data = datadef __iter__(self):for index in range(len(self.data)):yield self.data[index]class DataLoader:def __init__(self, sampler):self.sampler = iter(sampler)def __iter__(self):return selfdef __next__(self):return next(self.sampler)# 使用示例
    sampler = Sampler(range(100))  # 假设的采样器实例
    data_loader = DataLoader(sampler)
    for item in data_loader:print(item)
    

1.2.3 第三种方式,这种方式就是torch.utils.data.DataLoader的实现方式

  1. for item in data_loader:中,会调用data_loader.__iter__()返回一个迭代器Iter,这个迭代器Iter就是DataLoaderIter(self)。self是DataLoader作为参数传入到DataLoaderIter进行初始化。注意:DataLoaderIter(self)中的__iter__()只是为了让DataLoaderIter成为一个迭代器,实际运行过程中并不调用这个__iter__()
  2. 在生成迭代器DataLoaderIter之后,会调用迭代器DataLoaderIter中的__next__()
    # 打印0-99
    # Sampler 类似于 BatchSampler
    class Sampler:def __init__(self, data):self.data = datadef __iter__(self):for index in range(len(self.data)):yield self.data[index]# DataLoaderIter 类似于 _BaseDataLoaderIter
    class DataLoaderIter:def __init__(self, loader):self.sampler = iter(loader.sampler)def __iter__(self):return selfdef __next__(self):return next(self.sampler)# DataLoader 类似于 DataLoader
    class DataLoader:def __init__(self, sampler):self.sampler = samplerdef __iter__(self):return DataLoaderIter(self)# 使用示例
    if __name__ == "__main__":sampler = Sampler(range(100))  # 假设的采样器实例data_loader = DataLoader(sampler)epochs=3for i in range(epochs):for item in data_loader:print(item)
    

1.2.4 第四种方式,这种方式就是YOLOv9中InfiniteDataLoader的实现方式

运行流程:data_loader.__iter__() => InfiniteDataLoader.__iter__() => next(self.iterator) => DataLoaderIter.__next__() => next(self.sampler) => _RepeatSampler.__iter__() => Sampler.__iter__()

  1. 首先data_loader = InfiniteDataLoader():在InfiniteDataLoader中,语句object.setattr(self, ‘sampler’, _RepeatSampler(self.sampler))含义是先执行self.sampler = Sampler(range(100)), 然后执行self.sampler = _RepeatSampler(self.sampler)。self.iterator = super().__iter__()的含义是self.iterator = DataLoaderIter(self)。
  2. 然后对于for item in data_loader:,首先调用data_loader.__iter__()生成一个迭代器。for _ in range(len(self)):中,len(self) => len(self.sampler.sampler) => len(self.data)yield next(self.iterator)yield就是生成一个迭代器,由于DataLoaderIter中定义了__next__()next(self.iterator)调用的就是DataLoaderIter.__next__()得到next(self.sampler)
  3. 在步骤1中已经知道self.sampler = _RepeatSampler(self.sampler)next(self.sampler)此时调用的是_RepeatSampler.__iter__()。关于_RepeatSampler.__iter__()while True无限循环读取数据可以查看1.2.4.1小节
  4. yeild from 委托给Sampler.__iter__(),因此Sampler.__iter__()控制流程产出元素。

章节1.2.4使用self.iterator好处:在(目标检测中的epoch)每一次epoch时,都不需要重新进行初始化迭代器DataLoaderIter(self);而对于章节1.2.3中的每一epoch,for循环都会调用data_loader.__iter__(),返回的是DataLoaderIter(self),因此每一个epoch迭代器DataLoaderIter(self)都会重新初始化。

# 打印三遍0-99
class Sampler:def __init__(self, data):self.data = datadef __len__(self):return len(self.data)def __iter__(self):for index in range(len(self.data)):yield self.data[index]class DataLoaderIter:def __init__(self, loader):# self.sampler = iter(loader.sampler)self.sampler = iter(loader.sampler)def __iter__(self):return selfdef __next__(self):return next(self.sampler)class DataLoader:def __init__(self):self.sampler =  Sampler(range(100))  # 假设的采样器实例def __iter__(self):return DataLoaderIter(self)class InfiniteDataLoader(DataLoader):""" Dataloader that reuses workersUses same syntax as vanilla DataLoader"""def __init__(self, *args, **kwargs):super().__init__(*args, **kwargs)object.__setattr__(self, 'sampler', _RepeatSampler(self.sampler))self.iterator = super().__iter__()def __len__(self):return len(self.sampler.sampler)def __iter__(self):for _ in range(len(self)):yield next(self.iterator)class _RepeatSampler:""" Sampler that repeats foreverArgs:sampler (Sampler)"""def __init__(self, sampler):self.sampler = samplerdef __iter__(self):while True:yield from iter(self.sampler)# 使用示例
if __name__ == "__main__":data_loader = InfiniteDataLoader()epochs=3for i in range(epochs):for item in data_loader:print(item)
1.2.4.1 while True

这个是章节1.2.4代码的简化版,是为了理解,为什么注释掉while True后,外层循环遍历一个epoch后(只会打印一遍0-99),会出现报错

这个问题的理解关键其实是yeild from
yeild from 用于在一个生成器中委托给另一个可迭代对象进行迭代。换句话说,它使得当前生成器暂停自己的执行,并允许另一个迭代器控制流程,直接产出其元素。当那个迭代器完成(抛出 StopIteration 异常)时,yield from 表达式的执行结束,控制权返回到调用方。

代码理解,就是next(self.loader)在调用DataLoaderIte.__iter__()迭代器时,将其委托给Sampler.__iter__()Sampler.__iter__()直接产出元素,当Sampler.__iter__()迭代器完成后,yeild from yield from 表达式的执行结束,控制权返回DataLoaderIte.__iter__()

可以在for i in range(epcohs):处打一个断点进行调试,第一个epoch会顺利打印0-99,在第二个epoch开始,首先会跳到语句yield next(self.loader),此时进行单步调试后会直接跳到for index in range(len(self.data)):,跳过了yield from iter(self.sampler)index的值是99,也就代表Sampler.__iter__()迭代器还没有迭代完成。由于index=99, self.data=100,再次进行单步调试后,Sampler.__iter__()迭代器就迭代完成了,会跳到yield from iter(self.sampler)。如果注释掉while TrueDataLoaderIter.__iter__()也迭代完成了,进行单步调试后,next(self.loader)却没有得到值,所以会抛出 StopIteration 异常;当没有注释掉while True时,进行单步调试后,又会跳到Sampler.__iter__(),此时的index=0

# 打印三遍0-99
class Sampler:def __init__(self, data):self.data = datadef __iter__(self):for index in range(len(self.data)):yield self.data[index]class DataLoaderIter:def __init__(self, loader):# self.sampler = iter(loader.sampler)self.sampler = loader.samplerdef __iter__(self):while True:  # 注释遍历完第一个epoch后就会报错yield from iter(self.sampler)# print("报错")class DataLoader:def __init__(self, sampler):self.sampler = samplerself.loader = iter(DataLoaderIter(self))def __iter__(self):for _ in range(100):yield next(self.loader)# 使用示例
if __name__ == "__main__":sampler = Sampler(range(100))  # 假设的采样器实例data_loader = DataLoader(sampler)epochs = 3for i in range(epcohs):for item in data_loader:print(item)

2 YOLOv9

create_dataloader代码

def create_dataloader(path,imgsz,batch_size,stride,single_cls=False,hyp=None,augment=False,cache=False,pad=0.0,rect=False,rank=-1,workers=8,image_weights=False,close_mosaic=False,quad=False,min_items=0,prefix='',shuffle=False):if rect and shuffle:LOGGER.warning('WARNING ⚠️ --rect is incompatible with DataLoader shuffle, setting shuffle=False')shuffle = Falsewith torch_distributed_zero_first(rank):  # init dataset *.cache only once if DDPdataset = LoadImagesAndLabels(path,imgsz,batch_size,augment=augment,  # augmentationhyp=hyp,  # hyperparametersrect=rect,  # rectangular batchescache_images=cache,single_cls=single_cls,stride=int(stride),pad=pad,image_weights=image_weights,min_items=min_items,prefix=prefix)batch_size = min(batch_size, len(dataset))nd = torch.cuda.device_count()  # number of CUDA devicesnw = min([os.cpu_count() // max(nd, 1), batch_size if batch_size > 1 else 0, workers])  # number of workerssampler = None if rank == -1 else distributed.DistributedSampler(dataset, shuffle=shuffle)#loader = DataLoader if image_weights else InfiniteDataLoader  # only DataLoader allows for attribute updatesloader = DataLoader if image_weights or close_mosaic else InfiniteDataLoadergenerator = torch.Generator()generator.manual_seed(6148914691236517205 + RANK)return loader(dataset,batch_size=batch_size,shuffle=shuffle and sampler is None,num_workers=nw,sampler=sampler,pin_memory=PIN_MEMORY,collate_fn=LoadImagesAndLabels.collate_fn4 if quad else LoadImagesAndLabels.collate_fn,worker_init_fn=seed_worker,generator=generator), dataset

在YOLOv9中,当close_mosaic = 15(启用close_moasic)时,读取数据采用的是torch.utils.data.DataLoader,当close_mosaic=0,读取数据采用的是InfiniteDataLoader

2.1 DataLoader

2.1.1 DataLoader(介绍使用torch.utils.data.DataLoader读取数据)

再次强调,一定要进行调试理解
下面介绍按照章节1.2.3进行理解

读取数据集基本流程

train_loader.__iter__() => DataLoader.__iter__() => _SingleProcessDataLoaderIter(self) => _BaseDataLoaderIter.__next__() => _SingleProcessDataLoaderIter._next_data() => index = self._next_index() => data = self._dataset_fetcher.fetch(index)

获得数据集索引的流程
index = self._next_index() => _BaseDataLoaderIter._next_index() => DataLoader._index_sampler() => BatchSampler.__iter__() => RandomSampler.__iter__()

2.1.1.1 for循环:生成迭代器

对于一个epoch,使用的时for i, (imgs, targets, paths, _) in pbar:,忽略掉tqdm,tqdm只是为了显示读取进度条的作用,即可以当成for i, (imgs, targets, paths, _) in enumerate(train_loader):

train_loader = create_dataloader()create_dataloader使用DataLoader

下面代码注释部分是train,使用官方提供的训练命令对应的初始化值。

# loader = DataLoader 
loader(dataset,batch_size=batch_size,  # 32shuffle=shuffle and sampler is None,  # Truenum_workers=nw,  # 8sampler=sampler,  # Nonepin_memory=PIN_MEMORY,  # Truecollate_fn=LoadImagesAndLabels.collate_fn4 if quad else LoadImagesAndLabels.collate_fn,  # LoadImagesAndLabels.collate_fnworker_init_fn=seed_worker,  generator=generator), dataset  

按照章节1.2中介绍的,DataLoader 会调用DataLoader .__iter__()返回一个迭代器,从类DataLoader 中的__iter__()代码可以看出,由于self.persistent_workers 默认为False,返回的迭代器是self._get_iterator()

# class DataLoaderdef __iter__(self) -> '_BaseDataLoaderIter':# When using a single worker the returned iterator should be# created everytime to avoid resetting its state# However, in the case of a multiple workers iterator# the iterator is only created once in the lifetime of the# DataLoader object so that workers can be reusedif self.persistent_workers and self.num_workers > 0:if self._iterator is None:self._iterator = self._get_iterator()else:self._iterator._reset(self)return self._iteratorelse:return self._get_iterator()

接下来,从下面代码可以看出,当num_workers = 8时,返回的迭代器是_MultiProcessingDataLoaderIter(self)。当num_workers = 0时,返回的迭代器是_SingleProcessDataLoaderIter(self)

# class DataLoaderdef _get_iterator(self) -> '_BaseDataLoaderIter':if self.num_workers == 0:return _SingleProcessDataLoaderIter(self)else:self.check_worker_number_rationality()return _MultiProcessingDataLoaderIter(self)
DataLoader相对于InfiniteDataLoader最大的区别

使用torch.utils.data.DataLoader读取数据集,每一轮epoch返回的迭代器_MultiProcessingDataLoaderIter(self)或者_SingleProcessDataLoaderIter(self)都对这两个类重新进行了初始化。
使用InfiniteDataLoader,在InfiniteDataLoader.__init__就初始化_MultiProcessingDataLoaderIter(self)或者_SingleProcessDataLoaderIter(self),在epochs每一轮期间就不需要重新初始化了。

2.1.1.2 for循环:生成迭代器后调用迭代器的__next__()

_MultiProcessingDataLoaderIter是继承于_BaseDataLoaderIter,为了方便理解,令num_workers = 0,也就是返回的迭代器是_SingleProcessDataLoaderIter(self),这个也是继承_BaseDataLoaderIter。(两个迭代器的实现过程是一样的,只不过,_MultiProcessingDataLoaderIter是多进程读取,代码相对复杂一点)。

然后就是调用_SingleProcessDataLoaderIter__next__(),在_SingleProcessDataLoaderIter中并没有__next__(),因此调用的是_BaseDataLoaderIter__next__()。下面的这行代码是返回用来返回数据的

# class _BaseDataLoaderIter: def __next__(self) data = self._next_data()  

self._next_data()是在_SingleProcessDataLoaderIter中重写

class _SingleProcessDataLoaderIter(_BaseDataLoaderIter):def __init__(self, loader):super().__init__(loader)assert self._timeout == 0assert self._num_workers == 0# Adds forward compatibilities so classic DataLoader can work with DataPipes:#   Taking care of distributed shardingif isinstance(self._dataset, (IterDataPipe, MapDataPipe)):# For BC, use default SHARDING_PRIORITIEStorch.utils.data.graph_settings.apply_sharding(self._dataset, self._world_size, self._rank)self._dataset_fetcher = _DatasetKind.create_fetcher(self._dataset_kind, self._dataset, self._auto_collation, self._collate_fn, self._drop_last)def _next_data(self):index = self._next_index()  # may raise StopIterationdata = self._dataset_fetcher.fetch(index)  # may raise StopIterationif self._pin_memory:data = _utils.pin_memory.pin_memory(data, self._pin_memory_device)return data

self._next_index()是返回的数据集的索引,self._dataset_fetcher将返回的索引变为训练需要的数据

# class _BaseDataLoaderIterdef _next_index(self):return next(self._sampler_iter)  # may raise StopIteration

self._sampler_iter是一个迭代器,是在_SingleProcessDataLoaderIter初始化中定义的

# class _BaseDataLoaderIterself._sampler_iter = iter(self._index_sampler)

关键就来到这个self._index_sampler

# class _BaseDataLoaderIterself._index_sampler = loader._index_sampler 
2.1.1.3 BatchSampler

loader_SingleProcessDataLoaderIter(self)中的self(即是DataLoader类本身), 在DataLoader类中找到下面代码,知_index_sampler = self.batch_sampler

# class DataLoaderdef _index_sampler(self):# The actual sampler used for generating indices for `_DatasetFetcher`# (see _utils/fetch.py) to read data at each time. This would be# `.batch_sampler` if in auto-collation mode, and `.sampler` otherwise.# We can't change `.sampler` and `.batch_sampler` attributes for BC# reasons.if self._auto_collation:return self.batch_samplerelse:return self.sampler

self.batch_sampler就是是batch_samplerbatch_sampler

# class DataLoaderbatch_sampler = BatchSampler(sampler, batch_size, drop_last)
self.batch_sampler = batch_sampler

章节1.2.3中Sampler有一个__iter__(),是通过这个迭代数据的,因此在BatchSampler中也是通过__iter__()迭代数据的。for idx in self.sampler:的含义是获得datasets的idx序号,将其打包成一个batch。

# class BatchSamplerdef __iter__(self) -> Iterator[List[int]]:# Implemented based on the benchmarking in https://github.com/pytorch/pytorch/pull/76951if self.drop_last:sampler_iter = iter(self.sampler)while True:try:batch = [next(sampler_iter) for _ in range(self.batch_size)]yield batchexcept StopIteration:breakelse:batch = [0] * self.batch_sizeidx_in_batch = 0for idx in self.sampler:batch[idx_in_batch] = idxidx_in_batch += 1if idx_in_batch == self.batch_size:yield batchidx_in_batch = 0batch = [0] * self.batch_sizeif idx_in_batch > 0:yield batch[:idx_in_batch]
2.1.1.4 sample

DataLoader中最关键的是samplebatch_sampler,当shuffle=Truesample=RandomSampler,当shuffle=False时,sample=SequentialSampler

if shuffle:sampler = RandomSampler(dataset, generator=generator)  # type: ignore[arg-type]
else:sampler = SequentialSampler(dataset)  # type: ignore[arg-type]

因此,对于sample来说,shuffle(bool)决定了采用RandomSampler还是SequentialSampler

RandomSampler是打乱数据集的idx ,也就是打乱DataLoader读取datasets的顺序。SequentialSampler是不打乱DataLoader读取datasets的顺序。

注意,如果shuffle=True, 也就是采用RandomSampler,不管是DataLoader还是InfiniteDataLoader,每一epoch中的batch中图片idx都是不一样的(前一epoch和后一epoch的同一batch读取的图片是不一样)

2.2 InfiniteDataLoader

读取数据集基本流程

train_loader.__iter__() => InfiniteDataLoader.__iter__() => next(self.iterator) => _SingleProcessDataLoaderIter(self) => _BaseDataLoaderIter.__next__() => _SingleProcessDataLoaderIter._next_data() => index = self._next_index() => data = self._dataset_fetcher.fetch(index)

获得数据集索引的流程
index = self._next_index() => _BaseDataLoaderIter._next_index() => DataLoader._index_sampler() => _RepeatSampler.__iter__() => iter(self.sampler) => BatchSampler.__iter__() => RandomSampler.__iter__()

细节部分可以按照章节1.2.4理解
获得数据集索引的流程中存在RandomSampler.__iter__()的原因是,初始化_SingleProcessDataLoaderIter(self)时,章节2.1.1.2loader._index_sampler此时为_RepeatSampler(self.batch_sampler)

class InfiniteDataLoader(dataloader.DataLoader):""" Dataloader that reuses workersUses same syntax as vanilla DataLoader"""def __init__(self, *args, **kwargs):super().__init__(*args, **kwargs)object.__setattr__(self, 'batch_sampler', _RepeatSampler(self.batch_sampler))self.iterator = super().__iter__()def __len__(self):return len(self.batch_sampler.sampler)def __iter__(self):for _ in range(len(self)):yield next(self.iterator)class _RepeatSampler:""" Sampler that repeats foreverArgs:sampler (Sampler)"""def __init__(self, sampler):self.sampler = samplerdef __iter__(self):while True:yield from iter(self.sampler)

2.3 YOLOv9 close_mosaic

章节2提到,在YOLOv9中,当close_mosaic = 15(启用close_moasic)时,读取数据采用的是torch.utils.data.DataLoader,当close_mosaic=0,读取数据采用的是InfiniteDataLoader

2.3.1 YOLOv9 实现close_mosaic流程

train.py中,在for epoch in range(start_epoch, epochs):中有判断

if epoch == (epochs - opt.close_mosaic):LOGGER.info("Closing dataloader mosaic")dataset.mosaic = False

就是这么的简单,当然,读取数据集只需要采用torch.utils.data.DataLoader即可。

2.3.2 为什么close_mosaic=15的时候,读取数据只能采取DataLoader?

小tip:YOLOv9使用close_mosaic,调用DataLoader读取数据,每一个epoch前都会等待一会,这是正常现象,因为需要重新初始化迭代器。

DataLoader可以

这个其实来源于DataLoader相对于InfiniteDataLoader最大的区别那一小章节,由于DataLoder在每一epoch都需要重新初始化迭代器_SingleProcessDataLoaderIter(self),类_SingleProcessDataLoaderIter继承了类_BaseDataLoaderIter。在_BaseDataLoaderIte.__init__()中有

# class _BaseDataLoaderIter: def __init__()
self._dataset = loader.dataset# loader.dataset
# class DataLoader: def __init()
self.dataset = dataset

获取数据在章节2.1.1.2中的

# class _SingleProcessDataLoaderIter : def _next_data()
data = self._dataset_fetcher.fetch(index)  # may raise StopIteration

这个获取数据主要是将index传入构建数据集dataset类LoadImagesAndLabels中的__getitem__

# class LoadImagesAndLabels: def __getitem__()
mosaic = self.mosaic and random.random() < hyp['mosaic']

2.3.1中是将dataset.mosaic = False,这也会让train_loader.dataset.mosaic=False,这个可以调试一下下面代码进行理解,也可以直接在YOLOv9中进行调试,将断点设置在if epoch == (epochs - opt.close_mosaic):这一行,当运行完dataset.mosaic = False之后,train_loader.dataset.mosaic=False

class Dataset:def __init__(self):self.mosaic = Truedef print(self):print(self.mosaic)class DataLoder:def __init__(self, dataset):self.dataset = datasetdef print(self):print(self.dataset.mosaic)if __name__ == "__main__":dataset = Dataset()train_loader = DataLoder(dataset)train_loader.print()dataset.mosaic = Falsetrain_loader.print()

在将train_loader.dataset.mosaic置为False后,迭代器_SingleProcessDataLoaderIter中的_SingleProcessDataLoaderIter._dataset.mosaic也已经置为False,但是在迭代器_SingleProcessDataLoaderIter(self)初始化的时候,从下面的代码知,已经调用了_SingleProcessDataLoaderIter(self)._dataset。因此需要在每一epoch将迭代器_SingleProcessDataLoaderIter重新进行初始化,将train_loader.dataset.mosaic=False的dataset传入到_SingleProcessDataLoaderIter对_SingleProcessDataLoaderIter._dataset_fetcher进行初始化。在使用close_mosaic之后,重新初始化迭代器后_SingleProcessDataLoaderIter._dataset.mosaic = False,使用self._dataset_fetcher.fetch(index)调用dataset.__getitem__获取数据时的mosaic就为False了。

# class _SingleProcessDataLoaderIter: def __init__()self._dataset_fetcher = _DatasetKind.create_fetcher(
self._dataset_kind, self._dataset, self._auto_collation, self._collate_fn, self._drop_last)
InfiniteDataLoader不可以

而对于InfiniteDataLoader而言,其是在create_dataloader()时,就已经在InfiniteDataLoader.__init__()中初始化了self.iterator = super().__iter__() => self.iterator = _SingleProcessDataLoaderIter(self)

dataset.mosaic = Falsetrain_loader.iterator._dataset.mosaic确实也置为False(可以按照上一小节DataLoader可以中的断点进行调试),但是在迭代器_SingleProcessDataLoaderIter(self)初始化的时候,从下面代码可以看出,已经调用了_SingleProcessDataLoaderIter(self)._dataset。因此,self._dataset_fetcher的值是初始化时候的_dataset.mosaic决定的,在epoch中改变的dataset.mosaic可以改变_dataset.mosaic的值,但是改变不了_dataset_fetcher。

# class _SingleProcessDataLoaderIter: def __init__()self._dataset_fetcher = _DatasetKind.create_fetcher(
self._dataset_kind, self._dataset, self._auto_collation, self._collate_fn, self._drop_last)

2.4 YOLOv8(version = “8.2.31”) 实现close_mosaic流程

YOLOv8中的InfiniteDataLoader有一点一点不同于YOLOv9

class InfiniteDataLoader(dataloader.DataLoader):"""Dataloader that reuses workers.Uses same syntax as vanilla DataLoader."""def __init__(self, *args, **kwargs):"""Dataloader that infinitely recycles workers, inherits from DataLoader."""super().__init__(*args, **kwargs)object.__setattr__(self, "batch_sampler", _RepeatSampler(self.batch_sampler))self.iterator = super().__iter__()def __len__(self):"""Returns the length of the batch sampler's sampler."""return len(self.batch_sampler.sampler)def __iter__(self):"""Creates a sampler that repeats indefinitely."""for _ in range(len(self)):yield next(self.iterator)def reset(self):"""Reset iterator.This is useful when we want to modify settings of dataset while training."""self.iterator = self._get_iterator()class _RepeatSampler:"""Sampler that repeats forever.Args:sampler (Dataset.sampler): The sampler to repeat."""def __init__(self, sampler):"""Initializes an object that repeats a given sampler indefinitely."""self.sampler = samplerdef __iter__(self):"""Iterates over the 'sampler' and yields its contents."""while True:yield from iter(self.sampler)

YOLOv8不管是使用close_mosaic还是不适用close_mosaic,使用的数据加载器都是InfiniteDataLoader,从下面代码可知,在将self.train_loader.dataset.mosaic = False,后又重新调用了self.train_loader.reset() => self._get_iterator()重新初始化了一个迭代器_SingleProcessDataLoaderIter(self),此时的_SingleProcessDataLoaderIter._dataset_fetcher的参数_SingleProcessDataLoaderIter._dataset.mosaic就是False。

# class BaseTrainer: def _do_train()
if epoch == (self.epochs - self.args.close_mosaic):self._close_dataloader_mosaic()self.train_loader.reset()def _close_dataloader_mosaic(self):"""Update dataloaders to stop using mosaic augmentation."""if hasattr(self.train_loader.dataset, "mosaic"):self.train_loader.dataset.mosaic = Falseif hasattr(self.train_loader.dataset, "close_mosaic"):LOGGER.info("Closing dataloader mosaic")self.train_loader.dataset.close_mosaic(hyp=self.args)

3 参考链接

  1. VScode如何Debug(调试)进入标准库文件/第三方包源码

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/30295.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

GLib库内存块数据类型简单用法

代码; #include <glib.h> int main() {GMemChunk *chunk; // 定义内存块gchar *mem[10]; // 定义指向原子的指针数组gint i, j;chunk g_mem_chunk_new( // 创建内存块"Test MemChunk", // 名称5, // 原子的长度50, …

DNS污染是什么?防止和清洗DNS污染的解决方案

在运营互联网业务中&#xff0c;通常会遇到各种各样的问题。其实DNS污染就是其中一个很严重的问题&#xff0c;它甚至会导致我们的业务中断&#xff0c;无法进行。今天就来了解一下DNS污染是什么&#xff1f;以及如何防止和清洗DNS污染。 什么是DNS&#xff1f; 首先我们要了解…

Android找不到so,实际上apk中有的

解决apk中有.so&#xff0c;实际运行时找不到的问题 排查方向&#xff1a; ①、.so安装位置是否实际存在文件&#xff08;context.getApplicationInfo().nativeLibraryDir&#xff09; ②、当前ARM架构适配配置或者匹配&#xff08;armeabi-v7a, arm64-v8a, x86_64, ...&#…

华为appgallery上架

为AppGallery是华为公司推出的应用商店&#xff0c;它是华为手机的预装应用商店&#xff0c;也是全球第三大应用商店。如果您是一个开发者&#xff0c;您可能会想知道如何将您的应用程序发布到华为AppGallery。本文将介绍华为AppGallery上架的原理和详细步骤。 原理介绍&#…

“灵活就业者“超两亿人 游戏开发者如何破局?

随着“灵活就业”者数量突破两亿&#xff0c;我相信“寒气”已经传递到每一位普通人&#xff01;对于游戏行业的“灵活就业”者&#xff0c;应当如何破局&#xff1f; 首先应该恭喜大家&#xff0c;选择了一个相对“稳健”的行业&#xff0c;无论大环境如何&#xff0c;游戏/软…

机器学习课程复习——ANN

Q&#xff1a;ANN&#xff1f; 基本架构 由输入层、隐藏层、输出层等构建前馈/反馈传播 工作原理 先加权求和&#xff1a;每个神经元的输出是输入加权和的激活再送入激活函数&#xff1a;激活函数的存在使得其能够拟合各类非线性任务 联想&#xff1a;像adaboosting的加权求…

SpringBoot Starter 通用接口加密组件(防篡改)+ RequestBodyAdvice和ResponseBodyAdvice原理

防篡改&#xff1a; 如何保证接口安全&#xff0c;做到防篡改防重放&#xff1f;_接口防止串改-CSDN博客 接口安全设计之防篡改和防重放_接口防篡改机制-CSDN博客 参考博客&#xff1a; RequestBodyAdvice和ResponseBodyAdvice原理详解-CSDN博客 SpringBoot Starter 通用接口…

【CT】LeetCode手撕—手撕快排

目录 题目1-思路-快排1-1 快排的核心思想快速排序算法步骤优美的调整区间 1-2 ⭐快排的实现 2- 实现⭐912. 排序数组——题解思路 3- ACM 实现 题目 原题连接&#xff1a;912. 排序数组 1-思路-快排 1-1 快排的核心思想 选择一个基准 基准左侧的元素都小于该元素基准右侧的元…

旅游小程序(Uniapp+FastAdmin+ThinkPHP)

&#x1f30f;旅游系统小程序&#xff0c;开启智慧旅行新纪元&#xff01;&#x1f680; 一款基于UniappFastAdminThinkPHP开发的旅游系统&#xff0c;包含消费者端&#xff08;手机端&#xff09;、机构工作人员&#xff08;手机端&#xff09;、机构端&#xff08;PC&#x…

[Qt] Qt Creator中配置 Vs-Code 编码风格

新建vscode-onedark.xml文档 &#xff0c;将如下内容复制进去&#xff0c;并配置到Creator中&#xff1a; <?xml version"1.0" encoding"UTF-8"?> <style-scheme version"1.0" name"One Dark"><style name"Tex…

判断单链表是否带环且返回节点

今天鄙人为大家带来的是一道简单的逻辑运算题。用用到了一个我们在链表中提及过的方法快慢法。这道题其实没啥考的实际意义。只是我们如果能了解这道题的解决方法的话。对我们后面梳理逻辑会有很大的帮助。 单链表的题目 我们可以看到上面的题目。就是让我们判断是否带环。也许…

14 学习PID--步进电机梯形加减速实现原理

步进电机加减速使用的场景有那些呢&#xff1f;为什么要使用加减速呢&#xff1f; 硬件驱动细分器与软件的细分参数或定时器分频参数设置不当时启动电机时&#xff0c;会遇见步进电机有啸叫声但是不会转动&#xff0c;这是因为软件产生脉冲的频率大于步进电机的启动频率&#x…

java: 不兼容的类型: org.apache.xmlbeans.XmlObject无法转换为x2006.main.CTRow

我使用的xmlbeans版本是5.0&#xff0c;使用xmlbeans包做转换时&#xff0c;报错&#xff0c;正如标题显示得那样 解决办法 额外再引入下面的jar包 <dependency><groupId>org.apache.xmlbeans</groupId><artifactId>xmlbeans</artifactId><…

研导智能科技 简介

研导智能科技&#xff08;日照&#xff09;有限公司&#xff08;简称&#xff1a;研导智能科技&#xff09;是一家致力于人工智能与教育融合的高科技企业。公司主要开展AI辅助科研产品开发、教育培训、个性化智能教育场景空间设计与部署等业务&#xff0c;致力于通过创新的智能…

AI音乐大模型:是颠覆还是助力?

近年来&#xff0c;随着人工智能技术的飞速发展&#xff0c;AI在音乐领域的应用也日益广泛。最近一个月&#xff0c;一款名为“音乐大模型”的AI产品在音乐圈引起了广泛关注。这款产品通过深度学习技术&#xff0c;可以自动生成旋律、编曲、歌词等音乐元素&#xff0c;让素人也…

2023-2024“讯方杯”全国总决赛即将开赛,精彩一触即发!

讯方杯 2023-2024“讯方杯”全国大学生信息技术应用及创新大赛全国总决赛将于2024年6月21日一23日在广东东莞举行&#xff0c;旨在为全国高校大学生打造竞技和交流平台&#xff0c;提升学生的ICT知识水平和实践动手能力&#xff0c;助力学生迈向更高的竞赛舞台。 本次大赛由深…

《QT从基础到进阶·七十二》基于Qt开发的文件保险柜工具并支持文件各种加密和解密

1、概述 源码放在文章末尾 该项目实现了文件各种加密和解密的功能&#xff0c;能够有效的保障文件的安全&#xff0c;主要包含如下功能&#xff1a; 1、支持所有 AES 密钥长度&#xff1b; AES_128 AES_192 AES_256 2、支持ECB、CBC、CFB、OFB四种模式&#xff1b; 3、支持ZER…

微信小程序学习(二):常用样式和组件

常用样式和组件 在小程序中不能使用HTML标签&#xff0c;也就没有DOM和BOM&#xff0c;同时仅仅支持部分CSS选择器。WXML 充当的就是类似 HTML 的角色&#xff0c;只不过在 WXML 中没有div、p、 span、img、a 等标签&#xff0c;在 WXML 中需要使用 小程序提供的 view、text 、…

MyBatis的配置文件,即:src->main->resources的配置

目录 1、properties 标签 1.1 mybatis-config.xml 1.2 db.properties 1.3 在SqlMapConfig.xml 中 引入数据库配置信息 2、typeAliases 标签 2.1 定义别名 2.2 使用别名 3、Mappers标签 作用&#xff1a;用来在核心配置文件中引入映射文件 引入方式&#xff0c;有以下…

AI 已经在污染互联网了。。赛博喂屎成为现实

大家好&#xff0c;我是程序员鱼皮。这两年 AI 发展势头迅猛&#xff0c;更好的性能、更低的成本、更优的效果&#xff0c;让 AI 这一曾经高高在上的技术也走入大众的视野&#xff0c;能够被我们大多数普通人轻松使用&#xff0c;无需理解复杂的技术和原理。 其中&#xff0c;…