Virtual Embryo Challenge更新于 10-03 18:47(北京时间) / 每 5 分钟更新

总览 · ← 返回运行 20261002-202908-search-t1-scr-D

节点 n19

VAE潜位移改为kNN真实细胞选择:z_pred=z_last+0.5·dz 在末阶段真实潜码中取最近邻,输出=该真实细胞原始表达(k=1,全panel),去掉QM分位数匹配与γ数值混合。

运行?一次完整的自动搜索或 Agent 会话,有自己的锁定配置和证据包。20261002-202908-search-t1-scr-D
父节点n15
子节点—
操作?种子:人写的起点;改进:在父节点上改;草稿:从头写;修复:修父节点的报错。改进
状态已打分
分数搜索目标分 47.86(-1.2) · X3 47.86(-1.2)
审查未审查
用时?从运行开始到结束(或到现在)的挂钟时间。26 分
程序版本6f982fc8a78dd4558ecea8b2f82117ee628b606e (programs.git)

方法说明?节点程序自带的 METHOD.md:这个程序做了什么、为什么。

来自 programs.git 6f982fc8a7:solution/METHOD.md

VAE潜位移改为kNN真实细胞选择:z_pred=z_last+0.5·dz 在末阶段真实潜码中取最近邻,输出=该真实细胞原始表达(k=1,全panel),去掉QM分位数匹配与γ数值混合。

METHOD(node 19,improve over node 15,family: generative_latent)

方法

  • 保留父节点全部潜空间部件:HVG2000、Gaussian VAE(kl=0.005, latent=20, 100ep, CPU)、末两输入阶段 entropic-OT(POT sinkhorn, reg=0.1, cap 3000)耦合 + kNN(k=15) 局部回归得逐细胞位移 dz。
  • 替换解码后处理(PLAN 核心):不再 decode+分位数匹配+γ混合,而是 z_pred = z_last + scale·dz(scale 默认 0.5),对每个输出细胞在末阶段全部真实细胞的潜码中取 k 近邻(默认 k=1),权重 softmax(−(d²−d²_1)/h),h=中位第k距;输出 = 权重加权的末阶段原始表达(全 panel 取自被选真实细胞,非 HVG 也是真实值,不做嵌合;--select-hvg-only 可切换,实测无差异)。
  • 单输入退路:dz=0 → z_pred=z_last → 最近邻=自身 → 输出=输入细胞子集(copy)。
  • 视图无关:只用时间差与数据;--dump/--load 为开发期缓存(默认关闭,提交程序不依赖);seed 确定(default_rng/torch.manual_seed)。

机制生效证据(X3 A半, seed 0)

  • [mech] 日志:||dz|| mean=0.410,within-pop CV=0.337>0.3(继承父标准);k=1, scale=1 时 self_top1=0.525——47.5% 的输出细胞被位移换成了另一个真实细胞(潜距 d1_mean=0.134),输出与 copy_last 逐元素不同。
  • 机制关闭对照(--transition-scale 0):z_pred=z_last,最近邻=自身,输出=末阶段真实细胞子集(等价 copy_last),与 scale>0 输出不同 → 机制在运行。

查分结果(9 次,全部 X3 A半)

配置总分cell_statecovariationde_recoverydirection
k1 s1.045.5444.0147.3443.4448.03
k1 s0.5(默认)47.6349.3348.8043.0949.19
k1 s1.0 hvgo45.7844.0047.7043.4448.72
k1 s1.542.2237.1343.8142.0647.23
k1 s2.039.9432.4539.8242.0646.90
k3 s1.035.4535.7411.6142.4047.24
β0.5 混合 s142.0643.9730.5442.7448.29
β0.7 混合 s1.537.6036.3921.9942.0647.07

参照:父节点15=49.05(cs 68.25 / cov 28.78),copy_last 类对照≈50.65(cov 52.52)。

结论(诚实的负结果)

  1. PLAN 假设一半成立:kNN 真实细胞选择确实把 covariation 从 28.78 拉回 48.80(接近 copy_last 的 52.52),de_recovery 也回到 43+;但 cell_state 只有 44–49,没有保住父节点的 68。
  2. cell_state 随 scale 单调下降(s0≈copy 50.6 → s0.5 49.3 → s1 44.0 → s2 32.5):在 X3 上潜空间 OT 位移方向把细胞选向错误的邻居,位移越大越差。PLAN 风险 1 成立。
  3. 一切线性混合(k>1 加权、β 与自身混合)都摧毁 covariation(k3: 11.6;β0.5: 30.5),复证父节点教训。
  4. 默认取网格内最好的 s=0.5(47.63),仍低于父节点 49.05 与 copy_last 50.65(差距在 ~2 分噪声边缘)。该机制在 X3 上净效应为负;VAE 潜位移家族在此数据上不如 per-type EB 位移(节点 4/5/9/13 家族)。
  5. 下一步建议:放弃 generative_latent 家族的潜位移选择,回到节点 13(EB位移+PC投影+切向扩散,60.56);若保留本家族,需先在 B半/多 seed 上验证位移方向本身(direction 组始终 ~47–49 不动,说明 dz 与真实 E9.0→E9.5 变化方向对齐差)。

