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

总览 · ← 返回运行 20261002-202907-search-t1-scr-A

节点 n16

VAE(latent20) 上用同型 kNN 细胞级配对拟合位置依赖位移场 δ(z)=Wz+b(型内/型间 sd 比 0.98,父节点 0.08),再用 ridge 把 δ 校准到实测型级伪批量差的线性读出 G,按时间比外推、只加到最后阶段非零 HVG 位点(w=1.5)。

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

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

来自 programs.git 0af2d0cc70:solution/METHOD.md

VAE(latent20) 上用同型 kNN 细胞级配对拟合位置依赖位移场 δ(z)=Wz+b(型内/型间 sd 比 0.98,父节点 0.08),再用 ridge 把 δ 校准到实测型级伪批量差的线性读出 G,按时间比外推、只加到最后阶段非零 HVG 位点(w=1.5)。

方法(family: generative_latent,按 node16 PLAN 的三个 Fix + 一个新的解码 Fix)

  1. HVG 2500(seurat,最后输入阶段,仅 covered);log1p(CP10k) 直接用。
  2. Gaussian VAE:enc 2500→512→20(mu,logvar),dec 20→512→2500;MSE+0.1*KL;Adam 1e-3,batch 256,60 epoch(~30s CPU),训练细胞 ≤3000(X3 上两阶段共 3553)。Fix 3:latent 15→20。
  3. Fix 1(配对):两输入阶段按 celltype 匹配(每型 ≥10 细胞,X3 匹配 5 型 / 1093 个 first 阶段细胞)。对每个 first 阶段细胞 i,在同型的 last 阶段 latent 里取 K=10 个欧氏近邻,δ_i = mean(z_nn) − z_i;ridge(λ=1, bias 不罚) 拟合 δ(z)=Wz+b。KNN_PAIR=0 退回父节点的每型 Δz 均值目标(同一次程序内 A/B)。
  4. 外推:z_pred = z_last + s·δ(z_last),s = clip((t_target−t_last)/(t_last−t_first),0,2)·DISPLACEMENT_SCALE(X3 上 s=2.0;只用时间差,不用绝对时间)。单输入退路:无配对 → s=0 → 输出等于最后阶段。
  5. 新 Fix(解码,提交默认 DECODE_MODE=readout):非线性解码器在稀疏 scRNA 上重建太差(按基因 Pearson 0.387),dec(z_pred)−dec(z_last) 主要是噪声(父节点 resid 路线全配置 ≤ copy_last 地板)。改为把同一个 latent 位移场用 ridge 校准的线性算子读回基因空间:对每个匹配型取实测伪批量差 Δ_t = mean(x_last,t)−mean(x_first,t)(HVG,按 √(n1n2)/(√(n1n2)+30) 收缩)与型级位移 δ_t,解 G = (Σ_t Δ_t δ_tᵗ)(Σ_t δ_t δ_tᵗ + λI)⁻¹,λ=0.01(fit resid 0.0017 vs |Δ| 0.0320)。逐细胞基因变化 g_i = s·w·[G δ_{t(i)} + κ·G(δ_i − δ_{t(i)})],κ=1、w=1.5,只加在 x_last>0 的 HVG 位点并 clip≥0;非 HVG 与未匹配型保持原值。父节点的 resid 解码(含 Fix 2 表达门控 GATE_Q=0.9/GATE_LO=0.25)完整保留在代码里,可用 DECODE_MODE=resid 复现。

生物知识来源:无外部先验、无外部数据;只用视图内表达、celltype 标签与阶段时间差。CPU only(EXECUTION.json gpu=false),torch 单线程 + np.random.default_rng(seed),对 seed 确定(seed 0 复跑逐元素相同)。

机制生效证据(X3 视图,seed 0 实测)

  • δ 不再退化为常数:kNN 配对目标 within-type sd/|δ| = 1.135,拟合场 fit within-type sd 0.4794 / |δ| 均值 0.4911 = 0.976(父节点 0.08;KNN_PAIR=0 在同 latent 下为 0.52,fit_between_sd 0.1485 > 0.1302,即型间仍占主导)。型内位置依赖确实存在。
  • 位置依赖部分本身有效(κ A/B,其余全同):κ=1 → 50.68(λ=0.01)/ 50.14(λ=0.1);κ=0(每型常数读出)→ 49.30 / 49.25。cell_state 57.20 vs 53.67、55.10 vs 53.05。差值 +1.4 / +0.9,在噪声(~2)边缘但两个 λ 同向。
  • 实际改变了哪些细胞:652 个输出细胞中 480 个被位移改变(其余 172 个属于两阶段未匹配的细胞型,按设计不动);2166 个基因列被改动(HVG 内);w=1.5 时每细胞 L1 改变 mean=88.9 / p95=210.9;非零率 0.078→0.075(clip≥0 造成,未做稠密填充)。
  • 机制关闭对照:DISPLACEMENT_SCALE=0 → g=0,输出与 copy_last 下采样逐元素相同(已用 readout 与 resid 两种解码各验一次,max|Δ|=0),X3 = 48.28。即本节点相对关闭机制 +2.4~+3.5。
  • 表达门控(Fix 2)隔离:resid 解码下,同位移 w=0.5,有门控 46.22 vs 无门控 45.18(cell_state 45.71 vs 42.67)→ 门控有效但整条 resid 路线仍低于地板,故提交改用 readout 解码。

查分记录(vec-score X3,A 半,seed 0)

配置分数dedircell_statecov
DISPLACEMENT_SCALE=0(机制关闭 = copy_last 下采样)48.2843.4450.1250.8348.18
父路线 resid w=0.5 + gate + kNN(latent20)46.2243.0948.7245.7147.75
resid w=0.5 + gate,KNN_PAIR=048.2243.4450.1050.9747.72
resid w=0.5,gate 关45.1842.7448.6842.6747.60
resid w=0.25/1.0 + gate + kNN46.94 / 44.47
readout λ=1.0 κ=1 w=149.2244.9250.3752.4548.31
readout λ=0.1 κ=1 w=1(w=0.5)50.14(49.21)45.3050.4355.1048.39
readout λ=0.1/0.01 κ=0 w=149.25 / 49.3053.05 / 53.67
readout λ=0.01 κ=1 w=150.6844.9250.4457.2048.41
readout λ=0.001 κ=1 w=150.8044.9250.4557.6148.39
readout λ=0.01 κ=1 w=1.5(提交默认)51.8044.9250.5060.9548.31

相对父节点(T1 48.47 / X3 resid 48.14):X3 +3.7;四组分 de_recovery 43.09→44.92、direction 48.72→50.50、cell_state 45.71→60.95、covariation 47.75→48.31,全部不劣于父节点且三项超过关闭机制对照。

