Buckets:

redmingwei's picture
download
raw
8.19 kB
#!/usr/bin/env python3
"""build_l2_decay — 给 L2 补模型无关的衰减尺度(不拟合任何曲线族)。
两个量,分别作用在两条不同的曲线上,不能混用:
rho_half_ms / rho_zero_ms
作用在"跨单元共享相关曲线" rho 上(rho_curve 的输出)。
定义:从 lag=1 的值出发,首次降到 50% / 0 的滞后(线性插值),单位 ms。
尖峰的合适不变量;对 EEG 无效(通道间已被白化)。
acf1_half_ms / acf1_zero_ms
作用在"平均单通道自相关"上(rho_curve 同一次 FFT 的对角项 dia 归一化)。
定义同上。不受跨通道白化影响 —— 白化只改通道间关系,不改单通道时间自相关。
两者都只要求曲线在 max_lag 内降到阈值;降不到就标 censored(记录下界),
不外推、不假设函数族。
数值对拍:acf1 用 40 个单元的逐条 ACF 平均验证(应与 FFT 路径一致)。
输出: /domains/framework/l2_decay.parquet,并左连接回 l2_invariants.parquet
"""
from __future__ import annotations
import glob
import sys
import time
from pathlib import Path
import numpy as np
import pandas as pd
ROOT = Path("/domains")
OUT = ROOT / "framework"
MAX_LAG_SPIKE = 400
MAX_LAG_EEG = 100
N_EEG_TRIALS = 100
N_4D_TRIALS = 50
FRAC = 0.5
def n2(n):
k = 1
while k < n:
k <<= 1
return k
def curve_parts(x, max_lag):
"""与 rho_curve 相同的 FFT 路径;额外返回归一化对角项(平均单通道 ACF)。"""
T, d = x.shape
xc = x - x.mean(axis=0, keepdims=True)
N = n2(2 * T)
F = np.fft.rfft(xc, n=N, axis=0)
Fs = F.sum(axis=1)
tot = np.fft.irfft(Fs * np.conj(Fs), n=N)[:max_lag + 1]
dia = np.fft.irfft((np.abs(F) ** 2).sum(axis=1), n=N)[:max_lag + 1]
nrm = np.arange(T, T - max_lag - 1, -1)
tot = tot / np.maximum(nrm, 1)
dia = dia / np.maximum(nrm, 1)
t0 = max(tot[0], 1e-12)
rho = (tot - dia) / t0
acf1 = dia / max(dia[0], 1e-12)
return rho, acf1
def cross_time(curve, bin_ms, frac):
"""首次降到 frac*curve[1] 的滞后(线性插值)。frac=0 → 过零。"""
if len(curve) < 3:
return None, "too_short"
y1 = float(curve[1])
if not np.isfinite(y1) or y1 <= 0:
return None, "bad_start"
thr = frac * y1
idx = np.flatnonzero(curve[2:] <= thr)
if len(idx) == 0:
return None, "censored"
l = int(idx[0]) + 2
y0, yb = float(curve[l - 1]), float(curve[l])
f = (y0 - thr) / (y0 - yb) if y0 > yb else 0.0
return float((l - 1 + f) * bin_ms), "ok"
rows = []
t00 = time.time()
# ---------- 数值对拍 ----------
f0 = sorted(glob.glob(str(ROOT / "steinmetz/spikes_session_*.npz")))[0]
z0 = np.load(f0, mmap_mode="r")
x0 = np.round(np.asarray(z0["spikes"], dtype=np.float64) * 8).T
ml0 = min(MAX_LAG_SPIKE, x0.shape[0] - 2)
rho_f, acf_f = curve_parts(x0.astype(np.float32), ml0)
sub = np.random.default_rng(0).choice(x0.shape[1], size=min(40, x0.shape[1]), replace=False)
acc, rpos = [], []
for j in sub:
v = x0[:, j] - x0[:, j].mean()
ac = np.correlate(v, v, mode="full")[len(v) - 1:][:ml0 + 1]
acc.append(ac / max(ac[0], 1e-12))
print(f"[check] acf1 max|fast-brute| = {np.abs(acf_f - np.mean(acc, axis=0)).max():.3e}",
flush=True)
del x0, z0
def push(member, slice_id, rho, acf1, bin_ms, curv_kind, note=""):
"""curv_kind: 'spikes'(rho 与 acf1 都用)/ 'eeg'(只用 acf1)。"""
rec = dict(member=member, slice=slice_id, note=note,
rho_half_ms=None, rho_zero_ms=None,
rho_half_status=None, rho_zero_status=None,
acf1_lag1=float(acf1[1]) if len(acf1) > 1 else None,
acf1_half_ms=None, acf1_zero_ms=None,
acf1_half_status=None, acf1_zero_status=None,
max_lag_ms=len(acf1) * bin_ms)
rec["acf1_half_ms"], rec["acf1_half_status"] = cross_time(acf1, bin_ms, FRAC)
rec["acf1_zero_ms"], rec["acf1_zero_status"] = cross_time(acf1, bin_ms, 0.0)
if curv_kind == "spikes" and rho is not None:
rec["rho_half_ms"], rec["rho_half_status"] = cross_time(rho, bin_ms, FRAC)
rec["rho_zero_ms"], rec["rho_zero_status"] = cross_time(rho, bin_ms, 0.0)
rows.append(rec)
# ---------- 1. 原生尖峰 ----------
for member, pat, bin_ms in [("steinmetz", "steinmetz/spikes_session_*.npz", 50.0),
("primate_macaque_nlb", "primate/macaque_nlb/**/*.npz", 20.0)]:
for f in sorted(glob.glob(str(ROOT / pat), recursive=True)):
z = np.load(f, mmap_mode="r")
k = "spikes" if "spikes" in z.keys() else list(z.keys())[0]
a = np.asarray(z[k], dtype=np.float32)
x = a.T if a.shape[0] < a.shape[1] else a
if float((np.abs(x.astype(np.float64) * 8
- np.round(x.astype(np.float64) * 8)) < 1e-5).mean()) > 0.999:
x = np.round(x.astype(np.float64) * 8).astype(np.float32)
ml = min(MAX_LAG_SPIKE, x.shape[0] - 2)
rho, acf1 = curve_parts(x, ml)
push(member, Path(f).stem, rho, acf1, bin_ms, "spikes")
print(f" {member} {Path(f).stem} [{time.time()-t00:.0f}s]", flush=True)
del a, x
# ---------- 2. 人类 EEG:逐 trial 的通道 ACF 平均 ----------
for f in sorted(glob.glob(str(ROOT / "primate/human_eeg/**/*.npz"), recursive=True)):
if not f.endswith(".npz"):
continue
z = np.load(f, mmap_mode="r")
k = "eeg" if "eeg" in z.keys() else list(z.keys())[0]
a = np.asarray(z[k], dtype=np.float32)
nt, nch, T = a.shape
ml = min(MAX_LAG_EEG, T - 2)
sel = np.random.default_rng(0).choice(nt, size=min(N_EEG_TRIALS, nt), replace=False)
acc = np.mean([curve_parts(a[i].T, ml)[1] for i in sel], axis=0)
push("primate_human_eeg", Path(f).stem, None, acc, 4.0, "eeg",
note=f"single_channel_acf_over_{len(sel)}_trials")
print(f" human_eeg {Path(f).stem} [{time.time()-t00:.0f}s]", flush=True)
del a
# ---------- 3. 四轴 EEG ----------
for member, pat in [("omnibrain4d_eeg_test", "omnibrain4d/eeg/**/*.npz"),
("omnibrain4d_eeg_train", "omnibrain4d/eeg_train/**/*.npz")]:
for f in sorted(glob.glob(str(ROOT / pat), recursive=True)):
z = np.load(f, mmap_mode="r")
if "eeg" not in z.keys():
continue
a = np.asarray(z["eeg"], dtype=np.float32)
if a.ndim != 4:
continue
a = a[:50]
ni, nr, nch, T = a.shape
ml = min(MAX_LAG_EEG, T - 2)
flat = a.transpose(0, 1, 3, 2).reshape(ni * nr, T, nch)
sel = np.random.default_rng(0).choice(len(flat), size=min(N_4D_TRIALS, len(flat)),
replace=False)
acc = np.mean([curve_parts(flat[i], ml)[1] for i in sel], axis=0)
push(member, Path(f).stem, None, acc, 4.0, "eeg",
note=f"single_channel_acf_over_{len(sel)}_trials")
print(f" {member} {Path(f).stem} {a.shape} [{time.time()-t00:.0f}s]", flush=True)
del a, flat
tdf = pd.DataFrame(rows)
tdf.to_parquet(OUT / "l2_decay.parquet", index=False)
P = OUT / "l2_invariants.parquet"
df = pd.read_parquet(P)
newcols = [c for c in tdf.columns if c not in ("member", "slice", "note")]
for c in newcols:
if c in df.columns:
df = df.drop(columns=[c])
df = df.merge(tdf[["member", "slice"] + newcols], on=["member", "slice"], how="left")
df.to_parquet(P, index=False)
df.to_csv(OUT / "l2_invariants.csv", index=False)
print(f"\nrows={len(tdf)} [{time.time()-t00:.0f}s]")
for c in ["rho_half_status", "rho_zero_status", "acf1_half_status", "acf1_zero_status"]:
print(f"\n{c}: {tdf[c].value_counts().to_dict()}")
print("\nrho_half_ms (ok):")
print(tdf[tdf.rho_half_status == "ok"].groupby("member").rho_half_ms
.agg(["size", "median", lambda s: s.quantile(.1),
lambda s: s.quantile(.9)]).to_string())
print("\nacf1_half_ms (ok):")
print(tdf[tdf.acf1_half_status == "ok"].groupby("member").acf1_half_ms
.agg(["size", "median", lambda s: s.quantile(.1),
lambda s: s.quantile(.9)]).to_string())
print("\nacf1_lag1 median:")
print(tdf.groupby("member").acf1_lag1.median().to_string())

Xet Storage Details

Size:
8.19 kB
·
Xet hash:
5be6f2116dbbb46eee19e80ecc26307e80132d9f84e5a939fb30591b15d37011

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