Danny
Bilingual humanize-text-model (EN T5-small + ZH Chinese T5-small)
7cb8aac verified
Raw
History Blame Contribute Delete
5.65 kB
"""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"]]
# Deduplicate by input so samples are unique.
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)
# 1. AI -> human: BLEU + cliché removal.
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
# 2. Identity stability.
identity_ok = 0
for n, row in enumerate(identity_rows, 1):
out = humanizer.humanize(row["input_text"], num_beams=3)
# Allow light edits, no big drift: use char-level edit ratio.
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)
# 3. Protected spans.
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()