Stable Diffusion 训练实战:损失函数调优指南
为啥你跑出来的图总像隔夜饭?
先说个真事:上周隔壁组的小李,吭哧吭哧训了三天,出来的妹图脸像被擀面杖压过,背景糊得能当马赛克用。他当场破防,把键盘拍得啪啪响:'老子的 A100 白烧了?'我凑过去瞟一眼 loss 曲线,好家伙,跟过山车似的,最后一段直接蹦迪——典型的损失函数没喂对料。
Stable Diffusion 这玩意儿,模型骨架再壮,损失函数要是拉胯,就像给米其林大厨一把钝刀,顶级和牛也能切成抹布。很多人以为炼图=堆算力,其实真正的隐藏 BOSS 是损失函数:它悄悄告诉模型'哪好哪坏',它要是瞎,模型就敢把眼睛画到后脑勺。
扩散模型里那些看不见的裁判到底藏哪儿了?
先补一嘴原理,别怕,就两行:前向过程=给图狂加噪,直到变纯雪花;反向过程=教模型从雪花一步步猜回高清图。损失函数就在反向阶段蹲着,每步都拿'模型猜的噪声'和'真实加的噪声'比对,算个差,回传梯度。差得越小,说明模型越会'去噪',最后出来的图就越接近人间烟火。
官方默认用MSE(L2),公式小学数学:
loss = (ε_pred - ε_gt)²
看着人畜无害,实则佛得离谱——它只管像素级'均方',才不管边缘锐不锐、颜色绿不绿。于是模型躺平:反正平均误差小就行,高频细节?随缘吧,糊就糊,安全牌。
L2、L1、感知损失…谁才是真·画质担当?
把常见损失拉出来遛遛,先上代码,边遛边吐槽。(以下全基于 diffusers 库,自己训 LoRA/全量都行,改一行 loss 就能跑)
1. L2(MSE)——'老实人'
# 默认就是它,不用你写
loss = F.mse_loss(noise_pred, noise_target)
优点:稳,收敛快,适合 warmup。缺点:边缘糊成毛边纸,皮肤像开了十级磨皮,连毛孔都投降。
2. L1——'细节狂魔'
loss = F.l1_loss(noise_pred, noise_target)
绝对值误差,对异常值不敏感,边缘锐利一丢丢,但训练后期容易抖,loss 曲线跟帕金森似的。小窍门:先 L2 训 5000 步,再切 L1,世界瞬间清晰。
3. 感知损失(LPIPS)——'带眼神的裁判'
import lpips
loss_fn_vgg = lpips.LPIPS(net='vgg').cuda()
# 注意要把去噪后的图还原到 [0,1]
x0_pred = (x - sigma * noise_pred) / alpha
x0_pred = torch.clamp(x0_pred, -1, 1) * 0.5 + 0.5
x0_target = (x - sigma * noise_target) / alpha
x0_target = torch.clamp(x0_target, -1, 1) * 0.5 + 0.5
perceptual_loss = loss_fn_vgg(x0_pred, x0_target).mean()
这货拉来 VGG 当评委,看'语义'像不像,而不是死磕像素。结果:皮肤纹理、头发丝、睫毛都被鞭打,模型被迫学高频。缺点也酸爽——慢,显存直接 +3G,穷人慎入。
4. CLIP 损失——'人味儿提款机'
import open_clip
model, _, preprocess = open_clip.create_model_and_transforms(, pretrained=)
tokenizer = open_clip.get_tokenizer()
text = tokenizer([])
torch.no_grad():
text_feat = model.encode_text(text.cuda())
img_pred = F.interpolate(x0_pred, size=, mode=)
img_pred_feat = model.encode_image(img_pred)
clip_loss = -torch.cosine_similarity(text_feat, img_pred_feat).mean()