验证过 / 没验证

  • 验证过:X3 完整跑通(~45s,峰值内存 <3 GB),vec-check ok(seed 0 与 seed 1),seed 0 逐元素复现;λ∈{0.001,0.01,0.1,1}、w∈{0.5,1,1.5}、κ∈{0,1,1.5 未查分}、KNN_PAIR、GATE、SHRINK_N0 的 A/B;DISPLACEMENT_SCALE=0 精确等于 copy_last。
  • 没验证:w=1.5 是查分额度用尽前的最后一个点,w>1.5 未测(cell_state 仍随 w 单调上升,可能还有余量,也可能过冲);κ=1.5、SHRINK_N0=0 已生成预测但未查分;proxy/proxy2/final 视图未跑(代码只用时间差与标签,单输入退路已覆盖);多 seed 只跑了 seed 0/1,未查分(额度);latent 20 与 15 的对比只在 resid 路线上间接看过。
  • 如实报告:readout 解码把「latent 位移场」经由实测型级伪批量差校准回基因空间,因此增益的主体是型级位移经正确解码后的表达改变(cell_state +10),而 PLAN 要求的细胞级位置依赖(κ)在其之上只贡献 +0.9~+1.4(两个 λ 同向、幅度接近噪声)。父节点的解码器差值(resid)路线在 X3 上任何配置都不超过关闭机制对照,已放弃为主解码,仅保留门控(Fix 2)在 resid 分支中。

调研员的计划

名称cell-level kNN-paired latent displacement + expression-gated residual
动机Node 15 ANALYSIS confirms δ(z) degenerated to per-type constants: within-type δ sd=0.0202 vs |δ| mean=0.2561 (ratio 0.08). This is because regression targets were per-type mean Δz (only 5 distinct targets for 1093 points). de_recovery 44.09 is -7.29 vs node 2 baseline 51.38, caused by uniform residual weight diluting high-expression DE genes. covariation 47.50 is -5.02 vs 52.52. Both are the weakest groups and both trace to the same two structural issues: (1) degenerate displacement, (2) expression-blind residual application.
做法Fix 1 (core structural): Replace per-type mean Δz regression targets with cell-level kNN-paired displacements. For each cell in stage-first, find K=10 nearest neighbors (Euclidean in VAE latent, same celltype) in stage-last; target δ_i = mean(z_nn) − z_i. Fit δ(z)=Wz+b by ridge(λ=1) on these ~1093 cell-level pairs. This makes δ genuinely position-dependent. K range [5,20], pick by within-type δ sd / |δ| mean ratio ≥ 0.3. Fix 2 (de_recovery): Expression-gated residual weight. For each nonzero HVG position, compute w_eff = w · gate(x_last), where gate=1 if x_last < p90 of that cell's nonzero HVG values, gate=0.25 if above p90. This protects top-expressed DE genes from dilution. w base stays 0.5. Fix 3 (minor): latent dim 15→20 to give the displacement field more degrees of freedom. Single-input fallback unchanged (s=0, pure encode→decode). Run on X3 first with vec-score; if de_recovery recovers ≥ 2 points and within-type δ sd/|δ| ≥ 0.3, submit. Total extra code ~40 lines; VAE training ~70s unchanged.
风险(1) kNN pairing in a 15-20-dim latent with ~200 cells/type may still yield nearly uniform displacements if the VAE latent does not separate within-type heterogeneity; Engineer should check within-type δ sd immediately after pairing, before full scoring. (2) Expression gating may not recover de_recovery if the damage comes from covariance disruption rather than DE dilution; check de_recovery and covariation separately. (3) VAE reconstruction Pearson 0.36 means dec differences are noisy; if the fix shows <1 point gain on X3 after 3 queries, abandon and report honestly. (4) 30-min budget: VAE train ~70s + 3-4 scoring runs ~2.5 min each fits, but leave 10 min buffer.

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

对比:父节点版本 e925402f8d。改动的文件:solution/METHOD.md +31 −23、solution/run.py +233 −60

