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

Keras自定义损失函数下内置指标失效问题求助

问题分析与解决方案

你的自定义损失函数逻辑是正确的,但内置的Accuracy和MSE指标完全不匹配你的任务场景,这是导致异常结果的核心原因。下面拆解问题并给出修正方案:

一、异常原因拆解

1. 内置Accuracy始终为0

Keras内置的Accuracy(不管是分类还是回归版)的核心逻辑是逐元素匹配y_pred和y_true,但你的任务中:

  • y_true是区间上下界(每行2个值),不是要预测的目标值
  • y_pred是两个输出值,目标是让它们的均值落在y_true的区间内,而非让y_pred的元素和y_true的元素匹配
    这种情况下,内置Accuracy的计算逻辑完全不适用,返回0是必然结果,因为几乎不会出现y_pred的元素和y_true的区间上下界完全相等的情况。

2. 内置MSE始终为NaN

内置MSE是直接计算y_pred和y_true对应元素的均方误差,但你的任务目标不是让y_pred的两个元素分别等于区间的上下界,而是让y_pred的均值落在区间内。此外:

  • 如果训练数据中存在y_true上下界顺序颠倒(lower>upper)的异常样本,会导致MSE计算出现逻辑错误
  • 即使数据正常,内置MSE的计算结果也完全不符合你的任务目标,甚至可能因模型输出极端值导致数值溢出,最终变成NaN

二、修正方案:自定义匹配任务的指标

你需要替换内置指标,实现符合任务逻辑的自定义准确率和MSE指标:

1. 自定义区间准确率指标

统计样本中y_pred均值落在y_true区间内的比例:

import tensorflow as tf
from tensorflow.keras import metrics

class IntervalAccuracy(metrics.Metric):
    def __init__(self, name='interval_accuracy', **kwargs):
        super().__init__(name=name, **kwargs)
        self.correct = self.add_weight(name='correct', initializer='zeros')
        self.total = self.add_weight(name='total', initializer='zeros')
    
    def update_state(self, y_true, y_pred, sample_weight=None):
        # 计算y_pred的均值
        y_pred_avg = tf.reduce_mean(y_pred, axis=1)
        # 提取区间上下界
        y_true_lower = y_true[:, 0]
        y_true_upper = y_true[:, 1]
        # 判断是否在区间内
        within_range = tf.logical_and(y_pred_avg >= y_true_lower, y_pred_avg <= y_true_upper)
        correct = tf.cast(within_range, tf.float32)
        
        # 处理样本权重(可选)
        if sample_weight is not None:
            sample_weight = tf.cast(sample_weight, tf.float32)
            correct = tf.multiply(correct, sample_weight)
            total = tf.reduce_sum(sample_weight)
        else:
            total = tf.cast(tf.shape(y_true)[0], tf.float32)
        
        self.correct.assign_add(tf.reduce_sum(correct))
        self.total.assign_add(total)
    
    def result(self):
        return self.correct / self.total
    
    def reset_state(self):
        self.correct.assign(0.)
        self.total.assign(0.)

2. 自定义区间MSE指标

计算y_pred均值到区间边界的偏差的平方的均值(区间内偏差为0):

class IntervalMSE(metrics.Metric):
    def __init__(self, name='interval_mse', **kwargs):
        super().__init__(name=name, **kwargs)
        self.total_squared_error = self.add_weight(name='total_sq_err', initializer='zeros')
        self.total = self.add_weight(name='total', initializer='zeros')
    
    def update_state(self, y_true, y_pred, sample_weight=None):
        y_pred_avg = tf.reduce_mean(y_pred, axis=1)
        y_true_lower = y_true[:, 0]
        y_true_upper = y_true[:, 1]
        
        within_range = tf.logical_and(y_true_lower <= y_pred_avg, y_pred_avg <= y_true_upper)
        # 计算到最近边界的平方偏差
        distance_to_lower = tf.abs(y_pred_avg - y_true_lower)
        distance_to_upper = tf.abs(y_pred_avg - y_true_upper)
        min_distance = tf.reduce_min(tf.stack([distance_to_lower, distance_to_upper], axis=-1), axis=-1)
        squared_error = tf.where(within_range, tf.zeros_like(min_distance), tf.square(min_distance))
        
        # 处理样本权重(可选)
        if sample_weight is not None:
            sample_weight = tf.cast(sample_weight, tf.float32)
            squared_error = tf.multiply(squared_error, sample_weight)
            total = tf.reduce_sum(sample_weight)
        else:
            total = tf.cast(tf.shape(y_true)[0], tf.float32)
        
        self.total_squared_error.assign_add(tf.reduce_sum(squared_error))
        self.total.assign_add(total)
    
    def result(self):
        return self.total_squared_error / self.total
    
    def reset_state(self):
        self.total_squared_error.assign(0.)
        self.total.assign(0.)

3. 模型编译时使用自定义指标

替换内置指标,用自定义指标编译模型:

model.compile(optimizer='adam', loss=custom_loss, metrics=[IntervalAccuracy(), IntervalMSE()])

三、损失函数的小优化

可以简化损失函数的维度处理,让代码更简洁(功能不变):

def custom_loss(y_true, y_pred):
    y_pred_average = tf.reduce_mean(y_pred, axis=1)  # 形状为(batch_size,)
    
    y_true_lower = y_true[:, 0]
    y_true_upper = y_true[:, 1]
    
    within_range = tf.logical_and(y_true_lower <= y_pred_average, y_pred_average <= y_true_upper)
    
    # 计算到最近边界的距离
    distance_to_lower = tf.abs(y_pred_average - y_true_lower)
    distance_to_upper = tf.abs(y_pred_average - y_true_upper)
    min_distance = tf.reduce_min(tf.stack([distance_to_lower, distance_to_upper], axis=-1), axis=-1)
    
    loss = tf.where(within_range, tf.zeros_like(min_distance), min_distance)
    
    return loss

测试验证

用你的测试用例验证:

  • 区间准确率应为1/3(仅第一个样本的均值落在区间内)
  • 区间MSE应为(0² + 0.6² + 0.8²)/3 = 1/3 ≈ 0.333

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.25 09:15:26