基于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
额外建议
- 调整惩罚权重:如果模型仍然频繁违反单调约束,可以增大
lambda_penalty的值(比如从1.0调到2.0或5.0); - 微调预训练模型:在训练前期冻结
base_model,后期可以解冻并使用小学习率(比如1e-5)微调,让模型更好地适配你的序数分类任务; - 补充评估指标:除了你自定义的
soft_acc_multi_output,可以加入加权kappa系数(衡量序数预测的一致性),更全面地评估模型性能。
内容的提问来源于stack exchange,提问作者Isomorphism
相关产品推荐
相关产品推荐