diff --git a/solution/METHOD.md b/solution/METHOD.mdindex 11a8ebc..07fe06f 100644--- a/solution/METHOD.md+++ b/solution/METHOD.md@@ -1,35 +1,43 @@-VAE(2500 HVG, latent15) 学两输入阶段配对细胞型的 latent 位移 δ(z)=Wz+b(ridge λ=1),按时间比外推 z,用解码器差值 dec(z_pred)−dec(z_last) 加到最后阶段非零 HVG 上(w=0.5)。+VAE(latent20) 上用同型 kNN 细胞级配对拟合位置依赖位移场 δ(z)=Wz+b(型内/型间 sd 比 0.98,父节点 0.08),再用 ridge 把 δ 校准到实测型级伪批量差的线性读出 G,按时间比外推、只加到最后阶段非零 HVG 位点(w=1.5)。 -## 方法(family: generative_latent,按 PLAN 实现)+## 方法(family: generative_latent,按 node16 PLAN 的三个 Fix + 一个新的解码 Fix) -1. HVG 2500(seurat,最后输入阶段,仅 covered 基因);log1p(CP10k) 直接使用。-2. Gaussian VAE:enc 2500→512→15(mu,logvar),dec 15→512→2500;损失 MSE(按基因求和)+0.1*KL;Adam lr=1e-3,batch 256,60 epoch(~70s CPU),训练细胞 = 两阶段合并抽样 ≤3000。-3. 位移:两输入阶段按 celltype 匹配(每型 ≥10 细胞;X3 上匹配到 5 型,共 1093 个回归点);Δz_t = mean z_last,t − mean z_first,t;对 first 阶段细胞 ridge 拟合 δ(z)=Wz+b(λ=1,bias 不罚)。z_pred = z_last + s·δ(z_last),s = clip((t_target−t_last)/(t_last−t_first), 0, 2)·DISPLACEMENT_SCALE(X3 上时间比=2.0)。单输入退路:无配对 → s=0,纯 encode→decode。-4. 解码(提交默认 DECODE_MODE=resid):dX = dec(z_pred)−dec(z_last),x = x_last + RESID_W·dX,只施加在 x_last 非零的 HVG 位点,clip≥0;非 HVG 基因保留最后阶段原值;细胞按 sample_rows 下采样到 [min,max]。PLAN 字面的 dense 解码(DECODE_MODE=dense)和 mask 变体也保留在代码中。-5. 输出:view_io.write_prediction,全 genes.txt 面板。+1. HVG 2500(seurat,最后输入阶段,仅 covered);log1p(CP10k) 直接用。+2. Gaussian VAE:enc 2500→512→20(mu,logvar),dec 20→512→2500;MSE+0.1*KL;Adam 1e-3,batch 256,60 epoch(~30s CPU),训练细胞 ≤3000(X3 上两阶段共 3553)。**Fix 3:latent 15→20**。+3. **Fix 1(配对)**:两输入阶段按 celltype 匹配(每型 ≥10 细胞,X3 匹配 5 型 / 1093 个 first 阶段细胞)。对每个 first 阶段细胞 i,在同型的 last 阶段 latent 里取 K=10 个欧氏近邻,δ_i = mean(z_nn) − z_i;ridge(λ=1, bias 不罚) 拟合 δ(z)=Wz+b。`KNN_PAIR=0` 退回父节点的每型 Δz 均值目标(同一次程序内 A/B)。+4. 外推:z_pred = z_last + s·δ(z_last),s = clip((t_target−t_last)/(t_last−t_first),0,2)·DISPLACEMENT_SCALE(X3 上 s=2.0;只用时间差,不用绝对时间)。单输入退路:无配对 → s=0 → 输出等于最后阶段。+5. **新 Fix(解码,提交默认 DECODE_MODE=readout)**:非线性解码器在稀疏 scRNA 上重建太差(按基因 Pearson 0.387),dec(z_pred)−dec(z_last) 主要是噪声(父节点 resid 路线全配置 ≤ copy_last 地板)。改为把**同一个** latent 位移场用 ridge 校准的线性算子读回基因空间:对每个匹配型取实测伪批量差 Δ_t = mean(x_last,t)−mean(x_first,t)(HVG,按 √(n1n2)/(√(n1n2)+30) 收缩)与型级位移 δ_t,解 G = (Σ_t Δ_t δ_tᵗ)(Σ_t δ_t δ_tᵗ + λI)⁻¹,λ=0.01(fit resid 0.0017 vs |Δ| 0.0320)。逐细胞基因变化 g_i = s·w·[G δ_{t(i)} + κ·G(δ_i − δ_{t(i)})],κ=1、w=1.5,只加在 x_last>0 的 HVG 位点并 clip≥0;非 HVG 与未匹配型保持原值。父节点的 resid 解码(含 **Fix 2 表达门控** GATE_Q=0.9/GATE_LO=0.25)完整保留在代码里,可用 DECODE_MODE=resid 复现。 -生物知识来源:无外部生物先验;只用视图内表达与标签、时间差。CPU only(EXECUTION.json gpu=false),torch 单线程 + 固定 seed,确定。+生物知识来源:无外部先验、无外部数据;只用视图内表达、celltype 标签与阶段时间差。CPU only(EXECUTION.json gpu=false),torch 单线程 + `np.random.default_rng(seed)`,对 seed 确定(seed 0 复跑逐元素相同)。  ## 机制生效证据(X3 视图,seed 0 实测) -- δ 非常数:within-type δ 标准差 0.0202(|δ| 均值 0.2561)→ 位置依赖存在但相对型间差较小;位移确实改变了全部 652 个输出细胞的非零 HVG 位点值(resid 模式,非零率保持 0.078)。-- 零转移对照(DISPLACEMENT_SCALE=0,dense 模式):41.86 vs 完整 dense 40.43(分组:cov 18.91→16.14, cell_state 48.26→46.04, de 43.8→43.09, dir 50.59→50.45)——dense 下位移不加分;resid 模式下 scale=0 解析上精确等于 copy_last 下采样(dX=0)。-- dense 解码把 HVG 非零率 0.078→0.675,covariation 崩到 16;重建质量低:dec(z_last) 对输入的按基因 Pearson 均值仅 0.36 → 绝对解码值不可用,只能用解码差值(resid)。+- **δ 不再退化为常数**:kNN 配对目标 within-type sd/|δ| = 1.135,拟合场 fit within-type sd 0.4794 / |δ| 均值 0.4911 = **0.976**(父节点 0.08;`KNN_PAIR=0` 在同 latent 下为 0.52,fit_between_sd 0.1485 > 0.1302,即型间仍占主导)。型内位置依赖确实存在。+- **位置依赖部分本身有效**(κ A/B,其余全同):κ=1 → 50.68(λ=0.01)/ 50.14(λ=0.1);κ=0(每型常数读出)→ 49.30 / 49.25。cell_state 57.20 vs 53.67、55.10 vs 53.05。差值 +1.4 / +0.9,在噪声(~2)边缘但两个 λ 同向。+- **实际改变了哪些细胞**:652 个输出细胞中 480 个被位移改变(其余 172 个属于两阶段未匹配的细胞型,按设计不动);2166 个基因列被改动(HVG 内);w=1.5 时每细胞 L1 改变 mean=88.9 / p95=210.9;非零率 0.078→0.075(clip≥0 造成,未做稠密填充)。+- **机制关闭对照**:`DISPLACEMENT_SCALE=0` → g=0,输出与 copy_last 下采样**逐元素相同**(已用 readout 与 resid 两种解码各验一次,max|Δ|=0),X3 = 48.28。即本节点相对关闭机制 +2.4~+3.5。+- **表达门控(Fix 2)隔离**:resid 解码下,同位移 w=0.5,有门控 46.22 vs 无门控 45.18(cell_state 45.71 vs 42.67)→ 门控有效但整条 resid 路线仍低于地板,故提交改用 readout 解码。 -## 查分记录(vec-score X3,A 半)+## 查分记录(vec-score X3,A 半,seed 0) -| 配置 | 分数 | cov | cell_state | de | dir |+| 配置 | 分数 | de | dir | cell_state | cov | |---|---|---|---|---|---|-| dense 完整 | 40.43 | 16.14 | 46.04 | 43.09 | 50.45 |-| dense 零转移 | 41.86 | 18.91 | 48.26 | 43.80 | 50.59 |-| mask 完整 | 44.55 | 42.26 | 44.12 | 43.09 | 48.35 |-| resid w=1 | 47.74 | 46.61 | 50.42 | 43.44 | 49.73 |-| resid w=2 | 46.85 | 44.16 | 49.41 | 43.09 | 49.69 |-| resid w=0.5(提交默认) | 48.14 | 47.49 | 50.71 | 43.80 | 49.92 |-| resid w=0.25 | 48.17 | 47.85 | 50.80 | 43.44 | 50.02 |+| DISPLACEMENT_SCALE=0(机制关闭 = copy_last 下采样) | 48.28 | 43.44 | 50.12 | 50.83 | 48.18 |+| 父路线 resid w=0.5 + gate + kNN(latent20) | 46.22 | 43.09 | 48.72 | 45.71 | 47.75 |+| resid w=0.5 + gate,KNN_PAIR=0 | 48.22 | 43.44 | 50.10 | 50.97 | 47.72 |+| resid w=0.5,gate 关 | 45.18 | 42.74 | 48.68 | 42.67 | 47.60 |+| resid w=0.25/1.0 + gate + kNN | 46.94 / 44.47 | | | | |+| readout λ=1.0 κ=1 w=1 | 49.22 | 44.92 | 50.37 | 52.45 | 48.31 |+| readout λ=0.1 κ=1 w=1(w=0.5) | 50.14(49.21) | 45.30 | 50.43 | 55.10 | 48.39 |+| readout λ=0.1/0.01 κ=0 w=1 | 49.25 / 49.30 | | | 53.05 / 53.67 | |+| readout λ=0.01 κ=1 w=1 | 50.68 | 44.92 | 50.44 | 57.20 | 48.41 |+| readout λ=0.001 κ=1 w=1 | 50.80 | 44.92 | 50.45 | 57.61 | 48.39 |+| **readout λ=0.01 κ=1 w=1.5(提交默认)** | **51.80** | 44.92 | 50.50 | 60.95 | 48.31 |++相对父节点(T1 48.47 / X3 resid 48.14):X3 +3.7;四组分 de_recovery 43.09→44.92、direction 48.72→50.50、cell_state 45.71→60.95、covariation 47.75→48.31,全部不劣于父节点且三项超过关闭机制对照。  ## 验证过 / 没验证 -- 验证过:X3 视图完整跑通(~2.5 min),vec-check ok,seed 0 确定复现;resid w∈{0.25,0.5,1,2} 扫描,0.25–0.5 平台。-- 没验证:proxy / proxy2 / final 视图未跑(本节点只挂 X3;代码有单输入退路与视图无关时间比,逻辑上兼容);多 seed 未测;VAE 超参(latent 15、KL 0.1)未按 PLAN 备选值重调(resid 模式对重建质量不敏感,故未调)。-- 如实报告:resid w=0.5 的 48.14 与种子 copy_last(T1 47.92,X3 未直接测)差距在噪声(~2 分)内;本方法族在该数据上未证明显著优于复制。w 越小越贴近 copy_last 地板。+- 验证过:X3 完整跑通(~45s,峰值内存 <3 GB),vec-check ok(seed 0 与 seed 1),seed 0 逐元素复现;λ∈{0.001,0.01,0.1,1}、w∈{0.5,1,1.5}、κ∈{0,1,1.5 未查分}、KNN_PAIR、GATE、SHRINK_N0 的 A/B;DISPLACEMENT_SCALE=0 精确等于 copy_last。+- 没验证:w=1.5 是查分额度用尽前的最后一个点,w>1.5 未测(cell_state 仍随 w 单调上升,可能还有余量,也可能过冲);κ=1.5、SHRINK_N0=0 已生成预测但**未查分**;proxy/proxy2/final 视图未跑(代码只用时间差与标签,单输入退路已覆盖);多 seed 只跑了 seed 0/1,未查分(额度);latent 20 与 15 的对比只在 resid 路线上间接看过。+- 如实报告:readout 解码把「latent 位移场」经由实测型级伪批量差校准回基因空间,因此增益的主体是**型级位移经正确解码后的表达改变**(cell_state +10),而 PLAN 要求的细胞级位置依赖(κ)在其之上只贡献 +0.9~+1.4(两个 λ 同向、幅度接近噪声)。父节点的解码器差值(resid)路线在 X3 上任何配置都不超过关闭机制对照,已放弃为主解码,仅保留门控(Fix 2)在 resid 分支中。diff --git a/solution/run.py b/solution/run.pyindex 9801a31..8843506 100644--- a/solution/run.py+++ b/solution/run.py@@ -1,25 +1,40 @@ #!/usr/bin/env python """Gaussian VAE on HVGs with position-dependent linear latent transition. -family: generative_latent (PLAN: "Gaussian VAE on HVG with linear latent-transition, zero-shift control").--Steps (per PLAN):- 1. HVG(2500, seurat) on the last input stage, log1p(CP10k) used directly.- 2. Gaussian VAE: enc 2500->512->15, dec 15->512->2500, loss MSE + 0.1*KL,-    Adam lr=1e-3, batch=256, <=60 epochs / 5 min, <=3000 training cells.- 3. Latent dynamics: for cell types matched across the two input stages,-    dz_t = mean(z_last,t) - mean(z_first,t); fit delta(z) = W z + b by ridge-    (lambda=1.0) on first-stage cells; extrapolate-    z_pred = z_last + s * delta(z_last), s = dt(target-last)/dt(last-first)-    clipped to [0, 2] (time differences only -> view independent).- 4. Decode. Default mode "resid": the decoder-translated displacement-    dX = dec(z_pred) - dec(z_last) is added (weight RESID_W=0.5) to the-    last-stage HVG values at nonzero positions only, clip>=0; non-HVG genes-    keep the last-stage values. Alternative modes: "dense" (x=dec(z_pred),-    clip>=0, PLAN literal) and "mask" (dense restricted to nonzero positions).- 5. Single-input fallback: no transition fit, z_pred = z_last (encode->decode).-Control: DISPLACEMENT_SCALE=0 env var -> zero-shift (encode->decode only).+family: generative_latent (PLAN node 16: cell-level kNN-paired latent+displacement + expression-gated residual).++Steps:+  1. HVG(2500, seurat) on the last input stage, log1p(CP10k) used directly.+  2. Gaussian VAE: enc d->HIDDEN->LATENT(mu,logvar), dec LATENT->HIDDEN->d,+     loss MSE + KL_WEIGHT*KL, Adam lr=1e-3, batch=256, MAX_EPOCHS.+  3. Latent dynamics on cell types matched across the two input stages.+     Default (KNN_PAIR=1) builds *cell-level* paired targets: for each+     first-stage cell i, the K nearest neighbours (Euclidean, same celltype)+     in the last-stage latent give delta_i = mean(z_nn) - z_i, so the+     regression targets carry within-type position dependence instead of one+     mean per type. delta(z) = W z + b is then fitted by ridge (lambda=1).+     KNN_PAIR=0 reproduces the parent (per-type mean dz targets).+     z_pred = z_last + s * delta(z_last),+     s = clip(dt(target-last)/dt(last-first), 0, 2) * DISPLACEMENT_SCALE+     (time differences only -> view independent).+  4. Decode. Default mode "readout": the nonlinear decoder reconstructs sparse+     scRNA too poorly (per-gene Pearson ~0.39) for dec(z_pred)-dec(z_last) to+     carry signal, so the *same* latent field is read into gene space by a+     ridge-calibrated linear operator G fitted on the measured per-type+     pseudobulk differences of the two input stages:+        G = (sum_t Delta_t delta_t^T)(sum_t delta_t delta_t^T + lam I)^-1+        g_i = s * READOUT_W * [G delta_{t(i)} + KAPPA * G (delta_i - delta_{t(i)})]+     added to the last-stage HVG values at nonzero positions only, clip>=0;+     non-HVG genes and cell types without a matched counterpart keep their+     last-stage values. Mode "resid" (parent) keeps dX = dec(z_pred)-dec(z_last)+     with the expression gate (GATE_ON=1): positions at/above the per-cell+     GATE_Q quantile of that cell's nonzero HVG values get weight GATE_LO*w,+     all others w -> highly expressed DE genes are protected from dilution.+     Other modes: "dense" (x=dec(z_pred), PLAN literal), "mask".+  5. Single-input fallback: no transition fit, z_pred = z_last.+Controls: DISPLACEMENT_SCALE=0 -> zero shift (resid: output == copy_last+downsampled); KNN_PAIR=0 -> parent per-type-mean targets. Deterministic for a given --seed; CPU only; torch single-threaded. """ from __future__ import annotations@@ -32,17 +47,31 @@ import time import numpy as np from scipy import sparse -LATENT_DIM = 15-HIDDEN = 512-KL_WEIGHT = 0.1-LR = 1e-3-BATCH = 256-MAX_EPOCHS = 60-TRAIN_SECONDS = 300.0-MAX_TRAIN_CELLS = 3000-N_HVG = 2500-L2_LAMBDA = 1.0-MIN_TYPE_CELLS = 10++def _envi(name, default):+    return int(os.environ.get(name, str(default)))+++def _envf(name, default):+    return float(os.environ.get(name, str(default)))+++LATENT_DIM = _envi("LATENT_DIM", 20)+HIDDEN = _envi("HIDDEN", 512)+KL_WEIGHT = _envf("KL_WEIGHT", 0.1)+LR = _envf("LR", 1e-3)+BATCH = _envi("BATCH", 256)+MAX_EPOCHS = _envi("MAX_EPOCHS", 60)+TRAIN_SECONDS = _envf("TRAIN_SECONDS", 600.0)+MAX_TRAIN_CELLS = _envi("MAX_TRAIN_CELLS", 3000)+N_HVG = _envi("N_HVG", 2500)+L2_LAMBDA = _envf("L2_LAMBDA", 1.0)+MIN_TYPE_CELLS = _envi("MIN_TYPE_CELLS", 10)+KNN_PAIR = _envi("KNN_PAIR", 1)+KNN_K = _envi("KNN_K", 10)+GATE_ON = _envi("GATE_ON", 1)+GATE_Q = _envf("GATE_Q", 0.90)+GATE_LO = _envf("GATE_LO", 0.25)   def log(*a):@@ -57,7 +86,8 @@ def main():     args = ap.parse_args()     seed = int(args.seed) -    disp_scale_env = float(os.environ.get("DISPLACEMENT_SCALE", "1.0"))+    disp_scale_env = _envf("DISPLACEMENT_SCALE", 1.0)+    resid_w = _envf("RESID_W", 0.5)      rng = np.random.default_rng(seed)     import torch@@ -95,6 +125,7 @@ def main():     hvg_local = np.flatnonzero(tmp.var["highly_variable"].to_numpy())     hvg = np.flatnonzero(covered)[hvg_local]     d = len(hvg)+    del tmp, sub      # ---- gather training cells (all stages, capped) ----     mats, stage_of, type_of = [], [], []@@ -103,6 +134,7 @@ def main():         mats.append(np.asarray(X[:, hvg], dtype=np.float32))         stage_of.append(np.full(a.n_obs, si))         type_of.append(np.asarray(a.obs["celltype"].astype(str).to_numpy()))+        del X     Xall = np.concatenate(mats, axis=0)     stage_all = np.concatenate(stage_of)     type_all = np.concatenate(type_of)@@ -111,7 +143,8 @@ def main():         Xtr = Xall[pick]     else:         Xtr = Xall-    log(f"train cells={Xtr.shape[0]} genes(HVG)={d}")+    log(f"train cells={Xtr.shape[0]}/{Xall.shape[0]} genes(HVG)={d} latent={LATENT_DIM} "+        f"epochs<={MAX_EPOCHS} kl={KL_WEIGHT}")      # ---- VAE ----     class VAE(torch.nn.Module):@@ -138,6 +171,7 @@ def main():     Xt = torch.from_numpy(Xtr)     n = Xt.shape[0]     t0 = time.time()+    epoch = -1     for epoch in range(MAX_EPOCHS):         perm = torch.randperm(n)         tot = 0.0@@ -160,14 +194,20 @@ def main():      def encode_mean(X):         with torch.no_grad():-            mu, _ = model.encode(torch.from_numpy(X))+            mu, _ = model.encode(torch.from_numpy(np.ascontiguousarray(X)))         return mu.numpy()      def decode(Z):         with torch.no_grad():-            out = model.decode(torch.from_numpy(Z)).numpy()+            out = model.decode(torch.from_numpy(np.ascontiguousarray(Z))).numpy()         return np.clip(out, 0.0, None) +    def decode_rows(Z, chunk=512):+        out = np.empty((Z.shape[0], d), dtype=np.float32)+        for i in range(0, Z.shape[0], chunk):+            out[i:i + chunk] = decode(Z[i:i + chunk])+        return out+     # ---- encode every stage ----     Z = [encode_mean(m) for m in mats]     z_last = Z[-1]@@ -176,6 +216,7 @@ def main():     scale = 0.0     W = np.zeros((LATENT_DIM, LATENT_DIM), dtype=np.float64)     b = np.zeros(LATENT_DIM, dtype=np.float64)+    diag = {}     if len(stages) >= 2:         z_first = Z[0]         t_first, t_last = float(inputs[0]["time"]), float(inputs[-1]["time"])@@ -191,54 +232,186 @@ def main():         matched = [t for t in types1 if t in types2                    and len(types1[t]) >= MIN_TYPE_CELLS and len(types2[t]) >= MIN_TYPE_CELLS]         Xs, Ys = [], []+        tgt_within_sd, tgt_mean_abs = [], []         for t in sorted(matched):-            dz = z_last[types2[t]].mean(axis=0) - z_first[types1[t]].mean(axis=0)-            Xs.append(z_first[types1[t]])-            Ys.append(np.tile(dz, (len(types1[t]), 1)))+            A = z_first[types1[t]].astype(np.float64)+            B = z_last[types2[t]].astype(np.float64)+            if KNN_PAIR:+                k = int(min(KNN_K, B.shape[0]))+                # pairwise squared euclidean, chunked over rows of A+                Bsq = (B ** 2).sum(1)+                deltas = np.empty_like(A)+                for i0 in range(0, A.shape[0], 256):+                    Ac = A[i0:i0 + 256]+                    D = (Ac ** 2).sum(1)[:, None] + Bsq[None, :] - 2.0 * (Ac @ B.T)+                    idx = np.argpartition(D, k - 1, axis=1)[:, :k]+                    deltas[i0:i0 + 256] = B[idx].mean(axis=1) - Ac+                    del D+            else:+                deltas = np.tile(B.mean(0) - A.mean(0), (A.shape[0], 1))+            Xs.append(A)+            Ys.append(deltas)+            tgt_within_sd.append(float(deltas.std(axis=0).mean()))+            tgt_mean_abs.append(float(np.abs(deltas).mean()))         if Xs:-            Xr = np.concatenate(Xs).astype(np.float64)-            Yr = np.concatenate(Ys).astype(np.float64)+            Xr = np.concatenate(Xs)+            Yr = np.concatenate(Ys)             A = np.hstack([Xr, np.ones((Xr.shape[0], 1))])             reg = np.eye(LATENT_DIM + 1) * L2_LAMBDA             reg[-1, -1] = 0.0             sol = np.linalg.solve(A.T @ A + reg, A.T @ Yr)             W, b = sol[:LATENT_DIM], sol[LATENT_DIM]             scale = time_ratio * disp_scale_env-            within_sd = float(np.std([(z_last[types2[t]] @ W + b).std(axis=0).mean() for t in matched]))-            log(f"matched types={len(matched)} ridge cells={Xr.shape[0]} time_ratio={time_ratio:.3f} "-                f"disp_scale={scale:.3f} within-type delta sd={within_sd:.4f} "-                f"|delta| mean={np.abs(z_last @ W + b).mean():.4f}")+            # fitted field diagnostics+            fit_all = z_last.astype(np.float64) @ W + b+            within = []+            for t in sorted(matched):+                within.append(float(fit_all[types2[t]].std(axis=0).mean()))+            diag = dict(+                matched=len(matched), pairs=int(Xr.shape[0]), time_ratio=time_ratio,+                target_within_sd=float(np.mean(tgt_within_sd)),+                target_abs_mean=float(np.mean(tgt_mean_abs)),+                fit_within_sd=float(np.mean(within)),+                fit_abs_mean=float(np.abs(fit_all).mean()),+            )+            diag["target_ratio"] = diag["target_within_sd"] / max(diag["target_abs_mean"], 1e-9)+            diag["fit_ratio"] = diag["fit_within_sd"] / max(diag["fit_abs_mean"], 1e-9)+            # between-type spread of the fitted field (for comparison)+            tm = np.stack([fit_all[types2[t]].mean(0) for t in sorted(matched)])+            diag["fit_between_sd"] = float(tm.std(axis=0).mean())+            log(f"pairing={'knn' if KNN_PAIR else 'type-mean'} K={KNN_K} " ++                " ".join(f"{k}={v:.4f}" if isinstance(v, float) else f"{k}={v}"+                         for k, v in diag.items()))         else:             log("no matched types -> zero shift") -    z_pred = z_last + scale * (z_last.astype(np.float64) @ W + b)+    delta_last = z_last.astype(np.float64) @ W + b          # position-dependent field+    z_pred = z_last + scale * delta_last     z_pred = z_pred.astype(np.float32) +    # ---- calibrated linear gene-space readout of the latent displacement ----+    # The nonlinear decoder is too noisy on sparse scRNA (per-gene Pearson ~0.4),+    # so "readout" mode maps the *same* latent field delta(z) into gene space with+    # a ridge-fitted linear operator G calibrated on the measured per-type+    # pseudobulk differences of the two input stages:+    #   G = argmin sum_t || G delta_t - Delta_t ||^2 + lam ||G||^2+    # per-cell change g_i = G delta_i = G delta_{t(i)} + kappa * G (delta_i - delta_{t(i)})+    G = None+    RO_TYPES = []+    if os.environ.get("DECODE_MODE", "readout") == "readout" and len(stages) >= 2 and np.abs(W).sum() + np.abs(b).sum() > 0:+        si_last = len(stages) - 1+        lab1 = type_all[stage_all == 0]+        lab2 = type_all[stage_all == si_last]+        lam = _envf("READOUT_LAMBDA", 0.01)+        n0 = _envf("SHRINK_N0", 30.0)+        Dmat, Smat, tnames, tidx_last = [], [], [], []+        for t in sorted(set(lab1) & set(lab2)):+            i1 = np.flatnonzero(lab1 == t)+            i2 = np.flatnonzero(lab2 == t)+            if len(i1) < MIN_TYPE_CELLS or len(i2) < MIN_TYPE_CELLS:+                continue+            d1 = (mats[0][i1]).mean(0).astype(np.float64)+            d2 = (mats[si_last][i2]).mean(0).astype(np.float64)+            nt = float(np.sqrt(len(i1) * len(i2)))+            f = nt / (nt + n0)+            Dmat.append((d2 - d1) * f)+            Smat.append(delta_last[i2].mean(0))+            tnames.append(t)+            tidx_last.append(i2)+        if Dmat:+            Dm = np.stack(Dmat)                      # (T, d)+            Sm = np.stack(Smat)                      # (T, L)+            G = (Dm.T @ Sm) @ np.linalg.inv(Sm.T @ Sm + lam * np.eye(LATENT_DIM))+            resid_fit = float(np.abs(Dm - Sm @ G.T).mean())+            gnorm = float(np.abs(G).mean())+            RO_TYPES = list(tnames)+            log(f"readout: types={len(tnames)} lam={lam} shrink_n0={n0} "+                f"|G| mean={gnorm:.4f} fit resid(mean|.|)={resid_fit:.4f} "+                f"(Delta mean|.|={float(np.abs(Dm).mean()):.4f})")+     # ---- decode + build output ----     n_out = view_io.target_n_cells(man, last.n_obs)     rows = view_io.sample_rows(last.n_obs, n_out, rng)-    Xdec = decode(z_pred)[rows]                      # (n_out, d) HVG dense+    R = decode_rows(z_last[rows])+    Xdec = decode_rows(z_pred[rows])     Xout = Xl[rows].copy()                           # full panel, last-stage values-    mode = os.environ.get("DECODE_MODE", "resid")-    if mode == "resid":-        R = decode(z_last)[rows]-        dX = Xdec - R                                # decoder-translated latent displacement-        w = float(os.environ.get("RESID_W", "0.5"))-        upd = Xout[:, hvg] + w * dX-        Xout[:, hvg] = np.where(Xout[:, hvg] > 0, np.clip(upd, 0.0, None), 0.0)-        Xdec = Xout[:, hvg]+    mode = os.environ.get("DECODE_MODE", "readout")+    Hvg = Xout[:, hvg]+    topk = _envi("TOPK", 0)+    topk_free = _envi("TOPK_FREE", 0)+    if mode == "readout":+        rw = _envf("READOUT_W", 1.5)+        kap = _envf("READOUT_KAPPA", 1.0)+        g = np.zeros((z_last.shape[0], d), dtype=np.float64)+        if G is not None:+            lab_l = type_all[stage_all == len(stages) - 1]+            dcell = delta_last @ G.T+            for t in RO_TYPES:+                idx = np.flatnonzero(lab_l == t)+                dt = delta_last[idx].mean(0) @ G.T+                g[idx] = dt + kap * (dcell[idx] - dt)+            g *= scale * rw+        Hn = np.where(Hvg > 0, np.clip(Hvg + g[rows], 0.0, None), 0.0)+        l1 = np.abs(Hn - Hvg).sum(axis=1)+        nz_new = int(((Hn > 0) & (Hvg <= 0)).sum())+        log(f"readout apply: w={rw} kappa={kap} scale={scale:.3f} "+            f"cells_changed={int((l1>0).sum())}/{Hvg.shape[0]} L1/cell mean={l1.mean():.3f} "+            f"p95={np.percentile(l1,95):.3f} nonzero_before={int((Hvg>0).sum())} after={int((Hn>0).sum())}")+        Xout[:, hvg] = Hn+        Hvg = Hn+    elif mode == "resid":+        dX = Xdec - R                                  # decoder-translated latent displacement+        apply_mask = Hvg > 0+        if topk > 0:+            kk = int(min(topk, d))+            absd = np.abs(dX)+            if not topk_free:+                absd = np.where(apply_mask, absd, np.float32(-1.0))+            thr = np.partition(absd, d - kk, axis=1)[:, d - kk][:, None]+            keep = absd >= thr+            dX = np.where(keep, dX, 0.0)+            if topk_free:+                apply_mask = apply_mask | keep+            log(f"topk={kk} free={topk_free} kept={int(keep.sum())} "+                f"new_nonzero={int((keep & (Hvg <= 0)).sum())}")+        w = np.full(Hvg.shape, resid_w, dtype=np.float32)+        if GATE_ON:+            nzmask = Hvg > 0+            gate = np.ones(Hvg.shape, dtype=np.float32)+            n_gated = 0+            for i in range(Hvg.shape[0]):+                v = Hvg[i][nzmask[i]]+                if v.size == 0:+                    continue+                thr = np.quantile(v, GATE_Q)+                hi = nzmask[i] & (Hvg[i] >= thr)+                gate[i, hi] = GATE_LO+                n_gated += int(hi.sum())+            w = w * gate+            log(f"gate: q={GATE_Q} lo={GATE_LO} gated_positions={n_gated}/"+                f"{int(nzmask.sum())} nonzero ({n_gated/max(int(nzmask.sum()),1):.3f})")+        upd = Hvg + w * dX+        newh = np.where(apply_mask, np.clip(upd, 0.0, None), Hvg if topk_free else 0.0)+        l1 = np.abs(newh - Hvg).sum(axis=1)+        log(f"resid w={resid_w} cells_changed={int((l1>0).sum())}/{Hvg.shape[0]} "+            f"L1/cell mean={l1.mean():.3f} p50={np.percentile(l1,50):.3f} "+            f"p95={np.percentile(l1,95):.3f} max={l1.max():.3f} "+            f"|dX| mean={np.abs(dX)[Hvg>0].mean():.4f}")+        Xout[:, hvg] = newh+        Hvg = newh     else:         if mode == "mask":-            Xdec = np.where(Xout[:, hvg] > 0, Xdec, 0.0)-        Xout[:, hvg] = Xdec-    R0 = decode(z_last)[rows]-    A, B = Xl[rows][:, hvg].astype(np.float64), R0.astype(np.float64)-    Ac, Bc = A - A.mean(0), B - B.mean(0)+            Hvg = np.where(Xout[:, hvg] > 0, Xdec, 0.0)+        else:+            Hvg = Xdec+        Xout[:, hvg] = Hvg+    A_, B_ = Xl[rows][:, hvg].astype(np.float64), R.astype(np.float64)+    Ac, Bc = A_ - A_.mean(0), B_ - B_.mean(0)     den = np.sqrt((Ac ** 2).sum(0) * (Bc ** 2).sum(0)) + 1e-12     log(f"recon(z_last) per-gene pearson: mean={float(((Ac*Bc).sum(0)/den).mean()):.4f}")     nz_before = float((Xl[rows][:, hvg] > 0).mean())-    nz_after = float((Xdec > 0).mean())-    log(f"out cells={n_out} HVG nonzero frac: input={nz_before:.3f} decoded={nz_after:.3f}")+    nz_after = float((Hvg > 0).mean())+    log(f"out cells={n_out} HVG nonzero frac: input={nz_before:.3f} output={nz_after:.3f}")     Xsp = sparse.csr_matrix(Xout.astype(np.float32))     view_io.write_prediction(Xsp, genes, args.out)     log(f"wrote {args.out}")

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

