我正在尝试在不平衡的训练集上训练图像分类器。为了解决类别不平衡的问题,我想对类别或单个样本进行加权。对课程进行加权似乎不起作用。不知何故,对于我的设置,我无法找到指定样本权重的方法。您可以在下面阅读我如何加载和编码训练数据以及我尝试的两种方法。
我的训练数据存储在目录结构中,其中每个图像都放置在与其类别相对应的子文件夹中(我总共有 32 个类别)。由于训练数据太大,无法一次全部加载到内存中,因此我使用 image_dataset_from_directory 并通过它描述 TF 数据集中:
中的数据train_ds = keras.preprocessing.image_dataset_from_directory (training_data_dir,
batch_size=batch_size,
image_size=img_size,
label_mode='categorical')
我使用 label_mode 'categorical',以便标签被描述为 one-hot 编码向量。
然后我预取数据:
train_ds = train_ds.prefetch(buffer_size=buffer_size)
在这种方法中,我尝试通过 fit 的 class_weight 参数指定类的类权重:
model.fit(
train_ds, epochs=epochs, callbacks=callbacks, validation_data=val_ds,
class_weight=class_weights
)
对于每个类别,我们计算权重,该权重与该类别的训练样本数量成反比。这是按如下方式完成的(这是在上述 train_ds.prefetch() 调用之前完成的):
class_num_training_samples = {}
for f in train_ds.file_paths:
class_name = f.split('/')[-2]
if class_name in class_num_training_samples:
class_num_training_samples[class_name] += 1
else:
class_num_training_samples[class_name] = 1
max_class_samples = max(class_num_training_samples.values())
class_weights = {}
for i in range(0, len(train_ds.class_names)):
class_weights[i] = max_class_samples/class_num_training_samples[train_ds.class_names[i]]
我不确定这个解决方案是否有效,因为 keras 文档没有指定 class_weights 字典的键,以防标签是 one-hot 编码的。 我尝试以这种方式训练网络,但发现权重对最终的网络没有真正的影响:当我查看每个单独类别的预测类别的分布时,我可以识别整个训练集的分布,其中对于每个类别,主导类别的预测是最有可能的。 在没有指定任何班级权重的情况下进行相同的训练会产生类似的结果。 所以我怀疑权重似乎对我的情况没有影响。
这是因为指定类权重不适用于单热编码标签,还是因为我可能做错了其他事情(在我未在此处显示的代码中)?
作为尝试提出一个不同的(在我看来不太优雅)解决方案,我想通过 fit 方法的sample_weight 参数指定单个样本权重。然而从文档我发现:
[...] 当x是数据集、生成器或keras.utils.Sequence实例时,不支持此参数,而是提供sample_weights作为x的第三个元素。
在我的设置中确实是这种情况,其中 train_ds 是一个数据集。现在我真的很难找到文档,从中我可以导出如何修改train_ds,使其具有带有权重的第三个元素。我认为使用数据集的映射方法可能很有用,但我想出的解决方案显然无效:
train_ds = train_ds.map(lambda img, label: (img, label, class_weights[np.argmax(label)]))
有没有人有一个可以与
image_dataset_from_directory
加载的数据集结合使用的解决方案?
我发现自己今天也想知道同样的事情,这是与问题相关的源代码:
if y.shape.rank >= 2:
y_classes = tf.__internal__.smart_cond.smart_cond(
backend.shape(y)[-1] > 1,
lambda: backend.argmax(y, axis=-1),
lambda: tf.cast(tf.round(tf.squeeze(y, axis=-1)), tf.int64),
)
看来 Keras 默认处理 one-hot 编码目标。