验证与未验证

  • 已验证:X3 视图默认配置完整跑通(~2–4 min, CPU)、vec-check 通过;9 次 vec-score 如上。
  • 未验证:proxy/proxy2/final 视图未跑(本节点只挂 X3 尺子);代码继承父节点的 cap/分块逻辑,单输入退路逻辑存在但未实测;B半与多 seed 未测。
  • 生物学知识来源:无外部先验;只用视图内输入阶段数据与通用 ML/OT 组件。未使用禁窗(9.5<E≤13.5)或保留阶段/基因型的任何测量信息。

调研员的计划

名称VAE潜位移+kNN真实细胞选择替代γ混合,修复covariation
动机父节点15的covariation=28.78(对照52.52,-23.74)是最大失分组,ANALYSIS明确归因于γ=0.5线性混合破坏基因间相关结构。cell_state=68.25已由QM+混合修复,但代价是covariation。全树最佳节点13的covariation=44.54,节点5达48.20,说明保留真实表达向量是关键。de_recovery=42.11(-9.27)也需修复。
做法保留父节点VAE训练(HVG2000, latent20, kl=0.005, 100ep)和OT耦合+ kNN位移场不变,替换解码后处理:(1) z_pred = z_last + scale·dz(scale=1.0);(2) 对每个目标细胞,在末阶段所有真实细胞的潜码中找k个最近邻(k=1,3,5待网格),权重softmax(-d²/h),h=中位k距;(3) 输出=权重加权的真实末阶段原始表达(非解码值),保证基因间相关结构来自真实数据;(4) 非HVG保留末阶段值。去掉quantile-match和γ混合。参数网格:k∈{1,3,5},scale∈{0.5,1.0,1.5},共9组合,先跑k=1/scale=1确认方向,再扫其余。单输入退路:无OT,输出=输入(copy)。用vec-score查X3 A半,先跑2个配置(k=1 scale=1, k=3 scale=1)确认covariation回升>45,再扫全网格。耗时预估:VAE+OT约90s,kNN选择<5s,总计<150s。
风险1) k=1时输出只是重排真实细胞,若位移方向不准则cell_state大幅下降( Engineer应先跑k=1看cell_state是否<50);2) k较大时加权平均会平滑掉稀疏性,covariation回升有限;3) 位移幅度不足(||dz||小)导致大多数细胞选回自身,输出≈copy_last无进展;4) 30分钟内网格9组合可能紧张,优先跑3个关键配置。Engineer发现covariation未超45或总分未超49应立即停止网格、检查位移是否有效。

代码改动?这个节点的程序和父节点程序的逐行差别:绿色是新增,红色是删除。

对比:父节点版本 c05a67757a。改动的文件:solution/METHOD.md +34 −21、solution/run.py +107 −57

