XVI wiek (beta) API Github Twitter

Zapiski

Zapiski, notatki i ciekawostki dotyczące programowania związanego z historią, archeologią czy tzw. humanistyką cyfrową. Czyli o praktycznym wykorzystaniu różnych narzędzi i bibliotek.

Spis treści:

2022-09-25 OpenRefine - własny serwis rekoncyliacji/uspójniania
2022-10-01 spaCy, NER i półautomatyczne tworzenie indeksu postaci
2022-10-03 spaCy, POS i Matcher - czyli do czego przydają się rzeczowniki
2022-10-05 spaCy - jak poprawić wyniki NER czyli Barnim XI to też osoba...
2022-10-06 Artykuły, kursy, filmy - o NLP w badaniach historycznych
2022-10-07
Anotacja tekstów w Doccano i trenowanie własnego modelu
2022-10-10
Skrypty w poszukiwaniu wydarzeń
2022-10-17 spaCy, NEL - łączenie rozpoznanych encji z wikidata.org
2022-10-22 spaCy i automatyczne tagowanie encji w TEI Publisherze
2022-10-30 spaCy i koreferencje, czyli Jerzy bawił we Włoszech a jego włości popadały w ruinę


OpenRefine - własny serwis rekoncyliacji/uspójniania (25.09.2022)

OpenRefine jest popularnym narzędziem do oczyszczania i przekształcania danych. Polecam artykuły na temat OpenRefine: Oczyszczanie danych z użyciem OpenRefine oraz Cleaning Data with OpenRefine Jedną z jego ciekawszych funkcji jest możliwość rekoncyliacji danych przy wykorzystaniu zewnętrznych serwisów udostępniających dane do uspójniania. Często zdarzają  się w danych historycznych różnorakie sposoby zapisu imion i nazwisk postaci, lub nazw geograficznych. Nazwy miejscowości mogą występować w brzmieniu współczesnym, w formie używanej w XVI wieku, lub po prostu w formie błędnie zapisanej w źródle. Mechanizm uspójniania w OpenRefine pozwala uzgodnić nazwę występującą w naszych danych z nazwą pochodzącą z pewnego źródła. Np. powiązać "Zbigniewa Oleśnickiego" z identyfikatorem z bazy VIAF, lub powiązać miejscowość Brzozowa (występująca w źródłach także jako Brosoua) z identyfikatorem z Atlasu Historycznego Polski (Brzozowa_sdc_krk).

Problemem może być znalezienie odpowiedniego serwisu rekoncyliacji. O ile popularne źródła w rodzaju VIAF czy wikidata.org są dostępne Lista dostępnych serwerów rekoncyliacji. , o tyle uspójnianie z bazą miejscowości AHP lub naszą lokalną bazą osób może wymagać uruchomienia własnego serwisu. Nie jest to jednak takie trudne, istnieją bowiem gotowe narzędzia, które można wykorzystać do tego celu. Jednym z nich jest Reconcile-csv udostępniony (na otwartej licencji BSD-2) na stronie Open Knowledge Lab. Program ten pozwala na dopasowanie szukanej przez nas nazwy do nazw w swojej bazie poprzez mechanizm przybliżonego (rozmytego) porównywania (fuzzy matching), nazwy nie muszą być identyczne, mogą być podobne, program będzie wówczas proponował listę zbliżonych do podanej nazw wraz ze współczynnikiem podobieństwa. Bazą dla Reconcile-csv jest plik tekstowy w formacie CSV.  

Program jest plikiem *.jar, (warto pobrać z githuba też plik index.html.tpl, który jest szablonem wyświetlanym jako główna strona serwisu) do uruchomienia potrzebuje więc zainstalowanej Javy (JRE), potrzebny jest oczywiście plik CSV z danymi (plik w którym każdy wiersz jest rekordem danych, pola rozdzielone są przecinkami a pierwszy wiersz zawiera nazwy kolumn).

Polecenie uruchamiające serwer:
java -Xmx2g -jar reconcile-csv-0.1.2.jar plik.csv search_column id_column

gdzie plik.csv to nazwa naszego pliku z danymi, search_columm to nazwa pola w pliku z danymi, które będzie służyło do rekoncyliacji w OpenRefine, id_column to nazwa pola w pliku z danymi, które zawiera jednoznaczny identyfikator danych.

Po uruchomieniu serwis dostępny jest pod adresem http://localhost:8000/reconcile, i taki adres należy wprowadzić w OpenRefine (zwraca on zawartość w formacie json, czytelną dla programu - odbiorcy danych z serwisu). Adres główny serwisu (http://localhost:8000) wyświetli o nim podstawowe informacje w formie czytelnej dla człowieka.

serwis reconcile-csv

Lokalny serwis rekoncyliacji podłącza się do OpenRefine podobnie jak serwisy zewnętrzne, po uruchomieniu rekoncyliacji na wybranej kolumnie danych przycisk Add Standard Service wyświetli okienko w którym można podać adres serwisu, zaakceptowany serwis pojawi się na liście. Standardowa nazwa serwisu będzie różnić się od pokazanej na ilustracji i wygląda tak: 'CSV Reconciliation service'. Jest niestety zaszyta w kodzie źródłowym Reconcile-csv i jej zmiana wymaga rekompilacji programu (zob. dalszą część artykułu).

add standard service

Można wówczas rozpocząć proces uspójniania. Dane które według mechanizmu zostały pewnie dopasowane (współczynnik > 0.85) zostaną od razu przypisane do komórek kolumny dla której przeprowadzamy rekoncyliację, w innych przypadkach wyświetlona zostanie lista wraz z wartościami dopasowania. Dla każdej z pozycji można wyświetlić informacje z pliku csv będącego źródłem danych serwisu, na przykładzie poniżej są to dane miejscowości z Atlasu Historycznego Polski. Dostępne publicznie na licencji CC BY-ND 4.0: AHP 2.0 (IH PAN).

reconcile_view

