Detection Techniques Deep Dive

NER vs Regex vs Rules: PII Detection Techniques Compared

Regular expressions, dictionaries, checksum validators, and transformer-based NER each catch a different slice of personal data — and each fails in a different way. Here is how they actually compare, entity type by entity type, and how production systems combine them.

Compare Techniques

The PII Detection Technique Landscape

Every PII detector ever built answers the same question — "which spans of this text refer to a person or their identifiers?" — but the four major families of techniques answer it with completely different machinery. Regular expressions match character patterns. Dictionaries (gazetteers) match known strings against curated lists. Validators apply mathematical or structural rules, such as check digits, to confirm that a candidate string could be a real identifier. Named entity recognition — today almost always transformer-based — reads the text the way a human does and predicts, token by token, which spans denote which entity types.

The families are not interchangeable, and the marketing framing of "AI vs regex" obscures the real engineering question. Regex is unbeatable for some entity types and useless for others; NER is essential for unformatted PII but benefits enormously from validators cleaning up its numeric detections. Choosing a technique is really choosing a point on three axes at once: recall (what fraction of true PII you find), precision (what fraction of your detections are real), and operational cost (latency, compute, and the human effort of maintaining rules).

The decisive variable is the shape of the entity. Identifiers with a rigid machine-defined syntax — email addresses, IPv4 addresses, IBANs — sit at one extreme, where patterns describe the entity almost perfectly. Human-language entities — names, addresses, health conditions, dates written in prose — sit at the other extreme, where no finite pattern can enumerate the valid surface forms. Most real corpora contain both extremes, which is why every serious production system ends up hybrid.

This guide walks through each family's mechanics and failure modes, then puts them side by side in a per-entity accuracy table, and finishes with the hybrid architecture used by the PII Detection API itself: pattern and validator layers fused with a multilingual transformer NER model, exposed through one endpoint with a tunable confidence threshold. If you want to jump straight to observed behavior, paste your own worst-case text into the live demo and watch which techniques would have caught what.

Regex: Strengths and Hard Limits

Regular expressions earn their permanent place in PII detection through four properties. They are fast — a compiled pattern scans megabytes per second on one core, with no model loading and no GPU. They are deterministic — the same input always produces the same output, which simplifies testing and incident reproduction. They are auditable — a compliance reviewer can read \b\d{3}-\d{2}-\d{4}\b and verify exactly what it claims to catch. And they are cheap to deploy — every language runtime ships an engine, so a regex layer can live inside a logging library or a database trigger where a model cannot.

Those strengths apply only where the entity has a fixed surface syntax. The limits appear immediately outside that zone. Regex cannot detect unformatted PII: no pattern matches "my neighbor Priya moved back to Lagos after the diagnosis," yet that sentence contains a name, a location, and an implied medical fact. Regex also produces lookalike false positives: a nine-digit order number matches an SSN pattern, a sixteen-digit tracking code matches a card pattern, and a build timestamp matches a date-of-birth pattern. Pattern engines have no notion of meaning, only of shape, so every collision between an identifier format and ordinary business data becomes noise your reviewers must triage.

Internationalization multiplies the maintenance burden. Phone numbers alone span hundreds of national formats with optional country codes, extensions, and separator conventions; postal addresses, national IDs, and dates each add their own matrix of country-specific patterns. Teams that start with five patterns routinely end up curating hundreds, each one a small liability that can silently rot when an upstream format changes. And carelessly written patterns carry a performance trap: nested quantifiers can trigger catastrophic backtracking, where a single adversarial input pins a CPU for minutes — a real denial-of-service vector when the regex runs inside a request path.

The honest summary: regex is a precision instrument for a minority of entity types and a liability when stretched beyond them. The per-entity table below makes the boundary concrete.

Rule of thumb: if you cannot write down the complete grammar of an entity's surface forms, regex alone will miss a material fraction of it. Names, addresses, and free-text medical details all fail this test — no pattern library ever closes the gap.

