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

总览 · ← 返回运行 20261001-233756-search-t1-abc-r0-A-era

节点 n4

official-base copy_last(默认 alpha=0):跨阶段簇匹配收缩位移在 X3 上实测方向为负,验证后止损回退

运行?一次完整的自动搜索或 Agent 会话,有自己的锁定配置和证据包。20261001-233756-search-t1-abc-r0-A-era
父节点n2
子节点n7
操作?种子:人写的起点;改进:在父节点上改;草稿:从头写;修复:修父节点的报错。改进
状态已打分
分数搜索目标分 50.03(+0.0) · proxy 50.04(+0.0) · proxy2 50.04(+0.0) · X3 50.00(+0.0) · 3 次复测均分 50.09
审查未审查
用时?从运行开始到结束(或到现在)的挂钟时间。18 分
程序版本a8e08112ce0cfccd0be7b80a92611b18718e2c2b (programs.git)

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

来自 programs.git a8e08112ce:solution/METHOD.md

official-base copy_last(默认 alpha=0):跨阶段簇匹配收缩位移在 X3 上实测方向为负,验证后止损回退

方法

与父节点(node 2)行为完全一致:从最新官方输入阶段(无官方阶段时用最新外部阶段,即 X3)按 target_n_cells 用 np.random.default_rng(seed) 抽样输出,--alpha 默认 0,不平移。

代码中保留了一套完整实现但默认关闭的跨阶段簇匹配收缩位移(--alpha>0 时启用,仅当视图 ≥2 个阶段):

  • 两阶段各自 top-variance 2000 基因 + PCA(50) + Leiden(res 0.8, igraph, seed 固定);
  • 簇间 top-20 标志基因 Jaccard 贪心匹配(阈值 0.25),只平移匹配簇;
  • 每匹配簇伪批量差值,扣除看家基因(核糖体蛋白 Rps/Rpl ∩ 两阶段最稳定基因,均从视图数据现场计算)的中位偏移做批效应去除;
  • 基因级经验贝叶斯收缩 v/(v+s2);
  • 只对非零元素平移并截断到 ≥0(保住稀疏零结构,见教训 1);
  • 外部阶段未覆盖的基因(view_io 补齐列)delta 强制为 0。

查分记录(A 半,seed 0,共 8 次)

配置X3proxy2proxy
父节点 copy_last50.0050.0450.04
稠密平移 alpha=0.1/0.2/0.346.97/46.25/45.71--
非零平移 alpha=0.249.0350.40-
非零平移 alpha=0.149.00--
非零平移 alpha=0.2 无批去除48.67--
默认 alpha=0(本提交)通过 vec-check通过 vec-check50.40

PLAN 的保留门槛是 X3 胜出 ≥1.5;所有配置 X3 均 <50,按 PLAN 第 5 步回退,不提交负收益改动。

教训(本节点新证据,供后续节点用)

  1. 零结构是硬约束:对全矩阵加稠密小位移(哪怕 alpha=0.1、每基因 |delta| 中位数 ~0.01 log 单位)会把稀疏零填成非零,covariation 50.1→41.6、de_recovery 50→46.5;改为只平移非零元素后基本恢复(49.0)。任何表达值修改都必须保住零掩码。
  2. X3 上 E8.75→E9.0 簇级伪批量差值与真实 E9.0→E9.5 变化方向反相关:de_direction 在 alpha=0.1 与 0.2 下完全相同且为负(-0.0456),说明是符号问题不是幅度问题;批去除有帮助(48.67→49.03)但不改变符号。推论:final 视图(E8.5→E9.5 差值外推 E10.5,结构与 X3 相同:基底=较晚的同数据集阶段)上同族差值平移大概率同样有害,后续节点不应再试朴素/收缩簇差值,除非有证据表明 1 天间隔的官方数据差值方向与 X3 的 0.25 天心脏数据不同。
  3. proxy2 上跨数据集心脏位移(E8.5 基底 + Qiu E9.0)方向略正但幅度在噪声内(+0.36 < 2),不值得启用。
  4. Leiden 簇匹配本身工作正常:X3 上 98.6% 细胞被匹配、delta 幅度合理,失败原因在方向而非实现。

验证过 / 未验证

  • 验证:三个视图 vec-check 全部 ok;默认配置 proxy 50.40 与父节点一致;alpha=0 时新增代码路径完全不执行(行为逐位等同父节点);确定(固定 seed,Leiden random_state=seed)。
  • 未验证:final 视图(本地无),但 alpha=0 时与 proxy 走同一分支,风险极低。
  • 合规:只读视图内文件;核糖体蛋白看家基因名单是通用分子生物学知识(非禁窗测量所得),其余统计量(稳定性、簇、delta)全部现场从视图输入计算,无硬编码阶段统计量。

下一步建议

  1. 差值平移家族在 X3 已两次证伪(父节点 alpha=1 得 40.33;本节点收缩+批去除+非零约束仍 49.0)。想提 de_recovery/direction,需要方向性先验而非经验差值:如用 prior/ 里 GO/Reactome 通路 + 通用发育时序知识(哪些通路随体节/心脏发育上调)构造方向向量,先在 X3 验证 de_direction 符号再谈幅度。
  2. covariation/cell_state 对分布扰动极敏感:任何修改表达值的操作必须保零掩码、保单细胞方差(教训 1)。
  3. proxy 单阶段视图 copy_last ≈50±0.4 已到顶,不要再花查分额度。