Jak zmodyfikować program reconcile-csv: zarówno zmiana nazwy serwisu (nazwy wyświetlanej w OpenRefine), jak i dostosowanie programu Reconcile-csv do pracy na serwerze, wymaga zmian w jego kodzie źródłowym Repozytorium aplikacji w serwisie github. a w związku z tym pewnych umiejętności 'programistycznych'. Sama zmiana nazwy serwisu wymaga jednak tylko modyfikacji 50 wiersza pliku /src/reconcile_csv/core.clj: {:name "CSV Reconciliation service", zmiany tekstu "CSV Reconciliation service" na nowy i rekompilacji programu. Aplikacja została stworzona w języku clojure. Najłatwiejszym sposobem zarządzania i kompilacji projektu w języku clojure jest użycie systemu Leiningen, który w systemie Ubuntu można zainstalować poleceniem: sudo apt install leiningen. Rekompilacja projektu Reconcile-csv i budowa nowego pliku *.jar sprowadza się do uruchomienia polecenia: lein uberjar w katalogu z plikiem project.cli, skompilowany plik *.jar powinien znajdować się w podkatalogu /target.

Modyfikacja kodu i rekompilacja za każdym razem kiedy chcemy zmienić np. nazwę serwisu rekoncyliacji wyświetlaną w OpenRefine może być jednak dość niewygodna. Dlatego przygotowałem małą modyfikację programu reconcile-cvs która obsługuje cztery dodatkowe parametry uruchamiania z linii komend: adres_serwera, port, nazwa_serwisu_rekoncyliacji i nazwa_typu. W razie potrzeby wystawiania naszego serwisu na serwerze dostępnym w internecie można to zrobić za pośrednictwem serwera nginx, który będzie służył jako proxy dla wbudowanego w reconlice-csv jetty, wówczas reconcile-csv będzie pracował pod adresem np. localhost:8080, a o obsługę żądań zewnętrznych zadba nginx. Dodatkowo serwis wyświetla trochę bardziej czytelny podgląd danych z pliku csv, podczas uspójniania (widoczne ramki tabeli). reconcile_view

Przykładowe wywołanie:
java -Xmx2g -jar reconcile-csv-0.1.2.jar plik.csv search_column id_column "http://moj_serwer.org" "80" "Miasta-Osoby Serwis Rekoncyliacji" "miasta_osoby"

lub (obecnie port inny niż 80 trzeba podać także w adresie serwera):
java -Xmx2g -jar reconcile-csv-0.1.2.jar plik.csv search_column id_column "http://localhost:8000" "8000" "Miasta-Osoby Serwis Rekoncyliacji" "miasta_osoby"

Kod źródłowy modyfikacji można pobrać z github, skompilowany plik jar również: releases.


spaCy, NER i półautomatyczne tworzenie indeksu postaci (01.10.2022)

Postęp rozwoju metod przetwarzania języka naturalnego (NLP) w ciągu ostatnich kilku lat pozwala na coraz szersze ich użycie, także w odniesieniu do tekstów w języku polskim. Jedną z technik NLP jest rozpoznawanie jednostek nazwanych (NER - named entity recogition), w czystym, pozbawionym struktury tekście poddanym analizie NER można oznaczyć np. osoby, miejsca czy daty. Do czego może się to przydać w przypadku publikacji historycznych? Jednym z najprostszych przypadków użycia jaki przychodzi do głowy jest stworzenie indeksu osób dla publikacji gdzie takiego indeksu brakuje.

Do testu posłuży artykuł (właściwie książka, sądząc po objętości) Stanisława Bodniaka, "Polska a Bałtyk za ostatniego z Jagiellona" wydana w 1946 roku (Pamiętnik Biblioteki Kórnickiej 3, 42-276, 1939-46). Dzieło to ma zresztą ciekawą historię gdyż pierwszy druk i rękopis zostały utracone w wyniku działań wojennych, autor z zachowanych korekt i notatek odtworzył i wydał je po wojnie. Publikacja została zdigitalizowana przez Muzeum Historii Polski i udostępniona w bazie bazhum.muzhp.pl w formie pdf.

Sytuacja jednak nie jest idealna - pdf nie ma indeksu (nie wiem czy jest w papierowym wydaniu), ma warstwę OCR więc jest przeszukiwalny, jednak jakość OCR nie jest perfekcyjna, w tekście pojawiają się np. przypadkowe spacje wewnątrz słów. Aby to poprawić można proces ocr przeprowadzić powtórnie. Ponieważ na codzień pracuję w systemie Linux, narzędzia których używam związane są z tym systemem, jednak wiele z nich pracuje także pod Windows/Mac lub istnieją odpowiedniki dla tych środowisk.

reconcile_view

Ciekawym narzędziem do wprowadzania warstwy tekstowej ocr do plików pdf jest ocrmypdf, może on rówież nadpisać istniejącą warstwę tekstową czy zapisać rozpoznany przez ocr na nowo tekst w osobnym pliku tekstowym (warstwa tekstowa zapisana przez ten program w pdf również nie była rewelacyjna, ale sam plik z tekstem okazał się niezły i nadawał się do dalszej obróbki). Silnikiem OCR używanym przez omawianą aplikację jest Tesseract, uważany za najlepszy mechanizm OCR open source.

Polecenie uruchamiające przetwarzanie pdf-a wygląda tak:
ocrmypdf -l pol --force-ocr --sidecar output.txt Bodniak_input.pdf Bodniak_output.pdf

gdzie opcja: -l pol odpowiada za obsługę języka polskiego, --force-ocr wymusza nadpisanie istniejącej warstwy ocr w pdf, zaś opcja --sidecar output.txt powoduje zapisanie tekstu z pdf w pliku tekstowym. Nazwy plików pdf na końcu to odpowiednio oryginalny plik oraz wyjściowy zmodyfikowany plik pdf.

Oryginalny plik pdf pobrany z repozytorium bazhum.muzhp.pl może wymagać wstępnego przetworzenia (program ocrmypdf zgłasza błąd "EncryptedPdfError: Input PDF is encrypted. The encryption must be removed to perform OCR"), służy do tego program qpdf uruchamiany z linii komend: qpdf --decrypt plik_wejściowy.pdf plik wyjściowy.pdf.

Innym narzędziem, które może posłużyć do wydobycia tekstu z pliku pdf jest program gImageReader będący graficzną nakładką na silnik OCR Tesseract, oprócz samego procesu OCR ma on dodatkowe funkcje pozwalające usunąć znaki końca wiersza ze środka akapitów oraz połączyć wyrazy podzielone między wiersze (komercyjne programy OCR mają z pewnością dużo większe możliwości w zakresie oczyszczania i poprawiania tekstu po procesie OCR).

tekst_gimage_reader

Mając plik z tekstem (z zachowanymi numerami stron, które zamieniłem na tagi w postaci [PAGE: 45] na początku każdej strony) możemy poddać go próbnej analizie NER. Istnieje wiele narzędzi i modeli, które można wykorzystać do tego celu, dostępna jest np. kolekcja narzędzi projektu CLARIN-PL (analiza NER do przetestowania także: on-line z wykorzystaniem narzędzia Liner2). W tym teście skorzystam jednak z popularnej biblioteki NLP: spaCy i skryptów w języku Python.

Najwygodniejszym sposobem testowania różnych rozwiązań w Pythonie jest środowisko Jupyter Notebook Sama instalacja i konfiguracja Pythona, Jupytera i spaCy mogłaby być tematem osobnej zapiski, ale procudury ich instalacji są przystępnie opisane na podanych wyżej stronach.

Po uruchomieniu środowiska Jupyter i utworzeniu nowego notatnika należy zaimportować bibliotekę spaCy oraz dodatkowo mechanizm wizualizacji displacy. Kolejnym krokiem będzie załadowanie tekstu do analizy oraz modelu dla języka polskiego. Standardowo spaCy oferuje 3 modele (zob. https://spacy.io/models/pl), mały, średni i duży, różniące się wielkością (od 20 do 500 MB) i dokładnością. Do celów tego testu wystarczy model średni 'pl_core_news_md' (Polish pipeline optimized for CPU. Components: tok2vec, morphologizer, parser, lemmatizer (trainable_lemmatizer), tagger, senter, ner.).

import spacy
from spacy import displacy

nlp = spacy.load('pl_core_news_md')
with open('../data/bodniak_baltyk_small.txt', 'r', encoding='utf-8') as f:
text = f.read()

doc = nlp(text)
displacy.render(doc, style='ent')

Kod podany powyżej przeprowadza analizę NER wczytanego tekstu (jest to fragment artykułu S. Bodniaka, str. 43) oraz wyświetla wynik w bardzo przystępnej wizualnie formie, gdzie znalezione jednostki nazwane (osoby, daty i miejsca) oznaczone są kolorami i etykietą ('persName', 'date', 'placeName'). Jak widać mechanizm radzi sobie całkiem nieźle z typowymi przypadkami osób, znajduje określenia temporalne i miejscowości.

jupyter_spacy

Niektóre z postaci, np. Barnim XI (książę pomorski, w dolnej części tekstu na załączonym zrzucie ekranu) pozostają jednak nierozpoznane, jako osoby rozpoznawane są natomiast błędnie wyrażenia typu: 'Odstraszała' czy 'Podniosłyby', model użyty do testu był przygotowany na podstawie bardziej współczesnych tekstów stąd imion i innych określeń używanych historyczne po prostu może nie znać. Analizowany tekst dotyczy XVI wieku, ale gdyby występowały w nim postacie średniowieczne typu 'Jan z Goźlic' jako osoba zostałoby uznane prawdopodobnie tylko imię Jan a nie całe wyrażenie. Zdarzają się też błędne rozpoznania miejsc jako osób itp. Czy można model 'przekonać' do bardziej poprawnego rozpoznawania takich określeń? Oczywiście, istnieją także zapewne lepsze modele, ale jest to temat na inny wpis.

Mając tekst z informacjami o numerach stronach, możliwość rozpoznania postaci analizą NER, możemy przygotować ich indeks, posłuży do tego skrypt pythonowy create_index.py Do pobrania z serwisu github. w którym najpierw wczytywana jest treść każdej strony, wykonywana analiza NER, wyniki dopisywanie do słownika wyników, na koniec wyświetlana jest posortowana lista znalezionych postaci (266 osób, z tym że niektóre występują czasem podwójnie lub potrójnie z powodu np. literówek czy błędnie ustalonej formy podstawowej imienia i nazwiska) wraz z numerami stron. Lista nie jest z pewnością dokładna, wymaga oczyszczenia przez człowieka (występują wspomniane już wyżej błędne rozpoznania, problemy z odmianą imion i nazwisk, jakość ocr też wpływa na błędy wynikające z literówek) jednak z pewnością jest dużym ułatwieniem przy tworzeniu indeksu, no i jej przygotowaniem zajął się komputer, dając człowiekowi czas na zadania, których komputer na dziś nie jest w stanie się podjąć. Poniżej fragment przygotowanego indeksu po wstępnym oczyszczeniu.

Achacy Czema [262]
Adam Konarski [190, 273]
Adolf (holsztyński) [157]
Albrecht Giese [88, 120, 171]
Albrecht Wilkowski [116]
Albrecht (Hohenzollern) [43, 44, 51, 52, 56, 57, 58, 70, 134]
Aleksander Połubieński [216]
Andrzej Swarożyński [74, 81, 84, 104, 274]
Antoni Angela [44]
Bajerski [93, 94]
Barnim XI [126, 187]
Bąkowski [138]
Birsen [46]
Bogusław XIII [187]
Boleman [177, 178]
Chodkiewicz [101, 191, 207, 228, 263, 270, 271]
[...]
Łukasz Górnicki [43]
Łukasz Podoski [190, 222, 273]
Maciej Scharping [58, 60, 67, 74, 83]
Maciej Strubicz [77, 271]


spaCy, POS i Matcher - czyli do czego przydają się rzeczowniki (03.10.2022)

Mając indeks postaci występujących w publikacji Zob. poprzednią zapiskę. wiemy kto występuje w tekście, ale czy wiemy kim był? W przypadku osób powszechnie znanych kojarzymy, że Zygmunt August był królem, Albrecht Hohenzollern władcą Prus, ale jakie funkcje pełnili, jakich zawodów byli przedstawicielami ci pozostali, mniej znani? Czy analizy NLP mogą nam pomóc w odpowiedzi na te pytania? Tak i to nawet najbardziej podstawowe metody, jak określenia części mowy (POS - part of speech) oraz Matcher - silnik dopasowywania wzorców.

Podczas przetwarzania tekstu spaCy dzieli go na zdania oraz tokeny, każdy token jest zwykle słowem lub znakiem interpunkcyjnym. Dla każdego z tokenów spacy określa jaką jest częścią mowy, można więc dzięki temu odseparować same rzeczowniki występujące w tekście i posortować je w kolejności występowania. Taką pracę wykonuje skrypt popular_noun.py Do pobrania z serwisu github. .

Najpopularniejszy rzeczownik w publikacji Stanisława Bodniaka (Polska a Bałtyk za ostatniego Jagiellona) to słowo 'król' występujące w różnych formach ('królów, królach, króla, królu, królowi, królem, Króla, Król, król') 435 razy, kolejne zaś to 'rok', 'statek', 'straż', 'kapitan', 'komisja', 'okręt', 'żegluga', 'miasto', 'morze' - co nie jest zaskakujące biorąc pod uwagę tematykę artykułu. Wśród nich mamy dwa reprezentujące funkcję: król i kapitan. Przykład fragmentu wyników działania skryptu w tabelce poniżej:

FunkcjaLiczba wystąpieńTypowe formy
król 435 królów, królach, króla, królu, królowi, królem, Króla, Król, król
kapitan 192 kapitanowie, kapitan, kapitanów, kapitanach, Kapitan, kapitanami, kapitana, kapitanowi, kapitanem, kapitanom
komisarz 130 komisarzom, komisarze, komisarzem, Komisarze, komisarza, komisarzy, komisarz

Kolejna część pracy należy do człowieka, z otrzymanej listy należy wybrać inne funkcje i zawody, znajdziemy tam między innymi funkcje: komisarz, strażnik, kapr, poseł, car, kupiec, żołnierz, cesarz, senator, członek, kasztelan, urzędnik, admirał, delegat, mistrz, żeglarz, prezes, sekretarz, armator, władca, kanclerz, marynarz, właściciel, dowódca, podkanclerzy, namiestnik, książę, ks., starosta, sługa, wojownik, mocodawca, burmistrz. Mając listę funkcji można wykorzystując mechanizm Matcher - silnik dopasowywania wzorców biblioteki spaCy - wyszukać powiązania funkcji i osób Rule-based matching. .

Wzorzec według którego spaCy dopasowywuje tekst może wyglądać na przykład tak:
[{"LEMMA": {"IN": lista}}, {"POS":"ADJ", "OP":"?"},{"POS":"PROPN", "OP":"+"}],
gdzie lista jest wyżej wypisaną listą funkcji, LEMMA - oznacza formę podstawową wyrazu, "POS":"ADJ" oznacza wyraz będący przymiotnikiem, "POS":"PROPN" - oznacza wyraz będący jednostką nazwaną (np. osobą wymienioną z imienia i nazwiska), "OP":"?" to operator wskazujący, że poprzedni wzorzec może wystąpić 0 lub 1 raz, "OP":"+" zaś jest operatorem oczekującym wystąpienia wzorca co najmniej raz (lub więcej razy).

Efektem przetworzenia tekstu badanej publikacji S. Bodniaka przez zestaw podobnych reguł (skrypt funkcje_zawody.py - pod tym samym adresem co poprzedni skrypt) jest lista wyrażeń zawierających osoby i ich funkcje/zawody np.

  • "kasztelana chełmińskiego Jerzego Oleskiego"
  • "kanclerz Nils Gyllenstierna"
  • "admirałem szwedzkim Klausem Flemmingiem"
  • "namiestnik Inflant Jan Chodkiewicz"
  • "sekretarz królewski Kasper Geschkau"
  • "Michała Brunowa kanclerza Gotarda Kettlera"

Możemy stąd uzyskać np. listę występujących w tekście kapitanów i kaprów: Figenow, Gendtrichsen, Michał Starosta, Paweł Glasow, Jan Munckenbeck, Kersten Rode, Marcin Śchele, Mateusz Scharping, Marek Wilde.

Unikalnych znalezisk jest niespełna 90 czyli tylko dla 1/3 osób z indeksu postaci stworzonego w poprzedniej zapisce znaleziono ich funkcje lub zawód, ale jest to bardzo prosta metoda (pominąłem np. zawody/funkcje występujące mniej niż 10 razy), algortym można zapewne udoskonalić, poza tym te informacje dostajemy bez potrzeby ręcznego przeglądania 230 stron publikacji...


spaCy - jak poprawić wyniki NER czyli Barnim XI to
też osoba... (05.10.2022)

Rozpoznawanie jednostek nazwanych przeprowadzone na fragmencie publikacji S. Bodniaka zob. zapiskę z 1.10.2022. dało całkiem niezłe rezultaty w zakresie rozpoznawania osób, zdarzały się jednak takie nazwy, które mechanizm pominął. Na przykład 'Barnim XI' - książę pomorski. Czy można nauczyć spaCy rozpoznawania takich nietypowych imion? Tak, i nie musi się to wiązać z trenowaniem własnego modelu. EntityRuler jest komponentem potoku przetwarzania spaCy służącym do rozpoznawania jednostek nazwanych poprzez reguły Dokumentacja komponentu EntityRuler. i może zostać użyty do polepszenia wyników modelu statystycznego NER.

tekst bez rozpoznania imienia Barnim

Aby użyć komponentu należy dodać go metodą:
nlp.add_pipe("entity_ruler") oraz zdefiniować wzorce dla encji, które chcemy rozpoznawać w badanym tekście, mogą to być proste wzorce, np. pasujący do naszego przypadku:

pattern = [{"label":"persName", "pattern": "Barnim XI"}]

Mogą to także być bardziej zaawansowane wzorce oparte na tokenach:

[{"label": "persName", "pattern": [{"LOWER": "barnim"}, {"LOWER": "wielki"}]}]

Korzystamy w tym przypadku z istniejącej w modelu pl_core_news_md etykiety persName oznaczającej imiona i nazwiska osób.

Powtórne przetworzenie analizowanego fragmentu tekstu powoduje już poprawne rozpoznanie wyrażenia 'Barnim XI' jako osoby (label = persName)

tekst z rozpoznanym imieniem Barnim

Komponent EntityRuler potrafi jednak znacznie więcej, możemy z jego pomocą poprosić spaCy o rozpoznawanie nowej kategorii obiektów np. funkcji pełnionych przez osoby występujące w tekście. Zakładając, że interesują nas funkcje: król, hetman, senator oraz chcemy odnaleźć nie tylko dokładnie takie wystąpienia słów (np. 'senator') ale i wersję w liczbie mnogiej ('senatorowie') lub w formie odmienionej ('senatorowi') należy użyć bardziej zaawansowanego sposobu definiowana wzorca:
[{"label":"OCCUPATION", "pattern": [{"LEMMA": {"IN":["hetman","senator","marszałek", "król"]}}]}]

Nowa etykieta to 'OCCUPATION', 'LEMMA' oznacza formę podstawową słowa, np. dla wyrazów 'senator' i 'senatorowie' forma podstawowa będzie równa 'senator', zamiast jednej funkcji np. "LEMMA":"hetman" można we wzorcu wskazać od razu całą listę funkcji korzystając z zapisu: {"LEMMA": {"IN":["hetman","senator","marszałek", "król"]}. Efekt działania tak zdefiowanego wzorca wraz z całym fragmentem kodu widoczny jest dla zrzucie ekranu poniżej, a wszystko to bez trenowania własnego modelu. Notatnik jupytera z kodem ze zrzutu ekranu jest na githubie. Należy oczywiście zdawać sobie sprawę z ograniczeń, dla języka polskiego będą występować problemy z lematyzacją, lemma od słowa podkanclerzy to wg spacy 'podkancler', ale już lemma od słowa podkanclerzego to 'podkanclerzy'.

EntityRuler - funkcje i zawody

Artykuły, kursy, filmy - o NLP w badaniach historycznych (06.10.2022)

Internet jest pełen świetnych materiałów na temat NLP, uczenia maszynowego, programowania w pythonie czy konkretnych narzędzi i bibliotek, w tym miejscu chciałbym gromadzić linki przede wszystkim do tych z nich, które dotyczą humanistyki cyfrowej, wykorzystania NLP w badaniach historycznych lub były przygotowane z myślą o początkujących humanistach cyfrowych.

Kurs Natural Language Processing with spaCy & Python - Course for Beginners by Dr. William Mattingly na kanale freeCodeCamp.org: LINK oraz kurs Introduction to spaCy 3 w formie podręcznika online: LINK

Więcej świetnych filmów i kusrów tego samego autora dostępnych jest na jego własnym kanale YT: "Python Tutorials for Digital Humanities" LINK oraz na jego stronie np.: "Introduction To Named Entity Recognition With a Case Study of Holocaust NER" LINK

Yvonne Gwerder Named Entity Recognition in Digitized Historical Texts (praca magisterska, Universitat Zurich) PDF

Felipe Álvarez de Toledo López-Herrera Automated Tagging of Historical, Non-English Sources with Named Entity Recognition (NER): A Resource LINK

Vatsala Nundloll, Robert Smail, Carly Stevens, Gordon Blair Automating the extraction of information from a historical text and building a linked data model for the domain of ecology and conservation science LINK

Claire Grover, Sharon Givon, Richard Tobin, Julian Ball Named Entity Recognition for Digitised Historical Texts PDF

Helena Hubková, Named-entity recognition in Czech historical texts PDF

Blog NLP for Historical Texts https://nlphist.hypotheses.org/ prowadzony przez Michaela Piotrowskiego, autora książki Natural Language Processing for Historical Texts (2012) LINK

Asarsa Kunal Analysis of named entity recognition & entity linking in historical text (Masters theses, Northeastern University) PDF

Kimmo Kettunen, Eetu Mäkelä, Teemu Ruokolainen, Juha Kuokkala, Laura Löfberg Old Content and Modern Tools – Searching Named Entities in a Finnish OCRed Historical Newspaper Collection 1771–1910 LINK

Christian Henriot Rethinking historical research in the age of NLP (blog) LINK

Magdalena Turska: Informatyk wśród humanistów czyli krótka historia zderzenia światów i co z tego wynikło (video, nie dotyczy bezpośrednio NLP, ale wielu ciekawych aspektów styku informatyki i humanistyki) LINK

E. Oliveira, G. Dias, J. Lima, J.P.C. Pirovani: Using Named Entities for Recognizing Family Relationships LINK

Sam Fields, Camille Lyans Cole, Catherine Oei, Annie T Chen: Using named entity recognition and network analysis to distinguish personal networks from the social milieu in nineteenth-century Ottoman–Iraqi personal diaries LINK

Audrey Holmes: Named Entity Resolution for Historical Texts (praca magisterska, University of Washington, 2019) LINK

Nicolas Eymael Da Silva: Extraction of entities and relations in Portuguese from the Second HAREM Golden Collection (praca licencjacka, Universidade Federal Do Rio Grande Do Sul) PDF

N. Abadie, E. Carlinet, J. Chazalon, and B. Dumenieu: A Benchmark of Named Entity Recognition Approaches in Historical Documents Application to 19th Century French Directories PDF

Marcella Tambuscio, Tara Lee Andrews: Geolocation and Named Entity Recognition in Ancient Texts: A Case Study about Ghewond’s Armenian History PDF

Leonardo Zilio, Maria Jos´e Bocorny Finatto, and Renata Vieira: Named Entity Recognition Applied to Portuguese Texts from the XVIII Century PDF

Alistair Plum, Tharindu Ranasinghe, Spencer Jones, Constantin Orasan, Ruslan Mitkov: Biographical: A Semi-Supervised Relation Extraction Dataset PDF

Nitisha Jain, Alejandro Sierra-Múnera, Julius Streit, Simon Thormeyer, Philipp Schmidt, Maria Lomaeva and Ralf Krestel: Generating Domain-Specific Knowledge Graphs: Challenges with Open Information Extraction PDF

Fabio Chiusano: Building a Knowledge Base from Texts: a Full Practical Example (blog) LINK

Tomaz Bratanic: Extract knowledge from text: End-to-end information extraction pipeline with spaCy and Neo4j (blog) LINK


Anotacja tekstów w Doccano i trenowanie własnego
modelu (07.10.2022)

Wykorzystanie reguł do wyszukiwania encji nazwanych poprzez komponent EntityRuler biblioteki spaCy jest całkiem skutecznym sposobem poprawienia wyników NER. Możemy zastosować ten mechanizm jeżeli w używanym modelu brakuje np. jednej etykiety i łatwo jest uzupełnić ten brak za pomocą zestawu słów kluczowych, tworząc odpowiednie reguły. W trakcie prac nad tekstami spotkamy się jednak na pewno z bardziej skomplikowanymi przypadkami, a wówczas niezbędne stanie się wytrenowanie własnego modelu, dopasowanego do specyfiki naszych problemów. Przed procesem trenowania niezbędne jest jednak przejście przez trzy wstępne etapy: 1) należy zgromadzić zestaw danych (tekstów) do uczenia, 2) przygotować anotacje tych tekstów, 3) zdecydować, czy będziemy trenować własny model od zera, czy też dotrenowywać już istniejący. Krok drugi wiąże się też z wyborem oprogramowania do anotacji oraz przygotowaniem jej wyników do formy akceptowalnej przez spaCy.

