必须用函数式API构建共享骨干:Sequential无法实现单输入多分支,会导致权重重复、梯度错乱;需封装backbone模块并复用其output,任务头loss须加权平衡(推荐不确定性加权),编译时显式指定loss字典与sample_weight_mode,训练中分项监控指标。

必须用函数式 API 构建共享骨干,Sequential 不行
想搭多任务模型却用 tf.keras.Sequential?立刻停手。它无法表达“一个输入、多个分支输出”的结构,强行堆叠层会导致权重重复初始化、梯度错乱——你看到的 loss 下降可能是假象,实际某个任务根本没学。
正确路径只有一条:用函数式 API 显式定义共享部分为独立可复用模块。比如封装一个 build_backbone() 函数,返回 tf.keras.Model 或 tf.keras.layers.Layer 实例,再分别接不同任务头:
backbone = build_backbone(input_shape=(224, 224, 3)) cls_output = tf.keras.layers.Dense(10, name='cls')(backbone.output) reg_output = tf.keras.layers.Dense(4, name='reg')(backbone.output) model = tf.keras.Model(inputs=backbone.input, outputs=[cls_output, reg_output])
- 切忌写成:
x = Conv2D(32)(inputs); x = Conv2D(64)(x)然后两次调用Dense→ 这会创建两套互不相干的卷积权重 - 分类头常用
GlobalAveragePooling2D,检测/分割头必须保留空间维度,直接接Conv2D,否则 shape 对不上 - 所有任务头的输入 tensor 必须来自同一 backbone 实例的
.output,不能是重复调用同一组 layer 的结果
损失不能简单相加,权重必须可调或可学习
total_loss = loss_cls + loss_reg 是最常踩的坑。当分类用 sparse_categorical_crossentropy(量纲 ~1–3)、回归用 MSE(量纲可能 >100),后者梯度直接淹没前者,训练几轮后 loss_cls 就卡死在 2.3 不动。
解决方法分两级:
立即学习“Python免费学习笔记(深入)”;
- 手动加权(快速验证):
total_loss = 0.8 * loss_cls + 0.2 * loss_reg,但需反复试值 - 不确定性加权(推荐):引入可训练变量
sigma1,sigma2,构造total_loss = (1/(2*sigma1**2)) * loss_cls + tf.math.log(sigma1) + (1/(2*sigma2**2)) * loss_reg + tf.math.log(sigma2)
注意:Keras 编译时要显式传入各任务 loss 字典:model.compile(loss={'cls': 'sparse_categorical_crossentropy', 'reg': 'mse'}, ...),否则默认按顺序匹配,极易错位。
sample_weight 处理极易报 InvalidArgumentError
一加 sample_weight 就崩?典型错误是传入 shape 为 (N, 14, 5) 的权重,而 Keras 默认把最后一维当类别数处理,试图 squeeze 它——报错 cannot squeeze dim [2], expected a dimension of 1。
根本原因是 Keras 对 sample_weight 的 shape 校验极松:只检查 batch 维和时间/空间维对齐,完全不管通道数。解决方案只有两个:
- 为每个输出单独指定模式:
sample_weight_mode={'cls': 'temporal', 'reg': None}('temporal'表示权重 shape 应为(N, T),None表示(N,)) - 彻底绕开全局
sample_weight,改写自定义 loss 函数,在内部做明确广播:weighted_loss = tf.reduce_mean(loss * weights),自己控制维度对齐
记住:分类输出是 one-hot(shape (N, C)),权重必须是 (N,) 或 (N, 1);序列标注输出是 (N, T, C),权重就得是 (N, T)。
验证阶段必须拆开看各任务指标,不能只盯 total_loss
训练时 total_loss 一路下降,但 val_cls_acc 停在 0.1、val_reg_mae 却降到 0.05——说明回归任务完全主导了更新,分类任务已被放弃。这种“假收敛”在多任务中太常见。
编译模型时必须启用各任务专属 metric:metrics={'cls': 'sparse_categorical_accuracy', 'reg': 'mae'},并在 model.fit() 中观察每个 epoch 输出的 cls_sparse_categorical_accuracy 和 reg_mae 分项值。
真正难的不是结构搭建,而是让梯度和平共处:共享层一次更新,可能让分类受益、回归受损。没有银弹,只能靠 loss 加权 + 梯度裁剪 + 各任务 learning rate 微调组合试探。一旦发现某任务指标持续恶化,先检查 loss 权重是否失衡,再查该任务 label 预处理是否异常——90% 的问题出在这两步。


