调研员的计划

名称copy_last + 跨阶段按簇收缩位移(批效应去除,alpha<=0.3,先 X3 验证符号)
动机父节点 2 的 copy_last 在四个分组全部停在 ~50(cell_state 49.93 / covariation 50.11 / de_recovery 50.00 / direction 50.11),而评分基准规定 copy_last=50(k012):copy_last 预测与目标之间差一个完整时间步的变化,因此 DE 恢复与变化方向两组结构上只能拿基线分。proxy2(官方 E8.5 + Qiu E9.0)和 X3(E8.75 + E9.0)都有第二个输入阶段、final 有两个官方阶段,当前方法完全没利用。已知约束:朴素 alpha=1 伪批量平移在 X3 实测 40.33(父节点)、官方 pseudobulk_shift 48.6 < copy_last(k012),且任何抹平单细胞方差的平滑都会击穿 covariation(节点 2 里 PCA 重建使该组 51.3→5.9)。所以方向必须是:保留经验细胞云(抽样后原位平移,不做均值化/低秩重建)+ 幅度大幅收缩 + 去除跨数据集批效应偏移。
做法1) 代码骨架不变:基底仍取最新官方阶段(无官方才用外部,即 X3),按 target_n_cells 用固定 seed 抽样,保证单输入视图(proxy)行为与父节点逐位一致。2) 仅当视图有 ≥2 个阶段时启用位移:a) 对两个阶段分别做 PCA(50)+Leiden(scanpy,flavor='igraph',resolution 0.5~1.0,目标 10~30 簇)得到伪类型簇;用每簇相对全体的 top-20 标志基因集合的 Jaccard 重叠做跨阶段簇匹配(同一视图内两阶段,标签列不使用也不存在)。b) 对每个匹配上的簇计算伪批量差值 delta_c = mean(后阶段|簇) - mean(前阶段|簇)。c) 批效应去除:取一组看家/持稳基因(核糖体+细胞骨架等常用持家名单与表达最稳定的基因取交集,仅用视图内数据计算稳定性),用它们的两阶段伪批量差值作为该簇的批/技术偏移 b_c,发育信号 delta'_c = delta_c - b_c;同一数据集的两阶段(final 情形)b_c≈0,自动退化为收缩的伪批量位移。d) 每基因再乘经验贝叶斯收缩因子 delta'_cg * v_cg/(v_cg + s2)(v_cg 为该基因簇内方差,s2 取全体基因中位方差),抑制噪声基因。e) 对抽样的基底细胞逐细胞原位加 alpha * delta'_c 并在 0 处截断(log 空间),alpha 初值 0.1,搜索范围 {0.05, 0.1, 0.15, 0.2, 0.3};类型未匹配的细胞不平移。3) 验证顺序(共 ≤20 次查分,每次先 A 半):先在 X3 上测 alpha∈{0.1,0.2,0.3},要求胜过 50.00 至少 ~1.5 分才保留该配置,否则去掉批去除步骤再对比一次以定位原因;再在 proxy2 上测选出的 2 个 alpha,要求 >50.4 才保留;proxy 只抽查一次确认单阶段退路输出与父节点相同。4) 单阶段退路:proxy 及任何只有一个输入的视图直接走 copy_last 分支(与父节点完全相同),两阶段逻辑只在 manifest 报告 ≥2 阶段时激活,因此同一份代码在 final(官方 E8.5+E9.5,间隔 1 天、全胚)上成立,缺失基因沿用 view_io 的 E8.5 均值补齐,不为 proxy2 的 0.5 天间隔或心脏谱系做任何特化。5) 若 X3 与 proxy2 上所有配置都 ≤50,则回退为与父节点等价的实现并如实报告(不提交负收益改动)。
风险1) 收缩位移仍可能有害(父节点已证 alpha=1 在 X3 上 40.33):用极小 alpha(≤0.3)+ 批去除 + 基因级收缩三重缓冲,且第一步就在 X3 上验证符号,最坏只花 3 次查分即可止损。2) 无监督 Leiden 簇与真实细胞类型不完全对齐、跨阶段簇匹配错误会把错误方向加给细胞:用标志基因 Jaccard 匹配、只移匹配簇,匹配率 <60% 时收窄到匹配最好的簇。3) 批去除可能把真实发育信号也减掉:在 X3 上对比有/无批去除即可发现(X3 两阶段同为 Qiu 数据、技术一致,批去除应近中性或略正)。4) 若位移只在一个尺子上见效、另一尺子略降,节点分变化可能落在 ±2 噪声内:要求 X3 与 proxy2 同时 ≥+1.5 才判为真实提升,否则不算进步。5) 运行时间:两阶段各一次 PCA+Leiden 在 3 万细胞级约几十秒,远低于 30 分钟限额;Engineer 应先用 X3(细胞少)冒烟测试。

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

对比:父节点版本 a5593c0de3。改动的文件:solution/METHOD.md +30 −33、solution/run.py +196 −14

