使用自定义fairnessLoss时出现No gradients provided报错
批量梯度下降结合自定义公平性损失函数训练TensorFlow模型时梯度缺失问题
问题现象
使用批量梯度下降(batch gradient descent)结合自定义fairnessLoss函数训练模型时,第一个epoch后出现以下报错:
optimizer.apply_gradients(zip(grads, model_2.trainable_weights)) No gradients provided for any variable: (['dense_22/kernel:0', 'dense_22/bias:0', 'dense_23/kernel:0', 'dense_23/bias:0', 'dense_24/kernel:0', 'dense_24/bias:0'],).
但使用BinaryCrossEntropy损失函数时代码可正常运行。
问题根源
自定义损失函数fairnessLoss存在两个破坏TensorFlow自动微分机制的问题:
- 使用Python原生for循环遍历样本,无法被TensorFlow的计算图追踪,导致梯度无法反向传播。
- 使用
tf.Variable存储中间计算结果(如cse_min、n_min等),这类变量的assign_add操作不属于计算图的正向传播流程,梯度无法关联到模型参数。
TensorFlow的自动微分依赖于可追踪的张量运算,必须避免Python原生循环和独立变量的赋值操作,改用TensorFlow的向量化运算实现逻辑。
修复后的完整代码
# 导入必要库 import numpy as np import tensorflow as tf from tensorflow import keras from sklearn.model_selection import train_test_split from sklearn.preprocessing import MinMaxScaler import matplotlib.pyplot as plt lambda_par = tf.constant(0.5) def fairnessLoss(y_true, y_pred): # 分离少数类(y_true[i][0]==1)和多数类样本 mask_min = tf.equal(y_true[:, 0], 1.0) mask_maj = tf.logical_not(mask_min) # 计算少数类的交叉熵和样本数 cse_min = tf.reduce_sum(tf.math.log(y_pred[mask_min])) n_min = tf.cast(tf.reduce_sum(tf.cast(mask_min, tf.float32)), tf.float32) # 计算多数类的交叉熵和样本数 cse_maj = tf.reduce_sum(tf.math.log(1 - y_pred[mask_maj])) n_maj = tf.cast(tf.reduce_sum(tf.cast(mask_maj, tf.float32)), tf.float32) # 计算公平性误差项 tem1 = tf.divide(cse_min, n_min) tem2 = tf.divide(cse_maj, n_maj) fe = tf.square(tem1 - tem2) # 总损失:交叉熵总和 + lambda*公平性误差 total_loss = cse_min + cse_maj + lambda_par * fe return total_loss # 定义模型 model = tf.keras.Sequential([ tf.keras.layers.Dense(8, activation='sigmoid'), tf.keras.layers.Dense(8, activation='sigmoid'), tf.keras.layers.Dense(1, activation='sigmoid') ]) # 假设train_X和train_y已提前定义并预处理 batch_size = len(train_X) train_yy = tf.expand_dims(train_y, axis=1) # 替换手动列表转换为TensorFlow原生操作 train_dataset = tf.data.Dataset.from_tensor_slices((train_X, train_yy)) train_dataset = train_dataset.shuffle(buffer_size=1024).batch(batch_size) # 初始化评估指标 train_acc_metric = keras.metrics.BinaryAccuracy() epochs = 500 optimizer = keras.optimizers.Adam() losses = [] accuracy = [] for epoch in range(epochs): print(f"\nStart of epoch {epoch}") epoch_loss_avg = tf.keras.metrics.Mean() for step, (x_batch_train, y_batch_train) in enumerate(train_dataset): with tf.GradientTape() as tape: logits = model(x_batch_train, training=True) loss_value = fairnessLoss(y_batch_train, logits) # 计算梯度并更新参数 grads = tape.gradient(loss_value, model.trainable_weights) optimizer.apply_gradients(zip(grads, model.trainable_weights)) # 更新指标 epoch_loss_avg.update_state(loss_value) train_acc_metric.update_state(y_batch_train, logits) losses.append(epoch_loss_avg.result().numpy()) accuracy.append(train_acc_metric.result().numpy()) if step % 200 == 0: print(f"Training loss (for one batch) at step {step}: {float(loss_value):.4f}") print(f"Seen so far: {(step + 1) * batch_size} samples") print(f"Training accuracy: {train_acc_metric.result().numpy():.4f}") train_acc_metric.reset_states()
关键修复点
- 替换Python循环为TensorFlow向量化操作:使用
tf.equal、tf.logical_not生成掩码,通过张量索引直接筛选少数类和多数类样本,避免循环破坏计算图追踪。 - 移除
tf.Variable中间变量:全部使用tf.reduce_sum等张量运算直接计算结果,确保所有操作都在计算图中,梯度可正常反向传播。 - 简化标签转换:用
tf.expand_dims替代手动列表转换,保持张量操作一致性。 - 将
lambda_par改为tf.constant:若无需动态调整该参数,使用常量更符合计算图要求;若需动态调整,可改为可训练变量并添加到模型的可训练权重中。
内容的提问来源于stack exchange,提问作者KESHAV SINGHAL
相关产品推荐
相关产品推荐

