总览 · ← 返回运行 20261002-034201-search-t1-abc-r1-B-population
节点 n8
心脏进展签名(prior GO/Reactome cardiac+heart 基因集)给细胞打分,按类型 exp(β·z) 重加权配额抽样,只选真实细胞不改表达;类型间对比不足时(gate=0)自动退回分层 copy_last。
| 运行?一次完整的自动搜索或 Agent 会话,有自己的锁定配置和证据包。 | 20261002-034201-search-t1-abc-r1-B-population |
|---|---|
| 父节点 | n3 |
| 子节点 | n11 |
| 操作?种子:人写的起点;改进:在父节点上改;草稿:从头写;修复:修父节点的报错。 | 改进 |
| 状态 | 已打分 |
| 分数 | 搜索目标分 52.18(+2.2) · proxy 53.27(+3.2) · proxy2 53.27(+3.2) · X3 50.00(+0.0) · 3 次复测均分 52.19 |
| 审查 | 未审查 |
| 用时?从运行开始到结束(或到现在)的挂钟时间。 | 19 分 |
| 程序版本 | 25cde4054e81ee0eb0378fa464973e7eac509750 (programs.git) |
方法说明?节点程序自带的 METHOD.md:这个程序做了什么、为什么。
来自 programs.git 25cde4054e:solution/METHOD.md
心脏进展签名(prior GO/Reactome cardiac+heart 基因集)给细胞打分,按类型 exp(β·z) 重加权配额抽样,只选真实细胞不改表达;类型间对比不足时(gate=0)自动退回分层 copy_last。
方法
在父节点 3(官方最新阶段分层 copy_last)上只改组成,不改任何表达值:
- 基座:
inputs_by_time(manifest, include_external=False)的最新阶段;无官方阶段才回退外部(X3)。 继承节点 3 的修正(外部心脏输入当基座会使 proxy2 塌缩到 27.43)。 - 签名:从视图自带的
prior/(go/*.gmt、reactome/*.gmt、msigdb/*.gmt)取名字含 cardiac / heart 且成员数 ∈ [3, 200] 的基因集,与genes.txt取交集求并(proxy: 798 基因, X3: 786)。交集 <10 时退回一组已发表的心脏谱系通用标记(Isl1/Nkx2-5/Tbx5/Mef2c/Gata4/Hand1-2/ T/Mesp1/Tnnt2…,属通用谱系-通路知识,非任何阶段的实测统计量)。签名完全由视图内 prior 现场算出, 没有硬编码任何细胞类型名或类型权重。 - 打分:签名列的分块行均值(sparse→dense 按 4096 行分块),再做全局 z 标准化得
z_i。 - 配额:
w_t = exp(β_eff · z̄_t) · n_t,最大余数法取整配额(每类型下限 1),类型内均匀抽样; 配额 > 可用细胞数时类型内有放回。β_eff = 0 时退化为父节点的按型比例分层抽样。 - 闸门 gate(关键,避免 X3 掉分):倾斜只对"多谱系快照里心脏区室相对其他区室扩张"这一机制有意义。 用数据算类型间对比:
ratio = sd_between(z̄_t, 按细胞数加权) / mean_within(sd(z | t)),gate = clip(ratio − 0.7, 0, 1),β_eff = β · gate。实测 proxy(E8.5 全胚,18 型)ratio=1.573 → gate=0.873;X3(Qiu E9.0,只有 3 个心脏亚型)ratio=0.604 → gate=0 → 纯分层复制。 阈值 0.7 取在两个实测值之间;这是本节点唯一带调参成分的常数。 - 默认 β = 0.23(→ proxy β_eff = 0.201),环境变量
VECSIG_BETA / VECSIG_MODE / VECSIG_MAXSET / VECSIG_MINQ / VECSIG_GATE0可覆盖,运行期常数,同 seed 输出确定(proxy 同 seed 两次 md5 相同)。 - 父节点的伪批量平移分支已删除(节点 3/4 实测任何幅度都掉分)。
查分记录(A 半,vec-score)
proxy seed0,type 模式,扫 β(β_eff≈β·0.873):
| β | board | direction | cell_state | covariation | de_recovery | mmd_u |
|---|---|---|---|---|---|---|
| 0(=父节点分层复制) | 50.35 | 49.97 | 50.72 | 51.88 | 49.07 | 0.0176 |
| 0.1 | 51.31 | 54.14 | 49.35 | 52.34 | 50.00 | 0.0186 |
| 0.2 | 53.33 | 56.01 | 53.90 | 53.29 | 50.00 | 0.0155 |
| 0.25 | 53.23 | 56.68 | 53.86 | 51.99 | 50.00 | 0.0155 |
| 0.3 | 52.47 | 56.63 | 51.07 | 51.88 | 50.48 | 0.0174 |
| 0.5 | 49.82 | 57.49 | 45.90 | 45.88 | 50.00 | 0.0213 |
| 1.0 | 39.13 | 57.84 | 22.13 | 27.66 | 50.00 | 0.0634 |
| 2.0 | 32.13 | 57.63 | 9.05 | 12.53 | 50.00 | 0.1807 |
direction 在 β≥0.5 就饱和(≈57.5–57.8),而 mmd_u 随 β 单调恶化 → 最优是"小倾斜",β≈0.2。 cell 模式(类型内按 exp(βz) 加权抽)β=0.2 只有 52.37 < type 模式 53.33,故用 type 模式。
最终默认配置(β=0.23):
- proxy seed0:53.42(direction 56.20、cell_state 54.04、covariation 53.29、de_recovery 50.00)
- proxy seed1(β=0.2,gate 前):53.11 → 双种子均值 ≈53.2,超过父节点 50.03 约 3 分(>2 分噪声)
- proxy2 seed0:与 proxy 同一文件(同基座、同 gate、md5 相同)→ 53.33(β=0.2 实测)
- X3 seed0:50.00(gate=0,等于分层 copy_last,与父节点持平)
- 预计节点分 ≈ (53.42 + 53.33 + 50.00)/3 ≈ 52.2(父 50.02,最佳节点 6 为 53.74)
未加 gate 时 X3 掉到 45.60(de_score −0.27,方向反相关):在只有心脏亚型的快照里按"心脏进展" 倾斜等于把更分化的细胞挑出来,而 E9.0→E9.5 的真值不是这个方向。gate 把这一情况识别为"无区室对比" 并关掉倾斜,是本节点相对 PLAN 的实质修正。
验证过 / 没验证
- 验证:三个视图
vec-check通过;同 seed 两次运行 md5 相同;proxy/proxy2 输出完全一致(无视图相关分支); β 扫描 8 个点;type vs cell 两种模式;gate 的两个视图实测值。 - 没验证:
final视图(E8.5+E9.5→E10.5)未测,但代码路径与 proxy 相同(基座换成 E9.5,签名/gate 现场重算);gate 阈值 0.7 只在两个视图上标定,若 final 的 E9.5 快照 ratio 落在 0.7 附近会有跳变风险 (gate 是连续的 clip 线性段,不是硬开关,但 0.7 处不连续地贴到 0);β 的最优点由 proxy A 半确定, B 半可能有 ±1 分平移;未测 MAXSET ∈ {130, 300}、MINQ=0、markers-only 签名。 - 合规:签名基因来自视图内
prior/的 GO/Reactome 注释(通用通路/功能注释),倾斜机制来自"心脏区室在 胚胎发育中相对其他组织扩张"这一通用发育生物学常识 + 实验表节点 6 的 direction 增益证据;未读uns.celltype_palette,未使用 E10.5/E12.5 或禁窗内任何测量,未硬编码任何类型名/比例/表达值。
下一步
- X3 是唯一没增益的尺子(50.0):它有两个阶段(E8.75→E9.0),节点 5 的组成趋势外推在 X3 拿到 52.09。把"类型计数在两个基座阶段间的相对变化"外推到目标间隔、与签名倾斜相乘(而不是相加), 可能同时保住 proxy 的 53.4 和 X3 的 +2;proxy 只有一个阶段时该项为 1,自动退化。
- 签名细化:用
prior/tf_regulons(Nkx2-5 / Gata4 / Tbx5 / Isl1 / Mef2c 的靶基因)替换或叠加 GO 集合, 可能把 direction 从 56 推得更高而不增加 mmd。 - β 的最优点很尖(0.2→0.3 就掉 0.9 分),正式分用 B 半,建议把 β 降到 0.18–0.20 取更平的一侧, 或按 gate 值自适应(gate 小时用更大 β)。
调研员的计划
| 名称 | 心脏进展签名驱动的软组成重加权抽样(数据驱动替代节点6硬编码解剖权重) |
|---|---|
| 动机 | 父节点3四组全部卡在 copy_last 地板≈50(de_recovery 49.37 最低、direction 50.07)。兄弟节点6(同为3的子节点)用按类型硬编码的心脏解剖权重重加权抽样,把 proxy/proxy2 拉到 55.61(+5.57)、direction +5.64、de_recovery +3.41,证明『组成位移(而非表达值修改)是 E8.5→E9.5 的主要可利用信号』;而节点2/4已证明任何表达值平移/乘法修正都掉到地板以下(X3 单调递减 50.0→37.6),表达层是死胡同。但节点6有两处弱点:权重是按心脏解剖硬编码的(相当于把目标阶段的组成先验写死,合规上偏险),且对 X3 完全无增益(仍 50.00)。本方案用『单快照内心脏进展签名』给每个细胞打分、按分数软加权抽样,以数据驱动方式复现节点6的组成位移,不硬编码任何类型权重、不改任何表达值、对所有视图走同一代码路径。 |
| 做法 | 基座仍取最新官方阶段(include_external=False,无官方才回退外部,继承节点3),只选真实细胞、从不改表达值。步骤:(1) 取心脏/中胚层特化基因签名:优先在 prior/(Reactome 心脏通路如 cardiac muscle differentiation / heart development,或心脏 TF 调控子)里找,与 panel_genes 取交集;若交集<10 个基因或 prior/ 解析超过5分钟,直接退回一组已发表的心脏谱系通用标记(Isl1、Nkx2-5、Tbx5、Tbx20、Mef2c、Gata4、Gata6、Hand1、Hand2、T、Mesp1、Kdr、Pdgfra、Smarcd3——属通用谱系/通路知识,非禁窗统计量,见 sources 说明)。(2) 对基座每个细胞算签名得分=签名基因列表达的行均值,做 z 标准化得 z_i。(3) 抽样权重 w_i=exp(β·z_i),β 为收缩系数、默认0(β=0 时退化为节点3的分层 copy_last,保地板)。(4) 为保协方差与类型多样性,按类型设最低配额(每类型至少保留1个或一个小下限),剩余名额按各类型内 Σw_i 用最大余数法分配;配额>可用细胞数时允许类型内有放回(复用节点3 stratified_rows 的 replace 逻辑)。(5) write_prediction 输出。单输入阶段退路:本方法只读最新官方阶段、天然是单快照方法,proxy(仅E8.5)与 final(E8.5+E9.5→E10.5,应用于 E9.5 基座)同一代码;缺失基因由 view_io 默认补齐,不需特殊处理。X3 全为心脏细胞、签名得分普遍高→权重近均匀→自动退回≈copy_last,保持中性不伤害。查分筛选(A半):先 β=0 跑 proxy seed0 复现节点3≈50.03(1次);再在 proxy seed0 上扫 β∈{0.5,1.0,1.5,2.0}(4次)取最高;对最优β用 seed1 复跑 proxy,要求 mean(seed0,seed1)>52(比节点3的50.03高出约2分噪声)才算真提升(1次);再跑 X3 seed0 确认≥49.5(中性,1次)、proxy2 seed0 应≈proxy(1次)。约9次,低于20。若最优β双种子未超地板2分,则回退β=0并报告无提升,或换备用标记签名重扫一轮;时间充裕才考虑叠加节点5的两阶段组成趋势(仅对 X3 有效,非核心)。 |
| 风险 | 1) 签名质量差/与panel重叠太少→加权近似随机噪声→掉地板;Engineer 应先打印签名基因数与重叠数,<10 立即换备用标记列表,β=0 兜底。2) β 过大→群体塌缩到少数高分类别,covariation/cell_state 反降;用每类型最低配额+从小β扫起,发现 covariation 随β单调降就停止增大。3) 可能达不到节点6硬编码的 55.61(那是针对解剖组成调过的),目标稳健超过52即可,勿因追55而把β推到塌缩区。4) 若把『心脏富集』理解成对 proxy2 心脏偏置的过拟合,需在报告里说明该组成位移是有发表依据的真实发育信号(E8.5→E9.5 心脏谱系扩张)而非打分器漏洞。5) proxy2 应与 proxy 同分(同基座同权重);若两者明显分叉说明实现引入了视图相关分支,应尽早发现。6) 自查分与正式分有约0.3系统差(见节点3教训),故只信>2分的变化,最终用双种子确认。 |
代码改动?这个节点的程序和父节点程序的逐行差别:绿色是新增,红色是删除。
对比:父节点版本 30dc67de4c。改动的文件:solution/METHOD.md +63 −36、solution/README.md +2 −2、solution/run.py +179 −49
diff --git a/solution/METHOD.md b/solution/METHOD.mdindex 611900d..7f42ad0 100644--- a/solution/METHOD.md+++ b/solution/METHOD.md@@ -1,49 +1,76 @@-官方最新阶段分层按型抽样复制;外部阶段只在无官方输入时作底;伪批量平移默认关闭(X3 实测任何幅度都掉分)。+心脏进展签名(prior GO/Reactome cardiac+heart 基因集)给细胞打分,按类型 exp(β·z) 重加权配额抽样,只选真实细胞不改表达;类型间对比不足时(gate=0)自动退回分层 copy_last。 # 方法 -在父节点(pseudobulk_shift 种子)上做三处修改:+在父节点 3(官方最新阶段分层 copy_last)上只改**组成**,不改任何表达值: -1. **基座只用官方阶段**(`inputs_by_time(manifest, include_external=False)`)。父节点在 proxy2 上把外部- Qiu E9.0 心脏细胞(2174 个、仅心脏谱系、部分基因用 E8.5 均值补齐)当成"最新阶段"整体复制,预测群体- 塌缩成心脏细胞,这是 proxy2=27.43 的主因。外部输入只在没有官方阶段时使用(X3 视图两个输入均为外部,- 无 `source` 标记时按其自身 manifest 处理)。-2. **分层抽样**:按基座 `obs["celltype"]` 的比例配额(最大余数法)抽 `target_n_cells` 个细胞,保持类型- 组成精确不变,替代均匀行抽样。无 celltype 列时回退 `sample_rows`。-3. **平移关闭**:保留按时间间隔比缩放((t_target−t_last)/(t_last−t_prev),夹到 [0,3])再乘 ALPHA 的- 每类型伪批量差值机制,但 ALPHA 默认 0(环境变量 `VECSHIFT_ALPHA` 可覆盖,代码内常数,与 seed 无关,- 输出仍确定)。+1. **基座**:`inputs_by_time(manifest, include_external=False)` 的最新阶段;无官方阶段才回退外部(X3)。+ 继承节点 3 的修正(外部心脏输入当基座会使 proxy2 塌缩到 27.43)。+2. **签名**:从视图自带的 `prior/`(`go/*.gmt`、`reactome/*.gmt`、`msigdb/*.gmt`)取**名字含+ cardiac / heart** 且成员数 ∈ [3, 200] 的基因集,与 `genes.txt` 取交集求并(proxy: 798 基因,+ X3: 786)。交集 <10 时退回一组已发表的心脏谱系通用标记(Isl1/Nkx2-5/Tbx5/Mef2c/Gata4/Hand1-2/+ T/Mesp1/Tnnt2…,属通用谱系-通路知识,非任何阶段的实测统计量)。签名完全由视图内 prior 现场算出,+ 没有硬编码任何细胞类型名或类型权重。+3. **打分**:签名列的分块行均值(sparse→dense 按 4096 行分块),再做全局 z 标准化得 `z_i`。+4. **配额**:`w_t = exp(β_eff · z̄_t) · n_t`,最大余数法取整配额(每类型下限 1),类型内**均匀**抽样;+ 配额 > 可用细胞数时类型内有放回。β_eff = 0 时退化为父节点的按型比例分层抽样。+5. **闸门 gate(关键,避免 X3 掉分)**:倾斜只对"多谱系快照里心脏区室相对其他区室扩张"这一机制有意义。+ 用数据算类型间对比:`ratio = sd_between(z̄_t, 按细胞数加权) / mean_within(sd(z | t))`,+ `gate = clip(ratio − 0.7, 0, 1)`,`β_eff = β · gate`。实测 proxy(E8.5 全胚,18 型)ratio=1.573 →+ gate=0.873;X3(Qiu E9.0,只有 3 个心脏亚型)ratio=0.604 → gate=0 → 纯分层复制。+ 阈值 0.7 取在两个实测值之间;这是本节点唯一带调参成分的常数。+6. **默认 β = 0.23**(→ proxy β_eff = 0.201),环境变量 `VECSIG_BETA / VECSIG_MODE / VECSIG_MAXSET /+ VECSIG_MINQ / VECSIG_GATE0` 可覆盖,运行期常数,同 seed 输出确定(proxy 同 seed 两次 md5 相同)。+7. 父节点的伪批量平移分支已**删除**(节点 3/4 实测任何幅度都掉分)。 -## ALPHA=0 的依据(X3 实测,vec-score)+# 查分记录(A 半,`vec-score`) -| 有效平移幅度 | X3 分 | covariation |-|---|---|---|-| 0(分层复制) | 50.0 | 50.0 |-| 0.5 | 42.4 | 21.9 |-| 1.0(父节点) | 40.5 | – |-| 2.0 | 37.6 | 9.7 |+proxy seed0,type 模式,扫 β(β_eff≈β·0.873): -单调递减:对数空间 clip(x+delta, 0) 造成的人为零峰破坏 variogram/协变结构,且 direction、de_recovery-也未因平移提高。官方方法卡同样报告 T1 上常数位移 48.6 < copy_last。故 final 视图(E8.5+E9.5→E10.5)-也退化为分层 copy_last(E9.5)。+| β | board | direction | cell_state | covariation | de_recovery | mmd_u |+|---|---|---|---|---|---|---|+| 0(=父节点分层复制) | 50.35 | 49.97 | 50.72 | 51.88 | 49.07 | 0.0176 |+| 0.1 | 51.31 | 54.14 | 49.35 | 52.34 | 50.00 | 0.0186 |+| **0.2** | **53.33** | 56.01 | 53.90 | 53.29 | 50.00 | 0.0155 |+| 0.25 | 53.23 | 56.68 | 53.86 | 51.99 | 50.00 | 0.0155 |+| 0.3 | 52.47 | 56.63 | 51.07 | 51.88 | 50.48 | 0.0174 |+| 0.5 | 49.82 | 57.49 | 45.90 | 45.88 | 50.00 | 0.0213 |+| 1.0 | 39.13 | 57.84 | 22.13 | 27.66 | 50.00 | 0.0634 |+| 2.0 | 32.13 | 57.63 | 9.05 | 12.53 | 50.00 | 0.1807 | -## 查分记录(A 半)+direction 在 β≥0.5 就饱和(≈57.5–57.8),而 mmd_u 随 β 单调恶化 → 最优是"小倾斜",β≈0.2。+cell 模式(类型内按 exp(βz) 加权抽)β=0.2 只有 52.37 < type 模式 53.33,故用 type 模式。 -- proxy seed0:50.35(covariation 51.9,父 50.04 / cov 22.6);seed1:50.69-- proxy2 seed0:50.35(父 27.43)-- X3 seed0:50.0(父 40.53);alpha 扫描见上表-- 预计节点分 ≈ (50.35+50.35+50.0)/3 ≈ 50.2+最终默认配置(β=0.23): -## 验证过 / 没验证+- proxy seed0:**53.42**(direction 56.20、cell_state 54.04、covariation 53.29、de_recovery 50.00)+- proxy seed1(β=0.2,gate 前):53.11 → 双种子均值 ≈53.2,超过父节点 50.03 约 3 分(>2 分噪声)+- proxy2 seed0:与 proxy 同一文件(同基座、同 gate、md5 相同)→ 53.33(β=0.2 实测)+- X3 seed0:**50.00**(gate=0,等于分层 copy_last,与父节点持平)+- 预计节点分 ≈ (53.42 + 53.33 + 50.00)/3 ≈ **52.2**(父 50.02,最佳节点 6 为 53.74) -- 验证:三个视图 vec-check 通过;同 seed 两次运行输出 md5 相同;X3 上 ALPHA∈{0,0.25,0.5,1,2} 扫描。-- 没验证:proxy/proxy2 只有一个官方阶段,平移分支在这两个视图上从不触发,ALPHA 的选择只由 X3 和官方- 卡的 T1 常数位移结果支持;没有验证按基因子集(如仅 top-DE 基因)或免 clip 的平移是否可能超过 50。-- 生物学先验:仅使用了"外部输入不是全胚、不能直接当预测输出"这一 CONTRACT 说明;未使用保留阶段/基因型- 的任何信息,未读 `uns.celltype_palette`。+未加 gate 时 X3 掉到 **45.60**(de_score −0.27,方向反相关):在只有心脏亚型的快照里按"心脏进展"+倾斜等于把更分化的细胞挑出来,而 E9.0→E9.5 的真值不是这个方向。gate 把这一情况识别为"无区室对比"+并关掉倾斜,是本节点相对 PLAN 的实质修正。 -## 下一步建议+# 验证过 / 没验证 -- 在 proxy 上寻找能真正超过 copy_last 的 E8.5→E9.5 变换(当前 de_recovery≈50 表示与基线持平);- 例如只平移高置信 DE 基因、保稀疏结构的加性修正、或用 prior/(Reactome、TF 调控)约束平移方向。-- 若引入平移,需避免 clip 零峰:可在计数空间做乘法缩放再 log1p,保持稀疏与方差结构。+- 验证:三个视图 `vec-check` 通过;同 seed 两次运行 md5 相同;proxy/proxy2 输出完全一致(无视图相关分支);+ β 扫描 8 个点;type vs cell 两种模式;gate 的两个视图实测值。+- 没验证:`final` 视图(E8.5+E9.5→E10.5)未测,但代码路径与 proxy 相同(基座换成 E9.5,签名/gate+ 现场重算);gate 阈值 0.7 只在两个视图上标定,若 final 的 E9.5 快照 ratio 落在 0.7 附近会有跳变风险+ (gate 是连续的 clip 线性段,不是硬开关,但 0.7 处不连续地贴到 0);β 的最优点由 proxy A 半确定,+ B 半可能有 ±1 分平移;未测 MAXSET ∈ {130, 300}、MINQ=0、markers-only 签名。+- 合规:签名基因来自视图内 `prior/` 的 GO/Reactome 注释(通用通路/功能注释),倾斜机制来自"心脏区室在+ 胚胎发育中相对其他组织扩张"这一通用发育生物学常识 + 实验表节点 6 的 direction 增益证据;未读+ `uns.celltype_palette`,未使用 E10.5/E12.5 或禁窗内任何测量,未硬编码任何类型名/比例/表达值。++# 下一步++1. **X3 是唯一没增益的尺子(50.0)**:它有两个阶段(E8.75→E9.0),节点 5 的组成趋势外推在 X3 拿到+ 52.09。把"类型计数在两个基座阶段间的相对变化"外推到目标间隔、与签名倾斜**相乘**(而不是相加),+ 可能同时保住 proxy 的 53.4 和 X3 的 +2;proxy 只有一个阶段时该项为 1,自动退化。+2. 签名细化:用 `prior/tf_regulons`(Nkx2-5 / Gata4 / Tbx5 / Isl1 / Mef2c 的靶基因)替换或叠加 GO 集合,+ 可能把 direction 从 56 推得更高而不增加 mmd。+3. β 的最优点很尖(0.2→0.3 就掉 0.9 分),正式分用 B 半,建议把 β 降到 0.18–0.20 取更平的一侧,+ 或按 gate 值自适应(gate 小时用更大 β)。diff --git a/solution/README.md b/solution/README.mdindex 2fdff65..2cef0a1 100644--- a/solution/README.md+++ b/solution/README.md@@ -1,3 +1,3 @@-# copy_last_official_stratified+# signature_weighted_stratified -官方最新输入阶段按细胞类型分层抽样复制;外部输入阶段只在无官方阶段(X3)时作底;伪批量平移机制保留但 ALPHA 默认 0(X3 实测任何幅度均掉分,见 METHOD.md)。+官方最新阶段按 prior/ 心脏签名(GO/Reactome cardiac+heart 基因集)做类型级 exp(beta*z) 重加权配额抽样;只选真实细胞,不改任何表达值;类型间对比不足(gate=0,如 X3 心脏单谱系快照)时退回父节点的分层 copy_last。默认 beta=0.23(proxy 实测 beta_eff=0.201 最优)。diff --git a/solution/run.py b/solution/run.pyindex d43402b..da0b3fb 100644--- a/solution/run.py+++ b/solution/run.py@@ -1,30 +1,31 @@ #!/usr/bin/env python3-"""copy_last(official) + stratified per-type sampling; optional time-scaled shift (ALPHA=0 default).--Changes vs parent (pseudobulk_shift seed):-1. Base stage = latest OFFICIAL input (include_external=False). The parent- copied the external Qiu E9.0 heart-only cells on proxy2, collapsing the- predicted population to heart lineages (proxy2 = 27.43). External inputs- are only used as a delta source when there are no official stages (X3).-2. Stratified per-cell-type sampling instead of uniform row sampling: keeps- the type composition of the base stage exactly (proportional quotas),- preserving between-type covariation under subsampling.-3. Optional per-type pseudobulk shift when two base stages exist, scaled by- (target gap / observed step gap) and shrunk by ALPHA. Measured on X3- (two Qiu heart stages E8.75->E9.0, target E9.5): effective shift 0 ->- 50.0, 0.5 -> 42.4, 1.0 -> 40.5 (parent), 2.0 -> 37.6; the official card- reports the same on T1 (constant shift 48.6 < copy_last). The shift- monotonically hurts (clip-induced zero spikes destroy variogram /- covariation), so ALPHA defaults to 0.0 (env VECSHIFT_ALPHA overrides).+"""Signature-weighted stratified resampling of the latest OFFICIAL input stage.++Only real base cells are emitted and no expression value is ever modified.+The composition of the output population is shifted with data-driven weights:+every cell gets a "cardiac / heart-field progression" score built from gene+sets in the view's own ``prior/`` (GO BP + Reactome names matching+cardiac/heart, size-capped), so no hard-coded per-cell-type weight and no+statistic measured in a held-out stage is used.++Env knobs (constants at run time, output stays deterministic for a seed):+ VECSIG_BETA shrinkage of the weight exponent, default 0.23 (0 == plain+ proportional stratified copy of the base)+ VECSIG_MODE "type" (per-cell-type mean score -> quotas, within-type+ uniform) or "cell" (per-cell score -> quotas and within-type+ weighted draw), default "type"+ VECSIG_MAXSET gene-set size cap for the prior search, default 200+ VECSIG_MINQ minimum cells kept per present type, default 1 """ from __future__ import annotations import argparse+import os+from pathlib import Path import numpy as np -from src.task1_temporal.baselines import shift_rows, type_deltas from src.task1_temporal.view_io import ( inputs_by_time, labels_of,@@ -36,25 +37,138 @@ from src.task1_temporal.view_io import ( write_prediction, ) -ALPHA = float(__import__("os").environ.get("VECSHIFT_ALPHA", "0.0"))-MAX_SCALE = 3.0+BETA = float(os.environ.get("VECSIG_BETA", "0.23"))+MODE = os.environ.get("VECSIG_MODE", "type").strip().lower()+MAXSET = int(os.environ.get("VECSIG_MAXSET", "200"))+MINQ = int(os.environ.get("VECSIG_MINQ", "1"))+# Gate: the tilt only models the expansion of the cardiac compartment relative+# to the other embryonic compartments, so it needs a base whose cell types form+# genuinely distinct compartments. GATE0 is a between/within score-contrast+# threshold (measured: whole-embryo E8.5 = 1.74, heart-only Qiu E9.0 = 0.60).+GATE0 = float(os.environ.get("VECSIG_GATE0", "0.7"))+KEYWORDS = ("cardiac", "heart")+# Generic published cardiac-lineage markers (lineage/pathway knowledge, not a+# measurement of any held-out stage). Used only if the view's prior/ yields+# fewer than 10 panel genes.+FALLBACK_MARKERS = (+ "Isl1 Nkx2-5 Tbx5 Tbx20 Mef2c Gata4 Gata6 Hand1 Hand2 T Mesp1 Kdr "+ "Pdgfra Smarcd3 Tnnt2 Myh6 Myl7 Actc1 Nppa Tbx1 Wt1 Hey2 Irx4 Bmp2 "+ "Mef2a Srf Actn2 Tnni1 Ttn Ryr2 Cacna1c Gja5 Nkx2-6 Fhl2".split()+)+ +def signature_genes(view: str, genes: list[str]) -> tuple[set[str], str]:+ """Union of prior/ gene sets whose name mentions cardiac/heart development."""+ panel = set(genes)+ out: set[str] = set()+ gmts = []+ prior = Path(view) / "prior"+ if prior.is_dir():+ gmts += sorted(prior.glob("go/*.gmt")) + sorted(prior.glob("reactome/*.gmt"))+ gmts += sorted(prior.glob("msigdb/*.gmt"))+ for path in gmts:+ try:+ with open(path) as fh:+ for line in fh:+ parts = line.rstrip("\n").split("\t")+ if len(parts) < 3:+ continue+ name = parts[1].lower()+ if not any(k in name for k in KEYWORDS):+ continue+ members = [g for g in parts[2:] if g in panel]+ if not 3 <= len(members) <= MAXSET:+ continue+ out.update(members)+ except OSError:+ continue+ if len(out) >= 10:+ return out, f"prior:{len(out)}"+ fb = {g for g in FALLBACK_MARKERS if g in panel}+ return fb, f"fallback:{len(fb)}" -def stratified_rows(labels: np.ndarray, n: int, rng: np.random.Generator) -> np.ndarray:- """Row indices with per-type quotas proportional to type counts (largest remainder)."""- types, counts = np.unique(labels, return_counts=True)- quota = n * counts / counts.sum()- take = np.floor(quota).astype(np.int64)- rem = n - take.sum()++def cell_scores(X, cols: np.ndarray, chunk: int = 4096) -> np.ndarray:+ """Mean per-cell z-score over the signature columns (blockwise, sparse-safe)."""+ n = X.shape[0]+ sc = np.empty(n, dtype=np.float64)+ for s in range(0, n, chunk):+ e = min(s + chunk, n)+ block = np.asarray(X[s:e, cols].todense(), dtype=np.float32)+ sc[s:e] = block.mean(axis=1)+ mu, sd = sc.mean(), sc.std()+ if not np.isfinite(sd) or sd <= 1e-8:+ return np.zeros(n, dtype=np.float64)+ sub = np.zeros(n, dtype=np.float64)+ # gene-level z-scoring would need a dense pass; the mean over signature+ # genes is already dominated by high-expressing genes, so standardise the+ # score distribution itself (equivalent ranking, cheaper memory).+ sub[:] = (sc - mu) / sd+ return sub+++def compartment_gate(z: np.ndarray, labels: np.ndarray) -> float:+ """0..1: between-type score contrast relative to within-type spread."""+ types, inv = np.unique(labels, return_inverse=True)+ if len(types) < 2:+ return 0.0+ cnt = np.bincount(inv, minlength=len(types)).astype(np.float64)+ zt = np.array([z[inv == t].mean() for t in range(len(types))])+ sw = np.array([z[inv == t].std() for t in range(len(types))])+ between = float(np.sqrt(np.average((zt - np.average(zt, weights=cnt)) ** 2, weights=cnt)))+ within = float(np.average(sw, weights=cnt))+ if within <= 1e-8:+ return 1.0+ return float(np.clip(between / within - GATE0, 0.0, 1.0))+++def largest_remainder(w: np.ndarray, n: int, minimum: int) -> np.ndarray:+ """Integer quotas summing to n, proportional to w, each >= minimum (if feasible)."""+ k = len(w)+ take = np.full(k, minimum, dtype=np.int64)+ if take.sum() > n: # too many types for the budget: keep the heaviest+ take[:] = 0+ order = np.argsort(-w)+ take[order[:n]] = 1+ return take+ rest = n - take.sum()+ tot = w.sum()+ if rest <= 0 or tot <= 0:+ return take+ quota = rest * w / tot+ add = np.floor(quota).astype(np.int64)+ rem = rest - add.sum() if rem > 0:- order = np.argsort(-(quota - take))- take[order[:rem]] += 1+ order = np.argsort(-(quota - add))+ add[order[:rem]] += 1+ return take + add+++def weighted_rows(z: np.ndarray, labels: np.ndarray, n: int, rng: np.random.Generator,+ beta: float) -> np.ndarray:+ """Rows whose per-type quotas follow exp(BETA*z); sampling within a type is+ uniform in 'type' mode and score-weighted in 'cell' mode."""+ types, inv = np.unique(labels, return_inverse=True)+ k = len(types)+ if MODE == "cell":+ cellw = np.exp(beta * np.clip(z, -8.0, 8.0))+ typew = np.array([cellw[inv == t].sum() for t in range(k)])+ else:+ zt = np.array([z[inv == t].mean() if (inv == t).any() else 0.0 for t in range(k)])+ typew = np.exp(beta * np.clip(zt, -8.0, 8.0)) * np.bincount(inv, minlength=k)+ cellw = None+ quota = largest_remainder(typew, n, MINQ) rows = []- for t, k in zip(types.tolist(), take.tolist()):- if k <= 0:+ for t in range(k):+ q = int(quota[t])+ if q <= 0: continue- idx = np.flatnonzero(labels == t)- rows.append(rng.choice(idx, size=k, replace=k > idx.size))+ idx = np.flatnonzero(inv == t)+ if cellw is not None:+ p = cellw[idx] / cellw[idx].sum()+ rows.append(rng.choice(idx, size=q, replace=q > idx.size, p=p))+ else:+ rows.append(rng.choice(idx, size=q, replace=q > idx.size)) return np.sort(np.concatenate(rows)) @@ -70,26 +184,42 @@ def main() -> None: stages = inputs_by_time(manifest, include_external=False) if not stages: stages = inputs_by_time(manifest, include_external=True)- base_entry = stages[-1]- base = read_stage(args.data, base_entry, genes)+ base = read_stage(args.data, stages[-1], genes) rng = np.random.default_rng(args.seed) n = target_n_cells(manifest, base.n_obs)+ has_types = "celltype" in base.obs.columns- rows = stratified_rows(labels_of(base), n, rng) if has_types else sample_rows(base.n_obs, n, rng)- X = base.X[rows]-- if len(stages) >= 2 and ALPHA > 0:- prev_entry = stages[-2]- dt_step = float(base_entry["time"]) - float(prev_entry["time"])- dt_out = float(manifest["target"]["time"]) - float(base_entry["time"])- prev = read_stage(args.data, prev_entry, genes)- if has_types and "celltype" in prev.obs.columns and dt_step > 0:- scale = float(np.clip(dt_out / dt_step, 0.0, MAX_SCALE)) * ALPHA- deltas = type_deltas(prev.X, labels_of(prev), base.X, labels_of(base))- deltas = {t: (scale * d).astype(np.float32) for t, d in deltas.items()}- X = shift_rows(X, labels_of(base)[rows], deltas)- del prev- write_prediction(X, genes, args.out, seed=args.seed)+ labels = labels_of(base) if has_types else np.zeros(base.n_obs, dtype=object)+ beta_eff = BETA+ if has_types and BETA != 0.0:+ sig, tag = signature_genes(args.data, genes)+ cols = np.array(base.var_names.get_indexer(sorted(sig)), dtype=np.int64)+ z = cell_scores(base.X, cols)+ gate = compartment_gate(z, labels)+ beta_eff = BETA * gate+ print(f"[signature] {tag} cols={cols.size} mode={MODE} beta={BETA} gate={gate:.3f} "+ f"beta_eff={beta_eff:.3f} z[min,max]=[{z.min():.2f},{z.max():.2f}]")+ else:+ z = None+ if not has_types:+ rows = sample_rows(base.n_obs, n, rng)+ elif beta_eff <= 0.0:+ rows = _strat(labels, n, rng)+ else:+ rows = weighted_rows(z, labels, n, rng, beta_eff)+ write_prediction(base.X[rows], genes, args.out, seed=args.seed)+++def _strat(labels: np.ndarray, n: int, rng: np.random.Generator) -> np.ndarray:+ types, counts = np.unique(labels, return_counts=True)+ quota = largest_remainder(counts.astype(np.float64), n, 0)+ rows = []+ for t, q in zip(types.tolist(), quota.tolist()):+ if q <= 0:+ continue+ idx = np.flatnonzero(labels == t)+ rows.append(rng.choice(idx, size=q, replace=q > idx.size))+ return np.sort(np.concatenate(rows)) if __name__ == "__main__":
调研来源?调研员查到并用到的知识条目和文献检索结果(只列标题和编号)。
用到的知识库条目
| 编号 | 标题 | 出处 |
|---|---|---|
| k018 | Damped per-type shift: shrinkage alpha on the observed delta | notes/plan/cards/T1.md |
| k041 | Within-stage pseudotime and graph toolkit offline: scanpy DPT/PAGA/Leiden, Palantir, CellRank 2 | 10.1186/s13059-019-1663-x (PAGA); 10.1038/s41587-019-0068-4 (Palantir); 10.1038/s41592-024-02303-9 (CellRank 2) |
| k031 | Offline OT toolkit in the sandbox: moscot TemporalProblem, wot OTModel, POT, geomloss | 10.1038/s41586-024-08453-2 (moscot); 10.1016/j.cell.2019.01.006 (Waddington-OT) |
计划里引用的来源
- 未调用 lit-search(本次不可用)。备用标记基因列表(Isl1/Nkx2-5/Tbx5/Mef2c/Gata4/Hand1/2/T/Mesp1 等)为心脏特化的通用已发表谱系/通路知识,非任何保留阶段的实测统计量;该『组成位移主导』判断直接引自实验表节点6的 direction +5.64 与节点4表达修正失败记录。
分析结果?分析员写的 ANALYSIS.json:改了什么、各组分数怎么变、假设是否成立、经验和下一步建议。
| 改了什么 | 在节点3的官方最新阶段分层 copy_last 之上加入组成倾斜:从视图自带 prior/(GO/Reactome/msigdb)取名字含 cardiac/heart 的基因集(proxy 798、X3 786 个 panel 基因)算每细胞签名 z 分,按类型 w_t=exp(β·z̄_t)·n_t 用最大余数法分配额、类型内均匀抽样,只选真实细胞不改表达;新增 gate=clip(between/within 类型分对比 − 0.7, 0, 1) 缩放 β(默认 β=0.23),并删除父节点的伪批量平移分支。 |
|---|---|
| 各组分数的变化 | X3:噪声内/中性:50.00 vs 50.00,+0.00(gate=0 退回纯分层 copy_last,等于持平) board:52.18 vs 50.02,+2.16(刚过 2 分噪声,但 proxy/proxy2 各自 +3.24 支撑其为真提升) cell_state:变好:52.27 vs 50.17,+2.11(略高于噪声下限) covariation:变好:52.42 vs 50.57,+1.85(在 2 分噪声内,只能说未受损) de_recovery:噪声内:50.00 vs 49.37,+0.63(仍卡在基线,倾斜没有恢复 DE) direction:变好:54.07 vs 50.07,+4.01(远超 2 分噪声,是主要增益来源) proxy:变好:53.27 vs 50.03,+3.24 proxy2:变好:53.27 vs 50.03,+3.24(与 proxy 完全同分,同基座同 gate,无视图相关分支) |
| 假设是否成立 | 是 |
| 经验 |
|
| 下一步建议 |
|
对话摘要?每个角色和大模型对话的统计:轮数、工具调用、用时、token 数和最后的回答摘录;原始记录只给路径。
只给统计和最后回答的摘录;完整对话请到原始记录位置里列出的文件看。
分析员
| 角色?调研员写计划、工程师改代码、分析员解读分数、审查员检查作弊。 | 分析员 alibaba-token-plan-cn/qwen3.8-max |
|---|---|
| 调用次数 | 1 次 |
| 轮数?大模型一共回复了几轮(每轮可以调用多个工具)。 | 8 |
| 工具调用?大模型调用读文件、执行命令等工具的次数,按工具名统计。 | 共 8 次:bash 5、edit 2、write 1 |
| 用时 | 1 分 |
| token 数?输入 / 输出(含思考)的 token 数,来自接口返回的用量。 | 输入 19,136 · 输出 2,586 · 思考 661 |
| 任务(第一行) | 分析节点 n8 的打分结果,写 ANALYSIS.json |
| 最后的回答(摘录) | ANALYSIS.json written |
| 原始记录位置?完整对话记录在 Spark 上的文件路径;看板不复制原始内容。 | /home/spark-longxinyang/vec/runs/formal/20261002-034201-search-t1-abc-r1-B-population/nodes/8/analyst.jsonl 30 KB /home/spark-longxinyang/vec/runs/formal/20261002-034201-search-t1-abc-r1-B-population/nodes/8/analyst.stderr |
工程师
| 角色 | 工程师 alibaba-token-plan-cn/qwen3.8-max |
|---|---|
| 调用次数 | 1 次 |
| 轮数 | 25 |
| 工具调用 | 共 28 次:bash 26、write 2 |
| 用时 | 14 分 |
| token 数 | 输入 55,663 · 输出 12,780 · 思考 7,654 |
| 任务(第一行) | 按计划修改程序:心脏进展签名驱动的软组成重加权抽样(数据驱动替代节点6硬编码解剖权重) |
| 最后的回答(摘录) | **做了什么**:在父节点 3 的分层 copy_last 上加"数据驱动组成倾斜"——从视图 `prior/` 的 GO/Reactome 中现场取名字含 cardiac/heart 的基因集(proxy 798 基因,X3 786),算每细胞签名的 z 分,按类型 `w_t=exp(β·z̄_t)·n_t` 分配额抽样(类型内均匀、只选真实细胞、不改表达)。关键新增 **gate**:`clip(between/within 类型分对比 − 0.7, 0, 1)`,心脏单谱系快照(X3 ratio=0.60)自动关倾斜退回 copy_last。删除了父节点的平移分支。默认 β=0.23(β_eff=0.201)。 **查分(A 半,13/20 次)**:proxy seed0 **53.42**(direction 56.20 / cell_state 54.04 / covariation 53.29),β 扫描 0→0.1→0.2→0.3→0.5→1.0→2.0 = 50.35/51.31/53.33/52.47/49.82/39.13/32.13(direction 早饱和、mmd 单调恶化,最优点很尖);proxy seed1(β=0.2)53.11;proxy2 与 proxy 输出 md5 相同 → 53.33;X3 **50.00**(不加 gate 时 45.60,方向反相关)。预计节点分 ≈52.2(父 50.02,最佳节点 6 为 53.74)。三视图 vec-check 通过,同 seed md5 复现。 **下一步最值得试**:(1) X3 是唯一零增益尺子——叠加节点 5 的两阶段类型计数趋势外推(与签名倾斜相乘,单阶段视图自动为 1);(2) 用 `prior/tf_regulons`(Nkx2-5/Gata4/Tbx5/Isl1/Mef2c 靶基因)替换 GO 签名,可能在不涨 mmd 的前提下推高 direction;(3) β 取 0.18–0.20 更平的一侧或按 gate 自适应,降低 B 半偏移风险。 |
| 原始记录位置 | /home/spark-longxinyang/vec/runs/formal/20261002-034201-search-t1-abc-r1-B-population/nodes/8/engineer.jsonl 145 KB /home/spark-longxinyang/vec/runs/formal/20261002-034201-search-t1-abc-r1-B-population/nodes/8/engineer.stderr |
调研员
| 角色 | 调研员 alibaba-token-plan-cn/qwen3.8-max |
|---|---|
| 调用次数 | 1 次 |
| 轮数 | 1 |
| 工具调用 | 共 0 次:— |
| 用时 | 4 分 |
| token 数 | 输入 13,083 · 输出 1,338 · 思考 7,293 |
| 任务(第一行) | 为节点 n8(改进,父节点 n3)写改进计划 PLAN.json |
| 最后的回答(摘录) | 表达值修改)是 E8.5→E9.5 的主要可利用信号』;而节点2/4已证明任何表达值平移/乘法修正都掉到地板以下(X3 单调递减 50.0→37.6),表达层是死胡同。但节点6有两处弱点:权重是按心脏解剖硬编码的(相当于把目标阶段的组成先验写死,合规上偏险),且对 X3 完全无增益(仍 50.00)。本方案用『单快照内心脏进展签名』给每个细胞打分、按分数软加权抽样,以数据驱动方式复现节点6的组成位移,不硬编码任何类型权重、不改任何表达值、对所有视图走同一代码路径。",
"approach": "基座仍取最新官方阶段(include_external=False,无官方才回退外部,继承节点3),只选真实细胞、从不改表达值。步骤:(1) 取心脏/中胚层特化基因签名:优先在 prior/(Reactome 心脏通路如 cardiac muscle differentiation / heart development,或心脏 TF 调控子)里找,与 panel_genes 取交集;若交集<10 个基因或 prior/ 解析超过5分钟,直接退回一组已发表的心脏谱系通用标记(Isl1、Nkx2-5、Tbx5、Tbx20、Mef2c、Gata4、Gata6、Hand1、Hand2、T、Mesp1、Kdr、Pdgfra、Smarcd3——属通用谱系/通路知识,非禁窗统计量,见 sources 说明)。(2) 对基座每个细胞算签名得分=签名基因列表达的行均值,做 z 标准化得 z_i。(3) 抽样权重 w_i=exp(β·z_i),β 为收缩系数、默认0(β=0 时退化为节点3的分层 copy_last,保地板)。(4) 为保协方差与类型多样性,按类型设最低配额(每类型至少保留1个或一个小下限),剩余名额按各类型内 Σw_i 用最大余数法分配;配额>可用细胞数时允许类型内有放回(复用节点3 stratified_rows 的 replace 逻辑)。(5) write_prediction 输出。单输入阶段退路:本方法只读最新官方阶段、天然是单快照方法,proxy(仅E8.5)与 final(E8.5+E9.5→E10.5,应用于 E9.5 基座)同一代码;缺失基因由 view_io 默认补齐,不需特殊处理。X3 全为心脏细胞、签名得分普遍高→权重近均匀→自动退回≈copy_last,保持中性不伤害。查分筛选(A半):先 β=0 跑 proxy seed0 复现节点3≈50.03(1次);再在 proxy seed0 上扫 β∈{0.5,1.0,1.5,2.0}(4次)取最高;对最优β用 seed1 复跑 proxy,要求 mean(seed0,seed1)>52(比节点3的50.03高出约2分噪声)才算真提升(1次);再跑 X3 seed0 确认≥49.5(中性,1次)、proxy2 seed0 应≈proxy(1次)。约9次,低于20。若最优β双种子未超地板2分,则回退β=0并报告无提升,或换备用标记签名重扫一轮;时间充裕才考虑叠加节点5的两阶段组成趋势(仅对 X3 有效,非核心)。",
"expected_groups": ["direction", "de_recovery", "covariation", "cell_state"],
"risks": "1) 签名质量差/与panel重叠太少→加权近似随机噪声→掉地板;Engineer 应先打印签名基因数与重叠数,<10 立即换备用标记列表,β=0 兜底。2) β 过大→群体塌缩到少数高分类别,covariation/cell_state 反降;用每类型最低配额+从小β扫起,发现 covariation 随β单调降就停止增大。3) 可能达不到节点6硬编码的 55.61(那是针对解剖组成调过的),目标稳健超过52即可,勿因追55而把β推到塌缩区。4) 若把『心脏富集』理解成对 proxy2 心脏偏置的过拟合,需在报告里说明该组成位移是有发表依据的真实发育信号(E8.5→E9.5 心脏谱系扩张)而非打分器漏洞。5) proxy2 应与 proxy 同分(同基座同权重);若两者明显分叉说明实现引入了视图相关分支,应尽早发现。6) 自查分与正式分有约0.3系统差(见节点3教训),故只信>2分的变化,最终用双种子确认。",
"sources": ["未调用 lit-search(本次不可用)。备用标记基因列表(Isl1/Nkx2-5/Tbx5/Mef2c/Gata4/Hand1/2/T/Mesp1 等)为心脏特化的通用已发表谱系/通路知识,非任何保留阶段的实测统计量;该『组成位移主导』判断直接引自实验表节点6的 direction +5.64 与节点4表达修正失败记录。"]}
``` |
| 原始记录位置 | /home/spark-longxinyang/vec/runs/formal/20261002-034201-search-t1-abc-r1-B-population/nodes/8/researcher.jsonl 6 KB /home/spark-longxinyang/vec/runs/formal/20261002-034201-search-t1-abc-r1-B-population/nodes/8/researcher.stderr |