用到的知识库条目

编号标题出处
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)
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)

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

改了什么在父节点 VAE 位移上做了 4 处改动:kNN(K=10, 同型) 细胞级配对目标替代每型 Δz 均值、latent 15→20、表达门控 (GATE_Q=0.9/GATE_LO=0.25) 保留在 resid 分支;并把提交默认解码从 resid 换成新的 readout——用两输入阶段实测型级伪批量差 ridge 标定线性算子 G(λ=0.01),逐细胞 g=s·w·[Gδ_t + κ·G(δ_i−δ_t)],κ=1、w=1.5,只加到 x_last>0 的 HVG 位点。
各组分数的变化cell_state:变好:61.32 vs 51.11,+10.21(远超噪声,是本节点全部增益的来源)
covariation:噪声内:48.23 vs 47.50,+0.73(PLAN 期望的第二组没有改善)
de_recovery:噪声内:45.16 vs 44.09,+1.07(< T1 噪声 ~2;PLAN 设定的 ≥2 分恢复目标未达成)
direction:噪声内:50.99 vs 50.46,+0.53
榜分:变好:52.08 vs 48.47,+3.61;耗时 26.9s(父 33.7s)、峰值内存 1.74GB(父 2.15GB),资源同时下降
family_idgenerative_latent
假设是否成立否
经验
  1. 在 VAE latent 重建质量差(按基因 Pearson ~0.39)时,用 dec(z_pred)−dec(z_last) 作为基因空间增量全是噪声:父节点 resid 路线任何 w(0.25/0.5/1/2) 都 ≤ copy_last 地板;把同一个 latent 位移场改用「实测型级伪批量差 ridge 标定的线性算子 G」读回基因空间,X3 48.28→51.80、T1 cell_state +10.21。结论:latent 动力学的瓶颈通常在解码/标定,不在位移场本身。
  2. kNN 细胞级配对确实消除了退化(fit within-type δ sd/|δ| 0.08→0.976),但这只是必要条件、不带来分数:位置依赖项 κ=1 vs κ=0 仅 +1.4/+0.9(两个 λ 同向、幅度在 T1 噪声 ~2 内)。诊断指标改善 ≠ 分数改善,必须再做 κ A/B 才能归因。
  3. 增益主体是「每型常数位移 × 正确标定」,即退化成型级常数位移 + 线性读出,因此 PLAN 声称的细胞级机制不能记为已验证;预期改善的 de_recovery/covariation 都在噪声内,意外改善的是 cell_state。
  4. 机制关闭对照 DISPLACEMENT_SCALE=0 与 copy_last 下采样逐元素相同(max|Δ|=0,X3 48.28),这是可信的零对照写法:用环境变量让关闭态解析上等于 baseline,而不是靠近似。
  5. 把超参改成 env 读取(LATENT_DIM/KNN_PAIR/GATE_*/READOUT_*/KAPPA)+ 同一 run 内 A/B,使 30 分钟预算内完成 λ∈{0.001,0.01,0.1,1}、w∈{0.5,1,1.5}、κ、KNN_PAIR、GATE 扫描,代价是查分额度成为瓶颈(w>1.5、κ=1.5、SHRINK_N0=0 生成未查分)。
  6. w 从 1→1.5 时 cell_state 52.45→60.95 单调上升且未见回落,说明 w 仍在欠拟合区间而非最优点;但只在单一视图单 seed 上观察到,存在过冲风险。
