LAB 03 · CycleGAN:可逆不等于语义正确
先阅读保存结果和解释,再按本册步骤选择是否运行。在 Deepnote 阅读与运行 · 下载 Notebook
CycleGAN:翻过去再翻回来,为什么仍可能翻错?
本册构造两个各有四类对象的有限域:图形入口使用建筑线图与彩色图,后段保留形状标签的穷举。我们只给出两边各自的样本分布,不提供哪个对象对应哪个对象的配对,希望保留对象身份、只改变样式;这里没有真实照片或图像模型训练。
先预测。 如果某个翻译既完美匹配目标域分布,又可以无损翻译回来,它是否一定把圆映到圆?本册穷举全部有限映射,找出一个你能逐项验证的反例。
本轮入口:先看下面的保存图,再只改一个参数。 原有可手算代码与推导完整保留在后面。
先看问题与保存结果
先看24个建筑双射都可循环回去的反例。配对锚点提供额外对应信息;这个有限规则实验没有训练 CycleGAN。真实 D/G 参数更新和覆盖结果在 LAB08。

连续概念解释。读完后留下一个结果:固定量、改变项、实际观察,以及它支持的机制。
先看保存结果:可辨认建筑的错误双射与配对锚点



第一张图依次是源域线图、目标域彩色图、逆映射返回线图。尖屋顶房被错配成平顶楼,仍可被逆函数恢复。等概率四对象允许24个零边际误差、零循环误差的双射;0/1/2/3/4个锚点分别留下24/6/2/1/1个候选。三对正确锚点已由双射性确定最后一对。
结果身份: 2026-09-28由下方同源Python代码在本地CPU实际生成并核对。建筑由可检查的几何代码绘制,用来替代原来的形状标签;有限构造的域,不是照片、CycleGAN训练或其输出。原四形状穷举保留,用相同排列集合复核。 图不是模型训练输出。
保存数据、图片与代码随下载材料提供;静态图无需GPU。学生重算需要Python、NumPy、matplotlib,不发起网络请求、不打印密钥,只在当前目录写入本册输出文件夹。
只改一个变量:ANCHORS
将 ANCHORS 从1改为0、2、3或4。固定同样四个建筑与24种双射,只增加前几个已知正确配对。蓝线表示仍可接受的候选,灰线表示被锚点排除。
在下面参数区改值后运行本格;重算图会显示在输出中,并保存在 lab03-visual-output。接着用图中的具体变化解释,不从一次样本判断整个分布。
查看可执行代码
import math, json, itertools, platform
from pathlib import Path
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.patches import Rectangle,Polygon,Wedge,ConnectionPatch
# Pin the first ANCHORS correct source-target pairs, then enumerate every survivor.
ANCHORS = 1 # safely change to 0, 2, 3 or 4
OUT=Path('lab03-visual-output');OUT.mkdir(exist_ok=True)
assert ANCHORS in range(5)
BUILDINGS=['gable house','flat office','twin towers','dome hall']
PERMS_VIS=list(itertools.permutations(range(4)))
def vis_building(ax,index,domain):
ax.set(xlim=(0,1),ylim=(0,1),aspect='equal');ax.axis('off')
wall='white' if domain=='X' else ['#e8b28a','#98bec7','#bdafb4','#a8b88b'][index]
roof='white' if domain=='X' else '#697984';edge='#233b4d';window='white' if domain=='X' else '#eaf5fa'
def box(x,y,w,h,fc=wall):ax.add_patch(Rectangle((x,y),w,h,facecolor=fc,edgecolor=edge,lw=1.8))
ax.plot([.05,.95],[.08,.08],color=edge,lw=2)
if index==0:
box(.19,.08,.62,.52);ax.add_patch(Polygon([[.12,.6],[.5,.91],[.88,.6]],facecolor=roof,edgecolor=edge,lw=1.8));box(.43,.08,.14,.27,window)
box(.25,.37,.13,.13,window);box(.62,.37,.13,.13,window)
elif index==1:
box(.15,.08,.7,.73);box(.1,.81,.8,.07,roof)
for x in [.24,.46,.68]:
for y in [.28,.48,.68]:box(x,y,.09,.08,window)
elif index==2:
box(.08,.08,.3,.8);box(.62,.08,.3,.8);box(.38,.25,.24,.17,roof)
for x in [.17,.71]:
for y in [.3,.48,.67]:box(x,y,.11,.08,window)
else:
box(.16,.08,.68,.45);ax.add_patch(Wedge((.5,.53),.34,0,180,facecolor=roof,edgecolor=edge,lw=1.8));box(.43,.08,.14,.26,window)
ax.set_title(('line ' if domain=='X' else 'color ')+BUILDINGS[index],fontsize=10)
wrong=(1,2,3,0)
fig,axes=plt.subplots(3,4,figsize=(11.6,8.0))
for i in range(4):
vis_building(axes[0,i],i,'X');vis_building(axes[1,i],wrong[i],'Y');vis_building(axes[2,i],i,'X')
for row in [0,1]:
fig.add_artist(ConnectionPatch((.5,0),(.5,1),coordsA=axes[row,i].transAxes,coordsB=axes[row+1,i].transAxes,arrowstyle='->',color='#c49113',lw=2,shrinkA=6,shrinkB=17))
fig.suptitle('A wrong bijection still returns every building: cycle error = 0; semantic accuracy = 0',fontsize=13)
fig.subplots_adjust(hspace=.6,top=.90);fig.savefig(OUT/'lab03-buildings-cycle.png',dpi=150);plt.show();plt.close(fig)
survivors=[p for p in PERMS_VIS if all(p[i]==i for i in range(ANCHORS))]
fig,axes=plt.subplots(4,6,figsize=(12,7.8))
for ax,p in zip(axes.flat,PERMS_VIS):
valid=p in survivors;ax.set(xlim=(-.25,1.25),ylim=(-.35,3.45));ax.axis('off')
for i,j in enumerate(p):ax.plot([0,1],[3-i,3-j],color='#003262' if valid else '#c9cdd1',lw=2,alpha=1 if valid else .6)
for x in [0,1]:ax.scatter([x]*4,[3,2,1,0],color=['#e39164','#54889b','#a6819a','#879e5a'],s=30,zorder=3)
for i in range(ANCHORS):ax.scatter([0,1],[3-i,3-i],s=110,facecolors='none',edgecolors='#d69b08',lw=1.5,zorder=4)
ax.set_title(' '.join(str(i+1) for i in p)+(' kept' if valid else ' ruled out'),fontsize=8,color='#003262' if valid else '#92979d')
fig.suptitle(f'All 24 bijections; {ANCHORS} paired anchor(s) leave {len(survivors)} candidates',fontsize=14)
fig.tight_layout();fig.savefig(OUT/'lab03-anchor-enumeration.png',dpi=160);plt.show();plt.close(fig)
records=[]
for count in range(5):
keep=[p for p in PERMS_VIS if all(p[i]==i for i in range(count))]
accuracy=[sum(i==j for i,j in enumerate(p))/4 for p in keep]
records.append({'anchors':count,'survivors':len(keep),'worst_accuracy':min(accuracy)})
assert [r['survivors'] for r in records]==[24,6,2,1,1]
fig,ax=plt.subplots(figsize=(7.5,3.5));ax.bar([r['anchors'] for r in records],[r['survivors'] for r in records],color='#003262');ax.set(xlabel='Number of paired anchors',ylabel='Surviving bijections',xticks=range(5),title='Extra correspondence removes ambiguity');fig.tight_layout();fig.savefig(OUT/'lab03-anchor-count.png',dpi=160);plt.show();plt.close(fig)
payload={'kind':'finite constructed building images; no CycleGAN training','buildings':BUILDINGS,'anchors':ANCHORS,'survivors':survivors,'counts':records,'wrong_bijection':wrong,'python':platform.python_version()}
(OUT/'lab03-results.json').write_text(json.dumps(payload,indent=2));print(json.dumps(records,indent=2))



