TensorFlow 损失函数怎么选?一张决策图搞定回归、分类和不平衡数据
损失函数决定了模型往哪个方向优化——选错了,训练再久也是白费。TensorFlow 内置了十几种损失函数,加上自定义能力,选择面很广,但真正常用且需要搞清楚的也就那么几类。
先搞清楚你的任务类型
选损失函数的第一步不是看哪个函数厉害,而是明确你的任务:
- 回归(预测连续值,比如房价、温度)→ MSE / MAE / Huber
- 二分类(是或否,比如垃圾邮件检测)→ Binary Crossentropy
- 多分类(多个互斥类别,比如手写数字识别)→ Categorical / Sparse Categorical Crossentropy
- 特殊场景(类别不平衡、图像分割、生成模型)→ Focal Loss / Dice Loss / KL Divergence
这个分类不是随便列的——同一类里的函数互相之间有明确的取舍逻辑,下面挨个说清楚。
回归损失:MSE、MAE 和 Huber 的取舍
三个函数各有脾气,选谁取决于你的数据长什么样。
MSE(均方误差)—— 默认选择,但对异常值过敏
pythonmodel.compile(optimizer="adam", loss="mse")
MSE 对大误差施加二次惩罚——预测偏差 10 的样本,惩罚是偏差 1 的 100 倍。这意味着如果你的数据里有几个极端异常值(比如房价数据里突然混入一栋别墅),MSE 会拼命去拟合它们,结果把整体预测带偏。
什么时候用:数据干净、分布均匀,且你确实想对大误差更严格。
MAE(平均绝对误差)—— 异常值多时的保底方案
pythonmodel.compile(optimizer="adam", loss="mae")
MAE 的惩罚和误差成线性关系,异常值不会像 MSE 那样获得不成比例的影响力。代价是在 0 点处不可导,梯度始终相同,收敛可能比 MSE 慢一些。
什么时候用:数据有明显异常值,或者你不想让少数极端样本主导训练方向。
Huber —— 两者的折中
pythonfrom tensorflow.keras.losses import Huber model.compile(optimizer="adam", loss=Huber(delta=1.0))
Huber 的设计很直觉:误差小于 delta 时按 MSE 算(收敛快),误差大于 delta 时切换成 MAE(不被异常值绑架)。delta 就是那个分界线,调大调小直接影响模型对异常值的容忍度。
什么时候用:数据有异常值但你仍然想保留 MSE 在小误差时的收敛优势。实际上大多数回归任务 Huber 都是个比 MSE 更稳的选择,只是很多人不知道。
一个实际例子:预测用户付费金额时,90% 的用户付费在 0-100 元,但有个别用户付费过万。用 MSE 会导致模型过度关注那些大额用户,预测结果偏高;用 Huber(delta=10)就能忽略极端值的影响,同时保证小额预测的精度。
分类损失:交叉熵家族
分类任务几乎都用交叉熵(Crossentropy),区别在于标签格式和分类数量。
二分类 → Binary Crossentropy
pythonmodel.compile(optimizer="adam", loss="binary_crossentropy")
输出层用 sigmoid 激活,标签是 0 或 1。这是二分类的标准配置,没什么好犹豫的。
注意:如果你的正负样本比例悬殊(比如欺诈检测,正常交易 99.9%,欺诈 0.1%),直接用 Binary Crossentropy 会让模型倾向于全预测为多数类。这时候需要 Focal Loss 或加权交叉熵。
多分类 → 两种 Crossentropy
这是很多人搞混的地方:
| Categorical Crossentropy | Sparse Categorical Crossentropy | |
|---|---|---|
| 标签格式 | one-hot 编码,如 [0, 1, 0] | 整数,如 1 |
| 输出层激活 | softmax | softmax |
| 适合场景 | 类别少、标签已 one-hot | 类别多、不想手动 one-hot |
python# 标签是 one-hot y_train = [[0, 1, 0], [1, 0, 0], [0, 0, 1]] model.compile(optimizer="adam", loss="categorical_crossentropy") # 标签是整数 y_train = [1, 0, 2] model.compile(optimizer="adam", loss="sparse_categorical_crossentropy")
经验:类别超过 10 个时,Sparse 版本更省内存也更好用。功能上完全等价,只是输入格式不同。
特殊场景的损失函数
Focal Loss —— 类别不平衡的杀手锏
pythondef focal_loss(gamma=2.0, alpha=0.25): def loss(y_true, y_pred): y_true = tf.cast(y_true, tf.float32) epsilon = tf.keras.backend.epsilon() y_pred = tf.clip_by_value(y_pred, epsilon, 1.0 - epsilon) cross_entropy = -y_true * tf.math.log(y_pred) weight = alpha * tf.pow(1.0 - y_pred, gamma) return tf.reduce_mean(tf.reduce_sum(weight * cross_entropy, axis=1)) return loss model.compile(optimizer="adam", loss=focal_loss(gamma=2.0, alpha=0.25))
Focal Loss 的核心思想:模型已经分对的样本,少花点力气;分不对的样本,加大火力。gamma 控制对易分类样本的抑制程度(越大抑制越强),alpha 控制正类的权重。
什么时候用:目标检测、欺诈检测、罕见病诊断——任何正负样本比例超过 10:1 的场景。
Dice Loss —— 图像分割标配
pythondef dice_loss(smooth=1.0): def loss(y_true, y_pred): y_true = tf.cast(y_true, tf.float32) y_pred = tf.cast(y_pred, tf.float32) intersection = tf.reduce_sum(y_true * y_pred) union = tf.reduce_sum(y_true) + tf.reduce_sum(y_pred) dice = (2.0 * intersection + smooth) / (union + smooth) return 1.0 - dice return loss model.compile(optimizer="adam", loss=dice_loss(smooth=1.0))
图像分割任务中,前景像素通常远少于背景像素。Dice Loss 基于 Dice 系数(衡量两个区域的重叠度),对小目标分割更友好。实际项目中经常和 Crossentropy 组合使用:total_loss = bce + dice_loss。
KL Divergence —— 生成模型专用
pythonmodel.compile(optimizer="adam", loss="kld")
KL 散度衡量两个概率分布的差异,在 VAE(变分自编码器)中让编码分布逼近标准正态分布,在知识蒸馏中让学生模型模仿教师模型的输出分布。日常分类回归任务用不上它。
自定义损失函数
内置函数覆盖不了所有场景。两种写法:
函数式——简单直接
pythondef custom_loss(y_true, y_pred): mse = tf.reduce_mean(tf.square(y_true - y_pred)) reg = tf.reduce_mean(tf.square(y_pred)) return mse + 0.01 * reg model.compile(optimizer="adam", loss=custom_loss)
类式——需要传参数时
pythonclass WeightedMSE(tf.keras.losses.Loss): def __init__(self, weight=1.0, name="weighted_mse"): super().__init__(name=name) self.weight = weight def call(self, y_true, y_pred): return self.weight * tf.reduce_mean(tf.square(y_true - y_pred)) model.compile(optimizer="adam", loss=WeightedMSE(weight=2.0))
类式写法的好处是参数可以在 __init__ 中初始化,不用 functools.partial 那种绕弯路的方式。而且保存模型时能正确序列化。
多任务学习中的损失组合
一个模型同时预测多个目标时,需要组合多个损失函数。关键问题是权重怎么设——最简单的做法是手动调,更科学的方法是用 Uncertainty Weighting:
pythondef multi_task_loss(y_true, y_pred): cls_pred, reg_pred = y_pred[:, :10], y_pred[:, 10:] cls_true, reg_true = y_true[:, :10], y_true[:, 10:] cls_loss = tf.keras.losses.categorical_crossentropy(cls_true, cls_pred) reg_loss = tf.keras.losses.mse(reg_true, reg_pred) return 0.5 * cls_loss + 0.5 * reg_loss model.compile(optimizer="adam", loss=multi_task_loss)
手动设 0.5/0.5 是起点,实际项目里通常需要根据各任务的收敛速度调整——哪个任务 loss 下降太快就降低权重,反之加大,保持各任务梯度量级大致相当。
损失函数调试要点
选完损失函数不代表万事大吉,训练过程中需要关注几个信号:
- Loss 值是否在合理范围:MSE 在房价预测时可能几百,在手写数字分类时可能 0.01,这都正常。但如果 Binary Crossentropy 跑到负数,说明标签或预测值有问题。
- 训练/验证 Loss 的差距:训练 Loss 持续下降但验证 Loss 开始上升,不是损失函数的问题,是过拟合——该加正则化或早停,不是换损失函数。
- Loss 突然变 NaN:学习率太大或交叉熵里预测值出现了 0——加上 epsilon 裁剪:
tf.clip_by_value(y_pred, 1e-7, 1 - 1e-7)。
快速选择参考
| 你的任务 | 数据特点 | 推荐损失函数 |
|---|---|---|
| 回归 | 数据干净 | MSE |
| 回归 | 有异常值 | Huber(delta 根据异常值大小设) |
| 回归 | 异常值很多 | MAE |
| 二分类 | 样本均衡 | Binary Crossentropy |
| 二分类 | 样本不平衡 | Focal Loss |
| 多分类 | 标签 one-hot | Categorical Crossentropy |
| 多分类 | 标签整数 | Sparse Categorical Crossentropy |
| 图像分割 | 前景小 | Dice Loss + BCE 组合 |
| 生成模型 | VAE/GAN | KL Divergence |
选损失函数不需要一步到位——先用最简单的(回归用 MSE,分类用 Crossentropy),跑出基线结果,再根据训练曲线和业务需求调整。大部分情况下换个损失函数带来的提升远不如调数据和特征。