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

总览 · ← 返回运行 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)

  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):

配置boardcell_stateexpr_changelocal_spatialshapenbd raw
对照 k=0(eps 配额,align)57.9064.7764.0350.6752.110.0932
机制开 k=15(eps 配额,align)60.4364.8964.7259.7252.410.0648
机制开 k=15(count 配额,提交默认)61.2065.8864.8962.1751.880.0585
k=15 count 配额 s=0.25 / s=0.657.62 / 58.5958.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()

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

用到的知识库条目

编号标题出处
k023Time-split validation with a held-out intermediate or next time pointnotes/handover/02_知识学习路线.md
k007Interval staging and held-out-window filtering of external datanotes/official/来件/virtualembryo.ai/rules.md
k024World-model evaluation dimensions for state-transition predictorsnotes/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_idT2HI-04
假设是否成立是
经验
  1. 在 mix 家族中把抽样单元从独立细胞换成空间块(种子+k近邻、去重、k=15 与评分邻域定义对齐),neighborhood_mmd raw 从 0.083/0.093 降到 0.058,local_spatial +8.8——保持真实组织的局部表达-坐标配对比重建它便宜得多。
  2. 块抽样有形状代价:occupancy_dice 0.834→0.812、shape_scale 组 -2.2(本已贴地板),块内坐标同源、块间缺过渡会轻微损害占据形状;做块类方法时要同时盯 occupancy_dice。
  3. 关闭机制对照(k=0,同 seed)证明机制真实生效:输出逐位不同,同侧 15-NN 比例 0.73→0.91,nbd raw 0.093→0.058;这种『开关+配对结构统计量』的对照设计可复用。
  4. 两种 logit 组成配额(count 几何均值、eps 伪计数)都只把 cell_state/expression_change 做到 65.9/64.9,达不到节点 10/13/17 的 ~72:mix 家族的表达侧瓶颈在组成配额本身,与抽样单元无关;块抽样节点总分 61.36 仍低于树内最佳 66.6。
  5. 配额指数偏移 s=t·shift 扫到 0.25/0.6 都更差(57.6/58.6),s=t 即 PLAN 原设定最优,不要在该维度上再扫。
mechanism_active是
下一步建议
  1. 针对 local_spatial:把节点 13 的同型 kNN 收缩(k=20, α=1.25 附近)叠加到块抽样输出上——两者作用于 neighborhood_mmd 的机制不同(抽样保配对 vs 后处理收配对),可能可加性,先用 vec-score 对比块抽样单独/叠加两配置。
  2. 针对 expression_change/cell_state:修组成配额——对 B 侧新兴类型做单纯形投影上权,或直接移植节点 10 的确切 logit 组成代码到块抽样骨架上;这是与树内最佳 65-66.6 的主要差距来源(约 6 分)。
  3. 针对 shape_scale:occupancy_dice 已贴地板,试 k_block=25-30 敏感性以及块间空间排布(对齐方式 procrustes vs 不对齐、两侧块交错选取),目标把 occupancy_dice raw 拉回 ≥0.83;后续节点应以节点 17(66.60)而非节点 2 为基座。

对话摘要?每个角色和大模型对话的统计:轮数、工具调用、用时、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