Dictionaries & Gazetteers

Dictionary matching — checking tokens against curated lists of known names, cities, drugs, or employers — predates machine learning NER and still appears inside many detection stacks. It is best understood as a recall booster with a built-in ambiguity problem.

Name Lists

Census-derived first- and surname lists cover the most common names in a population and can flag capitalized tokens that match. Coverage is the weakness: name distributions have an enormous long tail, immigration constantly imports names absent from any national census file, and transliteration produces dozens of spellings per name. A list that covers 90% of a population by frequency still misses a large share of distinct individuals — often exactly the minority names whose holders are most exposed by a leak.

Location Gazetteers

Geographic gazetteers (GeoNames-style databases) list cities, regions, and street-name vocabularies, supporting address and location detection. They work well for unambiguous place names but collide constantly with ordinary English: Reading, Nice, Mobile, and Of (a village in Turkey) are all real places. Without context, a gazetteer either over-fires on prose or is filtered so aggressively that genuine locations slip through.

The "April Bloom" Problem

Dictionary ambiguity has a canonical illustration: is "April Bloom" a person, or a phrase about spring flowers? Amber, Rose, Sky, Mark, Bill, Grace, Junior — thousands of legitimate names double as common words. A pure lookup approach must either flag every occurrence (destroying precision) or maintain fragile stop-lists (destroying recall for people actually named Bill Mark). Only context resolves the ambiguity, which is precisely what lookup methods lack.

Where Dictionaries Still Earn Their Keep

Inside hybrid systems, dictionaries survive as features and specialized vocabularies rather than standalone detectors: drug-name and diagnosis lexicons sharpen medical-entity detection, employer and institution lists support employment data, and domain-specific terms (internal project codenames, VIP client names) can be layered on top of a model's output as custom rules. As the sole detection mechanism, they are obsolete.

Checksums & Validators: The Precision Layer

Between dumb patterns and full NER sits an underrated family: validators, which apply the mathematical and structural rules that real identifiers must satisfy. They rarely find anything on their own; their job is to reject lookalikes that pattern or model layers propose, converting mediocre precision into excellent precision at almost zero cost.

The classic example is the Luhn algorithm for payment cards. Every valid card number's digits satisfy a mod-10 checksum, so a random sixteen-digit string passes only 10% of the time. A regex for card shapes might flag every order number and tracking code in your logs; adding a Luhn check silently discards nine out of ten of those false positives before a human ever sees them. Combined with issuer-prefix rules (BIN ranges), the false-positive rate drops further still.

Nearly every serious identifier has an equivalent rule. IBANs embed a mod-97 checksum over the rearranged account string, plus per-country length tables — a candidate IBAN either satisfies ISO 13616 arithmetic or it is not an IBAN. US SSNs have structural constraints rather than a checksum: area group "000", "666", and 900–999 are never issued, and group/serial zeros are invalid, which rules out many nine-digit lookalikes. National IDs elsewhere are stricter: Spain's DNI ends in a computable check letter, China's resident ID carries an ISO 7064 check digit, and so on through dozens of national schemes. Even routing numbers, IMEIs, and credit-card expiration plausibility can be validated arithmetically.

Validators have one blind spot worth naming: they evaluate validity, not sensitivity. A test card number from payment-gateway documentation passes Luhn perfectly, and a real SSN typed with one digit wrong fails structural checks even though the surrounding sentence obviously discusses a real person's SSN. That is why validators belong inside a pipeline — tightening precision on candidates that context has already made plausible — rather than acting as the final arbiter of what is sensitive. The detection engine behind this site runs exactly such validator layers on its numeric entity types; the full identifier catalogue is on the entities page.

Transformer-Based NER: Reading for Meaning

