总览 · ← 返回运行 20261003-093415-search-t1-r2-D-s1
节点 n8
高斯VAE潜空间+跨阶段1-NN耦合训练CFM位移场,从末阶段潜位置外推T=(Δt目标/Δt训练)步,残差=dec(z_pred)−dec(z)加回输入(软零门、±2截断、范数截断1.5×耦合中位数),λ=0.5阻尼;单输入阶段回退为均匀复制。
| 运行?一次完整的自动搜索或 Agent 会话,有自己的锁定配置和证据包。 | 20261003-093415-search-t1-r2-D-s1 |
|---|---|
| 父节点 | (种子,没有父节点) |
| 子节点 | n12 |
| 操作?种子:人写的起点;改进:在父节点上改;草稿:从头写;修复:修父节点的报错。 | 草稿 |
| 状态 | 已打分 |
| 分数 | 搜索目标分 48.75 · X3 46.75 · proxy10 52.75 |
| 审查 | 未审查 |
| 用时?从运行开始到结束(或到现在)的挂钟时间。 | 38 分 |
| 程序版本 | adf9221b975ce7f568054107a51463cec0e35b8a (programs.git) |
方法说明?节点程序自带的 METHOD.md:这个程序做了什么、为什么。
来自 programs.git adf9221b97:solution/METHOD.md
高斯VAE潜空间+跨阶段1-NN耦合训练CFM位移场,从末阶段潜位置外推T=(Δt目标/Δt训练)步,残差=dec(z_pred)−dec(z)加回输入(软零门、±2截断、范数截断1.5×耦合中位数),λ=0.5阻尼;单输入阶段回退为均匀复制。
方法族与机制(family: generative_latent)
- VAE:encoder/decoder 512-512,latent d=20,stage 作 one-hot 批次协变量,KL 权重 1e-3,高斯损失(数据为 log1p(CP10k),无 counts,故非 scVI)。HVG=2500(scanpy flavor='seurat';scikit-misc 不可用,禁 seurat_v3),非 HVG 基因直接复制输入。
- 耦合:对后一输入阶段每个细胞在前一阶段潜空间取 1-NN,得 (z_src, z_tgt) 对;CFM(MLP 128×2,t 拼接输入)回归速度场 v(z_t,t)≈z_tgt−z_src。
- 外推:只用时间差 T=(t_target−t_last)/(t_last−t_prev),t 夹在 [0,1],Euler 积分,位移×λ=0.5 并截断到 1.5×耦合位移中位数。
- 机制生效证据(X3,E8.75→E9.0,2174 对):耦合位移中位数 11.52,sd 5.21(型内离散度显著非零,非每型常数差);λ=1 时预测位移均值 17.1(被截断);实际改变:约 11% 的 HVG 条目非零残差,每细胞平均 |dx|≈0.11(log 空间)。位移依赖潜位置(同型细胞位移不同)。
- 对照(--off ≡ λ=0):跳过场,z_pred=z。第一版残差用 dec(z_pred)−x(off=重建),X3 off=45.37、on(λ1)=45.40、de_score 两者完全相同(−0.2571)→ 场对基因排序贡献≈0,而解码器重建本身跌破 copy_last(47.92),主要砸 covariation(41.5) 与 de。据此改为残差 = dec(z_pred)−dec(z)(off 时输出=精确复制),把解码偏差从输出中消掉,只保留场在基因空间的像。
已验证 / 未验证
- 已验证:X3 跑通、vec-check 通过(652 细胞);proxy(单输入)走复制回退,vec-check 通过(5118 细胞);seed 确定(default_rng/torch.manual_seed,无全局随机依赖)。第一版残差的 off/on X3 查分各 1 次。
- 时间耗尽前来不及对第二版残差做完整 λ 扫描;λ 默认取保守 0.5(外推方向押错时四组齐降的风险,见 PLAN risks #3)。
- 未验证:第二版残差在 X3 的最终分数是否超过 copy_last;proxy10 分数(=均匀复制水平,节点 5 显示此回退不吃亏)。
- 已知局限:只有一次转移,场弱辨识;文献先验(k030)警告潜动力学向均值坍缩,故强阻尼+截断。
知识来源
- scVI(10.1038/s41592-018-0229-2)、scvi-tools(10.1038/s41587-021-01206-w):VAE 结构与 stage 协变量用法(仅方法,无预训练权重)。
- CFGen(arXiv:2407.11734):潜空间条件流匹配生成细胞;本实现为自写的等价 CFM 损失(t~U[0,1],线性插值,回归耦合差)。
- 未使用任何保留阶段/保留基因型的测量数据;未读 external/(proxy 为空,X3 的 external 与输入重复,未使用);未使用 uns.celltype_palette。程序不读视图路径/名称、不读绝对时间(只用时间差),视图无关。
调研员的计划
| 名称 | VAE潜空间+CFM局部位移场+稀疏残差解码(generative_latent 最小版) |
|---|---|
| 动机 | de_recovery 是全树最弱组:榜首节点 3 仅 49.78(direction 57.77 / covariation 54.21 / cell_state 53.21),说明现有方案(组成重加权、OT 常数位移)缺少细胞状态相关的基因程序变化。节点 6 的 graph_fate 四组全崩(总 47.66,X3 45.72),证明随机游走式搬细胞破坏分布与共变,不能再走;节点 5 证明解码/表示改造仍有空间(cell_state +5.09)。X3 权重为 2,是主要优化对象。文献库 k030 警告:只有两个快照、一次转移,潜空间动力学只能弱辨识、且会向均值坍缩,所以场必须带阻尼并与 copy_last 对照,允许如实失败。 |
| 做法 | 步骤:(1) 读视图(不区分视图身份),取两个输入阶段;HVG 用 scanpy flavor='seurat'(k035:scikit-misc 在 aarch64 编译失败,禁 seurat_v3),2,500 个,训练基因=HVG,输出按视图 genes.txt 补齐非 HVG。(2) 高斯 VAE(数据是 log1p(CP10k) 无 counts,如实称 Gaussian VAE 而非 scVI):encoder 512-512,latent d=20(搜索 10/20/30),decoder 512-512,stage 作 batch 协变量防止把阶段差异当批次抹掉,KL 权重 1e-3,先在 ≤2,000 细胞子集、后 ≤4,000 全量,epochs 上限 100,训练时限 8 分钟;推理用均值(确定性、不采样)。(3) 潜空间耦合:对 E9.5 每个细胞在 E8.5 潜空间取 1-NN(或对称 3-NN 均值),得到配对 (z_src, z_tgt);这是位移的数据,不另做全量 OT。(4) 用 torchcfm 训练小 MLP 场 f(z)(128×2 层):条件流匹配损失 ||v_θ(z_t,t)−(z_tgt−z_src)||,t~U[0,1],z_t 线性插值;时限 4 分钟。(5) 预测:以最后一个输入阶段为起点,z_pred=z+λ·f(z),λ 阻尼初值 1.0、搜索 {0.5,0.75,1.0};欧氏步长截断在训练耦合位移中位数的 1.5 倍。(6) 残差解码:x̂=decoder(z_pred);输出 = 输入 + clip(x̂−x_enc, ±2.0);对基因施加软零门 g=sigmoid((x̂−0.05)/0.05),输入为 0 的基因输出按 g 缩小(允许少量从 0 变非 0),非 HVG 直接复制输入。不做任何全局缩放或组成重加权。(7) 单输入阶段退路(如视图只给一个阶段):场无法训练,直接原样复制最后阶段(节点 5 已验证该退路不吃亏),并把该分支写进代码而非按视图名判断。快速筛选:先跑 --recon-only(场关)在 X3 查 1 次,若低于 copy_last 参考水平就启用基因级回退(重建偏差大的基因直接复制输入);再按权重顺序查 X3→proxy10,λ 只挑 2-3 个值,总查分 ≤8 次留余量给复跑确认(噪声约 2 分,单次 +2 以内不算数)。 |
| 风险 | 1) 解码器重建本身就跌破 copy_last(高斯均值+软门会侵蚀零值与方差,直接砸 mmd_u/variogram):Engineer 第一步就跑 recon-only 查分,跌破即启用基因级复制回退,仍跌破则本节点如实报告解码失败。2) 只有一次转移,场不可辨识:on/off 输出几乎相同即机制未运行,如实报告,不调参硬凑。3) 场在 X3 上外推方向押错(k030/方向库:押错方向四组一起降):先单独查 X3,四组全降则把 λ 压到 0.5 再试一次,仍不行即失败。4) 均值坍缩:潜在均值预测把细胞拉向平均,检查潜空间方差与零值率。5) 训练超时:严格按子集先行、epoch/秒数双上限。 |
代码改动?这个节点的程序和父节点程序的逐行差别:绿色是新增,红色是删除。
对比:这个提交的上一版(种子程序:相对空仓库)。改动的文件:solution/EXECUTION.json +1 −0、solution/METHOD.md +22 −0、solution/run.py +277 −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..ab98dbf--- /dev/null+++ b/solution/METHOD.md@@ -0,0 +1,22 @@+高斯VAE潜空间+跨阶段1-NN耦合训练CFM位移场,从末阶段潜位置外推T=(Δt目标/Δt训练)步,残差=dec(z_pred)−dec(z)加回输入(软零门、±2截断、范数截断1.5×耦合中位数),λ=0.5阻尼;单输入阶段回退为均匀复制。++## 方法族与机制(family: generative_latent)++- VAE:encoder/decoder 512-512,latent d=20,stage 作 one-hot 批次协变量,KL 权重 1e-3,高斯损失(数据为 log1p(CP10k),无 counts,故非 scVI)。HVG=2500(scanpy flavor='seurat';scikit-misc 不可用,禁 seurat_v3),非 HVG 基因直接复制输入。+- 耦合:对后一输入阶段每个细胞在前一阶段潜空间取 1-NN,得 (z_src, z_tgt) 对;CFM(MLP 128×2,t 拼接输入)回归速度场 v(z_t,t)≈z_tgt−z_src。+- 外推:只用时间差 T=(t_target−t_last)/(t_last−t_prev),t 夹在 [0,1],Euler 积分,位移×λ=0.5 并截断到 1.5×耦合位移中位数。+- 机制生效证据(X3,E8.75→E9.0,2174 对):耦合位移中位数 11.52,sd 5.21(型内离散度显著非零,非每型常数差);λ=1 时预测位移均值 17.1(被截断);实际改变:约 11% 的 HVG 条目非零残差,每细胞平均 |dx|≈0.11(log 空间)。位移依赖潜位置(同型细胞位移不同)。+- 对照(--off ≡ λ=0):跳过场,z_pred=z。第一版残差用 dec(z_pred)−x(off=重建),X3 off=45.37、on(λ1)=45.40、de_score 两者完全相同(−0.2571)→ 场对基因排序贡献≈0,而解码器重建本身跌破 copy_last(47.92),主要砸 covariation(41.5) 与 de。据此改为残差 = dec(z_pred)−dec(z)(off 时输出=精确复制),把解码偏差从输出中消掉,只保留场在基因空间的像。++## 已验证 / 未验证++- 已验证:X3 跑通、vec-check 通过(652 细胞);proxy(单输入)走复制回退,vec-check 通过(5118 细胞);seed 确定(default_rng/torch.manual_seed,无全局随机依赖)。第一版残差的 off/on X3 查分各 1 次。+- 时间耗尽前来不及对第二版残差做完整 λ 扫描;λ 默认取保守 0.5(外推方向押错时四组齐降的风险,见 PLAN risks #3)。+- 未验证:第二版残差在 X3 的最终分数是否超过 copy_last;proxy10 分数(=均匀复制水平,节点 5 显示此回退不吃亏)。+- 已知局限:只有一次转移,场弱辨识;文献先验(k030)警告潜动力学向均值坍缩,故强阻尼+截断。++## 知识来源++- scVI(10.1038/s41592-018-0229-2)、scvi-tools(10.1038/s41587-021-01206-w):VAE 结构与 stage 协变量用法(仅方法,无预训练权重)。+- CFGen(arXiv:2407.11734):潜空间条件流匹配生成细胞;本实现为自写的等价 CFM 损失(t~U[0,1],线性插值,回归耦合差)。+- 未使用任何保留阶段/保留基因型的测量数据;未读 external/(proxy 为空,X3 的 external 与输入重复,未使用);未使用 uns.celltype_palette。程序不读视图路径/名称、不读绝对时间(只用时间差),视图无关。diff --git a/solution/run.py b/solution/run.pynew file mode 100644index 0000000..79e0971--- /dev/null+++ b/solution/run.py@@ -0,0 +1,277 @@+"""VAE latent space + conditional flow matching displacement field + sparse residual decode.++View-agnostic: depends only on view data (expression, stage time differences) and --seed.+Single-input fallback: uniform copy of the last input stage (no field can be trained).+"""+import argparse+import json+import os+import time++import numpy as np+++def log(*a):+ print(*a, flush=True)+++def read_view(data_dir):+ with open(os.path.join(data_dir, "manifest.json")) as f:+ man = json.load(f)+ genes = open(os.path.join(data_dir, man["genes_file"])).read().split()+ inputs = sorted(man["inputs"], key=lambda e: e["time"])+ return man, genes, inputs+++def load_stage(data_dir, entry):+ import anndata as ad+ a = ad.read_h5ad(os.path.join(data_dir, entry["path"]))+ X = a.X+ if not isinstance(X, np.ndarray):+ X = X.toarray()+ return np.asarray(X, dtype=np.float32)+++def pick_hvg(Xs, seed, n_top=2500):+ import anndata as ad+ import scanpy as sc+ Xc = np.vstack(Xs)+ rng = np.random.default_rng(seed)+ if Xc.shape[0] > 6000:+ idx = rng.choice(Xc.shape[0], 6000, replace=False)+ Xc = Xc[idx]+ a = ad.AnnData(X=Xc.copy())+ sc.pp.highly_variable_genes(a, flavor="seurat", n_top_genes=n_top)+ return np.flatnonzero(a.var["highly_variable"].values)+++def make_vae(n_in, n_stage, d_lat, seed):+ import torch+ import torch.nn as nn+ torch.manual_seed(seed)++ class Enc(nn.Module):+ def __init__(self):+ super().__init__()+ self.net = nn.Sequential(nn.Linear(n_in + n_stage, 512), nn.ReLU(),+ nn.Linear(512, 512), nn.ReLU())+ self.mu = nn.Linear(512, d_lat)+ self.lv = nn.Linear(512, d_lat)++ def forward(self, x, s):+ h = self.net(torch.cat([x, s], 1))+ return self.mu(h), self.lv(h).clamp(-8, 2)++ class Dec(nn.Module):+ def __init__(self):+ super().__init__()+ self.net = nn.Sequential(nn.Linear(d_lat + n_stage, 512), nn.ReLU(),+ nn.Linear(512, 512), nn.ReLU(),+ nn.Linear(512, n_in), nn.ReLU())++ def forward(self, z, s):+ return self.net(torch.cat([z, s], 1))++ return Enc(), Dec()+++def train_vae(Xh, stage_id, n_stage, d_lat, seed, t_budget):+ import torch+ torch.manual_seed(seed)+ enc, dec = make_vae(Xh.shape[1], n_stage, d_lat, seed)+ Xt = torch.from_numpy(Xh)+ S = torch.zeros(Xh.shape[0], n_stage)+ S[torch.arange(Xh.shape[0]), torch.from_numpy(stage_id)] = 1.0+ opt = torch.optim.Adam(list(enc.parameters()) + list(dec.parameters()), lr=1e-3)+ rng = np.random.default_rng(seed + 1)+ n = Xt.shape[0]+ bs = 256+ t0 = time.time()+ ep = 0+ while ep < 100 and time.time() - t0 < t_budget:+ perm = rng.permutation(n)+ tot = 0.0+ for i in range(0, n, bs):+ b = torch.from_numpy(perm[i:i + bs])+ xb, sb = Xt[b], S[b]+ mu, lv = enc(xb, sb)+ z = mu + torch.randn_like(mu) * (0.5 * lv).exp()+ rec = dec(z, sb)+ loss = ((rec - xb) ** 2).sum(1).mean() + 1e-3 * (-0.5 * (1 + lv - mu.pow(2) - lv.exp()).sum(1).mean())+ opt.zero_grad()+ loss.backward()+ opt.step()+ tot += float(loss) * len(b)+ ep += 1+ log(f"VAE: {ep} epochs, loss={tot / n:.4f}, {time.time() - t0:.1f}s")+ return enc, dec+++def couple(zs, zt, seed):+ from sklearn.neighbors import NearestNeighbors+ nn = NearestNeighbors(n_neighbors=1).fit(zs)+ _, idx = nn.kneighbors(zt)+ return zs[idx[:, 0]], zt+++def train_cfm(z0, z1, d_lat, seed, t_budget):+ import torch+ import torch.nn as nn+ torch.manual_seed(seed)+ net = nn.Sequential(nn.Linear(d_lat + 1, 128), nn.ReLU(),+ nn.Linear(128, 128), nn.ReLU(),+ nn.Linear(128, d_lat))+ opt = torch.optim.Adam(net.parameters(), lr=1e-3)+ Z0 = torch.from_numpy(z0)+ Z1 = torch.from_numpy(z1)+ U = Z1 - Z0+ rng = np.random.default_rng(seed + 2)+ n = Z0.shape[0]+ bs = 256+ t0 = time.time()+ ep = 0+ while ep < 300 and time.time() - t0 < t_budget:+ perm = rng.permutation(n)+ for i in range(0, n, bs):+ b = torch.from_numpy(perm[i:i + bs])+ tt = torch.rand(len(b), 1)+ zt = (1 - tt) * Z0[b] + tt * Z1[b]+ v = net(torch.cat([zt, tt], 1))+ loss = ((v - U[b]) ** 2).sum(1).mean()+ opt.zero_grad()+ loss.backward()+ opt.step()+ ep += 1+ log(f"CFM: {ep} epochs, loss={float(loss):.4f}, {time.time() - t0:.1f}s")+ return net+++def integrate(net, z, T, lam, max_norm, n_steps=None):+ import torch+ zc = z.clone()+ if n_steps is None:+ n_steps = int(max(4, np.ceil(T * 8)))+ h = T / n_steps+ disp = torch.zeros_like(zc)+ with torch.no_grad():+ for k in range(n_steps):+ tcur = min(k * h, 1.0)+ tt = torch.full((zc.shape[0], 1), tcur)+ v = net(torch.cat([zc, tt], 1))+ step = v * h+ disp = disp + step+ zc = zc + step+ d = lam * disp+ nrm = d.norm(dim=1, keepdim=True)+ scale = torch.clamp(max_norm / (nrm + 1e-8), max=1.0)+ return z + d * scale+++def main():+ ap = argparse.ArgumentParser()+ ap.add_argument("--data", required=True)+ ap.add_argument("--out", required=True)+ ap.add_argument("--seed", type=int, required=True)+ ap.add_argument("--lam", type=float, default=float(os.environ.get("LAM", "0.5")))+ ap.add_argument("--off", action="store_true", default=os.environ.get("VEC_OFF", "0") == "1")+ args = ap.parse_args()+ seed = args.seed+ np.random.seed(seed)+ import torch+ torch.manual_seed(seed)++ man, genes, inputs = read_view(args.data)+ n_genes = len(genes)+ min_cells = int(man.get("min_cells", 1))+ max_cells = int(man.get("max_cells", 10 ** 9))+ t_last = inputs[-1]["time"]+ t_target = man["target"]["time"]++ Xs = [load_stage(args.data, e) for e in inputs]+ X_last = Xs[-1]+ n_last = X_last.shape[0]++ rng = np.random.default_rng(seed)+ n_out = int(min(max(n_last, min_cells), max_cells))+ if n_last >= n_out:+ rows = np.sort(rng.choice(n_last, n_out, replace=False))+ else:+ rows = np.sort(rng.choice(n_last, n_out, replace=True))+ Xout = X_last[rows].copy()++ two_stage = len(inputs) >= 2+ if two_stage and not args.off:+ # time ratios from differences only (view-independent)+ dt_train = inputs[-1]["time"] - inputs[-2]["time"]+ dt_pred = t_target - t_last+ T = float(dt_pred / dt_train) if dt_train > 0 else 1.0+ else:+ T = 1.0++ if two_stage:+ d_lat = 20+ # subsample for training+ caps = 4000+ sub = []+ for X in Xs[-2:]:+ if X.shape[0] > caps:+ idx = rng.choice(X.shape[0], caps, replace=False)+ sub.append(X[idx])+ else:+ sub.append(X)+ hvg = pick_hvg(sub, seed, n_top=2500)+ log(f"HVG: {len(hvg)} genes")+ Xh = np.vstack([s[:, hvg] for s in sub]).astype(np.float32)+ stage_id = np.concatenate([np.full(s.shape[0], i, dtype=np.int64) for i, s in enumerate(sub)])+ enc, dec = train_vae(Xh, stage_id, 2, d_lat, seed, t_budget=300)++ with torch.no_grad():+ zs = enc(torch.from_numpy(sub[0][:, hvg]),+ torch.zeros(sub[0].shape[0], 2).index_fill_(1, torch.tensor([0]), 1.0))[0].numpy()+ zt = enc(torch.from_numpy(sub[1][:, hvg]),+ torch.zeros(sub[1].shape[0], 2).index_fill_(1, torch.tensor([1]), 1.0))[0].numpy()+ z0, z1 = couple(zs, zt, seed)+ med = float(np.median(np.linalg.norm(z1 - z0, axis=1)))+ within = np.std(np.linalg.norm(z1 - z0, axis=1))+ log(f"coupling: median disp={med:.3f}, sd={within:.3f}, n={len(z0)}")+ cfm = train_cfm(z0, z1, d_lat, seed, t_budget=150)++ # encode output rows at last stage, integrate, decode residual+ Xr = Xout[:, hvg].astype(np.float32)+ s_one = torch.zeros(n_out, 2)+ s_one[:, 1] = 1.0+ with torch.no_grad():+ z = enc(torch.from_numpy(Xr), s_one)[0]+ if args.off:+ z_pred = z+ else:+ z_pred = integrate(cfm, z, T, args.lam, 1.5 * med)+ x_hat = dec(z_pred, s_one).numpy()+ recon0 = dec(z, s_one).numpy()++ resid = np.clip(x_hat - recon0, -2.0, 2.0).astype(np.float32)+ # soft zero gate: genes at 0 in input only move if decoder is confident+ expo = np.clip(-(x_hat - 0.05) / 0.05, -60, 60)+ gate = (1.0 / (1.0 + np.exp(expo))).astype(np.float32)+ zero_mask = Xr == 0+ resid[zero_mask] *= gate[zero_mask]+ Xout[:, hvg] = np.maximum(Xr + resid, 0.0)+ moved = np.abs(resid).sum(1)+ log(f"residual: mean |dx| per cell={np.abs(resid).mean():.4f}, frac nonzero changed={np.mean(resid != 0):.4f}")+ log(f"disp norms: mean={float((z_pred - z).norm(dim=1).mean()):.3f}")+ else:+ log("single input stage: uniform copy fallback")++ import anndata as ad+ import scipy.sparse as sp+ obs = {}+ a = ad.AnnData(X=sp.csr_matrix(Xout.astype(np.float32)))+ a.var_names = genes+ if man.get("needs_coords"):+ a.obsm["spatial_3D"] = np.zeros((n_out, 3), dtype=np.float32)+ a.write_h5ad(args.out)+ log(f"wrote {args.out}: {a.shape}")+++if __name__ == "__main__":+ main()
调研来源?调研员查到并用到的知识条目和文献检索结果(只列标题和编号)。
用到的知识库条目
| 编号 | 标题 | 出处 |
|---|---|---|
| k035 | Latent generative models offline: scVI (scvi-tools) and diffusion/flow generators (scDiffusion, CFGen) | 10.1038/s41592-018-0229-2 (scVI); 10.1038/s41587-021-01206-w (scvi-tools); 10.1093/bioinformatics/btae518 (scDiffusion); arXiv:2407.11734 (CFGen) |
| k030 | Why latent world models (JEPA/OPF) are not a T1 starting point | notes/competition/03_solution_landscape.md |
| k032 | Neural ODE population dynamics from snapshots: TrajectoryNet, PRESCIENT, MIOFlow, scNODE | arXiv:2002.04461 (TrajectoryNet); 10.1038/s41467-021-23518-w (PRESCIENT); arXiv:2206.14928 (MIOFlow); 10.1093/bioinformatics/btae393 (scNODE) |
计划里引用的来源
- 10.1038/s41592-018-0229-2 — scVI:VAE 在单细胞上的去噪潜空间与生成解码;因无 counts,本方案改用 log 数据的高斯解码器
- 10.1038/s41587-021-01206-w — scvi-tools:VAE 结构与 stage 协变量用法的参考(只用方法,不加载任何预训练权重)
- arXiv:2407.11734 — CFGen:潜空间条件流匹配生成细胞的先例,本方案用 torchcfm 重建其核心
分析结果?分析员写的 ANALYSIS.json:改了什么、各组分数怎么变、假设是否成立、经验和下一步建议。
| 改了什么 | 全新实现:高斯 VAE(512-512,latent d=20,stage 协变量)+ 跨阶段 1-NN 耦合训练的 CFM 位移场,外推 T=Δt比值 步、λ=0.5 阻尼、位移截断 1.5×耦合中位数,残差 = dec(z_pred)−dec(z)(clip ±2、软零门)加回输入;单输入阶段(proxy10)退路为均匀复制末阶段。对照 node 3 为全新代码。 |
|---|---|
| 各组分数的变化 | cell_state:混合但净变坏 -3.06:proxy10 mmd_u 0.0298→0.0405(得分 -4.02,复制退路差于 node 3 的组成处理);X3 mmd_u 0.0356→0.0335(得分 +0.63,噪声内)。 covariation:变坏 -9.46:X3 variogram 0.001377→0.002022(得分 -2.20,VAE 解码残差破坏基因共变结构);proxy10 variogram 0.001028→0.001233(得分 -1.27,复制退路水平)。 de_recovery:变坏 -3.54:proxy10 de_score 原始值 0.2679→0.0164(得分 -2.48,proxy10 走均匀复制退路,复制水平的 de_score 远低于 node 3 的方法);X3 de_score -0.1818→-0.1948(得分 -0.09,噪声内)。 direction:变坏 -4.98:proxy10 de_direction 0.36→0.1785(得分 -3.15,同样是复制退路所致);X3 de_direction 0.0264→-0.004(得分 -0.29,噪声内)。 |
| family_id | generative_latent |
| 假设是否成立 | 否 |
| 经验 |
|
| mechanism_active | unclear |
| 下一步建议 |
|
对话摘要?每个角色和大模型对话的统计:轮数、工具调用、用时、token 数和最后的回答摘录;原始记录只给路径。
只给统计和最后回答的摘录;完整对话请到原始记录位置里列出的文件看。
分析员
| 角色?调研员写计划、工程师改代码、分析员解读分数、审查员检查作弊。 | 分析员 alibaba-token-plan-cn/qwen3.8-max |
|---|---|
| 调用次数 | 1 次 |
| 轮数?大模型一共回复了几轮(每轮可以调用多个工具)。 | 3 |
| 工具调用?大模型调用读文件、执行命令等工具的次数,按工具名统计。 | 共 3 次:bash 2、write 1 |
| 用时 | 1 分 |
| token 数?输入 / 输出(含思考)的 token 数,来自接口返回的用量。 | 输入 13,671 · 输出 1,342 · 思考 2,183 |
| 任务(第一行) | 分析节点 n8 的打分结果,写 ANALYSIS.json |
| 最后的回答(摘录) | ANALYSIS.json written |
| 原始记录位置?完整对话记录在 Spark 上的文件路径;看板不复制原始内容。 | /home/spark-longxinyang/vec/runs/formal/20261003-093415-search-t1-r2-D-s1/nodes/8/analyst.jsonl 11 KB /home/spark-longxinyang/vec/runs/formal/20261003-093415-search-t1-r2-D-s1/nodes/8/analyst.stderr |
工程师
| 角色 | 工程师 alibaba-token-plan-cn/qwen3.8-max |
|---|---|
| 调用次数 | 1 次(失败 1 次) |
| 轮数 | 18 |
| 工具调用 | 共 25 次:bash 19、write 3、edit 3 |
| 用时 | 27 分 |
| token 数 | 输入 40,125 · 输出 7,325 · 思考 7,824 |
| 任务(第一行) | 按计划修改程序:VAE潜空间+CFM局部位移场+稀疏残差解码(generative_latent 最小版) |
| 报错 | exit 143: |
| 最后的回答(摘录) | — |
| 原始记录位置 | /home/spark-longxinyang/vec/runs/formal/20261003-093415-search-t1-r2-D-s1/nodes/8/engineer.jsonl 75 KB /home/spark-longxinyang/vec/runs/formal/20261003-093415-search-t1-r2-D-s1/nodes/8/engineer.stderr |
调研员
| 角色 | 调研员 alibaba-token-plan-cn/qwen3.8-max |
|---|---|
| 调用次数 | 1 次 |
| 轮数 | 1 |
| 工具调用 | 共 0 次:— |
| 用时 | 2 分 |
| token 数 | 输入 11,944 · 输出 1,659 · 思考 2,784 |
| 任务(第一行) | 为节点 n8(草稿,父节点 nNone)写改进计划 PLAN.json,思路 generative_latent |
| 最后的回答(摘录) | KL 权重 1e-3,先在 ≤2,000 细胞子集、后 ≤4,000 全量,epochs 上限 100,训练时限 8 分钟;推理用均值(确定性、不采样)。(3) 潜空间耦合:对 E9.5 每个细胞在 E8.5 潜空间取 1-NN(或对称 3-NN 均值),得到配对 (z_src, z_tgt);这是位移的数据,不另做全量 OT。(4) 用 torchcfm 训练小 MLP 场 f(z)(128×2 层):条件流匹配损失 ||v_θ(z_t,t)−(z_tgt−z_src)||,t~U[0,1],z_t 线性插值;时限 4 分钟。(5) 预测:以最后一个输入阶段为起点,z_pred=z+λ·f(z),λ 阻尼初值 1.0、搜索 {0.5,0.75,1.0};欧氏步长截断在训练耦合位移中位数的 1.5 倍。(6) 残差解码:x̂=decoder(z_pred);输出 = 输入 + clip(x̂−x_enc, ±2.0);对基因施加软零门 g=sigmoid((x̂−0.05)/0.05),输入为 0 的基因输出按 g 缩小(允许少量从 0 变非 0),非 HVG 直接复制输入。不做任何全局缩放或组成重加权。(7) 单输入阶段退路(如视图只给一个阶段):场无法训练,直接原样复制最后阶段(节点 5 已验证该退路不吃亏),并把该分支写进代码而非按视图名判断。快速筛选:先跑 --recon-only(场关)在 X3 查 1 次,若低于 copy_last 参考水平就启用基因级回退(重建偏差大的基因直接复制输入);再按权重顺序查 X3→proxy10,λ 只挑 2-3 个值,总查分 ≤8 次留余量给复跑确认(噪声约 2 分,单次 +2 以内不算数)。",
"expected_groups": ["de_recovery", "direction", "covariation"],
"risks": "1) 解码器重建本身就跌破 copy_last(高斯均值+软门会侵蚀零值与方差,直接砸 mmd_u/variogram):Engineer 第一步就跑 recon-only 查分,跌破即启用基因级复制回退,仍跌破则本节点如实报告解码失败。2) 只有一次转移,场不可辨识:on/off 输出几乎相同即机制未运行,如实报告,不调参硬凑。3) 场在 X3 上外推方向押错(k030/方向库:押错方向四组一起降):先单独查 X3,四组全降则把 λ 压到 0.5 再试一次,仍不行即失败。4) 均值坍缩:潜在均值预测把细胞拉向平均,检查潜空间方差与零值率。5) 训练超时:严格按子集先行、epoch/秒数双上限。",
"family_id": "generative_latent",
"mechanism": "VAE 学输入表达的潜空间分布,在潜空间用条件流匹配训练一个依赖细胞位置的位移场(由跨阶段局部 1-NN 耦合监督),从最后输入阶段外推一步后由解码器以稀疏残差方式生成细胞。",
"vs_constant_shift": "位移是潜空间位置的连续函数(CFM 回归自局部配对),同类型不同位置的细胞得到不同位移;而常数位移对每型只加一个固定向量。残差解码保留每细胞稀疏结构与文库特征,不是全局加常数或重加权组成;阻尼与步长截断限制外推。",
"mechanism_evidence": "1) 位移范数的型内方差/型间方差比显著小于 1,且位移范数与细胞在潜空间的位置相关(散点/相关系数);2) on vs off 的四组分差值分别报告,DE 两项或 covariation 有超噪声变化才说明场在动基因结构;3) 零值率、每细胞位移的分布、实际改变最大的基因与细胞子集要写进报告。若位移型内离散度接近 0,说明退化为每型常数差,机制声明不成立。",
"mechanism_off_control": "同一程序加 --off(等价 λ=0):跳过场、只做编码→解码→残差输出。预期:off 输出≈输入的重建,分数反映纯解码器质量(应不低于 copy_last 太多);on 与 off 的差异全部归因于潜空间位移场。若两者分数与四组分相同,判定机制未运行。",
"sources": ["10.1038/s41592-018-0229-2 — scVI:VAE 在单细胞上的去噪潜空间与生成解码;因无 counts,本方案改用 log 数据的高斯解码器", "10.1038/s41587-021-01206-w — scvi-tools:VAE 结构与 stage 协变量用法的参考(只用方法,不加载任何预训练权重)", "arXiv:2407.11734 — CFGen:潜空间条件流匹配生成细胞的先例,本方案用 torchcfm 重建其核心"]}
``` |
| 原始记录位置 | /home/spark-longxinyang/vec/runs/formal/20261003-093415-search-t1-r2-D-s1/nodes/8/researcher.jsonl 6 KB /home/spark-longxinyang/vec/runs/formal/20261003-093415-search-t1-r2-D-s1/nodes/8/researcher.stderr |