☰
Cycle GAN训练自己的数据集:原理、数据准备与避坑指南
2026/9/25 22:43:13 网站建设 项目流程

简介:CycleGAN源码与论文配套训练指南,面向需要在自己数据集上完成风格迁移的深度学习初学者、毕业设计学生及科研人员,重点解决域迁移、画风转换等实际应用问题。压缩包共75个文件,大小约41.87MB,其中以36个Python脚本为核心,配套14个Shell训练脚本、7个Markdown说明文档、5个TeX论文源文件及2个Jupyter示例,覆盖从模型搭建、数据预处理到训练测试的完整流程。目前已有1256人学习下载。内容除CycleGAN原始论文与官方PyTorch实现外,还包含Pix2Pix关联模型框架、教程博客及演示图片,便于对照论文理解循环一致性损失等关键机制,同时提供环境配置与依赖安装指引。实际可将普通照片转为梵高风格、游戏画面转为真实世界,也适用于医疗图像跨模态生成;训练时间相对可控,测试阶段响应快,兼顾科研复现与工程落地需求。

1. 先想清楚:Cycle GAN 跑自己的数据,到底在解决什么

很多人拿到 Cycle GAN,第一反应是复现梵高和照片那张效果图,跑通以后才发现自己真正想要的是“把 A 文件夹变成 B 文件夹”的批量迁移能力。Cycle GAN 是不需要配对样本的图像风格变换框架,只要你有两个风格域的图片集合,它就能同时训练两个生成器:A 到 B、B 到 A,核心是用循环一致性把内容保住、只换风格。这套方案对标注几乎零要求,最大工作量落在整理数据集和调那三个损失权重上。适合手里有大量未配对图片、想快速出原型的研究者和工程师,也适合把渲染图、涂鸦、街景批量统一风格的内容管线。

2. 原理先立住:配对数据反而不想要,循环一致性损失到底拟合了什么

2.1 不配对也能训练:对抗损失把“域”而不是“图”对应起来

传统图像翻译要用 pix2pix 这类方案,要求输入和输出像素级配对,比如同一张街景的分割图和真实照片。配对数据非常贵,很多时候根本拍不到同一场景的两种风格。Cycle GAN 换了一条路:我只要收集一大批 A 域图片和一大批 B 域图片,并不要求它们一一对应。生成器 G_A2B 的任务是把任意一张 A 域图变得像 B 域的风格,判别器 D_B 的任务是分辨这张图到底是真 B 还是假 B。两个网络对抗训练,最后 G_A2B 输出的分布会逐渐贴近 B 域的整体分布。

但只靠对抗损失有一个大问题:G_A2B 可以把图片改成任何一张“看起来像 B 域”的图,而不保证内容还是原来那张 A 图。比如把照片变成梵高风格,网络完全可以把照片里的房子改成一棵树,只要树也像梵高画的就行。于是论文里加了一个循环一致性损失:A 图先生成 B,再用另一个生成器 G_B2A 把它变回 A,要求重建出来的图和原图尽量一致。这个约束逼着 G_A2B 只改动风格信息,保留内容结构。反向也一样,B 图经过 G_B2A 生成假 A,再用 G_A2B 重建回 B。

理解到这里就够了:Cycle GAN 没有“配对”,但靠“绕一圈能回来”的方式实现了内容保真。这也是为什么它适合跑自己的数据集,因为现实中我们最容易拿到的就是两堆没有对应关系的图。

2.2 生成器与判别器选型:为什么是 ResNet 生成器加 70x70 PatchGAN

常见的 PyTorch Cycle GAN 实现里,生成器默认是 ResNet 结构的 9 个 block,而不是 U-Net。ResNet 做风格迁移的好处是它通过残差学习“在原始内容上叠加风格差异”,天然适合内容保真。判别器默认是 70x70 PatchGAN,也就是输出一个 70x70 的矩阵,每个点代表原图一个 patch 的真假判断,而不是整张图只输出一个真/假。这样做让判别器更关注局部纹理和笔触质感,同时也更容易训练。

