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

GAN每次运行均出现模式崩溃、生成重复人脸输出问题排查

核心错误定位

代码存在5处硬逻辑错误,直接引发模式崩溃,和训练技巧无关:

  • 真假样本标签完全写反
    训练判别器时,生成的假样本被标记为真(标签值1),真实训练样本被标记为假(标签值0),判别器彻底学反真假判定规则;训练生成器时,目标标签设为0(假),等于要求生成器输出判别器判定为假的图像,训练逻辑完全颠倒,生成器只需输出固定类别的图像就能适配错误的损失导向,直接触发模式崩溃。
  • 真实样本采样逻辑失效
    采样指针start初始化为0后从未更新,每个训练步读取的真实样本永远是数据集最靠前的batchSize张图,判别器接触到的真实样本多样性为0,只会引导生成器输出和这部分固定样本高度相似的结果。
  • 生成器上采样通道顺序错误
    转置卷积上采样是从低分辨率小特征图逐步放大到目标分辨率,通道数应随分辨率升高逐层递减。现有代码第一层转置卷积仅输出64通道,后续逐层升到512通道,高分辨率层堆积大量冗余高维特征,模型极易收敛到固定输出模式。
  • 训练轮次参数冲突
    全局定义EPOCHS=30000,训练循环直接调用全局变量,train()函数传入的轮次参数完全不生效,实际迭代步数和预期不符,很容易在训练失衡状态下提前进入模式崩溃。
  • 冗余逻辑引发框架状态异常
    代码定义了RMSprop优化器但从未调用,判别器、GAN网络构建时重复设置discriminator.trainable = False,极易触发Keras的可训练标志同步bug,导致判别器该更新时不更新、该冻结时被更新,训练彻底失衡。
修复方案

按以下顺序修改即可解决重复输出问题:

  1. 修正标签逻辑:训练判别器时真实样本标签为1、生成样本标签为0,叠加0.05幅度的标签平滑;训练生成器时目标标签为1,即引导生成器输出让判别器判定为真的样本。
  2. 修复真实样本采样逻辑:每个训练步移动采样指针,遍历完整个数据集后重置指针到0,保证所有真实样本都能参与训练。
  3. 调整生成器转置卷积通道顺序:从初始2*2分辨率特征图开始,通道数随上采样逐层从512降到32,最后输出3通道RGB图像,匹配DCGAN的标准结构。
  4. 清理冗余逻辑:删除未使用的RMSprop优化器定义、重复的可训练标志设置,训练循环使用函数传入的轮次、批大小参数,避免全局变量冲突。
  5. 调整判别器训练流程:每步训练取半批真实样本、半批生成样本分别计算损失,避免样本拼接顺序错误引发的标签错位。
修正后关键代码
random_dim = 100
PREVIEW_ROWS = 6
PREVIEW_COLS = 6
IMAGE_SIZE = 64
CHANNELS = 3

def get_optimizer():
    return Adam(lr=0.0002, beta_1=0.5, beta_2=0.999)

def get_generator():
  generator = Sequential()
  generator.add(Dense(512 * 2 * 2, input_dim=random_dim))
  generator.add(LeakyReLU(alpha=0.2))
  generator.add(Reshape((2, 2, 512)))

  # 通道数随上采样逐层递减
  generator.add(Conv2DTranspose(512, (3, 3), strides=(2,2), padding='same'))
  generator.add(LeakyReLU(alpha=0.2))
  generator.add(BatchNormalization())

  generator.add(Conv2DTranspose(256, (3, 3), strides=(2,2), padding='same'))
  generator.add(LeakyReLU(alpha=0.2))
  generator.add(BatchNormalization())

  generator.add(Conv2DTranspose(128, (3, 3), strides=(2,2), padding='same'))
  generator.add(LeakyReLU(alpha=0.2))
  generator.add(BatchNormalization())

  generator.add(Conv2DTranspose(64, (3, 3), strides=(2,2), padding='same'))
  generator.add(LeakyReLU(alpha=0.2))
  generator.add(BatchNormalization())

  generator.add(Conv2DTranspose(32, (3, 3), strides=(2,2), padding='same'))
  generator.add(LeakyReLU(alpha=0.2))
  generator.add(BatchNormalization())
  
  generator.add(Conv2D(3, (3,3), activation='tanh', padding='same'))

  input_layer = Input(shape=(random_dim,))
  generated_image = generator(input_layer)
  generator.summary()
  return Model(input_layer, generated_image)

