|
|
本帖最后由 day270010678 于 2026-11-11 23:24 编辑
- import json, sys
- sys.path.insert(0, r'D:\AI\Inswapper')
- sys.path.insert(0, r'D:\AI\Inswapper\faceswap')
- from pathlib import Path
- import cv2, numpy as np, torch
- from faceswap.business.saehd_trainer import TrainingConfig
- from faceswap.models.saehd.saehd_model import SAEHDModel
- md = Path(r'D:\AI\Inswapper\workspace\model\saehd\ZIBI06_df-ud')
- cfg = TrainingConfig.from_dict(json.loads((md / 'training_config.json').read_text(encoding='utf-8')))
- dev = torch.device('cuda')
- m = SAEHDModel(cfg, md, dev); m._set_eval()
- RES = int(m.resolution)
- al = Path(r'D:\AI\Inswapper\workspace\data_dst\aligned')
- bgr = cv2.resize(cv2.imread(str(sorted(al.iterdir())[0])), (RES, RES), interpolation=cv2.INTER_LANCZOS4)
- def to_t(npbgr):
- return torch.from_numpy(npbgr[:, :, ::-1].copy()).permute(2, 0, 1).float().div(255).unsqueeze(0).to(dev)
- def shifted(dx):
- M = np.float32([[1, 0, float(dx)], [0, 1, 0]])
- out = cv2.warpAffine(bgr, M, (int(RES), int(RES)), flags=cv2.INTER_CUBIC, borderMode=cv2.BORDER_REFLECT)
- return to_t(out)
- x0 = to_t(bgr)
- with torch.no_grad():
- e0 = m.encoder(x0)
- # dense2 输出(真正的 code, 8x8) 与 upscale1 输出(16x16)
- with torch.amp.autocast(device_type='cuda', enabled=False):
- d1 = m.inter.dense1(e0.float())
- d2 = m.inter.dense2(d1)
- code8 = d2.view(1, m.inter.ae_out_ch, m.inter.lowest_dense_res, m.inter.lowest_dense_res)
- z0 = m.inter(e0)
- E0 = e0[0].view(m.encoder.get_out_ch(), m.encoder.get_out_res(), m.encoder.get_out_res())
- print('encoder flat =', tuple(e0.shape), ' | code8 =', tuple(code8.shape), ' | inter out =', tuple(z0.shape))
- C8, C16, EF = {}, {}, {}
- for dx in (-32, -24, -16, -8, 8, 16, 24, 32):
- xd = shifted(dx)
- with torch.no_grad():
- ee = m.encoder(xd)
- with torch.amp.autocast(device_type='cuda', enabled=False):
- dd1 = m.inter.dense1(ee.float()); dd2 = m.inter.dense2(dd1)
- c8 = dd2.view(1, m.inter.ae_out_ch, m.inter.lowest_dense_res, m.inter.lowest_dense_res)
- zz = m.inter(ee)
- C8[dx] = (c8 - code8)[0].float().cpu().numpy()
- C16[dx] = (zz - z0)[0].float().cpu().numpy()
- EF[dx] = (ee[0].view(m.encoder.get_out_ch(), m.encoder.get_out_res(), m.encoder.get_out_res())
- - E0).float().cpu().numpy()
- print('\n=== 每个通道的最强能量(看 Δ 是否有空间结构) ===')
- for name, D in (('code8', C8), ('enc(reshaped)', EF)):
- e = np.abs(D[16]).mean(axis=(1, 2))
- print(f'{name}: 通道能量 min={e.min():.4f} max={e.max():.4f} 均值={e.mean():.4f}')
- print('\n=== phaseCorrelate: Δ 图相对基准是否整体位移 ===')
- def pc(a, b):
- a = a.astype(np.float32); b = b.astype(np.float32)
- a = a - a.mean(); b = b - b.mean()
- win = cv2.createHanningWindow((a.shape[1], a.shape[0]), cv2.CV_32F)
- try:
- (sx, sy), r = cv2.phaseCorrelate(a, b, win)
- return sx, sy, r
- except Exception as ex:
- return float('nan'), float('nan'), float('nan')
- for c in range(min(6, m.inter.ae_out_ch)):
- for dx in (16, 32):
- sx, sy, r = pc(C8[8][c], C8[dx][c])
- print(f' code8 ch{c}: (+8 vs +{dx}) -> ({sx:+.2f},{sy:+.2f}) resp={r:.3f}')
- print('\n=== 对照: encoder 特征图(卷积输出应表现为整体位移) ===')
- # encoder 通道数 768, res 16 -> 每通道位移应 ~dx/16 (因为 256->16 下采样)
- for c in [0, 100, 300, 600]:
- for dx in (16, 32):
- sx, sy, r = pc(EF[8][c], EF[dx][c])
- print(f' enc ch{c}: (+8 vs +{dx}) -> ({sx:+.2f},{sy:+.2f}) resp={r:.3f} (理论≈{(dx-8)/16:.2f})')
- print('\n=== Δz 与 Δz 同一 dx 不同通道之间的关系: 排序稳定性 ===')
- # 若 code 有位置轴, Δ(code8) 沿 dx 应有稳定的主方向
- for dx in (8, 16, 32):
- flat = C8[dx].reshape(-1)
- print(f' code8 dx={dx:+d}: |Δ|={np.linalg.norm(flat):.4f}')
- print('\nDONE')
- 2026-11-11 23:04:37 [saehd_model] [INFO] 构建优化器: 86 个参数张量 / 1 组, 初始 lr=5.00e-05; state 待加载(checkpoint 存在时将由 try_load 恢复)
- 2026-11-11 23:04:38 [base_model] [INFO] 优化器 state(src_dst_opt.pth): 86/86 参数动量已恢复, step=3301, lr=[5e-05]
- encoder flat = (1, 196608) | code8 = (1, 384, 8, 8) | inter out = (1, 384, 16, 16)
- === 每个通道的最强能量(看 Δ 是否有空间结构) ===
- code8: 通道能量 min=9.4113 max=21.9896 均值=13.6229
- enc(reshaped): 通道能量 min=0.0121 max=4.9938 均值=0.0488
- === phaseCorrelate: Δ 图相对基准是否整体位移 ===
- code8 ch0: (+8 vs +16) -> (+0.41,+0.38) resp=0.901
- code8 ch0: (+8 vs +32) -> (+9.09,+6.82) resp=0.093
- code8 ch1: (+8 vs +16) -> (-0.37,-0.29) resp=0.941
- code8 ch1: (+8 vs +32) -> (-0.70,-2.47) resp=0.719
- code8 ch2: (+8 vs +16) -> (+3.73,-0.40) resp=0.209
- code8 ch2: (+8 vs +32) -> (+3.23,-0.45) resp=0.211
- code8 ch3: (+8 vs +16) -> (+0.68,+3.31) resp=-0.311
- code8 ch3: (+8 vs +32) -> (-0.80,+1.77) resp=-0.940
- code8 ch4: (+8 vs +16) -> (+0.84,-0.38) resp=0.709
- code8 ch4: (+8 vs +32) -> (-1.83,+1.23) resp=-0.196
- code8 ch5: (+8 vs +16) -> (-0.49,-0.03) resp=0.890
- code8 ch5: (+8 vs +32) -> (-0.15,-0.95) resp=1.263
- === 对照: encoder 特征图(卷积输出应表现为整体位移) ===
- enc ch0: (+8 vs +16) -> (+1.49,-3.88) resp=0.264 (理论≈0.50)
- enc ch0: (+8 vs +32) -> (+7.70,-10.92) resp=0.080 (理论≈1.50)
- enc ch100: (+8 vs +16) -> (+0.42,-0.65) resp=0.788 (理论≈0.50)
- enc ch100: (+8 vs +32) -> (+0.93,-0.81) resp=0.667 (理论≈1.50)
- enc ch300: (+8 vs +16) -> (+0.41,+0.24) resp=0.874 (理论≈0.50)
- enc ch300: (+8 vs +32) -> (+1.08,+0.31) resp=0.840 (理论≈1.50)
- enc ch600: (+8 vs +16) -> (-1.25,+0.04) resp=0.327 (理论≈0.50)
- enc ch600: (+8 vs +32) -> (-0.60,-0.74) resp=0.214 (理论≈1.50)
- === Δz 与 Δz 同一 dx 不同通道之间的关系: 排序稳定性 ===
- code8 dx=+8: |Δ|=1509.4431
- code8 dx=+16: |Δ|=2746.1697
- 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,等这里验证完毕了,再考虑在现有的基础上再建立个新架构。不过这里的分析确实给我当初的设想做了个验证。
|
|