diff --git a/solution/METHOD.md b/solution/METHOD.mdindex 01d56f2..7953e63 100644--- a/solution/METHOD.md+++ b/solution/METHOD.md@@ -1,48 +1,45 @@-# official-base copy_last(外部阶段不作基底,去掉有害的伪批量平移)+# official-base copy_last(默认 alpha=0):跨阶段簇匹配收缩位移在 X3 上实测方向为负,验证后止损回退  ## 方法 -预测 = 从**最新官方输入阶段**按 `target_n_cells` 随机抽样(`np.random.default_rng(seed)`,给定 seed 确定)。不做任何表达平移。+与父节点(node 2)行为完全一致:从最新官方输入阶段(无官方阶段时用最新外部阶段,即 X3)按 `target_n_cells` 用 `np.random.default_rng(seed)` 抽样输出,`--alpha` 默认 0,不平移。 -阶段选择逻辑(不写死阶段名,遍历 manifest):-- `official = inputs_by_time(manifest, include_external=False)`;非空则以最新官方阶段为基底。-- 官方阶段为空(X3 型视图,两个输入都是外部心脏数据)时,才用全部输入里的最新阶段。+代码中保留了一套完整实现但默认关闭的**跨阶段簇匹配收缩位移**(`--alpha>0` 时启用,仅当视图 ≥2 个阶段):+- 两阶段各自 top-variance 2000 基因 + PCA(50) + Leiden(res 0.8, igraph, seed 固定);+- 簇间 top-20 标志基因 Jaccard 贪心匹配(阈值 0.25),只平移匹配簇;+- 每匹配簇伪批量差值,扣除看家基因(核糖体蛋白 Rps*/Rpl* ∩ 两阶段最稳定基因,均从视图数据现场计算)的中位偏移做批效应去除;+- 基因级经验贝叶斯收缩 `v/(v+s2)`;+- **只对非零元素平移并截断到 ≥0**(保住稀疏零结构,见教训 1);+- 外部阶段未覆盖的基因(view_io 补齐列)delta 强制为 0。 -各视图行为:-- `proxy`(只有官方 E8.5):copy_last E8.5。-- `proxy2`(官方 E8.5 + 外部 Qiu E9.0 心脏):**忽略 Qiu 作为基底**,copy_last 官方 E8.5。父节点在 proxy2 上把 Qiu 心脏细胞直接当全胚预测输出,得 27.43;本节点 50.40。-- `final`(官方 E8.5+E9.5):copy_last E9.5,不平移。-- `X3`:copy_last Qiu E9.0(全 2174 细胞,未达上限),输出基因按该视图 genes.txt。+## 查分记录(A 半,seed 0,共 8 次) -## 为什么去掉平移(查证过的证据)+| 配置 | X3 | proxy2 | proxy |+|---|---|---|---|+| 父节点 copy_last | 50.00 | 50.04 | 50.04 |+| 稠密平移 alpha=0.1/0.2/0.3 | 46.97/46.25/45.71 | - | - |+| 非零平移 alpha=0.2 | 49.03 | 50.40 | - |+| 非零平移 alpha=0.1 | 49.00 | - | - |+| 非零平移 alpha=0.2 无批去除 | 48.67 | - | - |+| **默认 alpha=0(本提交)** | 通过 vec-check | 通过 vec-check | **50.40** | -1. 在 X3 上实测:对 E8.75→E9.0 的按类型伪批量差值做平移得 **40.33**,同一抽样不平移得 **50.00**(A 半,seed 0)。差值平移显著有害。-2. 方法卡记载官方 pseudobulk_shift 在真实 T1 上 48.6,低于 copy_last。-3. 因此对所有 ≥2 阶段的视图(含 final)统一不平移。这是数据驱动的决定,不是省事。+PLAN 的保留门槛是 X3 胜出 ≥1.5;所有配置 X3 均 <50,按 PLAN 第 5 步回退,不提交负收益改动。 -## 试过并否决的方向(本节点内实测)+## 教训(本节点新证据,供后续节点用) -- **PCA 低秩去噪重建**(30 / 100 成分,proxy):de_recovery 50→51.5,但 covariation 51.3→**5.9**(variogram 恶化 15 倍),总分 40.9/41.2,远低于 copy_last 50.4。PLAN 里的 kNN 平滑同理会抹掉细胞间方差,未再单独测(PCA 已证明协方差组对任何平滑极其敏感)。-- **用 Qiu E9.0 给官方 E8.5 的心脏类型加跨数据集差值**:proxy2 视图内没有 Qiu 的第二个时间点,跨技术差值被批次效应污染,且心脏类型标签不可对齐,未实施。--## 查分记录(A 半,seed 0,除注明外)--| 视图 | 本节点 | 父节点 |-|---|---|---|-| proxy | 50.40 | 50.04 |-| proxy2 | 50.40 | 27.43 |-| X3 | 50.00 | 40.53 |-| proxy seed1 | 49.33 | - |--估计节点分 ≈ 50.3(父 39.34)。seed 间 ±1 属抽样噪声。+1. **零结构是硬约束**:对全矩阵加稠密小位移(哪怕 alpha=0.1、每基因 |delta| 中位数 ~0.01 log 单位)会把稀疏零填成非零,covariation 50.1→41.6、de_recovery 50→46.5;改为只平移非零元素后基本恢复(49.0)。任何表达值修改都必须保住零掩码。+2. **X3 上 E8.75→E9.0 簇级伪批量差值与真实 E9.0→E9.5 变化方向反相关**:de_direction 在 alpha=0.1 与 0.2 下完全相同且为负(-0.0456),说明是符号问题不是幅度问题;批去除有帮助(48.67→49.03)但不改变符号。推论:**final 视图(E8.5→E9.5 差值外推 E10.5,结构与 X3 相同:基底=较晚的同数据集阶段)上同族差值平移大概率同样有害**,后续节点不应再试朴素/收缩簇差值,除非有证据表明 1 天间隔的官方数据差值方向与 X3 的 0.25 天心脏数据不同。+3. proxy2 上跨数据集心脏位移(E8.5 基底 + Qiu E9.0)方向略正但幅度在噪声内(+0.36 < 2),不值得启用。+4. Leiden 簇匹配本身工作正常:X3 上 98.6% 细胞被匹配、delta 幅度合理,失败原因在方向而非实现。  ## 验证过 / 未验证 -- 验证:三个视图 `vec-check` 全部 ok;seed 0/1 均能跑;运行 ~4s、内存远低于限额;输出确定。-- 未验证:final 视图(本地无该视图构造),但代码路径与 proxy 的单阶段分支一致,仅多一个 `inputs_by_time` 排序取最新,风险低。-- 未使用任何保留阶段(E10.5/E12.5、禁窗 9.5<E≤13.5)或保留基因型的信息;程序只读视图内输入文件,无硬编码统计量、无外部生物学先验。+- 验证:三个视图 `vec-check` 全部 ok;默认配置 proxy 50.40 与父节点一致;alpha=0 时新增代码路径完全不执行(行为逐位等同父节点);确定(固定 seed,Leiden random_state=seed)。+- 未验证:final 视图(本地无),但 alpha=0 时与 proxy 走同一分支,风险极低。+- 合规:只读视图内文件;核糖体蛋白看家基因名单是通用分子生物学知识(非禁窗测量所得),其余统计量(稳定性、簇、delta)全部现场从视图输入计算,无硬编码阶段统计量。  ## 下一步建议 -1. proxy 单阶段下 copy_last 似乎接近该视图可达上限(~50±1);提分空间在 final(两个官方阶段)——但 X3 证据表明朴素差值平移有害,可试**按基因程序(GO/Reactome 通路)约束的、幅度收缩且仅作用于高置信类型的平移**,并先在 X3 上验证符号。-2. cell_state/covariation 对平滑零容忍、对噪声结构敏感:任何生成式方法必须保留单细胞级噪声(例如按类型重采样经验云 + 保留残差,而非均值化)。+1. 差值平移家族在 X3 已两次证伪(父节点 alpha=1 得 40.33;本节点收缩+批去除+非零约束仍 49.0)。想提 de_recovery/direction,需要**方向性先验**而非经验差值:如用 prior/ 里 GO/Reactome 通路 + 通用发育时序知识(哪些通路随体节/心脏发育上调)构造方向向量,先在 X3 验证 de_direction 符号再谈幅度。+2. covariation/cell_state 对分布扰动极敏感:任何修改表达值的操作必须保零掩码、保单细胞方差(教训 1)。+3. proxy 单阶段视图 copy_last ≈50±0.4 已到顶,不要再花查分额度。diff --git a/solution/run.py b/solution/run.pyindex de245fe..ef2a7fe 100644--- a/solution/run.py+++ b/solution/run.py@@ -1,16 +1,33 @@ #!/usr/bin/env python3-"""official-base copy_last.+"""official-base copy_last + optional cluster-matched shrunken delta shift.  Base cells are sampled from the latest OFFICIAL input stage whenever one-exists; external stages (proxy2's Qiu heart-only E9.0) are never used as the-base for official targets, because their cell-type composition covers only a-single lineage and their technology differs. External stages are used only-when no official stage exists (X3-style views).--No pseudobulk shift is applied: measured on X3 (E8.75+E9.0 -> E9.5) the-last-step per-type delta HURT (40.3 vs 50.0 for plain copy), matching the-organisers' report that pseudobulk_shift on real T1 (48.6) is below-copy_last. So this is copy_last on the best available base.+exists (external stages such as proxy2's Qiu heart E9.0 are never the base+for an official target); external stages are used only when no official+stage exists (X3-style views). With a single input stage this is exactly+the parent's copy_last.++When the view has >=2 input stages, we additionally apply a heavily+shrunken, cluster-matched pseudobulk delta to the sampled base cells:++1. PCA(50)+Leiden on each of the last two stages -> pseudo-type clusters.+2. Cross-stage cluster matching by Jaccard overlap of top-20 marker genes.+3. Per matched cluster: delta_c = pseudobulk(later|c') - pseudobulk(earlier|c),+   restricted to genes actually covered by the later stage (external stages+   may miss panel genes; filled columns are ignored, delta=0 there).+4. Batch/technical offset removal: b_c = median of delta_c over stable+   "housekeeping-like" genes (ribosomal proteins intersected with the most+   expression-stable genes, computed from view data only);+   delta'_c = delta_c - b_c.+5. Gene-level empirical-Bayes shrinkage: delta'_cg *= v_cg / (v_cg + s2),+   v_cg = within-cluster variance, s2 = median within-cluster variance.+6. Per-cell: X <- clip(X + alpha * delta'_{cluster(cell)}, 0) in log space.+   Cells whose cluster is unmatched are left untouched.++Measured history: alpha=1 per-type delta on X3 scored 40.33 vs 50.00 for+plain copy, so alpha defaults to 0 and is only enabled at small values+after sign-checking on X3 / proxy2. Single-cell noise structure is fully+preserved (per-gene additive shift on sampled cells, no smoothing). """  from __future__ import annotations@@ -18,8 +35,10 @@ from __future__ import annotations import argparse  import numpy as np+import scipy.sparse as sp  from src.task1_temporal.view_io import (+    covered_mask,     inputs_by_time,     load_manifest,     panel_genes,@@ -29,24 +48,187 @@ from src.task1_temporal.view_io import (     write_prediction, ) +DEFAULT_ALPHA = 0.0+RIBOSOMAL_PREFIXES = ("Rps", "Rpl")+++def _dense(adata_rows) -> np.ndarray:+    X = adata_rows+    if sp.issparse(X):+        X = X.toarray()+    return np.asarray(X, dtype=np.float32)+++def _cluster(adata, seed: int, n_pcs: int = 50, res: float = 0.8):+    import scanpy as sc++    a = adata.copy()+    X = a.X+    mu = np.asarray(X.mean(axis=0)).ravel()+    m2 = np.asarray(X.multiply(X).mean(axis=0)).ravel()+    v = np.maximum(m2 - mu ** 2, 0)+    keep = v > 0+    idx = np.where(keep)[0]+    top = idx[np.argsort(v[idx])[::-1][:2000]]+    hv = a[:, np.sort(top)].copy()+    sc.pp.scale(hv, max_value=10)+    sc.tl.pca(hv, n_comps=min(n_pcs, hv.n_vars - 1, hv.n_obs - 1))+    a.obsm["X_pca"] = hv.obsm["X_pca"]+    sc.pp.neighbors(a, n_neighbors=15, use_rep="X_pca")+    sc.tl.leiden(a, resolution=res, flavor="igraph", n_iterations=2,+                 directed=False, random_state=seed)+    return a.obs["leiden"].to_numpy().astype(int)+++def _pseudobulk(Xd: np.ndarray, labels: np.ndarray, k: int) -> np.ndarray:+    n_genes = Xd.shape[1]+    means = np.zeros((k, n_genes), dtype=np.float64)+    var = np.zeros((k, n_genes), dtype=np.float64)+    for c in range(k):+        m = labels == c+        n = int(m.sum())+        if n == 0:+            continue+        sub = Xd[m]+        mu = sub.mean(axis=0)+        means[c] = mu+        var[c] = np.maximum((sub ** 2).mean(axis=0) - mu ** 2, 0.0)+    return means, var+++def _markers(means: np.ndarray, top: int = 20) -> list[set]:+    out = []+    gmean = means.mean(axis=0)+    for c in range(means.shape[0]):+        spec = means[c] - gmean+        idx = np.argsort(spec)[::-1][:top]+        out.append(set(idx.tolist()))+    return out+++def _jaccard(a: set, b: set) -> float:+    u = len(a | b)+    return len(a & b) / u if u else 0.0+++def _stable_genes(Xd0: np.ndarray, Xd1: np.ndarray, gene_names) -> np.ndarray:+    """Ribosomal-protein genes (generic housekeeping, name-based) whose+    global pseudobulk change between stages is small (computed from the+    view data only)."""+    ribo = np.array([str(g).startswith(RIBOSOMAL_PREFIXES) for g in gene_names])+    if ribo.sum() < 20:+        ribo = np.ones(len(gene_names), dtype=bool)+    d = np.abs(Xd1.mean(axis=0) - Xd0.mean(axis=0))+    order = np.argsort(d)  # most stable first+    mask = np.zeros(len(gene_names), dtype=bool)+    picked = 0+    for j in order:+        if ribo[j]:+            mask[j] = True+            picked += 1+            if picked >= 100:+                break+    if picked < 20:  # fallback: most stable genes overall+        mask[order[:100]] = True+    return mask+++def _cluster_delta(Xd0, lab0, Xd1, lab1, covered, gene_names, min_match=0.25,+                   use_batch=True):+    """Returns (delta_per_label0, delta_per_label1, match_frac) where delta+    arrays are (n_clusters_stage, n_genes) with zeros for unmatched."""+    k0, k1 = lab0.max() + 1, lab1.max() + 1+    m0, v0 = _pseudobulk(Xd0, lab0, k0)+    m1, v1 = _pseudobulk(Xd1, lab1, k1)+    mk0, mk1 = _markers(m0), _markers(m1)+    J = np.zeros((k0, k1))+    for i in range(k0):+        for j in range(k1):+            J[i, j] = _jaccard(mk0[i], mk1[j])+    d0 = np.zeros_like(m0)+    d1 = np.zeros_like(m1)+    matched0 = np.zeros(k0, dtype=bool)+    used1 = np.zeros(k1, dtype=bool)+    pairs = []+    while True:+        i, j = np.unravel_index(np.argmax(J), J.shape)+        if J[i, j] < min_match:+            break+        pairs.append((i, j))+        J[i, :] = -1+        J[:, j] = -1+    stable = _stable_genes(Xd0, Xd1, gene_names)+    n_matched_cells = 0+    for i, j in pairs:+        raw = m1[j] - m0[i]+        raw = np.where(covered, raw, 0.0)+        b = np.median(raw[stable]) if (use_batch and stable.any()) else 0.0+        raw = raw - b+        # empirical-Bayes gene shrinkage using pooled within-cluster variance+        v = 0.5 * (v0[i] + v1[j])+        s2 = np.median(v)+        raw = raw * (v / (v + s2)) if s2 > 0 else raw+        d0[i] = raw+        d1[j] = raw+        matched0[i] = True+        used1[j] = True+        n_matched_cells += int((lab0 == i).sum())+    frac = n_matched_cells / max(len(lab0), 1)+    return d0, d1, frac, (matched0, used1)+  def main() -> None:     parser = argparse.ArgumentParser()     parser.add_argument("--data", required=True)     parser.add_argument("--out", required=True)     parser.add_argument("--seed", type=int, default=0)+    parser.add_argument("--alpha", type=float, default=DEFAULT_ALPHA)+    parser.add_argument("--no-batch", action="store_true",+                        help="disable housekeeping batch-offset removal")     args = parser.parse_args()      manifest = load_manifest(args.data)     genes = panel_genes(args.data, manifest)      official = inputs_by_time(manifest, include_external=False)-    stages = official if official else inputs_by_time(manifest)+    all_stages = inputs_by_time(manifest)+    base_entry = official[-1] if official else all_stages[-1] -    last = read_stage(args.data, stages[-1], genes)+    base = read_stage(args.data, base_entry, genes)     rng = np.random.default_rng(args.seed)-    rows = sample_rows(last.n_obs, target_n_cells(manifest, last.n_obs), rng)-    write_prediction(last.X[rows], genes, args.out, seed=args.seed)+    rows = sample_rows(base.n_obs, target_n_cells(manifest, base.n_obs), rng)+    Xb = base.X[rows]++    alpha = args.alpha+    times = sorted({float(e["time"]) for e in all_stages})+    if alpha > 0 and len(all_stages) >= 2 and len(times) >= 2:+        e0, e1 = all_stages[-2], all_stages[-1]+        a0 = read_stage(args.data, e0, genes)+        a1 = read_stage(args.data, e1, genes)+        # genes covered by the later stage (external stages miss panel genes+        # and view_io fills them with earlier-stage means -> fake deltas)+        cov = covered_mask(args.data, e1, genes)+        Xd0 = _dense(a0.X)+        Xd1 = _dense(a1.X)+        lab0 = _cluster(a0, args.seed)+        lab1 = _cluster(a1, args.seed)+        d0, d1, frac, _ = _cluster_delta(+            Xd0, lab0, Xd1, lab1, cov, genes, use_batch=not args.no_batch)+        base_is_later = (base_entry.get("path") == e1.get("path")+                         and base_entry.get("time") == e1.get("time"))+        dcells = d1 if base_is_later else d0+        lab_base = lab1 if base_is_later else lab0+        if frac >= 0.3:+            Xd = _dense(Xb)+            cell_lab = lab_base[rows]+            shift = alpha * dcells[cell_lab]+            # shift only expressed entries: keeps the zero structure intact+            # (dense additive noise on zeros wrecked covariation/de groups)+            nz = Xd > 0+            Xd[nz] = np.clip(Xd[nz] + shift[nz], 0, None)+            Xb = Xd.astype(np.float32)++    write_prediction(Xb, genes, args.out, seed=args.seed)   if __name__ == "__main__":

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