def get_discriminator():
  disc_input = Input(shape=(IMAGE_SIZE, IMAGE_SIZE, 3))
  discriminator = Sequential()
  discriminator.add(GaussianNoise(0.1))
  
  discriminator.add(Conv2D(64, (3, 3), padding='same'))
  discriminator.add(LeakyReLU(alpha=0.2))
  discriminator.add(GaussianNoise(0.1))
  discriminator.add(AveragePooling2D())

  discriminator.add(Conv2D(128, (3, 3), padding='same'))
  discriminator.add(LeakyReLU(alpha=0.2))
  discriminator.add(GaussianNoise(0.1))
  discriminator.add(AveragePooling2D())

  discriminator.add(Conv2D(256, (3, 3), padding='same'))
  discriminator.add(LeakyReLU(alpha=0.2))
  discriminator.add(GaussianNoise(0.1))
  discriminator.add(AveragePooling2D())

  discriminator.add(Conv2D(512, (3, 3), padding='same'))
  discriminator.add(LeakyReLU(alpha=0.2))
  discriminator.add(GaussianNoise(0.1))
  discriminator.add(AveragePooling2D())
  
  discriminator.add(Flatten())
  discriminator.add(LeakyReLU(alpha=0.2))
  discriminator.add(Dropout(0.5))
  discriminator.add(Dense(1, activation='sigmoid'))

  discriminator_model = Model(disc_input, discriminator(disc_input))
  discriminator_model.compile(loss='binary_crossentropy', optimizer=get_optimizer(), metrics=["accuracy"])
  return discriminator_model

def get_gan_network(discriminator, random_dim, generator, optimizer):
  discriminator.trainable = False
  gan_input = Input(shape=(random_dim,))
  x = generator(gan_input)
  gan_output = discriminator(x)
  gan = Model(inputs=gan_input, outputs=gan_output)
  gan.compile(loss='binary_crossentropy', optimizer=optimizer, metrics=["accuracy"])
  return gan

X_train = training_data
fixed_noise = np.random.normal(0, 1, (PREVIEW_ROWS * PREVIEW_COLS, 100))

def train(epochs=8000, batchSize=32):
  halfBatch = batchSize // 2
  print(f'训练集大小: {X_train.shape[0]}, 训练轮次: {epochs}, 批大小: {batchSize}')

  adam = get_optimizer()
  generator = get_generator()
  discriminator = get_discriminator()
  gan = get_gan_network(discriminator, random_dim, generator, adam)

  d_losses = []
  g_losses = []
  start=0

  for epoch in range(epochs):
    # 采样半批真实样本,移动采样指针
    end = start + halfBatch
    real_imgs = X_train[start:end]
    start = end if end + halfBatch <= X_train.shape[0] else 0

    # 生成半批假样本
    latent_vectors = np.random.normal(size=(halfBatch, random_dim))
    fake_imgs = generator.predict(latent_vectors, verbose=0)

    # 构造带平滑的标签
    real_labels = np.ones((halfBatch, 1)) - 0.05 * np.random.random((halfBatch, 1))
    fake_labels = np.zeros((halfBatch, 1)) + 0.05 * np.random.random((halfBatch, 1))

    # 训练判别器
    d_loss_real = discriminator.train_on_batch(real_imgs, real_labels)
    d_loss_fake = discriminator.train_on_batch(fake_imgs, fake_labels)
    d_loss = 0.5 * np.add(d_loss_real, d_loss_fake)
    d_losses.append(d_loss)

    # 训练生成器
    latent_vectors = np.random.normal(size=(batchSize, random_dim))
    valid_labels = np.ones((batchSize, 1))
    g_loss = gan.train_on_batch(latent_vectors, valid_labels)
    g_losses.append(g_loss)

    if epoch % 50 == 49:  
      save_images(epoch, fixed_noise, generator)

if __name__ == '__main__':
    train()

可选优化:如果修改后仍有轻微模式重复,可将判别器学习率调整为生成器的1/2,或采用每训练2次判别器、训练1次生成器的更新频率,进一步稳定训练过程。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.28 08:27:19