ppuzio Claude Opus 5.5 commited on
Commit
53cf067
·
1 Parent(s): 514c4b8

Card: names upstream card kept in names/; 841-dev selection over 304 points

Browse files

The upstream FastPDN NER — Polish PII repo is no longer publicly available, so its model card at revision
636c57fe is kept unchanged as names/UPSTREAM_README.md (sha256 as in names/NOTICE.md). The 841-dev section says
the model, epoch and threshold were chosen on the same passages (171 + 133 epoch/threshold points), so its
scores are development scores. Same weights, rules and API as 2.0.9.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Files changed (3) hide show
  1. README.md +4 -4
  2. names/NOTICE.md +3 -1
  3. names/UPSTREAM_README.md +120 -0
README.md CHANGED
@@ -28,7 +28,7 @@ Python rules do the identifiers they can prove. A transformer NER head adds phon
28
 
29
  - **Ground:** `scrub_pii.py` rules
30
  - **Additive labels:** XLM-RoBERTa-large token classifier (epoch 5 of 7), BIO tags `phone` / `pii`, threshold 0.95
31
- - **Names (opt-in):** [FastPDN NER — Polish PII](https://huggingface.co/ArkadiuszPawlak/fastpdn-ner-polish-pii) (CC-BY-4.0) in `names/`, float32
32
  - **Throughput:** about 80k chars/s on one RTX 4090 with `scrub_many` + `dtype="float16"` and 3 processes; names cost about 11%
33
  - **Changes:** `CHANGELOG.md`
34
 
@@ -93,7 +93,7 @@ Unlabelled phones and identifiers (the rules take a bare phone only in the group
93
 
94
  ## Names
95
 
96
- `names=True` (CLI `--names`) adds a second model, [FastPDN NER — Polish PII](https://huggingface.co/ArkadiuszPawlak/fastpdn-ner-polish-pii) by ArkadiuszPawlak, revision `636c57fe…`, fine-tuned from [clarin-pl/FastPDN](https://huggingface.co/clarin-pl/FastPDN) on filtered KPWr plus LLM-synthetic data. `names/` holds it converted from ONNX to safetensors with no value changed. **`names/` is licensed [CC-BY-4.0](https://creativecommons.org/licenses/by/4.0/), not MIT**; `names/NOTICE.md` has the attribution, the changes and the file hashes. NERGAL uses only its person tags (`PERSON`, `PERSON_F`, `PERSON_L`), masks whole words, and always runs it in float32. It leaves procedural role nouns out of a person span (`Odwołujący Jan Nowak` → `Odwołujący [PERSON]`): since 2.0.5 the parties, representatives and offices odwołujący, przystępujący, zamawiający, protestujący, skarżący, przewodniczący, pozwany, powód, wykonawca, wnioskodawca, uczestnik, pełnomocnik, oferent, protokolant, inwestor and prezes, in any inflection and the feminine forms, also cut to the bare participle stem (`Zamawiając`) or with a number glued on (`Odwołujący3`). A number right after one (`Odwołujący 1`) is left out too. Since 2.0.7 a role cut short in damaged text (`Odwołująceg`, `Wykonawc`) or glued to a lowercase word the text also uses on its own (`Odwołującywskazał`) is left out; a word glued before a role (`przezOdwołującego`), a number after a damaged role and other damaged shapes can still be masked.
97
 
98
  **Scope with names on.** The name test labels every person: private individuals, public officials, politicians and historical figures alike, plus fictional characters, nicknames, partial names and initials. Titles and roles stay outside the span. Names inside a street, patronage, organisation or work title, eponyms, usernames, and names inside emails or URLs are not person spans. Masking public figures removes who said, signed or wrote what, which is why names are opt-in. This is the labelling target; the model does not meet it everywhere.
99
 
@@ -126,7 +126,7 @@ The other nine roles tracked (wnioskodawca, uczestnik, powód, pozwany, przewodn
126
 
127
  ## 841-dev
128
 
129
- 841 development passages, 215 with gold PII, **315 spans** (130 phone, 185 other PII). Sources match Dynaword (EUR-Lex, HPLT, Wikipedia, parliamentary and government text, plus smaller news/literary slices). Labels mix unchanged silver with human review. Since 1.2.0 the gold follows phone policy v3 (short and emergency numbers dropped, joined numbers split) and includes reviewed corrections; before, it held 354 spans. The files contain real identifiers, so they are not released with the weights.
130
 
131
  ## Compared with other systems
132
 
@@ -144,7 +144,7 @@ Same split and gold. Every model runs through the NERGAL recipe with names off:
144
 
145
  Zero-shot [GLiNER 2.5](https://huggingface.co/fastino/gliner2.5-multi-v1) is not competitive, especially on non-phone PII (7/185 whole, naked). The fine-tuned GLiNER that NERGAL replaced is the more precise union (39 false characters vs 80) and the stronger phone model (128/130 whole, naked, vs 115), but it masks 4 fewer values whole and leaves 12 residual passages vs 9; XLM-R is the stronger model on other PII (160/185 vs 135). NERGAL 2.0.9 wholly masks 124/130 phones and 179/185 other PII, each under its own placeholder (`[PHONE]` / `[PII]`); exact-span precision 91.05%, recall 93.65%, F1 92.33%.
146
 
147
- XLM-R and epoch 5 were chosen in September 2026, on the earlier gold and before the phone-policy cut. Fine-tuned GLiNER, HerBERT-large and XLM-R-large were trained on the same split; only XLM-R passed the content-preservation gate, and epoch 5 at 0.95 (seed `202609160`) was the only one of 133 epoch/threshold points that covered more than the fine-tuned GLiNER union of that time while adding no false-mask characters it did not already make. Under the 1.2.0 rules, gold and phone cut, that no longer holds: the GLiNER union makes fewer false masks (table above).
148
 
149
  ## Load
150
 
 
28
 
29
  - **Ground:** `scrub_pii.py` rules
30
  - **Additive labels:** XLM-RoBERTa-large token classifier (epoch 5 of 7), BIO tags `phone` / `pii`, threshold 0.95
31
+ - **Names (opt-in):** [FastPDN NER — Polish PII](https://huggingface.co/ArkadiuszPawlak/fastpdn-ner-polish-pii) (CC-BY-4.0) in `names/`, float32; the upstream repo is no longer public, so `names/UPSTREAM_README.md` keeps its card
32
  - **Throughput:** about 80k chars/s on one RTX 4090 with `scrub_many` + `dtype="float16"` and 3 processes; names cost about 11%
33
  - **Changes:** `CHANGELOG.md`
34
 
 
93
 
94
  ## Names
95
 
96
+ `names=True` (CLI `--names`) adds a second model, [FastPDN NER — Polish PII](https://huggingface.co/ArkadiuszPawlak/fastpdn-ner-polish-pii) by ArkadiuszPawlak, revision `636c57fe…`, fine-tuned from [clarin-pl/FastPDN](https://huggingface.co/clarin-pl/FastPDN) on filtered KPWr plus LLM-synthetic data. `names/` holds it converted from ONNX to safetensors with no value changed. **`names/` is licensed [CC-BY-4.0](https://creativecommons.org/licenses/by/4.0/), not MIT**; `names/NOTICE.md` has the attribution, the changes and the file hashes. The upstream repo is no longer publicly available (checked 2026-10-30); `names/UPSTREAM_README.md` is its model card at that revision, unchanged, with the training data and evaluation it reports. NERGAL uses only its person tags (`PERSON`, `PERSON_F`, `PERSON_L`), masks whole words, and always runs it in float32. It leaves procedural role nouns out of a person span (`Odwołujący Jan Nowak` → `Odwołujący [PERSON]`): since 2.0.5 the parties, representatives and offices odwołujący, przystępujący, zamawiający, protestujący, skarżący, przewodniczący, pozwany, powód, wykonawca, wnioskodawca, uczestnik, pełnomocnik, oferent, protokolant, inwestor and prezes, in any inflection and the feminine forms, also cut to the bare participle stem (`Zamawiając`) or with a number glued on (`Odwołujący3`). A number right after one (`Odwołujący 1`) is left out too. Since 2.0.7 a role cut short in damaged text (`Odwołująceg`, `Wykonawc`) or glued to a lowercase word the text also uses on its own (`Odwołującywskazał`) is left out; a word glued before a role (`przezOdwołującego`), a number after a damaged role and other damaged shapes can still be masked.
97
 
98
  **Scope with names on.** The name test labels every person: private individuals, public officials, politicians and historical figures alike, plus fictional characters, nicknames, partial names and initials. Titles and roles stay outside the span. Names inside a street, patronage, organisation or work title, eponyms, usernames, and names inside emails or URLs are not person spans. Masking public figures removes who said, signed or wrote what, which is why names are opt-in. This is the labelling target; the model does not meet it everywhere.
99
 
 
126
 
127
  ## 841-dev
128
 
129
+ 841 development passages, 215 with gold PII, **315 spans** (130 phone, 185 other PII). Sources match Dynaword (EUR-Lex, HPLT, Wikipedia, parliamentary and government text, plus smaller news/literary slices). Labels mix unchanged silver with human review. Since 1.2.0 the gold follows phone policy v3 (short and emergency numbers dropped, joined numbers split) and includes reviewed corrections; before, it held 354 spans. The files contain real identifiers, so they are not released with the weights. The model, epoch and threshold were chosen on these same passages ([below](#compared-with-other-systems)), so the scores here are development scores, not a held-out test.
130
 
131
  ## Compared with other systems
132
 
 
144
 
145
  Zero-shot [GLiNER 2.5](https://huggingface.co/fastino/gliner2.5-multi-v1) is not competitive, especially on non-phone PII (7/185 whole, naked). The fine-tuned GLiNER that NERGAL replaced is the more precise union (39 false characters vs 80) and the stronger phone model (128/130 whole, naked, vs 115), but it masks 4 fewer values whole and leaves 12 residual passages vs 9; XLM-R is the stronger model on other PII (160/185 vs 135). NERGAL 2.0.9 wholly masks 124/130 phones and 179/185 other PII, each under its own placeholder (`[PHONE]` / `[PII]`); exact-span precision 91.05%, recall 93.65%, F1 92.33%.
146
 
147
+ XLM-R and epoch 5 were chosen in September 2026 on this split, with the earlier gold and before the phone-policy cut. Fine-tuned GLiNER, HerBERT-large and XLM-R-large were trained on the same split and each scored at 3 epochs × 19 thresholds (171 points); only XLM-R passed the content-preservation gate. A follow-up trained XLM-R for 7 epochs, and epoch 5 at 0.95 (seed `202609160`) was the only one of its 133 epoch/threshold points that covered more than the fine-tuned GLiNER union of that time while adding no false-mask characters it did not already make: 304 points searched on this split in all. Under the 1.2.0 rules, gold and phone cut, that no longer holds: the GLiNER union makes fewer false masks (table above).
148
 
149
  ## Load
150
 
names/NOTICE.md CHANGED
@@ -6,7 +6,8 @@
6
  - Attribution: "FastPDN NER — Polish PII (ONNX)" by ArkadiuszPawlak, fine-tuned from
7
  [clarin-pl/FastPDN](https://huggingface.co/clarin-pl/FastPDN) on filtered KPWr
8
  ([clarin-pl/kpwr-ner](https://huggingface.co/datasets/clarin-pl/kpwr-ner)) plus LLM-synthetic data.
9
- Upstream model card README.md sha256 `9ca776b1157e5dedfb9d404ee17a584ebfc80f4dc426beb68f69200a52522c6f`.
 
10
 
11
  ## Changes
12
 
@@ -30,4 +31,5 @@
30
  | `model.safetensors` | `2fa84ec6abd0c1b12ba0ba1548e89c9fca36fc58f03e66074db423b0e98ec8e6` |
31
  | `tokenizer.json` | `3a690c2d605076ad3901d946d4c0145fbfb19fef2f01370c7388430aa9f31edf` |
32
  | `convert.py` | `a698bc993b253fc70d960aa14af37a5088afb79c77dae70557c7056ce394333e` |
 
33
  | `NOTICE.md` | this file |
 
6
  - Attribution: "FastPDN NER — Polish PII (ONNX)" by ArkadiuszPawlak, fine-tuned from
7
  [clarin-pl/FastPDN](https://huggingface.co/clarin-pl/FastPDN) on filtered KPWr
8
  ([clarin-pl/kpwr-ner](https://huggingface.co/datasets/clarin-pl/kpwr-ner)) plus LLM-synthetic data.
9
+ Upstream model card README.md sha256 `9ca776b1157e5dedfb9d404ee17a584ebfc80f4dc426beb68f69200a52522c6f`, kept
10
+ unchanged as `UPSTREAM_README.md` because the upstream repo is no longer publicly available (checked 2026-10-30).
11
 
12
  ## Changes
13
 
 
31
  | `model.safetensors` | `2fa84ec6abd0c1b12ba0ba1548e89c9fca36fc58f03e66074db423b0e98ec8e6` |
32
  | `tokenizer.json` | `3a690c2d605076ad3901d946d4c0145fbfb19fef2f01370c7388430aa9f31edf` |
33
  | `convert.py` | `a698bc993b253fc70d960aa14af37a5088afb79c77dae70557c7056ce394333e` |
34
+ | `UPSTREAM_README.md` | `9ca776b1157e5dedfb9d404ee17a584ebfc80f4dc426beb68f69200a52522c6f` |
35
  | `NOTICE.md` | this file |
names/UPSTREAM_README.md ADDED
@@ -0,0 +1,120 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ language: pl
3
+ license: cc-by-4.0
4
+ tags:
5
+ - token-classification
6
+ - ner
7
+ - pii
8
+ - polish
9
+ - onnx
10
+ - bert
11
+ base_model: clarin-pl/FastPDN
12
+ datasets:
13
+ - clarin-pl/kpwr-ner
14
+ ---
15
+
16
+ # FastPDN NER — Polish PII (ONNX)
17
+
18
+ Fine-tuned [`clarin-pl/FastPDN`](https://huggingface.co/clarin-pl/FastPDN)
19
+ for detecting personal data (PII) and organizations in Polish text.
20
+
21
+ Detects: **PERSON** (full name), **PERSON_F** (first name), **PERSON_L** (last name),
22
+ **STREET** (street/road), **CITY**, **ORG** (company/institution).
23
+
24
+ House numbers are intentionally excluded from NER — handled downstream by regex in post-processing.
25
+
26
+ Intended use: Polish web forms — browser-side inference via
27
+ [`@xenova/transformers`](https://github.com/xenova/transformers.js) + ONNX Runtime Web (WASM),
28
+ no backend required.
29
+
30
+ ## Training data
31
+
32
+ Merged in `data/merged/conll/` from two sources:
33
+
34
+ | Split | Synthetic | KPWr | Total | Tokens |
35
+ |-------|-----------|------|-------|--------|
36
+ | train | 12 750 | 3 935 | 16 685 | 248 437 |
37
+ | validation | 1 500 | 437 | 1 937 | 29 384 |
38
+ | test | 750 | 750 | 1 500 | 25 547 |
39
+
40
+ - **KPWr filtered** — [clarin-pl/kpwr-ner](https://huggingface.co/datasets/clarin-pl/kpwr-ner)
41
+ Polish press corpus; LOC-only (geographic) and schematic form-label samples removed.
42
+ - **LLM-synthetic** — generated with GPT-4o-mini and claude-haiku-4-5,
43
+ covering STREET with city suffix, PERSON in email context, and mixed cases.
44
+
45
+ Fine-tuned for 3 epochs with early stopping (patience=2), best checkpoint selected by **eval F1 STREET**.
46
+
47
+ ## Evaluation (`data/merged/conll/test.conll`, 1 500 sentences, seqeval)
48
+
49
+ | Entity | fp32 F1 | int8 F1 | Δ fp32→int8 |
50
+ |--------|---------|---------|-------------|
51
+ | PERSON | 97.7% | 97.1% | −0.6pp |
52
+ | PERSON_F | 100.0% | 99.4% | −0.6pp |
53
+ | PERSON_L | 100.0% | 99.2% | −0.8pp |
54
+ | ORG | 85.1% | 82.7% | −2.5pp |
55
+ | STREET | 98.9% | 99.0% | +0.2pp |
56
+ | CITY | 96.9% | 96.7% | −0.2pp |
57
+ | **overall** | **96.0%** | **95.4%** | **−0.6pp** |
58
+
59
+ INT8 quantization loss = **−0.6pp overall** (target was <3pp). No entity loses more than 2.5pp.
60
+
61
+ ## Label mapping
62
+
63
+ The model outputs 13 BIO classes:
64
+
65
+ | Model label | Meaning |
66
+ |-------------|---------|
67
+ | `B-PERSON` / `I-PERSON` | full person name |
68
+ | `B-PERSON_F` / `I-PERSON_F` | first name only |
69
+ | `B-PERSON_L` / `I-PERSON_L` | last name only |
70
+ | `B-ORG` / `I-ORG` | company / institution |
71
+ | `B-STREET` / `I-STREET` | street / road / avenue |
72
+ | `B-CITY` / `I-CITY` | city |
73
+ | `O` | not an entity |
74
+
75
+ ## Files
76
+
77
+ | File | Format | Notes |
78
+ |------|--------|-------|
79
+ | `model.onnx` | FP32 | highest quality, BERT graph-optimized |
80
+ | `model_quantized.onnx` | INT8 | **recommended for browser** |
81
+ | `onnx/model_quantized.onnx` | INT8 | alias for Transformers.js `dtype:"q8"` |
82
+ | `config.json` | JSON | label mapping, model config |
83
+ | `tokenizer.json` | JSON | HerBERT tokenizer |
84
+ | `tokenizer_config.json` | JSON | HerBERT tokenizer config |
85
+
86
+ ## Usage
87
+
88
+ ### Python (Transformers)
89
+
90
+ ```python
91
+ from transformers import pipeline
92
+
93
+ ner = pipeline(
94
+ "token-classification",
95
+ model="ArkadiuszPawlak/fastpdn-ner-polish-pii",
96
+ aggregation_strategy="simple",
97
+ )
98
+ result = ner("Jan Kowalski mieszka przy ul. Marszałkowskiej 1, 00-001 Warszawa.")
99
+ # [{"entity_group": "PERSON", "word": "Jan Kowalski", ...},
100
+ # {"entity_group": "STREET", "word": "ul. Marszałkowskiej", ...},
101
+ # {"entity_group": "CITY", "word": "Warszawa", ...}]
102
+ ```
103
+
104
+ ### Browser (@xenova/transformers + ONNX Runtime Web)
105
+
106
+ ```js
107
+ import { pipeline } from "@xenova/transformers";
108
+
109
+ const ner = await pipeline(
110
+ "token-classification",
111
+ "ArkadiuszPawlak/fastpdn-ner-polish-pii",
112
+ { dtype: "q8", aggregation_strategy: "simple" }
113
+ );
114
+
115
+ const raw = await ner("Jan Kowalski mieszka przy ul. Marszałkowskiej 1 w Warszawie.");
116
+ console.log(raw);
117
+ // [{ entity_group: "PERSON", word: "Jan Kowalski", score: 0.99 },
118
+ // { entity_group: "STREET", word: "ul. Marszałkowskiej", score: 0.98 },
119
+ // { entity_group: "CITY", word: "Warszawa", score: 0.97 }]
120
+ ```