Knowledge Base

The Bake-Off: Do You Need a Neural Net to Catch 'Chief Happiness Wizard'?

Title-guard C2.5 — 2026-07-16. Every number in this post comes from a measured run on the same 4-core/8GB VPS that runs everything else in this series (`bench_semantic.py`, results in `data/eval/semantic_bakeoff.json`).

The itch we needed to scratch

Title-guard's lexical pipeline (C2.7) validates job titles at 909/s on this box with 97.7% accuracy on the frozen eval. But it has a documented tail it can't reach, and we've been honest about it since C2:

  1. Joke titles built from real words. "Chief Happiness Wizard", "Senior Vibes Coordinator", "Dragon Wrangler Executive" — every word is real, the grammar is fine, only the meaning is absurd. Lexical matching caught ~56% of these.
  2. Wrong-occupation mapping. The char+word TF-IDF nearest-neighbor maps "Head of Growth Marketing" to Soil and Plant Scientists (character overlap with "growth"), and "Pharmacovigilance Specialist" to Landscaping and Groundskeeping Workers. The titles pass validation — but the canonical occupation is nonsense, and that mapping is half the product.

The obvious answer is "use AI embeddings." But C2.7 taught us a rule we keep re-learning: lists decay, statistics hold — and also that plausible-sounding signals fail in embarrassing ways when you actually measure them (the word-pair novelty signal looked great until it flagged a third of legitimate international titles). So instead of assuming, we ran a bake-off.

The contenders

All three are built from the same corpus — 45,302 unique real titles (O*NET alternate + reported titles + 600 SEC officer titles), nothing derived from the eval sets:

Contender Type The idea
PMI composition counting Score adjacent word-pairs by normalized pointwise mutual information. "Growth Marketing" co-occurs; "Soil Marketing" doesn't. Interpretable, no neural net.
Word-bigram LM counting An interpolated bigram language model over real titles; score = mean log-probability per transition. Joke titles should have improbable word sequences.
Static embeddings neural Model2Vec potion-base-8M (8M params, 256-dim, ~30MB): embed the corpus once, score a title by max cosine to any real title. Jokes land far from everything.

Plus the C2 baseline (the existing lexical score) for context.

The decision rule, written down before running: if a counting method catches the same tail as embeddings, the system stays neural-net-free. Protocol per C2.7: thresholds tuned on the frozen set only (at 80% joke recall); the holdout is scored exactly once with those frozen thresholds, no fixes afterward.

Results

Frozen set (417 titles, 9 categories) — AUC separating legit titles from garbage_joke, plus false-positive rates at the 80%-joke-recall threshold:

Scorer AUC (joke) AUC (all garbage) FPR legit overall FPR intl FPR exec
PMI 0.185 0.149 100% 100% 100%
Word LM 0.989 0.984 1.5% 0% 4.1%
Embeddings 0.975 0.983 3.4% 8% 4.1%
C2 baseline 0.959 0.987 8.7% 10% 18.4%

Holdout (423 titles, scored once, frozen thresholds):

Scorer AUC (joke) Joke recall FPR legit overall
PMI 0.436 98%* 99.6%*
Word LM 0.974 90% 6.0%
Embeddings 0.955 76% 7.1%
C2 baseline 0.892 82% 20.2%

\PMI's "98% recall" is it flagging literally everything — see the autopsy.*

Three headlines:

  1. The counting method won. A word-bigram LM — 0.48s to build, no dependencies, 74,000 titles/s — beats an 8M-parameter embedding model on the joke tail, on both frozen and holdout. At the frozen threshold it catches 90% of holdout jokes at 6% legit false-positive rate; the C2 baseline managed 82% at 20%.
  2. Both new scorers held up on the holdout (AUC dropped only ~0.015–0.02). This is the "statistics hold" pattern again: neither scorer memorizes joke titles; they measure properties of title-ness. Compare C2.7, where the fictional-titles list decayed 98%→12%.
  3. PMI didn't just lose — it inverted. AUC 0.185 means jokes scored higher than legit titles. This deserves an autopsy, because the mechanism is a general lesson.

Autopsy: why PMI inverts (and why "just count word pairs" keeps failing)

Real examples from the run, with (pair count, word counts):

Title Pair npmi Seen?
Regional Sales Manager sales·manager (16 co-occurrences!) −0.23 yes, often
Software Engineer software·engineer (13) −0.12 yes
Chief Happiness Wizard happiness·wizard (0; both words OOV) +0.51 never
Senior Vibes Coordinator senior·vibes (0; "vibes" OOV) +0.19 never

Two compounding failures:

