| """Benchmark the humanizer model. |
| |
| Measures: |
| - corpus BLEU (sacrebleu) between outputs and rule references on AI->human |
| held-out rows (meaning preservation), |
| - AI-cliché removal rate on AI-flavored inputs, |
| - identity stability on ALL clean-prose (identity) pairs in the corpus, |
| - protected-span preservation on ALL examples containing protected spans, |
| - latency (characters per second, MPS/CPU). |
| """ |
|
|
| import argparse |
| import json |
| import re |
| import sys |
| import time |
| from pathlib import Path |
|
|
| sys.path.insert(0, str(Path(__file__).resolve().parent.parent)) |
|
|
| from humanize import Humanizer, protect |
|
|
| EN_CLICHES = [ |
| "it is important to note that", "it is worth noting that", "moreover", |
| "furthermore", "in conclusion", "leverage", "seamless", "robust", |
| "testament", "delve", "cutting-edge", "state-of-the-art", |
| ] |
| ZH_CLICHES = [ |
| "值得注意的是", "综上所述", "赋能", "降本增效", "闭环", "无缝", |
| "由此可见", "总而言之", |
| ] |
|
|
|
|
| def scan(data_dir: str): |
| rows = [] |
| for split in ("train", "val", "test"): |
| with open(Path(data_dir) / f"{split}.jsonl", encoding="utf-8") as f: |
| for line in f: |
| rows.append(json.loads(line)) |
| return rows |
|
|
|
|
| def main(): |
| parser = argparse.ArgumentParser() |
| parser.add_argument("--model", default="checkpoints/humanize-text-model") |
| parser.add_argument("--data", default="data") |
| parser.add_argument("--ai-limit", type=int, default=500) |
| args = parser.parse_args() |
|
|
| from sacrebleu import corpus_bleu |
|
|
| humanizer = Humanizer(model_id=args.model) |
| rows = scan(args.data) |
|
|
| ai_rows = [r for r in rows if r["input_text"] != r["output_text"]][: args.ai_limit] |
| identity_rows = [r for r in rows if r["input_text"] == r["output_text"]] |
| protected_rows = [ |
| r for r in rows |
| if re.search(r"PROTECTED_\d+", r["input_text"]) or re.search( |
| r"https?://|PROTECTED_\d+|\d", r["input_text"] |
| ) |
| ] |
| protected_rows = [r for r in protected_rows if "PROTECTED_" in r["input_text"] or "https://" in r["input_text"]] |
|
|
| |
| def dedupe(items): |
| seen, out = set(), [] |
| for r in items: |
| if r["input_text"] in seen: |
| continue |
| seen.add(r["input_text"]) |
| out.append(r) |
| return out |
|
|
| ai_rows = dedupe(ai_rows) |
| identity_rows = dedupe(identity_rows) |
| protected_rows = dedupe(protected_rows) |
|
|
| print(f"AI->human rows: {len(ai_rows)} | identity rows: {len(identity_rows)} | protected rows: {len(protected_rows)}", flush=True) |
|
|
| |
| refs, hyps = [], [] |
| cliche_in = 0 |
| cliche_removed = 0 |
| t0 = time.time() |
| for n, row in enumerate(ai_rows, 1): |
| out = humanizer.humanize(row["input_text"], num_beams=3) |
| hyps.append(out) |
| refs.append(row["output_text"]) |
| lower = out.lower() |
| hits_in = [c for c in (ZH_CLICHES if row["lang"] == "zh" else EN_CLICHES) |
| if c in (row["input_text"] if row["lang"] == "zh" else row["input_text"].lower())] |
| hits_out = [c for c in (ZH_CLICHES if row["lang"] == "zh" else EN_CLICHES) |
| if c in lower] |
| if hits_in: |
| cliche_in += 1 |
| if not hits_out: |
| cliche_removed += 1 |
| if n % 50 == 0: |
| print(f" ...ai {n}/{len(ai_rows)}", flush=True) |
| bleu = corpus_bleu(hyps, [refs]).score |
|
|
| |
| identity_ok = 0 |
| for n, row in enumerate(identity_rows, 1): |
| out = humanizer.humanize(row["input_text"], num_beams=3) |
| |
| ratio = sum(1 for a, b in zip(out, row["input_text"]) if a != b) / max(len(row["input_text"]), 1) |
| if ratio <= 0.15: |
| identity_ok += 1 |
| if n % 25 == 0: |
| print(f" ...identity {n}/{len(identity_rows)}", flush=True) |
|
|
| |
| protected_ok = 0 |
| protected_total = 0 |
| failures = [] |
| for n, row in enumerate(protected_rows, 1): |
| masked, spans = protect(row["input_text"]) |
| out = humanizer.humanize(row["input_text"], num_beams=3) |
| for span in spans: |
| protected_total += 1 |
| if span in out: |
| protected_ok += 1 |
| else: |
| failures.append((row["lang"], span, out)) |
| if n % 25 == 0: |
| print(f" ...protected {n}/{len(protected_rows)}", flush=True) |
|
|
| elapsed = time.time() - t0 |
| chars = sum(len(r["input_text"]) for r in ai_rows + identity_rows + protected_rows) |
|
|
| print("=" * 60) |
| print(f"model: {args.model}") |
| print(f"corpus BLEU (AI->human, n={len(ai_rows)}): {bleu:.1f}") |
| print(f"cliche removal rate: {cliche_removed / max(cliche_in, 1):.1%} ({cliche_removed}/{cliche_in})") |
| print(f"identity stability (n={len(identity_rows)}): {identity_ok / max(len(identity_rows), 1):.1%} ({identity_ok}/{len(identity_rows)})") |
| print(f"protected-span preservation: {protected_ok}/{protected_total} " |
| f"({protected_ok / max(protected_total, 1):.1%})") |
| print(f"throughput: {chars / max(elapsed, 0.01):.0f} chars/s") |
| if failures: |
| print(f"\nprotected failures ({len(failures)}):") |
| for lang, span, out in failures[:10]: |
| print(f" [{lang}] lost {span!r} in {out[:80]!r}") |
|
|
| print("\nsample outputs:") |
| for row in ai_rows[:5]: |
| print(" IN :", row["input_text"][:100]) |
| print(" OUT:", humanizer.humanize(row["input_text"], num_beams=3)[:100]) |
| print() |
|
|
|
|
| if __name__ == "__main__": |
| main() |
|
|