diff --git a/solution/METHOD.md b/solution/METHOD.mdindex 7a5a1c5..439d346 100644--- a/solution/METHOD.md+++ b/solution/METHOD.md@@ -1,30 +1,43 @@-Gaussian VAE 潜空间转移:两输入阶段潜码 entropic-OT 耦合+kNN 局部回归得逐细胞位移,外推末阶段潜码解码,HVG 分位数匹配回末阶段边缘分布后以 γ=0.5 与真实值混合;非 HVG 保留末阶段值。-# METHOD+VAE潜位移改为kNN真实细胞选择:z_pred=z_last+0.5·dz 在末阶段真实潜码中取最近邻,输出=该真实细胞原始表达(k=1,全panel),去掉QM分位数匹配与γ数值混合。+# METHOD(node 19,improve over node 15,family: generative_latent) +## 方法 -## 方法族与机制(family: generative_latent,按 PLAN)+- 保留父节点全部潜空间部件:HVG2000、Gaussian VAE(kl=0.005, latent=20, 100ep, CPU)、末两输入阶段 entropic-OT(POT sinkhorn, reg=0.1, cap 3000)耦合 + kNN(k=15) 局部回归得逐细胞位移 dz。+- **替换解码后处理(PLAN 核心)**:不再 decode+分位数匹配+γ混合,而是 z_pred = z_last + scale·dz(scale 默认 0.5),对每个输出细胞在末阶段**全部真实细胞**的潜码中取 k 近邻(默认 k=1),权重 softmax(−(d²−d²_1)/h),h=中位第k距;输出 = 权重加权的末阶段**原始表达**(全 panel 取自被选真实细胞,非 HVG 也是真实值,不做嵌合;`--select-hvg-only` 可切换,实测无差异)。+- 单输入退路:dz=0 → z_pred=z_last → 最近邻=自身 → 输出=输入细胞子集(copy)。+- 视图无关:只用时间差与数据;`--dump/--load` 为开发期缓存(默认关闭,提交程序不依赖);seed 确定(default_rng/torch.manual_seed)。 -- HVG 2000(各输入阶段都覆盖的基因,按合并方差选);标准化后进 VAE:enc 2000(+2 阶段 one-hot)→256→64→z(20),dec 20→64→256→2000,Gaussian 似然,KL 权重 **0.005**(PLAN 的 0.1 使潜空间塌缩,||dz||≈0.001,机制失效;实测降到 0.005 后 ||dz||≈0.41),Adam 1e-3,batch 256,100 epochs,CPU(EXECUTION.json gpu=false)。-- 转移:Z(E8.75 类) 与 Z(E9.0 类) 之间 POT `ot.sinkhorn`(reg=0.1,代价按中位数归一,每阶段 ≤3000 细胞);δ_i = 重心映射 − z0_i;对每个末阶段细胞取 k=15 个最近源细胞,softmax(−d²/h)(h=中位 k 距)加权平均 δ 得 dz_j。z_pred = z_last + scale·dz,scale 默认常数 1.0(auto 模式=时间差之比 clip[0,2],X3 上为 2.0,实测略差)。-- 解码后处理(PLAN 步骤 7 的修改,实测必要):直接把解码值写回会摧毁表达纹理(cell_state 8.9、covariation 8.4,总 28.0,远低于 copy_last)。改为 (a) 每个 HVG 基因把解码值的秩映射到末阶段该基因的经验分位数(恢复稀疏与边缘分布),(b) 与末阶段真实值按 γ=0.5 线性混合(纯解码 γ=1 时 covariation 48 但 cell_state 35;混合把 cell_state 拉到 68,代价是 covariation 降到 28.6)。clamp ≥0。-- 单输入退路:无转移,输出=该阶段 encode-decode(自编码对照),符合 PLAN 步骤 8。-- 视图无关:只用时间差与数据;seed 确定(np.random.default_rng(seed)、torch.manual_seed(seed),纯 CPU)。+## 机制生效证据(X3 A半, seed 0) -## 机制生效证据+- `[mech]` 日志:||dz|| mean=0.410,within-pop CV=0.337>0.3(继承父标准);k=1, scale=1 时 self_top1=0.525——**47.5% 的输出细胞被位移换成了另一个真实细胞**(潜距 d1_mean=0.134),输出与 copy_last 逐元素不同。+- 机制关闭对照(`--transition-scale 0`):z_pred=z_last,最近邻=自身,输出=末阶段真实细胞子集(等价 copy_last),与 scale>0 输出不同 → 机制在运行。 -- `[mech]` 日志:||dz|| mean=0.4097,**within-population CV=0.337 > 0.3**(PLAN 证据标准 1):位移确实随细胞潜位置变化,不是每型常数。-- 机制开/关对照(同一程序 `--transition-scale 0` vs 默认,X3 A 半,seed 0):-  - 关(scale=0,纯解码无 QM 时):28.0;关+QM+γ1 未单测;-  - 开:scale=1, γ=1, QM:42.98;scale=2, γ=1, QM:42.02;**scale=1, γ=0.5, QM(默认):48.92**。-  - scale=0 与 scale>0 输出不同(dz≠0),机制在跑;转移主要改变 covariation/de_recovery 方向(de_recovery 44.9→41.7,direction 49.8→49.1),而 QM+γ 混合主要修复 cell_state。-- 输出零值率/库大小:nnz/cell 2273 vs 输入 1974(混合使部分零被填),在格式检查内合规。+## 查分结果(9 次,全部 X3 A半) -## 验证与未验证+| 配置 | 总分 | cell_state | covariation | de_recovery | direction |+|---|---|---|---|---|---|+| k1 s1.0 | 45.54 | 44.01 | 47.34 | 43.44 | 48.03 |+| **k1 s0.5(默认)** | **47.63** | 49.33 | 48.80 | 43.09 | 49.19 |+| k1 s1.0 hvgo | 45.78 | 44.00 | 47.70 | 43.44 | 48.72 |+| k1 s1.5 | 42.22 | 37.13 | 43.81 | 42.06 | 47.23 |+| k1 s2.0 | 39.94 | 32.45 | 39.82 | 42.06 | 46.90 |+| k3 s1.0 | 35.45 | 35.74 | 11.61 | 42.40 | 47.24 |+| β0.5 混合 s1 | 42.06 | 43.97 | 30.54 | 42.74 | 48.29 |+| β0.7 混合 s1.5 | 37.60 | 36.39 | 21.99 | 42.06 | 47.07 |++参照:父节点15=49.05(cs 68.25 / cov 28.78),copy_last 类对照≈50.65(cov 52.52)。 -- 已验证:X3 视图(E8.75+E9.0→E9.5)vec-check 通过,5 次 vec-score(28.0 / 42.0 / 43.0 / 48.9 / 48.9-默认复现同哈希)。默认输出与打分过的 g05 配置逐位相同。-- 未验证:final(E8.5+E9.5→E10.5)与 proxy 视图未跑分(本节点只挂 X3 尺子);代码路径对单输入(proxy)有退路、对大细胞数有 cap(train 10k/阶段、OT 3k/阶段、kNN 分块),但耗时只在 X3(~3.5k 细胞,1–1.5 min)实测。γ、kl、scale 只做了粗网格,B 半与多 seed 未测。-- 生物学知识来源:未使用禁窗(9.5<E≤13.5)或保留阶段的任何测量信息;方法只依赖视图内输入阶段数据与通用 ML 组件,无外部先验写入。+## 结论(诚实的负结果) -## 下一步+1. PLAN 假设一半成立:kNN 真实细胞选择确实把 covariation 从 28.78 拉回 48.80(接近 copy_last 的 52.52),de_recovery 也回到 43+;但 cell_state 只有 44–49,**没有**保住父节点的 68。+2. cell_state 随 scale 单调下降(s0≈copy 50.6 → s0.5 49.3 → s1 44.0 → s2 32.5):在 X3 上潜空间 OT 位移方向把细胞选向**错误的**邻居,位移越大越差。PLAN 风险 1 成立。+3. 一切线性混合(k>1 加权、β 与自身混合)都摧毁 covariation(k3: 11.6;β0.5: 30.5),复证父节点教训。+4. 默认取网格内最好的 s=0.5(47.63),仍低于父节点 49.05 与 copy_last 50.65(差距在 ~2 分噪声边缘)。**该机制在 X3 上净效应为负**;VAE 潜位移家族在此数据上不如 per-type EB 位移(节点 4/5/9/13 家族)。+5. 下一步建议:放弃 generative_latent 家族的潜位移选择,回到节点 13(EB位移+PC投影+切向扩散,60.56);若保留本家族,需先在 B半/多 seed 上验证位移方向本身(direction 组始终 ~47–49 不动,说明 dz 与真实 E9.0→E9.5 变化方向对齐差)。++## 验证与未验证 -γ=0.5 的线性混合牺牲了 covariation(48→28.6);更优方向是用潜位移做"真实细胞选择/组成调制"(对 z_pred 取最近真实末阶段细胞)而非数值混合,可同时保住 cell_state 与 covariation;或把解码 dz 投回基因空间做保稀疏位移(与节点 4/5/9 的强部件组合)。+- 已验证:X3 视图默认配置完整跑通(~2–4 min, CPU)、vec-check 通过;9 次 vec-score 如上。+- 未验证:proxy/proxy2/final 视图未跑(本节点只挂 X3 尺子);代码继承父节点的 cap/分块逻辑,单输入退路逻辑存在但未实测;B半与多 seed 未测。+- 生物学知识来源:无外部先验;只用视图内输入阶段数据与通用 ML/OT 组件。未使用禁窗(9.5<E≤13.5)或保留阶段/基因型的任何测量信息。diff --git a/solution/run.py b/solution/run.pyindex 33b0567..007a95a 100644--- a/solution/run.py+++ b/solution/run.py@@ -149,11 +149,20 @@ def main(argv=None):     ap.add_argument("--ot-reg", type=float, default=0.1)     ap.add_argument("--ot-cap", type=int, default=3000)     ap.add_argument("--train-cap", type=int, default=10000)-    ap.add_argument("--transition-scale", type=float, default=1.0,+    ap.add_argument("--transition-scale", type=float, default=0.5,                     help="-1: auto = clip(dt_target/dt_input, 0, 2); 0 disables the mechanism (control)")     ap.add_argument("--no-covariate", action="store_true")     ap.add_argument("--blend-gamma", type=float, default=0.5,                     help="HVG output = (1-g)*last-stage real values + g*quantile-matched decode")+    ap.add_argument("--knn-select", type=int, default=1,+                    help="k>0: output = softmax-weighted REAL last-stage cells nearest to z_pred "+                         "(no decode/QM/blend); k=0: legacy decode path")+    ap.add_argument("--select-hvg-only", type=int, default=0,+                    help="1: only HVG columns come from selected neighbours, non-HVG keep own values")+    ap.add_argument("--select-beta", type=float, default=1.0,+                    help="output = (1-b)*own last-stage values + b*selected-neighbour values")+    ap.add_argument("--dump", default=None, help="dev: save latents/dz npz")+    ap.add_argument("--load", default=None, help="dev: load latents/dz npz (skip VAE/OT)")     args = ap.parse_args(argv)      view = Path(args.data)@@ -169,7 +178,93 @@ def main(argv=None):     n_out = int(np.clip(n_last, man["min_cells"], man["max_cells"]))     rows_out = np.sort(rng.choice(n_last, size=n_out, replace=n_out > n_last)) -    # HVGs among genes covered by every input stage+    scale = args.transition_scale+    if scale < 0:+        dt_in = inputs[-1]["time"] - inputs[0]["time"] if len(inputs) >= 2 else 0.0+        dt_tg = man["target"]["time"] - inputs[-1]["time"]+        scale = float(np.clip(dt_tg / dt_in, 0.0, 2.0)) if dt_in > 0 else 1.0++    if args.load:+        zz = np.load(args.load)+        Z_last, dz_all, hvg = zz["Z_last"], zz["dz_all"], zz["hvg"]+    else:+        Z_last, dz_all, hvg = _latent_field(Xs, man, args, rng, inputs, genes, masks)+        if args.dump:+            np.savez(args.dump, Z_last=Z_last, dz_all=dz_all, hvg=hvg, scale=scale)++    # ---------------- kNN real-cell selection in latent space (default path)+    if args.knn_select > 0:+        Zp = Z_last[rows_out] + scale * dz_all[rows_out]+        k = min(args.knn_select, Z_last.shape[0])+        d2 = ((Zp[:, None, :] - Z_last[None, :, :]) ** 2).sum(-1)+        knn = np.argpartition(d2, k - 1, axis=1)[:, :k]+        dk = np.take_along_axis(d2, knn, axis=1)+        srt = np.argsort(dk, axis=1)+        knn = np.take_along_axis(knn, srt, axis=1)+        dk = np.take_along_axis(dk, srt, axis=1)+        h = max(np.median(dk[:, -1]), 1e-8)+        W = np.exp(-(dk - dk[:, :1]) / h)+        W /= W.sum(axis=1, keepdims=True)+        sel = np.zeros((n_out, Z_last.shape[0]), dtype=np.float32)+        sel[np.arange(n_out)[:, None], knn] = W.astype(np.float32)+        Xsel = sparse.csr_matrix(sel) @ Xs[-1]+        self_frac = float((knn[:, 0] == rows_out).mean())+        print(f"[mech] scale={scale:.3f} k={k} self_top1={self_frac:.3f} "+              f"d1_mean={dk[:,0].mean():.3f} w1_mean={W[:,0].mean():.3f} "+              f"||dz||={np.linalg.norm(dz_all[rows_out],axis=1).mean():.4f}", flush=True)+        Xsel_d = np.asarray(Xsel.todense(), dtype=np.float32)+        b = float(np.clip(args.select_beta, 0.0, 1.0))+        if b < 1.0:+            Xown = Xs[-1][rows_out].toarray().astype(np.float32)+            Xsel_d = (1.0 - b) * Xown + b * Xsel_d+        if args.select_hvg_only:+            Xout = Xs[-1][rows_out].toarray().astype(np.float32)+            Xout[:, hvg] = Xsel_d[:, hvg]+        else:+            Xout = Xsel_d+        Xout[~np.isfinite(Xout)] = 0.0+        _write_out(Xout, genes, args.out, Xs[-1], n_last)+        return++    # ---------------- legacy decode path (--knn-select 0)+    mu_g, sd_g, model, d_in = _fit_cache["model_parts"]+    A_last = _fit_cache["A_last"]+    Dp = decode(model, (Z_last[rows_out] + scale * dz_all[rows_out]).astype(np.float32)) * sd_g + mu_g+    Dp[~np.isfinite(Dp)] = 0.0+    if args.quantile_match:+        E = np.asarray(Xs[-1][:, hvg].todense(), dtype=np.float32)+        E.sort(axis=0)+        q = (np.argsort(np.argsort(Dp, axis=0), axis=0).astype(np.float64) + 0.5) / Dp.shape[0]+        idx = np.minimum((q * E.shape[0]).astype(np.int64), E.shape[0] - 1)+        Dp = np.take_along_axis(E, idx, axis=0).astype(np.float32)+        del E+    np.maximum(Dp, 0.0, out=Dp)+    Dp[Dp < 0.01] = 0.0+    Xout = Xs[-1][rows_out].toarray().astype(np.float32)+    g = float(np.clip(args.blend_gamma, 0.0, 1.0))+    if g > 0:+        Xout[:, hvg] = (1.0 - g) * Xout[:, hvg] + g * Dp+    Xout[~np.isfinite(Xout)] = 0.0+    _write_out(Xout, genes, args.out, Xs[-1], n_last)+++_fit_cache: dict = {}+++def _write_out(Xout, genes, out_path, X_last, n_last):+    import anndata as ad+    import pandas as pd+    adata = ad.AnnData(X=sparse.csr_matrix(Xout),+                       obs=pd.DataFrame(index=[f"c{i}" for i in range(Xout.shape[0])]),+                       var=pd.DataFrame(index=list(genes)))+    adata.X.eliminate_zeros()+    adata.write_h5ad(out_path)+    print(f"[out] {adata.shape} nnz/cell={adata.X.nnz / adata.shape[0]:.0f} "+          f"input nnz/cell={X_last.nnz / n_last:.0f}", flush=True)+++def _latent_field(Xs, man, args, rng, inputs, genes, masks):+    """HVG selection + VAE training + OT/kNN displacement field. Returns (Z_last, dz_all, hvg)."""     cov_all = np.logical_and.reduce(masks)     var_tot = np.zeros(len(genes), dtype=np.float64)     for X in Xs:@@ -181,7 +276,6 @@ def main(argv=None):     order = cand[np.argsort(-var_tot[cand], kind="stable")]     hvg = np.sort(order[: args.hvg]) -    # standardisation stats over all input cells     S = sparse.vstack([X[:, hvg] for X in Xs]).tocsr()     mu_g = np.asarray(S.mean(axis=0)).ravel().astype(np.float64)     sd_g = np.sqrt(np.maximum(np.asarray(S.multiply(S).mean(axis=0)).ravel() - mu_g ** 2, 0))@@ -193,14 +287,13 @@ def main(argv=None):     use_cov = not args.no_covariate     n_st = len(A) -    def cov_of(si: int, n: int) -> np.ndarray | None:+    def cov_of(si: int, n: int):         if not use_cov:             return None         c = np.zeros((n, n_st), dtype=np.float32)         c[:, si] = 1.0         return c -    # train (cap cells per stage)     train_parts, cov_parts = [], []     for si, D in enumerate(A):         n = D.shape[0]@@ -214,18 +307,8 @@ def main(argv=None):      Z = [encode_mean(model, d_in, D, cov_of(si, D.shape[0])) for si, D in enumerate(A)] -    scale = args.transition_scale-    if scale < 0:-        if n_st >= 2:-            dt_in = inputs[-1]["time"] - inputs[0]["time"]-            dt_tg = man["target"]["time"] - inputs[-1]["time"]-            scale = float(np.clip(dt_tg / dt_in, 0.0, 2.0)) if dt_in > 0 else 1.0-        else:-            scale = 0.0--    # ---- latent displacement field (needs >=2 stages and scale > 0)-    dz_out = None-    if n_st >= 2 and scale > 0:+    dz_all = np.zeros_like(Z[-1])+    if n_st >= 2:         import ot          Z0f, Z1f = Z[-2], Z[-1]@@ -241,9 +324,7 @@ def main(argv=None):         Pi = np.nan_to_num(Pi, nan=0.0, posinf=0.0, neginf=0.0)         rs = Pi.sum(axis=1, keepdims=True)         rs[rs <= 0] = 1.0-        delta = (Pi / rs) @ P1 - P0                      # per-source-cell displacement-        # kNN-weighted local regression: dz for every last-stage cell (chunked)-        dz_all = np.zeros_like(Z1f)+        delta = (Pi / rs) @ P1 - P0         k = min(args.k, len(i0))         for s in range(0, n1, 1024):             Zq = Z1f[s:s + 1024]@@ -254,45 +335,14 @@ def main(argv=None):             w = np.exp(-dk / h)             w /= w.sum(axis=1, keepdims=True)             dz_all[s:s + 1024] = np.einsum("ij,ijk->ik", w, delta[knn])-        dz_out = dz_all[rows_out]         nrm = np.linalg.norm(dz_all, axis=1)-        print(f"[mech] ||dz|| mean={nrm.mean():.4f} cv={nrm.std() / max(nrm.mean(), 1e-9):.4f} "-              f"scale={scale:.3f} ||delta||={np.linalg.norm(delta, axis=1).mean():.4f}", flush=True)--    # ---- predict latents for output cells, decode-    Zp = Z[-1][rows_out].copy()-    if dz_out is not None:-        Zp += scale * dz_out-    Dp = decode(model, Zp.astype(np.float32)) * sd_g + mu_g-    Dp[~np.isfinite(Dp)] = 0.0-    if args.quantile_match:-        # per-gene quantile match: map decoded ranks onto the last stage's-        # empirical marginal (restores sparsity / value distribution per gene)-        E = np.asarray(Xs[-1][:, hvg].todense(), dtype=np.float32)  # n_last x hvg-        E.sort(axis=0)-        q = (np.argsort(np.argsort(Dp, axis=0), axis=0).astype(np.float64) + 0.5) / Dp.shape[0]-        idx = np.minimum((q * E.shape[0]).astype(np.int64), E.shape[0] - 1)-        Dp = np.take_along_axis(E, idx, axis=0).astype(np.float32)-        del E-    np.maximum(Dp, 0.0, out=Dp)-    Dp[Dp < 0.01] = 0.0--    # ---- assemble full-panel output: last stage's values, HVGs blended-    Xout = Xs[-1][rows_out].toarray().astype(np.float32)-    g = float(np.clip(args.blend_gamma, 0.0, 1.0))-    if g > 0:-        Xout[:, hvg] = (1.0 - g) * Xout[:, hvg] + g * Dp-    Xout[~np.isfinite(Xout)] = 0.0+        cv_pop = nrm.std() / max(nrm.mean(), 1e-9)+        print(f"[mech] ||dz|| mean={nrm.mean():.4f} cv={cv_pop:.4f} "+              f"||delta||={np.linalg.norm(delta, axis=1).mean():.4f}", flush=True) -    import anndata as ad-    import pandas as pd-    adata = ad.AnnData(X=sparse.csr_matrix(Xout),-                       obs=pd.DataFrame(index=[f"c{i}" for i in range(Xout.shape[0])]),-                       var=pd.DataFrame(index=list(genes)))-    adata.X.eliminate_zeros()-    adata.write_h5ad(args.out)-    print(f"[out] {adata.shape} nnz/cell={adata.X.nnz / adata.shape[0]:.0f} "-          f"input nnz/cell={Xs[-1].nnz / n_last:.0f}", flush=True)+    _fit_cache["model_parts"] = (mu_g, sd_g, model, d_in)+    _fit_cache["A_last"] = A[-1]+    return Z[-1], dz_all, hvg   if __name__ == "__main__":