用到的知识库条目

编号标题出处
k018Damped per-type shift: shrinkage alpha on the observed deltanotes/plan/cards/T1.md
k012Official T1 scoring, output contract and adversarial controlsnotes/official/来件/virtualembryo.ai/task1-temporal.md; notes/official/来件/virtualembryo.ai/baselines.md
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:改了什么、各组分数怎么变、假设是否成立、经验和下一步建议。

改了什么实现了完整的跨阶段簇匹配收缩位移(PCA50+Leiden res0.8、top20标志基因Jaccard贪心匹配、核糖体看家基因批去除、经验贝叶斯基因收缩、仅平移非零元素),但验证后把默认 --alpha 设为 0,提交版本行为与父节点逐位等价;榜分 50.03、四个分组与三个尺子全部 +0.00(耗时 1.2→1.3s、内存 1.26GB 不变),即本节点净收益为零,只留下代码机制与证据。
各组分数的变化cell_state:噪声内(实际无变化):49.93 → 49.93,+0.00。
covariation:噪声内(实际无变化):50.11 → 50.11,+0.00。
de_recovery:噪声内(实际为完全无变化):50.00 → 50.00,+0.00;提交配置 alpha=0 未启用位移。
direction:噪声内(实际无变化):50.11 → 50.11,+0.00。
假设是否成立否
经验
  1. 在 X3(E8.75+E9.0,同数据集、间隔 0.25 天、心脏谱系)上,簇级伪批量差值位移方向与真实后续变化反相关:alpha=0.1 与 0.2 的 de_direction 都是 -0.0456(不随幅度变化),说明是符号错误而非幅度不足,继续缩小 alpha 无用。
  2. 对全矩阵加稠密小位移(即使 alpha=0.1、|delta| 中位数 ~0.01 log)会把稀疏零填成非零,covariation 50.1→41.6、de_recovery 50→46.5;改成只平移原本非零的元素并截断到 ≥0 后基本恢复(X3 49.0)。任何改表达值的操作必须保零掩码。
  3. 看家基因批去除有小幅正作用(X3 alpha=0.2 非零位移 48.67→49.03)但不足以翻符号;簇匹配本身没问题(X3 98.6% 细胞被匹配),失败在方向而非实现。
  4. Engineer 报告的 proxy2 50.40 是 alpha=0.2 实验配置的结果,与提交配置无关:官方变化量表显示提交版 proxy2=50.04,与父节点相同,+0.36 那种差值也远小于 2 分噪声,不能当作提升证据。
  5. 止损流程本身有效:先在细胞数最少的 X3 上验符号、门槛设为胜出 ≥1.5,只用 8 次查分就判定整个差值位移家族无望并回退,没有提交负收益改动;默认参数指向父节点行为是让实验代码可安全入库的做法。
  6. 经验差值家族在本任务已两次证伪(父节点 alpha=1 得 40.33,本节点收缩+批去除+保零仍 49.0),且 X3 与 final 结构相同(基底=同数据集较晚阶段、外推一步),因此不应再在 final 上试同族方法。
