--- language: - ko - en license: apache-2.0 library_name: transformers pipeline_tag: token-classification base_model: openai/privacy-filter tags: - token-classification - ner - pii - privacy - pii-masking - korean - finance - bioes - viterbi - mixture-of-experts datasets: - BCCard/pii-masking-openpii-finance metrics: - f1 - precision - recall --- # 1. Overview A Korean/English **PII detection model for the finance domain**, built by full fine-tuning [`openai/privacy-filter`](https://huggingface.co/openai/privacy-filter) (1.4B MoE, 50M active) on synthetic finance-domain PII data. It tags **18 PII entity types** (73 BIOES classes) at token level and is intended as the **NER layer of a multi-layer PII-masking gateway** in front of LLM services — behind a regex backstop for fully structured identifiers, never as a standalone compliance guarantee. On held-out validation it reaches **strict span-F1 0.956 (ko) / 0.969 (en)**. On an independent, adversarially-hardened Golden Set it holds **0.944 (ko) / 0.907 (en)** with **masking coverage 0.996 (ko) / 0.998 (en)** — i.e. ≥99.5% of gold PII characters are covered by predicted spans. ## 1.1. TL;DR * **Base model**: [`openai/privacy-filter`](https://huggingface.co/openai/privacy-filter) — 1.4B-parameter MoE (128 experts, 50M active), 8 layers, hidden 640, bidirectional banded attention (±128), o200k tokenizer * **Domain / Language**: Finance (BC Card — cards, accounts, national IDs, customer service text) / Korean + English * **Task**: Token classification (BIOES) → character-offset PII spans → masking * **Labels (18)**: `PERSON, RRN, FRN, CARD_NUMBER, ACCOUNT_NUMBER, SECRET, USER_ID, EMAIL, PHONE, PASSPORT, DRIVER_LICENSE, GENERIC_ID, ADDRESS, ZIPCODE, DATE, CARD_EXPIRY, CVC, IPIN` * **Method**: Full fine-tuning (all parameters incl. experts & router) with a re-initialized 73-class head (rows copied from the base head by taxonomy mapping) * **Decoding**: **constrained BIOES Viterbi** (not per-token argmax) + whitespace span refinement — the bundled `viterbi_calibration.json` exposes precision↔recall operating-point biases without retraining * **Format**: BF16 (attention `sinks` kept FP32), single safetensors + tokenizer + label taxonomy + Viterbi calibration sidecar * **Sequence length**: trained on sequences ≤768 tokens — chunk longer inputs * **Intended use** - In-house **PII masking gateway** (detect → mask before text reaches an LLM) - Korean-centric finance text with mixed English (IDs, e-mails, card numbers) ## 1.2. Label Taxonomy (N=18) The 18 labels re-map the upstream ai4privacy source labels to the granularity a Korean financial masking policy needs - merging fragments into single spans (`GIVENNAME`/`SURNAME` → `PERSON`, `CITY`/`STREET`/`BUILDINGNUM` → `ADDRESS`) and adding Korea-specific classes absent upstream (`RRN`, `FRN`, `IPIN`, `CARD_EXPIRY`, `CVC`, `SECRET`). `data source` records the row-source buckets in which each label occurs: `ko` means `openpii-1.5m-ko`, `en` means `openpii-1.5m-en`, and `domain` means locally synthesized rows. | label | description | data source | |-------|-------------|-------------| | `PERSON` | full name (surname + given, single span) | ko, en, domain | | `RRN` | resident registration number (Korea) | ko, domain | | `FRN` | foreign registration number | domain | | `CARD_NUMBER` | credit/debit card PAN | ko, en, domain | | `ACCOUNT_NUMBER` | bank account number | ko, domain | | `SECRET` | auth secret (password / API key / token) | ko, domain | | `USER_ID` | online member ID | ko, en, domain | | `EMAIL` | email address | ko, en, domain | | `PHONE` | phone number (mobile / landline) | ko, en, domain | | `PASSPORT` | passport number | ko, en, domain | | `DRIVER_LICENSE` | driver's license number | ko, en, domain | | `GENERIC_ID` | generic identifier (no KO counterpart) | ko, en, domain | | `ADDRESS` | address (city / street / building, single span) | ko, en, domain | | `ZIPCODE` | postal code | ko, en, domain | | `DATE` | date / time | ko, en, domain | | `CARD_EXPIRY` | card expiry date | domain | | `CVC` | card verification code | domain | | `IPIN` | I-PIN number | domain | Each entity type has `B-`, `I-`, `E-` and `S-` boundary classes, plus the background class `O`. This yields 73 output classes. The bundled `label-taxonomy.yaml` and `config.json` must remain in the same label order. ## 1.3. Usage ```python import torch from transformers import AutoModelForTokenClassification, AutoTokenizer model_id = "BCCard/MoAI-Privacy-Filter" tokenizer = AutoTokenizer.from_pretrained(model_id) model = AutoModelForTokenClassification.from_pretrained(model_id) model.eval() text = "고객 모아이님(000000-0000000)께서 010-0000-0000로 연락 요청하셨습니다." enc = tokenizer(text, return_offsets_mapping=True, add_special_tokens=False, return_tensors="pt") offsets = enc.pop("offset_mapping")[0].tolist() with torch.no_grad(): logits = model(**enc).logits[0] # [T, 73] — raw output # 1) Decode the logits with constrained BIOES Viterbi (recommended; see note below) # and map token paths to character spans via `offsets` — the detector's output: # -> [{"start": 3, "end": 6, "label": "PERSON"}, '모아이' # {"start": 8, "end": 22, "label": "RRN"}, '000000-0000000' # {"start": 26, "end": 39, "label": "PHONE"}] '010-0000-0000' # 2) Masking is downstream application logic — replace each span according to # your masking policy, e.g.: # -> "고객 [PERSON]님([RRN])께서 [PHONE]로 연락 요청하셨습니다." ``` > **Decoding note** — this model (like its base) is post-trained for **constrained Viterbi decoding** > over the BIOES transition grammar, *not* independent per-token argmax. Argmax can emit invalid tag > sequences (span splits / orphan tags) and measurably lowers span-F1. The bundled > `viterbi_calibration.json` follows the upstream operating-point schema: its six transition biases > shift the precision↔recall trade-off at deploy time without retraining (all `0.0` = neutral). ## 1.4. Training Data | Dataset | Role | Size | |---------|------|------| | (Public) [BCCard/pii-masking-openpii-finance](https://huggingface.co/datasets/BCCard/pii-masking-openpii-finance) (v2) | Training / validation | ~58.5k train rows · ~14.5k validation rows | | (Private) BCCard/pii-masking-openpii-finance-test (v2) | Golden Set (release gate; never used for training/tuning) | 2,000 rows (ko 1,460 / en 540) | * Sources: curated Korean subset of `ai4privacy/pii-masking-openpii-1.5m` (label taxonomy remapped, name spans merged & naturalized) + finance-domain synthetic templates + **~30% English replay** (forgetting guard) * Hard-example design baked into v2: surface-similar non-PII decoys (FP suppression), label-confusion pairs in one sentence (RRN↔FRN, DRIVER_LICENSE↔GENERIC_ID), weak-context true PII (FN suppression), long-span address boundary variants * All values are synthetic; validity-pattern collisions with real identifiers are removed at generation time (e.g. card numbers are forced to fail Luhn) ## 1.5. Training Procedure | Item | Value | |------|-------| | Method | Full fine-tuning (1.4B params — experts and router included) | | Head | 33-class base head → 73-class head, initialized by copying base rows via taxonomy mapping | | Loss | Token-level cross-entropy | | Batch | effective 16 (per-device × world × accum), fixed across hardware layouts | | LR / scheduler | 1e-4 / linear decay, warmup 3% | | Optimizer | AdamW (fused), weight decay 0.0, max_grad_norm 1.0 | | Epochs | 5 — best checkpoint by validation span micro-F1, **decoded with the same constrained Viterbi as deployment** | | Precision | FP32 master weights + BF16 autocast; MoE router/experts explicitly kept FP32 during compute | | Hardware | 1× NVIDIA H100 (~5h) |