如何为单热编码二进制分类器创建 Keras 自定义损失函数?

数据挖掘 Python 喀拉斯 张量流 损失函数
2021-10-12 06:57:41

我有一个带有单热编码标签的 CNN 二进制分类器,它是我使用 Keras 编写的,它只是没有训练到我想要鼓励的指标。我的数据非常不平衡(91% 0 类,9% 1 类),无论我做什么,它总是有利于大多数类的准确性。我玩过班级重量。我试过创建平衡的测试和训练文件。我的 CNN 似乎更难在少数类中找到模式,所以它总是返回多数类的改进。我想做的是创建一个自定义损失,允许我定义一个分数,奖励少数类的真阳性 (TP) 并惩罚其假阳性 (FP)。类似于以下内容:


def minority_score(y_true, y_pred):

    max_value_true = K.argmax(y_true, -1)
    max_value_pred = K.argmax(y_pred, -1)

    FP = np.logical_and(K.eval(max_value_pred) == 1, K.eval(max_value_true) == 0)
    TP = np.logical_and(K.eval(max_value_pred) == 1, K.eval(max_value_true) == 1)
    score = (TP *3) - FP # punish each FP with a -1 and reward each TP with a +3
    return score # invert if using as loss function

model = build_model()

model.compile(loss=minority_score,
    optimizer=keras.optimizers.Adam(lr=0.0001),
    metrics=[minority_score, metrics.categorical_accuracy])

我尝试了各种方法,但由于我的标签是单热编码的,我总是在解码它们时遇到问题。y_pred并且y_true是上例中的张量。在该示例中,我收到此错误:

InvalidArgumentError (see above for traceback): You must feed a value for placeholder tensor 'conv2d_1_input' with dtype float and shape [?,28,28,1]

这是所有放在一起的相关代码。谢谢你。


def minority_score(y_true, y_pred):

    max_value_true = K.argmax(y_true, -1)
    max_value_pred = K.argmax(y_pred, -1)

    # below line errors out
    FP = np.logical_and(K.eval(max_value_pred) == 1, K.eval(max_value_true) == 0) 
    TP = np.logical_and(K.eval(max_value_pred) == 1, K.eval(max_value_true) == 1)
    score = (TP *3) - FP # punish each FP with a -1 and reward each TP with a +3
    return score 

class Modeler:
    def build_model(self):
        """
        this method only builds the scaffolding
        weights should be set and/or loaded
        outside of it

        """

        model = Sequential()

        # layerset 1
        model.add(Conv2D(
            filters=32,
            kernel_size=[3, 3],
            strides=(1, 1),
            input_shape=(28, 28, 1),
            padding='same'
        ))
        model.add(BatchNormalization())
        model.add(Activation("relu"))
        model.add(MaxPooling2D(pool_size=(2, 2), strides=(2, 2)))
        model.add(Dropout(0.2))
        # layerset 2
        model.add(Conv2D(
            filters=64,
            kernel_size=[3, 3],
            padding='same'
        ))
        model.add(BatchNormalization())
        model.add(Activation("relu"))
        model.add(MaxPooling2D(pool_size=(2, 2)))
        model.add(Dropout(0.2))
        # layerset 3
        model.add(Conv2D(
            filters=128,
            kernel_size=[3, 3],
            padding='same'
        ))
        model.add(BatchNormalization())
        model.add(Activation("relu"))
        model.add(MaxPooling2D(pool_size=(2, 2)))
        model.add(Dropout(0.25))

        model.add(Flatten())
        model.add(Dense(2048, activation='relu'))
        model.add(Dense(self.num_classes, activation='softmax'))

        return model

    def compile_model(self, model, learning_rate):
        model.compile(loss=minority_score,
                      optimizer=keras.optimizers.Adam(lr=learning_rate),
                      metrics=[minority_score, metrics.categorical_accuracy)
        return model

    def one_hot_encode_labels(self, data):

        return keras.utils.to_categorical(data, self.num_classes)

modeler = modeler.build_model()
model = modeler.compile_model()

# load model weights from checkpoint here
# load and shape data here
# create class weight dictionary here

model.fit(x_train, y_train,
          batch_size=batch_size,
          epochs=num_epochs,
          verbose=1,
          validation_data=(x_test, y_test),
          callbacks=callbacks_list,
          class_weight=weight_dict)


```
2个回答

如果您的问题是不平衡分类,我认为无法通过自定义损失函数解决该问题。

构建自定义的、平衡的小批量通常是要做的事情,如果它不起作用,可能是您的数据集非常不平衡,即使这个技巧也不起作用。请问您对“稀有”类有多少观察?

如果它们太少,图像增强可能是可行的方法:在每次训练迭代将原始图像输入网络之前对原始图像应用随机失真是一种人为增加数据集大小的方法(同时对抗过度拟合) )。

另一种方法是创建一个自动编码器,并将问题视为异常检测任务。异常检测必须处理异常,根据定义,异常是非常罕见的事件。您可以利用您的模型仅正确学习一个类的事实,并将另一个类的出现视为异常。它的外观应该通过自动编码器从异常压缩损失中检测出来。

假设 CLASS_WEIGHTS 包含您要为每个类应用的权重,您可以使用以下函数来对预定义损失的结果进行加权。

from tensorflow.keras import backend as K

def class_weighted_loss(y_true, y_pred, **kwargs):
  weights = tf.constant(np.array([CLASS_WEIGHTS]), dtype=tf.float32)
  y_class = K.argmax(y_true, axis=1)
  w = tf.gather(weights, y_class)
  loss = keras.losses.categorical_crossentropy(y_true, y_pred)
  result = loss * w / K.sum(w)
  return result

带有完整示例的笔记本: https ://colab.research.google.com/drive/1FB-dnyNeicdUYAvGvll1XykhcFadjUfd

在我的测试用例中,使用加权损失并不是一个明显的胜利。我认为我在这两种情况下的结果都非常相似。