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

基于ImageNet预训练DenseNet的序数回归损失函数优化问询

解决序数分类中的损失函数问题

你的问题核心在于二元交叉熵(binary crossentropy)没有考虑序数标签的单调性约束——你的编码方式是累积式的(比如类别3对应[1,1,0,0],表示该样本"≥2且≥3,但<4且<5"),这要求模型输出的概率必须满足p1 ≥ p2 ≥ p3 ≥ p4(因为如果一个样本≥3,那它必然≥2,所以P(≥3)不能大于P(≥2))。而二元交叉熵独立计算每个输出位的损失,完全忽略了这种顺序依赖,导致违反顺序的预测不会被额外惩罚。

下面给出两种针对性的解决方案,适配你现有的模型架构:

方案1:带单调约束惩罚的二元交叉熵损失

在原二元交叉熵的基础上,加入一个惩罚项,专门惩罚违反p1 ≥ p2 ≥ p3 ≥ p4的预测。这样既保留了二元交叉熵对每个标签位的拟合能力,又强制模型学习符合序数逻辑的输出。

代码实现

import tensorflow.keras.backend as K

def ordinal_binary_crossentropy(y_true, y_pred):
    # 基础二元交叉熵损失
    bce_loss = K.mean(K.binary_crossentropy(y_true, y_pred), axis=1)
    
    # 计算单调约束惩罚:对所有p_i < p_{i+1}的情况进行惩罚
    monotonic_penalty = 0.0
    num_outputs = y_pred.shape[1]
    for i in range(num_outputs - 1):
        # 当p_{i+1} > p_i时,产生惩罚值(取差值的正值部分)
        penalty = K.maximum(0.0, y_pred[:, i+1] - y_pred[:, i])
        monotonic_penalty += K.mean(penalty)
    
    # 总损失 = 二元交叉熵 + 惩罚项(可调整惩罚权重lambda_penalty)
    lambda_penalty = 1.0  # 可根据验证集表现调整,比如2.0、5.0
    return bce_loss + lambda_penalty * monotonic_penalty

使用方式

编译模型时替换原损失函数:

model.compile(optimizer=keras.optimizers.Nadam(), 
              loss=ordinal_binary_crossentropy, 
              metrics=[soft_acc_multi_output])

效果说明

对于你提到的两个错误预测:

  • [0.9, 0.7, 0, 0.6]:p3=0 < p4=0.6,会产生0.6的惩罚;
  • [0.9, 0.7, 0.6, 0]:所有相邻概率都满足p_i ≥ p_{i+1},无惩罚。
    因此前者的总损失会显著高于后者,符合你的需求。

方案2:标准序数分类的累积链接损失(更推荐)

这是序数分类领域的标准方法,直接基于累积概率的对数似然构建损失,同时加入单调约束,更贴合你的任务逻辑。

适配现有架构的实现(保留sigmoid激活)

def cumulative_link_loss(y_true, y_pred):
    # 计算所有累积标签的交叉熵之和
    ce_loss = 0.0
    num_outputs = y_true.shape[1]
    for i in range(num_outputs):
        ce = K.binary_crossentropy(y_true[:, i], y_pred[:, i])
        ce_loss += K.mean(ce)
    
    # 加入单调约束惩罚
    monotonic_penalty = 0.0
    for i in range(num_outputs - 1):
        penalty = K.maximum(0.0, y_pred[:, i+1] - y_pred[:, i])
        monotonic_penalty += K.mean(penalty)
    
    return ce_loss + 1.0 * monotonic_penalty

进阶优化:改用线性激活+有序逻辑损失

如果想更贴近有序逻辑回归的理论,可以把最后一层的激活改为linear,直接输出logit,然后用累积对数似然损失:

# 修改模型最后一层
preds = Dense(4, activation="linear")(x)

# 定义有序逻辑损失
def ordered_logit_loss(y_true, y_pred):
    loss = 0.0
    num_outputs = y_true.shape[1]
    for i in range(num_outputs):
        # 将logit转换为累积概率
        cumulative_prob = K.sigmoid(y_pred[:, i])
        # 计算交叉熵
        ce = -y_true[:, i] * K.log(cumulative_prob) - (1 - y_true[:, i]) * K.log(1 - cumulative_prob)
        loss += K.mean(ce)
    
    # 单调约束:logit必须单调递减(保证累积概率单调递减)
    monotonic_penalty = 0.0
    for i in range(num_outputs - 1):
        penalty = K.maximum(0.0, y_pred[:, i+1] - y_pred[:, i])
        monotonic_penalty += K.mean(penalty)
    
    return loss + 1.0 * monotonic_penalty

额外建议

  1. 调整惩罚权重:如果模型仍然频繁违反单调约束,可以增大lambda_penalty的值(比如从1.0调到2.0或5.0);
  2. 微调预训练模型:在训练前期冻结base_model,后期可以解冻并使用小学习率(比如1e-5)微调,让模型更好地适配你的序数分类任务;
  3. 补充评估指标:除了你自定义的soft_acc_multi_output,可以加入加权kappa系数(衡量序数预测的一致性),更全面地评估模型性能。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.06 15:52:32