deepfacelab中文网

 找回密码
 立即注册(仅限QQ邮箱)
查看: 218|回复: 7

下巴褶皱,鼻唇沟等这种高频细节

[复制链接]

47

主题

294

帖子

2306

积分

初级丹圣

Rank: 8Rank: 8

积分
2306
 楼主| 发表于 3 天前 | 显示全部楼层 |阅读模式
星级打分
  • 1
  • 2
  • 3
  • 4
  • 5
平均分:NAN  参与人数:0  我的评分:未评
本帖最后由 day270010678 于 2026-11-9 18:49 编辑

组
参数量
瓶颈维数
下巴 ridge 保留
鼻唇沟 ridge(左 / 右)
SD 保留
NCC
BASE(2× 降采样再上采)——99.7%99.6% / 99.5%99.9%1.000
SPATIAL4.01 M6,688(32ch@11×19,有位置)89.6%91.6% / 90.2%99.8%0.986~0.990
GLOBAL65.70 M384(无位置,与 df-du 同构)75.0%75.6% / 68.8%89.5~92.2%0.917~0.937

roi_ae_01337.png
瓶颈是 “空间自由度”,而不是参数量、也不是分辨率。通过我的pytorch测试,跟dfl的ae结构一致,所以我的这个测试,dfl也应该完全适应。
训练曲线也一致:2500 步后 SPATIAL 的 HF 损失 0.0094,GLOBAL 是 0.0140。

1. 瓶颈是 “有没有空间自由度”,不是参数量。 GLOBAL 参数量是 SPATIAL 的 16 倍,瓶颈维数却只有 1/17,结果 ridge 反而低 15~21pp。这条对照直接排除了 “加宽通道 / 加参数就能出褶皱” 的路子。

2. ROI 旁路的容量成立。 只给 6,688 个数(有空间维度),褶皱的脊就能恢复到 90%。对比真实模型的全脸 384 维只有 37.5%。

3. 分辨率在这个频带上不是杀手。 BASE(把 ROI 降到一半再升回来)保留了 99.9% 的 ridge—— 因为褶皱在 512 空间里是 12~25px,2× 重采样只影响 <4px 的成分。所以 “把模型从 256 改到 512” 治不了这个问题,只有 “让 ROI 走独立高分辨率支路” 才有用。

4. 但也别指望一步到位。 SPATIAL 仍差约 10% ridge,视觉上比 BASE 略软(l1 0.031 ≈ 8/255)。说明表示层能拿回大约七成(37.5% → 90%),剩下的靠更强的监督(对抗)或更多步数。

TARGET / BASE / SPATIAL / GLOBAL 四列(01337、01653)。GLOBAL 那列明显更平、更灰 —— 它就是 “384 维 + 8×8 网格” 的指纹。

监督目标,有两种方法:

(a) 重建优先型:refiner 挂在 `decoder_dst` 后面,输入 = 256 预测上采样的 ROI + warped dst 的高频带 + mask,输出 ROI 的 512 细化。先生命的是 4 列(重建)能画出褶皱,swap 列靠 warped dst 的高频自然继承。

(b) 细节迁移型:直接面向最终目标,把 dst 的褶皱形状迁到 src 的皮肤上。好处是一步到位,坏处是褶皱和皮肤纹理(毛孔、肤龄)在同一条 1.5–8px 频带里,网络很容易把 dst 的皮肤也一起搬过来 —— 那就变成 “src 的脸 + dst 的皮”,不是换脸。



测试的模型是这里的随便找的一个https://dfldata.cc/forum.php?mod=viewthread&tid=18758
回复

使用道具 举报

19

主题

217

帖子

1669

积分

初级丹圣

Rank: 8Rank: 8

积分
1669
发表于 昨天 09:44 | 显示全部楼层
如果瓶頸特別寬,重建的圖像會更清晰,相應的學習換臉的能力會變差
反之窄的話,重建的圖像會油畫質感,相應的換臉能力會更好
iperov的4~5層下採樣就是均衡考慮的
此外iperov的5e-5的lr也是只為這個下採樣考慮的通用局部優解
B站菜级玩家
回复 支持 反对

使用道具 举报

19

主题

217

帖子

1669

积分

初级丹圣

Rank: 8Rank: 8