Celem testu będzie przygotowanie danych, a następnie wytrenowanie modelu rozpoznającego zawody/funkcje (dla uproszczenia będzie to mały model trenowany 'od zera'). Próbką tekstów do anotacji w naszym teście będzie zbiór fragmentów artykułu S. Bodniaka Zob. zapiskę z 1.10.2022. , pochodzących z różnych części tej publikacji, lecz zawsze zawierających określenia nazw zawodów lub funkcji. Zbiór liczy w sumie 105 zdań, czyli parę procent całego tekstu, zapewne dla osiągnięcia naprawdę dobrych wyników powinien być wyraźnie większy (wspomina się o minimum 100 przykładach dla każdej uczonej encji). Najlepszą aplikacją do anotacji współpracującą z spaCy jest z pewnoścą Prodigy Strona aplikacji prodigy. , narzędzie przygotowane przez autorów spaCy - firmę Explosion. Jest to jednak oprogramowanie komercyjne i, choć w realnej sytuacji z pewnością warto w nie zainwestować (ceny zaczynają się od 390 USD za licencję), to w naszym teście użyjemy narzędzia dostępnego bezpłatnie. Istnieje ich co najmniej kilka, np. ner-annotator, TagEditor (aplikacja desktopowa dla systemu Windows), brat, INCEpTION czy doccano. Każde z nich na pewno dobrze się sprawdzi przy wprowadzaniu etykiet, jednak najlepsze wrażenie zrobiło na mnie doccano, aplikacja webowa, którą można w prosty sposób zainstalować lokalnie korzystając z dockera. Opis instalacji znajduje się na stronie projektu, polecam ścieżkę z wykorzystaniem docker-compose. doccano - instalacja poprzez docker-compose.