要注意的是生成器里用的是 InstanceNorm,不是 BatchNorm。因为 Cycle GAN 训练时的 batch size 几乎都是 1,BatchNorm 在单样本下统计不了均值方差,要么崩要么变成另一种形式的 InstanceNorm。很多新手把 batch size 改成 4 想加速,结果风格学得很差,原因不在这,而是 4 张不同风格的图共享归一化统计量,反而干扰了 InstanceNorm 的设计意图。判别器层数 n_layers_D 默认是 3,对应 70x70 感受野;如果图片复杂度低,可以改成 2 让判别器更弱一点,生成器更容易骗过它。

2.3 论文里的三组损失,对应训练代码里的哪几行

读论文时建议盯着损失函数部分看。Cycle GAN 的训练对象是四个网络:G_A2B、G_B2A、D_A、D_B。生成器的总损失由三块拼起来,在官方 PyTorch 实现的 _train 函数里,大致长这样:

# 生成器一次迭代的损失计算,cycle_gan 模型的核心 # identity_loss:B 域图经过 G_A2B 应该尽量不变,A 域图经过 G_B2A 也尽量不变 identity_loss = L1(G_A2B(b), b) * lambda_identity + \ L1(G_B2A(a), a) * lambda_identity # adversarial_loss:生成结果要骗过判别器,这里用的 LSGAN 的 MSE 形式 adversarial_loss_A = MSE(D_A(G_B2A(b)), real_label) # 让 B->A 像真 A adversarial_loss_B = MSE(D_B(G_A2B(a)), real_label) # 让 A->B 像真 B # cycle_loss:绕一圈回来,重建图和原图做 L1 距离 cycle_loss = L1(G_B2A(G_A2B(a)), a) * lambda_A + \ L1(G_A2B(G_B2A(b)), b) * lambda_B G_loss = identity_loss + adversarial_loss_A + adversarial_loss_B + cycle_loss

三个权重的默认值很关键:lambda_A 和 lambda_B 都是 10,lambda_identity 在多数实现里默认 0.5。lambda_A 控制“A 生成 B 再回到 A”的重建强度,lambda_B 控制反向。你如果只关心 A 到 B 的方向,可以把 lambda_B 调小,这样正方向的生成自由度更大,风格更强,代价是反向重建会变差。

读论文和代码对照的正确顺序是:先看生成器和判别器的 forward,确认输入输出形状;再在循环里找到 G_loss 的拼接位置;最后只改 lambda 和数据集路径。不要一开始就去纠结网络内部结构,Cycle GAN 能跑通的关键是损失权重和数据,不是网络微调。

2.4 训练前先读懂数据集目录:最常用的 PyTorch 实现要求什么结构

大多数开源 Cycle GAN 项目都沿用同一个目录约定:dataroot/name/trainA/和dataroot/name/trainB/,test 的时候再加testA/和testB/。train.py 的--dataroot指向上层目录,--name是项目子目录。以最常用的 PyTorch 版本为例,完整结构是这样:

datasets/ └── myproj/ ├── trainA/ # A 域训练图,例如真实照片 │ ├── 0001.jpg │ ├── 0002.jpg └── trainB/ # B 域训练图,例如油画 ├── 0001.jpg ├── 0002.jpg ├── testA/ # 最后验证效果的 A 域图,不进训练 └── testB/ # 最后验证效果的 B 域图,不进训练

文件名建议统一成纯数字加 jpg 或 png,防止中文路径在某些预处理脚本下乱码。A 域和 B 域图片数量可以不一样,Cycle GAN 不要求配对,也不要求数量均衡;但如果一个域只有 20 张,另一个域有 3000 张,训练会更偏向“把少数域学成记忆”而不是翻译。