Modern NER models are transformers fine-tuned for token classification: the input text is split into subword tokens, each token is encoded into a contextual embedding — a vector that represents the token as used in this exact sentence — and a classification head predicts, for every token, whether it begins or continues an entity span of a given type. The output is a set of typed spans with character offsets and a probability, which is exactly the detected_entities structure the API returns: type, matched text, start/end, and a confidence score.

Contextual embeddings are the whole trick. In a static word list, "Bloom" is one entry with one meaning. In a transformer, the vector for "Bloom" in "Ms. Bloom will call you" is computed from the surrounding tokens and lands in a completely different region than "Bloom" in "the cherry trees bloom in April." The model never needs a rule about honorifics; it has absorbed from training data that the pattern honorific + capitalized token + verb of communication overwhelmingly indicates a person. That is why NER handles the unformatted majority of PII — names, addresses in prose, ages, employers, health conditions — that no pattern or list can enumerate.

Two further properties matter operationally. First, multilinguality: transformers pre-trained on multilingual corpora transfer entity knowledge across languages, so one model detects names and addresses in Spanish, German, Japanese, or Arabic text without per-language rule sets — the API covers 60+ languages this way (see supported languages). Second, calibrated confidence: every span carries a score, giving you a single dial to trade precision against recall. A compliance sweep can run at threshold: 0.3 to maximize recall; a real-time chat filter can run at 0.7 to avoid interrupting users with false alarms.

The costs are real but bounded: NER needs meaningful compute (typically served behind an API rather than embedded in every process), adds tens to a few hundred milliseconds of latency, and is probabilistic — identical-looking inputs can score differently near the decision boundary, which is why measurement discipline matters. How to quantify that behavior with span-level precision and recall is the subject of our accuracy measurement guide.

Context Windows: How Surrounding Tokens Decide

The practical difference between pattern matching and NER is easiest to see on a single ambiguous string. Take the digits 4556 7375 8689 9855. In "please charge card 4556 7375 8689 9855," the nearby token card pushes the model decisively toward CREDIT_CARD_NUMBER. In "your confirmation number is 4556 7375 8689 9855," the token confirmation pushes it away. A regex sees the identical sixteen digits in both sentences and must fire — or not fire — identically in both.

