Melikshah/Operational-Harm / scripts /36_selectivity.py
Melikshah's picture
download
raw
10.8 kB
import argparse
import importlib
import json
import sys
import opharm
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.lines import Line2D
from opharm.bench.evaluate import ENV_WORDS
from opharm.paths import BENCH, RESULTS, ROOT
sys.path.insert(0, str(ROOT / "scripts"))
rv = importlib.import_module("16_rule_variants")
LOCAL = ("qwen35_4b", "qwen35_9b", "qwen3_4b_2507", "gemma4_e4b", "llama31_8b")
NAMES = {"qwen35_4b": "Qwen3.5-4B", "qwen35_9b": "Qwen3.5-9B", "qwen3_4b_2507": "Qwen3-4B-2507", "gemma4_e4b": "Gemma-4-E4B",
"llama31_8b": "Llama-3.1-8B", "gemini38_flash": "Gemini 3.8 Flash", "gemini31_pro": "Gemini 3.1 Pro"}
MODE_COLOR = {"direct": "#2a78d6", "narration": "#eb6834", "thinking": "#1baf7a"}
INK, QUIET, GRID = "#0b0b0b", "#52514e", "#e1e0d9"
MARKER = {"registered": "o", "rule_variant": "^", "trigger": "s", "recovery": "D", "self_check": "*", "regex_guard": "X"}
FAMILY = {"registered": "registered rule", "rule_variant": "other rule wordings", "trigger": "harmless trigger rule",
"recovery": "recovery rule (probe)", "self_check": "self-check wrapper (simulated)", "regex_guard": "regex guard on the environment word"}
CELLS, FREE = ("DP", "DS", "BP", "BS"), ("DS", "BP", "BS")
def read(name):
for stem in (name + "_final", name):
path = RESULTS / f"{stem}.json"
if path.exists():
return json.loads(path.read_text()), stem
return {}, None
def est(entry):
return None if entry is None else entry["estimate"]
def row(model, family, condition, mode, split, need, over, source, paired=None, **extra):
sel = paired["estimate"] if paired else None if need is None or over is None else round(need - over, 4)
return {"model": model, "family": family, "condition": condition, "mode": mode, "split": split, "required_ask": need, "over_ask": over,
"selectivity": sel, "selectivity_ci_template": (paired or {}).get("ci_template"), "source": source, **extra}
def collect(models):
rows = []
for m in models:
modes, src = read(f"modes_{m}")
for mode, s in modes.get("splits", {}).get("heldout", {}).get("modes", {}).items():
rows.append(row(m, "registered", "registered", mode, "heldout", est(s["cells"]["DPC"]["ask"]), est(s["over_ask"]), src, s["ask_selectivity"],
required_ci_template=(s["cells"]["DPC"]["ask"] or {}).get("ci_template"), over_ci_template=(s["over_ask"] or {}).get("ci_template"),
required_ask_covered=est(s["covered_DPC"]["ask"]), paired_correct=est(s["paired_correct_abstention"])))
rules, src = read(f"rules_{m}_v2")
for name, c in rules.get("conditions", {}).items():
need = [x for x in CELLS if rv.gold(name, x) == "abstain"]
free = [x for x in CELLS if rv.gold(name, x) == "act"]
if name in ("registered", "trigger_present", "trigger_absent") or not need or not free:
continue
mean = lambda cells: round(float(np.mean([c[f"{x}_asks"][0] for x in cells])), 4)
rows.append(row(m, "rule_variant", name, "direct", "dev", mean(need), mean(free), src))
trig, src = read(f"trigger_{m}")
for mode, s in trig.get("runs", {}).items():
a, b = s["by_variant"]["trigger_present.all"]["ask"], s["by_variant"]["trigger_absent.all"]["ask"]
rows.append(row(m, "trigger", "trigger", mode, "dev", est(a), est(b), src, s["paired"]["all"]["ask_present_minus_absent"],
cells=s.get("cells"), required_ci_template=(a or {}).get("ci_template"),
over_ci_template=(b or {}).get("ci_template"), paired_correct=est(s["paired"]["all"]["paired_correct"])))
rec, src = read(f"recovery_{m}")
for mode, s in rec.get("runs", {}).items():
for e in ("state", "health", "drill"):
a, b = s["cells"][f"R.{e}.L"]["ask"], s["cells"][f"R.{e}.R"]["ask"]
rows.append(row(m, "recovery", e, mode, "probe", est(a), est(b), src, s["selectivity"][f"R.{e}"], required_ci_template=(a or {}).get("ci_template"),
over_ci_template=(b or {}).get("ci_template"), paired_correct=est(s["paired_correct"][f"R.{e}"])))
sec, src = read(f"secondary_{m}")
check = sec.get("splits", {}).get("heldout", {}).get("self_check_from_tool_free_q1")
if check and all(check[x + "C"]["self_check_ask"] for x in CELLS):
rows.append(row(m, "self_check", "ask when its own q1 answer is yes", "simulated", "heldout", check["DPC"]["self_check_ask"][0],
round(float(np.mean([check[x + "C"]["self_check_ask"][0] for x in FREE])), 4), src))
if (BENCH / "instances.jsonl").exists():
flag = {x: [] for x in CELLS}
for r in map(json.loads, open(BENCH / "instances.jsonl")):
if r["set"] == "main" and r["split"] == "heldout" and r["policy"] == "C":
flag[r["target"] + r["env"]].append(bool(ENV_WORDS["P"].search("".join(t for _, t in r["user"]))))
rows.append(row("none", "regex_guard", "ask when the ticket names production", "none", "heldout", round(float(np.mean(flag["DP"])), 4),
round(float(np.mean([v for x in FREE for v in flag[x]])), 4), "benchmark/instances.jsonl"))
return rows
def figure(rows, models):
shown = [m for m in models if any(r["model"] == m for r in rows)]
cols = min(3, len(shown))
lines = -(-len(shown) // cols)
fig, axes = plt.subplots(lines, cols, figsize=(3.1 * cols, 3.25 * lines + 0.9), sharex=True, sharey=True, squeeze=False, layout="constrained")
guard = next((r for r in rows if r["family"] == "regex_guard"), None)
for ax, m in zip(axes.ravel(), shown):
ax.plot([0, 1], [0, 1], color=GRID, lw=1.2, zorder=1)
own = [r for r in rows if r["model"] == m and r["required_ask"] is not None and r["over_ask"] is not None]
path = [r for mode in ("direct", "narration", "thinking") for r in own if r["family"] == "registered" and r["mode"] == mode]
ax.plot([r["over_ask"] for r in path], [r["required_ask"] for r in path], color=QUIET, lw=0.9, zorder=2)
for r in sorted(own, key=lambda r: r["family"] != "rule_variant"):
small, drill = r["family"] == "rule_variant", r["family"] == "recovery" and r["condition"] == "drill"
ax.plot(r["over_ask"], r["required_ask"], MARKER[r["family"]], color=MODE_COLOR.get(r["mode"], QUIET), ms=5 if small else 8 if r["family"] != "self_check" else 11,
mec=INK if drill else "white", mew=1.3 if drill else 1.0, alpha=0.75 if small else 1.0, zorder=5 if drill else 3 if small else 4, clip_on=False)
if guard:
ax.plot(guard["over_ask"], guard["required_ask"], MARKER["regex_guard"], color=INK, ms=7, mec="white", mew=0.8, zorder=4, clip_on=False)
ax.set_title(NAMES.get(m, m), fontsize=9.5, color=INK, loc="left")
ax.set_xlim(-0.03, 1.03)
ax.set_ylim(-0.03, 1.03)
ax.set_aspect("equal")
ax.set_xticks([0, 0.5, 1], ["0", "50%", "100%"])
ax.set_yticks([0, 0.5, 1], ["0", "50%", "100%"])
ax.tick_params(labelsize=8, colors=QUIET, length=0)
ax.grid(color=GRID, lw=0.6)
ax.set_axisbelow(True)
for side in ax.spines.values():
side.set_visible(False)
first = axes[0, 0]
first.text(0.02, 1.0, "ideal", fontsize=7.5, color=QUIET, va="top")
first.text(0.60, 0.50, "no selectivity", fontsize=7.5, color=QUIET, rotation=45, rotation_mode="anchor", ha="center", va="top")
for ax in axes.ravel()[len(shown):]:
ax.set_visible(False)
for column in axes.T:
last = [ax for ax in column if ax.get_visible()][-1]
last.tick_params(labelbottom=True)
last.set_xlabel("asks where the rule does not require it", fontsize=8.5, color=INK)
for ax in axes[:, 0]:
ax.set_ylabel("asks where the rule requires it", fontsize=8.5, color=INK)
handles = [Line2D([], [], marker="o", ls="", color=c, mec="white", ms=8, label=f"{k} mode") for k, c in MODE_COLOR.items()]
handles.append(Line2D([], [], color=QUIET, lw=0.9, label="registered rule across modes"))
handles += [Line2D([], [], marker=MARKER[f], ls="", color=INK if f == "regex_guard" else QUIET, mec="white", ms=11 if f == "self_check" else 8,
label=FAMILY[f]) for f in MARKER if any(r["family"] == f for r in rows)]
if any(r["family"] == "recovery" for r in rows):
at = next(i for i, h in enumerate(handles) if h.get_label() == FAMILY["recovery"]) + 1
handles.insert(at, Line2D([], [], marker="D", ls="", color=QUIET, mec=INK, mew=1.3, ms=8, label="recovery rule with the restore drill"))
fig.legend(handles=handles, loc="outside lower center", ncol=4, fontsize=8, frameon=False, labelcolor=INK)
out = RESULTS / "figures"
out.mkdir(parents=True, exist_ok=True)
for ext in ("png", "pdf"):
fig.savefig(out / f"selectivity.{ext}", dpi=200, bbox_inches="tight", facecolor="white")
plt.close(fig)
def main():
ap = argparse.ArgumentParser()
ap.add_argument("--models", default=",".join(LOCAL) + ",gemini38_flash")
args = ap.parse_args()
models = args.models.split(",")
rows = collect(models)
out = {"sources": "a file with the suffix _final is used when it exists; each row names its source",
"definitions": {"required_ask": "share of prompts answered with a confirmation request where the rule in force requires one",
"over_ask": "the same share where the rule in force does not require one",
"registered": "DP against DS, BP and BS under the registered rule, held-out", "rule_variant": "cells per rule wording as in 16_rule_variants",
"trigger": "trigger present against trigger absent, all cells", "recovery": "last restore path against redundant path, by evidence level",
"self_check": "simulated policy that asks whenever the model's tool-free answer to q1 is yes",
"regex_guard": "asks whenever the ticket text matches the production word pattern of the evaluator"},
"rows": rows}
RESULTS.mkdir(exist_ok=True)
(RESULTS / "selectivity.json").write_text(json.dumps(out, indent=1))
figure(rows, models)
for r in rows:
print(f"{r['model']:15s} {r['family']:13s} {r['condition'][:22]:22s} {r['mode']:10s} required {r['required_ask']} over {r['over_ask']} selectivity {r['selectivity']}")
print("wrote", RESULTS / "selectivity.json", "and", RESULTS / "figures" / "selectivity.png")
if __name__ == "__main__":
main()

Xet Storage Details

Size:
10.8 kB
·
Xet hash:
8267d19537d2fbb16853c885da1eaeea4dda4fb6f16b17ae7936bafffcb2b315

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