自定义损失函数需为双参数可调用对象,用partial或闭包预设超参;metric必须继承tf.keras.metrics.Metric并实现update_state、result、reset_state;多输出模型需用字典指定loss和metrics。

如何在 tf.keras.Model 中正确注册自定义损失函数
TensorFlow 要求自定义损失函数必须是可调用对象,且签名必须为 loss_fn(y_true, y_pred),不能多参数或少参数。常见错误是直接传入带额外参数的函数(比如 my_loss(y_true, y_pred, alpha=0.5)),这会导致训练时报错 ValueError: Expected 2 arguments, got 3。
正确做法是用闭包或 functools.partial 预设超参:
import tensorflow as tf from functools import partial <p>def focal_loss(y_true, y_pred, gamma=2.0, alpha=0.25): epsilon = tf.keras.backend.epsilon() y_pred = tf.clip_by_value(y_pred, epsilon, 1. - epsilon) pt = y_true <em> y_pred + (1 - y_true) </em> (1 - y_pred) ce = -tf.log(pt) weight = alpha <em> tf.pow(1 - pt, gamma) return tf.reduce_mean(weight </em> ce)</p><h1>✅ 正确:用 partial 固定超参后传入 compile</h1><p>model.compile( optimizer='adam', loss=partial(focal_loss, gamma=2.0, alpha=0.75), metrics=['accuracy'] )
- 不要在
loss参数里直接调用函数(如loss=focal_loss(...)),那会立刻执行并返回标量,不是函数 -
y_true和y_pred是 batch 维度的张量,务必使用 TensorFlow 原生运算(tf.*),避免混用 NumPy - 如果 loss 需访问模型状态(如正则项),应改写为
tf.keras.losses.Loss子类,并重写call()
为什么自定义 metric 必须继承 tf.keras.metrics.Metric
简单函数(如 def f1_score(y_true, y_pred): ...)不能直接用于 metrics=[f1_score],因为 Keras 在训练中需要跨 batch 累积状态(如 TP、FP、FN),而普通函数每次只看到当前 batch,无法计算全局 F1。
必须用类方式实现,手动管理状态变量:
立即学习“Python免费学习笔记(深入)”;
class F1Score(tf.keras.metrics.Metric):
def __init__(self, name='f1_score', **kwargs):
super().__init__(name=name, **kwargs)
self.precision = tf.keras.metrics.Precision()
self.recall = tf.keras.metrics.Recall()
<pre class='brush:python;toolbar:false;'>def update_state(self, y_true, y_pred, sample_weight=None):
self.precision.update_state(y_true, y_pred, sample_weight)
self.recall.update_state(y_true, y_pred, sample_weight)
def result(self):
p = self.precision.result()
r = self.recall.result()
return 2 * ((p * r) / (p + r + tf.keras.backend.epsilon()))
def reset_state(self):
self.precision.reset_state()
self.recall.reset_state()✅ 注册后即可使用
model.compile(..., metrics=[F1Score()])
- 不能只重写
__call__—— Keras 不会调用它来累积;必须实现update_state、result、reset_state - 内部尽量复用现有 metric(如
Precision、Recall),避免手动维护self.true_positives等变量出错 - 如果 metric 依赖模型输出以外的中间层(如 attention weights),需改用
tf.keras.callbacks.Callback在on_batch_end中提取
如何调试自定义 loss/metric 返回 NaN 或 Inf
训练中 loss 突然变成 nan,90% 情况源于自定义函数里的数值不稳定操作:除零、log(0)、sqrt(负数)。TensorFlow 默认不报错,只静默传播 NaN。
开启调试最有效的方法是插入 tf.debugging 断言:
def safe_focal_loss(y_true, y_pred):
y_pred = tf.clip_by_value(y_pred, 1e-7, 1-1e-7) # 防 log(0)
pt = y_true * y_pred + (1 - y_true) * (1 - y_pred)
tf.debugging.assert_all_finite(pt, 'pt contains NaN/Inf')
ce = -tf.math.log(pt)
tf.debugging.assert_all_finite(ce, 'ce contains NaN/Inf')
return tf.reduce_mean(ce)- 训练前先用小 batch 数据单独测试 loss/metric 函数,输入
y_true和y_pred张量,检查输出是否为标量且 finite - 禁用
tf.function编译(加@tf.function(autograph=False))可让错误堆栈更清晰,便于定位行号 - GPU 上 NaN 更难追踪,可临时设
os.environ['TF_DETERMINISTIC_OPS'] = '1'减少随机性干扰
在多输出模型中为不同输出指定不同 loss 和 metric
当模型有多个输出(如主任务 + 辅助任务),不能只传一个 loss 函数,否则所有输出共享同一 loss 计算,结果不可控。
必须用字典明确映射:
model = tf.keras.Model(inputs=inp, outputs={'main': out1, 'aux': out2})
<p>model.compile(
optimizer='adam',
loss={
'main': 'sparse_categorical_crossentropy',
'aux': lambda y_true, y_pred: tf.keras.losses.mse(y_true, y_pred) * 0.3
},
loss_weights={'main': 1.0, 'aux': 0.3},
metrics={
'main': ['accuracy'],
'aux': [tf.keras.metrics.MeanAbsoluteError()]
}
)- loss 字典的 key 必须与模型输出层的 name 一致(即
tf.keras.layers.Dense(..., name='main')) - lambda loss 可以,但不能带自由变量(如引用外部 list);若需复杂逻辑,仍建议用
partial或独立函数 - 注意
loss_weights是乘在每个 output loss 标量之后再求和,不是影响梯度缩放比例
TensorFlow 的自定义 loss/metric 看似灵活,实际约束很具体:函数签名、状态管理、数值稳定性、多输出对齐,漏掉任一环节都会导致静默失败或训练崩溃。动手前先验证单 batch 行为,比等跑完 100 个 epoch 发现指标不对要省力得多。


