下一步建议
  1. 针对 de_recovery/direction:放弃经验差值,改用 prior/(GO/Reactome/CollecTRI)加通用发育时序知识构造通路级方向向量(每基因一个符号×权重),先在 X3 上只跑 alpha∈{0.05,0.1},要求 de_direction 由 -0.0456 转正且总分 >51.5 才继续,否则立即止损。
  2. 针对 covariation/cell_state:所有位移实现一律继承本节点的保零掩码 + 逐细胞原位加法 + clip(≥0)(solution/run.py 的 nz 分支),并保留 run.py 里已写好的 _cluster/_cluster_delta/_stable_genes,不要重写;禁止任何均值化、低秩重建或 kNN 平滑(节点 2 的 PCA 重建使 covariation 51.3→5.9)。
  3. 针对 proxy(单阶段官方视图):copy_last 已稳定在 50.04(seed 间 ±1 属抽样噪声),不要再花查分额度;剩余提分空间只在 final(两个官方阶段),且方法必须先通过 X3 符号检验这道门。

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

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

分析员

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

工程师

角色工程师 alibaba-token-plan-cn/qwen3.8-max
调用次数1 次
轮数24
工具调用共 30 次:bash 21、edit 5、read 2、write 2
用时14 分
token 数输入 76,404 · 输出 10,078 · 思考 8,574
任务(第一行)按计划修改程序:copy_last + 跨阶段按簇收缩位移(批效应去除,alpha<=0.3,先 X3 验证符号)
最后的回答(摘录)
完成。