调研来源?调研员查到并用到的知识条目和文献检索结果(只列标题和编号)。

用到的知识库条目

编号标题出处
k031Offline OT toolkit in the sandbox: moscot TemporalProblem, wot OTModel, POT, geomloss10.1038/s41586-024-08453-2 (moscot); 10.1016/j.cell.2019.01.006 (Waddington-OT)
k035Latent generative models offline: scVI (scvi-tools) and diffusion/flow generators (scDiffusion, CFGen)10.1038/s41592-018-0229-2 (scVI); 10.1038/s41587-021-01206-w (scvi-tools); 10.1093/bioinformatics/btae518 (scDiffusion); arXiv:2407.11734 (CFGen)
k041Within-stage pseudotime and graph toolkit offline: scanpy DPT/PAGA/Leiden, Palantir, CellRank 210.1186/s13059-019-1663-x (PAGA); 10.1038/s41587-019-0068-4 (Palantir); 10.1038/s41592-024-02303-9 (CellRank 2)

分析结果?分析员写的 ANALYSIS.json:改了什么、各组分数怎么变、假设是否成立、经验和下一步建议。

改了什么保留父节点VAE+OT潜位移场,把解码后处理(quantile-match+γ=0.5混合)替换为:z_pred=z_last+0.5·dz,在末阶段全部真实细胞潜码中取k=1近邻(softmax权重),输出=被选真实细胞的原始表达(全panel)。scale默认从1.0降到0.5,新增--knn-select/--select-beta/--select-hvg-only开关,legacy decode路径保留在--knn-select 0。
各组分数的变化cell_state:变坏(68.25→49.45,-18.80,远超噪声)
covariation:变好(28.78→48.71,+19.93,远超噪声,接近copy_last对照52.52)
de_recovery:噪声内(+1.64,T1噪声约2分)
direction:噪声内(+0.19)
榜分:噪声内偏负(49.05→47.86,-1.19 < 2分噪声,但METHOD内网格最优47.63仍低于父节点和copy_last≈50.65)
family_idgenerative_latent
假设是否成立否
经验
  1. 在真实末阶段细胞潜码中做kNN重选(k=1、输出真实表达向量)能把被γ混合破坏的covariation从28.78拉回48.71,证实covariation失分来自数值混合而非位移本身。
  2. cell_state随位移scale单调下降(s0≈50.6→s0.5=49.3→s1=44.0→s2=32.5,METHOD网格数据):当潜空间OT位移方向与真实阶段变化不对齐时(direction组始终~47-49不动),位移越大把细胞选向越错误的邻居,重选真实细胞的净效应为负。
  3. 任何跨细胞/跨值的线性平均都摧毁covariation:k=3 softmax加权得11.61,β=0.5与自身混合得30.54(METHOD网格)——基因间相关结构只能来自单个真实表达向量,不能靠平均。
  4. k=1重选+scale=0对照严格退化为copy_last,机制开关设计有效(self_top1=0.525,47.5%输出细胞被换成其他真实细胞),是干净的可复用对照范式。
  5. 去掉QM后重选路径cell_state上限只有copy_last水平(~50),达不到父节点QM+混合的68:QM对cell_state的贡献是重选无法替代的。