mechanism_active否
下一步建议
  1. 针对 cell_state:在 X3/T1 上扫 READOUT_W∈{1.5,2,2.5,3}(λ=0.01, κ=1 固定),找 cell_state 峰值与 covariation/de_recovery 开始下降的拐点;每档同时记录 L1/cell 与非零率变化,若 covariation 掉 >2 分即停。
  2. 针对 covariation(48.23,最弱组之一):readout 只在 x_last>0 位点做加法性位移,会压缩基因间相关;试 G 的分型×PC 子空间版本或在 g 上叠加一个保持型内协方差的再着色步(参考 node 12 的白化-再着色),并与当前提交做同 seed A/B。
  3. 针对 de_recovery(45.16,仍比 node 2 baseline 51.38 低 6 分):把表达门控从 resid 分支移植到 readout 分支(对 g 按 x_last 的 p90 缩放),并按 DE 基因列表给 g 乘 >1 的基因级权重,先查 GATE_ON=0/1 两点隔离贡献。
  4. 泛化性:当前所有结论只来自 X3 单视图 seed 0;提交前在 proxy/final 视图与 seed 1 上各跑一次 readout λ=0.01 w=1.5,确认 +3.6 不是视图特异(KNN_PAIR=0 对照同跑,用于确认型级 readout 是主贡献)。

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

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

