| |
| """Multi-seed stability: how reproducible are DeepPTR results?""" |
| from _common import * |
| from sklearn.metrics import silhouette_score |
|
|
| OUT = output_dir("14_multiseed") |
| N_SEEDS = 5 |
|
|
|
|
| def main(): |
| set_figure_style() |
|
|
| print("Loading and preprocessing pancreas...") |
| adata_raw = scptr.datasets.pancreas() |
| scptr.pp.filter_genes(adata_raw) |
| scptr.pp.normalize_layers(adata_raw) |
| scptr.pp.neighbors(adata_raw, n_neighbors=30) |
| scptr.pp.smooth_layers(adata_raw) |
| scptr.tl.estimate_beta(adata_raw) |
| adata_base = select_top_genes(adata_raw, n_top=300) |
|
|
| _, hl_human = load_halflife_refs() |
|
|
| records = [] |
| gamma_meds = [] |
|
|
| for seed in range(N_SEEDS): |
| print(f"\n Seed {seed}...") |
| adata_v = adata_base.copy() |
| from scipy.sparse import issparse |
| for key in ("spliced", "unspliced"): |
| if key in adata_v.layers and issparse(adata_v.layers[key]): |
| adata_v.layers[key] = np.asarray(adata_v.layers[key].todense()) |
|
|
| hp = dict(DEEP_HP) |
| hp["seed"] = seed |
| torch.set_num_threads(4) |
| model, history = scptr.deep.fit_deepptr(adata_v, verbose=False, **hp) |
|
|
| gamma_med = np.median(adata_v.layers["gamma"], axis=0) |
| gamma_meds.append(gamma_med) |
|
|
| r, n = halflife_spearman(adata_v, hl_human) |
|
|
| sil = np.nan |
| if "clusters" in adata_v.obs.columns and "X_z_T" in adata_v.obsm: |
| labels = adata_v.obs["clusters"].astype("category").cat.codes.values |
| sil = silhouette_score(adata_v.obsm["X_z_T"], labels, sample_size=min(2000, len(labels))) |
|
|
| records.append({ |
| "seed": seed, "halflife_r": float(r), "n_genes": n, |
| "silhouette_zT": float(sil), |
| "n_epochs": len(history.train_loss), |
| "final_val_loss": history.val_loss[-1], |
| }) |
| print(f" HL r={r:.4f}, sil={sil:.4f}, epochs={len(history.train_loss)}") |
|
|
| |
| cross_rs = [] |
| for i in range(N_SEEDS): |
| for j in range(i + 1, N_SEEDS): |
| r, _ = stats.spearmanr(gamma_meds[i], gamma_meds[j]) |
| cross_rs.append(float(r)) |
|
|
| |
| top50_sets = [set(np.argsort(gm)[::-1][:50]) for gm in gamma_meds] |
| overlaps = [] |
| for i in range(N_SEEDS): |
| for j in range(i + 1, N_SEEDS): |
| overlaps.append(len(top50_sets[i] & top50_sets[j])) |
|
|
| |
| hl_rs = [r["halflife_r"] for r in records] |
| sils = [r["silhouette_zT"] for r in records] |
|
|
| print(f"\n{'=' * 60}") |
| print("MULTI-SEED SUMMARY (pancreas, N=5)") |
| print("=" * 60) |
| print(f" Half-life r: {np.mean(hl_rs):.4f} ± {np.std(hl_rs):.4f}") |
| print(f" Silhouette z_T: {np.mean(sils):.4f} ± {np.std(sils):.4f}") |
| print(f" Cross-seed γ r: {np.mean(cross_rs):.4f} ± {np.std(cross_rs):.4f}") |
| print(f" Top-50 overlap: {np.mean(overlaps):.1f} ± {np.std(overlaps):.1f} / 50") |
|
|
| results = { |
| "per_seed": records, |
| "halflife_mean": float(np.mean(hl_rs)), |
| "halflife_std": float(np.std(hl_rs)), |
| "silhouette_mean": float(np.mean(sils)), |
| "silhouette_std": float(np.std(sils)), |
| "cross_seed_gamma_r_mean": float(np.mean(cross_rs)), |
| "cross_seed_gamma_r_std": float(np.std(cross_rs)), |
| "top50_overlap_mean": float(np.mean(overlaps)), |
| "top50_overlap_std": float(np.std(overlaps)), |
| } |
| save_json(results, "multiseed", OUT) |
|
|
| |
| fig, axes = plt.subplots(1, 3, figsize=(14, 4)) |
| axes[0].bar(range(N_SEEDS), [abs(r) for r in hl_rs], color="steelblue", alpha=0.7) |
| axes[0].axhline(np.mean([abs(r) for r in hl_rs]), color="red", ls="--") |
| axes[0].set_xlabel("Seed"); axes[0].set_ylabel("|r| with half-life") |
| axes[0].set_title(f"Half-life r: {np.mean(hl_rs):.4f}±{np.std(hl_rs):.4f}") |
|
|
| axes[1].bar(range(N_SEEDS), sils, color="darkorange", alpha=0.7) |
| axes[1].axhline(np.mean(sils), color="red", ls="--") |
| axes[1].set_xlabel("Seed"); axes[1].set_ylabel("Silhouette") |
| axes[1].set_title(f"Silhouette: {np.mean(sils):.4f}±{np.std(sils):.4f}") |
|
|
| axes[2].hist(cross_rs, bins=8, color="seagreen", alpha=0.7) |
| axes[2].set_xlabel("Cross-seed gamma r"); axes[2].set_ylabel("Count") |
| axes[2].set_title(f"Gamma agreement: {np.mean(cross_rs):.4f}±{np.std(cross_rs):.4f}") |
|
|
| fig.suptitle("Multi-seed stability (N=5)", y=1.02) |
| fig.tight_layout() |
| save_fig(fig, "multiseed", OUT) |
|
|
|
|
| if __name__ == "__main__": |
| main() |
|
|