内容要求原因
图片格式jpg / png 均可,不要带中文文件名部分图像库和缓存机制会踩编码坑
图片尺寸训练时会缩放到 286 再裁剪到 256,无需提前统一提前统一反而丢失原始分布信息
配对关系不要求一一配对这是 Cycle GAN 与 pix2pix 的本质区别
风格纯度trainA 里不要混入明显 B 风格图混入会让判别器无所适从,收敛变慢

如果你手上的数据本来就是一一配对的,比如同一个场景的白天和夜晚照片,那优先用 pix2pix,不要为了用 Cycle GAN 而用。Cycle GAN 在配对数据上学出来的效果通常不如 pix2pix,因为它把一半的建模能力浪费在了不存在的对应关系上。

3. 训练自己的数据集:目录、依赖、参数,四步出第一张结果

3.1 环境准备:一个 Python 环境装完所有依赖

很多人熟悉的是“用 YOLOv5 训练自己的数据集”那个流程:下代码、装依赖、改 yaml、开训。Cycle GAN 是同一个路子,只是它不需要标注文件,需要的是两个图像文件夹。环境要求很轻,PyTorch、visdom、dominate、Pillow 就够。

conda create -n cyclegan python=3.8 -y conda activate cyclegan pip install torch torchvision pip install dominate visdom pillow # 以社区最常用的 PyTorch 实现为例 git clone https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix cd pytorch-CycleGAN-and-pix2pix

visdom 是用来画 loss 曲线的可视化服务,一定要提前启动。训练时不开 visdom 也能跑,只要加--display_id 0,但新手强烈建议开着,因为判别器和生成器的对抗震荡很难只凭终端数字判断。

python -m visdom.server

启动后浏览器打开本地 8097 端口,能看到空的 dashboard。训练命令跑起来后,loss 曲线会自动往这里推。Windows 下 visdom 偶尔会因为系统代理环境变量报错,如果页面一直连不上,优先检查 8097 端口是否被占用,不要急着怪模型。CPU 也能训练,--gpu_ids -1指定,但 256 分辨率下速度会慢到让人怀疑人生,最好还是搞张 6G 显存以上的卡。

3.2 图片整理脚本:把 A/B 散图自动变成 train/test 目录

假设你从某处下载或者收集了两堆图,分别放在raw_A/和raw_B/里,里面可能还有子目录、gif、透明通道图片。直接拖进 train 文件夹会让训练意外中断。我习惯先跑一遍整理脚本,把扩展名过滤、统一改名、按 9:1 拆出测试集:

import os import random import shutil from pathlib import Path root = Path("./datasets/myproj") trainA, trainB = root / "trainA", root / "trainB" testA, testB = root / "testA", root / "testB" for d in [trainA, trainB, testA, testB]: d.mkdir(parents=True, exist_ok=True) ext = {".jpg", ".jpeg", ".png", ".bmp"} def organize(src_folder, train_dir, test_dir, prefix, test_ratio=0.1): files = [] for p in Path(src_folder).rglob("*"): if p.suffix.lower() in ext: files.append(p) random.seed(42) random.shuffle(files) n_test = max(1, int(len(files) * test_ratio)) for i, p in enumerate(files): if i < n_test: dst = test_dir / f"{prefix}_{i:05d}.jpg" else: dst = train_dir / f"{prefix}_{i-n_test:05d}.jpg" shutil.copy2(p, dst) print(f"{src_folder}: {len(files)} files, test={n_test}") organize("./raw_A", trainA, testA, "A") organize("./raw_B", trainB, testB, "B")

这段脚本会遍历子目录,把 jpg、png、bmp 全部复制出来,按序号重命名,同时从每域抽 10% 作为 test。注意是copy2不是move,保留原始文件防止后面想重新清洗。脚本里的prefix加在文件名前面,避免两个域重名。整理完之后最好是肉眼把 testA/testB 各自翻一遍,确认没有半张损坏图片。

3.3 训练命令与关键参数:哪些能调、哪些建议别动

数据准备好之后,训练命令非常短:

python train.py \ --dataroot ./datasets/myproj \ --name myproj \ --model cycle_gan \ --load_size 286 \ --crop_size 256 \ --batch_size 1 \ --lr 0.0002 \ --n_epochs 100 \ --n_epochs_decay 100 \ --display_id 1 \ --print_freq 100 \ --save_epoch_freq 10

这里--n_epochs 100表示前 100 个 epoch 用固定学习率 0.0002,--n_epochs_decay 100表示后 100 个 epoch 线性衰减到 0,总共训练 200 个 epoch。--save_epoch_freq 10每 10 轮存一个 checkpoint,默认情况会同时保存 G_A2B、G_B2A、D_A、D_B 四个网络。训练完在checkpoints/myproj/下能找到latest_net_G_A.pth。

参数作用建议值
--load_size/--crop_size先缩放再随机裁剪,做空间增强286 / 256,显存大可以 512 / 480
--batch_size一次迭代样本数固定 1,改大了会破坏 InstanceNorm 语义
--lr初始学习率0.0002,爆 NaN 时降到 0.0001
--lambda_A/--lambda_B循环一致性权重默认 10,只关心单方向可调小另一个
--lambda_identity颜色保持权重0.5,照片转油画可开到 1.0
--n_layers_D判别器卷积层数3,效果差可以降到 2
--gan_mode对抗损失形式lsgan,不要改成 wgan 除非你真的会调
--pool_size判别器历史缓冲池50,默认值不用动

方向参数--direction AtoB在 train 阶段没有实际影响,因为损失是对称的,两个生成器同时训练。它真正起作用的是后面 test.py 推理阶段,决定输出 A 风格还是 B 风格。所以训练时不用管 direction。

3.4 冒烟测试:一小时看出你的数据行不行

不要一上来就训 200 个 epoch。我一般先用每域 20 到 30 张图做冒烟测试,把 epoch 压到 20,确认数据集路径、dataloader、loss 计算全都没有问题:

python train.py \ --dataroot ./datasets/myproj \ --name smoke_test \ --model cycle_gan \ --load_size 286 --crop_size 256 \ --batch_size 1 --lr 0.0002 \ --n_epochs 20 --n_epochs_decay 0 \ --display_id 0 --save_epoch_freq 5

20 个 epoch 在一张普通显卡上大约半小时到一小时。用--display_id 0关掉 visdom,直接看终端输出。判断是否正常的经验是:total_loss 里的 cycle_loss 部分应该稳步下降,D_A、D_B 在 0.5 附近震荡,而不是一路冲到 0 或者 1。如果 cycle_loss 纹丝不动,说明生成器从一开始就没学到任何重建能力,问题多半出在数据而不是网络,回去检查 trainA/trainB 是不是放了同一个域的图片。

冒烟测试通过后,把smoke_test的 latest checkpoint 直接拿来跑一遍 test.py,虽然风格大概率很粗糙,但至少能确认推理路径是通的。这一步排掉的问题,远比省下的一个小时更值钱。

4. 数据选型才是效果天花板:域怎么选、怎么洗、怎么拆

4.1 选域的核心标准:内容重合、风格一致

Cycle GAN 的极限通常由数据决定,而不是网络。第一批跑出来的效果差,绝大多数是因为两个域的“内容分布”完全没有交叉。比如你想把真实照片变成梵高风格,A 域全是人物全身照,B 域全是风景油画,生成器想要成功欺骗判别器,最省力的办法是把照片里的所有东西都涂成风景,人物直接被抹掉。因为 GAN 只要求结果像 B 域,不要求结果里还有人物。

所以选域的第一标准是:两个域里出现的物体类别要尽可能重合。照片里有天空、房子、树、人,油画里也应该有这些。第二标准是风格差异要一致。如果你想做“照片变梵高”,B 域就只放梵高风格的画,不要为了凑数量把莫奈和毕加索也放进去。那样判别器学到的 B 域不是一个风格,而是“任何非照片的绘画”,生成结果会变成一个风格大杂烩。

