Niewidka: jak ukrywam dane osobowe w dokumentach bez GPU
Chciałem zbudować narzędzie, które pozwoli korzystać z zewnętrznych czatów AI bez przekazywania im danych osobowych. Wklejam pismo albo otwieram dokument Worda, program zastępuje nazwiska, adresy i numery identyfikacyjne znacznikami, a po otrzymaniu odpowiedzi przywraca oryginalne dane.
Tak powstała Niewidka. Kod jest publicznie dostępny na GitHubie na licencji MIT, a gotowe wersje można pobrać z sekcji wydań.
Technicznie jest to odwracalna pseudonimizacja, a nie anonimizacja w rozumieniu RODO. Program przechowuje mapę zamian, dzięki której [OSOBA_1] może ponownie stać się konkretnym nazwiskiem.
Od początku zależało mi na działaniu lokalnym, bez wysyłania dokumentów do chmury i bez konieczności posiadania GPU. Okazało się, że wystarczy połączenie reguł z niewielkim modelem rozpoznawania nazw własnych.
Reguły, model i polska odmiana
Najpierw próbowałem rozwiązać jak najwięcej problemów deterministycznie. Numery PESEL, NIP, REGON, kont bankowych, telefonów, adresy e-mail i inne charakterystyczne dane da się wykrywać regułami.
Nie można jednak ślepo ufać sumom kontrolnym. W testowych dokumentach wiele numerów PESEL zawierało literówki. Gdybym odrzucał każdy numer z niepoprawną sumą, przepuszczałbym dane, które nadal mogą identyfikować człowieka.
Trudniejsze okazały się imiona i nazwiska. Tutaj wykorzystałem model NER oparty na XLM-RoBERTa, uruchamiany przez ONNX Runtime na CPU. Z jego wyników pobieram wyłącznie rozpoznane osoby. Pozostałe rodzaje danych obsługują przede wszystkim reguły, które lepiej wyznaczają granice numerów i adresów.
Szybko natrafiłem na problem tokenizacji. Model potrafił rozcinać polskie nazwiska w środku słowa, przez co wykrywał tylko ich fragmenty. Rozwiązaniem okazała się prosta poprawka dekodera: rozszerzanie wykrytych zakresów do granic całych słów.
Bez ponownego trenowania modelu znacząco poprawiło to wykrywanie nazwisk.
Drugim problemem była polska fleksja. Ta sama osoba może występować w dokumencie jako „Kowalski”, „Kowalskiego” i „Kowalskiemu”. Chciałem, żeby wszystkie formy należały do jednej osoby, ale przy przywracaniu tekstu zachowywały oryginalną odmianę.
Zastosowałem więc wspólny identyfikator osoby i osobne znaczniki dla form gramatycznych. Grupowanie opiera się na heurystykach końcówek nazwisk. Nie jest idealne, dlatego w przypadkach niejednoznacznych program nie zgaduje.
Mniejszy model nie musi być gorszy
Początkowo model zajmował ponad gigabajt. Większość miejsca pochłaniała warstwa embeddingów.
Zdecydowałem się na własną kwantyzację do 4 bitów, obejmującą również tę warstwę. Dzięki temu znacznie zmniejszyłem rozmiar modelu bez pogorszenia skuteczności na przygotowanym korpusie.
Wybrana wersja analizowała krótką próbkę w około 13 milisekund, zużywając około 600 MiB RAM. To pomiar samego przetwarzania próbki, nie całej operacji wykonywanej przez użytkownika. Obsługa pełnego dokumentu przez interfejs trwa zwykle kilka sekund.
Przed eksperymentami ustaliłem kryteria akceptacji. To miało znaczenie, ponieważ pierwsze warianty nie spełniały wymaganego progu precyzji wykrywania osób. Nie obniżałem progu, żeby uznać je za sukces.
Sprawdzałem też, czy dokładanie kolejnych modeli poprawi wyniki. Próba wykorzystania LFM2.5 jako drugiego detektora osób nie przyniosła poprawy wykrywania, za to zwiększyła liczbę fałszywych trafień, czas i zużycie pamięci. Podobnie dołożenie spaCy dawało niewielki zysk kosztem wielu niepotrzebnych zamazań.
Jako detektor osób go odrzuciłem; został tylko na najwyższym poziomie do wykrywania adresów IP, kodów SWIFT i loginów. Dodatkowe modele mają sens tylko wtedy, gdy ich korzyść można zmierzyć.
Testy i granice bezpieczeństwa
Najważniejszy sprawdzian przeprowadziłem na 30 realistycznych pismach. Były wśród nich umowy, decyzje administracyjne, dokumentacja medyczna i inne dokumenty zawierające trudne przypadki polskich nazwisk oraz adresów.
Program ukrył 99,1% ścisłych danych osobowych. Pozostały cztery niewykryte przypadki dotyczące nietypowych zapisów adresowych.
Wszystkie dokumenty udało się następnie przywrócić bez zmian w treści.
Porównałem też Niewidkę z kilkoma otwartymi rozwiązaniami opartymi na Presidio, spaCy i wyrażeniach regularnych. Na tym zestawie moje rozwiązanie wykrywało więcej danych i rzadziej zamazywało niepotrzebne fragmenty tekstu.
Jest jednak ważne zastrzeżenie: reguły stroiłem na tych samych dokumentach, na których mierzyłem wynik końcowy. Dlatego 99,1% traktuję jako wynik optymistyczny, a nie dowód skuteczności na nieznanych dokumentach. Potrzebny jest niezależny zbiór testowy, którego wcześniej nie widziałem.
Samo wykrywanie to zresztą tylko część bezpieczeństwa.
Niewidka działa według zasady fail-closed. Jeśli model zgłosi błąd, program nie oddaje wyniku. Gotowy dokument jest ponownie wczytywany i skanowany w poszukiwaniu pozostałych danych. Podejrzane trafienia blokują kopiowanie do czasu decyzji użytkownika.
W przypadku Worda program zachowuje układ i style, usuwa komentarze oraz metadane, a przy nieobsługiwanych elementach mogących ujawnić dane blokuje eksport.
Mapa zamian jest przechowywana osobno, w postaci zaszyfrowanej AES-256-GCM. Aktualizacje modeli podlegają weryfikacji podpisu i sumy kontrolnej.
Co dalej?
Nie sprawdziłem jeszcze dokładnie działania na słabszych laptopach biurowych ani na natywnym Windowsie. Obecna wersja nie obsługuje PDF-ów i OCR. Pozostaje również problem rozpoznawania nazw firm.
Nie chcę jednak dokładać kolejnych modeli tylko dlatego, że są dostępne. Najpierw trzeba pokazać, że rzeczywiście poprawiają skuteczność.
Najważniejsza lekcja z tego projektu jest dla mnie prosta: w ochronie danych większy model nie oznacza automatycznie bezpieczniejszego rozwiązania. Czasem więcej daje poprawienie granic słów, dobrze napisany walidator albo dodatkowy test dokumentu po przetworzeniu.
A nawet bardzo dobry wynik na własnym korpusie nie zastępuje sprawdzianu na danych, których program wcześniej nie widział.