Praca w aplikacji doccano zaczyna się od zalogowania na nasze konto (podczas instalacji zakładane jest konto użytkownika będącego administratorem, wraz z hasłem, później można utworzyć dodatkowe konta nadając im role np. anotatora, czy osoby akceptującej anotacje) i utworzenia projektu. Zakładając nowy projekt należy wskazać typ projektu (rodzaj zadania), w naszym przypadku chcemy  rozpoznawać jednostki nazwane (NER), należy więc wybrać sequence labeling.

doccano lista projektów

Do utworzonego projektu można zaimportować dane do anotacji (menu Dataset), doccano akceptuje różne pliki tekstowe (lub zestawy plików), zaimportowane dane będą widoczne w formie tabeli.

doccano lista dokumentów

Przed rozpoczęciem anotowania niezbędne jest jeszcze utworzenie etykiet, którymi będzie oznaczany tekst. Biorąc pod uwagę cel testu jedyną używaną etykietą będzie occupation na oznaczenie zawodu lub funkcji pełnionej przez postaci występujące w tekście.

doccano tworzenie etykiet

Sam proces anotacji w doccano jest bardzo zbliżony do pracy w innych aplikacjach tego typu. Zaznaczamy słowo lub wyrażenie, program wyświetla wówczas podręczne menu, z którego można wybrać właściwą etykietę. Każda z etykiet ma swój kolor (definiowany przez użytkownika na etapie tworzenia etykiet) i takim kolorem doccano podkreśla oznaczony fragment tekstu (wyświetla również nazwę etykiety).