查看保存的计算输出
[
{
"anchors": 0,
"survivors": 24,
"worst_accuracy": 0.0
},
{
"anchors": 1,
"survivors": 6,
"worst_accuracy": 0.25
},
{
"anchors": 2,
"survivors": 2,
"worst_accuracy": 0.5
},
{
"anchors": 3,
"survivors": 1,
"worst_accuracy": 1.0
},
{
"anchors": 4,
"survivors": 1,
"worst_accuracy": 1.0
}
]
本轮运行与后续阅读
本地执行与云端复核均已完成。 2026-09-28,本册全部代码在本地CPU从空命名空间顺序执行;另通过Deepnote全本运行,状态为success,运行ID为 845ec1ef-3f83-4842-9874-59d2e56790cb,完成时间 2026-09-28T15:30:04.984Z(UTC)。运行快照检查到本册图形输出且无失败代码块。建筑映射和24双射图已输出;0/1/2/3/4个锚点分别留下24/6/2/1/1个候选,原穷举与频率检查通过。
云端运行使用Python 3.13数据科学环境。平台总用时包含启动、Notebook执行与输出保存,不作为算法速度基准;五本均为小规模CPU计算,不调用外部模型或付费API。上方静态图仍明确保留其本地生成来源,云端输出是另一次实际复核。学生修改参数后得到的是自己的新结果。
下面保留此前逐步计算与推导。历史本地数字保留原身份,可把图中一个关系追到对应公式。
三个判断分开计算
令源域 $X=\{\text{线稿圆、线稿方、线稿三角、线稿星}\}$,目标域 $Y$ 为相应照片。先让两域四种对象都等概率。
对每个双射 $G:X\to Y$,定义 $F=G^{-1}$。我们分别检查:
- 边际匹配:$G(X)$ 与 $Y$ 的分布 TV 距离;
- 循环一致:$F(G(x))=x$ 且 $G(F(y))=y$ 的错误率;
- 语义正确:翻译后是否仍是同一种形状。
第三个指标需要额外知道我们想保留的语义,不能从前两个指标的名字中自动得到。
查看可执行代码
import itertools
import math
from collections import Counter
SHAPES = ("circle", "square", "triangle", "star")
N = len(SHAPES)
PX = [1/N] * N
PY = [1/N] * N
PERMS = list(itertools.permutations(range(N)))
def inverse(permutation):
inv = [None] * len(permutation)
for source, target in enumerate(permutation):
inv[target] = source
return tuple(inv)
def pushforward(permutation, source_distribution):
output = [0.0] * len(permutation)
for i, j in enumerate(permutation):
output[j] += source_distribution[i]
return output
def total_variation(a, b):
return 0.5 * sum(abs(x-y) for x, y in zip(a,b))
def cycle_error(permutation):
inv = inverse(permutation)
wrong = sum(inv[permutation[i]] != i for i in range(len(permutation)))
wrong += sum(permutation[inv[j]] != j for j in range(len(permutation)))
return wrong / (2*len(permutation))
def semantic_accuracy(permutation):
return sum(i == j for i,j in enumerate(permutation))/len(permutation)
accuracies = Counter()
for perm in PERMS:
assert total_variation(pushforward(perm, PX), PY) < 1e-12
assert cycle_error(perm) == 0
accuracies[semantic_accuracy(perm)] += 1
print("bijections:", len(PERMS))
print("All have marginal TV=0 and cycle error=0.")
print("semantic accuracy -> number of equally admissible mappings")
for accuracy, count in sorted(accuracies.items()):
print(f"{accuracy:.2f} -> {count}")
assert len(PERMS) == math.factorial(4)
assert accuracies[0.0] == 9 and accuracies[1.0] == 1
查看保存的计算输出
bijections: 24 All have marginal TV=0 and cycle error=0. semantic accuracy -> number of equally admissible mappings 0.00 -> 9 0.25 -> 8 0.50 -> 6 1.00 -> 1
历史本地保存输出(2026-09-26;本轮执行记录见开篇)
bijections: 24
All have marginal TV=0 and cycle error=0.
semantic accuracy -> number of equally admissible mappings
0.00 -> 9
0.25 -> 8
0.50 -> 6
1.00 -> 1
把一个完全错误的双射看清楚
下一格让圆变方、方变三角、三角变星、星变圆;逆映射负责把它们一一还原。因此“回到原样”约束了信息不能随便丢掉,却没有独自指定哪个目标形状才是正确翻译。
注意这不是说 CycleGAN 无法学到有意义的映射;它证明的是:在此对称的数据分布与约束下,仅凭边际匹配和循环一致,目标映射不可唯一识别。实际网络架构、数据和训练过程还会引入其他偏好。
查看可执行代码
wrong_perm = (1, 2, 3, 0)
back = inverse(wrong_perm)
print("source -> translated -> reconstructed")
for i, j in enumerate(wrong_perm):
print(f"drawing {SHAPES[i]} -> photo {SHAPES[j]} -> drawing {SHAPES[back[j]]}")
print("marginal TV:", total_variation(pushforward(wrong_perm, PX), PY))
print("cycle error:", cycle_error(wrong_perm))
print("semantic accuracy:", semantic_accuracy(wrong_perm))
# 定义域用标签区分,因此可以同时在原目标域上施加 identity。
def G(tagged, perm):
domain, index = tagged
return ("Y", perm[index]) if domain == "X" else tagged
def F(tagged, perm):
domain, index = tagged
return ("X", inverse(perm)[index]) if domain == "Y" else tagged
for perm in PERMS:
identity_errors = sum(G(("Y",i),perm) != ("Y",i) for i in range(N))
identity_errors += sum(F(("X",i),perm) != ("X",i) for i in range(N))
assert identity_errors == 0
print("All 24 also admit zero identity error on their own domains in this tagged construction.")
查看保存的计算输出
source -> translated -> reconstructed drawing circle -> photo square -> drawing circle drawing square -> photo triangle -> drawing square drawing triangle -> photo star -> drawing triangle drawing star -> photo circle -> drawing star marginal TV: 0.0 cycle error: 0.0 semantic accuracy: 0.0 All 24 also admit zero identity error on their own domains in this tagged construction.
历史本地保存输出(2026-09-26;本轮执行记录见开篇)
source -> translated -> reconstructed
drawing circle -> photo square -> drawing circle
drawing square -> photo triangle -> drawing square
drawing triangle -> photo star -> drawing triangle
drawing star -> photo circle -> drawing star
marginal TV: 0.0
cycle error: 0.0
semantic accuracy: 0.0
All 24 also admit zero identity error on their own domains in this tagged construction.
加一个真正包含语义信息的条件
现在额外告诉系统:“线稿圆应该对应照片圆”。这是一对配对锚点,含有原先没有的跨域信息。它排除了一部分双射,却未必确定剩余三种形状的对应。
上一格的 identity 扩展只针对标签明确、互不相交的有限域;它展示在这个构造中 identity 约束也不能自动决定跨域语义。不能将它直接当作真实图像网络共享参数后的完整分析。
查看可执行代码
print("paired anchors | surviving bijections | worst semantic accuracy")
for anchor_count in range(N+1):
survivors = [p for p in PERMS
if all(p[i] == i for i in range(anchor_count))]
worst = min(semantic_accuracy(p) for p in survivors)
print(anchor_count, len(survivors), f"{worst:.2f}")
assert len(survivors) == math.factorial(N-anchor_count)
assert len([p for p in PERMS if p[0] == 0]) == 6
查看保存的计算输出
paired anchors | surviving bijections | worst semantic accuracy 0 24 0.00 1 6 0.25 2 2 0.50 3 1 1.00 4 1 1.00
历史本地保存输出(2026-09-26;本轮执行记录见开篇)
paired anchors | surviving bijections | worst semantic accuracy
0 24 0.00
1 6 0.25
2 2 0.50
3 1 1.00
4 1 1.00
只改分布:频率能消除歧义吗?
把两边形状频率都改成不同的 0.4、0.3、0.2、0.1,精确边际匹配确实能选出一个双射。但这利用的是“同一语义在两域频率相同”的额外条件。
接着保留源域频率,交换目标域圆与星、方与三角的频率。先预测:完美匹配频率的映射,还会是保持语义的映射吗?这是数据分布变化带来的辨识问题,不是训练不够久。
查看可执行代码
source_freq = [0.4, 0.3, 0.2, 0.1]
for label, target_freq in [("same semantic frequencies", [0.4,0.3,0.2,0.1]),
("reversed domain frequencies", [0.1,0.2,0.3,0.4])]:
matched = [p for p in PERMS
if total_variation(pushforward(p, source_freq), target_freq) < 1e-12]
print(label)
for perm in matched:
print(" G =", perm, "cycle error =", cycle_error(perm),
"semantic accuracy =", semantic_accuracy(perm))
print(" semantic identity marginal TV =", total_variation(source_freq, target_freq))
assert len(matched) == 1
print("All exhaustive marginal, inverse, anchor and frequency checks passed.")
查看保存的计算输出
same semantic frequencies G = (0, 1, 2, 3) cycle error = 0.0 semantic accuracy = 1.0 semantic identity marginal TV = 0.0 reversed domain frequencies G = (3, 2, 1, 0) cycle error = 0.0 semantic accuracy = 0.0 semantic identity marginal TV = 0.4 All exhaustive marginal, inverse, anchor and frequency checks passed.
历史本地保存输出(2026-09-26;本轮执行记录见开篇)
same semantic frequencies
G = (0, 1, 2, 3) cycle error = 0.0 semantic accuracy = 1.0
semantic identity marginal TV = 0.0
reversed domain frequencies
G = (3, 2, 1, 0) cycle error = 0.0 semantic accuracy = 0.0
semantic identity marginal TV = 0.4
All exhaustive marginal, inverse, anchor and frequency checks passed.
自己做一次反证
在不看语义评价列的情况下,仅凭循环一致与边际匹配,能选出四种形状的正确关系吗?请给出两个都满足约束、但彼此不同的映射。
然后只增加第二个配对锚点,写下还剩多少个可行解。若你选择加入几何距离或形状相似性,请明确它引入了什么额外假设。不要把这些新信息算成原损失自己发现的答案。
与论文的关系和边界
CycleGAN 原项目页说明了无配对翻译、对抗边际匹配与循环约束,也报告过标签置换一类歧义。这里用有限集合独立构造了一个可穷举反例。
本册没有训练判别器或生成器,没有展示真实图像失败率。精确分布 TV 代替了学习判别器的比较对象,因此结论是“这些约束在某些分布上不能唯一识别语义”,不是“任何 CycleGAN 都会翻错”。
接回主讲
把本册接在无配对翻译与 cycle consistency 之后。课堂可以展示一个错误双射和逆映射,其余穷举与频率实验留作主动学习。追问生成图像能否用于推断原场景事实时,要重新检查哪些信息来自输入、哪些来自生成先验。
从课堂展开:推导、反例与变量实验
先完成上面的有限例子,再按自己尚未弄清的问题选择推导。已有代码只覆盖本册明确列出的计算;后面的研究练习是可继续提出的实验,不是已经运行的结果。
原有解释按连续主题拆到下方;本入口继续保留。
两个域、两个循环,以及锚点真正增加的信息
本册有限模型把 X、Y 看作不相交的两个域。G:X→Y 与 F:Y→X 应同时满足 F(G(x))≈x 和 G(F(y))≈y,并分别检查目标域分布;不能只验一个往返方向。N26 的房屋反例讲清这一缺口,本册用四种形状穷举,N60 再选择一条新配对信息。
identity 项是在目标域上要求 G(y)≈y、在源域上要求 F(x)≈x,它不是人脸身份保证。代码允许在各自域上单独定义这些恒等行为,因此跨域错误双射仍可能通过检查;实际神经网络共享参数会带来不同约束,不能从有限构造推出真实训练的失败率。
每个配对锚点 x_i↔y_i 增加的是原先没有的对应信息。在四元素双射中,k 个不同正确锚点留下 (4-k)! 个候选;三个已固定时,第四个由双射条件决定。若不假设双射,最后这步就不能照搬。先说新证据排除了哪些候选,再讨论剩余语义。
D 步:生成图参与判断,G 暂时不改
更新判别器时,为什么要截断生成器的梯度?
一次 D 步取一批真实样本,再用当前 G 和随机 z 产生一批假样本。D 分别给两批样本打分,并按真假标签计算损失。此时目标是改进裁判,G 应保持不变;实现中常把生成图从 G 的计算图中分离。分离不是删除图片,也不是取消判别器对图片的敏感性,而是防止本次反向传播更新 G。真实与生成批次的预处理必须一致,否则 D 可能靠分辨率、裁剪或编码格式轻易识别来源。同一张假图可以当作固定输入,仍然为判别器参数提供有效训练信号。
追问:假图已经 detach,判别器还能根据它学习吗?
能。detach 只阻止梯度继续追溯到生成图的生产计算,不会改变假图的数值,也不会切断 D 自己的参数图。D 仍然能学习如何把这批输入分成真假。可以把它理解为拿到一份固定考卷:本轮修改评分规则,而不同时修改出题器;下一轮再让生成器回应新规则。
G 步:D 不更新,但梯度要穿过 D
冻结判别器,和切断判别器的计算图是一回事吗?
更新 G 时,我们再次由 z 产生图像,再用 D 对图像打分。D 的参数本步保持固定,但 D 的输出如何随输入图像改变,正是指导 G 的梯度。链式法则把损失对分数的变化、分数对图像的变化、图像对 G 参数的变化连起来。如果用 no_grad 把整个 D 前向计算包住,或者把 G 的输出 detach,反馈就无法传回 G。冻结参数与阻断计算图是两种不同操作,不能因为都叫“固定 D”而混为一谈。前向计算中的数据流和反向传播中的参数更新范围,需要分别画清楚。
追问:既然这一步不训练 D,把它放进 no_grad 应该更省显存吧?
若这样阻断了 D 到输入的导数,G 就失去了对抗反馈。我们只需要不累计或不更新 D 的参数梯度,仍须保留从 D 输出经生成图回到 G 的可微路径。省显存要先尊重依赖关系。可验证的办法是查看 G 参数是否得到非零梯度,而不是只看程序是否成功运行。
一轮交替更新,不是把对手训练到满分
D 多走几步,G 会不会必然学得更好?
GAN 的交替训练让一方更新时暂时固定另一方,但不意味着每一轮都必须求出对方的全局最优。有限算力下,我们只进行若干梯度步骤。增加 D 的更新次数,可能使区分能力更好,也可能暴露分布分离、过拟合和反馈不平衡;增加 G 步数同样可能追逐一个已过时的判别器。学习率、批量、正则化和更新比例共同影响动态。一个数据集上有效的步数比例,不应被当作所有 GAN 的定律。训练记录需要说明每一方实际执行的更新次数。
追问:理论说 D 应该最优,所以每次先把 D 训练到接近百分之百正确最合理吗?
理论分析中的最优 D 是给定分布与无限能力等条件下的工具,不等于实践中把训练集准确率刷满。训练集满分可能来自过拟合或捷径。有限网络的交替动态还取决于 G 能收到什么梯度。应在相同预算下比较多个更新方案,并检查留出数据和样本覆盖,而非追求一个准确率终点。
覆盖缺失,还是抽样太少
十六张图里没出现稀有模式,能判定模型漏学了吗?
假设某模式在模型中概率为百分之一,独立抽十六次都没遇到它的概率是 0.99 的十六次方,约为百分之八十五。一个小画廊因此很难证明模型遗漏了稀有模式。若要研究覆盖,先给出预期模式比例,再设定足够的抽样预算,并重复多个随机种子。也可以反过来问:要把“完全没看到”的概率压到某个水平,需要多少样本?这个计算把直觉批评变成了可设计的实验,同时提醒我们别把随机波动都归因于训练失败。
追问:我抽了一小批没有看到少数类,所以这个 GAN 已发生模式坍塌吗?
这是一条需要继续检查的信号,还不是充分结论。先估计少数类预期概率,并计算当前样本数下漏看的机会。再增加抽样、换种子、检查训练数据中该类是否足够。只有在合理预算和一致协议下持续缺失,才更有依据讨论模型覆盖问题,而不是一次画廊的偶然性。
固定潜变量,才看得见训练改变了什么
每轮都换一批 z,会混淆哪两件事?
训练到不同轮次时,如果潜变量也不断变化,画面差异同时包含参数更新与随机输入变化。固定一组 z 可以减少这种混淆,让同一位置的图像随训练演变。可是固定画廊也可能被过度挑选,不能独自代表全体样本。较稳妥的记录包含固定 z 快照、独立新抽样和明确的模型检查点;做条件生成时还固定条件。记录种子不意味着两个不同算法的每张图天然一一对应,因为它们对潜变量的几何解释可能完全不同。
追问:固定了随机种子,所有模型之间就已经是严格公平的逐图比较了吗?
固定种子让各自过程可重复,但不同模型的噪声维度、预处理和映射可能不同,相同种子未必对应同一潜在内容。它更适合追踪同一系统的变化。跨模型仍应统一数据、条件集合、样本量和计算预算,并比较分布统计;逐张图片只能作为有说明的补充示例。
判别器的捷径会改变生成器的学习方向
模型学到真实结构,还是只学到来源痕迹?
若真实照片经过某种压缩、假图没有,或者真图有固定边框,D 不必理解物体就能辨别来源。G 随后会被推动去复制这些痕迹。问题来自目标允许的捷径,而不一定来自网络能力不足。检查方法可以从对齐尺寸、色域、增强方式和编码流程开始,再在留出数据上观察是否保持区分能力。这里的诊断不是要求数据处理完全没有差异,而是要确认差异属于任务真正想学的分布特征,而非由管线无意引入的标签泄漏。
追问:GAN 只要训练损失下降,学到什么区分线索都没有关系吧?
损失只反映当前任务定义。若真假标签与不相关的处理痕迹绑定,优化成功可能只是学会这些痕迹;生成器会响应错误的反馈方向。应问这些线索在目标应用和留出数据上是否仍合理。通过一致预处理和受控移除捷径,才能判断观察到的进步是否属于希望学习的结构。
把条件打乱,是一个低成本诊断
模型真的利用了条件,还是只生成常见样本?
对于已有配对数据,可以保持图像不变,把条件换成另一条不匹配的描述或边缘图,再比较模型行为。若 D 对匹配与错配组合几乎没有差别,就值得检查它是否使用了条件;若 G 在不同条件下持续输出相似结果,也需调查条件通道。这个测试不是完备证明,因为有些条件差异本来无关,且错误配对可能分布过于异常。好的实验先定义应该改变和应该保持的属性,再选择具体反例,让观察能够对应一个可被证伪的假设。
追问:只要改提示后图像变化了,就说明模型正确理解条件了吗?
变化只是说明输出受输入影响,未必符合条件语义。随机扰动、数据捷径或无关颜色改变也会造成变化。应先指定可检查的目标属性,再判断它是否按预期改变,并检查无关属性是否被不必要地破坏。条件敏感与条件正确是两个层次,实验需要同时报告。
相关阅读:Image-to-Image Translation with Conditional Adversarial Networks · Vision + Language (and Foundation Models)
约束 critic,比删掉 sigmoid 更关键
把损失换成均值差,就已经实现 WGAN 了吗?
课程中的原始 WGAN 通过权重裁剪限制 critic,并配合新的目标训练。权重裁剪提供了一种实现方式,但会限制网络可表示的函数,不能被误读为精确构造了所有一阶 Lipschitz 函数。若只删除对数或 sigmoid,却省略约束,所得程序不再对应相同的理论问题。工程比较时,应记录约束方式、阈值、更新比例和网络,而不是只保留“WGAN”标签。本页聚焦上传讲义中的裁剪版本,其他正则方法应单独给出来源和假设。
追问:我已经用真实均分减去假均分,为什么还不能直接称为 Wasserstein 距离?
因为对偶表达式限制了可选评分函数的变化速度。没有相应约束,网络可以仅靠放大输出把差拉大,而不是反映分布移动的代价。有限训练的critic也只是一个近似估计器。报告应写明实际目标与约束,并谨慎区分critic损失、距离估计和生成质量,避免把三者视作同一个量。
一个二维实验,同时看见质量和覆盖
怎样设计一个不依赖大模型的 GAN 实验?
建立八个高斯簇构成的二维目标分布,固定训练样本数、网络规模与总更新预算。每个检查点同时画目标点、生成点和判别器或critic的等高线,并统计落在每个簇附近的样本数。可一次只改变G损失、更新比例或约束强度,观察梯度与覆盖如何变化。阈值与簇的定义必须事先记录,避免事后调整成好看结论。这个实验验证的是简化问题中的机制,不是模拟真实图像生成难度,也不提供跨模型的普适排行榜。
追问:二维实验里 WGAN 更稳,是否就能得出它在图像上一定更好的结论?
不能。二维分布可精确观察模式,因此适合理解目标几何和优化机制;真实图像还涉及表示、数据规模、网络和评价。你可以得出在记录的二维设定与预算下某配置更稳,并提出需要下一步检验的原因。将局部证据升级为跨任务结论,需要额外实验,而不是换一张更大的图。
加了随机噪声,模型也可能不理它
为什么条件生成器不一定产生丰富多解?
若配对训练总要求输出贴近一个参考,生成器可能更愿意依赖条件,而把额外噪声忽略。pix2pix原论文就讨论了这种现象,其实现使用dropout后仍只观察到有限随机性。这提醒我们:有 z 的接口不是多样性的证据。需要固定条件、重复改变随机输入,再观察差异是否既有意义又符合条件。若差异只是细小噪点,也不能视作捕捉了全部合理变化。表达与训练一个丰富条件分布,需要比“多加一个随机向量”更明确的设计。
追问:我的生成器输入有 z,所以报告可以直接写模型支持多模态输出吧?
接口只能说明模型有机会使用随机性。你需要证明在同一条件下改变z会得到不同且有效的结果,并检查变化覆盖了哪些语义因素。如果G忽略z,或者只改变无关细节,多解能力仍有限。报告应展示受控采样与评价,而不是从结构图推断已获得的行为。
相关阅读:Image-to-Image Translation with Conditional Adversarial Networks
一个可逆的错误翻译,也能通过循环检查
为什么非配对翻译存在不可辨识性?
构造一个精确玩具例:X和Y各有三个等概率符号,预期关系是按颜色对应。任意排列都能把X的均匀分布映成Y的均匀分布;只要F取该排列的逆,双循环误差就为零。一个把红色对到蓝色的错误排列,因此同时满足域分布与循环条件。这是我们重构的有限例子,用来说明目标无法独自识别语义。增加少量配对、标签或结构约束可能减少歧义,但应明确加入了什么额外信息,不能将其成功归功于循环本身。
追问:如果对抗和循环都达到零误差,为什么还可能不是我们想要的翻译?
因为损失只检查它被定义要检查的条件。边际匹配关心输出总体频率,循环关心可逆性;它们没有写入红色应对应红色等语义规定。多个映射可以同时满足目标。这个反例不否定方法的实用性,而是指出成功还依赖数据结构、网络偏置或额外监督,需另行验证。
相关阅读:Unpaired Image-to-Image Translation using Cycle-Consistent Adversarial Networks
CycleGAN 消融:每次只拿走一个约束
怎样判断成功来自哪一项设计?
一个可复查的消融实验保留相同训练集、网络规模、预处理与预算,比较完整目标、去循环项、去对抗项,以及加入或去掉identity项。评价不能只列总损失,因为各版本的目标本就不同;应另行检查目标域真实性、输入结构保持、循环误差和非必要颜色变化。使用固定测试输入并展示失败切片,能够减少挑图偏差。若只做课堂规划而未训练,应明确标为待验证假设,不把预期效果画成已经得到的曲线。
追问:删除一个损失后总损失更小,是否说明删掉它让模型更好了?
不能直接比较,因为你改变了被加总的项目和优化问题。少一项后数值更小可能只是算式变了。应使用共同的外部评价协议,比域外观、结构保持与任务正确性等行为。消融的目的是理解某项设计带来的约束,而不是在不同定义的训练损失之间选最低值。
相关阅读:Unpaired Image-to-Image Translation using Cycle-Consistent Adversarial Networks
样式混合与随机细节,是两种不同干预
换一部分 w 和换噪声,应该分别观察什么?
样式混合可以让合成网络的部分层使用样本A的中间表示,其他层使用样本B的表示,用于观察不同尺度控制的作用。另一种实验保持样式不变,只改变注入的随机噪声,考察局部细节如何变化。这两种操作检验的问题不同:前者研究分层控制,后者研究随机细节与全局结构的分离。若同时改动样式与噪声,就难以归因。示例中的语义规律应从实际输出总结,而不是事先把生成器说成完全解耦的可控绘图系统。
追问:两张图混合后出现想要的发型,所以这个潜空间已经完全解耦了吗?
一次成功混合只能说明该操作在这些样本上表现出某种可控性。完全解耦是更强主张,需要跨样本、跨属性检查是否有副作用。可以固定噪声和未编辑层,系统改变指定层并记录身份、姿态等变化。保留不成功案例,才能看出控制范围和属性耦合,而不是被精选例图误导。
相关阅读:A Style-Based Generator Architecture for Generative Adversarial Networks