Download scripts/evaluate.py from chaliasif/saudi-tender-tagger: direct link, hf CLI and curl.
- Browser
- Download file 5.35 kB
-
https://huggingface.co/chaliasif/saudi-tender-tagger/resolve/main/scripts/evaluate.py
- Command line
-
hf download hf://chaliasif/saudi-tender-tagger/scripts/evaluate.py
-
curl -L -o evaluate.py https://huggingface.co/chaliasif/saudi-tender-tagger/resolve/main/scripts/evaluate.py
5.35 kB
| #!/usr/bin/env python3 | |
| """Compare baseline vs encoder on the untouched test split. Writes outputs/metrics.json and outputs/eval_report.txt | |
| and models/saudi-tender-tagger/config.json (which model ships per label).""" | |
| import json, os, sys | |
| from collections import Counter | |
| import pandas as pd | |
| from sklearn.metrics import accuracy_score, f1_score | |
| sys.path.insert(0, os.path.dirname(__file__)); from common import * | |
| O = f"{ROOT}/outputs"; te = pd.read_csv(f"{ROOT}/data/published/test.csv", dtype=str).fillna(""); tr = pd.read_csv(f"{ROOT}/data/published/train.csv", dtype=str).fillna("") | |
| te = te.set_index("ref") | |
| seen = set(zip(tr.title.map(norm_text), tr.agency.map(norm_text))) | |
| novel = [(norm_text(t), norm_text(a)) not in seen for t, a in zip(te.title, te.agency)] | |
| te["novel"] = novel | |
| def m(y, p): return {"accuracy": round(accuracy_score(y, p), 4), "macro_f1": round(f1_score(y, p, average="macro"), 4)} | |
| out, lines, ship = {}, [], {} | |
| P = {} | |
| for lab in ("activity", "ttype"): | |
| for model in ("baseline", "encoder"): | |
| P[(lab, model)] = pd.read_csv(f"{O}/pred_{model}_{lab}.csv", dtype=str).set_index("ref") | |
| T3 = json.load(open(f"{O}/top3.json")); ENC = {l: json.load(open(f"{O}/encoder_{l}_metrics.json")) for l in ("activity", "ttype")} | |
| for lab in ("activity", "ttype"): | |
| rows = {} | |
| for model in ("baseline", "encoder"): | |
| d = P[(lab, model)].loc[te.index] | |
| r = m(d.true, d.pred); r["accuracy_novel_titles"] = round(accuracy_score(d.true[te.novel], d.pred[te.novel]), 4) | |
| rows[model] = r | |
| c = Counter(zip(d.true[d.true != d.pred], d.pred[d.true != d.pred])).most_common(6) | |
| lines.append(f"\n[{lab} / {model}] {r}\n top confusions (true -> predicted, count):") | |
| lines += [f" {a} -> {b}: {n}" for (a, b), n in c] | |
| f1s = pd.Series(f1_score(d.true, d.pred, average=None, labels=sorted(set(d.true)), zero_division=0), index=sorted(set(d.true))) | |
| sup = d.true.value_counts() | |
| w = f1s[sup[f1s.index] >= 5].sort_values().head(5) | |
| lines.append(" weakest classes (support>=5): " + "; ".join(f"{k} F1={v:.2f} n={sup[k]}" for k, v in w.items())) | |
| if lab == "activity": rows["baseline"]["top3_accuracy"] = round(T3["activity_baseline"], 4); rows["encoder"]["top3_accuracy"] = round(ENC["activity"]["top3_accuracy"], 4); rows["encoder"]["base_model"] = ENC["activity"]["base_model"] | |
| win = max(rows, key=lambda k: (rows[k]["macro_f1"], rows[k]["accuracy"])); rows["ships"] = win; out[lab] = rows; ship[lab] = win | |
| # derived labels from predicted activity vs truth | |
| for model in ("baseline", "encoder"): | |
| d = P[("activity", model)].loc[te.index] | |
| ps = [sector_of(x) for x in d.pred]; pk = [kind_of(x, tt, ti) for x, tt, ti in zip(d.pred, P[("ttype", model)].loc[te.index].pred, te.title)] | |
| out.setdefault("sector_derived", {})[model] = m(te.sector, ps); out.setdefault("kind_derived", {})[model] = m(te.kind, pk) | |
| lines.append(f"\n[sector derived from predicted activity / {model}] {out['sector_derived'][model]}") | |
| lines.append(f"[kind rules on predicted activity+type / {model}] {out['kind_derived'][model]}") | |
| c = Counter((a, b) for a, b in zip(te.sector, ps) if a != b).most_common(5) | |
| lines.append(" top sector confusions: " + "; ".join(f"{a}->{b}:{n}" for (a, b), n in c)) | |
| # MARBERTv2 6-epoch row (measured, not shipped: no stated license) | |
| mb = json.load(open(f"{O}/marbert_6ep/encoder_activity_metrics.json")) | |
| out["activity_marbertv2_6ep_not_shipped"] = {"accuracy": round(mb["accuracy"], 4), "macro_f1": round(mb["macro_f1"], 4), "top3_accuracy": round(T3.get("activity_marbert_6ep", float("nan")), 4), "reason": "base model has no stated license"} | |
| # sector: direct TF-IDF head vs derived from the shipped activity model | |
| ship_act = out["activity"]["ships"] | |
| dsd = P[("activity", ship_act)].loc[te.index] | |
| derived = m(te.sector, [sector_of(x) for x in dsd.pred]) | |
| bs = pd.read_csv(f"{O}/pred_baseline_sector.csv", dtype=str).set_index("ref").loc[te.index] | |
| direct = m(bs.true, bs.pred) | |
| out["sector_direct_tfidf"] = direct; out["sector_derived_from_shipped_activity"] = derived | |
| out["sector_route"] = "direct" if direct["macro_f1"] > derived["macro_f1"] else "derived" | |
| ship["sector"] = out["sector_route"] | |
| lines.append(f"\n[sector] direct TF-IDF {direct} vs derived from {ship_act} activity {derived} -> ship {out['sector_route']}") | |
| bm = json.load(open(f"{O}/baseline_metrics.json")) | |
| out["baseline_direct_sector_and_kind"] = {k: bm[k] for k in ("sector", "kind")} | |
| out["test_n"] = len(te); out["test_novel_title_n"] = int(te.novel.sum()) | |
| json.dump(out, open(f"{O}/metrics.json", "w"), indent=1, ensure_ascii=False) | |
| open(f"{O}/eval_report.txt", "w").write(json.dumps(out, indent=1, ensure_ascii=False) + "\n" + "\n".join(lines)) | |
| cfg = {"name": "saudi-tender-tagger", "input": "title + ' | ' + agency", "max_length": 128, | |
| "heads": {**{l: ({"type": "encoder", "path": f"{l}_encoder"} if ship[l] == "encoder" else {"type": "tfidf", "path": f"{l}_tfidf"}) for l in ("activity", "ttype")}, **({"sector": {"type": "tfidf", "path": "sector_tfidf"}} if ship["sector"] == "direct" else {})}, | |
| "derived": {"sector": "labels/activity_to_sector.json", "kind": "labels/activity_to_kind.json + rules in scripts/common.py"}} | |
| json.dump(cfg, open(f"{ROOT}/models/saudi-tender-tagger/config.json", "w"), indent=1) | |
| print(open(f"{O}/eval_report.txt").read()) | |