Fix the second problem by treating OOV pairs as maximally suspicious and you've reinvented the word-pair novelty signal that C2.7 already measured and rejected for flagging ⅓ of legitimate international/executive titles. Composition-counting on a 45k corpus is trapped either way: score novelty → false-flag rare-but-real; smooth it → reward gibberish. The C2.7 rejection now has a mechanism, confirmed from a second direction.

The word LM escapes the trap because interpolated backoff makes it measure familiarity, not association: an OOV word like "vibes" gets a low unigram probability directly, and frequent words raise (never lower) sequence probability. Familiarity is the right question for validity.

What embeddings are actually for: the mapping tail

Embeddings lost the joke-detection contest but decisively fixed the other tail — the wrong-occupation mappings, straight from the run:

Title C2 lexical nearest Embedding nearest
Head of Growth Marketing Soil and Plant Scientists (0.60) marketing manager (0.54)
Pharmacovigilance Specialist Landscaping and Groundskeeping Workers (0.37) pharmacologist (0.72)
Managing Director Producers and Directors (0.55) product management director (0.85)

Two clean fixes; "Managing Director" improves from absurd to merely imperfect (it should reach general/chief executives — a c-suite prior on the reference index would finish the job). So the bake-off's answer isn't "embeddings lose" — it's each tool has exactly one job: the word LM guards validity, embeddings repair the canonical mapping.

Speed and cost, measured

Component Build time Throughput (1 core) 10M records
PMI 0.45s 78,637/s (rejected)
Word LM 0.48s 74,048/s ~2.3 min
Embeddings (potion-8M) 2.97s (incl. 30MB model fetch) 4,734/s ~35 min (or ~2 min after unique-title dedup)
C2 lexical pipeline 0.42s (cached index) 273–291/s/core ~3.1h (4 cores)

The semantic layer is free relative to the existing pipeline: the word LM runs 250× faster than the current bottleneck, and even brute-force embedding of all 10M titles costs half an hour on one core. Peak RSS for the whole bench — all four scorers plus the C2 TF-IDF index — was 3.9GB, inside this box's 8GB budget; the three new scorers alone need well under 100MB (the embedding reference matrix is 45,302 × 256 float32 ≈ 44MB).

For the "do I need OpenAI/Claude/Gemini?" question the numbers are now concrete: a local 30MB static model scored 20,000 titles in 4.2 seconds on one core of a budget VPS. API embeddings would cost pennies (~4M tokens ≈ $0.08 for a deduped 10M-record corpus) but add rate limits and a dependency for quality this task doesn't need. LLM-as-judge stays where the cascade design put it: the final 1–2% uncertain band only (~$50–150 per 10M-record run with a Haiku-class model), never bulk.

Pros and cons

Word-bigram LMpros: best joke-tail accuracy, survives the holdout, 74k/s, half-second build, zero dependencies, fully interpretable (you can print the improbable transition that fired). Cons: no semantic understanding — it validates but cannot map; a fluent-sounding fake ("Regional Development Manager" for a company with no such role) sails through, because that's a truth problem, not a language problem — that's what the C3 evidence layer is for.

Static embeddingspros: fixes the mapping failures the product has carried since C2; 30MB; fast enough to run on everything; also a competent joke detector (holdout AUC 0.955). Cons: lost to a bigram counter on the headline task; adds a model artifact + HF fetch to the build; 256-dim vectors explain themselves less well than a bigram probability.

PMI compositionpros: the autopsy. Cons: everything else. Rejected, documented, and the rejection generalizes: this is the second independent failure of word-pair statistics on this corpus size.

The C2 baseline stays: it's still the best all-garbage detector (frozen AUC 0.987) because profanity/gibberish/placeholder flags are its home turf. Nothing here replaces it; the new layers stack on top.

What production looks like now

raw title
  → L1  C2 lexical (291/s/core): profanity, gibberish, placeholder, seniority
  → L1½ word-LM gate (74k/s): joke/fluency score            ← NEW, ~free
  → L2  embedding mapping (4.7k/s): canonical occupation     ← NEW, replaces
        when lexical similarity is low or occupations disagree   worst mappings
  → L3  evidence layer (C3 design): ATS / registries / filings — truth, not language

Limitations, honestly

The takeaway

We asked whether the joke-title tail needs a neural net. Measured answer: no for detection, yes (a tiny one) for mapping. A 0.48-second bigram model beat an 8M-parameter embedding model at catching absurd titles — while the embedding model quietly fixed the product's documented class of mapping bugs for 30MB and three seconds of build time. The general lesson is the same one this series keeps paying for and cashing in: don't argue about approaches, bench them — the plausible-sounding one (PMI) finished dead last with an inverted score, and the boring one won.

Next: fold the word-LM gate into the production score, generate a fresh holdout, then start C3.1 (the offline evidence layer — H-1B bulk index + industry×occupation matrix) per the [C3 design](https://vault.binary.ovh/title-guard-c3-design).