Skip to content

Latest commit

 

History

History
202 lines (144 loc) · 6.01 KB

File metadata and controls

202 lines (144 loc) · 6.01 KB

验证集重构:从采样验证到去噪验证

问题诊断

原有验证方式(错误)

验证集使用推理式验证(Sampling Validation):

  • 从纯噪声 x = randn() * σ_max 开始
  • 执行多步迭代去噪(VE-EDM / DDIM)
  • 最终得到 y0_hat 与真实 y0 计算 MSE

问题

  1. 口径不一致:训练用 teacher-forcing(从真实y₀加噪),验证用采样(从纯噪声)
  2. 损失不可比:训练loss含λ(σ)加权,验证loss是纯MSE
  3. 监控失效:无法及时发现模型去噪能力退化、过拟合等问题
  4. 概念混淆:把"推理质量评估"当成了"训练验证"

正确的验证方式

核心概念

扩散模型的训练/验证和推理是两类不同的事

阶段 目的 输入来源 是否需要y₀
训练 学习去噪能力 y₀ + σ·ε ✅ 是
验证 测量去噪能力 y₀ + σ·ε ✅ 是
推理 应用去噪能力 纯噪声(多步) ❌ 否

验证集必须使用真实y₀构造x_t,这不是"作弊",而是标准做法(teacher-forced validation)。


新的验证实现

去噪验证(Denoising Validation)

完全复制训练流程,只是不更新梯度:

def evaluate(model, loader, device, args):
    """去噪验证:与训练完全对齐"""
    for batch in loader:
        X, Y = batch  # Y是真实y₀
        
        # 1. 采样σ ~ LogUniform(σ_min, σ_max)
        log_sigma = torch.rand(B) * (log_σ_max - log_σ_min) + log_σ_min
        sigma = torch.exp(log_sigma)
        
        # 2. 从真实y₀构造x_t(关键!使用真实标签)
        eps = torch.randn_like(Y)
        x_t = Y + sigma * eps
        
        # 3. EDM预条件化
        c_in = 1.0 / sqrt(σ² + σ_data²)
        c_skip = σ_data² / (σ² + σ_data²)
        c_out = σ·σ_data / sqrt(σ² + σ_data²)
        
        x_scaled = c_in * x_t
        f_θ = model.forward_score(X, x_scaled, t_idx)
        ŷ= c_skip * x_t + c_out * f_θ
        
        # 4. 计算损失(与训练一致,含λ加权)
        loss = MSE(ŷ₀, Y)
        if edm_lambda:
            λ = (σ² + σ_data²) / (σ·σ_dataλ = λ / λ.mean()  # batch归一化
            loss = λ * loss
        
        # 5. mask机制(覆盖率权重)
        loss = apply_coverage_mask(loss, mask)

修改内容

1. evaluate() 函数完全重写

  • 移除:所有采样逻辑(VE-EDM、DDIM、multi-step)
  • 新增:与训练对齐的单步去噪验证
  • 保留:统计指标计算(corr、rank_ic、hit_rate等)

2. 命令行参数清理

移除(不再需要):

  • --sampler:验证采样器类型
  • --steps:多步采样步数
  • --use_yz_cond:验证是否启用yzvol

原因:去噪验证直接从训练参数读取σ分布、yzvol配置等。

3. 日志输出更新

# 旧的(混淆)
print(f"val_loss={val_loss:.6f}")  # 不清楚是什么loss

# 新的(清晰)
print(f"val_denoise_loss={val_loss:.6f}")  # 明确是去噪验证loss
print(f"[denoise_val] corr={r:.6g} rank_ic={rank_ic:.6g}")

4. 训练循环

# 验证入口
print("[Validation] 去噪验证 (denoising validation, teacher-forcing)")
val_metrics = evaluate(model, val_loader, device, args)

# Early stopping 使用去噪验证loss
if val_loss < best_val_loss:
    best_val_loss = val_loss
    save_checkpoint(...)

推理质量如何评估?

答案:通过回测(Backtest)

去噪验证只测量去噪能力,真实推理质量需要:

# backtest.py
predictions = ve_edm_samples(  # 从纯噪声多步采样
    model, X, num_samples=100, steps=10
)
# 计算业务指标:Sharpe、IC、收益等

职责分离

  • train.pyevaluate():监控模型学习状态(去噪验证)
  • backtest.py:评估推理质量(完整采样)

理论依据

为什么这是标准做法?

  1. 扩散模型训练目标:学习 p(y₀|x_t, σ),即"给定噪声水平,从x_t恢复y₀"
  2. 验证应该测量:模型是否学会了这个映射
  3. 如何测量:用相同的数据生成方式(y₀ + σ·ε)测试预测精度

类比:

  • 语言模型训练用teacher forcing,验证也用teacher forcing计算perplexity
  • 推理时才用自回归生成

扩散模型也是同理:

  • 训练/验证:teacher-forcing(从y₀构造x_t)
  • 推理:自由生成(从纯噪声逐步去噪)

对比总结

项目 旧方式(错误) 新方式(正确)
验证方式 采样验证(从纯噪声) 去噪验证(从y₀+噪声)
与训练一致性 ❌ 不一致 ✅ 完全一致
loss可比性 ❌ 不可比 ✅ 可直接对比
监控过拟合 ❌ 滞后 ✅ 及时
推理质量评估 ❌ 间接 通过回测单独评估
代码复杂度 高(多采样器) 低(单一流程)

重要提醒

"验证集用y₀构造x_t"不是作弊

这是扩散模型的标准验证方式,目的是测量去噪能力,不是测量推理质量。

推理质量需要通过回测单独评估,那才是真正的"模型能不能做预测"。


文件修改清单

train.py:

  • evaluate() 函数完全重写
  • 移除采样器相关参数
  • 更新日志输出标识
  • 保持early stopping逻辑不变(用去噪loss)

❌ 不需要修改:

  • backtest.py:推理采样逻辑保持不变
  • model.py:模型结构无变化
  • config.json:验证相关参数已不使用

预期效果

  1. 训练更稳定:验证loss与训练loss口径一致,学习率调度更准确
  2. 问题发现更快:过拟合、崩塌等问题能及时在验证loss中体现
  3. 代码更清晰:职责分离,训练验证是一套,推理回测是另一套
  4. 符合规范:与学术界标准做法一致(EDM、DDPM等论文都这么做)

生成时间:2025-01-XX 版本:v1.0