积分
1669
发表于 昨天 10:00 | 显示全部楼层
此外還有,iperov設計的數據增強參數,隨機旋轉和隨機xy偏移。這個設計出來是為了彌补特徵點對齊精度不足而導致輸入數據有抖動的情況。如果把這兩個參數關掉,你可以發現即使是窄瓶頸,也可以重建高清圖像。但是這時候模型就不能換臉了。
出現油畫感的原因就是L1Loss為了讓模型學習像素那個位置什麼顏色,模型為了偷懶快速下降loss,學習的是這個位置的像素的平均顏色。因為上面説的隨機偏移和隨機旋轉的關係,模型看見的這個地方的像素每次都不同,它會更傾向去找一個平均值,而不是根據已經展平的code(因為做了一次增秩,展平為張量,code裡面沒有空間位置信息了)去猜那裡一定是有這個細節。
所以不是説ae架構,以及瓶頸的問題導致他沒有細節,而是説在這種數據增強策略下,模型学的是重建平均值,而不是真細節。此外這個細節重建即使你開tf64精度算都是算得平均值的,這個和訓練精度沒有關係,是L1Loss的目標讓他去学的平均值
B站菜级玩家
回复 支持 反对

使用道具 举报

47

主题

294

帖子

2306

积分

初级丹圣

Rank: 8Rank: 8

积分
2306
 楼主| 发表于 昨天 20:46 | 显示全部楼层
本帖最后由 day270010678 于 2026-11-11 20:47 编辑
caiji132156 发表于 2026-11-11 10:00
此外還有,iperov設計的數據增強參數,隨機旋轉和隨機xy偏移。這個設計出來是為了彌补特徵點對齊精度不足而 ...

你这句话在哪里看到的,我测试的结果跟他的不同啊,他说“位置 - 像素错位“,这是数据多样性,不是错位,“”code 里没有空间位置信息了“”我这里Encoder.forward:   x = torch.flatten(x, 1)      # 4D → 2D, 降维Inter.forward:x.view(b, ae_out_ch, 16, 16) # 立刻恢复 16×16,位置以 flatten 的索引顺序完整保留,view一步就还原成特征图,后面接的是卷积。丢的不是 “位置”,是分辨率。我测试了:把输入整体平移 8px,输出跟着平移 7.9px;平移 16px---13.8~15.8;平移 32px---31.1~32.5。如果 code 里真没有位置信息,输出根本不会跟着输入走。:
回复 支持 反对

使用道具 举报

19

主题

217

帖子

1669

积分

初级丹圣

Rank: 8Rank: 8

积分
1669
发表于 昨天 22:02 | 显示全部楼层
day270010678 发表于 2026-11-11 20:46
你这句话在哪里看到的,我测试的结果跟他的不同啊,他说“位置 - 像素错位“,这是数据多样性,不是错位, ...

你可以去编辑code的,自己看看哪个code会影响偏移
B站菜级玩家
回复 支持 反对

使用道具 举报

19

主题

217

帖子

1669

积分

初级丹圣

Rank: 8Rank: 8

积分
1669
发表于 昨天 22:17 | 显示全部楼层
本帖最后由 caiji132156 于 2026-11-11 22:18 编辑

day270010678 发表于 2026-11-11 20:46
你这句话在哪里看到的,我测试的结果跟他的不同啊,他说“位置 - 像素错位“,这是数据多样性,不是错位, ...


