分享

TensorFlow-Keras 4.训练时对样本和类别加权

 LibraryPKU 2022-06-30 发表于北京

模型训练中,如果希望模型更偏向某一类数据或更聚焦于某一批样本,可以采用对数据类别和数据加权的形式达到上述效果。keras 默认设置下,样本的权重取决于其在数据集中的频率,即原始样本的分布。有两种方法可以对数据进行加权,而与采样频率无关。

Tips:

如下代码为方便测试跑通准备,分别构建了深度模型并加载了手写数字识别数据,可以直接忽略看后面~

  1. def get_uncompiled_model():
  2. inputs = keras.Input(shape=(784,), name="digits")
  3. x = layers.Dense(64, activation="relu", name="dense_1")(inputs)
  4. x = layers.Dense(64, activation="relu", name="dense_2")(x)
  5. outputs = layers.Dense(10, activation="softmax", name="predictions")(x)
  6. model = keras.Model(inputs=inputs, outputs=outputs)
  7. return model
  8. def get_compiled_model():
  9. model = get_uncompiled_model()
  10. model.compile(
  11. optimizer="rmsprop",
  12. loss="sparse_categorical_crossentropy",
  13. metrics=["sparse_categorical_accuracy"],
  14. )
  15. return model
  16. # 加载MNIST数据集
  17. (x_train, y_train), (x_test, y_test) = keras.datasets.mnist.load_data()
  18. print("训练集大小 {} 测试集大小 {}".format(len(x_train),len(x_test)))
  19. # Preprocess the data (these are NumPy arrays)
  20. x_train = x_train.reshape(60000, 784).astype("float32") / 255
  21. x_test = x_test.reshape(10000, 784).astype("float32") / 255
  22. y_train = y_train.astype("float32")
  23. y_test = y_test.astype("float32")
  24. # Reserve 10,000 samples for validation
  25. # 0-50000 训练 50000-60000 评估
  26. x_val = x_train[-10000:]
  27. y_val = y_train[-10000:]
  28. x_train = x_train[:-10000]
  29. y_train = y_train[:-10000]
  30. print("验证集大小 {} 验证集大小 {}".format(len(x_val),len(y_val)))

一.类别加权

类别加权通过将字典传递给 Model.fit() 的 class_weight 参数来设置的。该词典将类别索引映射到应用于属于该类别的样本的权重。这可用于平衡类而不重采样,或用于训练对特定类更为重视的模型。所以一般类别加权有如下两种诉求:

->平衡样本:平衡类,如果 A B 两个类别A少B多,则可以调高A从而达到平衡

->加权样本:A B一样 但是更重视A类,则给A类添加权重

比如类 "0" 是数据中类 "1" 的一半,但是想在训练时让 0 和 1 有均衡的训练效果,则可以使用Model.fit(..., class_weight={0: 1., 1: 0.5})。下面在手写数字识别上尝试一下类别加权:

  1. def addWeightByClassWeight():
  2. import numpy as np
  3. class_weight = {
  4. 0: 1.0,
  5. 1: 1.0,
  6. 2: 1.0,
  7. 3: 1.0,
  8. 4: 1.0,
  9. # Set weight "2" for class "5",
  10. # making this class 2x more important
  11. 5: 2.0,
  12. 6: 1.0,
  13. 7: 1.0,
  14. 8: 1.0,
  15. 9: 1.0,
  16. }
  17. print("Fit with class weight")
  18. model = get_compiled_model()
  19. model.fit(x_train, y_train, class_weight=class_weight, batch_size=64, epochs=1)
  20. addWeightByClassWeight()

正常训练后来看下训练后的模型参数:

weight_Dense_1, bias_Dense_1 = model.get_layer('predictions').get_weights()

这里取最后一层 layer 的权重和截距,最后一层的参数个数 64 x 10,我们取 0-9 共10个 [64,1] 的向量看下各组参数,不好区分

再看下截距项

[-0.06948844  0.05270129  0.01319615 -0.02406324 -0.00456933  0.07021471-0.04232612  0.02836654 -0.07063926 -0.00124919]

5 的截距项相对其他数字还是较高的,当然每次训练结果不一定,也有其他数字截距和 5 相近的情况,但是多次试验下来看,整体 5 的平均截距还是大于其他参数。

二.样本加权

使用 NumPy 数据进行训练时:将sample_weight参数传递给Model.fit() 。使用 tf.data 或其他任何迭代器进行训练时:传入(input_batch, label_batch, sample_weight_batch)。sample_weights 数组是一个数字数组,用于指定批次中每个样品在计算总损失时应具有的重量。它通常用于不平衡的分类问题(其想法是将更多的权重分配给鲜为人知的类)。极端情况下使用权重为1和0时,该数组可用作损失函数的掩码 (完全丢弃某些样本对总损失的贡献)。这里给出 tf.DataSet 的使用方法:

  1. def addWeightBySampleWeight():
  2. sample_weight = np.ones(shape=(len(y_train),))
  3. # 通过对最终预测标签提权重 它通常用于不平衡的分类问题 因为给y_train增加了量 所以在损失函数会有更多贡献 变向提高了该类的重要性
  4. sample_weight[y_train == 5] = 2.0
  5. # Create a Dataset that includes sample weights
  6. # (3rd element in the return tuple).
  7. train_dataset = tf.data.Dataset.from_tensor_slices((x_train, y_train, sample_weight))
  8. # Shuffle and slice the dataset.
  9. train_dataset = train_dataset.shuffle(buffer_size=1024).batch(64)
  10. model = get_compiled_model()
  11. model.fit(train_dataset, epochs=10)
  12. addWeightBySampleWeight()

同上,看下最后一层的参数,还是不好分辨,但是和上面有的区别就是参数更加收敛了,原因是这次训练了 10 个epoch,但是上面只训练了 1 个epoch。

这次看截距项就很明显了:

三.总结

总的来说, class_weights 针对 类别不均匀的实验,例如正负样本悬殊或者多分类中,某类样本特别多或者特别少。如果样本的数量很多,则它的权重就越低,反之越高。sample_weights 针对样本加权,思路和类别权重类似,即样本数多的类别样本权重低,反之样本权重高。sample_weights 和 class_weights 如果只看上面例子会觉得比较类似,但是需要注意两边的维度,class_weights 的 size 是 distinct(class) 的长度,而 sample_weights 的 size 和训练样本的长度是一致的。这是因为class_weights是针对所有类别,而 sample_weights 是针对所有样本,假设有一批 10000 数量的样本,前5000是真实可靠的数据,后5000是可能出错的数据,即置信度不同,那么就可以调整 sample_weights,提高前 5000 样本的权重,降低后面 5000 样本的权重。除了在 DataSet 截断传入 sample_weights,sample_weights 也可以在自定义 Model 时写在反向传播的逻辑里,有兴趣可以打开 tensorflow官方API

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 全屏 打印 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多