This works because self-attention lets every token weigh every other token in the window when computing its embedding. The evidence the model exploits is exactly what a human reviewer would cite: trigger words ("SSN:", "born on", "lives at"), syntactic role (object of "call" vs subject of "expired"), document register (a medication name in a clinical note vs in a pharmacy's stock report), and even cross-sentence cues within the window. Context also sets entity boundaries: in "transferred to Sacred Heart Medical Center, Portland," the model must decide where the facility name ends and the city begins — a span decision no character pattern can express.

Context windows have finite size, which creates two practical rules for API users. Send coherent units — a whole ticket, log line group, or paragraph — rather than isolated fragments, because a bare "Rodriguez, 4/12/85" carries far less signal than the sentence around it. And when you must chunk long documents under the 50,000-character request limit, overlap chunk boundaries by a sentence or two so entities straddling a boundary are seen with context at least once.

Why this matters for tuning: context is also what the custom_instruction parameter manipulates. An instruction like "employee ID numbers in the format EMP-XXXXX are not PII" gives the engine document-level context that suppresses a systematic false positive without touching any regex or retraining anything.

Accuracy Characteristics by Entity Type

The table summarizes how each technique family typically performs per entity type. "High/Medium/Low" describe the achievable operating point for a well-built implementation of that technique alone; the final column notes the combination production systems actually use.

Entity Type Regex Alone Dictionary Alone + Checksum/Validator Transformer NER Best Production Combo
EMAIL_ADDRESS Precision high, recall high — near-perfect syntax Not applicable Domain plausibility adds little High both; adds obfuscated forms ("john dot doe at gmail") Regex + NER for obfuscations
CREDIT_CARD_NUMBER Recall high, precision low (any 13–19 digits) Not applicable Luhn + BIN lifts precision to high High; context separates cards from order codes Pattern + Luhn + NER context
SSN / national IDs Medium precision; 9-digit lookalikes fire constantly Not applicable Structural rules (invalid areas, check letters/digits) lift precision High; trigger words ("SSN", "NI number") disambiguate unformatted forms Per-country validator + NER
PHONE_NUMBER Medium/medium — international formats explode pattern count Not applicable Country-code & length plausibility helps High both, all locales, incl. "call me on five five five…" NER with light format validation
PERSON_NAME Not viable Medium recall, low precision (April Bloom problem) Not applicable High both — the flagship NER entity NER; lists only as extra features
ADDRESS Low — format diversity defeats patterns Low precision from place-name ambiguity Postal-code format checks only High; resolves span boundaries across street/city/region NER + gazetteer features
DATE_OF_BIRTH High recall on date shapes, but cannot tell DOB from any other date Not applicable Range plausibility (age 0–120) helps slightly High — "born 4/12/85" vs "invoiced 4/12/85" is a context call NER with date-format normalization
MEDICAL_DATA / DIAGNOSIS Not viable Medium via clinical lexicons; misses lay phrasing ("her heart thing") Not applicable High; understands colloquial and clinical phrasing NER + medical lexicon features
API_KEY / PASSWORD / secrets High for known prefixes (AKIA…, sk-…); low for generic tokens Provider prefix lists help Entropy scoring is the effective "validator" High; flags "the admin password is hunter2" with no pattern at all Prefix patterns + entropy + NER

A second lens — operational rather than accuracy — completes the picture:

Property Regex Dictionary Validators Transformer NER
Throughput / latency Fastest; microseconds Fast with proper indexing Negligible overhead Tens–hundreds of ms; batch to amortize
Maintenance burden High — pattern sprawl per format & country High — lists decay continuously Low — identifier math rarely changes Low for users of a managed API; model updates arrive centrally
Multilingual coverage Per-language rework Per-language lists Mostly language-independent Native — one model, 60+ languages
Explainability Fully transparent Fully transparent Fully transparent Score-based; explain via confidence + matched span

Hybrid Architectures: How the Layers Compose

No credible production detector is single-technique. The question is not "NER or regex?" but "in what order, and who overrides whom?" Three composition patterns cover nearly every deployment.

Regex Prefilter → NER Full Scan

When scanning enormous corpora (logs, archives, data lakes), a cheap local regex pass first triages which documents plausibly contain formatted identifiers, and the context-aware API pass then scans the flagged subset plus a random sample of the "clean" remainder. The sample is essential — it measures what the prefilter misses (chiefly names and prose PII) so the triage rule is a measured cost decision, not a blind spot.

  • Cuts API volume dramatically on sparse corpora
  • Random-sample audit keeps the prefilter honest
  • Never use prefilter-only results for compliance sign-off

NER Detect → Validator Confirm

The inverse composition runs inside the detection engine itself: the model proposes candidate spans with confidence scores, and validators then confirm or veto numeric candidates — Luhn for cards, mod-97 for IBANs, structural rules for SSNs. A card-shaped number in card-shaped context that fails Luhn is either a typo'd real card or a fake; the engine reflects that uncertainty in a lower confidence score rather than a hard yes/no, and your threshold decides the outcome.

  • Checksum pass/fail modulates confidence, not existence
  • Kills the classic "order number flagged as card" ticket
  • Built into the API — you consume it as better precision

Rule Overrides on Top

The final layer is organization-specific policy: entity scoping and plain-language exceptions. With the API this is configuration, not code — entities / exclude_entities scope a scan to the types a use case cares about, threshold sets the precision-recall operating point, and custom_instruction expresses exceptions like "ticket references formatted TKT-nnnnn are not identifiers." Keep these overrides in version-controlled config and re-run your evaluation set when they change.

  • Scope PCI scans to card-family entities only
  • Suppress systematic internal-ID false positives declaratively
  • Treat tuning parameters like any other reviewed config

Code: Regex vs API, Side by Side

The fastest way to internalize the comparison is to run both approaches on the same text. Start with the API baseline — one call, scoped to a few entity types, with an explicit threshold:

cURL
curl -X POST https://piidetectionapi.com/api/moderate.php \
  -H "Content-Type: application/json" \
  -d '{
    "api_key": "YOUR_API_KEY",
    "api_type": "pii_detection",
    "text": "Card 4556737586899855 declined for A. Okafor; call her on 0044 7911 123456. Order 4556737586899855001 shipped.",
    "entities": ["CREDIT_CARD_NUMBER", "PERSON_NAME", "PHONE_NUMBER"],
    "threshold": 0.6
  }'

