RPN损失函数异常求助:低损失但预测框偏差极大
我的Region Proposal Network(RPN)损失函数的损失值很低,模型评估结果如下:
2/2 [==============================] - 1s 30ms/step - loss: 4.3797e-04
然而其预测结果却严重偏离,可视化置信度Top10的检测框时,所有测试图像的检测框完全偏离目标。
我怀疑问题出在RPN损失函数中,以下是我的损失函数代码:
class RPN_Loss(tf.keras.losses.Loss): def call(self, y_true, y_pred): # Calculate binary cross entropy bce = tf.keras.losses.BinaryCrossentropy() # Split true and pred tensors into objectness and transform tensors transform_true,objectness_true = tf.split(y_true, num_or_size_splits=[4,1],axis=-1) transform_pred,objectness_pred = tf.split(y_pred, num_or_size_splits=[4,1],axis=-1) # Calculate transform loss transform_loss = tf.where(tf.abs(tf.square(transform_pred - transform_true)) < 1, 0.5 * tf.square(transform_pred - transform_true), tf.abs(tf.square(transform_pred - transform_true)) - 0.5) transform_loss = tf.where(transform_true != [0,0,0,0], transform_loss * 1/256, 0) # Calculate classification loss cls_loss = bce(objectness_true, objectness_pred) cls_loss = tf.where(objectness_true == 1, (cls_loss) * 1/256, 0) + tf.where(objectness_true == 0, cls_loss * 1/256, 0) cls_loss = tf.abs(cls_loss) return tf.reduce_sum(tf.reduce_mean(transform_loss) + tf.reduce_mean(cls_loss))
我尤其认为最后一行存在错误,应该使用reduce_sum而非reduce_mean。但替换为reduce_sum后,损失值会变得极高(如537、nan或无穷大)。另外,我对分类损失取了绝对值,不确定是否正确。未取绝对值时损失会逐渐发散至负无穷,取绝对值后该问题得到解决。
以下是我的RPN网络结构代码供参考:
def get_rpn(feature_map_width,feature_map_height,k): num_anchors = feature_map_width*feature_map_height*k inputs = tf.keras.Input(shape=(feature_map_width, feature_map_height, 3), name="img") conv2d = tf.keras.layers.Conv2D(filters=512, kernel_size=3, padding="same", activation="relu",name='conv2d')(inputs) objectness_unshaped = tf.keras.layers.Conv2D(filters=k, kernel_size=1, padding="same", name='objectness_unshaped',activation='sigmoid')(conv2d) objectness = tf.keras.layers.Reshape((num_anchors,1),name='objectness')(objectness_unshaped) transform_unshaped = tf.keras.layers.Conv2D(filters=k*4, kernel_size=1, padding="same", name='transform_unshaped',activation="linear")(conv2d) transform = tf.keras.layers.Reshape((num_anchors,4),name='transform')(transform_unshaped) combined_output = tf.keras.layers.Concatenate(axis=-1,name='concatenate')([transform,objectness]) combined_output = tf.keras.layers.Reshape((num_anchors,5),name='combined_output')(combined_output) model = tf.keras.Model(inputs=inputs, outputs=combined_output, name="region_proposal_network") tf.keras.utils.plot_model(model, "model.png", show_shapes=True) model.compile(optimizer=tf.keras.optimizers.experimental.SGD(learning_rate=LEARNING_RATE), loss=RPN_Loss()) return model
顺便说明,我使用的是学习率为0.025的SGD优化器。作为TensorFlow、计算机视觉与机器学习新手,恳请各位提供帮助:我的损失函数代码是否存在错误?
1. 分类损失核心错误
你直接用批次平均的交叉熵标量做逐元素掩码操作,逻辑完全错误。tf.keras.losses.BinaryCrossentropy()默认返回的是整个批次的平均损失,不能和逐元素的tf.where结合。此外,对分类损失取绝对值完全没必要——损失发散到负无穷是因为错误的损失计算逻辑导致梯度混乱,而非交叉熵本身会产生负值。
2. 回归损失问题
- 平滑L1损失实现错误:正确逻辑是对预测与真实值的差值绝对值做判断,而非差值平方的绝对值,应该写成
tf.abs(transform_pred - transform_true) < 1。 - 掩码逻辑错误:
transform_true != [0,0,0,0]会对每个元素单独判断,导致部分元素被掩码、部分不被,应该用tf.reduce_all(transform_true == 0, axis=-1, keepdims=True)判断整个回归目标是否全为0。 - 1/256的缩放过于激进,直接把损失压到极低,模型失去优化动力。
3. 损失聚合逻辑混乱
最后一行tf.reduce_sum(tf.reduce_mean(...) + tf.reduce_mean(...))完全多余:两个mean已经是标量,相加后再sum没有意义。正确做法是对有效损失(掩码后)计算平均,再给分类、回归损失分配合理权重(比如分类权重1,回归权重10,这是RPN的经典设置)。
4. 其他潜在问题
- 学习率0.025过高,SGD下容易导致震荡或NaN,建议先降到0.001调试。
- 未提及anchor设置,如果anchor的尺寸、比例与数据集不匹配,也会导致预测框完全偏离目标。
修正后的损失函数代码
class RPN_Loss(tf.keras.losses.Loss): def __init__(self, cls_weight=1.0, reg_weight=10.0): super().__init__() self.cls_weight = cls_weight self.reg_weight = reg_weight # 使用逐样本交叉熵,from_logits=False对应sigmoid激活 self.bce = tf.keras.losses.BinaryCrossentropy(from_logits=False, reduction=tf.keras.losses.Reduction.NONE) def call(self, y_true, y_pred): # 拆分真实值与预测值 transform_true, objectness_true = tf.split(y_true, num_or_size_splits=[4, 1], axis=-1) transform_pred, objectness_pred = tf.split(y_pred, num_or_size_splits=[4, 1], axis=-1) # 1. 计算平滑L1回归损失 diff = transform_pred - transform_true abs_diff = tf.abs(diff) smooth_l1 = tf.where(abs_diff < 1.0, 0.5 * tf.square(abs_diff), abs_diff - 0.5) # 仅计算有目标的anchor的回归损失 reg_mask = tf.reduce_all(transform_true != 0, axis=-1, keepdims=True) reg_loss = tf.reduce_mean(tf.boolean_mask(smooth_l1, reg_mask)) if tf.reduce_any(reg_mask) else 0.0 # 2. 计算二分类交叉熵损失 cls_loss = self.bce(objectness_true, objectness_pred) # 仅计算标注为0或1的anchor损失(忽略-1的难例标注) cls_mask = tf.logical_or(objectness_true == 0, objectness_true == 1) cls_loss = tf.reduce_mean(tf.boolean_mask(cls_loss, cls_mask)) if tf.reduce_any(cls_mask) else 0.0 # 3. 总损失 return self.cls_weight * cls_loss + self.reg_weight * reg_loss
额外建议
- 检查数据集标注:RPN的回归目标应该是anchor到真实框的dx、dy、dw、dh偏移量,而非直接的框坐标,需确认是否做了正确的坐标转换。
- 增加难例挖掘:RPN通常控制正负样本比例为1:3,避免负样本过多主导损失。
内容的提问来源于stack exchange,提问作者KoalaKomputer

