【问题标题】:How to calculate F1 Macro in Keras?如何在 Keras 中计算 F1 宏?
【发布时间】:2017-09-18 17:35:58
【问题描述】:

在删除之前,我尝试使用 Keras 提供的代码。这是代码:

def precision(y_true, y_pred):
    true_positives = K.sum(K.round(K.clip(y_true * y_pred, 0, 1)))
    predicted_positives = K.sum(K.round(K.clip(y_pred, 0, 1)))
    precision = true_positives / (predicted_positives + K.epsilon())
    return precision

def recall(y_true, y_pred):
    true_positives = K.sum(K.round(K.clip(y_true * y_pred, 0, 1)))
    possible_positives = K.sum(K.round(K.clip(y_true, 0, 1)))
    recall = true_positives / (possible_positives + K.epsilon())
    return recall

def fbeta_score(y_true, y_pred, beta=1):
    if beta < 0:
        raise ValueError('The lowest choosable beta is zero (only precision).')

    # If there are no true positives, fix the F score at 0 like sklearn.
    if K.sum(K.round(K.clip(y_true, 0, 1))) == 0:
        return 0

    p = precision(y_true, y_pred)
    r = recall(y_true, y_pred)
    bb = beta ** 2
    fbeta_score = (1 + bb) * (p * r) / (bb * p + r + K.epsilon())
    return fbeta_score

def fmeasure(y_true, y_pred):
    return fbeta_score(y_true, y_pred, beta=1)

据我所见(我是这方面的业余爱好者),他们似乎使用了正确的公式。但是,当我尝试将其用作训练过程中的指标时,我得到了 val_accuracy、val_precision、val_recall 和 val_fmeasure 完全相同的输出。我相信即使公式正确也可能会发生,但我相信这不太可能。这个问题有什么解释吗?谢谢

【问题讨论】:

  • 输出值是否相同为零?
  • 您能否提供完整代码 - 与 fitcompile 通话?您能否提供有关您的数据的更多详细信息?
  • 这是 Keras 中的一个已知问题(请参阅:github.com/fchollet/keras/issues/5400)。精度、Rcall 和 F1-Score 正在以批量方式估算。
  • 标签有2个类别,0和1。我使用categorical_crossentropy,最后一个Dense层使用softmax激活函数。我尝试更改代码以使用 binary_crossentropy 和使用 relu 的最后一个 Dense 层,并且精度等工作正常。我认为这是因为该函数不能应用于张量形状的数据。有什么建议吗?

标签: keras


【解决方案1】:

正如@Pedia 在上面的评论中所说,on_epoch_end,如github.com/fchollet/keras/issues/5400 中所述,是最好的方法。

