自定义损失函数应继承tf.keras.losses.Loss并重写call()方法,返回per-sample损失;避免直接用普通函数或tf.py_function(不可导);不可访问模型内部状态,需通过参数传入或model.add_loss();调试用tf.print,注意dtype一致。

自定义损失函数必须继承 tf.keras.losses.Loss 或实现 callable 接口
直接写一个普通函数(比如 def my_loss(y_true, y_pred))虽然能跑通,但会丢失梯度追踪、batch 维度对齐、reduction 行为等关键能力,尤其在多 GPU、TPU 或 tf.function 装饰下容易静默失败或梯度为 0。
推荐做法是子类化 tf.keras.losses.Loss,并重写 call() 方法。这样能确保与 Keras 训练循环完全兼容:
class WeightedMSE(tf.keras.losses.Loss):
def __init__(self, weights=None, name="weighted_mse"):
super().__init__(name=name)
self.weights = weights # 可为 tensor 或 None
<pre class="brush:php;toolbar:false;">def call(self, y_true, y_pred):
squared_error = tf.square(y_true - y_pred)
if self.weights is not None:
squared_error = squared_error * self.weights
return tf.reduce_mean(squared_error, axis=-1)
注意:call() 返回的是 per-sample 损失(shape 为 [batch_size]),Keras 会在内部按 reduction 参数(默认 tf.keras.losses.Reduction.AUTO)做聚合,不要提前用 tf.reduce_mean(..., axis=[0,1]) 把 batch 和 feature 维度全压掉。
使用 tf.py_function 包裹非 TF 原生逻辑要格外小心
如果你的损失涉及 NumPy 运算、文件读取、外部库调用(如 scipy.optimize),必须用 tf.py_function 封装,否则会报 TypeError: Cannot convert a symbolic Tensor to numpy 或在 @tf.function 下崩溃。
-
tf.py_function中的代码不参与图构建,无法被自动微分 —— 你必须手动提供grad参数或改用可导近似 - 输入 tensor 会被转成 numpy array,输出也需显式转回 tensor,且 dtype 必须匹配(例如返回
np.float32,不能是float64) - 性能较差,避免在每步训练中调用 IO 或高开销计算
示例(仅用于演示,实际慎用):
def numpy_based_loss(y_true, y_pred):
# 注意:这里 y_true/y_pred 是 numpy 数组
diff = np.abs(y_true - y_pred)
return np.log1p(diff).astype(np.float32)
<p>def tf_numpy_loss(y_true, y_pred):
return tf.py_function(
func=numpy_based_loss,
inp=[y_true, y_pred],
Tout=tf.float32
)
</p>
这种写法绕过了自动求导,训练会失败 —— 真要用,得额外注册梯度函数,复杂度陡增。
让 macOS 自动化像说话一样简单 —— 用代码控制你的 Mac,无需学习 AppleScript 系统要求 macOS 14+ (Sonoma) Python 3.9+ 需要开启:辅助功能 + 屏幕录制权限
损失函数里访问模型状态(如 dropout mask、中间层输出)不可行
损失函数接收的只是 y_true 和 y_pred,它不知道模型结构、训练模式(training=True/False)、也不持有任何层引用。想在损失中引入 KL 散度、对比学习正负对、或基于某层激活的正则项,必须把所需张量作为额外输入传进来,或者改用 model.add_loss() 方式注入。
- 错误做法:在损失函数里写
model.layers[2].output——model不在作用域内 - 正确做法 1:在
call()中接受第三个参数,由自定义训练循环传入中间特征 - 正确做法 2:在模型构建时用
model.add_loss(lambda: tf.keras.losses.kl_divergence(z_mean, z_log_var)),该 loss 会在总 loss 中自动累加
后者更干净,但只适用于无 y_true 依赖的 unsupervised 正则项;前者灵活但要求你控制整个训练 step。
调试时打印值会中断图执行,优先用 tf.print 而非 print
在 call() 里写 print(y_pred.shape) 看不出结果,因为 Eager 模式下可能不触发,Graph 模式下直接被忽略。要用 tf.print,它能在图中插入日志节点:
def call(self, y_true, y_pred):
tf.print("y_true shape:", tf.shape(y_true), "y_pred shape:", tf.shape(y_pred))
# ... compute loss
注意:tf.print 默认输出到 stderr,不阻塞执行,但频繁调用会影响性能;上线前应移除。另外,它不能打印 Python 对象(如 list、dict),只能打印 tensor 或可转为 tensor 的值。
最易被忽略的一点:自定义损失函数的 dtype 必须与模型输出一致。如果模型最后一层是 tf.float64,而你的损失里用了 tf.float32 常量(比如 1e-8),会导致隐式类型转换失败或梯度消失。统一用 tf.keras.mixed_precision.global_policy().compute_dtype 查询当前策略,或显式 cast。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!