**做了什么**:按 PLAN 实现了完整的跨阶段簇匹配收缩位移(PCA+Leiden 簇匹配、看家基因批去除、经验贝叶斯基因收缩、非零元素限定平移),在 X3/proxy2 上系统验证符号后按 PLAN 第 5 步止损,默认 alpha=0 回退为与父节点逐位等价的 official-base copy_last(代码保留位移机制供后续节点复用)。

**查分结果**(8 次,A 半):X3 稠密平移 46.97/46.25/45.71(alpha 0.1/0.2/0.3),非零平移 49.03/49.00/48.67,全部 <50.00;proxy2 非零 alpha=0.2 得 50.40(+0.36,噪声内);默认配置 proxy 50.40(与父节点一致),三视图 vec-check 通过,输出确定,2s 运行。

**关键发现**:① 稠密位移填零会击穿 covariation/de_recovery(保零掩码后基本恢复);② X3 上簇级差值与真实变化方向**反相关**(de_direction 负且不随 alpha 变号),是符号问题而非幅度问题——同结构的 final 视图上差值平移家族大概率同样有害。

**下一步最值得试**:放弃经验差值家族,改用 prior/(GO/Reactome/CollecTRI)+ 通用发育时序知识构造通路级方向先验向量,先在 X3 验证 de_direction 符号为正再启用;任何表达修改必须保零掩码。
原始记录位置/home/spark-longxinyang/vec/runs/formal/20261001-233756-search-t1-abc-r0-A-era/nodes/4/engineer.jsonl 113 KB
/home/spark-longxinyang/vec/runs/formal/20261001-233756-search-t1-abc-r0-A-era/nodes/4/engineer.stderr

