Token Classification
Transformers
Safetensors
lfm2
liquid
lfm2.5
bidirectional
masked-lm
encoder
pii
ner
privacy
multilingual
custom_code
Instructions to use LiquidAI/LFM2.5-Encoder-350M-PII-Detector with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use LiquidAI/LFM2.5-Encoder-350M-PII-Detector with Transformers:
# Use a pipeline as a high-level helper from transformers import pipeline pipe = pipeline("token-classification", model="LiquidAI/LFM2.5-Encoder-350M-PII-Detector", trust_remote_code=True)# pip install -U transformers accelerate # Load model directly from transformers import AutoTokenizer, AutoModelForTokenClassification tokenizer = AutoTokenizer.from_pretrained("LiquidAI/LFM2.5-Encoder-350M-PII-Detector", trust_remote_code=True) model = AutoModelForTokenClassification.from_pretrained("LiquidAI/LFM2.5-Encoder-350M-PII-Detector", trust_remote_code=True, device_map="auto") - Notebooks
- Google Colab
- Kaggle
Upload context_cued.py with huggingface_hub
Browse files- context_cued.py +109 -0
context_cued.py
CHANGED
|
@@ -548,3 +548,112 @@ def hybrid_with_context(text: str, model_spans: list[dict]) -> list[dict]:
|
|
| 548 |
return uniq
|
| 549 |
|
| 550 |
CONTEXT_TYPES = set(_CUES) # the 12 Group-A types this tier owns
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 548 |
return uniq
|
| 549 |
|
| 550 |
CONTEXT_TYPES = set(_CUES) # the 12 Group-A types this tier owns
|
| 551 |
+
|
| 552 |
+
|
| 553 |
+
# =========================================================================== #
|
| 554 |
+
# P2 — Group-B cue-gated capture (financial.amount / identity.date_of_birth /
|
| 555 |
+
# contact.phone / contact.postal_code). High-precision: BOTH a cue AND a
|
| 556 |
+
# value-shape must hold, so the precision-trap docs gain no new false positives.
|
| 557 |
+
# These ADD spans, authoritative for their type. Distinct from the Group-A ID
|
| 558 |
+
# tier above (those have no shape; these are shape + cue).
|
| 559 |
+
# --------------------------------------------------------------------------- #
|
| 560 |
+
# Shapes (reuse the shipped SNAP shapes so boundaries match the rest of the decode).
|
| 561 |
+
_GB_PHONE = re.compile(
|
| 562 |
+
r"(?<!\d)(?:\+?\d{1,3}[ \-.]?)?(?:\(\d{2,4}\)[ \-.]?)?\d{2,4}[ \-.]?\d{3}[ \-.]?\d{3,4}(?!\d)")
|
| 563 |
+
_GB_DATE = re.compile(
|
| 564 |
+
r"\b(?:\d{1,2}[\/.\-]\d{1,2}[\/.\-]\d{2,4}"
|
| 565 |
+
r"|\d{4}[\/.\-]\d{1,2}[\/.\-]\d{1,2}"
|
| 566 |
+
r"|(?:Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)[a-z]*\.?\s+\d{1,2},?\s+\d{4}"
|
| 567 |
+
r"|\d{1,2}\.?\s+(?:Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec|"
|
| 568 |
+
r"janv|f[ée]vr|mars|avr|mai|juin|juil|ao[ûu]t|sept|oct|nov|d[ée]c|"
|
| 569 |
+
r"ene|abr|ago|dic|gen|mag|giu|lug|set|ott)[a-zéûô]*\.?\s+\d{4}"
|
| 570 |
+
r"|\d{1,2}\s*(?:de\s+)?(?:[A-Za-zÀ-ÿ]{3,12})\s*(?:de\s+)?\d{4})\b")
|
| 571 |
+
_GB_AMOUNT = re.compile(
|
| 572 |
+
r"(?:[$€£¥₹₩]\s?\d[\d.,]*(?:\s?[KMB])?"
|
| 573 |
+
r"|\b(?:USD|EUR|GBP|JPY|CHF|CAD|AUD|INR|KRW)\s?\d[\d.,]*(?:\s?[KMB])?\b"
|
| 574 |
+
r"|\b\d[\d.,]*\s?(?:USD|EUR|GBP|JPY|CHF|INR|KRW|dollars|euros|pounds|yen)\b"
|
| 575 |
+
r"|\b\d[\d.,]*\s?[$€£¥₹₩])")
|
| 576 |
+
_GB_POSTAL = re.compile(
|
| 577 |
+
r"\b(?:\d{5}(?:-\d{4})?|[A-Z]{1,2}\d[A-Z\d]?\s?\d[A-Z]{2}|\d{4}\s?[A-Z]{2}|\d{4})\b")
|
| 578 |
+
|
| 579 |
+
# Cues (multilingual). Amount: also fires on a bare currency symbol adjacent to a
|
| 580 |
+
# number even WITHOUT a word cue (the symbol IS the cue). Phone reuses tel/mobile/...
|
| 581 |
+
_GB_CUES = {
|
| 582 |
+
"financial.amount": re.compile(
|
| 583 |
+
r"(?i)\b(?:amount|total|salary|balance|fee|price|sum|due|cost|charge|payment|"
|
| 584 |
+
r"betrag|gehalt|saldo|geb[üu]hr|preis|summe|montant|salaire|solde|frais|prix|"
|
| 585 |
+
r"importe|salario|saldo|precio|importo|stipendio|prezzo|valor|sal[áa]rio|"
|
| 586 |
+
r"金额|金額|급여|잔액|사용료|сумма|оклад|баланс|المبلغ|الراتب|"
|
| 587 |
+
r"kwota|wynagrodzenie|bel[oø]p|bel[ée]ag|summa|sum[ma]|m[ćc]e)\b"),
|
| 588 |
+
"identity.date_of_birth": re.compile(
|
| 589 |
+
r"(?i)(?:\bDOB\b|\bD\.?O\.?B\.?|date\s*of\s*birth|born(?:\s*on)?|"
|
| 590 |
+
r"geburtsdatum|geb\.?\s*am|geboren|date\s*de\s*naissance|n[ée]\s*le|"
|
| 591 |
+
r"fecha\s*de\s*nacimiento|nacid[oa]\s*el|data\s*di\s*nascita|nat[oa]\s*il|"
|
| 592 |
+
r"data\s*de\s*nascimento|geboortedatum|data\s*urodzenia|"
|
| 593 |
+
r"生年月日|生日|出生日期|생년월일|дата\s*рождения|تاريخ\s*الميلاد|"
|
| 594 |
+
r"ng[àa]y\s*sinh|วันเกิด|f[öo]delsedatum|syntym[äa]aika)"),
|
| 595 |
+
"contact.phone": re.compile(
|
| 596 |
+
r"(?i)\b(?:tel|telephone|t[ée]l[ée]phone|phone|mobile|mob|cell|cellphone|"
|
| 597 |
+
r"call|fax|whatsapp|handy|telefon|tel[ée]fono|telefono|telefoon|"
|
| 598 |
+
r"電話|手机|手機|전화|휴대폰|телефон|موبايل|هاتف|"
|
| 599 |
+
r"telefon|puhelin|tlf|s[đd]t|เบอร์โทร|m[óo]vil|celular|n[úu]mero)\b"),
|
| 600 |
+
"contact.postal_code": re.compile(
|
| 601 |
+
r"(?i)\b(?:zip|zip\s*code|postal\s*code|postcode|post\s*code|plz|postleitzahl|"
|
| 602 |
+
r"code\s*postal|c[óo]digo\s*postal|cap|codice\s*postale|c[ée]p|postcode|"
|
| 603 |
+
r"kod\s*pocztowy|邮编|邮政编码|郵便番号|우편번호|почтовый\s*индекс|"
|
| 604 |
+
r"الرمز\s*البريدي|postnummer|postinumero|m[ãa]\s*b[uư]u)\b"),
|
| 605 |
+
}
|
| 606 |
+
# Amount also fires on bare currency symbol -> number (symbol is the cue).
|
| 607 |
+
_GB_BARE_AMOUNT = re.compile(
|
| 608 |
+
r"(?:[$€£¥₹₩]\s?\d[\d.,]*(?:\s?[KMB])?|\b\d[\d.,]*\s?[$€£¥₹₩])")
|
| 609 |
+
|
| 610 |
+
# precision: a value must sit within this many chars AFTER the cue (cue->value gate).
|
| 611 |
+
_GB_WINDOW = 24
|
| 612 |
+
_GB_SHAPES = {
|
| 613 |
+
"financial.amount": _GB_AMOUNT,
|
| 614 |
+
"identity.date_of_birth": _GB_DATE,
|
| 615 |
+
"contact.phone": _GB_PHONE,
|
| 616 |
+
"contact.postal_code": _GB_POSTAL,
|
| 617 |
+
}
|
| 618 |
+
|
| 619 |
+
|
| 620 |
+
def group_b_cue_spans(text: str) -> list[dict]:
|
| 621 |
+
"""Cue+shape gated Group-B spans. A span is emitted only when a value of the
|
| 622 |
+
right SHAPE appears within _GB_WINDOW chars after a type cue (or, for amounts,
|
| 623 |
+
when a bare currency symbol abuts the number). High precision by construction:
|
| 624 |
+
no cue -> no span, so the precision traps stay clean."""
|
| 625 |
+
out = []
|
| 626 |
+
claimed = [False] * len(text)
|
| 627 |
+
cand = [] # (start, end, type, specificity)
|
| 628 |
+
|
| 629 |
+
for typ, cue_rx in _GB_CUES.items():
|
| 630 |
+
shape = _GB_SHAPES[typ]
|
| 631 |
+
for cm in cue_rx.finditer(text):
|
| 632 |
+
window = text[cm.end():cm.end() + _GB_WINDOW]
|
| 633 |
+
vm = shape.search(window)
|
| 634 |
+
if not vm:
|
| 635 |
+
continue
|
| 636 |
+
# value must START within the window (not be far downstream prose)
|
| 637 |
+
vs = cm.end() + vm.start()
|
| 638 |
+
ve = cm.end() + vm.end()
|
| 639 |
+
# require only separators/space between cue and value
|
| 640 |
+
gap = text[cm.end():vs]
|
| 641 |
+
if not re.fullmatch(r"[\s:#=.\-/() :№\"']{0,24}", gap):
|
| 642 |
+
continue
|
| 643 |
+
cand.append((vs, ve, typ, cm.end() - cm.start() + 100))
|
| 644 |
+
|
| 645 |
+
# bare currency-symbol amounts (symbol is the cue)
|
| 646 |
+
for vm in _GB_BARE_AMOUNT.finditer(text):
|
| 647 |
+
cand.append((vm.start(), vm.end(), "financial.amount", 50))
|
| 648 |
+
|
| 649 |
+
cand.sort(key=lambda c: (-c[3], -(c[1] - c[0])))
|
| 650 |
+
for vs, ve, typ, _spec in cand:
|
| 651 |
+
if vs < 0 or ve > len(text) or any(claimed[vs:ve]):
|
| 652 |
+
continue
|
| 653 |
+
if len(text[vs:ve].strip()) < 3:
|
| 654 |
+
continue
|
| 655 |
+
for i in range(vs, ve):
|
| 656 |
+
claimed[i] = True
|
| 657 |
+
out.append({"start": vs, "end": ve, "type": typ, "text": text[vs:ve]})
|
| 658 |
+
out.sort(key=lambda d: (d["start"], d["end"]))
|
| 659 |
+
return out
|