Meting 05 — wat de reprocess opleverde, en wat er niet uit te lezen valt
Meting 04 stelde vast wat de allow-lijst op 200
documenten terugwint. Daarna is de lijst over het hele corpus uitgerold met
POST /reprocess. Deze meting legt vast wat die run deed — en waarom de
voor-de-hand-liggende vergelijking "index vóór" naast "index ná" het verkeerde
antwoord geeft.
De run
klaar in 43578s: {'reanonymized': 569, 'skipped': 0, 'retryable': 0, 'failed': 0}
569 documenten, twaalf uur, nul mislukkingen. Ongeveer 77 seconden per document, vrijwel helemaal detectie. Samen met eerdere, afgebroken runs zijn 770 van de 779 documenten inmiddels onder de nieuwe lijsten ge-de-identificeerd.
Het ene getal dat klopt
Uit de audit-records van diezelfde run, detections.suppressed_by_list:
| type | onderdrukte detecties |
|---|---|
| LOCATION | 6.085 |
| ORGANIZATION | 4.594 |
| PERSON | 2.841 |
| totaal | 13.520 |
Dit getal is binnen één run gemeten, onder één versie van de code, op dezelfde tekst met en zonder de lijst. Dat is wat de lijst doet.
Het getal dat níét klopt
De vergelijking die zich opdringt — tel de tokens vóór de reprocess, tel ze erna — zegt iets anders:
| eerste de-identificatie | na reprocess | |
|---|---|---|
| tokens geplaatst | 130.973 | 150.108 |
| PERSON | 43.937 | 32.137 |
| LOCATION | 38.813 | 66.438 |
| POSTCODE | 0 | 653 |
Er staan 19.135 tokens méér in het corpus, terwijl de lijst er 13.520 zou onderdrukken. Gelezen als "de lijst maakte het erger" is dat een conclusie over de lijst. Die conclusie is fout.
Waarom
De basislijn is geen toestand, maar een verzameling. De eerste de-identificatie van deze documenten ligt in augustus (567) en september (203), verspreid over de versies die toen draaiden. De reprocess is één versie, van één dag. De vergelijking zet dus niet twee lijst-instellingen naast elkaar maar twee tijdvakken, met alles wat daartussen veranderde.
Het duidelijkste bewijs staat in de tabel zelf: geen enkel eerste record kent
het type postcode (0 van 770). Die detector kwam uit
meting 01 en is er ná die eerste ronde bij gekomen.
Hij vindt nu 653 postcodes die er altijd al stonden. Datzelfde geldt voor de
verdubbeling van LOCATION en de daling van PERSON: dat zijn verschuivingen in
wat de detector vindt, niet in wat de lijst weghaalt.
Het aandeel waarden dat met een kleine letter begint, steeg over diezelfde grens van 39% naar 44%. Ook dat is geen uitspraak over de lijst.
Maar is de detector dan grillig?
Dat was de eerste verdenking: als twee runs over dezelfde tekst niet hetzelfde
vinden, is elke voor/ná-meting zinloos. Gemeten, 20 documenten, drie keer
dezelfde tekst door detect_entities:
documenten met DRIE identieke runs: 20
documenten met verschil : 0
detecties die niet in alle drie zaten: 0
De detector is deterministisch. De variatie zit tussen versies, niet tussen runs. Een reprocess opnieuw draaien geeft hetzelfde; een reprocess vergelijken met een oudere ronde vergelijkt twee instrumenten.
Wat de lijst aan recall kost
Dat is de vraag die een allow-lijst moet verdienen, want hij haalt bescherming wég. Gemeten op het evalcorpus (500 documenten, waar bekend is wat erin zit). Detectie draaide één keer per document; de twee uitkomsten verschillen alleen in de lijsten erachter, dus dit is niet het instrument-probleem hierboven.
| precisie | recall | lekken | |
|---|---|---|---|
| zonder lijsten | 0,391 | 0,999 | 0 |
| met lijsten | 0,404 | 0,999 | 0 |
De recall beweegt niet. Geen enkele ingezaaide waarde verdwijnt door de lijst, en het aantal lekken blijft nul. De ene gemiste PERSON is in beide runs dezelfde.
Wat wél beweegt zijn de valse positieven:
| type | fp zonder | fp met |
|---|---|---|
| PERSON | 787 | 693 |
| ORGANIZATION | 808 | 758 |
144 minder verzinsels, nul minder bescherming. Dat is waar deze lijst voor bedoeld is.
Lees de absolute precisie hier níet als een cijfer over de detector
LOCATION, ORGANIZATION, DATE_TIME en PHONE_NUMBER hebben in dit corpus nul gouden spans: alles wat de detector daar vindt telt als valse positief, ook een correcte vondst. De precisie van 0,40 zegt dus meer over wat het corpus labelt dan over wat de detector kan. Alleen het verschil tussen de twee rijen is een uitspraak, want die twee draaiden op dezelfde detecties en hetzelfde goud.
Dat is ook waarom de allow-regels voor LOCATION hier niets doen (863 fp in
beide): de generieke termen die in het echte corpus bovenaan staan
(bestemmingsplan, plangebied) komen in deze synthetische tekst niet voor.
Zelf draaien:
python -m wordsworth.eval.pii_run gold.jsonl --lists lists
Zonder --lists meet die CLI de detectie zónder lijsten, ook op een installatie
waar ze aanstaan.
De regel die hieruit volgt
Een voor/ná over een reprocess heen isoleert de lijst alleen als de code ertussen niet veranderde. Anders meet je het verschil tussen twee versies en schrijft het op naam van de lijst.
Praktisch: het effect van een lijst komt uit suppressed_by_list in de records
van de run zelf. Dat getal is per constructie onder één versie gemeten. Wie toch
twee rondes wil vergelijken, moet eerst met de nieuwe code en de óúde lijsten
draaien — twee runs, één verschil.
Naschrift 2: het adres als PII, en wat dat aan de meting verandert
Tot 2026-09-22 zaaide het evalcorpus een woonadres met .lit() — gewone tekst.
Een detector die het adres vond, kreeg dat als valse positief aangerekend.
De deny-regel kon daar dus per definitie alleen verliezen.
Mark, 2026-09-22: "street addresses should be PII unless specified". Het
corpus zaait een woonadres nu als gouden LOCATION (straat + huisnummer als één
span), met de Postbus-regel als tegencase: het contactadres van een
organisatie hoort NIET gevonden te worden.
Dezelfde meting, hetzelfde corpus, alleen de lijsten verschillen:
| precisie | recall | lekken | |
|---|---|---|---|
| zonder lijsten | 0,391 | 0,891 | 207 |
| met lijsten | 0,432 | 0,999 | 0 |
lekken is hier niet een bijzaak maar het getal waar de invariant over gaat:
een gouden entiteit zonder enige overlappende voorspelling, van welk type dan
ook. 207 woonadressen die ongeredigeerd de index in zouden gaan, worden er
nul. LOCATION gaat van tp=0, fn=207 naar tp=207, fn=0.
En de precisie gaat óók omhoog (0,391 → 0,432), want de allow-lijst blijft in dezelfde run valse positieven weghalen. Er is hier dus geen ruil tussen bescherming en precisie; beide bewegen de goede kant op.
Eén nuance die niet weggelaten mag worden
Op dit corpus lekte het adres volledig: zonder de regel was er geen enkele
overlappende voorspelling. In productie zag het er anders uit — daar staat
[LOCATION:…] 12 in de tekst, dus daar vindt de detector de straatnaam wél en
blijft alleen het huisnummer staan.
Dat verschil zit in het corpus: het zaait vijf vaste straatnamen
(Dorpsstraat, Kerkstraat, Molenweg, Stationsplein, Groenestraat) en
die herkent de detector kennelijk niet als plaats. De regel doet in productie
dus vooral de span afmaken, en op dit corpus het hele adres vinden. Allebei
echt; het zijn niet dezelfde winst.
Naschrift: de deny-regel, en nog een verkeerde meter
Op 2026-09-22 draaide het corpus opnieuw, nu met de deny-regel voor het straatadres erbij: 770 documenten, 15,4 uur, nul achterstallig daarna. De regel voegde 3.231 straatadressen toe, in 403 van de 770 documenten.
Dat getal komt uit de list-laag van het detectie-spoor van die run. Een
deny-regel schrijft zijn eigen toevoeging daar weg, dus het is per constructie
gemeten onder één versie — dezelfde discipline als suppressed_by_list
hierboven.
Mijn eerste poging was iets anders, en fout. Ik telde in de opgeslagen tekst het
patroon [LOCATION:…] <getal> — een token met een kaal getal erachter — omdat
dát het geval is dat de regel moest opheffen. Uitkomst: 100 van 400 teksten.
Dat leest als "een kwart lekt nog steeds een huisnummer", en dat is niet wat er
gemeten werd. [LOCATION:…] 2019 is een plaatsnaam met een jaartal;
[LOCATION:…] 3 kan een tabelregel of een artikelnummer zijn. Het patroon is
het geval niet.
Precies de valkuil die dit document beschrijft, twee secties verderop, op dezelfde dag opnieuw ingelopen. Het onderscheid dat helpt: meet aan de handeling (wat deed de laag die de wijziging is), niet aan een vorm in het resultaat die ook zonder die handeling kan ontstaan.
Nog een valkuil, uit deze meting zelf
De eerste versie van de vergelijking meldde: tweede ronde 0 tokens, over de
hele linie. Een keurige tabel, netjes opgemaakt, volledig onzin. Oorzaak:
anonymize schrijft de counts op het hoogste niveau van het payload en
reanonymize onder een sleutel counts. De meting las de tweede vorm niet en
concludeerde "nul".
Een meting die nul rapporteert waar niets nul kán zijn, is een meetfout tot het tegendeel blijkt. Dat het er geloofwaardig uitziet, is precies het probleem.