Next, a Python script that pits a naive regex-plus-Luhn scanner against the API on identical input. Run it and compare the two result sets: the regex layer finds the well-formed card number (and correctly rejects the Luhn-invalid order code), but is structurally blind to the abbreviated name and the internationally formatted phone number:

Python
import re, requests

TEXT = ("Card 4556737586899855 declined for A. Okafor; "
        "call her on 0044 7911 123456. Order 4556737586899855001 shipped.")

def luhn_ok(number: str) -> bool:
    digits = [int(d) for d in number]
    checksum = 0
    for i, d in enumerate(reversed(digits)):
        if i % 2 == 1:
            d *= 2
            if d > 9:
                d -= 9
        checksum += d
    return checksum % 10 == 0

# --- Technique 1: regex + Luhn validator ---
regex_hits = []
for m in re.finditer(r"\b\d{13,19}\b", TEXT):
    verdict = "CREDIT_CARD_NUMBER" if luhn_ok(m.group()) else "rejected (Luhn)"
    regex_hits.append((m.group(), m.start(), verdict))

print("regex+Luhn:")
for hit in regex_hits:
    print("  ", hit)

# --- Technique 2: context-aware NER via the API ---
resp = requests.post(
    "https://piidetectionapi.com/api/moderate.php",
    json={
        "api_key": "YOUR_API_KEY",
        "api_type": "pii_detection",
        "text": TEXT,
        "threshold": 0.5,
    },
    timeout=30,
)
print("API NER:")
for e in resp.json()["detected_entities"]:
    print(f"   {e['type']:20s} {e['text']!r} "
          f"[{e['start']}:{e['end']}] conf={e['confidence']:.2f}")

Finally, the hybrid triage pattern in Node.js — a cheap local prefilter decides whether a document is obviously hot, and the API provides the authoritative scan whose results are merged with (and take precedence over) the prefilter's guesses:

JavaScript (Node)
const PREFILTER = [
  { type: "EMAIL_ADDRESS", re: /[\w.+-]+@[\w-]+\.[\w.]+/g },
  { type: "CANDIDATE_NUMBER", re: /\b\d{9,19}\b/g },
];

function prefilter(text) {
  const hits = [];
  for (const { type, re } of PREFILTER) {
    for (const m of text.matchAll(re)) {
      hits.push({ type, text: m[0], start: m.index, source: "regex" });
    }
  }
  return hits;
}

async function scan(text) {
  const cheap = prefilter(text);
  // Escalate to full NER when the prefilter fires — plus a 5%
  // random audit of "clean" docs so prose-only PII is measured.
  if (cheap.length === 0 && Math.random() > 0.05) {
    return { entities: [], mode: "prefilter-clean" };
  }

  const resp = await fetch("https://piidetectionapi.com/api/moderate.php", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({
      api_key: process.env.PII_API_KEY,
      api_type: "pii_detection",
      text,
      threshold: 0.5,
    }),
  });
  const { detected_entities } = await resp.json();

  // API spans are authoritative; keep prefilter hits only where
  // no API entity overlaps the same offsets.
  const merged = [...detected_entities];
  for (const h of cheap) {
    const covered = detected_entities.some(
      e => h.start >= e.start && h.start < e.end
    );
    if (!covered) merged.push({ ...h, confidence: 0.4, flagged_for_review: true });
  }
  return { entities: merged, mode: "full" };
}