就數學上來説,你inter都展平并且做了綫性混合了,哪來的精確坐標信息。你説的是“空間結構信息”
展平 \(F\mapsto \mathrm{vec}(F)\in\mathbb{R}^{HWC}\) 本身是雙射(只是重排索引),信息不丟失。問題不在展平,而在展平之後緊接著做的線性混合(全連接/線性映射 \(W\)):
z' = W\,\mathrm{vec}(F),\quad W\in\mathbb{R}^{d'\times HWC}.
這個 \(W\) 把所有空間位置的特徵線性混合到一起。數學後果是:
原來第 \((i,j)\) 個位置的特徵 \(f_{ij}\) 對應 vec 中的一段坐標索引;
經過 \(W\) 後,\(z'\) 的每一維都是所有位置特徵的線性組合:
z'_k = \sum_{i,j,c} W_{k,(i,j,c)}\, F_{ijc}.
於是單個維度 \(z'_k\) 不再對應任何單一空間位置,它攜帶的是全局的、位置混合後的統計量。要從 \(z'\) 恢復 \((i,j)\),需要解一個欠定/超定的線性系統,而且只有當 \(W\) 恰好保留了位置可分的子空間時才可能

簡單點來説,inter就把帶有精確坐標信息的編碼器輸出,通過綫性映射讓每個code之間互相關聯。
精確坐標需要 code 中存在能唯一標識 \((i,j)\) 的分量,即存在線性泛函 \(a\) 使 \(a^\top z' = i\)(或 \(j\))。這要求 \(W\) 的行空間包含「位置指示」方向。展平+全局線性混合後,每個 \(z'_k\) 都是全圖加權和,位置指示方向被平均掉,絕對定位信息在 \(W\) 的零空間或高度混合的方向中丟失

所以你自己去編輯inter輸出的code中的任意一維,在只編輯一個code維度的情況下,不會固定往一個方向偏移。也就是説,沒有單個維度,可以控制輸出圖像的X,Y坐標
B站菜级玩家
回复 支持 反对

使用道具 举报

19

主题

217

帖子

1669

积分

初级丹圣

Rank: 8Rank: 8

积分
1669
发表于 昨天 22:29 | 显示全部楼层
day270010678 发表于 2026-11-11 20:46
你这句话在哪里看到的,我测试的结果跟他的不同啊,他说“位置 - 像素错位“,这是数据多样性,不是错位, ...

你測試出來的結果和我說法不是矛盾的,實際上輸出跟隨平移可由端到端等變性實現,不需要code顯式存座標。即使 code 有某種位置相關結構,也不等於有精確座標信息。
你測得的數據,單純是解碼器的功勞。L1 Loss讓解碼器去讓輸出和輸入的像素差不多,也就是其實解碼器有從code裏面,隱式提取坐標的能力
B站菜级玩家
回复 支持 反对

使用道具 举报

47

主题

294

帖子

2306

积分

初级丹圣

Rank: 8Rank: 8

积分
2306
 楼主| 发表于 昨天 23:21 | 显示全部楼层
本帖最后由 day270010678 于 2026-11-11 23:24 编辑
caiji132156 发表于 2026-11-11 22:17
day270010678 发表于 2026-11-11 20:46
你这句话在哪里看到的,我测试的结果跟他的不同啊,他说“位置 - 像 ...
  1. import json, sys
  2. sys.path.insert(0, r'D:\AI\Inswapper')
  3. sys.path.insert(0, r'D:\AI\Inswapper\faceswap')
  4. from pathlib import Path
  5. import cv2, numpy as np, torch
  6. from faceswap.business.saehd_trainer import TrainingConfig
  7. from faceswap.models.saehd.saehd_model import SAEHDModel

  8. md = Path(r'D:\AI\Inswapper\workspace\model\saehd\ZIBI06_df-ud')
  9. cfg = TrainingConfig.from_dict(json.loads((md / 'training_config.json').read_text(encoding='utf-8')))
  10. dev = torch.device('cuda')
  11. m = SAEHDModel(cfg, md, dev); m._set_eval()
  12. RES = int(m.resolution)

  13. al = Path(r'D:\AI\Inswapper\workspace\data_dst\aligned')
  14. bgr = cv2.resize(cv2.imread(str(sorted(al.iterdir())[0])), (RES, RES), interpolation=cv2.INTER_LANCZOS4)

  15. def to_t(npbgr):
  16.     return torch.from_numpy(npbgr[:, :, ::-1].copy()).permute(2, 0, 1).float().div(255).unsqueeze(0).to(dev)

  17. def shifted(dx):
  18.     M = np.float32([[1, 0, float(dx)], [0, 1, 0]])
  19.     out = cv2.warpAffine(bgr, M, (int(RES), int(RES)), flags=cv2.INTER_CUBIC, borderMode=cv2.BORDER_REFLECT)
  20.     return to_t(out)

  21. x0 = to_t(bgr)
  22. with torch.no_grad():
  23.     e0 = m.encoder(x0)
  24.     # dense2 输出(真正的 code, 8x8) 与 upscale1 输出(16x16)
  25.     with torch.amp.autocast(device_type='cuda', enabled=False):
  26.         d1 = m.inter.dense1(e0.float())
  27.         d2 = m.inter.dense2(d1)
  28.         code8 = d2.view(1, m.inter.ae_out_ch, m.inter.lowest_dense_res, m.inter.lowest_dense_res)
  29.     z0 = m.inter(e0)
  30.     E0 = e0[0].view(m.encoder.get_out_ch(), m.encoder.get_out_res(), m.encoder.get_out_res())
  31.     print('encoder flat =', tuple(e0.shape), ' | code8 =', tuple(code8.shape), ' | inter out =', tuple(z0.shape))

  32. C8, C16, EF = {}, {}, {}
  33. for dx in (-32, -24, -16, -8, 8, 16, 24, 32):
  34.     xd = shifted(dx)
  35.     with torch.no_grad():
  36.         ee = m.encoder(xd)
  37.         with torch.amp.autocast(device_type='cuda', enabled=False):
  38.             dd1 = m.inter.dense1(ee.float()); dd2 = m.inter.dense2(dd1)
  39.             c8 = dd2.view(1, m.inter.ae_out_ch, m.inter.lowest_dense_res, m.inter.lowest_dense_res)
  40.         zz = m.inter(ee)
  41.     C8[dx] = (c8 - code8)[0].float().cpu().numpy()
  42.     C16[dx] = (zz - z0)[0].float().cpu().numpy()
  43.     EF[dx] = (ee[0].view(m.encoder.get_out_ch(), m.encoder.get_out_res(), m.encoder.get_out_res())
  44.               - E0).float().cpu().numpy()

  45. print('\n=== 每个通道的最强能量(看 Δ 是否有空间结构) ===')
  46. for name, D in (('code8', C8), ('enc(reshaped)', EF)):
  47.     e = np.abs(D[16]).mean(axis=(1, 2))
  48.     print(f'{name}: 通道能量 min={e.min():.4f} max={e.max():.4f} 均值={e.mean():.4f}')

  49. print('\n=== phaseCorrelate: Δ 图相对基准是否整体位移 ===')
  50. def pc(a, b):
  51.     a = a.astype(np.float32); b = b.astype(np.float32)
  52.     a = a - a.mean(); b = b - b.mean()
  53.     win = cv2.createHanningWindow((a.shape[1], a.shape[0]), cv2.CV_32F)
  54.     try:
  55.         (sx, sy), r = cv2.phaseCorrelate(a, b, win)
  56.         return sx, sy, r
  57.     except Exception as ex:
  58.         return float('nan'), float('nan'), float('nan')

  59. for c in range(min(6, m.inter.ae_out_ch)):
  60.     for dx in (16, 32):
  61.         sx, sy, r = pc(C8[8][c], C8[dx][c])
  62.         print(f'  code8 ch{c}: (+8 vs +{dx}) -> ({sx:+.2f},{sy:+.2f}) resp={r:.3f}')

  63. print('\n=== 对照: encoder 特征图(卷积输出应表现为整体位移) ===')
  64. # encoder 通道数 768, res 16 -> 每通道位移应 ~dx/16 (因为 256->16 下采样)
  65. for c in [0, 100, 300, 600]:
  66.     for dx in (16, 32):
  67.         sx, sy, r = pc(EF[8][c], EF[dx][c])
  68.         print(f'  enc ch{c}: (+8 vs +{dx}) -> ({sx:+.2f},{sy:+.2f}) resp={r:.3f}  (理论≈{(dx-8)/16:.2f})')

  69. print('\n=== Δz 与 Δz 同一 dx 不同通道之间的关系: 排序稳定性 ===')
  70. # 若 code 有位置轴, Δ(code8) 沿 dx 应有稳定的主方向
  71. for dx in (8, 16, 32):
  72.     flat = C8[dx].reshape(-1)
  73.     print(f'  code8 dx={dx:+d}: |Δ|={np.linalg.norm(flat):.4f}')
  74. print('\nDONE')

  75. 2026-11-11 23:04:37 [saehd_model] [INFO] 构建优化器: 86 个参数张量 / 1 组, 初始 lr=5.00e-05; state 待加载(checkpoint 存在时将由 try_load 恢复)
  76. 2026-11-11 23:04:38 [base_model] [INFO] 优化器 state(src_dst_opt.pth): 86/86 参数动量已恢复, step=3301, lr=[5e-05]
  77. encoder flat = (1, 196608)  | code8 = (1, 384, 8, 8)  | inter out = (1, 384, 16, 16)

  78. === 每个通道的最强能量(看 Δ 是否有空间结构) ===
  79. code8: 通道能量 min=9.4113 max=21.9896 均值=13.6229
  80. enc(reshaped): 通道能量 min=0.0121 max=4.9938 均值=0.0488

  81. === phaseCorrelate: Δ 图相对基准是否整体位移 ===
  82.   code8 ch0: (+8 vs +16) -> (+0.41,+0.38) resp=0.901
  83.   code8 ch0: (+8 vs +32) -> (+9.09,+6.82) resp=0.093
  84.   code8 ch1: (+8 vs +16) -> (-0.37,-0.29) resp=0.941
  85.   code8 ch1: (+8 vs +32) -> (-0.70,-2.47) resp=0.719
  86.   code8 ch2: (+8 vs +16) -> (+3.73,-0.40) resp=0.209
  87.   code8 ch2: (+8 vs +32) -> (+3.23,-0.45) resp=0.211
  88.   code8 ch3: (+8 vs +16) -> (+0.68,+3.31) resp=-0.311
  89.   code8 ch3: (+8 vs +32) -> (-0.80,+1.77) resp=-0.940
  90.   code8 ch4: (+8 vs +16) -> (+0.84,-0.38) resp=0.709
  91.   code8 ch4: (+8 vs +32) -> (-1.83,+1.23) resp=-0.196
  92.   code8 ch5: (+8 vs +16) -> (-0.49,-0.03) resp=0.890
  93.   code8 ch5: (+8 vs +32) -> (-0.15,-0.95) resp=1.263

  94. === 对照: encoder 特征图(卷积输出应表现为整体位移) ===
  95.   enc ch0: (+8 vs +16) -> (+1.49,-3.88) resp=0.264  (理论≈0.50)
  96.   enc ch0: (+8 vs +32) -> (+7.70,-10.92) resp=0.080  (理论≈1.50)
  97.   enc ch100: (+8 vs +16) -> (+0.42,-0.65) resp=0.788  (理论≈0.50)
  98.   enc ch100: (+8 vs +32) -> (+0.93,-0.81) resp=0.667  (理论≈1.50)
  99.   enc ch300: (+8 vs +16) -> (+0.41,+0.24) resp=0.874  (理论≈0.50)
  100.   enc ch300: (+8 vs +32) -> (+1.08,+0.31) resp=0.840  (理论≈1.50)
  101.   enc ch600: (+8 vs +16) -> (-1.25,+0.04) resp=0.327  (理论≈0.50)
  102.   enc ch600: (+8 vs +32) -> (-0.60,-0.74) resp=0.214  (理论≈1.50)

  103. === Δz 与 Δz 同一 dx 不同通道之间的关系: 排序稳定性 ===
  104.   code8 dx=+8: |Δ|=1509.4431
  105.   code8 dx=+16: |Δ|=2746.1697
  106.   code8 dx=+32: |Δ|=4172.0688
复制代码
你的这个数学论证确实是对的,我刚才的只能证明端到端等变,不能证明code有坐标。|Δz|对位移是亚线性的(4 倍位移只带来 2.76 倍变化),说明 code 对平移的响应在饱和,这更不是 "线性坐标" 该有的样子。


encoder(卷积, 保留局部) --- flatten ----dense1 ---- dense2(全连接, 全局混合) ----- code(8×8)
                                                                                                    位置信息在这里被抹平
                                                                                   decoder(卷积, 重新施加平移等变)


bottleneck 那一刻,每个 code 单元都是全图加权和,没有局部性,decoder 只能 "凭整体的统计量去重建细节",细粒度位置信息在进去时就已经丢了,这很可能就是皮肤褶皱、口腔结构这类 "高频、局部" 细节重建不出来的架构级原因之一。

其实我在新的架构中就打算采用两种:inter 换成卷积和decoder 加 skip,等这里验证完毕了,再考虑在现有的基础上再建立个新架构。不过这里的分析确实给我当初的设想做了个验证。

回复 支持 反对

使用道具 举报

QQ|Archiver|手机版|deepfacelab中文网 |网站地图

GMT+8, 2026-11-12 16:32 , Processed in 0.104884 second(s), 32 queries .

Powered by Discuz! X3.4

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表