最稳妥的做法是先做一个小型验证集:A 域 100 张照片,B 域 100 张同题材油画。如果这个验证集上能看到明显风格迁移,再把规模扩大到几千张。反过来,如果小验证集怎么调都不出效果,加数据也是白搭。

4.2 清洗三步:过滤坏图、去重、按域复查

从公开数据集、Hugging Face 这类平台下载下来的组合集,来源通常很杂,直接丢进 train 会让训练在不知不觉中崩掉。我清理数据一般走三步:自动过滤、自动去重、最后人工抽查。先跑一遍过滤脚本:

from PIL import Image import numpy as np import os, shutil def clean_folder(src, dst, min_side=200, max_ratio=2.5, min_std=8.0): os.makedirs(dst, exist_ok=True) for name in os.listdir(src): path = os.path.join(src, name) try: im = Image.open(path).convert("RGB") w, h = im.size if min(w, h) < min_side: # 太小的图直接不要 continue if max(w / h, h / w) > max_ratio: # 超长全景图会裁掉大量信息 continue gray = np.asarray(im.convert("L")).astype(np.float32) if gray.std() < min_std: # 纯色图、几乎全白的图没意义 continue shutil.copy2(path, os.path.join(dst, name)) except Exception: continue clean_folder("./raw_A", "./clean_A") clean_folder("./raw_B", "./clean_B")

min_std过滤低对比度图,值设 8 左右比较宽松,只剔除纯色块和严重过曝。不要设太大,否则暗光风格的图片会被全部误杀。

之后用文件哈希去重。网上数据集里大量重复图是常态,特别是爬下来的图片,同一张图可能被压缩成多种尺寸。去重脚本可以用 md5 对二进制文件算一遍,删除重复项。去重后再按域做人工抽查,重点看 A 域里有没有明显是 B 风格的图。比如做白天到夜晚任务时,A 域混入大批夜景,训练时生成器会困惑“该学亮度变化还是该学风格差异”。清洗宁缺毋滥,100 张干净图的效果通常好过 1000 张脏数据。

4.3 尺寸、增强与分辨率:让 crop 别做太多“内耗”

默认的数据增强是:图片先缩放到load_size=286,再随机裁剪到crop_size=256,同时随机水平翻转。这套组合对大多数任务够用。别开垂直翻转,天空和地面的语义关系会被颠倒,模型会学到“上下颠倒也没关系”的坏习惯。对于白天到夜晚这类光照任务,也不要额外做颜色抖动和亮度扰动,那等于人为抹掉两个域最核心的差异。

当图片长宽比很极端时,比如 16:9 的横构图,训练时随机裁剪 256x256 只裁到画面中心一小块,大量边缘内容永远进不了训练。我一般会先统一裁剪成 4:3 或 1:1,再放进 train。但这会让某些任务丢失关键内容,所以更稳妥的做法是尽可能收集本来就接近方形构图的素材,让训练和推理的分辨率分布保持一致。

如果目标风格的细节很重要,比如线稿上色需要看清笔触边界,可以把--crop_size从 256 提到 384。显存不够时的调整顺序是:先降 batch_size,再降 crop_size,不要用 CPU offload。生成器是 ResNet 9 个 block,感受野相对固定,盲目上 512 分辨率不一定更好,常常只是让伪影更精细。

4.4 划分 train/test:如何避免“用训练图当测试图”的低级翻车

很多开源数据集直接把所有图片放在一个文件夹里,只告诉你“一共有多少张”,没有拆分。如果自己随机抽 10% 做测试,却又没从训练集中剔除,测试效果会虚高。Cycle GAN 虽然没有标签,但生成器见过测试图之后,判别器也会影响生成器对这张图的拟合。正确做法是先把文件列表随机打乱,再切分,让 train 和 test 完全没有交集。

import random from pathlib import Path srcA = Path("./clean_A") files = [p.name for p in srcA.iterdir() if p.suffix.lower() in {".jpg", ".png"}] random.seed(42) random.shuffle(files) n_test = int(len(files) * 0.1) test_names = files[:n_test] train_names = files[n_test:] with open("./split_A.txt", "w") as f: for name in train_names: f.write(f"{name}\n") with open("./split_A_test.txt", "w") as f: for name in test_names: f.write(f"{name}\n")

