yolo dataloader
不理解 看代码 看代码 看代码 重要的事情说三遍 代码最好进行调试理解 代码是最好的老师
1 迭代器的简要介绍
1.1 iter和next的初步理解
__iter__()
: class 中定义的魔术方法
__next__()
: class 中定义的魔术方法
iter()
: 将一个定义了__iter__()
的类转化为一个迭代器
next()
: 读取迭代器中的数据
iter()和next()的原理图可以参考这个链接:Pytorch(三):Dataset和Dataloader的理解
注意:当一个类中同时定义了__iter__()和__next__(),这个类本身就是一个迭代器,或者如果只定义了__iter__(),需要使用iter()将类对象转化为迭代器
next()有一个点需要注意(见下面代码):
- 类Exmaple同时定义了
__iter__()
和__next__()
=> exmaple是一个迭代器,next(example)调用的是example中的__next__()
- 类Exmaple只定义了
__iter__()
=> 需要用iter(example)将exmaple转化为一个迭代器,next(example)调用的是example中的__iter__()
class Example:
def __init__(self, data):
self.data = data
def __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循环会捕获这个异常,并且知道这是迭代结束的信号,于是它会干净利落地结束循环,而不会将这个异常暴露给用户代码。- 具体的实现代码过程类似于
实际上,Python在幕后做的事情类似于下面的手动迭代过程:for i in obj: # 循环体
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 = data
def __iter__(self):
for index in range(len(self.data)):
yield self.data[index]
class DataLoader:
def __init__(self, sampler):
self.sampler = sampler
def 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
。
- 在
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__()
生成一个迭代器。 - 因为
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__()
。 - 还有一个注意的地方,这个在
torch.utils.data.DataLoader
中也出现过,就是使用next(self.sampler)
时一定要注意self.sampler必须是一个迭代器,因为sampler不是一个迭代器,所以需要在__init__()
使用iter(sampler)
将sampler转化成迭代器。# 打印0-99 class Sampler: def __init__(self, data): self.data = data def __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 self def __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
的实现方式
- 在
for item in data_loader:
中,会调用data_loader.__iter__()
返回一个迭代器Iter,这个迭代器Iter就是DataLoaderIter(self)
。self是DataLoader
作为参数传入到DataLoaderIter
进行初始化。注意:DataLoaderIter(self)
中的__iter__()
只是为了让DataLoaderIter
成为一个迭代器,实际运行过程中并不调用这个__iter__()
。 - 在生成迭代器
DataLoaderIter
之后,会调用迭代器DataLoaderIter
中的__next__()
。# 打印0-99 # Sampler 类似于 BatchSampler class Sampler: def __init__(self, data): self.data = data def __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 self def __next__(self): return next(self.sampler) # DataLoader 类似于 DataLoader class DataLoader: def __init__(self, sampler): self.sampler = sampler def __iter__(self): return DataLoaderIter(self) # 使用示例 if __name__ == "__main__": sampler = Sampler(range(100)) # 假设的采样器实例 data_loader = DataLoader(sampler) epochs=3 for i in range(epochs): for item in data_loader