【讨论】:

    【解决方案2】:

    自 Keras 2.0 起,指标 f1、精度和召回率已被删除。解决方案是使用自定义度量函数:

    from keras import backend as K
    
    def f1(y_true, y_pred):
        def recall(y_true, y_pred):
            """Recall metric.
    
            Only computes a batch-wise average of recall.
    
            Computes the recall, a metric for multi-label classification of
            how many relevant items are selected.
            """
            true_positives = K.sum(K.round(K.clip(y_true * y_pred, 0, 1)))
            possible_positives = K.sum(K.round(K.clip(y_true, 0, 1)))
            recall = true_positives / (possible_positives + K.epsilon())
            return recall
    
        def precision(y_true, y_pred):
            """Precision metric.
    
            Only computes a batch-wise average of precision.
    
            Computes the precision, a metric for multi-label classification of
            how many selected items are relevant.
            """
            true_positives = K.sum(K.round(K.clip(y_true * y_pred, 0, 1)))
            predicted_positives = K.sum(K.round(K.clip(y_pred, 0, 1)))
            precision = true_positives / (predicted_positives + K.epsilon())
            return precision
        precision = precision(y_true, y_pred)
        recall = recall(y_true, y_pred)
        return 2*((precision*recall)/(precision+recall+K.epsilon()))
    
    
    model.compile(loss='binary_crossentropy',
              optimizer= "adam",
              metrics=[f1])
    

    这个函数的返回行

    return 2*((precision*recall)/(precision+recall+K.epsilon()))
    

    已通过添加常量 epsilon 进行修改,以避免除以 0。因此不会计算 NaN。

    【讨论】:

    • 我试过了,但它只返回 NaN(准确率为 99%,但我得到了精确度和召回率,这段代码为 0%)
    • 找出问题所在。我的模型不是预测真实值。谢谢你,@Paddy。
    • 很奇怪,在评估整个测试集时,我也得到了一个 NaN 值。但是,在取前 5 个样本时,它正确地返回了 f1 分数。
    • return 2*((precision*recall)/(precision+recall)) 更改为 return 2*((precision*recall)/(precision+recall+K.epsilon())) 以修复 NaN
    • @RonakAgrawal ,即便如此,我也有 nan 价值观..!
    【解决方案3】:

    我也建议这种解决方法

    • 安装 ybubnov 的 keras_metrics 软件包
    • 在 for 循环中调用 model.fit(nb_epoch=1, ...),利用每个 epoch 后输出的精度/召回指标

    类似这样的:

        for mini_batch in range(epochs):
            model_hist = model.fit(X_train, Y_train, batch_size=batch_size, epochs=1,
                                verbose=2, validation_data=(X_val, Y_val))
    
            precision = model_hist.history['val_precision'][0]
            recall = model_hist.history['val_recall'][0]
            f_score = (2.0 * precision * recall) / (precision + recall)
            print 'F1-SCORE {}'.format(f_score)
    

    【讨论】:

    • 根据其 github 页面已弃用
    【解决方案4】:

    使用 Keras 度量函数不是计算 F1 或 AUC 之类的正确方法。

    这样做的原因是在验证的每个批处理步骤中都会调用度量函数。这样,Keras 系统就会计算批处理结果的平均值。这不是正确的 F1 分数。

    这就是为什么 F1 分数从 keras 的度量函数中删除的原因。见这里:

    正确的做法是使用自定义回调函数,如下所示:

    【讨论】:

      【解决方案5】:

      这是我使用子类化制作的流式自定义 f1_score 指标。它适用于 TensorFlow 2.0 beta,但我还没有在其他版本上尝试过。它在做什么它在整个时期跟踪真阳性、预测阳性和所有可能的阳性,然后在时期结束时计算 f1 分数。我认为其他答案只是给出每个批次的 f1 分数,当我们真的想要所有数据的 f1 分数时,这并不是最好的指标。

      我得到了 Aurélien Geron 的新书 Hands-On Machine Learning with Scikit-Learn & Tensorflow 2.0 的未经编辑的原始副本,我强烈推荐它。这就是我学习如何使用子类来实现这个 f1 自定义指标的方式。这是我见过的最全面的 TensorFlow 书籍。 TensorFlow 学习起来非常痛苦,这家伙奠定了编码基础以学习很多东西。

      仅供参考:在 Metrics 中,我必须将括号放在 f1_score() 中,否则它将不起作用。

      pip install tensorflow==2.0.0-beta1

      from sklearn.model_selection import train_test_split
      import tensorflow as tf
      from tensorflow import keras
      import numpy as np
      
      def create_f1():
          def f1_function(y_true, y_pred):
              y_pred_binary = tf.where(y_pred>=0.5, 1., 0.)
              tp = tf.reduce_sum(y_true * y_pred_binary)
              predicted_positives = tf.reduce_sum(y_pred_binary)
              possible_positives = tf.reduce_sum(y_true)
              return tp, predicted_positives, possible_positives
          return f1_function
      
      
      class F1_score(keras.metrics.Metric):
          def __init__(self, **kwargs):
              super().__init__(**kwargs) # handles base args (e.g., dtype)
              self.f1_function = create_f1()
              self.tp_count = self.add_weight("tp_count", initializer="zeros")
              self.all_predicted_positives = self.add_weight('all_predicted_positives', initializer='zeros')
              self.all_possible_positives = self.add_weight('all_possible_positives', initializer='zeros')
      
          def update_state(self, y_true, y_pred,sample_weight=None):
              tp, predicted_positives, possible_positives = self.f1_function(y_true, y_pred)
              self.tp_count.assign_add(tp)
              self.all_predicted_positives.assign_add(predicted_positives)
              self.all_possible_positives.assign_add(possible_positives)
      
          def result(self):
              precision = self.tp_count / self.all_predicted_positives
              recall = self.tp_count / self.all_possible_positives
              f1 = 2*(precision*recall)/(precision+recall)
              return f1
      
      X = np.random.random(size=(1000, 10))     
      Y = np.random.randint(0, 2, size=(1000,))
      X_train, X_test, y_train, y_test = train_test_split(X, Y, test_size=0.2)
      
      model = keras.models.Sequential([
          keras.layers.Dense(5, input_shape=[X.shape[1], ]),
          keras.layers.Dense(1, activation='sigmoid')
      ])
      
      model.compile(loss='binary_crossentropy', optimizer='SGD', metrics=[F1_score()])
      
      history = model.fit(X_train, y_train, epochs=5, validation_data=(X_test, y_test))
      

      【讨论】:

        【解决方案6】:

        正如@Diesche 所提到的,以这种方式实现 f1_score 的主要问题是它在每个批处理步骤中都被调用,并且导致的结果比其他任何事情都更令人困惑。

        我一直在努力解决这个问题,但最终通过使用回调解决了这个问题:在一个时期结束时,回调预测数据(在这种情况下,我选择只将它应用于我的验证数据)与新的模型参数,并为您提供在整个时期评估的连贯指标。

        我在 python3 上使用 tensorflow-gpu (1.14.0)

        from tensorflow.python.keras.models import Sequential, Model
        from sklearn.metrics import  f1_score
        from tensorflow.keras.callbacks import Callback
        from tensorflow.python.keras import optimizers
        
        
        
        optimizer = optimizers.SGD(lr=0.0001, decay=1e-6, momentum=0.9, nesterov=True)
        model.compile(optimizer=optimizer, loss="binary_crossentropy", metrics=['accuracy'])
        model.summary()
        
        class Metrics(Callback):
            def __init__(self, model, valid_data, true_outputs):
                super(Callback, self).__init__()
                self.model=model
                self.valid_data=valid_data    #the validation data I'm getting metrics on
                self.true_outputs=true_outputs    #the ground truth of my validation data
                self.steps=len(self.valid_data)
        
        
            def on_epoch_end(self, args,*kwargs):
                gen=generator(self.valid_data)     #generator yielding the validation data
                val_predict = (np.asarray(self.model.predict(gen, batch_size=1, verbose=0, steps=self.steps)))
        
                """
                The function from_proba_to_output is used to transform probabilities  
                into an understandable format by sklearn's f1_score function
                """
                val_predict=from_proba_to_output(val_predict, 0.5)
                _val_f1 = f1_score(self.true_outputs, val_predict)
                print ("val_f1: ", _val_f1, "   val_precision: ", _val_precision, "   _val_recall: ", _val_recall)
        

        函数from_proba_to_output如下:

        def from_proba_to_output(probabilities, threshold):
            outputs = np.copy(probabilities)
            for i in range(len(outputs)):
        
                if (float(outputs[i])) > threshold:
                    outputs[i] = int(1)
                else:
                    outputs[i] = int(0)
            return np.array(outputs)
        

        然后我通过在 fit_generator 的回调部分中引用此指标类来训练我的模型。我没有详细说明我的 train_generator 和 valid_generator 的实现,因为这些数据生成器特定于手头的分类问题,发布它们只会带来混乱。

            model.fit_generator(
        train_generator, epochs=nbr_epochs, verbose=1, validation_data=valid_generator, callbacks=[Metrics(model, valid_data)])
        

        【讨论】:

          猜你喜欢
          • 2017-09-11
          • 2021-08-29
          • 2020-11-08
          • 2021-04-11
          • 2021-05-29
          • 1970-01-01
          • 2020-10-02
          • 2019-09-14
          • 2020-10-02
          相关资源
          最近更新 更多