把切分名单保存成 txt,后续每次重新训练都用同一个名单,保证实验可复现。Cycle GAN 的测试集还有一个作用:用来观察“风格迁移强度”是否失控。生成器在训练后期可能开始添加奇怪笔触,如果 test 集里出现明显超出训练分布的内容,比如整张图变成抽象色块,说明训练时间过长,需要回滚到上一个 checkpoint。

5. 避坑清单:Cycle GAN 自己数据集训练最常见的五个翻车现场

5.1 loss 直接变 NaN,生成图全黑或全白

现象:训练迭代几十步后,终端 loss 变成 nan,visdom 曲线突然断掉,后续保存的生成图要么全黑要么全白。

原因:最常见的是学习率太大导致生成器或判别器梯度爆炸;其次是数据集里混入了损坏图片,比如 0 字节的 jpg、损坏的 png,ImageLoader 读到异常像素后把 NaN 传给网络;还有一个隐蔽原因是图片带 alpha 通道,PIL 读取后如果没转 RGB,某些实现会在归一化时算出 NaN。

解决:先把--lr从 0.0002 降到 0.0001,很多不稳定训练在 1e-4 下立刻变正常。然后写一个脚本遍历所有训练图片,打开后用im.convert("RGB")再核对尺寸和通道数。更简单的方式是回到清洗脚本,把所有图片统一转成 RGB 三通道再保存。最后,如果前两步都没解决,把--gan_mode从 lsgan 改成 vanilla,MSE 损失对判别器饱和更敏感,改成交叉熵能缓解部分梯度爆炸。

5.2 训练一百轮后输出还是输入:G 变成了恒等映射

现象:测试时 A 图经过生成器输出的图片和输入几乎一模一样,风格完全没有迁移,但 cycle loss 已经收敛得很低。

原因:生成器发现“把图片原样输出”就能满足内容重建,同时如果判别器不够强,风格方向的对抗又造不成压力,它就会偷懒走恒等捷径。另一个常见原因是lambda_identity开得太大,这个损失本来就是约束“输入经过反向生成器不要变”,如果权重压过了对抗损失,整个网络会退化成两个恒等函数。

解决:先把--lambda_identity设为 0,确认风格迁移能力恢复,再加回 0.5 做颜色保持。如果设 0 后仍然恒等,说明判别器太弱,把--n_layers_D从 3 改回 4,或者把生成器的--netG从 resnet_9blocks 换成 resnet_6blocks,降低生成器容量。最后不要忽略数据问题:如果 trainA 和 trainB 本身视觉差异极小,比如都是同一批照片的不同裁剪,Cycle GAN 学习到的自然就是恒等映射。

5.3 画面出现水渍状伪影或高频噪点

现象:生成的图片整体风格到位,但天空、墙面这类平坦区域出现一块块水渍样纹理,或者整张图蒙上一层细密噪点。

原因:判别器 PatchGAN 只关注局部 patch,生成器为了骗过它,会在平坦区域添加高频虚假纹理。这些纹理在判别器看来像目标域的特征,但人眼非常敏感。另一个原因是 LSGAN 的 MSE 损失对离群点惩罚重,生成器宁可输出平均灰也不愿冒险,最后产生带噪点的折中结果。

解决:先把--lambda_identity开到 1.0,让生成器保留更多原始颜色和低频信息,伪影通常立刻变淡。再把--n_layers_D降到 2,削弱判别器对局部纹理的敏感度。如果任务里文字边缘和细小结构总是崩,可以增大--crop_size,让生成器看到更大的上下文,减少为局部 patch 单独编造纹理的动机。最后,训练到后期如果伪影重新出现,多半是过拟合,直接加载更早的 checkpoint 测试。

5.4 换了数据集效果远不如论文示例,甚至学不出任何风格