scan("Refund for A. Okafor, card 4556737586899855.").then(console.log);
Try it on your data: the interactive demo runs the full hybrid engine on any text you paste, and pricing starts with a free tier sized for exactly this kind of bake-off against your existing regex layer.

Frequently Asked Questions

Is regex ever enough on its own?
Occasionally, yes — when the entity universe is genuinely closed and formatted. Stripping email addresses and IPv4 addresses from crash reports, or masking your own well-known token prefixes, are legitimate regex-only jobs. The trap is scope creep: the moment the requirement becomes "make sure no personal data appears here," names and prose identifiers enter the picture, and a regex-only system fails silently — it reports zero findings while missing the majority of actual PII. If a regulator or customer will rely on the answer, regex alone is not defensible.
Why does NER output confidence scores instead of yes/no answers?
Because language is genuinely ambiguous, and the model's probability estimate is useful information rather than indecision. "Jordan" in a sentence might be a person or a country; the model may score it 0.55 as a name. A hard yes/no would force one global error trade-off on every user. Exposing the score lets each application choose: a discovery sweep accepts low-confidence spans to maximize recall, while a customer-facing redactor demands high confidence to avoid mangling innocent text. Pick the threshold per use case, and validate the choice against a labeled sample.
How much do checksum validators actually reduce false positives?
For card numbers, the arithmetic is concrete: Luhn accepts only one in ten random digit strings, so a card-shape regex whose matches are 95% order numbers and tracking codes sees roughly 90% of that noise vanish after the checksum. IBAN mod-97 is stricter still — about one in 97 random candidates passes, and per-country length rules eliminate most of the rest. Structural SSN rules are weaker (they reject perhaps a third of random nine-digit strings) which is why SSN precision leans more heavily on context. Validators are the cheapest precision you will ever buy; they just cannot create recall.
Does transformer NER really work in languages other than English?
Yes — this is one of its structural advantages over rules. Multilingual pre-training lets one model recognize person names, addresses, and identifiers across scripts and grammars, including languages where capitalization (the crutch of many English-centric heuristics) does not exist, such as Chinese or Japanese, and languages with heavy inflection like Finnish or Turkish where a name's surface form changes by case. The API detects entities in 60+ languages through this mechanism; the current list is on the supported languages page. Rule-based stacks, by contrast, require rebuilding pattern and dictionary layers per language.
Can I combine my own rules and exceptions with the API?
Yes, at three levels. Scope with entities or exclude_entities to detect only the types a workflow cares about. Express organization-specific exceptions in plain language with custom_instruction — for example, excluding internal reference numbers that superficially resemble phone numbers. And nothing stops you from running your own regex layer beside the API and merging results, as in the hybrid example above; the returned character offsets make overlap resolution straightforward. Whatever you layer on, keep it in versioned configuration and re-test after changes.
Isn't NER too slow and expensive compared to regex?
Per character scanned, regex will always be cheaper — the honest comparison is cost per correct decision. A regex sweep that floods reviewers with lookalike numbers, or silently passes prose PII into a breach, is expensive in the currency that matters. In practice, NER latency (typically 100–300 ms per request) disappears inside batch pipelines and is acceptable in most real-time paths; batching text up to the 50,000-character request limit amortizes cost dramatically; and the prefilter architecture above reserves model calls for documents that need them. See pricing to put actual numbers against your volume.

Stop Choosing — Run the Hybrid

Pattern layers, checksum validators, and multilingual transformer NER, fused behind one API call with a tunable threshold. Benchmark it against your regex stack on your own data today.

Try the Live Demo View Pricing