分析员

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

工程师

角色工程师 alibaba-token-plan-cn/qwen3.8-max
调用次数1 次
轮数28
工具调用共 31 次:bash 25、edit 3、write 2、read 1
用时23 分
token 数输入 74,368 · 输出 15,413 · 思考 15,550
任务(第一行)按计划修改程序:cell-level kNN-paired latent displacement + expression-gated residual
最后的回答(摘录)
**做了什么**:在父节点 VAE 位移上实现 PLAN 三 Fix(kNN 细胞级配对、表达门控、latent 20)+ 一个新的解码 Fix:把 latent 位移场用 ridge 校准的线性算子 G 读回基因空间(G 由两输入阶段实测型级伪批量差标定),逐细胞按时间比外推、只加到最后阶段非零 HVG 位点。

**查分(X3,seed 0,额度用尽)**:机制关闭对照 48.28 → 提交默认 **51.80**(de 44.92 / dir 50.50 / cell_state 60.95 / cov 48.31),父节点 resid 路线 48.14。证据:kNN 配对使 fit within-type δ sd/|δ| 从 0.08 升到 0.976;κ=1 vs κ=0(位置依赖 vs 每型常数)+1.4/+0.9(两个 λ 同向、接近噪声);DISPLACEMENT_SCALE=0 逐元素等于 copy_last;seed 0 复现、vec-check ok。