doccano w trakcie anotacji

Po zakończonym procesie anotacji można otagowany zbiór danych wyeksportować do formatu JSONL, w którym znajduje się anotowany tekst oraz etykiety i ich pozycje w tekście. Niestety, nie jest to gotowy plik, który możemy użyć do trenowania modelu z użyciem spaCy. W obecnej wersji (3.4 w momencie pisania zapiski) spaCy oczekuje pliku binarnego z przyjętym zwyczajowo rozszerzeniem *.spacy (format DocBin). Na szczęście konwersja pliku *.jsonl na *.spacy nie jest skomplikowana, można do tego celu użyć przykładowego skryptu doccano2spacy.py, który wywoływany jest z nazwą pliku jsonl jako jedynym parametrem. Skrypt można pobrać z repozytorium github.

doccano fragment pliku jsonl

Mając zbiór uczący, można przystąpić do trenowania nowego modelu. Proces trenowania wywoływany jest z linii komend poleceniem spacy train, należy również  podać nazwę pliku konfiguracyjnego (np. config.cfg) z parametrami uczenia, spaCy ułatwi jego utworzenie z domyślnymi parametrami. Procedura opisana jest na stronie spacy.io (tak, opis jest długi - to jest skomplikowane!). Należy też podać ścieżkę do zbioru uczącego i walidacyjnego (aby zachować reguły gry należałoby także przygotować zbiór walidujący, tu dla uproszczenia skorzystam z kopii zbioru uczącego). Parametr --output polecenia wskazuje, gdzie będą zapisane wytrenowane modele (spacy zapisuje ostatni i najlepszy z modeli).
python -m spacy train config.cfg --output ./output --paths.train ./bodniak.spacy --paths.dev ./bodniak.spacy
Podczas działania (dla większych zbiorów czas wykonywania obliczeń może być spory, w tym przypadku będzie to kilkadziesiąt sekund) wyświetlany jest stan kolejnych iteracji uczenia (zob. zrzut ekranu poniżej).

proces trenowania modelu

Po zakończeniu trenowania można przystąpić do weryfikacji działania modelu. Wykorzystamy do tego środowisko jupyter i mechanizm wizualizacji displacy. Wczytanie nowego modelu w spaCy odbywa się podobnie jak w przypadku modelu standardowego z tym, że zamiast nazwy należy, podać ścieżkę do katalogu z zapisanym modelem: nlp = spacy.load('../output/model-best/').
Wynik działania na spreparowanym zdaniu, w którym występują nazwy funkcji/zawodów widać na załączonym niżej zrzucie ekranu z fragmentem notebooka aplikacji jupyter. Notebook test_ner_model.ipynb do pobrania z repozytorium. Można zauważyć poprawne rozpoznanie słów 'król', 'kapitan', 'kanclerzem', jednak wyrażenie 'podstarościm' pozostało niezauważone, najwyraźniej model nie zdołał się go nauczyć, w materiale uczącym występowało ono faktycznie niezbyt często.

test modelu ner

I jeszcze dwa przydatne linki: do dokumentacji aplikacji doccano LINK, oraz do artykułu, który dużo obszerniej opisuje tę aplikację LINK.


Skrypty w poszukiwaniu wydarzeń (10.10.2022)

Strona xvi-wiek.pl to przede wszystkim kalendarium ciekawych wydarzeń z XVI wieku, niektórych bardziej znanych, innych zaś zupełnie egzotycznych. Skąd jednak te informacje pochodzą? Spora część (także) z wikipedii, ale większość z drukowanych książek i artykułów a sposób ich wyszukiwania nie polegał najczęściej na spędzaniu wieczorów z herbatą i książką (choć to przyjemne zajęcie), lecz miał coś wspólnego z cyfrową humanistyką. Wiele współcześnie wydawanych publikacji jest już dostępnych od razu w formie elektronicznej, coraz więcej starszych jest digitalizowanych i udostępnianych w formie plików pdf (już rzadziej jako DjVu). Jeżeli zaś publikacja ma postać cyfrowego pliku (z tekstem), to pozwala na przetwarzanie jej przez narzędzia informatyczne.

Wpisy w kalendarium są faktami i wydarzeniami związanymi z konkretną datą dzienną. W przypadku xvi-wieku.pl przyjęte też zostały określone ramy czasowe: od 1490 do 1586 roku włącznie. Daty dzienne mogą być zapisane w różnej formie, bardziej nowoczesnej np. 12.08.1560, trochę bardziej tradycyjnej np. 12 VIII 1560, lub z wprost podaną nazwą miesiąca: 12 sierpnia 1560 r. Mogą to też być fragmenty dat typu "12 stycznia" gdyż z kontekstu wynika o jaki rok chodzi.

Posiadając zgromadzoną kolekcję plików pdf (pochodzących główne z repozytoriów cyfrowych RCIN, bazhum.muzhp.pl i innych źródeł o otwartym dostępie) można wyszukać w nich daty dzienne z wymaganego okresu, nie chodzi oczywiście o ręczne przeszukiwanie w przegladarce PDF-ów każdego oczekiwanego wariantu daty, można skorzystać z tzw. wyrażeń regularnych Definicja wyrażeń regularnych w Wikipedii oraz ładne wprowadzenie do tematu. i jednego z narzędzi, które na przeszukiwanie plików pdf w taki sposób pozwalają. Dla osób  pracujących w środowisku linuksowym pierwszym wyborem będzie zwykle narzędzie konsolowe np. pdfgrep (w systemie Windows na pewno istnieją równie dobre i wygodniejsze odpowiedniki np. dnGrep). Program pozwala na przetworzenie bieżącego katalogu, lub całego drzewa katalogów zawierającego pliki pdf i przeszukanie tekstowej zawartości pdf-ów wg zadanego wzorca.

Zakładając, że wzorzec powinien uwzględniać dzień - 1 lub 2 cyfry, miesiąc w formie liczby rzymskiej lub nazwy oraz rok czerocyfrowy, parametry wywołania programu pdfgrep mogłyby wygladać np. tak:

pdfgrep -rnPH "\s+\d{1,2}\s+([a-z]{3,}|[XVI]+)\s+(15|149)" *.pdf

gdzie -rnPH to parametry programu decydujące o wyszukiwaniu rekurencyjnym plików (czyli program będzie szukał w podkatalogach i podkatalogach katalogów), o wypisywnaiu w wynikach numerów stron, o użyciu wyrażeń regularnych i podawaniu w wyniku nazw plików pdf, pozostała część polecenia to wyrażenie zwracające daty oraz ścieżka do plików (z maską). Wyrażenie regularne ma trzy części \d{1,2} poszukuje liczby o długości od jednej do 2 cyfr, fragment ([a-z]{3,}|[XVI]+) szuka rzymskiego numeru miesiąca lub nazwy miesiąca, ostatnia część (15|149) odpowiada za rok, poszukiwana jest liczba 15 lub 149 będąca początkiem roku (co jest drobnym problemem bowiem oznacza, że wpadną w wynikach także lata 1587-99).

Uruchomienie powyższego polecenia w katalogu z publikacją S. Bodniaka zwraca 24 wyniki (zob. zrzut ekranu poniżej). Program wypisuje nazwę pliku pdf w którym znaleziono wzorzec, numer strony (zielona liczba), oraz krótki fragment tekstu ze znalezionym wzorcem, gdzie wzorzec jest wyróżniony czerwonym kolorem. Pozostaje zweryfikować datę na podanej stronie w pliku pdf, być może jest to data wydarzenia, które może trafić do kalendarium.

