Files
ts-as-modality/docs/superpowers/plans/2026-06-29-ts-as-modality-plan.md
2026-06-29 21:16:52 +08:00

13 KiB
Raw Permalink Blame History

时序作为模态的多模态模型 · 实现计划

对应规格docs/superpowers/specs/2026-06-29-ts-as-modality-design.md 日期2026-06-29 目标:把规格拆成有序、可验证的实现任务,按里程碑 M1→M5 推进 原则:每个任务都有明确的"完成判定"(可跑一条命令/测试验证);任务间依赖清晰;先建最小可跑通骨架,再迭代填充


阶段总览

M1 数据管线 ──► M2 模型可跑通 ──► M3 阶段①对齐训练 ──► M4 阶段②SFT ──► M5 完整评测
 (data)         (model)            (train stage1)        (train stage2)    (eval)
   └─ 合成+真实    └─ TS Encoder      └─ 50万 2-3ep        └─ 10万 3ep      └─ VUS-PR/问答/基线

每里程碑结束有"出口验证",通过才进下一个。首版先做"小规模冒烟"(数据 1k、训练几十步)确认管线通,再放大到全量。


M1 · 数据管线

目标:能产出合法 JSONL 样本(合成)+ 能加载真实 benchmark 窗口。

T1.1 项目脚手架

  • 创建 pyproject.toml:依赖 torch, transformers, peft, accelerate, datasets, numpy, tqdm, pyyaml, pytest
  • 创建 src/tsmm/__init__.pyconfigs/scripts/tests/ 目录骨架
  • data/checkpoints/ 并加 .gitignore
  • 完成判定python -c "import tsmm" 不报错;pytest tests/ 能跑(哪怕空)

T1.2 属性词表与采样 data/attributes.py

  • 定义变量名词表(CPU/内存/流量/延迟/温度/请求量…)、单位、采样率档位
  • sample_attributes(n_channels) → 返回 [{"name","unit","freq"}, ...]
  • 完成判定:单元测试 tests/test_attributes.py — 采样结果字段齐全、数量正确、可复现(设 seed)

T1.3 成分模型与时序合成 data/synthesis.py

  • 实现 generate_series(T, C, seed):趋势+周期+基线+噪声(高斯/Student-t)叠加
  • 实现 inject_anomaly(series, types):尖刺/水平偏移/方差膨胀/缺失段/缓慢漂移,返回 (series, labels)labels 为逐点 0/1 + 段元数据
  • 实现 couple_event(series, t, kind):在 t 注入阶跃并返回事件文本描述
  • 实现 add_missing(series, rate):随机丢弃 5-10% 点(用 NaN 占位)
  • 完成判定tests/test_synthesis.py — 给定 seed 输出确定;异常段位置与 labels 一致;事件 t 处确有阶跃;可视化 1 条样本人工确认形态合理

T1.4 指令与回答生成 data/instruct.py

  • 定义 6 类指令模板(描述/异常/根因/预测/比较/事件关联)
  • build_instruction(series_meta, labels, events, category)(instruction, answer)answer 含 JSON 区间(异常类)或数值(统计类)或文本(解释类)
  • Evol-Instruct 演化:用模板组合 + 随机改写生成多样化问法
  • 完成判定tests/test_instruct.py — 每类至少产 1 条,answer 的 JSON 可被 json.loads 解析;异常类 answer 段与 labels 吻合

