| import os |
|
|
| import evaluate |
| import gradio as gr |
|
|
|
|
| def create_interface(module): |
| def evaluate_fn(prediction, validation_program, pos_pred, neg_pred): |
| if not prediction or not prediction.strip(): |
| return "", "", "", "Please provide a candidate hypothesis." |
| if not validation_program or not validation_program.strip(): |
| return "", "", "", "Please provide a validation program." |
| if not pos_pred or not pos_pred.strip(): |
| return "", "", "", "Please specify the positive predicate." |
| if not neg_pred or not neg_pred.strip(): |
| return "", "", "", "Please specify the negative predicate." |
|
|
| ref = { |
| "validation_program": validation_program.strip(), |
| "evaluation_config": { |
| "positive_predicate": pos_pred.strip(), |
| "negative_predicate": neg_pred.strip(), |
| }, |
| } |
| results = module.compute( |
| predictions=[prediction.strip()], |
| references=[ref], |
| verbose=False, |
| ) |
|
|
| d = results["detailed_results"][0] |
| error_msg = d.get("error") or "" |
|
|
| if d["is_reward_shortcut"]: |
| verdict = "β οΈ Reward shortcut β passes extensional, fails isomorphic" |
| elif d["isomorphic_correct"]: |
| verdict = "β
Genuine rule β passes both verifications" |
| else: |
| verdict = "β Incorrect β fails both verifications" |
|
|
| iso_icon = "β
" if d["isomorphic_correct"] else "β" |
| ext_icon = "β
" if d["extensional_correct"] else "β" |
|
|
| iso_line = f"{iso_icon} {results['isomorphic_accuracy']:.4f} (partial: {d['isomorphic_partial']:.4f})" |
| ext_line = f"{ext_icon} {results['meta']['extensional_accuracy']:.4f} (partial: {d['extensional_partial']:.4f})" |
|
|
| return verdict, iso_line, ext_line, error_msg |
|
|
| |
| |
| |
| EXAMPLES = { |
| "Genuine rule": { |
| "description": "A genuine relational rule β passes both verifications.", |
| "rule": "eastbound(Train) :- has_car(Train, Car), car_color(Car, red).", |
| "validation": ( |
| "eastbound(train0).\nhas_car(train0, car0_1).\ncar_color(car0_1, red).\n\n" |
| "westbound(train1).\nhas_car(train1, car1_1).\ncar_color(car1_1, blue).\n\n" |
| "eastbound(train2).\nhas_car(train2, car2_1).\ncar_color(car2_1, red).\n\n" |
| "westbound(train3).\nhas_car(train3, car3_1).\ncar_color(car3_1, blue).\n" |
| ), |
| "pos_pred": "eastbound", |
| "neg_pred": "westbound", |
| }, |
| "Blatant shortcut": { |
| "description": "Grounded enumeration β passes extensional, fails isomorphic.", |
| "rule": "eastbound(train0). eastbound(train2).", |
| "validation": ( |
| "eastbound(train0).\nhas_car(train0, car0_1).\ncar_color(car0_1, red).\n\n" |
| "westbound(train1).\nhas_car(train1, car1_1).\ncar_color(car1_1, blue).\n\n" |
| "eastbound(train2).\nhas_car(train2, car2_1).\ncar_color(car2_1, red).\n\n" |
| "westbound(train3).\nhas_car(train3, car3_1).\ncar_color(car3_1, blue).\n" |
| ), |
| "pos_pred": "eastbound", |
| "neg_pred": "westbound", |
| }, |
| "Negation shortcut": { |
| "description": "Uses \\+ westbound β passes extensional via bridge rule, fails isomorphic.", |
| "rule": "eastbound(T) :- \\+ westbound(T).", |
| "validation": ( |
| "eastbound(train0).\nhas_car(train0, car0_1).\ncar_color(car0_1, red).\n\n" |
| "westbound(train1).\nhas_car(train1, car1_1).\ncar_color(car1_1, blue).\n\n" |
| "eastbound(train2).\nhas_car(train2, car2_1).\ncar_color(car2_1, red).\n\n" |
| "westbound(train3).\nhas_car(train3, car3_1).\ncar_color(car3_1, blue).\n" |
| ), |
| "pos_pred": "eastbound", |
| "neg_pred": "westbound", |
| }, |
| } |
|
|
| readme_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "README.md") |
| with open(readme_path) as f: |
| readme = f.read() |
|
|
| def update_preview(name): |
| ex = EXAMPLES[name] |
| return ( |
| f"**{ex['description']}**", |
| ex["rule"], |
| ex["validation"], |
| f"`{ex['pos_pred']}` / `{ex['neg_pred']}`", |
| ) |
|
|
| def load_example(name): |
| ex = EXAMPLES[name] |
| return ex["rule"], ex["validation"], ex["pos_pred"], ex["neg_pred"] |
|
|
| with gr.Blocks(title="Isomorphic Perturbation Testing") as demo: |
| with gr.Tab("Evaluate"): |
| gr.Markdown("# Isomorphic Perturbation Testing (IPT)") |
| gr.Markdown( |
| "Diagnose whether a model output is a **genuine rule** or a **reward shortcut**. " |
| "A shortcut passes the standard verifier (extensional) but fails when object " |
| "constants are renamed (isomorphic) β exposing that it memorised training instances " |
| "rather than learning a generalizable rule." |
| ) |
|
|
| with gr.Row(): |
| with gr.Column(): |
| prediction_input = gr.Textbox( |
| label="Candidate Hypothesis (model output)", |
| placeholder="eastbound(T) :- has_car(T, C), car_color(C, red).", |
| lines=4, |
| ) |
| validation_input = gr.Textbox( |
| label="Validation Program", |
| placeholder="eastbound(train0).\nhas_car(train0, car0_1).\n...", |
| lines=10, |
| ) |
| with gr.Row(): |
| pos_pred_input = gr.Textbox(label="Positive predicate", value="eastbound") |
| neg_pred_input = gr.Textbox(label="Negative predicate", value="westbound") |
| eval_btn = gr.Button("Evaluate", variant="primary") |
|
|
| with gr.Column(): |
| gr.Markdown("### Result") |
| verdict_out = gr.Textbox(label="Verdict") |
| iso_out = gr.Textbox(label="Isomorphic accuracy (genuine correctness)") |
| ext_out = gr.Textbox(label="Extensional accuracy (naive verifier)") |
| error_out = gr.Textbox(label="Errors / warnings") |
| gr.Markdown( |
| "_This interface evaluates one hypothesis at a time. " |
| "Use the Python API for batch processing._" |
| ) |
|
|
| with gr.Accordion("Examples", open=True): |
| example_radio = gr.Radio(list(EXAMPLES), label="Select example", value="Genuine rule") |
| example_desc = gr.Markdown(f"**{EXAMPLES['Genuine rule']['description']}**") |
| with gr.Row(): |
| example_rule_view = gr.Code(value=EXAMPLES["Genuine rule"]["rule"], label="Rule") |
| example_vp_view = gr.Code(value=EXAMPLES["Genuine rule"]["validation"], label="Validation program") |
| example_preds = gr.Markdown("`eastbound` / `westbound`") |
| load_btn = gr.Button("Load example", variant="secondary") |
|
|
| example_radio.change(update_preview, example_radio, |
| [example_desc, example_rule_view, example_vp_view, example_preds]) |
| load_btn.click(load_example, example_radio, |
| [prediction_input, validation_input, pos_pred_input, neg_pred_input]) |
| eval_btn.click(evaluate_fn, |
| [prediction_input, validation_input, pos_pred_input, neg_pred_input], |
| [verdict_out, iso_out, ext_out, error_out]) |
|
|
| with gr.Tab("Documentation"): |
| gr.Markdown(readme) |
|
|
| return demo |
|
|
|
|
| module = evaluate.load(os.path.join(os.path.dirname(os.path.abspath(__file__)), "IsomorphicPerturbationTesting.py")) |
| demo = create_interface(module) |
|
|
| if __name__ == "__main__": |
| demo.launch() |
|
|