You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

二分类任务中基于TensorFlow的Keras自定义指标:指定FPR求TPR

嘿,我来帮你搞定这个TensorFlow Keras自定义指标的需求!针对你二分类任务里要先按指定假阳性率(FPR=0.02)找阈值、再算对应真阳性率(TPR)的需求,我整理了一套可直接用的实现方案,咱们一步步来:

核心逻辑先理清楚

首先明确两个指标的定义:

  • 假阳性率(FPR):把背景(bg=0)误判为信号(sig=1)的比例 = 假阳性数 / 真实阴性总数
  • 真阳性率(TPR):正确识别信号的比例 = 真阳性数 / 真实阳性总数

我们的目标是:找到一个阈值t,当模型输出概率>t时判定为1,此时FPR刚好等于指定值X(这里是0.02),再计算这个t对应的TPR。

自定义Keras指标实现

Keras的自定义指标需要继承tf.keras.metrics.Metric,实现几个核心方法。下面是完整的可运行代码,我会逐段解释:

import tensorflow as tf
import numpy as np

class FPRConstrainedTPR(tf.keras.metrics.Metric):
    def __init__(self, target_fpr=0.02, name="fpr_constrained_tpr", **kwargs):
        super().__init__(name=name, **kwargs)
        self.target_fpr = target_fpr
        # 用来累积整个epoch的真实标签和模型预测概率
        self.y_true_accum = self.add_weight(
            name="y_true_accum", shape=[], dtype=tf.float32, 
            initializer="zeros", aggregation=tf.VariableAggregation.CONCAT
        )
        self.y_pred_accum = self.add_weight(
            name="y_pred_accum", shape=[], dtype=tf.float32, 
            initializer="zeros", aggregation=tf.VariableAggregation.CONCAT
        )
        # 存储最终计算出的阈值和对应TPR
        self.best_threshold = self.add_weight(name="best_threshold", initializer="zeros")
        self.tpr_at_target_fpr = self.add_weight(name="tpr_at_target_fpr", initializer="zeros")

    def update_state(self, y_true, y_pred, sample_weight=None):
        # 把输入张量转成一维,避免形状不匹配的问题
        y_true = tf.reshape(y_true, [-1])
        y_pred = tf.reshape(y_pred, [-1])
        
        # 累积数据:第一次累积直接赋值,后续批次拼接上去
        if tf.equal(tf.size(self.y_true_accum), 0):
            self.y_true_accum.assign(y_true)
            self.y_pred_accum.assign(y_pred)
        else:
            self.y_true_accum.assign(tf.concat([self.y_true_accum, y_true], axis=0))
            self.y_pred_accum.assign(tf.concat([self.y_pred_accum, y_pred], axis=0))

    def result(self):
        # 转成numpy数组计算更直观(也可以用纯TF操作,按需调整)
        y_true_np = self.y_true_accum.numpy()
        y_pred_np = self.y_pred_accum.numpy()
        
        # 分离真实阴性和阳性样本的预测概率
        neg_preds = y_pred_np[y_true_np == 0]
        pos_preds = y_pred_np[y_true_np == 1]
        
        # 处理边界情况:如果当前epoch没有阴性样本,直接设阈值为1.0,TPR为1.0
        if len(neg_preds) == 0:
            self.best_threshold.assign(1.0)
            self.tpr_at_target_fpr.assign(1.0)
            return self.tpr_at_target_fpr
        
        # 计算目标FPR对应的阈值:取阴性样本的(1 - target_fpr)分位数
        # 比如target_fpr=0.02,就是取阴性样本中第98%的分位数,这样只有2%的阴性样本概率高于阈值
        threshold = np.quantile(neg_preds, 1 - self.target_fpr)
        
        # 计算该阈值下的TPR
        tpr = np.mean(pos_preds > threshold) if len(pos_preds) > 0 else 0.0
        
        # 更新结果变量
        self.best_threshold.assign(threshold)
        self.tpr_at_target_fpr.assign(tpr)
        
        # 返回TPR作为指标显示,阈值可以通过属性单独获取
        return self.tpr_at_target_fpr

    def reset_states(self):
        # 每个epoch开始前重置累积数据和结果
        self.y_true_accum.assign(tf.constant([], dtype=tf.float32))
        self.y_pred_accum.assign(tf.constant([], dtype=tf.float32))
        self.best_threshold.assign(0.0)
        self.tpr_at_target_fpr.assign(0.0)

    def get_config(self):
        # 保存自定义参数,方便模型保存和加载
        config = super().get_config()
        config.update({"target_fpr": self.target_fpr})
        return config
怎么用这个指标?

在模型编译的时候直接加入即可:

model.compile(
    optimizer="adam",
    loss="binary_crossentropy",
    metrics=["accuracy", FPRConstrainedTPR(target_fpr=0.02)]
)

训练过程中,每个epoch结束后会输出这个指标的数值(也就是你要的目标FPR对应的TPR)。如果需要获取计算出的阈值,可以在训练后通过以下方式获取:

# 假设模型最后一个指标是我们自定义的这个
threshold = model.metrics[-1].best_threshold.numpy()
print(f"满足FPR=0.02的阈值为: {threshold}")
几点注意事项
  • 这个指标是基于整个epoch的数据集计算的,所以验证集上的结果是针对全量验证数据的阈值,不是单批次的
  • 如果是分布式训练,代码里的CONCAT聚合方式已经适配了多设备的数据累积
  • 当阴性样本数量极少时,分位数计算可能不稳定,你可以根据实际场景加入平滑处理或者提前过滤这种情况

内容的提问来源于stack exchange,提问作者jcollins

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.20 09:08:59