回归任务必须用Dense(1, activation=None)输出连续值,禁用sigmoid/softmax;损失函数须用MSE类,标签dtype必须为float32/64;多输出用Dense(n),标准化标签需反变换还原。

回归任务必须用 tf.keras.layers.Dense 单输出 + 线性激活
回归预测连续值,最后一层不能加 sigmoid 或 softmax——那会把输出强行压缩到 0~1 或概率分布,彻底毁掉数值范围。Dense 层输出维度必须是 1(单目标)或对应目标维度(多输出回归),且 activation=None(即线性激活,等价于不设 activation 参数)。
常见错误现象:model.predict() 输出全在 0.2~0.8 之间,训练 loss 下降但预测值严重偏移真实量纲;或者验证集 MAE 奇高,但 loss 曲线看起来“很平滑”——大概率是激活函数锁死了输出范围。
- 正确写法:
tf.keras.layers.Dense(1)或tf.keras.layers.Dense(1, activation="linear") - 错误写法:
tf.keras.layers.Dense(1, activation="sigmoid")、tf.keras.layers.Dense(1, activation="relu") - 多输出回归(如同时预测温度和湿度):用
tf.keras.layers.Dense(2),不加激活
损失函数选 tf.keras.losses.MeanSquaredError 而不是交叉熵
回归问题的监督信号是数值差异,不是类别归属。用 tf.keras.losses.SparseCategoricalCrossentropy 或 CategoricalCrossentropy 会导致梯度方向错乱、loss 不下降甚至 NaN——因为它们内部做了 log 和 one-hot 转换,输入却是一堆浮点数。
使用场景:只要标签是 float32/float64 的一维或多维数组(如 y_true.shape == (N, 1) 或 (N, 3)),就必须用 MSE 类损失。
- 推荐写法:
model.compile(loss=tf.keras.losses.MeanSquaredError())或简写loss="mse" - 等效但更明确:
loss=tf.keras.losses.MeanSquaredError(reduction=tf.keras.losses.Reduction.AUTO) - 避免写
loss="categorical_crossentropy",哪怕只有一列标签——TensorFlow 不会自动识别“单列=分类”,它只认 shape 和 dtype
model.fit() 输入标签必须是 float 类型,否则 MSE 计算失效
TensorFlow 在计算 MSE 时对数据类型敏感:如果 y_train 是 int32,MSE 内部可能触发隐式类型提升失败,或导致梯度为 0(尤其在低精度设备上)。现象是 loss 停滞在初始值附近,model.trainable_variables 的梯度全为 None 或零张量。
性能影响:int 标签强制 cast 到 float 会引入额外 kernel 启动开销,批量大时可观测到 step time 上升 5%~10%。
- 务必检查:
y_train.dtype应为float32或float64 - 修复方式:
y_train = y_train.astype(np.float32)或tf.cast(y_train, tf.float32) - 加载数据时直接指定:
pd.read_csv(..., dtype={"target": "float32"})
验证集 MSE 下降但预测值系统性偏高/偏低?检查标签缩放与反变换
很多人用 StandardScaler 或 MinMaxScaler 对标签做归一化后训练,却在预测后忘记用 inverse_transform 还原——结果所有预测值都在标准化后的区间里(比如均值 0、标准差 1),和原始业务单位完全脱节。
容易被忽略的点:scaler 必须在训练前拟合,且仅拟合训练集标签;验证集和测试集标签只能 transform,不能重新 fit。
- 正确流程:
scaler.fit(y_train); y_train_scaled = scaler.transform(y_train) - 预测后必须:
y_pred_original = scaler.inverse_transform(y_pred_scaled) - 若跳过这步,MSE 数值本身“看起来合理”,但业务上毫无意义——比如预测房价输出是 -0.87,而不是 87 万
缩放本身不影响模型收敛,但漏掉反变换会让整个回归链路失去落地能力。这点比 loss 函数选错更隐蔽,因为训练日志一切正常。