如何在TensorFlow中计算F1分数?附准确率代码求对应实现
如何在TensorFlow中实现对应准确率代码的F1分数计算
嘿,刚接触TensorFlow的话,从准确率转F1分数其实不难,先理清楚F1的核心逻辑——它是**精确率(Precision)和召回率(Recall)**的调和平均,公式是:F1 = 2 * (Precision * Recall) / (Precision + Recall)
结合你现有的准确率代码,我们可以复用其中的标签提取逻辑,再逐步计算F1需要的指标:
第一步:提取预测标签和真实标签
和你计算准确率时的逻辑完全一致,先把模型输出和真实标签转换成离散的类别索引:
# 提取output_1和y_1对应的标签 preds_1 = tf.argmax(output_1, axis=-1) true_1 = tf.argmax(y_1, axis=-1) # 同理处理output_2和y_2 preds_2 = tf.argmax(output_2, axis=-1) true_2 = tf.argmax(y_2, axis=-1)
第二步:二分类场景下的F1计算
如果你的任务是二分类(比如只有0和1两个类别),可以直接计算真阳性(TP)、假阳性(FP)、假阴性(FN)这三个核心指标,再推导F1:
# 定义一个小的epsilon,防止除以0出现NaN epsilon = 1e-7 # 计算output_1对应的TP/FP/FN(假设类别1是正类) tp_1 = tf.reduce_sum(tf.cast(tf.logical_and(tf.equal(preds_1, 1), tf.equal(true_1, 1)), tf.float32)) fp_1 = tf.reduce_sum(tf.cast(tf.logical_and(tf.equal(preds_1, 1), tf.equal(true_1, 0)), tf.float32)) fn_1 = tf.reduce_sum(tf.cast(tf.logical_and(tf.equal(preds_1, 0), tf.equal(true_1, 1)), tf.float32)) # 计算精确率和召回率 precision_1 = tp_1 / (tp_1 + fp_1 + epsilon) recall_1 = tp_1 / (tp_1 + fn_1 + epsilon) # 最终计算F1分数,和准确率一样给个name方便后续调用 f1_1 = 2 * (precision_1 * recall_1) / (precision_1 + recall_1 + epsilon) f1_1 = tf.identity(f1_1, name="f1_1") # 同理复制上述代码,替换变量为preds_2/true_2即可得到f1_2 tp_2 = tf.reduce_sum(tf.cast(tf.logical_and(tf.equal(preds_2, 1), tf.equal(true_2, 1)), tf.float32)) fp_2 = tf.reduce_sum(tf.cast(tf.logical_and(tf.equal(preds_2, 1), tf.equal(true_2, 0)), tf.float32)) fn_2 = tf.reduce_sum(tf.cast(tf.logical_and(tf.equal(preds_2, 0), tf.equal(true_2, 1)), tf.float32)) precision_2 = tp_2 / (tp_2 + fp_2 + epsilon) recall_2 = tp_2 / (tp_2 + fn_2 + epsilon) f1_2 = 2 * (precision_2 * recall_2) / (precision_2 + recall_2 + epsilon) f1_2 = tf.identity(f1_2, name="f1_2")
第三步:多分类场景下的F1计算
如果是多分类任务,通常会计算Macro-F1(每个类别的F1取平均),实现思路是遍历每个类别单独计算TP/FP/FN,再求平均:
epsilon = 1e-7 # 获取类别数量(从真实标签的最后一维维度获取) num_classes = tf.shape(y_1)[-1] # 初始化存储每个类别TP/FP/FN的数组 tp_1 = tf.zeros(num_classes, dtype=tf.float32) fp_1 = tf.zeros(num_classes, dtype=tf.float32) fn_1 = tf.zeros(num_classes, dtype=tf.float32) # 遍历每个类别计算指标 for cls in range(num_classes): # 判断当前样本是否被预测为该类别、是否真实属于该类别 is_pred_cls = tf.equal(preds_1, cls) is_true_cls = tf.equal(true_1, cls) # 更新对应类别的TP/FP/FN tp_1 = tf.tensor_scatter_nd_update(tp_1, [[cls]], [tf.reduce_sum(tf.cast(tf.logical_and(is_pred_cls, is_true_cls), tf.float32))]) fp_1 = tf.tensor_scatter_nd_update(fp_1, [[cls]], [tf.reduce_sum(tf.cast(tf.logical_and(is_pred_cls, tf.logical_not(is_true_cls)), tf.float32))]) fn_1 = tf.tensor_scatter_nd_update(fn_1, [[cls]], [tf.reduce_sum(tf.cast(tf.logical_and(tf.logical_not(is_pred_cls), is_true_cls), tf.float32))]) # 计算每个类别的精确率、召回率和F1 precision_per_cls_1 = tp_1 / (tp_1 + fp_1 + epsilon) recall_per_cls_1 = tp_1 / (tp_1 + fn_1 + epsilon) f1_per_cls_1 = 2 * (precision_per_cls_1 * recall_per_cls_1) / (precision_per_cls_1 + recall_per_cls_1 + epsilon) # 取平均得到Macro-F1 macro_f1_1 = tf.reduce_mean(f1_per_cls_1, name="macro_f1_1") # 同样的逻辑可以复制得到macro_f1_2
小提示:用TensorFlow内置指标简化代码
如果你不想手动实现,也可以用TF内置的tf.keras.metrics.F1Score,不过要注意它的输入格式(需要是one-hot编码的标签或类别索引),比如:
# 初始化F1指标(多分类选"macro",二分类选"binary") f1_metric_1 = tf.keras.metrics.F1Score(num_classes=num_classes, average='macro') # 更新指标(注意如果y_1已经是one-hot,直接传;如果是类别索引,要设置y_true的格式) f1_metric_1.update_state(y_1, output_1) # 获取结果 f1_1 = f1_metric_1.result()
不过这种方式更适合在训练循环中使用,和你现有手写准确率的风格略有不同,所以优先推荐前面的手动实现,更贴合你当前的代码逻辑。
内容的提问来源于stack exchange,提问作者William Scott
相关产品推荐
相关产品推荐

