1

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

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

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

我希望有人能帮帮忙。非常感谢

4

2 回答 2

6

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

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

于 2020-04-09T16:47:23.013 回答
0

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

首先,_Equalizer基类:

  • 创建多个RandomSubsetSamplers(每个类一个)
  • 基于函数 ( torch.maxor torch.min) 将表现为oversamplerorundersampler

代码:

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-06-02T09:42:05.220 回答