Semantic Enrichment Component (zkráceně SEC) poskytuje služby pro sémantické obohacení textu. Využívá se např. v systému 4A. Poskytuje např. službu anotace textu annotate
, hledání entit get_entities
a výpisu všech typů a atributů v KB get_entity_types_and_attributes
.
Veřejně je služba dostupná na http://sec.fit.vutbr.cz/ na portu 8082 (dokumentace k protokolu). Pro naše účely máme SEC API na všech serverech ve /var/secapi
a běží na portu 8082.
Aktuální verze nástroje je dostupná na gitu ve větvi D114-SEC_API (username je potřebné změnit za váš login).
git clone http://sec.fit.vutbr.cz/sec/secapi.git secapi && cd secapi git checkout -b D114-SEC_API origin/D114-SEC_API
Vytvoří se adresář secapi, do kterého se přesuneme. V adresáři ./NER
stáhneme KB a poté v adresáři ./SEC_API
sestavíme potřebné programy pomocí příkazu make.
cd ./NER && ./deleteKB.sh && ./downloadKB.sh) cd ./SEC_API && make)
Je potřeba si dát pozor na to, aby se při použití skriptu downloadKB.sh
nenacházely v adresářích secapi/NER
a secapi/NER/figa KB
a automaty (*.fsa). Je proto vhodné je ještě předtím smazat pomocí skriptu deleteKB.sh
. Pozor, skripty downloadKB.sh
a deleteKB.sh
se musí spouštět pouze z adresáře ve kterém jsou (tedy ./NER
)!
SEC naleznete v adresáři ./SEC_API
(odtud berme jako pracovní cestu — budou od něj odvozeny relativní cesty). Jeho skripty jsou napsány tak, aby mohly být volány z libovolného adresáře. Aktuálně je SEC rozdělen do skriptů sec_daemon.py, sec.py
a sec_api.py
.
sec_daemon.py
Skript sec_daemon.py je jádro celého SEC. Vznikl pro redukci paměťové náročnosti při paralelním běhu sec.py
. Jeho spuštěním je vytvořen Unix domain socket (UDS) na kterém čeká na připojení několika instancí skriptů sec.py
nebo sec_api.py
. Instance těchto dvou skriptů se s sec_daemon.py
dorozumívají pomocí vnitřního komunikačního protokolu popsaného dále.
./sec_daemon.py [-h] [-p PATH] [--own_kb_daemon]
Nepovinné argumenty: -h, --help Ukáže nápovědu a skončí. -p PATH, --uds_path PATH Nastaví cestu k Unix domain socketu, kde daemon čeká na klienty. Výchozí hodnota je ./daemon_uds relativně k adresáři skriptu. --own_kb_daemon Spustí vlastní KB daemon i když jiný již běží.
sec.py
Skript sec.py
je klient démonu sec_daemon.py
. Přes něj jsou služby SEC poskytované démonem reprezentovány uživateli. Na standardním vstupu je očekáván požadavek v JSON. Na standardní výstup je předána odpověď. Popis služeb a požadavků i s příklady naleznete prozatím v ./doc/sec_api.pdf
po sestavení příkazem make
.
./sec.py [-h] [-t [DIRECTORY]] [-p PATH] [-c CONFIG.json] [--plaintext] [-f FILENAME]
Nepovinné argumenty: -h, --help Ukáže nápovědu a skončí. -t [DIRECTORY], --testing_mode [DIRECTORY] Přepne do testovacího módu, jenž umožní testovat práci se strukturovanými anotacemi, které NER neumí. To znamená, že služba "annotate" hledá v URI dotazu "DOCUMENT_URI" hodnotu klíče "tid" a podle ní hledá v adresáři DIRECTORY soubor s odpovědí na "annotation_format". Pokud je takový soubor nalezen, pak místo výsledků z NERu se vrátí jeho obsah. URI dotaz "DOCUMENT_URI" může mít i klíč "aid". Oproti klíči "tid", obsah souboru, nalezeného podle této hodnoty, výsledek NERu pouze obohatí (připojí se k němu). Výchozí hodnota je ./testing_mode relativně k adresáři skriptu. -p PATH, --uds_path PATH Nastaví cestu k Unix domain socketu, kde daemon čeká na klienty. Výchozí hodnota je ./daemon_uds relativně k adresáři skriptu. -c CONFIG.json, --config_file CONFIG.json Nastaví službu a její parametry z JSON souboru, místo ze standardního vstupu. V tom případě je očekáván na standardním vstupu pouze text ke zpracování nebo nic. --plaintext Výstup služeb "annotate", "annotate_vertical" a "get_raw_annotations" je v čistém textu. Pokud nastane výjimka, zůstane v JSON. -f FILENAME, --filename FILENAME Nastaví filename pro službu "annotate_vertical".
sec_api.py
Skript sec_api.py
je velmi podobný skriptu sec.py
a proto jeho část využívá. Na rozdíl od něj na standardním vstupu může být požadavků zadáno více na jednu instanci. Po každém požadavku vytiskne na standardní výstup odpověď. Při spuštění vytvoří i server využívající protokol HTTP a čekající na portu 8082. Přes něj může jakýkoli HTTP klient pomocí HTTP požadavku POST poslat SEC požadavek o konkrétní službu a získat odpověď.
./sec_api.py [-h] [-t [DIRECTORY]] [-p PATH] [-n PORT]
Nepovinné argumenty: -h, --help Ukáže nápovědu a skončí. -t [DIRECTORY], --testing_mode [DIRECTORY] Přepne do testovacího módu, jenž umožní testovat práci se strukturovanými anotacemi, které NER neumí. To znamená, že služba "annotate" hledá v URI dotazu "DOCUMENT_URI" hodnotu klíče "tid" a podle ní hledá v adresáři DIRECTORY soubor s odpovědí na "annotation_format". Pokud je takový soubor nalezen, pak místo výsledků z NERu se vrátí jeho obsah. URI dotaz "DOCUMENT_URI" může mít i klíč "aid". Oproti klíči "tid", obsah souboru, nalezeného podle této hodnoty, výsledek NERu pouze obohatí (připojí se k němu). Výchozí hodnota je ./testing_mode relativně k adresáři skriptu. -p PATH, --uds_path PATH Nastaví cestu k Unix domain socketu, kde daemon čeká na klienty. Výchozí hodnota je ./daemon_uds relativně k adresáři skriptu. -n PORT, --net_port PORT Nastaví port, kde SEC čeká na klienty. Výchozí hodnota je 8082.
Vnitřní komunikační protokol je založen na modelu klient-server využívající Unix domain sockety (UDS) ve stream módu.
Pokud server zjistí chybné nastavení nebo dojde k chybě při zpracování, pošle klientovi informace o chybě a končí spojení s ním spojení.
Na příkazy se můžete podívat v souboru daemon_lib.py
. Zde pouze uvedu, že mají dynamicky generované dvoumístné číslo Opcode
.
Pro příkazy se používají dvě struktury paketů. Pro chyby je to:
2 bajty Řetězec 2 bajty ------------------------------------ | Opcode | Chybová zpráva | CRLF | ------------------------------------
Pro zbytek (vyjma file descriptoru) je to struktura, která se opakuje dokud není počet bajtů dat roven nule:
2 bajty Číslo (desítkové) 2 bajty N bajtů 2 bajty ------------------------------------------------------------- | Opcode | Počet bajtů dat N | CRLF | Raw data | CRLF | -------------------------------------------------------------
Pro zasílání file descriptorů se používá knihovna python-fdsend
.
Ve vývoji - dokumentace bude doplněna (nyní pouze stručně to nejnutnější):
sec_daemon.py
doplní pod:ner_manager.appendNER("default", module_annotate.NER())
podobný řádek s jiným jménem NERu a instancí jiného obalu
Specifikace je vytvořena podle našeho NERu a dalších požadavků. U výstupu z NERů se předpokládá syntaxe (BNF):
<výstup z NERů> ::= <origin_base> | <origin_base> "\t" <id> | <origin_base> "\t" <id> "\t" <direct_attributes> <origin_base> ::= <start_offset> "\t" <end_offset> "\t" <data_type> "\t" <string_between_offsets> "\t" <data> <data_type> ::= "kb" | "activity" | "date" | "interval" | "coref" | "uri" <data> ::= <data-kb> | <data-activity> | <data-date> | <data-interval> | <data-coref> | <data-uri> <data-kb> ::= <KB_row> | <KB_row> ";" <data-kb> <data-date> ::= <year> "-" <month> "-" <day> <data-interval> ::= <data-date> " -- " <data-date> <data-coref> ::= <data-kb> <direct_attributes> ::= <attribute> | <attribute> "|" <direct_attributes> <attribute> ::= <attribute_name> "[" <attribute_type> "]=" <attribute_value> <attribute_type> ::= "string" | "decimal" | "date" | "image" | "integer" | "uri" | <other_attribute_type> <year> ::= <digit> <digit> <digit> <digit> <month> ::= <digit> <digit> <day> ::= <digit> <digit> <digit> ::= "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9"
where:
Za účelem, aby při použití v SEC jiným programem nebylo potřeba vytvářet subproces se sec.py
, byla vytvořena třída Sec v sec.py
. Její metody jsou popsány přímo ve zdrojovém kódu. Stejně jako u sec.py
je nutné mít spuštěný sec_daemon.py
a inicializovat tuto třídu s cestou k Unix domain socketu, kde daemon čeká. Konfigurace se zadávají podobně jako pro sec.py
s tím rozdílem, že místo JSONu se používá alternativa Pythonu (viz tabulka).
Pro spouštění SEC na gridu (SGE) byl vytvořen skript ./sge/sec.sh
.
Byly na něj kladeny tyto požadavky:
sec.py
, bude sec_daemon.py
a sdílená KB).Výsledný ./sge/sec.sh
přijímá stejné argumenty jako sec.py
. I když byl dělán pro spouštění na gridu, je možné jej využít i na obyčejných strojích (což je možná zřejmé, ale radši to tu píšu).
V rámci tohoto cíle byl vytvořen přepínač --own_kb_daemon
u sec_daemon.py
a --plaintext u sec.py. K tomu bylo v KB démonu umožněno měnit název sdílené paměti argumentem programu.
Pro použití SEC se stdin/stdout NERu stačí využít služby "get_raw_annotations". Je k tomu třeba vytvořit si konfigurační soubor (např. "get_raw_annotations.cfg"), např. s:
{ "get_raw_annotations": {} }
Poté může být NER volán přes SEC takto:
./sge/sec.sh -c get_raw_annotations.cfg --plaintext
Pro běh na superpočítači Salomon (IT4I) byly vytvořeny skripty nacházející se ve složce ./salomon.
SEC je závislý na několika knihovnách, které na Salomonu nejsou nainstalované. Proto je nutné je nakopírovat z knot09:/mnt/minerva1/nlp/projects/corpproc/dependencies_for_salomon/opt
. Lze to provést např. tímto postupem:
$ mkdir -p ~/mnt/ssh-knot-knot09 $ sshfs xlogin01@knot09.fit.vutbr.cz:/ ~/mnt/ssh-knot-knot09/ $ cp -r ~/mnt/ssh-knot-knot09/mnt/minerva1/nlp/projects/corpproc/dependencies_for_salomon/opt ~/ $ fusermount -u ~/mnt/ssh-knot-knot09
Závislosti již jsou sestavené. Pokud by byla potřebná nová kompilace, stačí spustit ./salomon/prepare.sh.
Ke spuštění slouží skript ./salomon/start.sh
, jenž je v několika variantách. Každá z těchto variant očekává:
$ ls ~/parsed | sed 's/\.vert.*//g' > ~/namelist
po spuštění vytvoří:
annotate
Tato služba vrací anotace pro zadaný dokument. Využívá k tomu enrichment engine zvolený uživatelem. Anotace obsahují informaci o jejich umístění v dokumentu (počáteční a koncový ofset), délce a samotný anotovaný text. Dále obsahují informace získané z KB mezi nimiž je např. typ, název a URL na wikipedii.
Enrichment engine volí uživatel v parametru enrichment_engine
. Dále může zadat i jeho maximální dobu zpracování (v sekundách) parametrem enrichment_engine_timeout
. K výpisu všech enrichment engines lze využít službu "get_enrichment_engines".
Text v dokumentu bývá většinou víceznačný a proto enrichment engine může nalézt více možných entit k danému textu. Pokud je nastaven parametr disambiguate
, pak enrichment engine vybere ten nejpravděpodobnější význam anotovaného textu.
Formát výstupu této služby lze zvolit parametrem annotation_format
. Pro jeden vstup je možné zvolit více formátů výstupu. Pozn.: Toto by možná chtělo upravit tak, aby při "plaintext": false
byl výstupem vždy korektní JSON. Parametr "annotation_format"
může nabývat těchto hodnot:
"disambiguate": 0
způsobí chybu.Parametrem types_and_attributes
lze určit, jaké informace z KB se zahrnou do výstupu. Tedy je možné povolit jednotlivé typy a k nim buď všechny jejich atributy (syntaxe { str(type): "all" }
) nebo jen některé (syntaxe { str(type): [ str(attribute), ... ] }
). Jeho výchozí hodnotou je "all", což znamená, že je povolen výpis všech typů anotací i všech jejich atributů. Všechny dostupné typy a jejich atributy, lze vypsat pomocí služby "get_entity_types_and_attributes".
Parametr document_uri
slouží k zadání URL adresy ze které byl dokument přebrán. Je-li nastaven výstupní formát NIF, pak je tento parametr nutný.
Je-li parametr plaintext
nastaven na true, ruší zapouzdření výstupu do JSONu. V tom případě jsou různé výstupní formáty odděleny znakem '\0'
.
Ukázky a další informace je možné nalézt zde.
{ "annotate": { "input_text": str, "annotation_format": [ str, ... ], "disambiguate": int, "document_uri": str, "types_and_attributes": "all" | { str(type): "all" } | { str(type): [ str(attribute), ... ] }, "enrichment_engine": str, "enrichment_engine_timeout": int, "plaintext": bool } }
["disambiguate", "types_and_attributes", "document_uri", "enrichment_engine", "enrichment_engine_timeout", "plaintext"]
.document_uri
je povinný, pokud mezi hodnotami atributu annotation_format
je "NIF".{ "annotation": str }
Formát výstupu této služby lze zvolit parametrem annotation_format
. Zde tyto formáty popíši podrobněji.
XML dokument obsahující pouze anotovaný text. Je určen převážně pro další zpracování.
<?xml version="1.0" encoding="UTF-8"?> <!-- Generated using: trang -I xml -O rng *.sxml SXML.rng --> <grammar ns="" xmlns="http://relaxng.org/ns/structure/1.0" datatypeLibrary="http://www.w3.org/2001/XMLSchema-datatypes"> <start> <element name="suggestion"> <zeroOrMore> <element name="text"> <attribute name="e_offset"> <data type="integer"/> </attribute> <attribute name="s_offset"> <data type="integer"/> </attribute> <attribute name="string"/> <zeroOrMore> <element name="annotation"> <optional> <attribute name="id"> <data type="anyURI"/> </attribute> </optional> <attribute name="type"> <data type="NCName"/> </attribute> <zeroOrMore> <element name="attribute"> <optional> <attribute name="annotType"> <data type="NCName"/> </attribute> </optional> <attribute name="name"> <data type="NCName"/> </attribute> <attribute name="type"> <data type="NCName"/> </attribute> <text/> </element> </zeroOrMore> </element> </zeroOrMore> </element> </zeroOrMore> </element> </start> </grammar>
annotate_vertical
Speciální klon služby "annotate" pro anotaci vertikálu. (Z tohoto důvodu popíši pouze rozdíl.) Její částí je služba "deverticalize", jenž se stará o postupné získání jednotlivých dokumentů ze vstupu ve vertikálním formátu. Formát výstupu musí být určen v požadavku.
{ "annotate_vertical": { "input_text": str, "annotation_format": str, "vert_in_cols": [ str, ... ], "vert_out_cols": [ str, ... ], "types_and_attributes": "all" | { str(type): "all" } | { str(type): [ str(attribute), ... ] }, "enrichment_engine": str, "enrichment_engine_timeout": int, "filename": str, "num_workers": int, "plaintext": bool, "max_values_per_col": int | null, "wiki_mode": bool, "enable_figa": bool } }
["vert_in_cols", "vert_out_cols", "types_and_attributes", "enrichment_engine", "enrichment_engine_timeout", "filename", "num_workers", "plaintext"]
.annotation_format
může nabývat hodnot: ["mg4j", "manatee", "manatee2", "elasticsearch"]
.
vert_in_cols
, popř. vert_out_cols
, určuje význam/název jednotlivých sloupců ve vstupním, popř. výstupním, vertikálu. Pokud není zadán, význam je standardní ⇒ prvních 13 sloupců MG4J. Starý název atributu vert_in_cols
, tedy vert_cols
, je sice funkční, ale je již zavrhován. Názvy sloupců jsou vypsány v hlavičce formátu MG4J.filename
nastavuje jméno zdrojového souboru. Využívá se ve výstupním formátu MG4J.num_workers
nastaví počet paralelně zpracovávaných dokumentů z vertikálu. Výchozí hodnotou je počet procesorů.max_values_per_col
nastaví maximální počet hodnot na sloupec. Využívá se ve výstupním formátu MG4J. Výchozí hodnota je 4.wiki_mode
povoluje využití sloupce "docuri" pro zpracování vertikálů z wikipedie. Využívá se ve výstupním formátu MG4J. Funguje takto:
enable_figa == true
, pak hledá dle URL v hypertextovém odkazu (sloupec "link") entitu v KB:
wiki_mode: false
a enable_figa: true
.{ "annotation": str | [ { "title": str, "uri": str, "article": str }, ... ] }
V případě, že annotation_format
bude "elasticsearch", pak hodnotou klíče annotation
bude list objektů, v opačném případě řetězec.
deverticalize
Devertikalizuje text ve vertikálním formátu (see https://www.sketchengine.co.uk/documentation/preparing-corpus-text/ or http://nlp.fi.muni.cz/cs/PopisVertikalu).
{ "deverticalize": { "input_text": str, "vert_in_cols": [ str, ... ] } }
["vert_in_cols"]
.vert_in_cols
určuje význam jednotlivých sloupců ve vstupním vertikálu. Pokud není zadán, význam je standardní ⇒ prvních 13 sloupců MG4J. Starý název tohoto atributu vert_cols
je sice funkční, ale je již zavrhován.{ "deverticalized": [ { "id": str, "document": str }, ... ] }
<s></s>
a s tagy <g/>
tak se tím tagem <g/>
neslepí (např.: "<s>\nHello\n</g>\n!</s><s></g>\n!</s><s></g>\n!</s>"
⇒ "Hello!\n!\n!\n"
) a to kvůli <s>
.get_enrichment_engines
Vypíše všechny dostupné enrichment engines. Tedy hodnoty jakých může nabývat atribut enrichment_engine
, používaný u některých služeb.
{ "get_enrichment_engines": {} }
{ "enrichment_engines": [ str, ... ] }
get_entities
Dle zadaného jména do artibutu input_string
vypíše všechny entity z KB, které mají stejné nebo podobné jméno. Výstup je seřazen podle hodnoty atributu entity confidence
. Lze filtrovat stejně jako u služby "annotate"
pomocí atributu types_and_attributes
.
Ukázky a další informace je možné nalézt zde.
{ "get_entities": { "input_string": str, "types_and_attributes": "all" | { str(type): "all" } | { str(type): [ str(attribute), ... ] }, "max_results": int } }
["max_results"]
."input_string"
nastavuje jméno hledané entity nebo počáteční část toho jména zakončenou hvězdičkou '*'."types_and_attributes"
filtruje stejně jako u služby "annotate"
."max_results"
určuje maximální počet entit ve výstupu. Výchozí hodnota je 10.{ "data": [ { str(type): { str(attribute): str, ... } }, ... ] }
get_entity_by_uri
Vyhledání entity podle URI.
{ "get_entity_by_uri": { "input_string": str, "types_and_attributes": "all" | { str(type): "all" } | { str(type): [ str(attribute), ... ] } } }
{ "data": [ { str(type): { str(attribute): str, ... } }, ... ] }
get_entity_types_and_attributes
Vypíše všechny dostupné typy a jejich atributy. Tato informace lze využít u atributu types_and_attributes
, který u některých služeb slouží jako filtr.
{ "get_entity_types_and_attributes": {} }
{ "data": [ { "type": str(type), "attributes": [ str(attribute), ... ] }, ... ] }
get_kb_version
Vrátí číslo verze načténé KB.
{ "get_kb_version": {} }
{ "version": int }
get_raw_annotations
Vrátí řetězec získaný pomocí NERu.
{ "get_raw_annotations": { "input_text": str, "disambiguate": int, "enrichment_engine": str, "enrichment_engine_timeout": int, "plaintext": bool } }
["disambiguate", "enrichment_engine", "enrichment_engine_timeout", "plaintext"]
.{ "annotation": str }
"identifier"
"disambiguation"
<disambiguation> ::= <text v URI mezi závorkami> "," <popis> "(" <interval žití> ")" | <text v URI mezi závorkami> "(" <interval žití> ")" | <text v URI mezi závorkami> | <description> "(" <interval žití> ")" | <description> | <interval žití> | "" <interval žití> ::= <YYYY-MM-DD datum narození> -- <YYYY-MM-DD datum úmrtí> | "born " <YYYY-MM-DD datum narození>
["wikipedia_url", "date_of_birth", "date_of_death", "description"]
.
wikipedia_url
obsahuje kulaté závorky (tedy ["(", ")", "%28", "%29"]
), pak se přebírá text mezi nimi, v němž jsou podtržítka nahrazeny mezerami. Výše v BNF jako <text v URI mezi závorkami>
.description
obsahuje podřetězec začínající "is|was a|an|the"
, pak se přebírá jeho D_DESC_MAX_WORDS
slov a doplní "...", pokud se nedošlo na konec description
. Jinak pokud počet písmen je menší než D_DESC_MAX_CHARS
, pak se přebírá celý atribut description
. Výše v BNF jako <description>
.<interval žití>
.<description>
obsahuje podřetězec <text v URI mezi závorkami>
jako celé slovo (tedy (?:^|\W)Whole word(?=(?:$|\W))
), pak <disambiguation>
má syntax bez <text v URI mezi závorkami>
.