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

PyTorch搭建DCGAN在MNIST效果好但自定义轨迹数据集效果差调优

DCGAN自定义轨迹数据集生成失效调优方案

问题背景

基于PyTorch搭建标准DCGAN,使用包含20000条简单路径的数据集训练,目标是生成机器人轨迹数据。

  • 同一网络在MNIST数据集上训练20轮后生成效果达标,生成器、判别器损失均在0.7左右进入平台期
    MNIST训练后生成示例
    MNIST训练数据示例
  • 相同训练配置下,自定义轨迹数据集生成效果极差,判别器、生成器损失同样卡在0.7左右进入平台期
    路径数据集训练后GAN输出示例
    对应批次路径训练数据

注:D、G损失同时稳定在0.7左右不代表训练收敛。该数值是判别器对所有样本输出0.5概率(完全无法区分真假)时的BCELoss理论值(-ln0.5≈0.693),本质是训练进入无有效梯度的无效均衡。MNIST任务中网络先学到数字轮廓再进入该均衡,因此生成效果达标;轨迹任务中网络未学到有效路径特征就提前进入该均衡,因此生成结果失效。

现有代码实现

判别器代码

class Discriminator(nn.Module):
    def __init__(self, channels_img, features_d):
        super(Discriminator, self).__init__()
        self.disc = nn.Sequential(
            # input: N x channels_img x 64 x 64
            nn.Conv2d(
                channels_img, features_d, kernel_size=4, stride=2, padding=1
            ),
            nn.LeakyReLU(0.2),
            # _block(in_channels, out_channels, kernel_size, stride, padding)
            self._block(features_d, features_d * 2, 4, 2, 1),
            self._block(features_d * 2, features_d * 4, 4, 2, 1),
            self._block(features_d * 4, features_d * 8, 4, 2, 1),
            # After all _block img output is 4x4 (Conv2d below makes into 1x1)
            nn.Conv2d(features_d * 8, 1, kernel_size=4, stride=2, padding=0),
            nn.Sigmoid(),
        )

    def _block(self, in_channels, out_channels, kernel_size, stride, padding):
        return nn.Sequential(
            nn.Conv2d(
                in_channels,
                out_channels,
                kernel_size,
                stride,
                padding,
                bias=False,
            ),
            nn.BatchNorm2d(out_channels),
            nn.LeakyReLU(0.2),
        )

    def forward(self, x):
        return self.disc(x)

生成器代码

class Generator(nn.Module):
    def __init__(self, channels_noise, channels_img, features_g):
        super(Generator, self).__init__()
        self.net = nn.Sequential(
            # Input: N x channels_noise x 1 x 1
            self._block(channels_noise, features_g * 16, 4, 1, 0),  # img: 4x4
            self._block(features_g * 16, features_g * 8, 4, 2, 1),  # img: 8x8
            self._block(features_g * 8, features_g * 4, 4, 2, 1),  # img: 16x16
            self._block(features_g * 4, features_g * 2, 4, 2, 1),  # img: 32x32
            nn.ConvTranspose2d(
                features_g * 2, channels_img, kernel_size=4, stride=2, padding=1
            ),
            # Output: N x channels_img x 64 x 64
            nn.Tanh(),
        )

    def _block(self, in_channels, out_channels, kernel_size, stride, padding):
        return nn.Sequential(
            nn.ConvTranspose2d(
                in_channels,
                out_channels,
                kernel_size,
                stride,
                padding,
                bias=False,
            ),
            nn.BatchNorm2d(out_channels),
            nn.ReLU(),
        )

    def forward(self, x):
        return self.net(x)

训练循环代码

opt_gen = optim.Adam(gen.parameters(), lr=LEARNING_RATE_GEN, betas=(0.5, 0.999))
opt_disc = optim.Adam(disc.parameters(), lr=LEARNING_RATE_DISC, betas=(0.5, 0.999))
criterion = nn.BCELoss()

