如何在损失函数中对真阳性加权?基于TensorFlow二元交叉熵方案
这问题在样本不平衡的任务里太常见了——比如医疗诊断中漏诊(假阴性)的代价远高于误诊(假阳性),这时候我们就需要给假阴性对应的损失项加权重,让模型更重视正样本的预测。咱们从TensorFlow里最常用的二元交叉熵(BCE)开始拆解:
首先是标准的二元交叉熵损失公式:
$$\mathcal{L}(\mathbf y, \mathbf t)=-\frac {1}{N}\sum_n \left[ t_n\log y_n + (1-t_n)\log (1-y_n) \right]$$
其中:
- $t_n \in {0,1}$:第n个样本的真实标签(1代表正样本,0代表负样本)
- $y_n \in [0,1]$:模型对第n个样本的预测概率
- $N$:总样本数
这里要明确:公式里的第一项$t_n\log y_n$对应的是假阴性的损失——当$t_n=1$(真实是正样本)但$y_n$趋近于0(模型预测成负样本)时,这一项的绝对值会变得很大,也就是损失很高。如果我们需要进一步加重对假阴性的惩罚,就可以给这一项引入权重。
加权版二元交叉熵
我们给正样本对应的损失项引入权重$\mathbf c_n$(可以是全局统一值,也可以是每个样本的单独权重),公式就变成:
$$\mathcal{L}(\mathbf y, \mathbf c, \mathbf t)=-\frac {1}{N}\sum_n \left[ c_n \cdot t_n\log y_n + (1-t_n)\log (1-y_n) \right]$$
- 如果是针对整个正样本类别加权,比如正样本占比只有10%,可以把$c_n$设为固定值$c = \frac{\text{负样本数量}}{\text{正样本数量}}$(比如9),这样就能平衡正负样本的损失贡献。
- 如果是针对单个样本的个性化权重(比如某些正样本的优先级更高),可以给每个样本分配对应的$c_n$。
TensorFlow中的实现方式
1. 使用内置损失函数的class_weight参数
如果是类别级别的加权,直接用tf.keras.losses.BinaryCrossentropy配合模型拟合时的class_weight参数就可以:
import tensorflow as tf # 假设正样本权重设为9,负样本为1 class_weights = {0: 1.0, 1: 9.0} model.compile( optimizer='adam', loss=tf.keras.losses.BinaryCrossentropy(from_logits=False), metrics=['accuracy'] ) model.fit(x_train, y_train, class_weight=class_weights, epochs=10)
2. 自定义加权损失函数
如果需要更灵活的样本级加权,或者手动控制损失计算逻辑,可以自定义损失函数:
def weighted_bce(positive_weight): def loss(y_true, y_pred): # 加入极小值避免log(0)的数值问题 y_pred = tf.clip_by_value(y_pred, 1e-7, 1 - 1e-7) # 正样本损失项(假阴性惩罚) pos_loss = y_true * tf.math.log(y_pred) * positive_weight # 负样本损失项 neg_loss = (1 - y_true) * tf.math.log(1 - y_pred) # 计算平均损失并取反(BCE是负对数似然) return -tf.reduce_mean(pos_loss + neg_loss) return loss # 使用时传入正样本权重 model.compile(optimizer='adam', loss=weighted_bce(positive_weight=9.0))
3. 样本级加权的自定义损失
如果每个样本都有单独的权重,可以把权重作为输入传入(需要调整模型的输入结构):
def sample_weighted_bce(y_true, y_pred, sample_weights): y_pred = tf.clip_by_value(y_pred, 1e-7, 1 - 1e-7) pos_loss = y_true * tf.math.log(y_pred) neg_loss = (1 - y_true) * tf.math.log(1 - y_pred) # 给每个样本的损失乘以对应的权重 weighted_loss = sample_weights * (pos_loss + neg_loss) return -tf.reduce_mean(weighted_loss)
内容的提问来源于stack exchange,提问作者Nickpick

