03 系列APIs
Concat:将多个dataset合到一起
示例:
train_data_1 = COVID19Dataset(root_dir=img_dir, txt_path=path_txt_train)
train_data_2 = COVID19Dataset_2(root_dir_train)
train_data_3 = COVID19Dataset_3(root_dir, path_csv, "train")
train_set_all = ConcatDataset([train_data_1, train_data_2, train_data_3])
Subset:按索引将样本抽出构成子数据集
示例:
train_sub_set = Subset(train_set_all, [0, 1, 2, 5])
random_split:随机抽样构成几个切分好的数据集
示例:
set_split_1, set_split_2 = random_split(train_set_all, [4, 2])
下面主要介绍sampler
sampler与batch_sampler
都是采样器,区别:采用auto_collation时,采用batch_sampler
batch_sampler是一次返回一个batch的索引。通常我们用的都是batch_sampler,其对应的是BatchSampler类。
@property _index_sampler解析
这是DataLoader类中的一个属性装饰器方法,用于确定使用哪种采样器。其逻辑如下:
- **self._auto_collation为True时:**返回batch_sampler,用于自动将数据组装成批次
- **self._auto_collation为False时:**返回普通sampler,不进行批次组装
这个属性的主要作用是:
- 根据是否需要自动组装批次来选择合适的采样器
- 提供了一个统一的接口来访问采样器,使代码更简洁清晰
- 确保数据加载过程中使用正确的采样策略
属性装饰器(@property)解释
属性装饰器是Python中的一个特殊装饰器,它可以将一个方法转换为属性,使其能够像访问属性一样被调用,而不需要使用括号。
主要特点:
- **简化访问:**可以用访问属性的方式调用方法,使代码更简洁
- **封装保护:**提供了一种可以保护类的内部属性的方式
- **计算属性:**可以动态计算属性值,而不是存储固定的值
class Person:
def __init__(self):
self._age = 0
@property
def age(self):
return self._age
@age.setter
def age(self, value):
if value >= 0:
self._age = value
else:
raise ValueError("Age cannot be negative")
在这个例子中,age属性可以这样使用:
person = Person()
person.age = 25 # 使用setter
print(person.age) # 使用getter,输出25
BatchSampler
是在RandomSampler和SequentialSample之上封装的一个批抽取功能,如果需要shuffle,则传入RandomSampler,如果不需要打乱,则传入SequentialSampler。
学习一下BatchSampler如何产生一个batch的序号,并且支持drop_last的功能:
def __iter__(self) -> Iterator[List[int]]:
batch = []
for idx in self.sampler:
batch.append(idx)
if len(batch) == self.batch_size:
yield batch
batch = []
# 当for循环结束,且batch的数量又不满足batchsize时,则进入以下代码
# 其实就是drop_last的逻辑代码
if len(batch) > 0 and not self.drop_last:
yield batch
SequentialSampler
def __iter__(self) -> Iterator[int]:
return iter(range(len(self.data_source)))
这段代码定义了SequentialSampler(顺序采样器)的迭代器方法:
- **def iter(self):**定义了一个迭代器方法,返回类型注解指定为整数迭代器(Iterator[int])
- **return iter(range(len(self.data_source))):**返回一个从0到数据源长度的连续整数序列迭代器
举例说明:
# 假设数据源长度为5
data_source = [a, b, c, d, e]
sampler = SequentialSampler(data_source)
# 迭代器会依次返回:0, 1, 2, 3, 4
RandomSampler
def __iter__(self) -> Iterator[int]:
n = len(self.data_source)
if self.generator is None:
seed = int(torch.empty((), dtype=torch.int64).random_().item())
generator = torch.Generator()
generator.manual_seed(seed)
else:
generator = self.generator
if self.replacement:
for _ in range(self.num_samples // 32):
yield from torch.randint(high=n, size=(32,), dtype=torch.int64, generator=generator).tolist()
yield from torch.randint(high=n, size=(self.num_samples % 32,), dtype=torch.int64, generator=generator).tolist(
)
else:
yield from torch.randperm(n, generator=generator).tolist()
详细解释这段RandomSampler的代码实现:
- 初始化随机生成器:
- 如果没有提供generator,创建一个新的随机生成器并设置随机种子
- 如果提供了generator,直接使用提供的生成器
- 两种采样模式:
- replacement=True时(有放回采样):
- 每次生成32个随机数作为批次
- 使用torch.randint生成0到n-1之间的随机整数
- 最后处理不足32个的剩余样本
- replacement=False时(无放回采样):
- 使用torch.randperm生成0到n-1的随机排列
- 确保每个索引只被采样一次
- replacement=True时(有放回采样):
- 性能优化:
- 有放回采样时使用32为批次大小,这是为了提高随机数生成的效率
- 使用yield from语法来优化迭代器的性能
- 返回格式:
- 所有生成的随机索引都被转换为Python列表返回
- 确保返回的是整数类型的索引
这个采样器的主要作用是为数据加载提供随机的索引序列,是实现数据随机化的关键组件。
SubsetRandomSampler
可以通过索引定义一个子集的随机采样器
def iter(self) -> Iterator[int]:
for i in torch.randperm(len(self.indices), generator=self.generator):
yield self.indices[i]
WeightedRandomSampler
先来看它的原型:
torch.utils.data.WeightedRandomSampler(weights,num_samples,replacement=True, generator=None)
- weights (sequence) – 每个样本的采样权重,权重之和不必为1,只需要关心各样本之间的比例即可。
- num_samples (int) – 采样数量,一般设为样本总量。
- replacement (bool) –是否有放回采样。 True,表示有放回。
- generator (Generator) – 自定义生成器,通常用默认的。
示例:
# 第一步:计算每个类的采样概率
weights = torch.tensor([1, 5], dtype=torch.float)
# 第二步:生成每个样本的采样概率
train_targets = [sample[1] for sample in train_data.img_info]
samples_weights = weights[train_targets]
# 第三步:实例化WeightedRandomSampler
sampler_w = WeightedRandomSampler(
weights=samples_weights,
num_samples=len(samples_weights),
replacement=True)
这段代码演示了如何使用WeightedRandomSampler来处理不平衡数据集的采样。下面逐步解释:
- 第一步:设置类别权重
weights = torch.tensor([1, 5]) 表示两个类别的采样权重比例为1:5,这意味着第二个类别会被采样的概率是第一个类别的5倍 - 第二步:生成样本权重
通过train_targets获取每个样本的类别标签,然后使用weights[train_targets]为每个样本分配对应类别的权重 - 第三步:创建采样器
实例化WeightedRandomSampler时:- weights参数使用上面计算的样本权重
- num_samples设置为数据集大小
- replacement=True表示采用有放回采样
这种采样方式常用于处理数据集中类别分布不均匀的情况,通过给少数类更高的采样权重来平衡数据集。
WeightedRandomSampler还可以用来对 不均衡的数据进行均衡采样
假设一个不均衡数据集,每个类别数量分别是 10, 20,…, 100。总共550张样本,下面希望通过WeightedRandomSampler实现一个dataloader,每次采样550张样本,各类别的数量大约为55。
核心代码如下:
# 第一步:计算各类别的采样权重
# 计算每个类的样本数量
train_targets = [sample[1] for sample in train_data.img_info]
label_counter = collections.Counter(train_targets)
class_sample_counts = [label_counter[k] for k in sorted(label_counter)] # 需要特别注意,此list的顺序!
# 计算权重,利用倒数即可
weights = 1. / torch.tensor(class_sample_counts, dtype=torch.float)
解释这段代码的实现过程:
- 获取标签数据:
train_targets = [sample[1] for sample in train_data.img_info] 从训练数据中提取所有样本的标签 - 统计各类别数量:
使用collections.Counter统计每个类别的样本数量 - 排序统计:
通过[label_counter[k] for k in sorted(label_counter)]获取按类别排序后的样本数量统计。这里特别强调了需要注意list的顺序 - 计算权重:
使用1除以每个类别的样本数量来计算权重。这样样本数量越少的类别会获得越大的权重,从而在采样时有更大的概率被选中
这种方法的核心思想是通过赋予少数类更高的采样权重来平衡数据集。当我们使用这些权重进行采样时,少数类别的样本会被更频繁地采样,从而在每个batch中实现类别的均衡。