You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

GAN训练时生成器损失归零却输出随机图像的问题排查

GAN训练异常排查:生成器损失收敛至0但输出随机图像

问题背景

使用128x128尺寸、归一化到[-1,1]区间的图像数据集训练GAN,已训练300个epoch,但生成器始终输出随机图像。单独训练判别器时模型可正常做出预测,但联合训练时判别器损失先下降后趋于低位,生成器损失收敛至0。

模型代码

判别器代码

def make_discriminator_model():
    model = tf.keras.Sequential()
    model.add(layers.Input((128, 128, 3)))
    model.add(layers.Conv2D(32, (4,4), strides=(1, 1), padding='same', activation=layers.LeakyReLU(alpha=0.2)))
    model.add(layers.BatchNormalization())
    model.add(layers.Dropout(0.4))
    model.add(layers.Conv2D(64, (4,4), strides=(2, 2), padding='same', activation=layers.LeakyReLU(alpha=0.2)))
    model.add(layers.Dropout(0.4))
    model.add(layers.Conv2D(32, (4,4), strides=(1, 1), padding='same', activation=layers.LeakyReLU(alpha=0.2)))
    model.add(layers.BatchNormalization())
    model.add(layers.Dropout(0.4))
    model.add(layers.Conv2D(128, (4,4), strides=(2, 2), padding='same', activation=layers.LeakyReLU(alpha=0.2)))
    model.add(layers.Flatten())
    model.add(layers.Dropout(0.4))
    model.add(layers.Dense(1,activation='sigmoid'))
    return model

discriminator = make_discriminator_model()

生成器代码

def make_generator_model():
    model = tf.keras.Sequential()
    model.add(layers.Dense(16*16*128, input_dim=512, activation=layers.LeakyReLU(alpha=0.2)))
    model.add(layers.BatchNormalization())
    model.add(layers.Reshape((16, 16, 128)))
    model.add(layers.Conv2DTranspose(128, (4,4), strides=(2, 2), padding='same', activation=layers.LeakyReLU(alpha=0.2)))
    model.add(layers.BatchNormalization())
    model.add(layers.Conv2DTranspose(128, (4,4), strides=(2, 2), padding='same', activation=layers.LeakyReLU(alpha=0.2)))
    model.add(layers.BatchNormalization())
    model.add(layers.Conv2DTranspose(128, (4,4), strides=(2, 2), padding='same', activation=layers.LeakyReLU(alpha=0.2)))
    model.add(layers.Conv2D(3, (4,4), strides=(1, 1), padding='same', activation='tanh'))
    return model

generator = make_generator_model()

GAN训练代码

