Buckets:

illusion-brain/brain-domains / scripts /exp66_align_text_check.py
redmingwei's picture
download
raw
7.78 kB
#!/usr/bin/env python3
"""exp66 — 概念顺序对齐的**独立**检验(完全不碰 EEG)。
## 为什么需要这个
exp64/exp65 用「置换零分布」证明了 EEG-RDM 与 ViT-RDM 的对应不是偶然,
但那是**用 EEG 本身**做检验的:如果 EEG 与特征的顺序同时错位,置换检验同样会
给出显著结果(它检验的是「非随机」,不是「正确」)。
本脚本绕开 EEG,直接验证**特征文件的行序 == 概念序**:
test.pt 里每条试次都带 `text`(概念文本)、`img`(图像路径)、`label`(概念 ID)。
如果 ViT-H-14 的 `text_features` 就是按这 200 个概念的顺序抽的,那么
「由 test.pt 的文本字符串构造的 RDM」应当与「ViT text_features 的 RDM」高度相关,
且显著高于任何随机置换。
这是纯文本/纯特征层的检验,与 EEG 无关,因此能独立判定顺序对不对。
## 判据
r(字符串 RDM, ViT text RDM) > 0.5 且 > 置换零 95 分位 → 行序 == 概念序 ✅
同时输出:
* 概念 ID 是否与行号单调对应(label[i] 是否 == i 或某种固定置换)
* 图像路径样例(能否看出排序规则)
* 字符串 RDM 与 ViT **image** RDM 的相关(应当也显著 —— 概念相同的图更相似)
"""
from __future__ import annotations
import json
import os
import re
import time
from collections import Counter
from pathlib import Path
import numpy as np
for _v in ("OMP_NUM_THREADS", "OPENBLAS_NUM_THREADS", "MKL_NUM_THREADS"):
os.environ.setdefault(_v, "8")
EEG_PT = "/training/eeg/things-eeg/Preprocessed_data_250Hz_whiten/sub-01/test.pt"
FEAT_PT = "/training/eeg/eeg-image-decode/ViT-H-14_features_test.pt"
OUT_DIR = Path("/domains/runs/tier0-diag")
N_PERM = 200
SEED = 0
def log(*a):
print(*a, flush=True)
def _rank(a):
from scipy.stats import rankdata
return rankdata(a)
def spearman(a, b):
ra = _rank(a).astype(np.float64)
rb = _rank(b).astype(np.float64)
ra -= ra.mean()
rb -= rb.mean()
den = np.sqrt((ra * ra).sum() * (rb * rb).sum())
return float((ra * rb).sum() / den) if den > 0 else 0.0
def rdm_full(mat):
m = np.asarray(mat, dtype=np.float64)
m = m - m.mean(axis=1, keepdims=True)
m = m / (np.linalg.norm(m, axis=1, keepdims=True) + 1e-12)
r = m @ m.T
np.fill_diagonal(r, 1.0)
return 1.0 - r
def tri(R):
return R[np.triu_indices_from(R, k=1)]
def rsa_with_null(R_a, R_b, n_perm=N_PERM, rng=None):
rng = rng or np.random.default_rng(SEED)
a = tri(R_a)
r_obs = spearman(a, tri(R_b))
null = np.empty(n_perm)
n = R_b.shape[0]
for i in range(n_perm):
p = rng.permutation(n)
null[i] = spearman(a, tri(R_b[np.ix_(p, p)]))
return r_obs, null
def tokenize(s):
return re.findall(r"[a-z0-9]+", s.lower())
def tfidf_matrix(docs):
"""docs: list[str] → (n, vocab) TF-IDF(已 L2 归一)。"""
toks = [tokenize(d) for d in docs]
df = Counter()
for t in toks:
df.update(set(t))
vocab = sorted(df.keys())
idx = {w: i for i, w in enumerate(vocab)}
n = len(docs)
M = np.zeros((n, len(vocab)), dtype=np.float64)
for i, t in enumerate(toks):
for w in t:
M[i, idx[w]] += 1.0
dfv = np.array([df[w] for w in vocab], dtype=np.float64)
idf = np.log((1.0 + n) / (1.0 + dfv)) + 1.0
M *= idf
nrm = np.linalg.norm(M, axis=1, keepdims=True)
M = M / (nrm + 1e-12)
return M
def main():
import torch
t0 = time.time()
OUT_DIR.mkdir(parents=True, exist_ok=True)
rng = np.random.default_rng(SEED)
report = {"schema_version": "1.0", "script": "exp66_align_text_check"}
log("=" * 78)
log("exp66 — 概念顺序对齐的独立检验(不碰 EEG)")
log("=" * 78)
d = torch.load(EEG_PT, map_location="cpu", weights_only=False)
f = torch.load(FEAT_PT, map_location="cpu", weights_only=False)
txt = np.asarray(d["text"])
img = np.asarray(d["img"])
lab = np.asarray(d["label"])
n_img, n_rep = txt.shape
log(f"\n text {txt.shape} | img {img.shape} | label {lab.shape}")
report["shapes"] = {"text": list(txt.shape), "img": list(img.shape),
"label": list(lab.shape)}
# 每个概念取第 0 次重复的文本/图/标签
texts = [str(txt[c, 0]) for c in range(n_img)]
imgs = [str(img[c, 0]) for c in range(n_img)]
labels = [int(lab[c, 0]) for c in range(n_img)]
# 一致性:同一概念的所有重复是否同文本/同标签
txt_consistent = all(len(set(map(str, txt[c]))) == 1 for c in range(n_img))
lab_consistent = all(len(set(map(int, lab[c]))) == 1 for c in range(n_img))
log(f" 概念内文本一致: {txt_consistent} | 概念内标签一致: {lab_consistent}")
report["text_consistent"] = bool(txt_consistent)
report["label_consistent"] = bool(lab_consistent)
log("\n 样例(概念 0,1,2,198,199):")
for c in [0, 1, 2, 198, 199]:
log(f" [{c:3d}] label={labels[c]:>8} text={texts[c][:42]!r}")
log(f" img={imgs[c]}")
report["samples"] = [{"row": c, "label": labels[c], "text": texts[c],
"img": imgs[c]} for c in [0, 1, 2, 198, 199]]
uniq_labels = sorted(set(labels))
log(f"\n label 取值: n_unique={len(uniq_labels)} "
f"min={uniq_labels[0]} max={uniq_labels[-1]}")
log(f" label == 行号? {labels == list(range(n_img))}")
log(f" label 单调递增? {labels == sorted(labels)}")
report["label_unique"] = len(uniq_labels)
report["label_is_row_index"] = bool(labels == list(range(n_img)))
report["label_sorted"] = bool(labels == sorted(labels))
# 字符串 RDM
log("\n" + "=" * 78)
log("关键检验 — 字符串 RDM vs ViT 特征 RDM")
log("=" * 78)
M = tfidf_matrix(texts)
R_str = rdm_full(M)
R_txt_vit = rdm_full(f["text_features"].numpy().astype(np.float64))
R_img_vit = rdm_full(f["img_features"].numpy().astype(np.float64))
r_t, null_t = rsa_with_null(R_str, R_txt_vit, N_PERM, rng)
r_i, null_i = rsa_with_null(R_str, R_img_vit, N_PERM, rng)
pt = float((np.sum(np.abs(null_t) >= abs(r_t)) + 1) / (N_PERM + 1))
pi = float((np.sum(np.abs(null_i) >= abs(r_i)) + 1) / (N_PERM + 1))
log(f" r(字符串RDM, ViT text_features RDM) = {r_t:+.4f} p={pt:.4f} "
f"零分布 sd={null_t.std():.4f} 95分位={np.percentile(np.abs(null_t),95):.4f}")
log(f" r(字符串RDM, ViT img_features RDM) = {r_i:+.4f} p={pi:.4f} "
f"零分布 sd={null_i.std():.4f} 95分位={np.percentile(np.abs(null_i),95):.4f}")
ok = bool(pt < 0.05 and r_t > 0.5)
log(f"\n → {'✅ 特征文件行序 == 概念序(对齐确认)' if ok else '❌ 未通过'}")
report["r_text_vs_vit_text"] = {"r": r_t, "p": pt,
"null_sd": float(null_t.std()),
"null_p95": float(np.percentile(np.abs(null_t), 95))}
report["r_text_vs_vit_img"] = {"r": r_i, "p": pi,
"null_sd": float(null_i.std()),
"null_p95": float(np.percentile(np.abs(null_i), 95))}
report["order_confirmed"] = ok
report["elapsed_s"] = round(time.time() - t0, 1)
(OUT_DIR / "exp66_align_text_check.json").write_text(
json.dumps(report, indent=2, ensure_ascii=False), encoding="utf-8")
# 同时打到 stdout —— 防止 Job 写桶时 Xet 通道卡住导致结果丢失
print("=== JSON BEGIN ===")
print(json.dumps(report, indent=2, ensure_ascii=False))
print("=== JSON END ===")
log(f"\n报告 → {OUT_DIR}/exp66_align_text_check.json 耗时 {time.time()-t0:.0f}s")
if __name__ == "__main__":
main()

Xet Storage Details

Size:
7.78 kB
·
Xet hash:
6c3162fb286621fd068a8433ce24db6829e7b0a32c7babd0355f1ab769844ef6

Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.