mechanism_active是
下一步建议
  1. 放弃generative_latent潜位移重选路线,回到节点13的per-type EB位移+PC投影+切向扩散家族(60.56)继续改进,针对全部四组。
  2. 若保留本家族:先在B半/多seed上验证dz方向与真实E9.0→E9.5变化的对齐度(direction组~47-49不动说明对齐差),再决定是否值得调scale,针对direction组。
  3. 救cell_state可试decode(z+dz)−decode(z)的差值做保稀疏加性位移(不做重选、不做线性混合),叠加在真实末阶段表达上,针对cell_state+covariation两组。

对话摘要?每个角色和大模型对话的统计:轮数、工具调用、用时、token 数和最后的回答摘录;原始记录只给路径。

只给统计和最后回答的摘录;完整对话请到原始记录位置里列出的文件看。

分析员

角色?调研员写计划、工程师改代码、分析员解读分数、审查员检查作弊。分析员 alibaba-token-plan-cn/qwen3.8-max
调用次数1 次
轮数?大模型一共回复了几轮(每轮可以调用多个工具)。4
工具调用?大模型调用读文件、执行命令等工具的次数,按工具名统计。共 3 次:bash 2、write 1
用时不到 1 分
token 数?输入 / 输出(含思考)的 token 数,来自接口返回的用量。输入 13,132 · 输出 1,004 · 思考 1,038
任务(第一行)分析节点 n19 的打分结果,写 ANALYSIS.json
最后的回答(摘录)
ANALYSIS.json written
原始记录位置?完整对话记录在 Spark 上的文件路径;看板不复制原始内容。/home/spark-longxinyang/vec/runs/formal/20261002-202908-search-t1-scr-D/nodes/19/analyst.jsonl 7 KB
/home/spark-longxinyang/vec/runs/formal/20261002-202908-search-t1-scr-D/nodes/19/analyst.stderr