class GAN(tf.keras.models.Model):
    def __init__(self, generator, discriminator, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.generator = generator
        self.discriminator = discriminator

    def compile(self, g_opt, d_opt, g_loss, d_loss, *args, **kwargs):
        super().compile(*args, **kwargs)
        self.g_opt = g_opt
        self.d_opt = d_opt
        self.g_loss = g_loss
        self.d_loss = d_loss

    def train_step(self, batch):
        real_images = batch
        fake_images = self.generator(tf.random.normal((128, 512, 1)), training=False)

        # 训练判别器
        with tf.GradientTape() as d_tape:
            yhat_real = self.discriminator(real_images, training=True)
            yhat_fake = self.discriminator(fake_images, training=True)
            yhat_realfake = tf.concat([yhat_real, yhat_fake], axis=0)

            y_realfake = tf.concat([tf.ones_like(yhat_real), tf.zeros_like(yhat_fake)], axis=0)
            noise_real = -0.15*tf.random.uniform(tf.shape(yhat_real))
            noise_fake = 0.15*tf.random.uniform(tf.shape(yhat_fake))
            y_realfake += tf.concat([noise_real, noise_fake], axis=0)

            total_d_loss = self.d_loss(y_realfake, yhat_realfake)

        dgrad = d_tape.gradient(total_d_loss, self.discriminator.trainable_variables)
        self.d_opt.apply_gradients(zip(dgrad, self.discriminator.trainable_variables))

        # 训练生成器
        with tf.GradientTape() as g_tape:
            gen_images = self.generator(tf.random.normal((128,512,1)), training=True)
            predicted_labels = self.discriminator(gen_images, training=False)
            total_g_loss = self.g_loss(tf.ones_like(predicted_labels), predicted_labels)

        ggrad = g_tape.gradient(total_g_loss, self.generator.trainable_variables)
        self.g_opt.apply_gradients(zip(ggrad, self.generator.trainable_variables))

        return {"d_loss":total_d_loss, "g_loss":total_g_loss}

g_opt = tf.keras.optimizers.Adam(learning_rate=0.0001)
d_opt = tf.keras.optimizers.Adam(learning_rate=0.00001)
g_loss = tf.keras.losses.BinaryCrossentropy()
d_loss = tf.keras.losses.BinaryCrossentropy()
gan = GAN(generator, discriminator)
gan.compile(g_opt, d_opt, g_loss, d_loss)

问题排查与解决方案

1. 核心错误:生成器输入维度不匹配

生成器定义时使用input_dim=512,期望输入形状为(batch_size, 512),但训练时传入的噪声是tf.random.normal((128, 512, 1))(形状为(128,512,1))。这会导致第一层Dense层输出形状错误,后续Reshape和转置卷积完全偏离预期,生成器根本无法学到有效特征,必然输出随机图像。

修复方式:将训练中的噪声输入改为tf.random.normal((128, 512)),去掉多余的最后一维。


针对你的疑问解答

(1)total_g_loss损失函数是否有问题?

损失函数的逻辑本身没问题,但损失收敛至0不代表生成器有效:

  • 如果判别器完全失效(比如无论输入什么都输出1),生成器损失会直接降到0,但这是无效收敛;
  • 你的情况中,输入维度错误导致生成器梯度无法正确传播,损失数值的收敛没有实际意义。

优化建议:改用非饱和损失,即判别器最后一层去掉sigmoid,输出logits,同时损失函数设置from_logits=True:

# 判别器最后一层修改
model.add(layers.Dense(1))  # 去掉sigmoid

# 损失函数修改
g_loss = tf.keras.losses.BinaryCrossentropy(from_logits=True)
d_loss = tf.keras.losses.BinaryCrossentropy(from_logits=True)

这种方式能缓解梯度消失问题,让训练更稳定。

(2)生成器的隐藏层是否合适?

生成器的整体上采样逻辑(从16x16→32x32→64x64→128x128)是合理的,但存在几个可优化点:

  • BatchNormalization顺序错误:当前代码中是「线性层/卷积层 → 激活函数 → BatchNorm」,正确顺序应为「线性层/卷积层 → BatchNorm → 激活函数」。BatchNorm需要对线性输出做归一化,激活后的数值分布不适合做归一化,会破坏特征分布;
  • 通道数单一:所有转置卷积层都用128通道,可尝试逐步减少通道数(比如256→128→64),让特征提取更有层次;
  • 激活函数调整:可以在最后一层转置卷积后用ReLU,再通过tanh输出,避免梯度饱和。

其他调试建议

  1. 修正BatchNorm顺序:生成器和判别器的所有层都改为「线性/卷积层 → BatchNorm → 激活函数」,判别器的输入层可跳过BatchNorm(避免真实与假样本分布混淆);
  2. 平衡学习率:当前判别器学习率(1e-5)远小于生成器(1e-4),可能导致生成器更新过快,判别器无法跟上。可尝试将两者设为相同(比如1e-4),或让判别器学习率略高于生成器;
  3. 监控判别器准确率:除了损失,记录判别器对真实样本和假样本的分类准确率。如果真实样本准确率接近100%、假样本准确率接近0,说明判别器太强,可尝试:
    • 减少判别器的通道数或层数;
    • 对判别器的梯度进行裁剪;
    • 每训练2次判别器再训练1次生成器;
  4. 降低Dropout比例:当前Dropout比例(0.4)过高,可能导致特征丢失,尤其是生成器,可尝试降到0.2或暂时去掉Dropout;
  5. 调整批量大小:当前批量为128,可尝试更小的批量(比如64),带来更稳定的梯度;
  6. 可视化中间特征:定期查看生成器各层的输出特征图,确认是否有有效特征生成;
  7. 权重初始化:手动设置模型权重初始化(比如He初始化),避免随机初始化导致的训练停滞。

内容的提问来源于stack exchange,提问作者vassyesboy

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.05 05:05:55