如何混合不平衡的数据集以达到每个标签所需的分布?
How to mix unbalanced Datasets to reach a desired distribution per label?
我是 运行 我的神经网络 ubuntu 16.04,有 1 个 GPU (GTX 1070) 和 4 个 CPU。
我的数据集包含大约 35,000 张图像,但数据集不平衡:class 0 占 90%,class 1、2、3、4 共享其他 10%。因此,我通过使用 dataset.repeat(class_weight)
[我还使用一个函数来应用随机增强] 对 class 1-4 进行过采样,然后 concatenate
它们。
重采样策略为:
1) 一开始的时候,class_weight[n]
会被设置成一个很大的数字,这样每个class就会有和class0一样多的图片。
2) 随着训练的进行,epoch数增加,权重会根据epoch数下降,从而使分布更接近实际分布。
因为我的 class_weight
会因时代而异,所以我无法在一开始就打乱整个数据集。相反,我必须通过 class 接收数据 class,并在连接来自每个 class 的过采样数据后洗牌整个数据集。而且,为了实现平衡的批次,我必须按元素对整个数据集进行洗牌。
以下是我的部分代码。
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
为了说清楚,让我一步步描述为什么我会遇到这个问题:
因为我数据集中的classes不平衡,我想对那些少数classes.
进行过采样
因为 1.,我想对那些 class 应用随机增强并将大多数 class (class 0) 与它们连接起来。
经过研究,我发现repeat()如果有随机函数会产生不同的结果,所以我使用repeat()和my_random_augmentation_func来实现2 .
现在,已经实现了2.,我想合并所有的数据集,所以我使用concatenate()
4后,我现在面临一个问题:总共有大约40,000 - 180,000张图片(因为class_weight
逐个epoch改变,一开始会有180,000张图片总数,最后大约有 40,000 个),它们由 class 连接 class,数据集看起来像 [0000-1111-2222-3333-4444],因此批大小为 100,没有任何洗牌,每批几乎总是只有一个class,这意味着每批中的分布将是不平衡的。
为了解决5.中的"imbalanced batch"问题,我想到了应该对整个数据集进行shuffle,所以我使用了shuffle(180000)
.
最后,砰的一声,我的电脑在随机播放数据集中的 180000 个项目时死机了。
那么,有没有更好的方法可以让我获得平衡的批次,但仍然保持我想要的特征(例如,逐个改变分配时期)?
--- 编辑:问题已解决 ---
原来我不应该应用地图功能
在开头。我应该只接受文件名而不是真实文件,只是打乱文件名,然后将它们映射到真实文件。
更详细的,删除d0 = tf.data.TextLineDataset(train_csv_0)
之后的map(_parse_csv_train)
部分和其他4行,并在dataset = dataset.map(_parse_csv_train)
之后添加新行after shuffle( 180000).
我还要感谢@P-Gn,他"shuffling"部分的博客link真的很有帮助。它回答了我心中的一个问题,但我没有问:"Can I get similar randomness by using many small shuffles v.s. one large shuffle?"(我不会在这里给出答案,请查看该博客!)该博客中的方法也可能是解决此问题的潜在方法问题,但我还没有尝试过。
我建议依赖 tf.contrib.data.choose_from_datasets
, with labels picked by a 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]
请注意,"epoch" 的长度现在由多项式采样的长度定义。我在这里有点武断地将它设置为 max(class_num_samples)
——当你开始混合不同长度的数据集时,确实没有很好的选择来定义一个纪元。
然而,有一个具体的理由让它至少与最大数据集一样大:如您所见,调用 iterator.initializer
从头开始重新启动 Dataset
。因此,既然您的洗牌缓冲区比您的数据小得多(通常是这种情况),重要的是不要过早重新启动以确保训练看到所有数据。
关于洗牌
这个答案解决了使用自定义权重交错数据集的问题,而不是数据集改组的问题,这是一个不相关的问题。对大型数据集进行混洗需要做出妥协——如果不以某种方式牺牲内存和性能,就无法进行有效的动态混洗。例如,关于该主题的 this excellent blog post 以图形方式说明了缓冲区大小对洗牌质量的影响。
我是 运行 我的神经网络 ubuntu 16.04,有 1 个 GPU (GTX 1070) 和 4 个 CPU。
我的数据集包含大约 35,000 张图像,但数据集不平衡:class 0 占 90%,class 1、2、3、4 共享其他 10%。因此,我通过使用 dataset.repeat(class_weight)
[我还使用一个函数来应用随机增强] 对 class 1-4 进行过采样,然后 concatenate
它们。
重采样策略为:
1) 一开始的时候,class_weight[n]
会被设置成一个很大的数字,这样每个class就会有和class0一样多的图片。
2) 随着训练的进行,epoch数增加,权重会根据epoch数下降,从而使分布更接近实际分布。
因为我的 class_weight
会因时代而异,所以我无法在一开始就打乱整个数据集。相反,我必须通过 class 接收数据 class,并在连接来自每个 class 的过采样数据后洗牌整个数据集。而且,为了实现平衡的批次,我必须按元素对整个数据集进行洗牌。
以下是我的部分代码。
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
为了说清楚,让我一步步描述为什么我会遇到这个问题:
因为我数据集中的classes不平衡,我想对那些少数classes.
进行过采样
因为 1.,我想对那些 class 应用随机增强并将大多数 class (class 0) 与它们连接起来。
经过研究,我发现repeat()如果有随机函数会产生不同的结果,所以我使用repeat()和my_random_augmentation_func来实现2 .
现在,已经实现了2.,我想合并所有的数据集,所以我使用
concatenate()
4后,我现在面临一个问题:总共有大约40,000 - 180,000张图片(因为
class_weight
逐个epoch改变,一开始会有180,000张图片总数,最后大约有 40,000 个),它们由 class 连接 class,数据集看起来像 [0000-1111-2222-3333-4444],因此批大小为 100,没有任何洗牌,每批几乎总是只有一个class,这意味着每批中的分布将是不平衡的。为了解决5.中的"imbalanced batch"问题,我想到了应该对整个数据集进行shuffle,所以我使用了
shuffle(180000)
.最后,砰的一声,我的电脑在随机播放数据集中的 180000 个项目时死机了。
那么,有没有更好的方法可以让我获得平衡的批次,但仍然保持我想要的特征(例如,逐个改变分配时期)?
--- 编辑:问题已解决 ---
原来我不应该应用地图功能 在开头。我应该只接受文件名而不是真实文件,只是打乱文件名,然后将它们映射到真实文件。
更详细的,删除d0 = tf.data.TextLineDataset(train_csv_0)
之后的map(_parse_csv_train)
部分和其他4行,并在dataset = dataset.map(_parse_csv_train)
之后添加新行after shuffle( 180000).
我还要感谢@P-Gn,他"shuffling"部分的博客link真的很有帮助。它回答了我心中的一个问题,但我没有问:"Can I get similar randomness by using many small shuffles v.s. one large shuffle?"(我不会在这里给出答案,请查看该博客!)该博客中的方法也可能是解决此问题的潜在方法问题,但我还没有尝试过。
我建议依赖 tf.contrib.data.choose_from_datasets
, with labels picked by a 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]
请注意,"epoch" 的长度现在由多项式采样的长度定义。我在这里有点武断地将它设置为 max(class_num_samples)
——当你开始混合不同长度的数据集时,确实没有很好的选择来定义一个纪元。
然而,有一个具体的理由让它至少与最大数据集一样大:如您所见,调用 iterator.initializer
从头开始重新启动 Dataset
。因此,既然您的洗牌缓冲区比您的数据小得多(通常是这种情况),重要的是不要过早重新启动以确保训练看到所有数据。
关于洗牌
这个答案解决了使用自定义权重交错数据集的问题,而不是数据集改组的问题,这是一个不相关的问题。对大型数据集进行混洗需要做出妥协——如果不以某种方式牺牲内存和性能,就无法进行有效的动态混洗。例如,关于该主题的 this excellent blog post 以图形方式说明了缓冲区大小对洗牌质量的影响。