GAN每次运行均出现模式崩溃、生成重复人脸输出问题排查
核心错误定位
代码存在5处硬逻辑错误,直接引发模式崩溃,和训练技巧无关:
- 真假样本标签完全写反
训练判别器时,生成的假样本被标记为真(标签值1),真实训练样本被标记为假(标签值0),判别器彻底学反真假判定规则;训练生成器时,目标标签设为0(假),等于要求生成器输出判别器判定为假的图像,训练逻辑完全颠倒,生成器只需输出固定类别的图像就能适配错误的损失导向,直接触发模式崩溃。 - 真实样本采样逻辑失效
采样指针start初始化为0后从未更新,每个训练步读取的真实样本永远是数据集最靠前的batchSize张图,判别器接触到的真实样本多样性为0,只会引导生成器输出和这部分固定样本高度相似的结果。 - 生成器上采样通道顺序错误
转置卷积上采样是从低分辨率小特征图逐步放大到目标分辨率,通道数应随分辨率升高逐层递减。现有代码第一层转置卷积仅输出64通道,后续逐层升到512通道,高分辨率层堆积大量冗余高维特征,模型极易收敛到固定输出模式。 - 训练轮次参数冲突
全局定义EPOCHS=30000,训练循环直接调用全局变量,train()函数传入的轮次参数完全不生效,实际迭代步数和预期不符,很容易在训练失衡状态下提前进入模式崩溃。 - 冗余逻辑引发框架状态异常
代码定义了RMSprop优化器但从未调用,判别器、GAN网络构建时重复设置discriminator.trainable = False,极易触发Keras的可训练标志同步bug,导致判别器该更新时不更新、该冻结时被更新,训练彻底失衡。
修复方案
按以下顺序修改即可解决重复输出问题:
- 修正标签逻辑:训练判别器时真实样本标签为1、生成样本标签为0,叠加0.05幅度的标签平滑;训练生成器时目标标签为1,即引导生成器输出让判别器判定为真的样本。
- 修复真实样本采样逻辑:每个训练步移动采样指针,遍历完整个数据集后重置指针到0,保证所有真实样本都能参与训练。
- 调整生成器转置卷积通道顺序:从初始2*2分辨率特征图开始,通道数随上采样逐层从512降到32,最后输出3通道RGB图像,匹配DCGAN的标准结构。
- 清理冗余逻辑:删除未使用的RMSprop优化器定义、重复的可训练标志设置,训练循环使用函数传入的轮次、批大小参数,避免全局变量冲突。
- 调整判别器训练流程:每步训练取半批真实样本、半批生成样本分别计算损失,避免样本拼接顺序错误引发的标签错位。
修正后关键代码
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
相关产品推荐
相关产品推荐