调研员

角色调研员 alibaba-token-plan-cn/qwen3.8-max
调用次数2 次
轮数2
工具调用共 0 次:—
用时2 分
token 数输入 8,496 · 输出 2,625 · 思考 4,425
任务(第一行)为节点 n4(改进,父节点 n2)写改进计划 PLAN.json
最后的回答(摘录)
 / de_recovery 50.00 / direction 50.11),而评分基准规定 copy_last=50(k012):copy_last 预测与目标之间差一个完整时间步的变化,因此 DE 恢复与变化方向两组结构上只能拿基线分。proxy2(官方 E8.5 + Qiu E9.0)和 X3(E8.75 + E9.0)都有第二个输入阶段、final 有两个官方阶段,当前方法完全没利用。已知约束:朴素 alpha=1 伪批量平移在 X3 实测 40.33(父节点)、官方 pseudobulk_shift 48.6 < copy_last(k012),且任何抹平单细胞方差的平滑都会击穿 covariation(节点 2 里 PCA 重建使该组 51.3→5.9)。所以方向必须是:保留经验细胞云(抽样后原位平移,不做均值化/低秩重建)+ 幅度大幅收缩 + 去除跨数据集批效应偏移。",
  "approach": "1) 代码骨架不变:基底仍取最新官方阶段(无官方才用外部,即 X3),按 target_n_cells 用固定 seed 抽样,保证单输入视图(proxy)行为与父节点逐位一致。2) 仅当视图有 ≥2 个阶段时启用位移:a) 对两个阶段分别做 PCA(50)+Leiden(scanpy,flavor='igraph',resolution 0.5~1.0,目标 10~30 簇)得到伪类型簇;用每簇相对全体的 top-20 标志基因集合的 Jaccard 重叠做跨阶段簇匹配(同一视图内两阶段,标签列不使用也不存在)。b) 对每个匹配上的簇计算伪批量差值 delta_c = mean(后阶段|簇) - mean(前阶段|簇)。c) 批效应去除:取一组看家/持稳基因(核糖体+细胞骨架等常用持家名单与表达最稳定的基因取交集,仅用视图内数据计算稳定性),用它们的两阶段伪批量差值作为该簇的批/技术偏移 b_c,发育信号 delta'_c = delta_c - b_c;同一数据集的两阶段(final 情形)b_c≈0,自动退化为收缩的伪批量位移。d) 每基因再乘经验贝叶斯收缩因子 delta'_cg * v_cg/(v_cg + s2)(v_cg 为该基因簇内方差,s2 取全体基因中位方差),抑制噪声基因。e) 对抽样的基底细胞逐细胞原位加 alpha * delta'_c 并在 0 处截断(log 空间),alpha 初值 0.1,搜索范围 {0.05, 0.1, 0.15, 0.2, 0.3};类型未匹配的细胞不平移。3) 验证顺序(共 ≤20 次查分,每次先 A 半):先在 X3 上测 alpha∈{0.1,0.2,0.3},要求胜过 50.00 至少 ~1.5 分才保留该配置,否则去掉批去除步骤再对比一次以定位原因;再在 proxy2 上测选出的 2 个 alpha,要求 >50.4 才保留;proxy 只抽查一次确认单阶段退路输出与父节点相同。4) 单阶段退路:proxy 及任何只有一个输入的视图直接走 copy_last 分支(与父节点完全相同),两阶段逻辑只在 manifest 报告 ≥2 阶段时激活,因此同一份代码在 final(官方 E8.5+E9.5,间隔 1 天、全胚)上成立,缺失基因沿用 view_io 的 E8.5 均值补齐,不为 proxy2 的 0.5 天间隔或心脏谱系做任何特化。5) 若 X3 与 proxy2 上所有配置都 ≤50,则回退为与父节点等价的实现并如实报告(不提交负收益改动)。",
  "expected_groups": ["de_recovery", "direction"],
  "risks": "1) 收缩位移仍可能有害(父节点已证 alpha=1 在 X3 上 40.33):用极小 alpha(≤0.3)+ 批去除 + 基因级收缩三重缓冲,且第一步就在 X3 上验证符号,最坏只花 3 次查分即可止损。2) 无监督 Leiden 簇与真实细胞类型不完全对齐、跨阶段簇匹配错误会把错误方向加给细胞:用标志基因 Jaccard 匹配、只移匹配簇,匹配率 <60% 时收窄到匹配最好的簇。3) 批去除可能把真实发育信号也减掉:在 X3 上对比有/无批去除即可发现(X3 两阶段同为 Qiu 数据、技术一致,批去除应近中性或略正)。4) 若位移只在一个尺子上见效、另一尺子略降,节点分变化可能落在 ±2 噪声内:要求 X3 与 proxy2 同时 ≥+1.5 才判为真实提升,否则不算进步。5) 运行时间:两阶段各一次 PCA+Leiden 在 3 万细胞级约几十秒,远低于 30 分钟限额;Engineer 应先用 X3(细胞少)冒烟测试。",
  "sources": []
}
```
原始记录位置/home/spark-longxinyang/vec/runs/formal/20261001-233756-search-t1-abc-r0-A-era/nodes/4/researcher.jsonl 5 KB
/home/spark-longxinyang/vec/runs/formal/20261001-233756-search-t1-abc-r0-A-era/nodes/4/researcher.1.jsonl 5 KB
/home/spark-longxinyang/vec/runs/formal/20261001-233756-search-t1-abc-r0-A-era/nodes/4/researcher.stderr