**下一步最值得试**:w>1.5(cell_state 仍随 w 单调上升,额度和时间都用尽未测);κ=1.5 与 SHRINK_N0=0 的预测已生成但未查分;把 readout 的 G 从型级 5 对扩到分型×PC 子空间(T1 上 node 12 的白化-再着色可作 covariation 修复),并在 proxy/final 视图验证迁移。
原始记录位置/home/spark-longxinyang/vec/runs/formal/20261002-202907-search-t1-scr-A/nodes/16/engineer.jsonl 176 KB
/home/spark-longxinyang/vec/runs/formal/20261002-202907-search-t1-scr-A/nodes/16/engineer.stderr

调研员

角色调研员 alibaba-token-plan-cn/qwen3.8-max
调用次数1 次
轮数1
工具调用共 0 次:—
用时2 分
token 数输入 16,325 · 输出 1,044 · 思考 4,207
任务(第一行)为节点 n16(改进,父节点 n15)写改进计划 PLAN.json
最后的回答(摘录)
ay still yield nearly uniform displacements if the VAE latent does not separate within-type heterogeneity; Engineer should check within-type δ sd immediately after pairing, before full scoring. (2) Expression gating may not recover de_recovery if the damage comes from covariance disruption rather than DE dilution; check de_recovery and covariation separately. (3) VAE reconstruction Pearson 0.36 means dec differences are noisy; if the fix shows <1 point gain on X3 after 3 queries, abandon and report honestly. (4) 30-min budget: VAE train ~70s + 3-4 scoring runs ~2.5 min each fits, but leave 10 min buffer.",
  "family_id": "generative_latent",
  "mechanism": "VAE latent displacement δ(z)=Wz+b trained on cell-level kNN-paired targets within each cell type across two input stages, making the displacement depend on each cell's position in latent space rather than collapsing to a per-type constant.",
  "vs_constant_shift": "Per-type constant shift assigns one Δz vector to all cells of a type. Here each cell gets a displacement that depends on its own latent coordinates z_i via the linear map Wz+b, fitted on ~1093 cell-level paired targets (not 5 type means). The within-type variance of δ should be comparable to the between-type variance if the mechanism is real.",
  "mechanism_evidence": "Engineer must report: (1) within-type δ sd / |δ| mean ratio (target ≥ 0.3, parent was 0.08); (2) per-group score changes vs parent, especially de_recovery and covariation; (3) number of cells whose output actually differs from copy_last and the distribution of per-cell L1 changes; (4) expression-gate ablation: score with gate=1 everywhere vs gated, to isolate the de_recovery fix.",
  "mechanism_off_control": "DISPLACEMENT_SCALE=0 env var (already in code): z_pred = z_last, dX = dec(z_last)−dec(z_last) = 0, output equals copy_last downsampled. Additionally, setting KNN_PAIR=0 env var falls back to the old per-type-mean targets, providing an A/B within the same run.",
  "sources": []
}
```
原始记录位置/home/spark-longxinyang/vec/runs/formal/20261002-202907-search-t1-scr-A/nodes/16/researcher.jsonl 5 KB
/home/spark-longxinyang/vec/runs/formal/20261002-202907-search-t1-scr-A/nodes/16/researcher.stderr