Elasticsearch: Vyhledáváme hezky česky (a taky slovensky)

V prvním díle jsme si představili knihovnu Lucene a vysvětlili jsme si, jakou nabízí „out-of-the-box“ podporu fulltextového vyhledávání pro češtinu. Ve druhém díle budeme pokračovat a ukážeme si hunspell token filter. Praktickou část opět demonstrujeme s použitím Elasticsearch.
Seriál: Elasticsearch: Vyhledáváme hezky česky (2 díly)
- Elasticsearch: Vyhledáváme hezky česky 1. 7. 2013
- Elasticsearch: Vyhledáváme hezky česky (a taky slovensky) 4. 9. 2013
Nálepky:
Hned na úvod bych rád podotknul, že hunspell token filter nabízí podporu i pro slovenštinu. V průběhu článku se k tomu několikrát vrátíme.
(Použité verze: Elasticsearch 0.90.3, Lucene 4.4)
Lucene
Jakým způsobem analyzuje Lucene textová data jsme si podrobněji vysvětlili v předchozím díle. Už víme, že těžištěm podpory češtiny v Lucene jsou především token filtry. Dnes se budeme věnovat hunspell token filtru.
Hunspell token filter
Hunspell token filter je založený na jazykových slovnících používaných spell-checkerem Hunspell. Systémů pro spell-checking exituje celá řada. Kromě Hunspellu to je např. MySpell nebo Ispell. Nejsem odborníkem na tuto problematiku, ale zdá se mi, že Hunspell patří mezi systémy nejmladší a (snad i) nejpropracovanější. Jak samotný název napovídá, původně vzniknul pro podporu maďarštiny, protože ostatní systémy nebyly pro tento jazyk dostatečně flexibilní. Dnes podporuje mnoho dalších jazyků. Vývojově na starší systémy navazuje a je s mini často kompatibilní co do formátu slovníkových souborů, to znamená, že Hunspell může používat i slovníky pro Ispell nebo MySpell.
Ač je Hunspell určen především pro identifikaci a opravu překlepů, dají se jeho slovníky dobře použít pro analýzu slov (tokenů) v Lucene.
Hunspell potřebuje pro každý podporovaný jazyk slovníkové soubory dvou typů:
- Soubor s příponou .dic. Jedná se o dictionary. Je to seznam slov daného jazyka, ideálně v kořenovém tvaru. Obecně může být slovník rozdělen do více takových souborů.
Jak se lze dočíst v manu, ve slovníku (.dic) může být pro každé slovo dále definována řada morfologických informací. Ty ovšem nejsou v Lucene implementaci používány, proto se jim nebudeme věnovat. Ostatně český ani slovenský slovník, který budeme používat, takové morfologicé informace neobsahuje. - Soubor s příponou .aff. Jedná se o affixová pravidla, která lze aplikovat na vybraná slova ze slovníku. Pravidla určují, jak lze k těmto slovům přidávat předpony a přípony a vygenerovat tak správné slovní tvary v daném jazyce.
Například pro slovo hledat lze pomocí affixových pravidel vygenerovat následující slovní tvary – viz.: http://www.pravidla.cz/hledej.php?qr=hledat.
Služba pravidla.cz uvádí, že používá Ispell slovník. Ve skutečnosti i my budeme pro češtinu používat Ispell slovník.
Pro potřeby Lucene analýzy je však zajímavý opačný případ, kdy pro libovolný z těchto slovních tvarů dokážeme zpětně určit slovo základní (schválně neříkám kořen slova, protože velmi záleží na obsahu slovníku, ten totiž nemusí obsahovat pouze slova v kořenovém tvaru).
Algoritmus je takový, že Lucene zkusí pro každý vstupní token vytipovat affixová pravidla, která by mohla být použita pro tvorbu tohoto tokenu a zpětně vytvoří kandidáta na výchozí slovo. Pokud je kandidát nalezen ve slovníku (.dic) a zároveň je pro kandidáta povoleno použití daného pravidla (.aff), nalezli jsme základní slovo. Tento algoritmus se pro nalezeného kandidáta dále rekurzivně opakuje.
Až do verze Lucene 4.3 byla úroveň rekurzivního opakování stanovena na hodnotu 2. Od Lucene 4.4 (a Elasticsearch 0.90.3) je počet úrovní rekurze volitený. Pro každý jazyk (a každý slovník) je potřeba volit vhodnou hodnotu rekurzivního opakování. Češtině a slovenštině dle mého názoru nejvíce svědčí hodnota 0. To vychází z předpokladu, že na kořen slova stačí aplikovat affixová pravidla pouze jednou a můžeme získat úplnou množinu slovních tvarů. Pro jiné jazyky (či jinak konstruované slovníky) to ovšem platit nemusí.
Odkud Hunspell slovníky vzít?
- Slovenský slovník: Projekt sk-spell se aktivně stará o řadu slovenských slovníků, konkrétně Hunspell najdeme zde: http://www.sk-spell.sk.cx/hunspell-sk.
- Český slovník: Ukážeme si, jak získat potřebné soubory z OpenOffice balíku českých slovníků. Jedná se o Ispell slovník a zdá se mi, že není tak aktivně udržovaný jako slovenský hunspell (to ale nemusí nutně znamenat, že je méně kvalitní).
- Nenašli jste slovník, který potřebujete? Tak si napište vlastní nebo upravte některý z existujících slovníků. Možná to zní jako šílenost, ale zkusím ukázat, že to může být užitečné.
České slovníky z OpenOffice
Stáhněte extension „České slovníky (cs-CZ)“ a rozbalte jej:
$ unzip dict-cs-2.0.oxt Archive: dict-cs-2.0.oxt inflating: cs_CZ.aff # <- affixová pravidla inflating: cs_CZ.dic # <- seznam slov inflating: description.xml inflating: Dictionaries.xcu inflating: hyph_cs_CZ.dic creating: META-INF/ inflating: META-INF/manifest.xml inflating: README_cs.txt inflating: README_en.txt inflating: th_cs_CZ_v3.dat inflating: th_cs_CZ_v3.idx
České slovníky pro OpenOffice obsahují tyto části:
- Kontrolu pravopisu
- Slovník synonym
- Slovník dělení slov
Nás v tuto chvíli zajímá pouze první ze slovníků. Jedná se o soubory cs_CZ.aff
acs_CZ.dic
, který obsahuje přes 160.000 českých slov a hesel. Oba slovníkové soubory jsou licencované pod GPL (viz README_cs.txt).
Jak jsem už zmínil, jedná se o český slovník pro Ispell. Jeho tvůrcem je Petr Kolář. Bohužel se mi nepodařilo zjistit, zda Petr Kolář stále slovník aktivně udržuje. Za jakékoliv informace o Petru Kolářovi, či vůbec o aktuálním stavu správy českého slovníku, budu vděčný.
Slovenské slovníky z sk-spell
Stáhněte a rozbalte nejnovější verzi Hunspell slovníku:
$ wget http://www.sk-spell.sk.cx/files/hunspell-sk-20110228.zip $ unzip hunspell-sk-20110228.zip Archive: hunspell-sk-20110228.zip creating: hunspell-sk-20110228/ creating: hunspell-sk-20110228/doc/ inflating: hunspell-sk-20110228/doc/AUTHORS inflating: hunspell-sk-20110228/doc/Copyright inflating: hunspell-sk-20110228/doc/Flagy.AUTHORS inflating: hunspell-sk-20110228/doc/Flagy.Changelog inflating: hunspell-sk-20110228/doc/Flagy.README inflating: hunspell-sk-20110228/doc/Flagy.TODO inflating: hunspell-sk-20110228/doc/GPL inflating: hunspell-sk-20110228/doc/LGPL inflating: hunspell-sk-20110228/doc/MPL inflating: hunspell-sk-20110228/sk_SK.aff # <- affixová pravidla inflating: hunspell-sk-20110228/sk_SK.dic # <- seznam slov
Licenční podmínky slovníkových souborů najdete v doc/Copyright
.
Slovníky bychom měli, dále pokračujeme praktickou částí a ukážeme si, jak je použít v Elasticsearch.
Elasticsearch
Pokud ještě nemáte nainstalovaný Elasticsearch, nainstalujte jej teď. V případě, že process Elasticsearch zrovna běží, je nutné jej nejdříve zastavit. V obou případech můžete postupovat podle návodu z předchozího dílu.
Instalace Hunspell slovníků
Předpokládejme, že jsme Elasticsearch nainstalovali do adresáře elasticsearch-0.90.3
. Ten obsahuje adresář config
. V něm je potřeba vytvořit adresář hunspell
, v tom dále vytvoříme adresáře cs_CZ
a sk_SK
. Do těch je potřeba nakopírovat patřičné slovníkové soubory. Výsledná souborová struktura by měla vypadat následovně:
elasticsearch-0.90.3 # <- zde byl nainstalován Elasticsearch ∟ config ∟ hunspell ∟ cs_CZ ⊢ cs_CZ.aff ⊢ cs_CZ.dic ∟ settings.yml ∟ sk_SK ⊢ sk_SK.aff ⊢ sk_SK.dic ∟ settings.yml
Soubory settings.yml
byly vytvořeny následovně:
echo 'strict_affix_parsing: false' > settings.yml
V některých případech se může stát, že affixové soubory používají „nestandardní“ zápis pro určitá pravidla. Nastavení strict_affix_parsing
řídí, jak má Lucene v takovém případě pravidlům rozumět. Slovníky, které v našem případě používáme, nestandardní zápis sice nepoužívají, ale není na škodu na tohle nastavení upozornit.
Další možnosti konfigurace jsou popsány v dokumentaci hunspell tokenfilter.
Look Ma, I’m a Hacker!!!
Abychom vůbec mohli Elasticsearch teď nastartovat, bude třeba udělat jeden „hot-fix“ v souboru českého slovníku cs_CZ.aff
. Jinak start Elasticsearch ukončí Java Exception.
Could not load hunspell dictionary [cs_CZ] java.util.regex.PatternSyntaxException: Unclosed character class near index 45 .*[aeiouyáéíóúýůěr][^aeiouyáéíóúýůěrl][^aeiouy ^
Na řádku 2119 je toiž suffixové pravidlo s neúplným regexp výrazem. Celý řádek by měl správně obsahovat následující pravidlo:
SFX A nout l [aeiouyáéíóúýůěr][^aeiouyáéíóúýůěrl][^aeiouyáéíóúýůěrl]nout
„Drsoni“ by mohli udělat něco jako:
# ---------------------------------- # 1) convert .aff file to UTF-8 # 2) do `sed` magic # 3) convert also .dic file to UTF-8 # 4) cleanup # ---------------------------------- $ iconv -f ISO-8859-2 -t UTF-8 cs_CZ.aff > cs_CZ.aff.utf8 $ sed "1s/ISO8859-2/UTF-8/" cs_CZ.aff.utf8 > cs_CZ.aff.utf8.1 $ sed "2119s/$/áéíóúýůěrl\]nout/" cs_CZ.aff.utf8.1 > cs_CZ.aff.utf8 $ iconv -f ISO-8859-2 -t UTF-8 cs_CZ.dic > cs_CZ.dic.utf8 $ rm cs_CZ.aff.utf8.1 $ mv cs_CZ.aff.utf8 cs_CZ.aff $ mv cs_CZ.dic.utf8 cs_CZ.dic
Výsledkem je opravený soubor .aff
a oba slovníkové soubory zkonvertované do kódováníUTF-8
. Upravené kódování se bude hodit později, až budeme slovník rozšiřovat.
„Měkkoni“ si můžou oba upravené soubory stáhnou například z Lucene-4311.
Konfigurace hunspell token filtru pro češtinu a slovenštinu
Nyní Elasticsearch nastartujeme a vytvoříme konfiguraci custom
analyzérů pro index i
, který bude používat hunspell token filter.
curl -X DELETE 'localhost:9200/i' curl -X PUT 'localhost:9200/i' -d '{ "settings" : { "analysis" : { "analyzer" : { "cestina_hunspell" : { "type" : "custom", "tokenizer" : "standard", "filter" : [ "stopwords_CZ", "cs_CZ", "lowercase", "stopwords_CZ", "remove_duplicities" ] }, "slovenstina_hunspell" : { "type" : "custom", "tokenizer" : "standard", "filter" : [ "stopwords_SK", "sk_SK", "lowercase", "stopwords_SK", "remove_duplicities" ] } }, "filter" : { "stopwords_CZ" : { "type" : "stop", "stopwords" : [ "právě", "že", "_czech_" ], "ignore_case" : true }, "stopwords_SK" : { "type" : "stop", "stopwords" : [ "ako", "sú", "a", "v" ], "ignore_case" : true }, "cs_CZ" : { "type" : "hunspell", "locale" : "cs_CZ", "dedup" : true, "recursion_level" : 0 }, "sk_SK" : { "type" : "hunspell", "locale" : "sk_SK", "dedup" : true, "recursion_level" : 0 }, "remove_duplicities" : { "type" : "unique", "only_on_same_position" : true }}}}}'
Konfigurace vytváří analyzéry pro češtinu (cestina_hunspell
) a slovenštinu (slovenstina_hunspell
). Oba používají podobný seznam token filtrů. Pojdmě se podrobněji podívat, jakou úlohu jednotlivé token filtry při analýze mají.
Hunspell token filter
V konfiguraci hunspell token filtru pro češtinu (cs_CZ
) a slovenštinu (sk_SK
) bych upozornil na dvě nastavení:
recursion_level
je nastaven na hodnotu 0.
Můžete vyzkoušet i jiné nezáporné hodnoty recursion_level
. Defaultně je nastavena hodnota 2, což není podle mě ideální (i když určitou logiku to má). Pro češtinu (a předpokládám i pro slovenštinu) budete s rostoucí hodnotou dostávat horší výsledky, navíc platí, že čím kratší vstupní token, tím horší výsledky lze očekávat.
Horším výsledkem mám na mysli to, že kromě správného základního slova může být vygenerováno jedno či více jiných základních slov, které nemají shodný kořen se vstupním tokenem (falešná základní slova). Z pohledu fulltextového vyhledávání je takové chování zcela nevhodné.
Experimenty s nenulovou hodnotou recurion_level
ponechávám na čtenáři a nebudu se touto problematikou v článku příliš zabývat.
dedup
je aktivní. Používá se pro odstranění duplicitních výstupních tokenů.
Obecně lze použít různá affixová pravidla pro odvození stejného základního slova. Řečeno jinak: k jednomu základnímu slovu se lze dostat různou cestou. V takovém případě jsou obě (identická) základní slova výstupem hunspell token filtru (obzvláště při nenulových hodnotáchrecursion_level
se pravděpodobnost tohoto jevu zvyšuje). Tyto duplicitní tokeny je užitečné odfiltrovat.
Remove duplicities
Ačkoliv je pro hunspell token filter volba dedup
aktivní, narazil jsem na případy, kdy mi nefungovala podle očekávání (zatím nevím, jestli je chyba na mojí straně, nebo v Lucene implementaci). Z toho důvodu jsem si pojistil odstranění duplicitních tokenů pomocí unique token filteru.
Stopwords
Stopwords pro češtinu jsme definovali jako rozšíření defaultního seznamu slov, který je součástí Lucene. Na defaultní seznam se odkazujeme pomocí _czech_
a přidali jsme další dvě slova: právě
, že
.
Protože Lucene zatím nenabízí defaultní stopwords pro slovenštinu, musíme celý seznam slov nadefinovat v konfiguraci. Pro naše potřeby bude stačit malý výběr inspirovaný tímto seznamem.
Další možnosti konfigurace jsou popsány v dokumentaci stop tokenfilter.
Pořadí token filtrů
Token filtry jsou na jednotlivé tokeny aplikovány v tom pořadí, v jakém jsou uvedeny v analyzéru. Proč jsem zvolil právě následné pořadí?
[ "stopwords_CZ", "cs_CZ", "lowercase", "stopwords_CZ", "remove_duplicities" ]
- Aplikace hunspell token filtru může být drahá operace. Pro každý vstupní token se provádí řada lookup operací do paměti a i když je lookup operace rychlá, stále to je operace, kterou je lepší neprovádět vůbec, je-li to možné. Proto nejdříve odstraníme co nejvíce stopwords.
- Následuje aplikace hunspell token filtru. Všimněte si, že vstupní tokeny doposud nebyly převedeny na malá písmena. To je proto, že hunspell slovníky mohou být „case-sensitive“ (například u jmen a zkratek). Má to však i své důsledky. Podrobnosti dále.
- Převedeme tokeny na malá písmena.
- Podruhé (!) aplikujeme tentýž stopwords filter. Velmi totiž záleží na konkrétním seznamu stopwords slov. Například defaultní česká stopwords neobsahují všechny slovní tvary. Kupříkladu obsahuje slovo
který
ale už neobsahuje slovokterému
(mimochodem, tuto skutečnost by si měli uvědomit všichni, kdo používají CzechAnalyzer představený v minulém díle). Řešením je buď rozšíření stopwords, vytvoření zcela nového seznamu nebo si elegantně vypomůžeme hunspell slovníkem a druhým průchodem stopwords filtrem (což je případ konfigurace analýzy v této ukázce). - Na závěr odstraníme případné duplicitní tokeny.
Takto definovaný seznam token filtrů berte pochopitelně jen jako ukázku. V praxi může být nastavení analýzy mnohem sofistikovanější.
Komu se „kouzla“ se stopwords zdají příliš krkolomná, toho bych rád upozornil nacommons terms query
a doporučuji přečíst si tento článek. Stopwords token filtrům se dá vyhnout.
Ještě si dovolím malou poznámku k defaultnímu seznamu českých stopwords. Skutečnost, že seznam neobsahuje všechny slovní tvary, může vypadat jako nedostatek, ale pokud vím, tak Lukáš Zapletal (autor českých stopwords pro Lucene) používal Ispell slovník pro analýzu českých textů, a to by mohl být důvod, proč mu neúplný seznam nevadil (stejně tak to nevadí teď nám, pokud stopwords token filteru předchází hunspell token filter).
Hunspell token filter v akci!
Nejrychlejší cesta, jak vyzkoušet funkci hunspell token fitleru je přes analyze API.
Příklad #1: „Právě se mi zdálo, že se kolem okna něco mihlo.“
curl 'localhost:9200/i/_analyze?analyzer=cestina_hunspell&pretty=true' -d 'Právě se mi zdálo, že se kolem okna něco mihlo.'
Po odstranění stopwords: [právě
, se
, mi
, že
] a aplikaci hunspell token filteru jsou výstupem následující tokeny:
zdát
kolem
,kolo
okno
něco
mihnout
Všimněte si, že z tokenu kolem
se nám zrodily tokeny dva (oba na stejné pozici), neboť se také může jednat o Instrumentál, tedy 7. pád podstatného jména kolo
.
Příklad #2: „On se nejvíc zajímal o nejnesnesitelnější způsob, jak co nejvíce nezlobit.“
curl 'localhost:9200/i/_analyze?analyzer=cestina_hunspell&pretty=true' -d 'On se nejvíc zajímal o nejnesnesitelnější způsob, jak co nejvíce nezlobit.'
víc
zajímat
snesitelnější
způsob
,způsoba
zlobit
Zde vidíme, že hunspell umí odstranit i předpony (ne, nej, nejne). Na konci článku se k této vlastnosti ještě vrátíme, neboť to trochu zlobí. Všimněte si, že zatímco z nejvíc
se urodilovíc
, nejvíce
zmizelo úplně. Proč? Protože více
je v seznamu stopwords, kdežtovíc
není.
A co je to způsoba
? No, to asi bude ten … přechodník. To je něco jako Werichův Příchozí Vejda, nebo ten druhej – Úprka (ne, to už je vlastně jméno, a k těm se dostaneme později také). Nebo … že by to byl tento způsoba?
Příklad #3: Zkusíme slovenštinu – „Ako vieme, lacné parochne a bokombrady sú klasikou v mužskej móde.“
curl 'localhost:9200/i/_analyze?analyzer=slovenstina_hunspell&pretty=true' -d 'Ako vieme, lacné parochne a bokombrady sú klasikou v mužskej móde.'
vedieť
lacný
parochňa
bokombrada
klasika
mužský
móda
Tak schválně, že nevíte, co je to parochňa nebo bokombrada?
Rozšíření slovníku
Doposud jsme si ukázali, že hunspell slovník je načten z jediného souboru s příponou .dic
. Výsledný slovník však může být vytvořen z více souborů. Elasticsearch se pokusí načíst a sloučit všechny soubory s příponou .dic
, které v adresáři pro daný jazyk nalezne.
Pořadí, v jakém jsou soubory načteny není IMO specifikován, takže pozor v případě, kdy se pokusíte nějaké slovo z jednoho souboru „přepsat“ novou definicí ve druhém souboru. Kódování všech souborů s příponou .dic
musí být stejné a je specifikováno prvním příkazem v souboru .aff
.
Soubor s příponou .aff
může být pouze jediný, pokud byste náhodou měli v adresáři pro daný jazyk více souborů s touto příponou, Elasticsearch načte („náhodně“) pouze jeden z nich.
V naší ukázce se pokusíme existující slovník obohatit o pár moravských výrazů.
Vytvořte soubor elasticsearch-0.90.3/config/hunspell/cs_CZ/morava.dic
s následujícím obsahem:
9 čupnout/JTN čupnutí/SN čupnutý/YRN čupět/AN čupění/SN čupící/YN zavazející/YN zavazet/AJTN zavazení/SN
Číslo na prvním řádku udává počet slov ve slovníku (Elasticsearch však touto hodnotu nepoužívá). Kódování souboru musí být ve shodě s prvním příkazem v souboru .aff
. V našem případě tedy UTF-8
(viz. odstavec „Look Ma, I’m a Hacker!!!“, kde jsme kódování takto změnili).
Připravený soubor si můžete stáhnout zde nebo přes wget (nezapomeňte jej uložit do správného adresáře).
wget https://gist.github.com/lukas-vlcek/6341611/raw/485078f9bb5dfbbca16fd7529e21bf64d6cac9e1/morava.dic
Nyní restartujte Elasticsearch process.
Příklad: „Ty, zavazíš mi ve výhledu, čupni si!“. Tak jsem si čupnul.
curl 'localhost:9200/i/_analyze?analyzer=cestina_hunspell&pretty=true' -d '"Ty, zavazíš mi ve výhledu, čupni si!". Tak jsem si čupnul.'
zavazet
výhled
čupnout
čupnout
Modifikace existujících slovníků či vytváření nových a doplňujících slovníků je podle mě jeden z nejzajímavějších přínosů hunspell token filtru.
Vady na kráse
Až potud funguje hunspell token filter velmi nadějně. Dokonce lépe, než jsem vůbec očekával. Když jsem totiž začal na článku pracovat, tak ještě nešlo konfigurovat hodnotu recursion_level
a hunspell byl pro češtinu prakticky nepoužitelný (jediná cesta vedla přes vlastní rozšíření Lucene nebo implementaci vlastního plugin pro Elasticsearch). Přesto má hunspell implementace stále pár vad na kráse.
Ne, Nej, Nejne … a další předpony
S českým i slovenským Hunspell slovníkem lze rozpoznat několik prefixů, které mají být odstraněny, aby bylo možné slovo správně převést na základní tvar. Typickým příkladem jsou předpony: „ne“, „nej“ a „nejne“. Bohužel současná Lucene implementace má v tomto směru nepříjemná omezení.
Hunspell token filter například správně převede slovo „nezavazet“ na „zavazet“, zároveň správně rozpozná slova, u kterých se tyto předpony odstranit nedají (např. „nemocnice“, „nejapný“). Problém však nastává u slov, u kterých je potřeba odstranit předponu a zároveň upravit či odstranit i příponu. Například „nezavazela„. Aby hunspell převedl i takové slovo na „zavazet“, už by bylo potřeba dvou průchodů algoritmem, tedy museli bychom nastavitrecursion_level
na hodnotu 1. A to v mnoha případech nebude dělat dobrotu s ohledem na falešná základní slova, která mohou být generována ze slov v textu.
Možná by se dal najít nějaký šikovný workaround, ale správným řešením bude jedině úprava Lucene implementace.
Case-sensitivity
Hunspell slovníky můžou být case-sensitive. Například český slovník rozlišuje jména, zkratky… atd. Umí například z „Karla Čapka“ udělat „Karel Čapek“. Takže pokud budete hledat cokoli o „Čapkovi“, „Čapcích“, „Čapkům“ atd, tak to může dopadnout dobře. To je výborná vlastnost.
Problém ale nastává, pokud chcete zároveň použít i lowercase token fitler a umožnit tak uživatelům hledat i přes „čapek“, „čapka“, „čapkovi“, „čapkcích“, „čapkům“ … pokud totiž odstraníte velká písmena dřív, než se token dostane do hunspell token filteru, tak o tuto vlastnost přicházíte. Na druhou stranu, pokud aplikujete lowercase až po hunspellu, tak máte problém s velkými písmeny na začátku vět (takové tokeny nebudou správně analyzované).
Příklad #1: „Potkal jsem Karla Čapka“ – lowercase až za hunspellem.
curl 'localhost:9200/i/_analyze?analyzer=cestina_hunspell&pretty=true' -d 'Potkal jsem Karla Čapka'
Výstupem jsou následující tokeny:
potkal
(tenhle token není převeden na základní tvar)karla
,karel
(ženská a můžská varianta téhož jména)čapek
Příklad #2: „Potkal jsem Karla Čapka“ – lowercase před hunspellem. (V našem případě nebudeme definovat nový analyzátor, ale převedeme celou větu do lowercase „ručně“.)
curl 'localhost:9200/i/_analyze?analyzer=cestina_hunspell&pretty=true' -d 'potkal jsem karla čapka'
Výstupem jsou následující tokeny:
potkat
(teď je to už správně)karla
(ostaní odvozené slovní tvary odkarel
nebudou „matchovat“)čapka
(totéž platí zde, navíc,čapka
je také pokrývka hlavy)
Zatím jsem moc nepřemýšlel nad tím, jestli pro tento problém existuje jednoduché řešení, ale domnívám se, že jednou z cest by mohlo být přímé rozšíření Lucene implementace hunspellu tak, aby umožnil aplikovat lowercase interně. To znamená, že vstupem by byl token před aplikací lowercase a pokud by hunspell nedokázal takové slovo převést na základní tvar, zkusil by interně aplikovat lowercase a pak znovu převést slovo na základní tvar. Takové řešení by zřejmě vyřešilo oba problémy se jmény i tokeny na začátku vět.
Pokud jde o workaround, nabízí se možnost rozšíření slovníku tak, aby slova, která obsahují velká písmena, existovala i ve variantě v lowercase. Takový slovník by nemělo být obtížné vytvořit. Z pohledu analýzy by to znamenalo aplikovat lowercase před hunspellem.
Lucene-5057
Další problém se jmenuje: „Hunspell stemmer generates multiple tokens“ a má svůj JIRA ticket: LUCENE-5057.
Už jsme viděli, že hunspell token filter může pro jeden vstupní token vygenerovat více výstupních tokenů (i když hodnota recursion_level
je 0). Například:
kolem
->kolem
,kolo
den
->den
,dna
,dno
Je to užitečná a správná vlastnost. Zádrhel však může nastat, pokud chcete při hledání použít v uživatelském dotazu operátor AND
.
Ukážeme si příklad. Nejdříve vytvoříme mapping a následně zaindexujeme dokument:
curl -X PUT 'localhost:9200/i/t/_mapping' -d ' { "t" : { "properties" : { "text" : { "type" : "string", "analyzer" : "cestina_hunspell"} } } }' curl -X POST 'localhost:9200/i/t?refresh=true' -d ' { "text" : "Auto má malé kolo" }'
Pro tento dokument jsou zaindexovány následující tokeny:
auto
malý
kolo
A teď zkusíme vyhledávat podle dotazu „auto s malým kolem“. Aby se problém projevli, použijeme match query
:
curl -X GET 'localhost:9200/i/t/_search' -d '{ "query" : { "match" : { "text" : { "query" : "auto s malým kolem", "operator" : "AND" }}}}'
Takové query dokument nenajde. Je to proto, že se hledá dokument, který obsahuje všechny následující termy: [ auto
, malý
, kolem
, kolo
]. V tom našem dokumentu chybí termkolem
.
Při použití query_string
je dokument už nalezen správně.
curl -X GET 'localhost:9200/i/t/_search' -d '{ "query" : { "query_string" : { "query" : "auto s malým kolem", "default_field" : "text", "default_operator" : "AND" }}}'
Na závěr
Cesta k dokonalému vyhledávání zde nekončí. Dalším krokem může být integrace pokročilého lematizátoru. Na akademické půdě existují zajímavé implementace českých lematizátorů, např. Morfo. Určitě by nebylo marné prozkoumat, jak jej lze integrovat s Lucene. A podívat se blíže na Apache openNLP by také nebylo na škodu.
U dalších článků na téma Lucene a Elasticsearch se těším nashledanou.
skvely clanok, dakujem. mam vsak otazku, ako je to v takomto pripade keby hladam text bez diakritiky?
Ano, diakritiku odstranit můžete. To je hezký dotaz. Můžete ji odstranit buď pomocí
asciifolding
token filtru [1] nebo pomocí sofistikovanějšíhoICU folding
token filtru [2]. Pokud použijeteICU folding
, tak ten zajistí i převod na malé znaky, takželowercase
token filter už pak použít nemusíte. Podívejte se do prvního dílu [3], tak jsem o tom také psal.Každopádně bych diakritiku odstranil až po aplikaci hunspell filtru.
[1] http://www.elasticsearch.org/guide/reference/index-modules/analysis/asciifolding-tokenfilter/
[2] http://www.elasticsearch.org/guide/reference/index-modules/analysis/icu-plugin/
[3] http://www.zdrojak.cz/clanky/elasticsearch-vyhledavame-cesky/
Narazil jsem na problém s diakritikou, konkrétně se jedná například o výraz „dámský“ vs „damsky“. Můj analyzer vypadá zhruba takto:
Slovníku (hunspell ani lemmagen) se ale nedaří výrazy spojit, pokud provedu příkaz:
S výsledkem:
]
}
Žádný slovník to prostě nezvládne převést na správný tvar a při vyhledávání je to problém. Máte někdo zkušenosti jak tohle řešit? Do vlastního slovníku se mi moc nechce, protože bych musel neustále reinsexovat X tisíc produktů a upravovat konfigurace, vytvářet nové indexy apod …
Díky
Vypadá to, že jste to docela vyladil. Nechcete výslednou českou konfiguraci i se slovníky zveřejnit na GitHubu?
To není špatný nápad. Popřemýšlím nad tím.
Zkusil jsem popisovanou konfiguraci nad ElasticSearch 1.2.1, který aktuálně používáme a nedaří se mi to rozchodit. Když jsem si nainstaloval 0.9.0 tak samozřejmě funguje. Nevíte, Lukáši, jaké zásadní změny se v ES přihodily a co je třeba upravit? Zatím se mi nedaří nic rozumného dohledat.
Zdravím,
s příchodem ES 1.2.x došlo k pár změnám v Hunspell token filteru (a nutno podotknout, že by to měly být změny k lepšímu). Zatím jsem neměl možnost updatovat článek, ale snad se to brzy podaří. Nicméně, zkusím Vám dát pár tipů, kde bych čekal nějaký problém:
recursion_level
parameter. Mělo by to fungovat lépe by default.Pokud problém přetrvá, klidně mi napište na mejl (najdete na webu). Alespoň budu mít dobrý materiál pro tvorbu dalšího dílu tohoto seriálu :-)
Díky, zítra si s tím budu hrát, o výsledky se do mailu podělím.
Zdravím, prosím co znamenají ta označení /JTN, /SN, /YRN, /AN …. Respektive kde k nim dohledat nějaké informace. Díky
To je z formátů oněch slovníků. Když si stáhnete české vloníky viz sekce České slovníky z OpenOffice, tak se skládají z dvou důležitých souborů:
cs_CZ.aff # <- affixová pravidla
cs_CZ.dic # <- seznam slov
Aff obsahuje definice oněch JTN, SN, YRN, což jsou vzásadě skloňovací pravidla (možné předpony, přípony a koncovky, které to slovo může mít).
Když se do něj teď dívám, tak hned na začáku je definice N, vypadá takhle:
Neznám přesně ten formá, ale definuje to předponu ne-. A znamená to, že ke všem slovům, které v souboru .dif budou mít /N lze přidat ne-.
Ještě pro ukázku přiložím definici pro R, ta je:
Ta definuje koncovky kategorie R. Čili všem slovům, ktéré mají v .dic slovníku /NR lze přidat předpony kategorie N a přípony kategorie R.
V článku je příklad čupnutý/YRN, takže k němu patří i nečupnutý, čupnutě, nečupnutě atd.
Dokumentace asi tady https://www.systutorials.com/docs/linux/man/4-hunspell/#lbAQ