pytorch-如何使用加权随机采样器进行欠采样

时间:2020-02-20 12:43:03

标签: neural-network pytorch conv-neural-network imbalanced-data cnn

我有一个不平衡的数据集,想对过高代表的类进行低采样,我该如何处理。我想使用weightedrandomsampler,但我也愿意接受其他建议。

到目前为止,我假设我的代码必须像下面这样构造。但是我不知道该怎么做。

trainset = datasets.ImageFolder(path_train,transform=transform) ... sampler = data.WeightedRandomSampler(weights=..., num_samples=..., replacement=...) ... trainloader = data.DataLoader(trainset, batchsize = batchsize, sampler=sampler)

我希望有人可以提供帮助。非常感谢

2 个答案:

答案 0 :(得分:3)

根据我的理解,pytorch WeightedRandomSampler的“ weights”参数与numpy.random.choice的“ p”参数有些相似,这是一个样本将被随机选择的可能性。 Pytorch使用权重代替随机样本训练示例,它们在文档中指出权重不必总和为1,所以我的意思是这并不完全像numpy的随机选择。重量越重,样本就越有可能被采样。

当replacement = True时,这意味着可以多次绘制训练示例,这意味着您可以在训练集中拥有训练示例的副本,以用于训练模型。过度采样。同时,如果权重较低,则与其他训练样本权重相比,情况恰好相反,这意味着这些样本被随机抽样的机会较低;采样不足。

我不知道将num_samples参数与火车装载程序一起使用时如何工作,但是我可以警告您不要将批量大小放在那儿。今天,我尝试放入批处理大小,结果令人震惊。我的同事把班数* 100,他的成绩要好得多。我所知道的是,您不应在此处放置批处理大小。我还尝试将所有训练数据的大小设为num_samples,虽然效果更好,但是却花了很多时间进行训练。无论哪种方式,都可以尝试一下,看看哪种最适合您。我猜想,安全的选择是将训练示例的数量用于num_samples参数。

这是我看到其他人使用的示例,我也将其用于二进制分类。似乎工作正常。您将每个课程的训练示例数取反,并为每个训练示例设置相应的权重。

使用训练集对象的快速示例

labels = np.array(trainset.samples)[:,1]#转到数组并获取所有列标签1(即标签)

labels = labels.astype(int)#更改为int

majority_weight = 1/num_of_majority_class_training_examples

minority_weight = 1/num_of_minority_class_training_examples

sample_weights = np.array([majority_weight, minority_weight])#假设您的少数类在标签对象中为整数1。如果没有,请切换位置,使其为dominant_weight,多数_weight。

weights = samples_weights[labels]#这将遍历每个训练示例,并使用标签0和1作为sample_weights对象中的索引,该对象是您希望该类的权重。

sampler = WeightedRandomSampler(weights=weights, num_samples=, replacement=True)

trainloader = data.DataLoader(trainset, batchsize = batchsize, sampler=sampler)

由于pytorch文档说权重不必总和为1,我认为您也可以使用不平衡类之间的比率。例如,如果您有100个多数派培训样本和50个少数派培训样本,则比率为2:1。为了平衡这一点,我认为您可以对每个多数类培训示例使用权重1.0,对所有少数族裔培训示例使用权重2.0,因为从技术上讲,您希望选择少数族裔的可能性增加2倍,这将平衡您的随机选择期间的课程。

我希望这可以有所帮助。抱歉,我的草率写作很匆忙,没有人回答。我自己为此苦苦挣扎,也没有找到任何帮助。如果没有任何意义,请说出这些,然后我将对其进行重新编辑,以便在我有空闲时间时更加清楚。

答案 1 :(得分:0)

基于 torchdata(免责声明:我是作者)可以创建自定义欠采样器。

首先,_Equalizer 基类:

  • 创建多个 RandomSubsetSampler(每个类一个)
  • 基于函数(torch.maxtorch.min)将表现为 oversamplerundersampler

代码:

class _Equalizer(Sampler):
    def __init__(self, labels: torch.tensor, function):
        if len(labels.shape) > 1:
            raise ValueError(
                "labels can only have a single dimension (N, ), got shape: {}".format(
                    labels.shape
                )
            )
        tensors = [
            torch.nonzero(labels == i, as_tuple=False).flatten()
            for i in torch.unique(labels)
        ]
        self.samples_per_label = getattr(builtins, function)(map(len, tensors))
        self.samplers = [
            iter(
                RandomSubsetSampler(
                    tensor,
                    replacement=len(tensor) < self.samples_per_label,
                    num_samples=self.samples_per_label
                    if len(tensor) < self.samples_per_label
                    else None,
                )
            )
            for tensor in tensors
        ]

    @property
    def num_samples(self):
        return self.samples_per_label * len(self.samplers)

    def __iter__(self):
        for _ in range(self.samples_per_label):
            for index in torch.randperm(len(self.samplers)).tolist():
                yield next(self.samplers[index])

    def __len__(self):
        return self.num_samples

现在,我们可以创建 undersampler(添加了 oversampler,因为它现在很短):

class RandomUnderSampler(_Equalizer):
    def __init__(self, labels: torch.tensor):
        super().__init__(labels, "min")

class RandomOverSampler(_Equalizer):
    def __init__(self, labels):
        super().__init__(labels, "max")

只需将您的标签传递给 __init__(必须是 1D,但可以有多个或二进制类),您就可以对数据进行上/下采样。<​​/p>