Keras基于混淆矩阵编写自定义损失函数运行报错求解
问题背景
在Keras中实现二分类任务的自定义损失函数,优化目标为最小化加权错误成本:10 * FalsePositive(假阳性) + 500 * FalseNegative(假阴性),先后两次实现均触发运行报错。
第一版实现(基于sklearn混淆矩阵+pandas)
代码如下:
def custom_loss_func(y_train, y_pred): cm=metrics.confusion_matrix(y_train,y_pred) cm1 = pd.DataFrame(cm.reshape((1,4)), columns=['TN', 'FP', 'FN', 'TP']) cm1 = cm1.fillna(0) TC= 10*cm1.FP + 500*cm1.FN return float (TC) # Compiling the ANN ann.compile(optimizer = 'adam', loss = my_loss_fn, metrics = ['accuracy']) # Fitting the ANN to the Training set ann.fit(x_train,y_train,batch_size=32,epochs = 50, class_weight= class_weights)
触发报错:
NotImplementedError: Cannot convert a symbolic tf.Tensor (my_loss_fn/strided_slice:0) to a numpy array. This error may indicate that you're trying to pass a Tensor to a NumPy call, which is not supported.
第二版实现(基于TF原生指标)
代码如下:
def my_loss_fn(y_train, y_pred): FP = tf.keras.metrics.FalsePositives() FP.update_state(y_train, y_pred) FP=m.result().numpy() FN = tf.keras.metrics.FalseNegatives() FN.update_state(y_train, y_pred) FN=n.result().numpy() # Converted as Keras Tensors FP = tf.Variable(m) FN = tf.Variable(n) loss = 500*FN + 10*FP return loss
触发报错:
ValueError(f"No gradients provided for any variable: {variable}. " f"Provided `grads_and_vars` is {grads_and_vars}.") if vars_with_empty_grads: logging.warning( ("Gradients do not exist for variables %s when minimizing the loss. " "If you're using `model.compile()`, did you forget to provide a `loss`" "argument?"), ([v.name for v in vars_with_empty_grads]))
错误原因
- 第一版核心问题:TensorFlow/Keras训练过程中,传入损失函数的
y_train和y_pred是动态图模式下的符号张量,不支持直接传入sklearn、pandas等基于numpy实现的非TF原生函数,也不能在图构建阶段将符号张量转为numpy数组,因此触发类型转换错误。 - 第二版核心问题:虽然调用了TF自带的分类指标接口,但代码中执行
.numpy()操作将张量转为普通数值,又新建独立tf.Variable存储结果,完全切断了损失值和模型可训练参数之间的梯度传播链路,反向传播时无法拿到参数对应的梯度,因此触发无梯度报错;同时代码本身存在变量名引用错误(m/n未定义)。
正确实现方法
自定义Keras损失必须全程使用TensorFlow原生可微算子,保证从模型输出到损失值的整个计算路径可导,不能中途插入numpy转换、非TF计算逻辑。
针对加权FP/FN的损失需求,不需要调用混淆矩阵或指标接口,直接基于真实标签和预测概率计算软计数即可(硬阈值转0/1会导致梯度断裂,用概率值计算可保留完整梯度):
import tensorflow as tf def weighted_cost_loss(y_true, y_pred): # 预测概率做数值截断,避免出现log(0)类数值问题,不影响梯度传播 y_pred = tf.clip_by_value(y_pred, 1e-7, 1 - 1e-7) # 假阳性软计数:真实标签为0,预测为正类的概率和 fp = tf.reduce_sum((1 - y_true) * y_pred) # 假阴性软计数:真实标签为1,预测为负类的概率和 fn = tf.reduce_sum(y_true * (1 - y_pred)) # 按权重计算总损失 loss = 10 * fp + 500 * fn # 对batch做平均,适配batch训练逻辑,避免损失值随batch大小波动 return loss / tf.cast(tf.shape(y_true)[0], tf.float32) # 模型编译,注意:损失已经内置了FN的高权重,不需要额外传入class_weight避免重复加权 ann.compile(optimizer='adam', loss=weighted_cost_loss, metrics=['accuracy', tf.keras.metrics.FalsePositives(), tf.keras.metrics.FalseNegatives()]) # 模型训练 ann.fit(x_train, y_train, batch_size=32, epochs=50, validation_data=(x_val, y_val))
注意:如果模型最后一层没有加sigmoid激活,需要在损失函数内先对logits做sigmoid转换再计算,或者调整计算逻辑适配logits输入。
内容的提问来源于stack exchange,提问作者Pytno
相关产品推荐
相关产品推荐