pdfgrep wyniki przeszukiwania.png

Co jednak, gdyby wyszukiwanie miało bardziej ambitny cel, na przykład znalezienie dat dziennych, ale występujących w tym samym zdaniu co postać historyczna i może jednocześnie z jakąś nazwą geograficzną? Takie zadanie może być trudne do wykonania za pomocą samych wyrażeń regularnych, ale będzie świetnym ćwiczeniem z wykorzystaniem biblioteki spaCy. Dla uproszczenia testowi poddany zostanie fragment tekstu, w którym tylko jedno ze zdań pasuje do określonych założeń.

przykładowy fragment tekstu

Przetworzenie tekstu przy pomocy biblioteki pozwoli rozpoznać nazwy własne i wyrażenia temporalne.

daty analiza ner

spaCy zadba także o podział tekstu na zdania. Aby wyszukać te z nich, które spełniają określone wyżej kryteria wystarczy zbadać dla każdego zdania, czy występuje w nim osoba, nazwa geograficzna i data. Każda rozpoznana encja posiada przypisaną etykietę, te mogą się różnić zależnie od użytego modelu (w tym przykładzie wczytany został największy standardowy model dostępny dla języka polskiego - pl_core_news_lg). Encje osób oznaczane są etykietą 'persName', encje miejsc jako 'placeName', a wyrażenia temporalne jako 'date'. Prosty kod filtrujący wraz z wynikiem widoczny jest na zrzucie ekranu poniżej. Program dodatkowo sprawdza czy znaleziona data jest datą dzienna z lat 1490-1599, oraz czy encja 'placeName' jest nazwą zaczynającą się z dużej litery (aby pominąć znaleziska typu 'król szwedzki').

kod filtrujący i wynik działania

Wydrukowane zdanie jest jedynym z przetwarzanego fragmentu tekstu zawierającym osobę (lub osoby), miejscowość lub inną nazwę geograficzną oraz datę - z dokładnością  do konkretnego dnia. Można teraz tę samą metodę zastosować do całej treści publikacji Stansława Bodniaka, przy czym ponieważ spaCy pracuje raczej na tekstach (a nie plikach binarnych) użyty zostanie nie plik pdf, ale plik tekstowy z zawartością pdf-a, przygotowany w jednej z poprzednich zapisek. zob. zapiskę z 1.10.2022. Kod przygotowany w notebooku można zapisać w formie funkcji, która będzie wywoływana dla każdej strony przetwarzanego tekstu, wynik w postaci numeru strony i znalezionego zdania spełniającego przyjęte warunki będzie wypisywany na ekran konsoli. Notebook wyszukiwanie_dat.ipynb i skrypt events_finder.py do pobrania z repozytorium. Efekt działania skryptu to około 20 znalezisk, które mogą być potencjalnymi wydarzeniami, wartymi opisania w kalendarium (fragment wyniku na zrzucie ekranu zamieszczonym poniżej).

wynik działania skryptu event_finder.py

spaCy, NEL - łączenie rozpoznanych encji z wikidata.org (17.10.2022)

Rozpoznanie w tekście nazw własnych może być wstępem do następnego kroku - połączenia znalezionych encji np. osób z bazą wiedzy, uzyskujemy wówczas identyfikację danej osoby, pojawiający się w analizowanej publikacji Fryderyk II staje się tożsamy z duńskim królem żyjącym w XVI wieku, który w bazie wiedzy np. wikidata.org posiada jednoznaczny identyfikator Q154041. Proces łączenia encji z identyfikatorami z baz wiedzy określany jest często jako named-entity linking (czyli NEL) Entity linking. lub named-entity disambiguation i nie jest tak prostym zadaniem jak się pozornie wydaje. Fryderyków II czy miejscowości o nazwie 'Warszawa' może w bazie wiedzy istnieć wiele, nazwa miejscowości w publikacji może być  zniekształcona (może być jednym z używanych wariantów nazwy), może być nazwą używaną w XVI wieku, lub nazwą polską używaną przed 1939 rokiem dla miejscowości dziś leżącej poza Polską. Zygmunt II August może w analizowanym tekście występować także jako Zygmunt August, lub po prostu jako król Zygmunt.Trudność polega właśnie na automatycznym dopasowaniu właściwego elementu z bazy wiedzy do encji z publikacji, z uwzględnieniem drobnych różnic w pisowni oraz kontekstu.

Jednym z możliwych rozwiązań problemu łączenia encji z tekstu z bazami wiedzy jest obecny w bibliotece spaCy komponent EntityLinker Dokumentacja na stronie spaCy. . Pozwala on na wytrenowanie własnego modelu, który na podstawie przygotowanej bazy wiedzy, rozpoznanej encji nazwanej i kontekstu w której wystąpiła będzie potrafił wskazywać właściwy dla niej identyfikator.

W ramach testu Test przygotowany na bazie tutorialu z repozytorium przykładów spaCy opartego jak zwykle na tekście publikacji S. Bodniaka rozpoznawane i łączone z bazą  wiedzy będą dwie postacie: króla Zygmunta II Augusta i cara Ivana IV Groźnego, źródłem bazy wiedzy będzie zaś wikidata.org. Identyfikatory z Wikidata (QID) dla obu postaci to odpowiednio Q54058 i Q7996. W przypadku analizowanej publikacji nie ma właściwie jakiejś wieloznaczności w encjach osób, Zygmunt August będzie zawsze tym Zygmuntem, królem Polski, Iwan będzie carem Rosji. Większym wyzwaniem byłoby gdyby w tekście występował np. Mateusz Scharping jako kaper i inna postać np. Ernest Scharping będącą np. niemieckim dyplomatą, wówczas dla encji będących samym nazwiskiem łączenie z identyfikatorami z wikidata.org musiałoby następować na podstawie kontekstu. Tu jednak nie ma takiej sytuacji, do bazy można natomiast dodać dodatkowe aliasy naszych postaci, również pewnie identyfikowalne z elementami wikidata np. 'Zygmunt August', 'Iwan IV', 'Iwan Groźny'.

tworzenie bazy wiedzy

Oprócz obiektu KnowledgeBase (naszej bazy wiedzy) potrzebne są również próbki tekstów do trenowania modelu - przygotowane dane treningowe na podstawie zdań z artykułu Stanisława Bodniaka zawierających wzmianki na temat analizowanych postaci. Dane treningowe zawierają w przypadku tego testu około 30 zdań (zapewne powinno być trochę więcej). Tak jak w przypadku anotacji dla trenowania modelu NER tu również anotacja została przeprowadzona w programie doccano, a dane wyjściowe z doccano wymagały przetworzenia na format oczekiwany przez spaCy (tu z pliku *.jsonl dane importowane są bezpośrednio do skryptu w pythonie). Ponieważ jednak doccano nie wspiera bezpośrednio anotacji pod kątem NEL, a przynajmniej nie widzę takiej opcji, zastosowane zostało małe obejście problemu - ze względu na małą liczbę anotowanych postaci w roli etykiet użyto identyfiktorów QID z wikidata.

anotacja doccano nel

Mając przygotowany do trenowania dataset należy go podzielić na cześć treningową i część walidującą, zwykle 20% zbioru wystarcza do walidacji. Proces trenowania dla tak małego zestawu danych nie powinien trwać dłużej niż 30 sekund.

proces trenowania nel

Wynik zaś można zweryfikować na testowym fragmencie artykułu, który nie był częścią zbioru uczącego. Jak widać na zrzucie poniżej, zarówno Zygmunt August jak i Iwan Groźny zostali rozpoznani i zidentyfikowani poprawnie, jednak po przetworzeniu całej publikacji można zauważyć, że wyniki dla cara są jednak słabsze, model nie zawsze przypisuje identyfikator z wikidata do rozpoznanej encji tej postaci. Problemem oprócz może zbyt małej próbki treningowej może być język polski, trudniejszy w przetwarzaniu od angielskiego choćby ze względu na fleksję. Notebook NEL_test.ipynb do pobrania z repozytorium. wyniki analizy nel