for epoch in range(NUM_EPOCHS):
    # Target labels not needed! <3 unsupervised
    # for batch_idx, (real, _) in enumerate(dataloader):
    for batch_idx, real in enumerate(dataloader):
        real = real.to(device)
        noise = torch.randn(BATCH_SIZE, NOISE_DIM, 1, 1).to(device)
        fake = gen(noise)

        ### Train Discriminator: max log(D(x)) + log(1 - D(G(z)))
        disc_real = disc(real.float()).reshape(-1)
        loss_disc_real = criterion(disc_real, torch.ones_like(disc_real))
        disc_fake = disc(fake.detach()).reshape(-1)
        loss_disc_fake = criterion(disc_fake, torch.zeros_like(disc_fake))
        loss_disc = (loss_disc_real + loss_disc_fake) / 2
        disc.zero_grad()
        loss_disc.backward()
        opt_disc.step()

        ### Train Generator: min log(1 - D(G(z))) <-> max log(D(G(z))
        output = disc(fake).reshape(-1)
        loss_gen = criterion(output, torch.ones_like(output))
        gen.zero_grad()
        loss_gen.backward()
        opt_gen.step()

        # Print losses occasionally and print to tensorboard
        if batch_idx % 100 == 0:
            print(
                f"Epoch [{epoch}/{NUM_EPOCHS}] Batch {batch_idx}/{len(dataloader)} \
                  Loss D: {loss_disc:.4f}, loss G: {loss_gen:.4f}"
            )

            with torch.no_grad():
                fake = gen(fixed_noise)
                # take out (up to) 32 examples
                img_grid_real = torchvision.utils.make_grid(
                    real[:BATCH_SIZE], normalize=True
                )
                img_grid_fake = torchvision.utils.make_grid(
                    fake[:BATCH_SIZE], normalize=True
                )

                writer_real.add_image("Real", img_grid_real, global_step=step)
                writer_fake.add_image("Fake", img_grid_fake, global_step=step)

            step += 1

调优方案

按优先级从高到低排查修改:

  • 数据预处理对齐
    生成器最后一层为nn.Tanh(),输出范围固定为[-1,1],必须将轨迹训练数据通过(data - 0.5)/0.5归一化到相同区间,禁止归一化到[0,1]或其他范围,否则判别器可直接通过数值范围区分真假样本,快速进入无效均衡。
    可视化阶段先关闭make_grid的normalize=True参数,直接查看原始输出的像素分布,避免被自动拉伸后的可视化结果误导,确认真实数据和生成数据的数值范围完全对齐后再开启归一化展示。
  • 训练策略调整
    • 加入标签平滑:真实样本标签设为0.9,生成样本标签设为0.1,避免判别器输出过度接近0/1导致梯度消失。
    • 替换损失函数:去掉判别器最后一层的nn.Sigmoid(),将nn.BCELoss()替换为nn.BCEWithLogitsLoss(),提升数值稳定性。
    • 调整学习率:将生成器、判别器的学习率从MNIST常用的2e-4下调到1e-4,避免训练震荡。
    • 动态调整训练频次:训练初期每训练1次生成器,训练2次判别器;若判别器损失跌破0.3,立刻改为每训练2次生成器训练1次判别器,保证两者训练强度匹配,避免判别器过早失效。
  • 网络结构适配
    轨迹数据是单通道稀疏线条,像素占比远低于MNIST数字,标准DCGAN的多次2倍下采样会快速丢失细粒度路径特征:
    • 将判别器前2个卷积块的步长从2改为1,降低下采样倍率,保留细粒度线条特征。
    • 将噪声维度NOISE_DIM从默认100提升到128~256,给生成器足够的编码空间表达不同轨迹形态。
    • 若生成结果仍存在块状噪声,将判别器部分BatchNorm层替换为InstanceNorm,缓解稀疏数据的BN统计量偏移问题。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.30 06:33:21