Keras自定义GAN梯度报错:未提供变量梯度的技术求助
问题:非标准GAN训练时出现梯度为空报错
本人有Keras神经网络开发经验,正在构建一款非标准GAN——用预训练人脸图像质量判别器替代可训练判别器,实现48×48到96×96的图像超分辨率提升。之前用常规GAN做简单插值导致画质损失,所以改用预训练判别器方案,但训练时一直报ValueError: No gradients provided for any variable,报错指向generator_optimizer.apply_gradients(...)这一行,求解决办法。
训练步骤函数
@tf.function def train_step(images_x, images_y): with tf.GradientTape() as gen_tape: generated_images = generator(images_x, training=True).numpy() # 集成预训练网络,返回形状为(batch_size,1)的numpy数组,值范围0-1 fake_output = K.constant(face_detector.estimate(generated_images)) # K = tensorflow backend gen_loss = generator_loss(fake_output) gradients_of_generator = gen_tape.gradient(gen_loss, generator.trainable_variables) generator_optimizer.apply_gradients(zip(gradients_of_generator, generator.trainable_variables)) return gen_loss
通用训练函数
def train(dataset_x, dataset_y, epochs): history = [] MAX_PRINT_LABEL = 10 th = BUFFER_SIZE // (BATCH_SIZE * MAX_PRINT_LABEL) for epoch in range(1, epochs + 1): print(f'{epoch}/{EPOCHS}: ', end='') start = time.time() n = 0 gen_loss_epoch = 0 l = len(dataset_x) for o in range(l): tf.config.run_functions_eagerly(True) # 怀疑问题出在这里,但不用急切执行train_step就跑不通 gen_loss = train_step(dataset_x[o], dataset_y[o]) tf.config.run_functions_eagerly(False) print(gen_loss, disc_loss) gen_loss_epoch += K.mean(gen_loss) if (n % th == 0): print('=', end='') n += 1 generator.save("generator.h5") history += [gen_loss_epoch / n] return history
生成器结构
cross_entropy = tf.keras.losses.BinaryCrossentropy(from_logits=True) hidden_dim = 2 def dropout_and_batch(): return Dropout(0.3)(BatchNormalization()) img_height = 96 img_width = 96 img_channels = 3 latent_dim = 2 input_img = Input(shape=(48, 48, 3)) x = Conv2D(32, 3, padding='same', activation='relu')(input_img) x = Conv2D(32, 3, padding='same', activation='relu')(x) x = Conv2D(32, 3, padding='same', activation='relu')(x) x = Flatten()(input_img) z_mean = Dense(latent_dim)(x) z_log_var = Dense(latent_dim)(x) @keras.saving.register_keras_serializable() def sampling(args): z_mean, z_log_var = args epsilon = tf.random.normal([batch_size, latent_dim], 0, 1, tf.float32) return z_mean + tf.exp(0.5 * z_log_var) * epsilon z = Lambda(sampling, output_shape=(latent_dim,))([z_mean, z_log_var]) decoder_input = Input(shape=(latent_dim,)) x = Dense(img_width*img_height*3, activation='relu')(decoder_input) x = Reshape((img_width, img_height, 3))(x) x = Conv2D(32, 3, padding='same', activation='relu')(x) x = Conv2D(32, 3, padding='same', activation='relu')(x) decoded = Conv2D(3, 3, padding='same', activation='sigmoid')(x) encoder = Model(input_img, [z_mean, z_log_var, z]) decoder = Model(decoder_input, decoded) generator = Model(input_img, decoder(encoder(input_img)[2]))
自定义损失函数
def generator_loss(fake_output): loss = cross_entropy(tf.ones_like(fake_output), fake_output) return loss
数据集加载代码
for path in glob("faces/*.jpg"): img = Image.open(path) y_train.append(np.asarray(img.resize((96, 96)))) if y_train[-1].shape != (96, 96, 3): del y_train[-1] continue x_train.append(np.asarray(img.resize((48, 48)))) x_train = np.asarray(x_train)/255 y_train = np.asarray(y_train)/255 BUFFER_SIZE = x_train.shape[0] BATCH_SIZE = 100 BUFFER_SIZE = BUFFER_SIZE // BATCH_SIZE * BATCH_SIZE x_train = x_train[:BUFFER_SIZE] y_train = y_train[:BUFFER_SIZE] print(x_train.shape, y_train.shape) train_dataset_x = list(tf.data.Dataset.from_tensor_slices(x_train).shuffle(BUFFER_SIZE).batch(BATCH_SIZE)) train_dataset_y = list(tf.data.Dataset.from_tensor_slices(y_train).shuffle(BUFFER_SIZE).batch(BATCH_SIZE))
问题原因与解决方案
核心原因
报错本质是TensorFlow的自动微分计算链被打断,导致梯度无法回溯到生成器的可训练变量,具体问题点:
.numpy()转换切断梯度链:generated_images = generator(...).numpy()将Tensor转为numpy数组,破坏了计算图的微分追踪,后续所有操作都无法关联到生成器变量。K.constant()固化输出:把预训练模型的输出转为常量Tensor,同样无法参与梯度传导。- 频繁切换急切执行模式:训练循环中反复开关
tf.config.run_functions_eagerly(),打乱了计算图的稳定构建。
修正步骤
1. 修复train_step,保留Tensor流
去掉.numpy()和K.constant(),确保所有操作在TensorFlow计算图内进行。如果预训练模型只能接受numpy输入,先转换但要立刻转回Tensor:
@tf.function def train_step(images_x, images_y): with tf.GradientTape() as gen_tape: # 保留Tensor,不转numpy generated_images = generator(images_x, training=True) # 若预训练模型仅支持numpy,先临时转换,再转回Tensor fake_output_np = face_detector.estimate(generated_images.numpy()) fake_output = tf.convert_to_tensor(fake_output_np, dtype=tf.float32) # 可选:添加像素级MSE损失,提升训练稳定性 mse_loss = tf.keras.losses.MeanSquaredError()(images_y, generated_images) gen_loss = generator_loss(fake_output) + 0.01 * mse_loss gradients_of_generator = gen_tape.gradient(gen_loss, generator.trainable_variables) # 增加空梯度判断,避免报错 if gradients_of_generator: generator_optimizer.apply_gradients(zip(gradients_of_generator, generator.trainable_variables)) return gen_loss
如果预训练判别器是Keras模型,直接用Tensor输入:fake_output = face_detector(generated_images, training=False),完全保留梯度链。
2. 移除急切执行的频繁切换
删除训练循环中的tf.config.run_functions_eagerly()切换,要么全程用图模式(@tf.function),要么在训练函数开头设置一次急切执行:
def train(dataset_x, dataset_y, epochs): history = [] MAX_PRINT_LABEL = 10 th = BUFFER_SIZE // (BATCH_SIZE * MAX_PRINT_LABEL) # 可选:若仍需急切执行,仅设置一次 # tf.config.run_functions_eagerly(True) for epoch in range(1, epochs + 1): print(f'{epoch}/{EPOCHS}: ', end='') start = time.time() n = 0 gen_loss_epoch = 0 l = len(dataset_x) for o in range(l): gen_loss = train_step(dataset_x[o], dataset_y[o]) # 删除未定义的disc_loss打印 print(gen_loss) gen_loss_epoch += tf.reduce_mean(gen_loss) if (n % th == 0): print('=', end='') n += 1 generator.save("generator.h5") history.append(gen_loss_epoch / n) print(f' | 耗时: {time.time()-start:.2f}s') return history
3. 验证生成器可训练变量
打印generator.trainable_variables确认变量存在,若有不可训练层,检查是否是故意设置的:
print("生成器可训练变量数量:", len(generator.trainable_variables))
内容的提问来源于stack exchange,提问作者Freddy
相关产品推荐
相关产品推荐