Sposób łączenia i identyfikacji z bazami wiedzy dostępny w spaCy jest jednak tylko jedną z wielu możliwości Zob. także narzędzie elinker przygotowane przez konsorcjum Clarin-PL. . Przed dołączeniem do biblioteki komponentu EntityLinker powstawały rozwiązania przygotowane przez użytkowników spaCy np. pakiet spacy-entity-linker Zobacz opis. . Pomysł zakładał przygotowanie lokalnej bazy wiedzy (w formacie sqlite) opartej na wikidata i dopasowywanie pojęć w przetwarzanym tekście do etykiet i aliasów elementów w wikidata. Takie podejście nie wymagało trenowania modelu, nie uwzględniało jednak kontekstu w którym występowało badane wyrażenie. Przygotowana baza wiedzy oparta jest na angielskiej instancji wikidata a jej zmiana na inną, jak opisano w dokumentacji pakietu, nie jest niestety prosta.

Można za to przetestować zbliżone, ale mniej wydajne rozwiązanie w postaci bezpośredniego odpytywania przez API serwisu wikidata.org. Rozpoznawaniu i łączeniu będą poddawane tylko postacie (encje z etykietą persName ropoznane przez spaCy), tylko w polskiej wersji językowej wikidata, a otrzymane wyniki będą dodatkowo weryfikowane poprzez sprawdzenie czy znaleziony element posiada właściwość instance of = 'human' oraz czy właściwość date of death (P570) z wikidata wskazuje na okres historyczny tożsamy z chronologią  tematyki artykułu. Funkcja wikilinker, której kod jest dostępny w notebooku wspomianym wyżej, ma trzy obowiązkowe argumenty: string z nazwą szukanej postaci, najwcześniejsza data śmierci postaci, najpóźniejsza data śmierci postaci (oraz opcjonalny czwarty argument z liczbą rozważanych elementów wyszukanych w wikidata - zwykle wyszukiwanie zwraca więcej niż 1 pozycję, szczególnie dla mniej precyzyjnych tekstów np. 'Albrecht' ta liczba może być spora, domyślnie przyjęto 10). Znając tematykę artykułu i okres historyczny jakiego dotyczy możemy w wyszukiwaniu uwzlgędnić zakres lat 1550-1625, co znacznie poprawi precyzję wyszukiwania. Fragment kodu i efekt wyszukiwania widoczny jest na zrzucie poniżej.

wikilinker wyszukiwanie

W 9 przypadkach na 10, funkcja wikilinker znalazła prawidłowy identyfikator postaci w wikidata.org, udało się to nawet dla imienia 'Albrechta', zapewne trochę przypadkiem dla podanego zakresu lat (zakresu dla daty śmierci postaci) i imienia pierwszy znaleziony element to właśnie Albrecht Hohenzollern. Jedyna nieznaleziona postać to Szymon Maricjus-Czystochlebski - prawdopodobnie jest to Q9352684 (Szymon Marycjusz, alias: Szymon Maricjusz z Pilzna) Zobacz w wikidata. , ale różnica między encją NER w tekście a etykietą w wikidata.org jest zbyt duża by identyfikator mógł zostać znaleziony. Można też podejrzewać, że nawet gdyby etykieta lub alias tego elementu w wikidata miały formę taką jak występuje w tekście, to ze względu na fleksję wyszukiwanie również by się nie powiodło Przed wyszukiwaniem w wikidata.org skrypt dokonuje lematyzacji encji (przekształca imię i nazwisko postaci do formy podstawowej) jednak ta funkcjonalność w używanym modelu spaCy nie jest doskonała, dla rzadszych imion, nazwisk będzie raczej niepoprawna. .

Oczywiście proste funkcje tego typu nie zastąpią prawdziwych mechanizmów NEL, jednak gdy możemy z góry sprecyzować zakres łączenia (tylko osoby, tylko polska wikidata, tylko określony zakres chronologiczny) rezulat bywa całkiem znośny.

Eksperymenty z automatyczną anotacją i łączeniem z bazą wiedzy można przeprowadzić bez programowania, np. dzięki stronie wikifier.org, która analizuje wklejony przez użytkownika tekst, łącząc znalezione encje/pojęcia z hasłami wikipedii. Wikifier obsługuje 100 języków, w tym polski, jego sposób działania opisany jest w artykule Annotating Documents With Relevant Wikipedia Concepts. Na zrzucie ekranu poniżej widoczny jest efekt przetworzenia przez Wikifier fragmentu publikacji S. Bodniaka.

strona wikifier.org

spaCy i automatyczne tagowanie encji w TEI Publisherze (22.10.2022)

Czy trzeba programować by zajmować się przetwarzaniem języka naturalnego? Prosta odpowiedź brzmi: nie, można przecież wynająć programistę lub specjalistę data science. Ale ponieważ czas programistów kosztuje, a komunikacja między humanistami a specjalistami technicznymi bywa skądinąd wyzwaniem i często rzutuje na jakość wyników, można skorzystać z innej ścieżki - istnieją narzędzia nie wymagające umiejętności programowania. Choć nadal należy rozumieć co się robi, co jest możliwe a co niekoniecznie. NLP jest bardzo dynamicznie rozwiającą się technologią, ale stopień jej rozwoju jest inny dla każdego języka naturalnego (to co dziś jest możliwe dla tekstu angielskiego, niekoniecznie będzie możliwe dla tekstu polskiego).

Jednym z popularnych narzędzi wśród humanistów cyfrowych jest TEI Publisher Oficjalna strona aplikacji TEI Publisher. , aplikacja znacznie ułatwiająca przygotowanie edycji cyfrowych na bazie dokumentów xml w standardzie TEI. Program ten od jakiegoś czasu posiadał możliwość ręcznej anotacji tekstu podstawowymi eykietami typu osoba i miejsce Zobacz video: TEI Publisher Annotation Facility: Flexible, Browser Based Annotations for TEI. . Od wersji 8.00 (jeszcze nie opublikowanej) TEI Publisher będzie miał także możliwość anotacji automatycznej przy użyciu metod NLP (NER), a wykorzystuje w tym celu bibliotekę spaCy Artykuł na blogu e-editiones. .

Obecnie jedyną metodą przetestowania rozwojowej wersji TEI Publishera jest jego instalacja we własnym zakresie. Autorzy aplikacji udostępniają jednak repozytorium Zobacz repozytorium eeditiones. z przygotowanymi plikami konfiguracyjnymi do utworzenia i uruchomienia własnej instancji opartej na kontenerach dockera. Ten sposób instalacji uwalnia nas od potrzeby ręcznego konfigurowania wszystkich komponentów: javy, pythona, eXist-db, spaCy itd. Po pobraniu (sklonowaniu) repozytorium należy uruchomić budowę obrazów dockera ze źródeł poleceniem:
docker-compose build --build-arg ADMIN_PASS=my_pass
gdzie 'my_pass' oznacza hasło admina serwera eXist-db.

Budowanie może potrwać kilka minut, po jego zakończeniu można uruchomić rozwojową wersję aplikacji poleceniem: docker-compose up -d, co znów zajmie kilka-kilkanaście sekund, po czym aplikacja będzie widoczna w przeglądarce pod adresem http://localhost. Co prawda w stopce strony będzie ciągle widoczna wersja 7.10, ale w rzeczywistości działa wersja rozwojowa z gałęzi master repozytorium. Aby się o tym przekonać wystarczy wejść w kolekcję dokumentów 'Annotation Samples' dostarczoną z aplikacją, następnie w dowolny dokument, np. Letter #20 from Robert Graves to William Graves (at Oundle School) November 8, 1957, w pasku narzędzi po lewej stronie ekranu wśród ikon widoczna jest czarna ikonka z symbolem osób i plusem. To znak, że w systemie dostępna jest usługa NER. Po jej uruchomieniu aplikacja wyświetla okno dialogowe pozwalające wybrać model do przetwarzania tekstu. Standardowo dostępne są dwa: de_core_news_sm dla języka niemieckiego i en_code_web_sm dla języka angielskiego.

modele spaCy w TEI Publisherze

Przykładowy dokument jest po angielsku należy więc wybrać ten drugi model i przycisk Run. Po przetworzeniu można będzie zobaczyć nowe anotacje typu person lub organization.

wynik NER w TEI Publisherze

Przełączenie się na widok źródeł TEI dokumentu pokaże, że postacie np. Juanito zostały poprawnie otagowane znacznikiem .

kod XML-TEI

