如何混合不平衡的数据集以达到每个标签所需的分布?

时间:2018-09-14 04:50:43

标签: python tensorflow tensorflow-datasets

我正在使用1个GPU(GTX 1070)和4个CPU的ubuntu 16.04运行我的神经网络。

我的数据集包含大约35,000张图像,但数据集不平衡:0类占90%,而1,2,3,4类则占另外10%。因此,我使用dataset.repeat(class_weight) [我也使用一个函数来应用随机增强],然后concatenate对它们进行了1-4级过采样。

重新采样策略是:

1)首先,将class_weight[n]设置为一个较大的数字,以便每个类别将具有与类别0相同的图像量。

2)随着训练的进行,历元数增加,并且权重将根据历元数下降,从而使分布变得更接近实际分布。

由于我的class_weight会随着时代的变化而变化,因此我一开始就无法对整个数据集进行洗牌。取而代之的是,我必须逐个类别地接收数据,并在连接每个类别的过采样数据后重新整理整个数据集。而且,为了实现均衡的批次,我必须逐个元素地对整个数据集进行洗牌。

以下是我的代码的一部分。

def my_estimator_func():
    d0 = tf.data.TextLineDataset(train_csv_0).map(_parse_csv_train)
    d1 = tf.data.TextLineDataset(train_csv_1).map(_parse_csv_train)
    d2 = tf.data.TextLineDataset(train_csv_2).map(_parse_csv_train)
    d3 = tf.data.TextLineDataset(train_csv_3).map(_parse_csv_train)
    d4 = tf.data.TextLineDataset(train_csv_4).map(_parse_csv_train)
    d1 = d1.repeat(class_weight[1])
    d2 = d2.repeat(class_weight[2])
    d3 = d3.repeat(class_weight[3])
    d4 = d4.repeat(class_weight[4])
    dataset = d0.concatenate(d1).concatenate(d2).concatenate(d3).concatenate(d4)    
    dataset = dataset.shuffle(180000) # <- This is where the issue comes from
    dataset = dataset.batch(100)
    iterator = dataset.make_one_shot_iterator()  
    feature, label = iterator.get_next()
    return feature, label

def _parse_csv_train(line):
    parsed_line= tf.decode_csv(line, [[""], []])
    filename = parsed_line[0]
    label = parsed_line[1]
    image_string = tf.read_file(filename)
    image_decoded = tf.image.decode_jpeg(image_string, channels=3)
    # my_random_augmentation_func will apply random augmentation on the image. 
    image_aug = my_random_augmentation_func(image_decoded)
    image_resized = tf.image.resize_images(image_aug, image_resize)
    return image_resized, label

为了明确起见,让我逐步说明为什么要面对这个问题:

  1. 因为我的数据集中的类不平衡,所以我想对那些少数类进行过度采样。

  2. 由于1.,我想对那些类应用随机增广,并用它们连接多数类(类0)。

  3. 经过研究,我发现如果其中包含随机函数,repeat()会产生不同的结果,因此我将repeat()与my_random_augmentation_func一起使用以实现2。

  4. 现在,已经达到2,我想合并所有数据集,所以我使用concatenate()

  5. 在4之后,我现在面临一个问题:总共大约有40,000-180,000张图像(因为class_weight一次又一次地更改,开始时总共会有180,000张图像,最后大约有40,000个),并且它们是逐级连接的,数据集看起来像[0000-1111-2222-3333-4444],因此,批次大小为100,没有任何改组,几乎总是只有一个分类在每个批次中,这意味着每个批次中的分布将不平衡。

  6. 为了解决5中的“批次不平衡”问题,我想到了应该重新整理整个数据集的想法,因此我使用shuffle(180000)

  7. 最后,繁荣起来,我的计算机冻结了,洗了数据集中的180000个项目。

因此,是否有更好的方法可以使批次平衡,但仍保留我想要的特征(例如,逐个更改分配时期)?

---编辑:已解决问题---

原来,我不应该应用地图功能 一开始。我应该只输入文件名而不是真实文件,然后随机播放文件名,然后将它们映射到真实文件。

更详细地,删除map(_parse_csv_train)和其他4行之后的d0 = tf.data.TextLineDataset(train_csv_0)部分,并在shuffle(180000)之后添加一个新行dataset = dataset.map(_parse_csv_train)。 >

我还要对@ P-Gn表示感谢,他的“改组”部分中的博客链接确实很有帮助。它回答了我脑海中的一个问题,但我没有问:“我可以通过使用许多小混洗与一个大混洗来获得类似的随机性吗?” (我不会在这里给出答案,请检查该博客!)该博客中的方法也可能是此问题的潜在解决方案,但我还没有尝试过。

1 个答案:

答案 0 :(得分:1)

我建议使用tf.contrib.data.choose_from_datasets,标签由tf.multinomial分布选择。与其他基于样本拒绝的功能相比,此方法的优点是您不会失去读取未使用样本的I / O带宽。

以下是一个与您的案例相似的工作示例,其中包含一个虚拟数据集:

import tensorflow as tf

# create dummy datasets
class_num_samples = [900, 25, 25, 25, 25]
class_start = [0, 1000, 2000, 3000, 4000]
ds = [
  tf.data.Dataset.range(class_start[0], class_start[0] + class_num_samples[0]),
  tf.data.Dataset.range(class_start[1], class_start[1] + class_num_samples[1]),
  tf.data.Dataset.range(class_start[2], class_start[2] + class_num_samples[2]),
  tf.data.Dataset.range(class_start[3], class_start[3] + class_num_samples[3]),
  tf.data.Dataset.range(class_start[4], class_start[4] + class_num_samples[4])
]

# pick from dataset according to a parameterizable distribution
class_relprob_ph = tf.placeholder(tf.float32, shape=len(class_num_samples))
pick = tf.data.Dataset.from_tensor_slices(
  tf.multinomial(tf.log(class_relprob_ph)[None], max(class_num_samples))[0])
ds = tf.contrib.data.choose_from_datasets(ds, pick).repeat().batch(20)

iterator = ds.make_initializable_iterator()
batch = iterator.get_next()

with tf.Session() as sess:
  # choose uniform distribution
  sess.run(iterator.initializer, feed_dict={class_relprob_ph: [1, 1, 1, 1, 1]})
  print(batch.eval())
# [   0 1000 1001    1 3000 4000 3001 4001    2    3 1002 1003 2000    4    5 2001 3002 1004    6 2002]

  # now follow input distribution
  sess.run(iterator.initializer, feed_dict={class_relprob_ph: class_num_samples})
  print(batch.eval())
# [   0    1 4000    2    3    4    5 3000    6    7    8    9 2000   10   11   12   13 4001   14   15]

请注意,“历元”的长度现在由多项式采样的长度定义。我在这里max(class_num_samples)中任意地设置了它—在开始混合不同长度的数据集时,确实没有很好的选择来定义纪元。

但是,有一个具体的原因使其至少与最大的数据集一样大:正如您所注意到的,调用iterator.initializer从头开始重新启动Dataset。因此,既然您的改组缓冲区远小于数据(通常是这种情况),那么重要的是不要提早开始以确保训练能够看到所有数据。

关于改组

此答案解决了使用自定义权重交织数据集而不是数据集混洗的问题,这是一个不相关的问题。对大型数据集进行改组需要做出让步-在不以某种方式牺牲内存和性能的情况下,无法进行有效的动态改组。例如,该主题上有this excellent blog post,以图形方式说明了缓冲区大小对混洗质量的影响。