Keras上实现recall和precision,f1-score(多分类问题)

关于心电图的实验我一直都是在keras上实现的,怎么说呢,keras对于初学者来说十分友好,搭模型犹如搭积木,很多细节都被封装起来了,但是随着研究的深入,就会慢慢意识到,keras还是有很多不方便的地方。

比如说,keras2.0版本就已经删除了recall和precision,f1-score这三个指标。但对于样本不均衡的数据集来说,只看准确率是没有多大意义,尤其是我做的MIT数据集,N类心拍占比超过了88%(AAMI标准),所以,模型啥也不学,将所有类别都判断成N类,也有88%的准确率,因而,以F1-score指标作为保存最佳权重的验证标准,比起acc和loss来说,更合适一些。

关于如何解决这一问题?

https://stackoverflow.com/questions/43547402/how-to-calculate-f1-macro-in-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)

这样的计算方法是基于batch的,但在基于batch上计算是没有意义的,我们需要的是在每个epoch结束后,计算F1-score.所以正确的实现方法应该是,自定义添加一个callback,在on_epoch_end的时候通过sklearn的f1_score计算.如下:

 

from sklearn.metrics import recall_score, precision_score
class Metrics(keras.callbacks.Callback):
    def __init__(self, valid_data):
        super(Metrics, self).__init__()
        self.validation_data = valid_data

    def on_epoch_end(self, epoch, logs=None):
        logs = logs or {}
        val_predict = np.argmax(self.model.predict(self.validation_data[0]), -1)
        val_targ = self.validation_data[1]
        if len(val_targ.shape) == 2 and val_targ.shape[1] != 1:
            val_targ = np.argmax(val_targ, -1)

        _val_f1 = f1_score(val_targ, val_predict, average='macro')
        _val_recall = recall_score(val_targ, val_predict, average='macro')
        _val_precision = precision_score(val_targ, val_predict, average='macro')

        logs['val_f1'] = _val_f1
        logs['val_recall'] = _val_recall
        logs['val_precision'] = _val_precision
        print(" — val_f1: %f — val_precision: %f — val_recall: %f" % (_val_f1, _val_precision, _val_recall))
        return

然后在调用的时候,添加到callbacks里(这里我用的是fit_generator, generator是我自定义的一个训练数据生成器, 用fit也可以,这个没有影响)

history=model.fit_generator(generator=training_generator.data_label(),validation_data=(val_x,val_y),
                    steps_per_epoch=150,epochs=15,verbose=2,callbacks=Metrics(valid_data=(val_x, val_y))
                    )

这里的val_x,val_y代表的分别是验证集的数据和标签(标签不是one-hot格式),

这里,F1设置是Macro类型, 当然大家可以根据自己的需求,修改成Micro型也可,

Macro-F1: 根据每一个类别的准召率计算F1值,然后求均值. 忽略了样本间分布出现的不平衡问题.

Micro-F1: 不区分类别,直接用总体样本的准召率计算F1-score.

 

到这里,就可以使用新的metrics访问指标了.

另附参考连接:

https://medium.com/@thongonary/how-to-compute-f1-score-for-each-epoch-in-keras-a1acd17715a2

https://www.zhihu.com/question/53294625/answer/362401024

二分类问题上的实现:https://zhuanlan.zhihu.com/p/51356820

 

你可能感兴趣的:(深度学习,python)