T1.5 离线生成脚本 scripts/gen_synthetic.py

  • 多进程跑 T1.2-T1.4,产出 data/align.jsonl50万)+ data/sft.jsonl10万,Evol 演化)+ data/eval_synth.jsonl2k held-out,固定 seed
  • 支持 --n--out--seed--workers 参数;带 tqdm 进度
  • 完成判定python scripts/gen_synthetic.py --n 1000 跑通;抽样 5 条人工可视检 schema 合规(含 series/attributes/timestamps/events/instruction/answer/labels

T1.6 真实 benchmark 加载 data/real_bench.py

  • 加载 SMD/MSL/SMAP/SWaT/PSM(先用本地缓存或公开下载脚本;若无现成包,至少实现 SWaT/MSL 两个)
  • load_windows(name, T=512, stride=256) → 滑窗 + 归一化 + 异常标签
  • 用 LLM/模板把异常段转成问答对(评测用,不进训练),存 data/eval_real.jsonl
  • 完成判定:能加载至少 2 个数据集;窗口形状 [N, T, C];标签形状 [N, T];问答对非空

M1 出口验证gen_synthetic.py --n 1000 产出可视检合法的 JSONLreal_bench.py 能加载 ≥2 数据集。人工抽看 5 条合成样本确认成分/异常/事件正确。


M2 · 模型可跑通

目标TS Encoder + Projector + 多模态拼接能在 3060 上前向+反向不 OOM。

T2.1 TS Encoder model/ts_encoder.py

  • Patchify(patch=8, stride=4):通道独立切 patch,线性嵌入到 d=256
  • TSEncoder(d=256, layers=2, heads=4)2 层 Transformer + 可学习位置编码
  • 前向:[B, T, C][B, n_patches, 256]
  • 完成判定tests/test_ts_encoder.py — 随机输入 [2, 512, 5] 输出 [2, 128, 256];参数量 ≈4M

T2.2 Projector model/projector.py

  • Projector(256 → 896)Linear + LayerNorm
  • 完成判定:输出 [B, 128, 896] 对齐 Qwen2.5-0.5B hidden

T2.3 多模态拼接 model/multimodal.py

  • 用 Qwen2.5-0.5B tokenizer 把 [属性][时间戳][事件][问题] 文本 tokenize
  • 把 TS token[B,128,896])作为 inputs_embeds 插入对应位置:[属性][时间戳][TS tok][事件][问题]
  • 统一构造 inputs_embeds(文本走 get_input_embeddings()TS 走 Projector 输出)+ attention_mask + labels(仅回答段非 -100
  • 完成判定tests/test_multimodal.py — 拼接后 seq_len ≤1024mask/labels 形状对;回答段 label 非 -100

T2.4 训练/推理封装 model/wrapper.py

  • MultimodalTSModel:组合 TS Encoder + Projector + LLM + LoRA 挂载开关
  • forward(batch) 返回 lossgenerate(batch) 返回文本
  • 支持 freeze_llm(阶段①)和 enable_lora(r=16)(阶段②)两种模式
  • 完成判定:单 batch forward 在 3060 不 OOMgenerate 能出文本(哪怕乱码)

T2.5 Collator data/collator.py

  • 把 JSONL 样本 batch 化为 (series, attributes, timestamps, events, instruction, answer) → 张量
  • 处理变长 Cpadding + mask)、变长文本(padding + attention_mask
  • 完成判定tests/test_collator.py — batch=4 输出张量形状一致、无 NaN

M2 出口验证:单 batch 前向+反向在 3060 跑通,loss 是有限值且下降趋势;阶段①模式峰值显存 ~5GB。


M3 · 阶段① 对齐训练

目标50万条 2-3 epochTS token 不被忽略。

T3.1 损失函数 train/losses.py

  • lm_loss(logits, labels):仅回答段计 LM losslabels mask
  • contrastive_loss(model, batch):扰动时序(加噪/平移)→ 回答 embedding 应变化(InfoNCE 或扰动一致性)
  • 总 loss = lm_loss + λ * contrastive_loss(λ 可配,默认 0.1
  • 完成判定tests/test_losses.py — loss 标量、可反传、对比损失对扰动敏感

T3.2 阶段① 训练脚本 train/stage1.py + scripts/train_stage1.sh

  • 加载 data/align.jsonldatasets 流式)
  • freeze_llm=True,仅训 TS Encoder + ProjectorAdamW, lr=1e-4
  • 配置:bs=8, grad_accum=4, ctx=512, BF16, 梯度检查点
  • 每 2000 step 存 ckpt 到 checkpoints/stage1/TensorBoard 记录
  • 完成判定--max_steps 100 冒烟跑通不 OOM;loss 下降;峰值显存 ≤6GB

T3.3 TS-token 有效性检验

  • 训练后取 held-out 样本:原时序 vs 扰动时序,对比模型回答是否变化
  • "必看时序"样本:问具体数值/段位置,看模型能否答对
  • 完成判定:扰动后回答变化率 >50%;"必看时序"样本答对率显著高于纯 LLM 基线

M3 出口验证:阶段① ckpt 产出;扰动检验通过;显存 ≤6GB。失败则回查对比损失权重/数据质量。


M4 · 阶段② SFT

目标10万条 Evol-Instruct 3 epoch + LoRA6 类任务遵循指令。

T4.1 阶段② 训练脚本 train/stage2.py + scripts/train_stage2.sh

  • 加载阶段① ckptTS Encoder + Projector
  • enable_lora(r=16) on q/v_projLLM 其余冻结
  • 加载 data/sft.jsonl,配置:bs=4, grad_accum=8, ctx=1024, BF16, 梯度检查点
  • 每 2000 step 存 ckpt(含 LoRA 适配器)到 checkpoints/stage2/
  • 完成判定--max_steps 100 冒烟跑通;峰值显存 ≤11GB;loss 下降

T4.2 指令遵循抽检

  • 6 类任务各取 10 条 held-out,人工/规则看回答是否遵循格式与语义
  • 异常类:JSON 可解析、段位置合理
  • 解释类:回答切题、引用事件
  • 完成判定:6 类任务回答可用率 >70%JSON 解析成功率 >80%

M4 出口验证:阶段② ckpt 产出;6 类任务抽检达标;显存 ≤11GB。若 OOM 按风险对策降批量/开 CPU offload。


M5 · 完整评测

目标:产出 VUS-PR/Aff-F1/问答分 + 4 类基线对比。

T5.1 回答解析 eval/parse_answer.py

  • 从模型文本回答中提取 JSON 异常段 → 逐点分数 [T](段内 1.0,段外 0.0
  • 解析失败 → 全 0 + 记录失败标记
  • 完成判定tests/test_parse.py — 多种回答格式(含噪声/嵌套)能解析;失败有兜底

T5.2 异常检测指标 eval/ts_metrics.py

  • tsb_uad 算 VUS-PR(主);自实现 Affiliation-F1(λ=0.1/0.5/1.0)、AUC-PR、Point-F1(无PA)
  • PA-F1 单独算作对照
  • 完成判定tests/test_ts_metrics.py — 对已知段能算出合理值;trivial 基线(全报异常)VUS-PR 不虚高

T5.3 问答评测 eval/qa_judge.py

  • 规则解析轨:JSON/数值容差比对,产出准确率
  • LLM-judge 轨:调 GPT-4o 对 (问题,时序摘要,参考答案,模型答案) 打 0-5 分(可换开源 judge 做 sanity
  • 不一致样本导出供人工抽检
  • 完成判定:能对 eval 集产出 6 类任务的 {规则准确率, judge 均分, 解析成功率}

T5.4 对照基线 eval/baselines.py

  • 纯 LLM 数值喂文本:时序数值拼字符串喂 Qwen2.5-0.5B,同评测脚本
  • Time-LLM 风格重映射:同样 0.5B 骨干(简化复现)
  • ChatTS(若可复现则纳入,否则标注未复现)
  • TrivialRandom / Constant / 全报异常
  • 全部走同一套 parse_answer + ts_metrics,保证同口径
  • 完成判定4 类基线均产出同口径指标表

T5.5 评测脚本与报告 scripts/run_eval.sh

  • eval_synth.jsonl + eval_real.jsonl,产出 markdown 报告 reports/eval-YYYYMMDD.md
  • 报告含:每数据集×每任务的 VUS-PR/Aff-F1/AUC-PR/Point-F1/PA-F1(对照)/问答分/解析成功率/消融/基线对比
  • 标注是否用 PA、含 trivial 对照
  • 完成判定:一键 run_eval.sh 产出完整报告;报告满足规格 §5.4 必报清单

M5 出口验证:报告满足规格 §6.5 成功标准 —— 真实 benchmark VUS-PR 显著优于纯 LLM 基线;时序消融后指标明显下降;问答均分超基线;评测可复现含 trivial。


执行顺序与依赖

T1.1 ─► T1.2 ─► T1.3 ─► T1.4 ─► T1.5 ┐
                                       ├─► [M1 出口] ─► T2.1 ─► T2.2 ─► T2.3 ─► T2.4 ─► T2.5 ─► [M2 出口]
                  T1.6 ────────────────┘                                                          │
                                                                                                  ▼
                                                      T3.1 ─► T3.2 ─► T3.3 ─► [M3 出口]
                                                                                                  │
                                                                                                  ▼
                                                      T4.1 ─► T4.2 ─► [M4 出口]
                                                                                                  │
                                                                                                  ▼
                                         T5.1 ─► T5.2 ┐
                                         T5.3 ────────┼─► T5.4 ─► T5.5 ─► [M5 出口]

风险触发时的回退

  • M2 OOM:先降 ctx 到 256 / bs 到 2;仍不行退到只验证阶段①模式
  • M3 TS token 被忽略:提升对比损失 λ;增加"必看时序"样本占比;检查 Projector 是否饱和
  • M4 OOM:降 bs=2/grad_accum=16;开 optim=adamw_8bit + CPU offloadctx 降到 768
  • M5 合成→真实泛化差:在阶段②掺入 real_bench 改写样本重训;报告以真实 benchmark 为准

首次跑通的"冒烟路径"(先做这个)

不等全量数据,先用最小路径确认端到端通:

  1. gen_synthetic.py --n 200 产小数据
  2. T2.1-T2.4 单 batch 跑通
  3. stage1.py --max_steps 50 + stage2.py --max_steps 50 冒烟
  4. run_eval.sh 在 200 样本上产出报告骨架

冒烟通过后再放大到 M1 全量数据 + 完整训练。


本计划把规格拆成 22 个任务、5 个带出口验证的里程碑。每个任务有"完成判定"可独立验证。建议从"冒烟路径"开始,确认端到端通后再放大规模。