总览 · ← 返回运行 20261003-094241-search-t2-heart-interp-g24-D-s2
节点 n23
T2HI-04 邻域块抽样:以空间块为单元混抽括号两侧真实细胞,保留局部表达-坐标配对
| 运行?一次完整的自动搜索或 Agent 会话,有自己的锁定配置和证据包。 | 20261003-094241-search-t2-heart-interp-g24-D-s2 |
|---|---|
| 父节点 | (种子,没有父节点) |
| 子节点 | n26 |
| 操作?种子:人写的起点;改进:在父节点上改;草稿:从头写;修复:修父节点的报错。 | 草稿 |
| 状态 | 已打分 |
| 分数 | 搜索目标分 61.36 · proxy 61.36 · 3 次复测均分 61.53 |
| 审查 | 未审查 |
| 用时?从运行开始到结束(或到现在)的挂钟时间。 | 24 分 |
| 程序版本 | e1419b7c804337fa4b3f23b46975d01d6cd69b03 (programs.git) |
方法说明?节点程序自带的 METHOD.md:这个程序做了什么、为什么。
来自 programs.git e1419b7c80:solution/METHOD.md
T2HI-04 邻域块抽样:以空间块为单元混抽括号两侧真实细胞,保留局部表达-坐标配对
方法(family T2HI-04,机制默认打开:T2HI_BLOCK_K=15)
interp_bracket取目标两侧输入 (A, B, t);无括号(单输入)时退化为对锚阶段的块抽样子采样(全细胞数 ≤ max_cells 时原样输出)。- 细胞数 n = log 线性插值 (n_A, n_B, t),夹到 [min_cells, max_cells](本榜 n = max_cells)。
- 类型配额(logit 型组成插值):对 A∪B 词汇表,w(k) = (count_A(k)+1)^(1−t)·(count_B(k)+1)^t,归一化后按最大余数法分配 n,池上限截断并把缺口回补到另一侧。每类型的配额再按侧拆分:单侧类型全取其所在侧,共有类型按 t 拆。
- 块抽样(机制核心):每侧用 cKDTree 在(对齐后)空间坐标上取每细胞的 15 个最近邻(k_block=15,与评分邻域定义一致)。随机选种子,取其块(种子+15 近邻),去重(首现保留),直到达到该侧配额;不足时从剩余细胞补齐。
- 输出被选细胞的原始表达(CSR 行切片)与坐标:坐标先经
align_pair(procrustes)(心脏共有类型 <3 时自动 PCA 回退)放入同一帧,逐侧缩放到 target_rms = log 插值(RMS_A, RMS_B, t),拼接后整云再 scale_to_rms。不做任何逐细胞位移或表达插值。 --seed→np.random.default_rng(seed),全流程确定(已验证重跑逐位相同)。
知识来源:仅通用机制(评分邻域 k=15 的定义来自任务说明;坐标系性质来自方法卡对已发布阶段的描述)。未使用任何保留阶段/保留基因型信息;RMS、组成、配额全部从视图输入现场计算,无绝对时间分支,视图无关。
关闭机制对照(T2HI_BLOCK_K=0,同 seed,其余不变)
k=0 时每块只含种子本身,退化为按配额的独立细胞混抽(节点 10 风格 mix)。vec-score(A 半,T2:heart:val_interp):
| 配置 | board | cell_state | expr_change | local_spatial | shape | nbd raw |
|---|---|---|---|---|---|---|
| 对照 k=0(eps 配额,align) | 57.90 | 64.77 | 64.03 | 50.67 | 52.11 | 0.0932 |
| 机制开 k=15(eps 配额,align) | 60.43 | 64.89 | 64.72 | 59.72 | 52.41 | 0.0648 |
| 机制开 k=15(count 配额,提交默认) | 61.20 | 65.88 | 64.89 | 62.17 | 51.88 | 0.0585 |
| k=15 count 配额 s=0.25 / s=0.6 | 57.62 / 58.59 | 58.19 / 61.33 | – | 57.31 / 56.67 | – | 0.071 / 0.073 |
机制生效证据:
- neighborhood_mmd raw 0.0932 → 0.0648/0.0585(对照预期 ~0.05 未达到,但块抽样方向与 PLAN 预测一致,local_spatial +9~11.5);
- 输出细胞 15-NN 中同侧(同块来源)比例:对照 0.72–0.74 → 块抽样 0.905(局部配对确实被块保持);
- cell_state / expression_change 与对照持平或略升(±1.1 分内),机制不干扰表达分布,符合 PLAN 预期 3);
- shape_scale 降 0.2–0.5 分(<2),块间排列未明显破坏外形,符合 PLAN 预期 4)。
验证过 / 未验证
- 验证:proxy 视图 5 个配置查分(7 次查询);k0/k15 输出逐位不同(机制未退化);默认配置重跑逐位复现;vec-check 通过;eps vs count 两种配额、s=t·shift∈{0.625,1,1.5} 扫描(s=t 最优)。
- 未验证:count 配额下的 k=0 对照(块效应证据取自 eps 配对的 k0/k15);k_block≠15 的敏感性;final/伪装视图实跑(代码无视图依赖,但仅在本 proxy 视图上运行过);对齐方式(procrustes vs 不对齐对分影响 <0.5,未细扫)。
与树内已有方法的关系
低于节点 10/13/17(65.25–66.60):差距主要在 cell_state/expression_change(65.9/64.9 vs 72.4/71.7),即本实现的 logit 组成配额未能复现节点 10 的组成插值(其代码不可见,eps 伪计数与 count 几何均值两种读法都试过)。local_spatial 上块抽样 62.17 已超节点 10 的 65.8 以下的独立抽样基线(节点 2:54.0),但低于节点 13 的同型收缩(70.2)。下一步最值得试:把 count 配额换成对组成做单纯形投影的更强 B 侧新兴类型上权(或直接组合节点 13 的同型收缩到块抽样输出上),以及 k_block=25–30 的敏感性。
调研员的计划
| 名称 | T2HI-04 邻域块抽样:保局部结构的空间混抽 |
|---|---|
| 动机 | 当前最佳节点17(66.60)的 local_spatial=70.32 依赖后处理 kNN 收缩(k=20, α=1.25)来修复独立抽样破坏的邻域配对。节点2(纯mix)local_spatial仅54.03,节点10(logit mix)65.80,说明独立抽样本身破坏邻域。T2HI-04 直接在抽样阶段保持空间邻域完整性,避免先破坏再修复。形状组51.86仍贴地板,邻域skill若>0.5则门=1不额外加分,但推高neighborhood_mmd本身值25分。 |
| 做法 | 步骤:1) 读入两阶段数据,构建各自空间坐标的 kNN 图(k_block=15,与评分邻域定义对齐)。2) 用节点10相同的 logit 组成插值确定两侧细胞配额(t=0.5 时各50%)。3) 块抽样:从阶段A随机选种子细胞,取其 k_block 个空间近邻为一个块;阶段B同理。按配额决定从A、B各取多少块。若同一细胞被多个块选中,保留首次出现(去重)。若去重后不足目标细胞数,继续补抽新块。4) 输出所有被选中细胞的原始表达和原始坐标,不做任何位移或插值。5) 全云坐标 scale 到目标 RMS(与 copy_last 相同的 scale_to_rms)。6) 关闭机制对照:k_block=0 时每个块只含种子细胞本身,退化为独立细胞抽样(等价于节点10的mix),输出应逐位相同。关键参数:k_block=15(固定,与评分15-NN对齐);若细胞数不足可降到 k_block=10。vec-score 快速筛选:先跑 k_block=0(对照)和 k_block=15 两个配置,比较 neighborhood_mmd raw 值(对照预期~0.051,块抽样预期<0.045)。单输入阶段退路:若只有一个阶段(T1 proxy),所有块都从该阶段取,k_block 不变,组成配额无意义(全来自同一侧),等价于带邻域约束的 copy_last。 |
| 风险 | 1) 块重叠导致去重后细胞数不足或多样性下降——Engineer 应检查输出细胞数是否达标,不足时减小 k_block 或允许有放回抽块。2) 邻域块来自单一阶段,若目标时间点的真实邻域组成与任一输入阶段差异大,块结构可能不匹配——但这是插值任务的固有限制,mix 家族都面临。3) 形状组可能因块内坐标来自同一阶段、块间缺乏过渡而略降——用 d2_shape raw 监控,若 >0.05 则考虑块间加微小 jitter。4) 30分钟时限:实现本身简单(无优化循环),主要时间花在调试细胞数和 vec-score 查分;先小样本验证再全量。 |
代码改动?这个节点的程序和父节点程序的逐行差别:绿色是新增,红色是删除。
对比:这个提交的上一版(种子程序:相对空仓库)。改动的文件:solution/EXECUTION.json +1 −0、solution/METHOD.md +38 −0、solution/run.py +297 −0
diff --git a/solution/EXECUTION.json b/solution/EXECUTION.jsonnew file mode 100644index 0000000..9d5125c--- /dev/null+++ b/solution/EXECUTION.json@@ -0,0 +1 @@+{"gpu": false}diff --git a/solution/METHOD.md b/solution/METHOD.mdnew file mode 100644index 0000000..0c4620f--- /dev/null+++ b/solution/METHOD.md@@ -0,0 +1,38 @@+# T2HI-04 邻域块抽样:以空间块为单元混抽括号两侧真实细胞,保留局部表达-坐标配对++## 方法(family T2HI-04,机制默认打开:`T2HI_BLOCK_K=15`)++1. `interp_bracket` 取目标两侧输入 (A, B, t);无括号(单输入)时退化为对锚阶段的块抽样子采样(全细胞数 ≤ max_cells 时原样输出)。+2. 细胞数 n = log 线性插值 (n_A, n_B, t),夹到 [min_cells, max_cells](本榜 n = max_cells)。+3. 类型配额(logit 型组成插值):对 A∪B 词汇表,w(k) = (count_A(k)+1)^(1−t)·(count_B(k)+1)^t,归一化后按最大余数法分配 n,池上限截断并把缺口回补到另一侧。每类型的配额再按侧拆分:单侧类型全取其所在侧,共有类型按 t 拆。+4. **块抽样(机制核心)**:每侧用 cKDTree 在(对齐后)空间坐标上取每细胞的 15 个最近邻(k_block=15,与评分邻域定义一致)。随机选种子,取其块(种子+15 近邻),去重(首现保留),直到达到该侧配额;不足时从剩余细胞补齐。+5. 输出被选细胞的**原始表达**(CSR 行切片)与坐标:坐标先经 `align_pair(procrustes)`(心脏共有类型 <3 时自动 PCA 回退)放入同一帧,逐侧缩放到 target_rms = log 插值(RMS_A, RMS_B, t),拼接后整云再 scale_to_rms。不做任何逐细胞位移或表达插值。+6. `--seed` → `np.random.default_rng(seed)`,全流程确定(已验证重跑逐位相同)。++知识来源:仅通用机制(评分邻域 k=15 的定义来自任务说明;坐标系性质来自方法卡对已发布阶段的描述)。未使用任何保留阶段/保留基因型信息;RMS、组成、配额全部从视图输入现场计算,无绝对时间分支,视图无关。++## 关闭机制对照(`T2HI_BLOCK_K=0`,同 seed,其余不变)++k=0 时每块只含种子本身,退化为按配额的独立细胞混抽(节点 10 风格 mix)。vec-score(A 半,T2:heart:val_interp):++| 配置 | board | cell_state | expr_change | local_spatial | shape | nbd raw |+|---|---:|---:|---:|---:|---:|---:|+| 对照 k=0(eps 配额,align) | 57.90 | 64.77 | 64.03 | 50.67 | 52.11 | 0.0932 |+| 机制开 k=15(eps 配额,align) | 60.43 | 64.89 | 64.72 | 59.72 | 52.41 | 0.0648 |+| **机制开 k=15(count 配额,提交默认)** | **61.20** | 65.88 | 64.89 | 62.17 | 51.88 | 0.0585 |+| k=15 count 配额 s=0.25 / s=0.6 | 57.62 / 58.59 | 58.19 / 61.33 | – | 57.31 / 56.67 | – | 0.071 / 0.073 |++机制生效证据:+- neighborhood_mmd raw 0.0932 → 0.0648/0.0585(对照预期 ~0.05 未达到,但块抽样方向与 PLAN 预测一致,local_spatial +9~11.5);+- 输出细胞 15-NN 中同侧(同块来源)比例:对照 0.72–0.74 → 块抽样 0.905(局部配对确实被块保持);+- cell_state / expression_change 与对照持平或略升(±1.1 分内),机制不干扰表达分布,符合 PLAN 预期 3);+- shape_scale 降 0.2–0.5 分(<2),块间排列未明显破坏外形,符合 PLAN 预期 4)。++## 验证过 / 未验证++- 验证:proxy 视图 5 个配置查分(7 次查询);k0/k15 输出逐位不同(机制未退化);默认配置重跑逐位复现;vec-check 通过;eps vs count 两种配额、s=t·shift∈{0.625,1,1.5} 扫描(s=t 最优)。+- 未验证:count 配额下的 k=0 对照(块效应证据取自 eps 配对的 k0/k15);k_block≠15 的敏感性;final/伪装视图实跑(代码无视图依赖,但仅在本 proxy 视图上运行过);对齐方式(procrustes vs 不对齐对分影响 <0.5,未细扫)。++## 与树内已有方法的关系++低于节点 10/13/17(65.25–66.60):差距主要在 cell_state/expression_change(65.9/64.9 vs 72.4/71.7),即本实现的 logit 组成配额未能复现节点 10 的组成插值(其代码不可见,eps 伪计数与 count 几何均值两种读法都试过)。local_spatial 上块抽样 62.17 已超节点 10 的 65.8 以下的独立抽样基线(节点 2:54.0),但低于节点 13 的同型收缩(70.2)。下一步最值得试:把 count 配额换成对组成做单纯形投影的更强 B 侧新兴类型上权(或直接组合节点 13 的同型收缩到块抽样输出上),以及 k_block=25–30 的敏感性。diff --git a/solution/run.py b/solution/run.pynew file mode 100644index 0000000..ecc8d59--- /dev/null+++ b/solution/run.py@@ -0,0 +1,297 @@+"""T2HI-04: neighborhood-block sampling for spatial interpolation.++Sampling unit is a spatial block (seed cell + its k_block nearest spatial+neighbors within the same stage), so local expression-coordinate pairing of+real tissue is preserved. Per-type quotas come from log-space (logit-style)+interpolation of the two bracketing stages' type compositions; each type's+quota is split between the sides (single-side types go to their own side,+shared types split by t). Coordinates are output as-is (no per-cell+displacement); the concatenated cloud is centered and scaled to the+log-interpolated RMS of the two bracket stages.++Mechanism-off control: T2HI_BLOCK_K=0 makes each block a single cell,+degenerating to independent type-quota sampling (node-10-style mix).+"""++from __future__ import annotations++import argparse+import os+from pathlib import Path++import numpy as np+from scipy import sparse+from scipy.spatial import cKDTree+++def _env(name, default, cast):+ v = os.environ.get(name)+ return default if v is None else cast(v)+++K_BLOCK = _env("T2HI_BLOCK_K", 15, int)+EPS = _env("T2HI_EPS", 1e-3, float)+DO_ALIGN = _env("T2HI_ALIGN", 1, int)+QUOTA_MODE = _env("T2HI_QUOTA", "count", str)+T_SHIFT = _env("T2HI_TSHIFT", 1.0, float)+++def rms_radius(c):+ c = np.asarray(c, dtype=np.float64)+ c = c - c.mean(axis=0)+ return float(np.sqrt((c * c).sum(axis=1).mean()))+++def scale_to_rms(c, target):+ x = np.asarray(c, dtype=np.float64)+ x = x - x.mean(axis=0)+ r = float(np.sqrt((x * x).sum(axis=1).mean()))+ if r < 1e-8 or target <= 0:+ return x+ return x * (float(target) / r)+++def log_interp(a, b, t, damp=1.0):+ la = np.log(max(float(a), 1e-8))+ lb = np.log(max(float(b), 1e-8))+ return float(np.exp(la + damp * float(t) * (lb - la)))+++def type_quotas(labels_a, labels_b, t):+ """Log-space composition interpolation -> (types, fracs, side_share_b).++ side_share_b[k] in [0, 1]: fraction of type k's quota taken from stage B+ (0 for A-only types, 1 for B-only types, t for shared types).+ """+ la = np.asarray(labels_a).astype(str)+ lb = np.asarray(labels_b).astype(str)+ types = sorted(set(la.tolist()) | set(lb.tolist()))+ na, nb = len(la), len(lb)+ ca = {k: 0.0 for k in types}+ cb = {k: 0.0 for k in types}+ for k, v in zip(*np.unique(la, return_counts=True)):+ ca[str(k)] = float(v)+ for k, v in zip(*np.unique(lb, return_counts=True)):+ cb[str(k)] = float(v)+ s = float(np.clip(float(t) * T_SHIFT, 0.0, 1.0))+ w = np.zeros(len(types))+ share = np.zeros(len(types))+ for i, k in enumerate(types):+ pa = ca[k] / na+ pb = cb[k] / nb+ if pa <= 0 and pb <= 0:+ continue+ if QUOTA_MODE == "count":+ w[i] = ((ca[k] + 1.0) ** (1.0 - s)) * ((cb[k] + 1.0) ** s)+ elif pa <= 0:+ w[i] = (pb + EPS) ** s+ elif pb <= 0:+ w[i] = (pa + EPS) ** (1.0 - s)+ else:+ w[i] = (pa ** (1.0 - s)) * (pb ** s)+ share[i] = 1.0 if pa <= 0 else (0.0 if pb <= 0 else s)+ s = w.sum()+ fracs = w / s if s > 0 else np.full(len(types), 1.0 / len(types))+ return types, fracs, share+++def alloc_counts(fracs, pools, n):+ """Largest-remainder allocation of n cells to types, capped by pool sizes."""+ fracs = np.asarray(fracs, dtype=np.float64)+ pools = np.asarray(pools, dtype=np.int64)+ counts = np.minimum(np.floor(fracs * n).astype(np.int64), pools)+ rem = int(n - counts.sum())+ if rem > 0:+ order = np.argsort(-(fracs * n - np.floor(fracs * n)))+ guard = 0+ while rem > 0 and guard < 4 * len(fracs) + 8:+ progressed = False+ for i in order:+ if rem <= 0:+ break+ if counts[i] < pools[i]:+ counts[i] += 1+ rem -= 1+ progressed = True+ if not progressed:+ break+ guard += 1+ return counts+++def block_neighbors(coords, k):+ """For every cell: indices of its k nearest spatial neighbors (self included)."""+ tree = cKDTree(coords)+ kk = int(min(max(k, 1), len(coords)))+ _, nb = tree.query(coords, k=kk)+ return np.atleast_2d(nb)+++def block_sample(coords, nb, want, rng):+ """Draw random seeds; take seed+neighbors as a block; dedupe until `want`."""+ n = len(coords)+ want = int(min(want, n))+ if want <= 0:+ return np.array([], dtype=np.int64)+ if want >= n:+ return np.arange(n)+ taken = np.zeros(n, dtype=bool)+ order = rng.permutation(n)+ pos = 0+ picked = []+ got = 0+ while got < want:+ if pos >= n:+ # every cell used as a seed once; extend with remaining members+ rest = np.flatnonzero(~taken)+ if rest.size == 0:+ break+ add = rng.choice(rest, size=min(want - got, rest.size), replace=False)+ picked.append(add)+ got += len(add)+ break+ seed = order[pos]+ pos += 1+ if taken[seed]:+ continue+ members = nb[seed]+ members = members[~taken[members]]+ if members.size == 0:+ continue+ if got + members.size > want and got > 0:+ keep = want - got+ if keep < members.size:+ members = rng.choice(members, size=keep, replace=False)+ taken[members] = True+ picked.append(members)+ got += len(members)+ if not picked:+ return np.array([], dtype=np.int64)+ out = np.concatenate(picked).astype(np.int64)+ return out[:want]+++def main():+ ap = argparse.ArgumentParser()+ ap.add_argument("--data", required=True)+ ap.add_argument("--out", required=True)+ ap.add_argument("--seed", type=int, default=0)+ args = ap.parse_args()++ import json++ view = Path(args.data)+ manifest = json.loads((view / "manifest.json").read_text(encoding="utf-8"))+ genes = [+ ln.strip()+ for ln in (view / manifest["genes_file"]).read_text(encoding="utf-8").splitlines()+ if ln.strip()+ ]++ from src.task2_spatial.view_io import interp_bracket, read_stage, write_t2++ lo = int(manifest["min_cells"])+ hi = int(manifest["max_cells"])+ a_entry, b_entry, t = interp_bracket(manifest)++ rng = np.random.default_rng(int(args.seed))++ stage_a = read_stage(str(view), a_entry, genes)++ if b_entry is None:+ # Not bracketed: neighborhood-block subsample of the anchor stage.+ n = int(np.clip(stage_a.n, lo, hi))+ if n >= stage_a.n:+ idx = np.arange(stage_a.n)+ else:+ nb = block_neighbors(stage_a.coords, max(K_BLOCK, 1))+ idx = block_sample(stage_a.coords, nb, n, rng)+ expr = np.asarray(stage_a.X[idx].todense(), dtype=np.float32)+ coords = np.asarray(stage_a.coords[idx], dtype=np.float64)+ coords = scale_to_rms(coords, rms_radius(stage_a.coords))+ write_t2(args.out, expr, coords, genes, seed=int(args.seed))+ return++ stage_b = read_stage(str(view), b_entry, genes)+ t = float(t)+ n = int(round(log_interp(max(stage_a.n, 1), max(stage_b.n, 1), t)))+ n = int(np.clip(n, lo, hi))++ target_rms = log_interp(rms_radius(stage_a.coords), rms_radius(stage_b.coords), t)+ coords_a, coords_b = stage_a.coords, stage_b.coords+ if DO_ALIGN:+ from src.task2_spatial.frame import align_pair++ ca, cb, _info = align_pair(+ stage_a.coords, stage_b.coords, stage_a.labels, stage_b.labels, "procrustes"+ )+ coords_a = scale_to_rms(ca, target_rms)+ coords_b = scale_to_rms(cb, target_rms)++ types, fracs, share_b = type_quotas(stage_a.labels, stage_b.labels, t)+ la = np.asarray(stage_a.labels).astype(str)+ lb = np.asarray(stage_b.labels).astype(str)+ pools_a = np.array([(la == k).sum() for k in types], dtype=np.int64)+ pools_b = np.array([(lb == k).sum() for k in types], dtype=np.int64)+ pools = pools_a + pools_b+ counts = alloc_counts(fracs, pools, n)+ b_counts = np.minimum(np.round(share_b * counts).astype(np.int64), pools_b)+ a_counts = np.minimum(counts - b_counts, pools_a)+ # fix rounding overflow: push back to the other side where possible+ short = counts - a_counts - b_counts+ for i in np.flatnonzero(short > 0):+ room_a = pools_a[i] - a_counts[i]+ add = int(min(short[i], room_a))+ a_counts[i] += add+ short[i] -= add+ room_b = pools_b[i] - b_counts[i]+ add = int(min(short[i], room_b))+ b_counts[i] += add+ short[i] -= add+ n_a = int(a_counts.sum())+ n_b = int(b_counts.sum())+ total = n_a + n_b+ if total > n: # trim from the larger side proportionally (rare)+ excess = total - n+ cut_b = int(min(excess, n_b))+ n_b -= cut_b+ excess -= cut_b+ n_a -= int(min(excess, n_a))++ import sys++ print(+ f"[info] t={t:.3f} n={n} n_a={n_a} n_b={n_b} quota={QUOTA_MODE} align={DO_ALIGN} k={K_BLOCK} target_rms={target_rms:.1f}",+ file=sys.stderr,+ )++ k = max(K_BLOCK, 0)+ if k > 0:+ nb_a = block_neighbors(coords_a, k + 1)+ nb_b = block_neighbors(coords_b, k + 1)+ idx_a = block_sample(coords_a, nb_a, n_a, rng)+ idx_b = block_sample(coords_b, nb_b, n_b, rng)+ else:+ idx_a = rng.choice(stage_a.n, size=n_a, replace=False) if n_a else np.array([], dtype=np.int64)+ idx_b = rng.choice(stage_b.n, size=n_b, replace=False) if n_b else np.array([], dtype=np.int64)+ idx_a = np.sort(idx_a)+ idx_b = np.sort(idx_b)++ parts_x = []+ parts_c = []+ if idx_a.size:+ parts_x.append(np.asarray(stage_a.X[idx_a].todense(), dtype=np.float32))+ parts_c.append(np.asarray(coords_a[idx_a], dtype=np.float64))+ if idx_b.size:+ parts_x.append(np.asarray(stage_b.X[idx_b].todense(), dtype=np.float32))+ parts_c.append(np.asarray(coords_b[idx_b], dtype=np.float64))+ expr = np.vstack(parts_x) if len(parts_x) > 1 else parts_x[0]+ coords = np.vstack(parts_c) if len(parts_c) > 1 else parts_c[0]++ coords = scale_to_rms(coords, target_rms)++ write_t2(args.out, expr, coords.astype(np.float32), genes, seed=int(args.seed))+++if __name__ == "__main__":+ main()
调研来源?调研员查到并用到的知识条目和文献检索结果(只列标题和编号)。
用到的知识库条目
| 编号 | 标题 | 出处 |
|---|---|---|
| k023 | Time-split validation with a held-out intermediate or next time point | notes/handover/02_知识学习路线.md |
| k007 | Interval staging and held-out-window filtering of external data | notes/official/来件/virtualembryo.ai/rules.md |
| k024 | World-model evaluation dimensions for state-transition predictors | notes/competition/07_biomedical_world_models.md |
分析结果?分析员写的 ANALYSIS.json:改了什么、各组分数怎么变、假设是否成立、经验和下一步建议。
| 改了什么 | 从零实现 T2HI-04 邻域块抽样:logit 型组成插值定两侧配额,以「种子+15 空间近邻」块为单元混抽括号两侧真实细胞(表达/坐标原样,procrustes 对齐,整云缩放到 log 插值 RMS),带 T2HI_BLOCK_K=0 关闭机制对照。 |
|---|---|
| 各组分数的变化 | cell_state:噪声内:+0.01;mmd_u 0.0322→0.0326(-0.03)、variogram 0.0254→0.0251(+0.03),互抵。 expression_change:噪声内/略升:组 +0.86,proxy 分解 de_score raw 0.330→0.362(得分 +0.15)、de_direction 0.516→0.527(得分 +0.06),单项变化均小于 T2 约 1 分的噪声。 local_spatial:变好,远超噪声:+8.78(54.03→62.81);neighborhood_mmd raw 0.0829→0.0579,skill 0.540→0.628,得分 13.51→15.70(+2.19)。 shape_scale:变坏:-2.20(53.37→51.17),主要来自 occupancy_dice raw 0.8342→0.8123(skill 0.553→0.502,得分 -0.43,已贴地板 4.17),d2_shape 0.0401→0.0424(得分 -0.12),scale_log_ratio 不变;触及 PLAN 预设的『local 升但 shape 降>2』警戒线。 |
| family_id | T2HI-04 |
| 假设是否成立 | 是 |
| 经验 |
|
| mechanism_active | 是 |
| 下一步建议 |
|
对话摘要?每个角色和大模型对话的统计:轮数、工具调用、用时、token 数和最后的回答摘录;原始记录只给路径。
只给统计和最后回答的摘录;完整对话请到原始记录位置里列出的文件看。
分析员
| 角色?调研员写计划、工程师改代码、分析员解读分数、审查员检查作弊。 | 分析员 alibaba-token-plan-cn/qwen3.8-max |
|---|---|
| 调用次数 | 1 次 |
| 轮数?大模型一共回复了几轮(每轮可以调用多个工具)。 | 3 |
| 工具调用?大模型调用读文件、执行命令等工具的次数,按工具名统计。 | 共 2 次:bash 1、write 1 |
| 用时 | 1 分 |
| token 数?输入 / 输出(含思考)的 token 数,来自接口返回的用量。 | 输入 16,190 · 输出 1,279 · 思考 2,022 |
| 任务(第一行) | 分析节点 n23 的打分结果,写 ANALYSIS.json |
| 最后的回答(摘录) | ANALYSIS.json written |
| 原始记录位置?完整对话记录在 Spark 上的文件路径;看板不复制原始内容。 | /home/spark-longxinyang/vec/runs/formal/20261003-094241-search-t2-heart-interp-g24-D-s2/nodes/23/analyst.jsonl 7 KB /home/spark-longxinyang/vec/runs/formal/20261003-094241-search-t2-heart-interp-g24-D-s2/nodes/23/analyst.stderr |
工程师
| 角色 | 工程师 alibaba-token-plan-cn/qwen3.8-max |
|---|---|
| 调用次数 | 1 次 |
| 轮数 | 32 |
| 工具调用 | 共 38 次:bash 22、edit 8、read 5、write 3 |
| 用时 | 20 分 |
| token 数 | 输入 81,249 · 输出 12,714 · 思考 22,591 |
| 任务(第一行) | 按计划修改程序:T2HI-04 邻域块抽样:保局部结构的空间混抽 |
| 最后的回答(摘录) | 完成。总结: **做了什么**:从零实现 PLAN T2HI-04(邻域块抽样)——logit 型组成插值定类型/侧配额,以「种子+15 空间近邻」块为抽样单元混抽括号两侧真实细胞(表达/坐标原样,procrustes 对齐帧,整云缩放到 log 插值 RMS),机制开关 `T2HI_BLOCK_K`(默认 15,=0 为对照),单输入阶段有退路,seed 确定(已验证逐位复现),vec-check 通过。 **查分结果**(A 半,7/20 次):默认配置(count 配额, k=15, align)**61.20**(cell_state 65.88 / expr 64.89 / local 62.17 / shape 51.88)。对照 k=0:57.90,local 50.67 → 块机制使 nbd raw 0.093→0.058–0.065、local_spatial +9~11.5、同侧 15-NN 比例 0.73→0.91,机制生效。eps 配额 60.43;配额指数偏移 s=0.25/0.6 均更差。 **下一步最值得试**:cell_state/expression 是与节点 10/13/17(65–66.6)的主要差距——组成配额是杠杆(试对 B 侧新兴心脏类型上权或复现节点 10 的确切 logit 组成);其次把节点 13 的同型 kNN 收缩叠加到块抽样输出上(两者作用于 nbd 的机制不同,可能可叠加),以及 k_block=25–30 敏感性。 |
| 原始记录位置 | /home/spark-longxinyang/vec/runs/formal/20261003-094241-search-t2-heart-interp-g24-D-s2/nodes/23/engineer.jsonl 198 KB /home/spark-longxinyang/vec/runs/formal/20261003-094241-search-t2-heart-interp-g24-D-s2/nodes/23/engineer.stderr |
调研员
| 角色 | 调研员 alibaba-token-plan-cn/qwen3.8-max |
|---|---|
| 调用次数 | 2 次 |
| 轮数 | 2 |
| 工具调用 | 共 0 次:— |
| 用时 | 2 分 |
| token 数 | 输入 12,338 · 输出 2,278 · 思考 3,352 |
| 任务(第一行) | 为节点 n23(草稿,父节点 nNone)写改进计划 PLAN.json,思路 T2HI-04 |
| 最后的回答(摘录) | {
"name": "T2HI-04 邻域块抽样:保局部结构的空间混抽",
"motivation": "当前最佳节点17(66.60)的 local_spatial=70.32 依赖后处理 kNN 收缩(k=20, α=1.25)来修复独立抽样破坏的邻域配对。节点2(纯mix)local_spatial仅54.03,节点10(logit mix)65.80,说明独立抽样本身破坏邻域。T2HI-04 直接在抽样阶段保持空间邻域完整性,避免先破坏再修复。形状组51.86仍贴地板,邻域skill若>0.5则门=1不额外加分,但推高neighborhood_mmd本身值25分。",
"approach": "步骤:1) 读入两阶段数据,构建各自空间坐标的 kNN 图(k_block=15,与评分邻域定义对齐)。2) 用节点10相同的 logit 组成插值确定两侧细胞配额(t=0.5 时各50%)。3) 块抽样:从阶段A随机选种子细胞,取其 k_block 个空间近邻为一个块;阶段B同理。按配额决定从A、B各取多少块。若同一细胞被多个块选中,保留首次出现(去重)。若去重后不足目标细胞数,继续补抽新块。4) 输出所有被选中细胞的原始表达和原始坐标,不做任何位移或插值。5) 全云坐标 scale 到目标 RMS(与 copy_last 相同的 scale_to_rms)。6) 关闭机制对照:k_block=0 时每个块只含种子细胞本身,退化为独立细胞抽样(等价于节点10的mix),输出应逐位相同。关键参数:k_block=15(固定,与评分15-NN对齐);若细胞数不足可降到 k_block=10。vec-score 快速筛选:先跑 k_block=0(对照)和 k_block=15 两个配置,比较 neighborhood_mmd raw 值(对照预期~0.051,块抽样预期<0.045)。单输入阶段退路:若只有一个阶段(T1 proxy),所有块都从该阶段取,k_block 不变,组成配额无意义(全来自同一侧),等价于带邻域约束的 copy_last。",
"expected_groups": ["local_spatial", "shape_scale"],
"risks": "1) 块重叠导致去重后细胞数不足或多样性下降——Engineer 应检查输出细胞数是否达标,不足时减小 k_block 或允许有放回抽块。2) 邻域块来自单一阶段,若目标时间点的真实邻域组成与任一输入阶段差异大,块结构可能不匹配——但这是插值任务的固有限制,mix 家族都面临。3) 形状组可能因块内坐标来自同一阶段、块间缺乏过渡而略降——用 d2_shape raw 监控,若 >0.05 则考虑块间加微小 jitter。4) 30分钟时限:实现本身简单(无优化循环),主要时间花在调试细胞数和 vec-score 查分;先小样本验证再全量。",
"family_id": "T2HI-04",
"mechanism": "以空间邻域块(种子+15近邻)为抽样单元而非独立细胞,保持块内相对坐标和表达配对不变,使输出天然保留局部表达-空间一致性。",
"vs_constant_shift": "常数位移对每个细胞施加相同向量,不改变邻域关系但也不修复已破坏的配对;本机制在抽样阶段就保证输出的细胞原本就是空间近邻,邻域内的表达梯度是真实的,不是事后位移能模拟的。",
"mechanism_evidence": "1) neighborhood_mmd raw 应从对照(k=0)的~0.051 降到 <0.045;2) 输出中随机取100个细胞,检查其15-NN中来自同一块的比例应>60%(对照约15%);3) cell_state 和 expression_change 组分应与对照持平(±1分内),证明机制不干扰表达分布;4) 若 local_spatial 提升但 shape_scale 下降>2分,说明块间排列有问题。",
"mechanism_off_control": "设 k_block=0(环境变量 T2HI_BLOCK_K=0),每个抽样单元只含一个细胞,退化为独立细胞混抽。预期:输出与节点10等价(logit mix),neighborhood_mmd raw 回到~0.051,local_spatial 回到~65-66。若 k=0 和 k=15 输出逐位相同,说明块抽样代码未生效。",
"sources": []
} |
| 原始记录位置 | /home/spark-longxinyang/vec/runs/formal/20261003-094241-search-t2-heart-interp-g24-D-s2/nodes/23/researcher.jsonl 5 KB /home/spark-longxinyang/vec/runs/formal/20261003-094241-search-t2-heart-interp-g24-D-s2/nodes/23/researcher.1.jsonl 5 KB /home/spark-longxinyang/vec/runs/formal/20261003-094241-search-t2-heart-interp-g24-D-s2/nodes/23/researcher.stderr |