#!/usr/bin/env python3
"""EXP3 report: per-point two-stage results, the gate trade-off, 21x6 contingency
tables and the model inventory the project note asks for."""
from __future__ import annotations
import base64, io, json, os, sys, time
from html import escape
import numpy as np
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
import cfs_common as C
# track chosen on the command line (sd = BCSD input, ai = raw-coarse learned);
# empty = the legacy flat run at exp3/ root.
TRACK = sys.argv[1] if len(sys.argv) > 1 else ""
D = os.path.join(C.OUT, "AI_Results", "exp3", TRACK)
OUT = ("/home/tanpv/Progs/claude/HW_CFS_raw/ehd_cfs_exp3%s.html"
% (("_" + TRACK) if TRACK else ""))
WK = C.WEEK_NAMES
POOLED = {"W1": 0.7874, "W2": 0.6090, "W3": 0.5699, "W4": 0.5602,
"W5": 0.5598, "W6": 0.5167} # best pooled AUC, section 2 of the
REQ = {"W1": 0.8577, "W2": 0.8924, "W3": 0.8916, # main evaluation report
"W4": 0.8873, "W5": 0.8876, "W6": 0.8867}
CSS = open(os.path.join(os.path.dirname(__file__), "_report_css.txt")).read()
def fig(fn):
buf = io.BytesIO(); fn()
plt.savefig(buf, format="png", dpi=130, bbox_inches="tight"); plt.close()
return "" % \
base64.b64encode(buf.getvalue()).decode()
def load():
out = {}
for f in sorted(os.listdir(D)):
if f.endswith(".json"):
out[int(f[2:4])] = json.load(open(os.path.join(D, f)))
return out
def cell(k, v):
tg = {"POD": (">", .6), "POFD": ("<", .2), "FAR": ("<", .2), "CSI": (">", .4)}
if k not in tg or not np.isfinite(v):
return "
%s
" % ("—" if not np.isfinite(v) else "%.3f" % v)
op, r = tg[k]
ok = v > r if op == ">" else v < r
return "
%.3f
" % ("good" if ok else "bad", v)
def main():
R = load()
N = R[sorted(R)[0]]["_meta"].get("NMEMBER", 12) if R else 12
tlabel = {"sd": " — nhánh cfs_0.25_sd (BCSD)",
"ai": " — nhánh cfs_0.25_AI (học hạ quy mô)"}.get(TRACK, "")
H = ["",
"EXP3 — mô hình theo từng điểm lưới%s"
% (tlabel, CSS),
"
EXP3 — mô hình riêng cho từng điểm lưới, quyết định hai tầng bằng "
"p = M/N%s
" % tlabel,
"
21 điểm (3 điểm × 7 vùng), 6 tuần, đặc trưng local + "
"large-scale. Kiểm định 2001/2006/2009.
",
"
Mỗi (điểm, tuần): 2360 mẫu huấn luyện / 726 mẫu kiểm "
"định, 72 đặc trưng (16 local + 32 meso ±3 và ±8 ô + 16 toàn miền + 4 "
"gradient + 4 điều hoà). N = %d mô hình mỗi tầng. Ngưỡng cổng P1 và ngưỡng "
"tầng 2 P2 đều chốt trên năm huấn luyện bằng dự báo ngoài nếp gấp "
"(5 nếp, chia theo NĂM). Dựng lúc %s.
"
% (N, time.strftime("%Y-%m-%d %H:%M"))]
if not R:
H.append("
chưa có kết quả
"); open(OUT, "w").write("\n".join(H)); return
# ---- 1. does per-point modelling beat the pooled ceiling?
H.append("
1. Mô hình theo điểm có phá được trần của mô hình gộp không?
")
H.append("
AUC trên năm kiểm định, trung vị trên 21 điểm
"
"
Tuần
EXP3 reg (trung vị)
EXP3 clf (trung vị)
"
"
EXP3 clf (tốt nhất/21)
Mô hình gộp
AUC cần
"
"
Còn thiếu
")
med = {}
for w in WK:
a = {k: [R[i][w][k]["AUC_p1"] for i in R
if w in R[i] and k in R[i][w]] for k in ("reg", "clf")}
if not a["clf"]:
continue
med[w] = (float(np.median(a["reg"])), float(np.median(a["clf"])),
float(np.max(a["clf"])))
H.append("
")
def f1():
x = np.arange(len(med))
plt.figure(figsize=(7.5, 3.6))
plt.plot(x, [med[w][1] for w in med], "o-", label="EXP3 theo điểm (clf)")
plt.plot(x, [med[w][0] for w in med], "s-", label="EXP3 theo điểm (reg)")
plt.plot(x, [POOLED[w] for w in med], "^--", label="mô hình gộp toàn quốc")
plt.plot(x, [REQ[w] for w in med], "k-", lw=2, label="AUC cần có")
plt.xticks(x, list(med)); plt.ylabel("AUC"); plt.grid(alpha=.3)
plt.legend(fontsize=8); plt.ylim(.45, .95)
H.append(fig(f1))
# ---- 2. the gate
H.append("
2. Cổng tầng 1 — đóng được bao nhiêu mẫu, mất bao nhiêu sự kiện
")
H.append("
Đây là con số quyết định EXP3 có đáng dùng không. "
"Đóng 60% mẫu mà chỉ mất 3% sự kiện là món hời; đóng 77% mà mất 71% "
"sự kiện thì không hơn đóng ngẫu nhiên bao nhiêu.
")
H.append("
Trung vị trên 21 điểm, đường đánh đổi ngoài nếp gấp "
"trên năm huấn luyện (ensemble clf)
"
"
Tuần
P1 chọn được
% mẫu đóng (train)
"
"
% mẫu giữ (test)
% sự kiện test bị cổng giết
"
"
so với đóng ngẫu nhiên
")
for w in WK:
g = [R[i][w]["clf"]["gate"] for i in R if w in R[i] and "clf" in R[i][w]]
if not g:
continue
p1 = np.median([x["P1"] for x in g])
clo = np.median([1 - x["kept_frac_train"] for x in g])
kep = np.median([x["kept_frac"] for x in g])
lost = np.median([x["events_lost"] / max(x["events_total"], 1) for x in g])
H.append("
")
any_pt = R[sorted(R)[0]]
for w in WK:
if w in any_pt and "clf" in any_pt[w]:
m1 = any_pt[w]["clf"]["members1"]
m2 = (any_pt[w]["clf"]["stage2"] or {}).get("members", [])
H.append("
Tầng 1 (N = %d): %s
"
% (len(m1), escape(", ".join(m1))))
H.append("
Tầng 2 (N = %d): %s
"
% (len(m2), escape(", ".join(m2)) or "—"))
break
H.append("
Cùng một bộ họ mô hình cho cả hai ensemble, nên khác "
"biệt giữa reg và clf chỉ đến từ TARGET "
"(hồi quy Sev_obs so với phân loại sự kiện), không phải từ lớp mô hình. "
"lgbm = LightGBM, etree = ExtraTrees, rforest = RandomForest, "
"hgb = HistGradientBoosting, mlp = mạng nơ-ron, "
"ridge/enet/logreg = tuyến tính, knn = láng giềng gần nhất.