data: FP benchmark + gap-fix training set for transactional A2P#209
data: FP benchmark + gap-fix training set for transactional A2P#209Frhnfaya wants to merge 10 commits into
Conversation
…rovement Evaluate the OTS mBERT classifier against public SMS-classification benchmarks and a new multilingual adversarial suite, then close the identified gaps with a targeted synthetic dataset and an incremental fine-tune. Findings (v2.5 baseline): - 99.3% accuracy on the UCI SMS Spam Collection (classic benchmark) - 99.2% block rate on the Mishra & Soni SMS Phishing dataset, but only 2.7% phishing-label recall (smishing routed to spam) - 20.4% three-class / 69.9% block rate on the modern, multilingual IMC 2025 smishing corpus, with ~25% of phishing silently passed as ham - Non-English block rates falling to ~52-56% Improvement (v2.6 incremental fine-tune): - UCI block rate held at 99.2% (no classic-spam regression) - IMC25 block rate 69.9% -> 78.2%, phishing recall 17% -> 40% - Adversarial suite block rate 73.3% -> 98.0%, phishing recall 11.5% -> 82% - Multilingual block-rate gains concentrated where the model was weak (Italian +19.6pts, Japanese +19.7pts, Spanish +13.8pts) Adds an offline, reproducible evaluation harness, the adversarial test set, the Mishra & Soni benchmark loader, a synthetic data generator with locale-correct brands and hard-negative ham twins, an incremental fine-tune script, and a full written report (evals/REPORT.md).
…ardening Training (evals/finetune_tier1.py): - Apple Silicon MPS / CUDA / CPU device auto-detection - stratified train/validation split with per-epoch validation metrics - best-checkpoint selection by validation macro-F1 (fixes the last-epoch regressions from the proof-of-concept run) - class-weighted or focal loss to counter the phishing->spam collapse - warmup + linear LR schedule, gradient clipping Calibration (evals/calibrate_thresholds.py + run_eval --logit-bias): - training-free per-class logit-bias search to recover phishing recall; on the adversarial set this alone lifts block 73%->83% and phishing recall 12%->35% on the unchanged v2.5 weights Obfuscation hardening (production path): - strip zero-width / invisible formatting characters in the preprocessor - run homoglyph + zero-width normalisation in the dynamic-batching path (previously only the per-request path normalised, so the production default never did) Adds evals/TIER1.md runbook for reproducing on Apple Silicon.
- ModelInfo gains an 'architecture' field so 'version' is unambiguously the model semver (e.g. 2.7), not the network name; fix misleading schema examples - /health now reports both api_version (platform, e.g. 2.10.0) and a model block (name/version/architecture); 'version' kept as a backward-compatible alias - add model_manager.get_model_version() for a cheap version lookup
…obustness, normalisation tests - Untrack reproducible prediction dumps; gitignore catch-all (review TelecomsXChangeAPi#3) - Vendored BERT vocab -> Git LFS + Apache-2.0 attribution (review TelecomsXChangeAPi#1) - calibrate_thresholds: importlib load (no sys.path mutation) + with-statement (review TelecomsXChangeAPi#4,TelecomsXChangeAPi#5) - run_eval: ap.error instead of assert; DEFAULT_MODEL baseline note (review TelecomsXChangeAPi#8,TelecomsXChangeAPi#9) - finetune_incremental: --num-workers arg, default 0 (macOS safety) (review TelecomsXChangeAPi#7) - settings: OTS_MBERT_MODEL_PATH override for env-driven model rollback (review TelecomsXChangeAPi#2) - health/response_models: clarify architecture/tokenizer key; mark version deprecated (review TelecomsXChangeAPi#10,TelecomsXChangeAPi#11) - add 9 unit tests for the batching-path normalisation + fallback (review TelecomsXChangeAPi#6)
…ers module, CI, perf, tests Bugs: - settings: guard _apply_model_path_override against unknown default_mbert_version (KeyError) (TelecomsXChangeAPi#1) - run_eval: warn when scoring the v2.5 baseline default instead of production weights (TelecomsXChangeAPi#3) Quality: - batching_service: log a warning when EnhancedPreprocessor import fails (no silent disable) (TelecomsXChangeAPi#4) - extract shared dataset loaders to evals/loaders.py; run_eval + calibrate import it normally (drops importlib hack) (TelecomsXChangeAPi#5) - drop unreferenced intermediate summaries summary_v2.5_mishra/v2.6/v2.6_big.json (TelecomsXChangeAPi#6) - health: de-rot the architecture/tokenizer-key comment (no review-number ref) (TelecomsXChangeAPi#9) - enhanced_preprocessing: INVISIBLE_CHARS as \u escapes with codepoint comments (set unchanged) (minor) Performance: - batching_service: ASCII fast-path in _normalize_text skips normalisation for typical traffic (TelecomsXChangeAPi#7) API: - response_models: mark HealthResponse.version Field(deprecated=True) (TelecomsXChangeAPi#8) Tests/CI: - add GitHub Actions unit-test workflow running pytest (TelecomsXChangeAPi#12) - add settings-override tests incl. KeyError guard + per-instance isolation (TelecomsXChangeAPi#13) - add ASCII fast-path test; keep raise-swallow test on non-ASCII input (TelecomsXChangeAPi#7) Docs: - TIER1.md: loosen torch pin to stable; flag in-domain validation caveat (minor) - dataset/README_SYNTHETIC.md: mark synthetic data, warn against pipeline misuse (TelecomsXChangeAPi#11)
Addresses CodeQL actions hardening finding: limit GITHUB_TOKEN scope.
- load_imc25: skip and count rows with no scam_type instead of silently labelling them phishing; document IMC25 as an all-attack corpus (no ham class), confirming the spam/phishing mapping is correct for it - use md5(usedforsecurity=False) for the deterministic placeholder seed so the loader works under FIPS-enforced environments - REPORT.md: note IMC25 measures attack recall only (no false-positive signal), flag the missing multilingual legitimate-message control as the top next addition, and mark the batching-path obfuscation normalization as done rather than pending
- ham_benchmark_v1.csv: 88-msg held-out benchmark (en/es/it/pt, 8 categories) - ham_training_v1.csv: 486-row training set (282 ham + 204 phishing hard-negatives) - build_benchmark.py / build_training.py: reproducible generators - README_fp_fix.md: problem, baseline numbers, usage - summary_baseline.json: model 2.7 baseline (67% FP overall, government 94%) Zero overlap between benchmark and training set (leakage-free). Closes the gap identified in TelecomsXChangeAPi#202 comments: transactional A2P mislabelled spam.
|
@Frhnfaya thanks for putting this together — the FP gap is real and this is exactly the control set the eval was missing. Two things I need resolved before merging: 1. The baseline doesn't reproduce. Running the PR's own repro command ( 2. The leakage guard is exact-string only, and the two sets share templates. 21/88 benchmark rows have a ≥0.85 difflib match in Even at 36%, the transactional-A2P problem stands — government at 87.5% is genuinely broken — so I want this in as the FP gate before 2.7 graduates from RC. Happy to merge once the baseline reproduces and the templates are disjoint. Minor: README references |
|
Heads-up: #202 has merged, so this PR is now retargeted to Easiest fix, since this PR is purely additive: start a fresh branch from current Everything you need is now on |
@ajamous Dug into this — it's not a checkpoint mismatch, it's a calibration mismatch. run_eval.py Tried two settings against the released 2.7 weights on the benchmark:
Could you share the exact --logit-bias value (or command) you used for the release Separately — even at your calibrated 36.4%, government is still the worst category at |
|
@Frhnfaya No bias at all. The 36.4% run is uncalibrated. Without the --logit-bias flag the harness just takes the raw argmax, so calibration can't explain the difference between our runs. I re-ran it today on your exact benchmark file from this PR: Result: 32/88 FP (36.4%). 13 went to spam, 19 to phishing. Worst categories: government 87.5% FP, telecom 75%, delivery 33%. Env: torch 2.8.0, transformers 4.53.0, CPU. Same command, same code. So the only thing left that can differ is the weights file. Mine: Can you run shasum -a 256 on your copy? A run that predicts zero phishing on 88 messages doesn't look like the released 2.7. That model leans heavily toward phishing predictions. My guess is you re-trained 2.7 locally with finetune_tier1.py, and a local retrain will not match the released file. Agreed on the main point: government is the worst category in both of our runs, so the A2P problem is real either way. Once we confirm the weights match, let's use uncalibrated + released 2.7 as the standard baseline, regenerate summary_baseline.json with the command above, and update the WEIGHT table to match. Then this is good to merge along with the template dedup fix. |
@ajamous finally Confirmed — weights match (same SHA-256), and evals/run_eval.py + loaders.py are I'll pin to torch==2.8.0 / transformers==4.53.0 in a venv, rerun, and confirm I get Government being the worst category either way is good confirmation the core finding |
|
@ajamous Traced it further — I can't even install torch==2.8.0 in a clean venv; PyPI has no Rather than chase exact version parity, could you try one thing on your end: rerun Either way — happy to standardize on whichever baseline you want as the source of |
Follows up on the false-positive discussion in #202.
What this adds
ham_benchmark_v1.csv— 88-msg held-out benchmark (en/es/it/pt, 8 categories). Block rate on this set = false-positive rate. Locked — never train on it.ham_training_v1.csv— 486-row training set: 282 realistic branded A2P ham + 204 hard-negative scam twins (labelled phishing). Gap-weighted toward government and delivery where FP is worst. Zero overlap with benchmark.build_benchmark.py/build_training.py— reproducible generators.README_fp_fix.md— problem, root cause, baseline numbers, usage.evals/results/summary_baseline.json— model 2.7 baseline evidence.Baseline (model 2.7, 88-msg benchmark)
government94%,delivery92%,appointment/telecom75%,bank_alert67%personal/personal_money0% — conversational ham is cleanReproduction
Expected: overall FP 67%, government 94%, delivery 92%.
Honest caveat: both sets are constructed-realistic, not scraped. The before/after proves the model can learn the A2P-is-ham boundary; real operator A2P would be the gold-standard confirmation on top.