Tekst, który jest analizowany w tym cyklu zapisek jest jednak tekstem w języku polskim. Czy da się w podobny sposób jak przykładowy dokument przetworzyć publikację Stanisława Bodniaka? W nieoficjalnej wersji rozwojowej wymaga to pewnych zabiegów. W przypadku uruchomionej już instancji aplikacji niezbędna jest ingerencja w zawartość jednego z kontenerów obsługujących system TEI Publisher, ten w którym działa serwis NER oparty na bibliotece spaCy. Domyślnie po uruchomieniu nazywa się on teipublisher-docker-compose-ner-1 co można zobaczyć na liście kontenerów uruchamiając w konsoli komendę:
docker container ps
Do działającego kontenera można dostać się poleceniem exec dockera np.:
docker exec -it teipublisher-docker-compose-ner-1 bash,
po którego wywołaniu znajdziemy się w linuksowym środowisku kontenera (w tym przypadku opartym na debianie). To co jest niezbędne do obsługi języka polskiego to polski model spaCy, który można zainstalować poleceniem: python -m spacy download pl_core_news_md. Sama instalacja modelu nie spowoduje jeszcze jego dostępności na liście modeli w TEI Publisherze, konieczny jest jeszcze restart kontenera a najpierw wyjście z niego poleceniem exit. Do zatrzymania zestawu kontenerów obsługujących TEI Publishera wystarczy w linii komend wpisać: docker-compose stop, a po chwili ponownie uruchomić system poleceniem docker-compose start. Można by uniknąć konieczności modyfikowania zawartości kontenera, gdyby polski model instalowany był domyślnie, odpowiada za to plik konfiguracyjny Dockerfile w repozytorium serwisu NER TEI Publishera. Zobacz repozytorium. dodanie do polecenia RUN w tym pliku dodatkowego kodu:
&& python3 -m spacy download pl_core_news_md
rozwiązałoby sprawę.

Jednak dostępność polskiego modelu na liście modeli w interfejsie użytkownika TEI Publishera nie oznacza niestety, że potrafi on z tego modelu skorzystać. Problemem są odmienne etykiety, którymi model oznacza znalezione encje nazwane (named entity). Autorzy TEI Publishera przewidzieli mapowanie z etykiet używanych w modelach angielskich i niemieckich np. "person": ("PER", "PERSON") jednak w standardowych modelach spaCy dla języka polskiego etykiety są nieco inne: np. 'persName' dla osób czy 'orgName' dla organizacji. zob. na stronie spaCy , dla modelu pl_core_news_md należy rozwinąć sekcję Label Scheme i przewinąć do punktu NER, gdzie znajduje się lista etykiet NER używanych w tym modelu.

Aby to zmienić konieczna jest modyfikacja kodu źródłowego serwisu NER TEI Publishera, konkretnie pliku main.py w podkatalogu workspace/tei-publisher-ner/scripts, znów w działającym kontenerze (być może w oficjalnej wersji aplikacji będzie to już uwzględnione?) Tymczasowy fork z modyfikacją jest w repozytorium. . W pliku należy zmienić zawartość słownika MAPPINGS z:

            MAPPINGS = {
                "person": ("PER", "PERSON"),
                "place": ("LOC", "GPE"),
                "organization": ("ORG"),
                "author": ("AUT")
            }
        

na:

            MAPPINGS = {
                "person": ("PER", "PERSON", "persName"),
                "place": ("LOC", "GPE", "placeName", "geogName"),
                "organization": ("ORG", "orgName"),
                "author": ("AUT")
            }
        

Tym razem restart kontenerów nie będzie potrzebny, wywołanie narzędzia NER w TEI Publisherze wyświetla już znalezione w polskim tekście encje, na poniższym zrzucie ekranu widać też przygotowany przez TEI Publishera kod pliku XML w standardzie TEI Uwaga: zmiany wprowadzane bezpośrednio w kontenerach nie znikną po zatrzymaniu i ponownym uruchomieniu kontenera, oczywiście znikną jednak przy ponownym utworzeniu kontenerów z obrazów dockera. .

wynik NER w TEI Publisherze, tekst w języku polskim

Można spodziewać się, że pewne niedogodności wersji rozwojowej zostaną w przyszłości dopracowane, a sama instalacja może być przecież przeprowadzona na serwerze przez specjalistę. Lektura artykułu zlinkowanego w przypisie na początku zapiski pokazuje także, że w rozwojowej wersji aplikacji przewidziano bardziej zaawansowane możliwości np. trenowanie własnego modelu w oparciu o korpus tekstów anotowanych właśnie w TEI Publisherze Inną ciekawą aplikacją obsługującą 'wzbogacanie' tagowania plików TEI o rozpoznane jednostki nazewnicze dzięki NER i uczeniu własnych modeli jest NEISS - TEI Entity Enricher. . Aplikacja ta już dziś jest cennym narzędziem w arsenale cyfrowego humanisty, po ukazaniu się oficjalnej wersji 8.00 stanie się też systemem ułatwiającym praktyczne korzystanie z niektórych metod (NER) przetwarzania języka naturalnego bez potrzeby programowania i z pożytkiem w postaci łatwiejszego tworzenia edycji cyfrowych.


spaCy i koreferencje, czyli Jerzy bawił we Włoszech
a jego włości popadały w ruinę (30.10.2022)

Jednym z problemów podczas przetwarzania tekstów pisanych z myślą o ludzkim czytelniku a nie algorytmie wykonywanym przez komputer są informacje odnoszące się  do tej samej postaci, ale wyrażone nie wprost, lecz choćby poprzez zaimki osobowe lub dzierżawcze, czyli koreferencje. Czytając na przykład tekst:

"Jan Tarnowski, syn Jana Amora Iuniora i Barbary z Rożnowa, wnuczki Zawiszy Czarnego, pochodził z wpływowej szlacheckiej rodziny Leliwitów Tarnowskich...
Wychowywał się na dworach kardynała Fryderyka Jagiellończyka...
Odtąd też trwały: jego spór z siostrzeńcem Piotrem Kmitą i żale względem dworu."
Fragmenty biogramu Jana Tarnowskiego z polskiej wersji Wikipedii.

Można obawiać się, czy fakty podane w taki sposób będą czytelne dla skryptu przetwarzającego tekst. Wychowywał się - ale kto? Jego czyli kogo?

Wiedza na temat tego, kto kryje się pod wyrażeniem "jego", czy kogo dotyczy czasownik "wychowywał się" jest szczególnie przydatna przy próbach rozpoznawania relacji między encjami (np. osobami) w tekście, wiedząc że chodzi o Jana Tarnowskiego, możemy uzyskać informację iż ważną postacią dla niego byli Piotr Kmita i Ferdynad Jagiellończyk. Czy spaCy jest w stanie w tym pomóc i przede wszystkim czy to będzie działać dla języka polskiego? Okazuje się, że tak. W repozytorium należącym do Explosion AI, firmy rozwijającej spaCy znajduje się dodatkowa biblioteka Coreferee, przeznaczona do rozwiązywania koreferencji w języku angielskim, francuskim, niemieckim i polskim. Instalacja opisana jest na stronie repozytorium Zob. repozytorium github. , dokumentacja zawiera też opis działania biblioteki, wyjaśnienie decyzji podjętych podczas jej tworzenia Coreferee powstało w ramach projektu Holmes (zobacz wpis na blogu Explosion AI, Holmes niestety obsługuje tylko j. angielski i niemiecki), co jest przyczyną pewnych specyficznych cech narzędzia, np. pracuje raczej na pojednycznych tokenach. , także tabelę dokładności modeli dla poszczególnych języków (dla polskiego to 72-76%).

Jak użyć Coreferee? Poniżej prosty program przetwarzający wspomniany fragment biogramu hetmana Jana Amora Tarnowskiego.

biblioteka coreferee

Jak wydać na powyższym zrzucie ekranu sformułowania Wychowywał, Otrzymał, zajmował, jego zostały prawidłowo powiązane z encją 'Jan' Notebook koreferencje.ipynb do pobrania z repozytorium. .

Coreferee wychwytuje klastry (przykład powyżej zawiera akurat tylko jeden) tokenów odwołujących się do tej samej postaci, słowo jego mogłoby zostać zastąpione przez Jana Tarnowskiego, otrzymał przez Jan Tarnowski otrzymał (zob. zrzut ekranu poniżej), ale takie liczne powtórzenia byłyby męczące dla ludzkiego czytelnika, który świetnie sobie radzi z odczytywaniem informacji z kontekstu. Sprecyzowanie o kogo chodzi jest jednak niezbędne przy maszynowym przetwarzaniu treści, dlatego powstają narzędzia w rodzaju Coreferee.

zdanie z przetworzonymi koreferencjami

Parę tygodni temu firma Explosion AI opublikowała nowy komponent biblioteki spaCy Coreference Resolution, szczegółowy opis tego mechanizmu jest dostępy na blogu Explosion, niestety obsługuje na razie tylko język angielski Dostępny jest też krótki film Coreference Resolution in spaCy. .