关于心电图的实验我一直都是在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