Spaces:
Running
Running
Download engine/meidnet_eval/decoder_autopsy.py from Babu09/MEIDNet-Matter: direct link, hf CLI and curl.
- Browser
- Download file 5.55 kB
-
https://huggingface.co/spaces/Babu09/MEIDNet-Matter/resolve/main/engine/meidnet_eval/decoder_autopsy.py
- Command line
-
hf download hf://spaces/Babu09/MEIDNet-Matter/engine/meidnet_eval/decoder_autopsy.py
-
curl -L -o decoder_autopsy.py https://huggingface.co/spaces/Babu09/MEIDNet-Matter/resolve/main/engine/meidnet_eval/decoder_autopsy.py
5.55 kB
| """Why does the decoder recover 64% of compositions on Perov-5 and 1% on a multi-prototype dataset? | |
| Three candidate causes, each measured rather than argued: | |
| collapse the decoder predicts the majority species everywhere. Site accuracy is only meaningful against the | |
| majority-species baseline (predict the commonest element in every slot), so both are reported. | |
| cell size a fixed slot count has to serve 5-, 10-, 20- and 40-atom cells; recovery is therefore split by cell size. | |
| prototype 73 prototypes share one decoder; recovery is split by prototype as well. | |
| Usage: python decoder_autopsy.py <ckpt> --data <intake dir> [--split test] (fairchem-env) | |
| """ | |
| import argparse, collections, os, sys | |
| import numpy as np | |
| import pandas as pd | |
| import torch | |
| HERE = os.path.dirname(os.path.abspath(__file__)); sys.path.insert(0, HERE) | |
| from meidnet.benchmark import load_split # noqa: E402 | |
| from meidnet.checkpoint import load_checkpoint # noqa: E402 | |
| from meidnet.data import MaterialsDataset, split_dense # noqa: E402 | |
| from torch.utils.data import DataLoader # noqa: E402 | |
| def main(): | |
| ap = argparse.ArgumentParser() | |
| ap.add_argument("ckpt"); ap.add_argument("--data", required=True); ap.add_argument("--split", default="test") | |
| ap.add_argument("--json", help="also write the numbers here (used by discover.py for the generation/screening rule)") | |
| a = ap.parse_args() | |
| torch.set_num_threads(int(os.environ.get("OMP_NUM_THREADS", "4"))) | |
| lm = load_checkpoint(a.ckpt, device="cpu") | |
| cols = list(lm.stats.columns) | |
| recs, _ = load_split(a.data, a.split, cols, lm.model.max_sites) | |
| proto = {} | |
| path = f"{a.data}/{a.split}.csv" | |
| if os.path.exists(path): | |
| d = pd.read_csv(path, usecols=lambda c: c in ("material_id", "prototype")) | |
| if "prototype" in d.columns: | |
| proto = dict(zip(d.material_id.astype(str), d.prototype)) | |
| ms = lm.model.max_sites | |
| rows, slot_true, slot_pred = [], [], [] | |
| with torch.no_grad(): | |
| for b in DataLoader(MaterialsDataset(recs, lm.stats), batch_size=128, shuffle=False): | |
| cv = b["crystal_vec"] | |
| true = split_dense(cv, ms) | |
| zc, _ = lm.model.encode_crystal(cv) | |
| _, _, spc, _ = lm.model.crystal_decoder(zc, input_coords=torch.zeros(len(cv), ms, 3), | |
| center=torch.zeros(len(cv), 3)) | |
| n_sites = (true["species"].sum(-1) > 0).sum(1).numpy() | |
| for i in range(len(cv)): | |
| n = int(n_sites[i]) | |
| t = true["species"][i].argmax(-1).numpy()[:n] | |
| p = spc[i].argmax(-1).numpy()[:n] | |
| slot_true += list(t); slot_pred += list(p) | |
| rows.append(dict(n=n, exact=int(np.array_equal(np.sort(t), np.sort(p))), | |
| sites=float((t == p).mean()), n_pred_distinct=len(set(p.tolist())))) | |
| V = pd.DataFrame(rows) | |
| for k, r in enumerate(recs): | |
| V.loc[k, "prototype"] = proto.get(str(r.material_id), "?") | |
| st, sp = np.array(slot_true), np.array(slot_pred) | |
| majority = collections.Counter(st.tolist()).most_common(1)[0] | |
| base = (st == majority[0]).mean() | |
| print(f"{len(V)} structures, max_sites {ms}, {len(st)} real slots\n") | |
| print("1) COLLAPSE — is the decoder doing better than predicting the commonest element everywhere?") | |
| print(f" majority-species baseline (always predict the commonest element): {100*base:.0f}% of slots") | |
| print(f" decoder site accuracy: {100*(st == sp).mean():.0f}% of slots") | |
| print(f" distinct elements in the data {len(set(st.tolist())):3d} | distinct elements the decoder ever predicts " | |
| f"{len(set(sp.tolist())):3d} | predicted per structure, median {V.n_pred_distinct.median():.0f}") | |
| print(f" verdict: {'COLLAPSED towards the majority species' if (st == sp).mean() < base + 0.05 else 'genuinely better than the majority baseline'}\n") | |
| print("2) CELL SIZE — a fixed slot count serving several cell sizes") | |
| g = V.groupby("n").agg(structures=("exact", "size"), exact_pct=("exact", lambda x: 100 * x.mean()), | |
| site_pct=("sites", lambda x: 100 * x.mean())) | |
| print(g.round(1).to_string()) | |
| print() | |
| print("3) PROTOTYPE — one decoder shared by many prototypes") | |
| p = V.groupby("prototype").agg(structures=("exact", "size"), exact_pct=("exact", lambda x: 100 * x.mean()), | |
| site_pct=("sites", lambda x: 100 * x.mean())).sort_values("structures", ascending=False) | |
| print(p.head(8).round(1).to_string()) | |
| print(f"\noverall: {100*V.exact.mean():.1f}% exact compositions, {100*V.sites.mean():.0f}% sites") | |
| single = V[V.n == V.n.mode()[0]] | |
| if a.json: | |
| import json | |
| json.dump(dict(structures=len(V), majority_baseline=float(base), site_accuracy=float((st == sp).mean()), | |
| exact_composition=float(V.exact.mean()), distinct_elements_data=len(set(st.tolist())), | |
| distinct_elements_predicted=len(set(sp.tolist())), | |
| by_cell_size={int(k): float(v) for k, v in V.groupby("n").exact.mean().items()}), | |
| open(a.json, "w"), indent=1) | |
| print(f"restricted to the commonest cell size ({int(V.n.mode()[0])} atoms, {len(single)} structures): " | |
| f"{100*single.exact.mean():.1f}% exact, {100*single.sites.mean():.0f}% sites") | |
| if __name__ == "__main__": | |
| main() | |