现象:按教程跑通了代码,也建好了 trainA/trainB,但训练完风格迁移非常弱,测试图只是色调微变,完全没有论文里的冲击力。

原因:多数人忽略了一个前提:论文示例里的两个域内容高度重合,而且风格差异非常鲜明。夏天到冬天、照片到梵高,这类任务本身就有强烈的特征可供学习。而自制数据如果只是“室内照片”和“室外照片”,两域在颜色和纹理上的差异没有一致模式,生成器找不到可学的风格规则。

解决:先跑通论文自带的 apple2orange 或 photo2ukiyoe 示例,确认代码环境没问题,再换成自己的数据。自己的数据要达到类似效果,每个域至少要有 100 到 200 张内容类目一致的图片。如果数量不够,先用数据增强把量提上去,但注意增强只能增加视角变化,不能增加内容多样性。最后检查--direction是否设反了:训练时虽然两个方向都学,但测试时的输出方向要和你认为的“源域到目标域”一致。

5.5 测试图颜色错乱、红蓝通道颠倒

现象:训练过程中看生成的图片很正常,但 test.py 保存的结果里,绿色植物变成紫红色,蓝天变成橙色。

原因:这是最容易被误判为模型问题的低级坑。某些实现用 OpenCV 读取和保存图像,而 OpenCV 的默认通道顺序是 BGR,训练时如果用的是 PIL 的 RGB,推理时又混用了 cv2.imwrite,就会把 R 和 B 通道交换。另一个可能是在自定义 Dataset 里对图片做了通道翻转,但测试脚本没做同样的处理。

解决:查看 test.py 里图像保存的部分。常见做法是用Image.open读成 RGB,用imageio.imwrite或 PIL 保存,这样就全程走 RGB。如果你用了 OpenCV,统一在读取后加cv2.cvtColor(img, cv2.COLOR_BGR2RGB),保存前再转回 BGR。不要迷信某个教程里的图像预览代码,自己用 matplotlib 单独读测试图确认一遍最稳妥。通道问题不是玄学,是管线里最常见的“黑白匣子”。

6. 验证与进阶:用 A 到 B 再到 A 的重建检查,你的网络是否真的学会了

训练完成后,第一步不是急着夸效果,而是跑一遍批量推理,看生成结果和重建结果:

python test.py \ --dataroot ./datasets/myproj \ --name myproj \ --model cycle_gan \ --direction AtoB \ --epoch latest \ --num_test 50 \ --preprocess resize \ --no_flip

--preprocess resize是我强烈建议加上的参数,它让测试时不进行随机裁剪,保留完整构图。结果会输出到results/myproj/latest_test/,里面每张测试图都会被保存。之后把 A 域原图、A 到 B 的生成图、以及用生成图再经过 G_B2A 得到的重建图三张拼在一起看。如果重建图和原图轮廓基本一致,说明生成器学的是“换风格保内容”;如果风格很明显但重建完全认不出原图,说明循环一致性权重不够,需要加大lambda_A;反过来风格没迁移但重建很好,则说明对抗强度不足,检查判别器是否过强。

进阶玩法是调整不对称权重。比如你只想做“现实场景转二次元”,并不关心二次元转回现实,可以把lambda_B从 10 降到 2,让反向生成器更自由,这样正方向的风格会更强。对称性不是金科玉律,Cycle GAN 的损失结构允许你按使用场景偏置。

量化评估可以补一个 FID,社区常用 pytorch-fid 工具,对真实 B 域和生成 B 域各算一次特征分布距离,数值越小越好。但要注意小数据集下 FID 方差很大,少于 200 张图时参考意义不大,主要还得靠肉眼判断重建质量和风格一致性。

我自己的习惯是每次训练前固定选 10 张测试图,训练过程中每 50 个 epochs 跑一次推理,只看图不看 loss 曲线。真正确认收敛的是那些固定测试图的稳定性,不是 visdom 上的曲线形状。图像结果永远是 Cycle GAN 的唯一可信指标。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询