【问题标题】:Pytorch - how to undersample using weightedrandomsamplerPytorch - 如何使用加权随机采样器进行欠采样
【发布时间】:2020-06-04 19:06:31
【问题描述】:

我有一个不平衡的数据集,想对过度代表的类进行欠采样。我该怎么做。我想使用 weightedrandomsampler,但我也愿意接受其他建议。

到目前为止,我假设我的代码必须具有如下结构。但是我不知道该怎么做。

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

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

【问题讨论】:

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


    【解决方案1】:

    据我了解,pytorch WeightedRandomSampler 'weights' 参数有点类似于 numpy.random.choice 'p' 参数,后者是样本被随机选择的概率。 Pytorch 使用权重来随机抽样训练示例,他们在文档中声明权重不必总和为 1,所以我的意思是它与 numpy 的随机选择不完全一样。权重越大,样本被采样的可能性就越大。

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

    我不知道 num_samples 参数在与火车装载机一起使用时如何工作,但我可以警告您不要将批量大小放在那里。今天,我尝试设置批量大小,但结果很糟糕。我的同事把班级数*100,他的成绩要好得多。我所知道的是你不应该把批量大小放在那里。我还尝试将我所有的训练数据的大小用于 num_samples,它有更好的结果,但需要很长时间才能训练。无论哪种方式,玩它,看看什么最适合你。我猜想安全的选择是使用 num_samples 参数的训练示例数。

    这是我看到其他人使用的示例,我也将其用于二进制分类。它似乎工作得很好。您取每个类的训练样例数量的倒数,并为所有训练样例设置该类各自的权重。

    使用您的 trainset 对象的简单示例

    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。如果不是,则换个位置,使其成为少数权重、多数权重。

    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 倍,这将平衡您的随机选择期间的课程。

    我希望这会有所帮助。抱歉写的草率,我很着急,看到没有人回答。我自己也为此苦苦挣扎,但也找不到任何帮助。如果它没有意义,请直说,我会重新编辑它,并在我有空闲时间时使其更清楚。

    【讨论】:

    • num_samples 用于完全迭代整个数据集时抽取的样本总数。所以通常你希望它等于len(dataset)
    【解决方案2】:

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

    首先,_Equalizer 基类:

    • 创建多个RandomSubsetSamplers(每个类一个)
    • 基于函数(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,但可以有多个或二进制类),您就可以对数据进行上/下采样。

    【讨论】:

      猜你喜欢
      • 2021-08-20
      • 1970-01-01
      • 2019-09-03
      • 1970-01-01
      • 2021-10-12
      • 1970-01-01
      • 1970-01-01
      • 2015-05-26
      • 2022-01-11
      相关资源
      最近更新 更多