工程师

角色工程师 alibaba-token-plan-cn/qwen3.8-max
调用次数1 次
轮数23
工具调用共 24 次:bash 17、edit 5、read 1、write 1
用时23 分
token 数输入 65,804 · 输出 10,298 · 思考 10,383
任务(第一行)按计划修改程序:VAE潜位移+kNN真实细胞选择替代γ混合,修复covariation
最后的回答(摘录)
Done. Final state verified: `vec-check` ok, and the full default run reproduces the scored prediction bit-identically (determinism confirmed).

**做了什么**:按 PLAN 把父节点 15 的「VAE 解码+分位数匹配+γ混合」替换为潜位移 kNN 真实细胞选择:z_pred = z_last + scale·dz,在末阶段全部真实细胞潜码中取最近邻(k=1,softmax 权重),输出=被选真实细胞的原始表达(全 panel)。机制关闭对照(scale=0)严格退化为 copy_last。

**查分结果**(X3 A半,9 次):最优 k=1/scale=0.5 = **47.63**(cov 48.80 从父 28.78 大幅回升,de_recovery 43.09,但 cell_state 49.33 远低于父 68.25);scale 越大越差(s1=45.5、s2=39.9),k>1 与 β 线性混合都摧毁 covariation(k3=35.5、β0.5=42.1)。低于父 49.05 与 copy_last ~50.65(噪声边缘)——潜空间 OT 位移方向在 X3 上净效应为负,PLAN 风险 1 成立,已在 METHOD.md 如实记录。

