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.pySkript 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.pySkript 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.pySkript 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ří:
annotateTato 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_verticalSpeciá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.
        
deverticalizeDevertikalizuje 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_enginesVypíš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_entitiesDle 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_uriVyhledá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_attributesVypíš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_versionVrátí číslo verze načténé KB.
 {
     "get_kb_version": {}
 }
        
         {
     "version": int
 }
        
        get_raw_annotationsVrá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>.