**下一步最值得试**:放弃 generative_latent 潜位移家族,回到节点 13(per-type EB 位移+PC 投影+切向扩散,60.56)继续改进;若要救 cell_state,用 decode(z+dz)−decode(z) 的差值做保稀疏加性位移(父 ANALYSIS 建议 2),而非重选/混合真实细胞。
原始记录位置/home/spark-longxinyang/vec/runs/formal/20261002-202908-search-t1-scr-D/nodes/19/engineer.jsonl 223 KB
/home/spark-longxinyang/vec/runs/formal/20261002-202908-search-t1-scr-D/nodes/19/engineer.stderr

调研员

角色调研员 alibaba-token-plan-cn/qwen3.8-max
调用次数1 次
轮数1
工具调用共 0 次:—
用时1 分
token 数输入 15,998 · 输出 978 · 思考 1,668
任务(第一行)为节点 n19(改进,父节点 n15)写改进计划 PLAN.json
最后的回答(摘录)
```json
{
  "name": "VAE潜位移+kNN真实细胞选择替代γ混合,修复covariation",
  "motivation": "父节点15的covariation=28.78(对照52.52,-23.74)是最大失分组,ANALYSIS明确归因于γ=0.5线性混合破坏基因间相关结构。cell_state=68.25已由QM+混合修复,但代价是covariation。全树最佳节点13的covariation=44.54,节点5达48.20,说明保留真实表达向量是关键。de_recovery=42.11(-9.27)也需修复。",
  "approach": "保留父节点VAE训练(HVG2000, latent20, kl=0.005, 100ep)和OT耦合+ kNN位移场不变,替换解码后处理:(1) z_pred = z_last + scale·dz(scale=1.0);(2) 对每个目标细胞,在末阶段所有真实细胞的潜码中找k个最近邻(k=1,3,5待网格),权重softmax(-d²/h),h=中位k距;(3) 输出=权重加权的真实末阶段原始表达(非解码值),保证基因间相关结构来自真实数据;(4) 非HVG保留末阶段值。去掉quantile-match和γ混合。参数网格:k∈{1,3,5},scale∈{0.5,1.0,1.5},共9组合,先跑k=1/scale=1确认方向,再扫其余。单输入退路:无OT,输出=输入(copy)。用vec-score查X3 A半,先跑2个配置(k=1 scale=1, k=3 scale=1)确认covariation回升>45,再扫全网格。耗时预估:VAE+OT约90s,kNN选择<5s,总计<150s。",
  "expected_groups": ["covariation", "de_recovery"],
  "risks": "1) k=1时输出只是重排真实细胞,若位移方向不准则cell_state大幅下降( Engineer应先跑k=1看cell_state是否<50);2) k较大时加权平均会平滑掉稀疏性,covariation回升有限;3) 位移幅度不足(||dz||小)导致大多数细胞选回自身,输出≈copy_last无进展;4) 30分钟内网格9组合可能紧张,优先跑3个关键配置。Engineer发现covariation未超45或总分未超49应立即停止网格、检查位移是否有效。",
  "family_id": "generative_latent",
  "mechanism": "VAE潜空间中OT耦合产生的逐细胞位移z_pred,通过在真实末阶段细胞潜码中做kNN选择将潜位移转化为真实表达向量的重新分配,而非解码+数值混合。",
  "vs_constant_shift": "位移依赖每个细胞在潜空间的位置(OT耦合+kNN回归),不是每型常数差;选择的目标细胞因细胞而异,within-population CV>0.3已验证。常数位移对所有同型细胞给出相同输出,本方案同型内不同细胞因潜位置不同会被映射到不同真实邻居。",
  "mechanism_evidence": "1) ||dz|| mean>0.1且within-pop CV>0.3(继承父节点标准);2) 输出矩阵与copy_last逐元素比较:(A!=B).nnz应>0,且改变比例>20%;3) 四组分分别报告:预期covariation从28.78回升至>45,cell_state可能从68降至55-65(因去掉QM),总分应>49;4) 打印被选择细胞与自身的潜距离分布,确认非全部选回自身。",
  "mechanism_off_control": "--transition-scale 0:z_pred=z_last,最近邻即自身,输出=末阶段原始表达(等价copy_last)。预期:输出与copy_last逐元素一致(或仅浮点误差),分数回到~50(copy_last水平)。若scale=0时输出与scale>0相同,说明机制未运行。",
  "sources": []
}
```
原始记录位置/home/spark-longxinyang/vec/runs/formal/20261002-202908-search-t1-scr-D/nodes/19/researcher.jsonl 4 KB
/home/spark-longxinyang/vec/runs/formal/20261002-202908-search-t1-scr-D/nodes/19/researcher.stderr