Elasticsearch: Vyhledáváme hezky česky

Dobré fulltextové vyhledávání pro češtinu nemusí nutně znamenat investici do proprietárních knihoven a slovníků. Nevěříte? Zkusím vás přesvědčit! Ukážeme si, jak nakonfigurovat Elasticsearch pro fulltextové vyhledávání v českých textech.
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:
Nejdříve si ale představíme knihovnou Lucene a vysvětlíme si, jakou nabízí podporu pro češtinu (trochu suché teorie). Pak se podíváme na Elasticsearch, který je nad Lucene postavený a uvedeme si konkrétní konfigurace pro vyhledávání v češtině.
(Použité verze: Elasticsearch 0.90.0, Lucene 4.2.1)
Lucene
Lucene je výkonná open source knihovna pro fulltextové vyhledávání implementovaná v Javě. Mezi vývojáři je ceněná především pro svoji vyspělost a širokou komerční i komunitní podporu. Je to low-level knihovna, přímé použití není pro každého. Nabízí velmi detailní, nízkoúrovňové API, ale řadu problémů spojených s implementací rozsáhlejších systémů neřeší.
Analýza textu
Fulltextové vyhledávače ukládají data do invertovaných indexů. Aby Lucene mohl vytvořit invertovaný index, potřebuje vstupní text rozdělit na jednotlivá slova a případně tato slova dále upravit (například převést na základní tvar). Tento proces se nazývá analýza.
- Analyzér sestává z
char filtrů
, jednohotokenizéru
a několikatoken filtrů
.
- Chart filter může měnit, přidávat nebo odebírat jednotlivé znaky vstupního textu. Jeho vstupem i výstupem je stream znaků.
- Tokenizér je zodpovědný za rozsekání vstupního textu (streamu znaků) na jednotlivá slova. V Lucene se pro „slovo“ užívá označení token nebo term – v závislosti na kontextu. Výstupem tokenizéru je stream tokenů.
- Token filtry přicházejí na řadu jako poslední. Každý vstupní token je postoupen jednotlivým token filtrům v předem definovaném pořadí. Token filtry mohou vstupní token nějak zpracovat či modifikovat. Výstupem token filtru může být žádný, jeden či více tokenů.
Hezkým příkladem chart filtru je HTML strip filter, který z textu odstraňuje HTML značky. Umí se postarat i o překlad HTML entit.
Příkladem tokenizéru, který je vhodný pro slovanské jazyky (včetně češtiny) je StandardTokenizer, který za oddělovače slov považuje mezery, čárky, tečky, pomlčky a podobně. Složitější situace nastává u germánských jazyků (např. u němčiny kvůli „složeným slovům“) nebo u jazyků označovaných jako CJK (činština, japonština, korejština), ve kterých se jednotlivá slova v textu neoddělují. Pro takové případy existují v Lucene specializované tokenizéry.
Příkladem token filtru může být např:
- zjištění kořene slova (hledání -> [hledat])
- expanze na synonyma (hledat -> [hledat, pátrat, shánět])
- ngramy (např. 3-gramy: hledat -> [hle, led, eda, dat])
- odstranění diakritiky … atd.
Podpora češtiny v Lucene
Těžištěm podpory češtiny v Lucene jsou především token a char filtry, které umožňují zohlednit pravidla platná pro češtinu. Lucene nabízí „out of the box“ následující komponenty:
- Czech Stopwords token filtr
- Czech Stemmer token filtr
- Podporu ICU
- Hunspell token filtr (probereme ve druhém díle článku)
Czech Stopwords token filtr
Stopwords (či Stopslova) je seznam tokenů (slov), které se neindexují, token filter tyto tokeny zahodí. Nejsou tedy zahrnuty ve výsledném invertovaném indexu a nelze podle nich ani vyhledávat. Vhodnými kandidáty na stopslova jsou taková slova, která se v textu vyskytují velmi často a nejsou nositelem informační hodnoty. Vyloučení těchto slov z indexování má za následek menší velikost výsledného indexu a může pozitivně ovlivnit přesnost vyhledávání.
Seznam českých stopwords je součástí Lucene někdy od roku 2005. Jejich autorem je Lukáš Zapletal — timto zdravím kolegu z Red Hatu :-). Samozřejmě Lucene, stejně jako Elasticsearch, umožňuje nadefinovat vlastní seznam stopslov.
Czech Stemmer token filtr
Stematizace je proces nalezení základu slova (tzv. stem). Nejedná se tedy o Lematizaci, což je nalezení kořene slova (tzv. lemma). Stemmery jsou často implementovány jako „algoritmická ořezávátka koncovek slov“. Jejich výstupem nemusí být validní slovo v daném jazyce.
Od roku 2009 nabízí Lucene jeden stemmer pro češtinu (tento stemmer nenavrhli ani neimplementovali Češi). V budoucnu se možná dočkáme i stemmeru pro češtinu založeném na Snowballu. Existuje i implementace stemmeru pro Snowball od českých autorů (paper je zde). Uvidíme někdy v Lucene i tento?
Czech Analyzer
Všimli jste si, že v tuto chvíli už umíme sestavit první analyzér pro češtinu? Spojením Standard tokenizéru, Czech Stopwords a Czech Stemmeru totiž vznikne CzechAnalyzer. Za chvíli si ho vyzkoušíme.
Podpora ICU
Vzpomínáte si na okamžik, kdy jste se naučili, že v češtině má posloupnost znaků c
a h
specifický význam? Ano… písmeno Ch
! Nejen, že se jedná o jedno písmeno, ale je třeba si pamatovat, že má i určité místo v abecedě.
A přesně tohle je úkol pro Lucene analýzu využívající ICU. Obecně se jedná o podporu národních formátu a unicode standardu pro kódování znaků. Ve stručnosti se dá říci, že s ICU můžete v Lucene správně porovnávat řetězce, odstraňovat diakritiku a překládat speciání znaky, použít normalizované kódování národních znaků a v neposlední řadě použít ICU tokenizér. Za chvíli si ukážeme nějaké příklady.
Tolik teorie a teď pojďme na praktickou část.
Elasticsearch
Existuje několik cest, kudy se vývojář může vydat, pokud chce Lucene použít, ale nechce se „dotýkat“ detailních API či psát si (a tedy i udržovat) vlastní Lucene rozšíření. Za všechny můžeme zmínit třeba Hibernate Search, Solr, Compass nebo Elasticsearch. V další části použijeme Elasticsearch a hlavní pozornost zaměříme na konfiguraci analýzy.
Poznámka: Vývoj knihovny Compass byl ukončen a její použití proto nelze obecně doporučit. Milovníky historie však může zaujmout skutečnost, že Compass je přímým předchůdcem Elasticsearch.
Budeme používat výhradně REST API pro komunikaci s běžícím serverem. Veškeré ukázky lze pohodlně předvést na příkazovém řádku (předpokládám užití Linuxu nebo Mac OS).
Nainstalujte a spusťte Elasticsearch
Elasticsearch 0.90.0 můžete stáhnout pomocí wget
:
wget https://download.elasticsearch.org/elasticsearch/elasticsearch/elasticsearch-0.90.0.zip
Archiv rozbalte a nastavte aktuální cestu do nově vzniklého adresáře.
unzip elasticsearch-0.90.0.zip
cd elasticsearch-0.90.0
Pokud máte správně nainstalovanou Javu (doporučuji SUN JDK 1.7), tak nastartuje elasticsearch.
./bin/elasticsearch -f
Přepínač -f říká, že Elasticsearch má běžet v popředí (foreground). Znamená to, že v konzoli můžeme ihned sledovat logy (ty jsou standardně ukládány do ./logs/elasticsearch.log).
Jakmile v konzoli (nebo v logu) uvidíme něco jako
[2013-05-26 16:26:15,135][INFO ][node ] [Marrow] {0.90.0}[1202]: started
[2013-05-26 16:26:15,190][INFO ][gateway ] [Marrow] recovered [0] indices into cluster_state
tak jsme připraveni k další práci.
Pro jistotu ještě můžeme ověřit dostupnost REST end pointu pomocí curl příkazu. Otevřete si druhou konzoli a na příkazový řádek zadejte:
curl localhost:9200
#
# Výstupem je:
# {
# "ok" : true,
# "status" : 200,
# "name" : "Marrow",
# "version" : {
# "number" : "0.90.0",
# "snapshot_build" : false
# },
# "tagline" : "You Know, for Search"
# }
Běžící Elasticsearch můžete zastavit několika způsoby. Pokud jeho proces běží v popředí (-f), tak klasicky Ctrl+C, jinak je možné použít kill -9 <pid> (ačkoliv kill vypadá násilně, Elastic je na takovou situaci připravený a jedná se o běžné ukončení).
Konfigurace Czech Analyzéru
Začneme ukázkou použití Czech Analyzéru. V Elasticsearch je analyzér vázán na konkrétní index. Proto nejrychlejší cesta vede přes REST API pro vytváření indexů.
Poznámka: Pokud už máte s Elasticsearch nějakou zkušenost, tak možná víte, že analyzéry lze definovat i pro index templates nebo v konfiguračním souboru elasticsearch.yml, ale tím se teď nebudeme zabývat.
curl -X PUT 'localhost:9200/sbornik' -d '{
"settings" : {
"analysis" : {
"analyzer" : {
"cestina" : {
"type" : "czech"
}}}}}'
Tímto příkazem jsme vyrobili index sbornik
s nadefinovaný analyzérem cestina
, který používá Czech Analyzer z Lucene.
Pomocí Analyze API můžeme analyzér ihned vyzkoušet.
curl 'localhost:9200/sbornik/_analyze?analyzer=cestina&pretty=true' -d 'Bankovní poplatky jsou nehorázné!'
Výstupem analýzy textu „Bankovní poplatky jsou nehorázné!“ jsou tři tokeny (v tomto případě stemy): [„bankovn“,“poplatk“,“nehorázn“]. Jinými slovy: pokud bychom indexovali dokument, který by obsahoval právě tento text a použili přitom analyzátor cestina
, pak by invertovaný index obsahoval právě tyto tři termy.
Czech Analyzér nabízí možnost konfigurace stopslov a stemmer exclusion – tedy umožňuje vyjmout vyjmenované termy ze stemming procesu (může se hodit např. pro jména osob, měst a podobně).
Někdy se může hodit, že si sestavíte vlastní analyzér, který je Czech Analyzérů podobný, ale některé filtry nebo tokenizér chcete upravit. Inspirací nechť je gist ukázka, která naznačuje, jak na to.
Instalace a použití ICU pluginu
Pojďme se teď podívat, jakou podporu nabízí Elasticsearch pro ICU. Konkrétně si ukážeme třídění a case folding (který mimo jiné odstraňuje diakritiku).
Zastavte Elasticsearch a pak nainstalujte Elasticsearch ICU plugin.
./bin/plugin -install elasticsearch/elasticsearch-analysis-icu/1.9.0
# Počkejte na hlášku: Installed analysis-icu
Opět nastartujte Elasticsearch. Při startu si v logu ověřte, že analysis-icu
plugin byl nalezen.
[2013-06-18 23:53:13,527][INFO ][plugins ] [Atalanta] loaded [analysis-icu], sites []
ICU Collation
ICU collation se používá pro správné setřídění řetězců. Výstupem fulltextového vyhledávání bývá zpravidla seznam dokumentů, které jsou setříděné podle relevance. Elasticsearch však umožňuje setřídit výsledky vyhledávání i podle mnoha jiných kritérií. Pokud potřebujete třídit podle pole, ve kterém se vyskytují národní znaky, tak byste měli ICU collation vyzkoušet.
Mějme následující množinu názvů: [„Celer“,“Cizrna“,“Cypřiš“,“Chřest“]. Podívejme se, jak můžeme tuto množinu setřídit podle abecedy.
# Pokud index existuje, tak jej nejdříve odstraníme (včetně mappingu a analyzátorů)
curl -X DELETE 'localhost:9200/i/'
curl -X POST 'localhost:9200/i/' -d '{
"settings" : {
"analysis" : {
"analyzer" : {
"cs_icu_analyzer" : {
"type" : "custom",
"tokenizer" : "standard",
"filter" : ["cs_icu_collation"]
}
},
"filter" : {
"cs_icu_collation" : {
"type" : "icu_collation",
"language" : "cs"
}}}},
"mappings" : {
"t" : {
"properties" : {
"text_icu" : { "type" : "string", "analyzer" : "cs_icu_analyzer" },
"text" : { "type" : "string" }
}}}}'
Takto jsme připravili index i
s typem t
, který má nadefinované dvě pole text
a text_icu
. Následně zaindexujeme dokumenty.
curl -X POST 'localhost:9200/i/t/' -d '{ "text_icu" : "Celer", "text" : "Celer" }'
curl -X POST 'localhost:9200/i/t/' -d '{ "text_icu" : "Cypřiš", "text" : "Cypřiš" }'
curl -X POST 'localhost:9200/i/t/' -d '{ "text_icu" : "Cizrna", "text" : "Cizrna" }'
curl -X POST 'localhost:9200/i/t/' -d '{ "text_icu" : "Chřest", "text" : "Chřest" }'
Výsledek třídění dokumentů bez použití ICU a s použitím ICU můžeme jednoduše porovnat.
echo "Třídíme bez ICU \n"
curl -X GET 'localhost:9200/_search?pretty=true' -d '{
"query" : { "match_all" : {} },
"sort" : [ "text" ]
}'
# Celer, Chřest, Cizrna, Cypřiš
echo "Třídíme s ICU \n"
curl -X GET 'localhost:9200/_search?pretty=true' -d '{
"query" : { "match_all" : {} },
"sort" : [ "text_icu" ]
}'
# Celer, Cizrna, Cypřiš, Chřest
Gist: Kompletní ukázka ICU collation.
ICU Folding
Pokud chcete odstranit diakritiku, tak můžete použít asciifolding token filter. ICU však nabízí alternativu, která umí mnohem víc (a je také „dražší“). ICU folding totiž není jen o diakritice, ale obecně provádí mnohem sofistikovaňější transformace znaků, které jsou detailně popsány v dokumentaci ICU.
curl -X DELETE 'localhost:9200/i/'
curl -X POST 'localhost:9200/i/' -d '{
"settings" : {
"analysis" : {
"analyzer" : {
"icu_folding" : {
"type" : "custom",
"tokenizer" : "whitespace",
"filter" : ["icu_folding"]
},
"ascii_folding" : {
"type" : "custom",
"tokenizer" : "whitespace",
"filter" : ["asciifolding","lowercase"]
}}}}}'
Takto jsme připravili index i
se dvěma analyzátory, první používá ICU folding a druhý asciifolding token filter.
Výsledek analýzy textu „Běloučký kůň úpěl ódy!“ je pro oba analyzátory shodný.
curl 'localhost:9200/i/_analyze?analyzer=icu_folding&pretty=true' -d 'Běloučký kůň úpěl ódy!'
curl 'localhost:9200/i/_analyze?analyzer=ascii_folding&pretty=true' -d 'Běloučký kůň úpěl ódy!'
Ovšem v následujícím případě už narazíme na první rozdíly.
curl 'localhost:9200/i/_analyze?analyzer=icu_folding&pretty=true' -d 'dž ¼ № ℃ ™ Æ Ȣ ffi '
curl 'localhost:9200/i/_analyze?analyzer=ascii_folding&pretty=true' -d 'dž ¼ № ℃ ™ Æ Ȣ ffi '
Vidíme, že ICU folding si lépe poradí se znaky: ¼ № ℃ ™
V poslední ukázce je ovšem rozdíl ještě výraznější.
curl 'localhost:9200/i/_analyze?analyzer=icu_folding&pretty=true' -d 'º o ª a ℹ i ℇ e'
curl 'localhost:9200/i/_analyze?analyzer=ascii_folding&pretty=true' -d 'º o ª a ℹ i ℇ e'
V této se totiž naplno projevuje pravý účel ICU foldingu.
Gist: Kompletní ukázka ICU Folding.
Pro další studium ICU může sloužit článek Three Principles for Multilingal Indexing in Elasticsearch.
Na závěr
V tomto článku jsem se pokusil načrtnout přehled pro ty, kteří se potřebují zorientovat v základní problematice podpory češtiny ve fulltextovém vyhledávání založeném na Lucene a odpovídající konfiguraci pro Elasticsearch.
V navazujícím díle se seznámíme s token filtrem, který používá Hunspell, s českým Ispell slovníkem.
Dobrý den, chtěl bych se optat zda neuvažujete o detailnějším článku ohledně konfigurace a práce s API v rozsahu jako je nyní v seriálu Sphinx Search API zde na zdrojáku. Mám zkušenosti s Phinx ze dvou projektů, ale ze zkušeností mi bylo doporučováno spíše Elasticsearch. Byl bych za to velmi vděčný…
Pripájam sa, určite je viac ľudí, ktorí by to taktiež uvítali.
Zdravím,
dík za zájem o článek, je to určitě povzbuzující pro další psaní.
Upřimě řečeno o dalších článcích uvažuji, ale domnívám se, že články, po jakých voláte, by měly být především součástí samotných stránek elasticsearch.org (dev. tým několikrát zmínil, že na lepší dokumentaci či dokonce knížce pracuje). Proto se přikláním k jinému formátu článků, které se budou týkat spíš konkrétních oblastí, než obecného úvodu a konfiguraci. Samozřejmě se tím dostanu i ke konfiguraci a konkrétním ukázkám použití API. Takže pokud si někdo dá tu práci, a sám začne Elasticsearch zkoušet, tak se domnívám, že by takové články pro něj mohly mít větší přínos.
Po druhém díle o češtině bych se rád věnoval nějakému článku o distribuovaném modelu Elasticsearch (opět je to dost odvislé od toho, jak na low level úrovni pracuje Lucene), je to podle mě docela zásadní téma s velkým přesahem.
Takové téma je ovšem náročné na zpracování, takže v mezičase určitě neodmítám další náměty :-)
Pridavam se s prosbou o „tutorial“ od piky. Chapu, ze odborne clanky resici konkretni problem jsou mozna vice prinosem, ale pocet cilovych ctenaru neni az tak velky. Programator dnes resi spousty veci, potrebuje to kvalitne a aby to nemusel resit moc dlouho. Tutorial o Sphinx je uplne nejlepsi uvod do problemu, po jeho precteni si je clovek shopny udelat v pohod vyhledavani.
Já to naprosto chápu. Kdybych za sebou neměl několika-letou zkušenost s Elaticem a potřeboval se do něj dostat rychle, tak bych byl možná taky zoufalý. Ale jak jsem jednou napsal, podle mě takový basic tutoriál patří na stránky elasticsearch.org a já upřimně nerozumím tomu, proč tam dodnes není (a to říkám s tím, že jsem jednu dobu do dokumentace Elasticsearch.org přispíval a věnoval jí nemalé množství osobního času). Na druhou stranu, takové tutoriály se dnes dají na webu najít, chce to googlit. Řada dobrých úvodů o Elasticsearch lze najít na webu z dílny Karla Minaříka. Mohu je jedině doporučit.
Elasticsearch je ve své podstatě velmi komplexní systém a já asi nejsem ten nejlepší člověk, který by měl „basic“ tutoriál psát. Já rád věci chápu do hloubky a většinou se nespokojím s povrchní znalostí. U fultextu a Elasticsearch může povrchní znalost znamenat neefektivní využívání prostředků, nebo dokonce missuse (vypadá to jako databáze? pak to asi bude databáze – tak proč tam třeba neuložit soubory typu mp3?).
Ale myslím, že pokud tu bude dostatečná poptávka po základním tutoriálu, tak bude i nabídka :-)
Heh… s/missuse/misuse/
Ako je to s podporou slovenciny?
Myslím, že Lucene nenabízí „out of the box“ stemmer pro Slovenštinu.
Na druhou stranu by podle mě nemuselo být obtížné základní stemmer přidat. Třeba zde: http://vi.ikt.ui.sav.sk/Projekty/Projekty_2008%2F%2F2009/Hana_Pifková_-_Stemer by se dala načerpat inspirace a Lucene komunita by určitě ráda Slovenský stemmer přijala. Věřím tomu, že důkladnější googlení by odhalilo i více stemmerů, pak je to jen otázka licence a vůle :-)
A pokud byste měl fungující Java kód, tak můžete pro Elasticsearch napsat plugin a nemusíte čekat na to, až bude stemmer součástí Lucene.
Ve druhé díle ukážu Hunspell a pro ten Slovenské slovníky existují, takže tam určitá podpora existuje už dnes.
Třídíme do tříd podle nějakých společných znaků. Typický příklad jsou facety – výsledky jsou setříděny do tříd majícíh nějakou hodnotu stejnou (např. stejný tag) a je vrácen vždycky počet prvků v danné třídě. V angličtině nejspíš něco jako grouping :)
Řadíme do řady, tedy nějaké posloupnosti. V angličtině sorting.
Zrovna tak jak to bylo použité tady je to zjevné, ale obecně to vede ke zmatení ;) Podobně jako zaměňování kódování a šifrování ;) Mimo to to všem, které to stálo zkoušku z diskrétní matematiky, leze hrozně na nervy :D
Dík za poznámku. A aby to bylo ještě pochopitelnější, tak pro úplnost dodám, že Elasticsearch umožňuje řadit setřízené [1]. Jinými slovy, pokud máte facety a chcete je seřadit podle abecedy a né podle četnosti.
[1] http://www.elasticsearch.org/guide/reference/api/search/facets/terms-facet/ viz „ordering“
Super článek, díky za něj. Do budoucna by se mi líbilo něco praktičtějšího – např. jak na kooperaci MySQL a ES apod. – zkrátka nějaké demo „jak se to dělá v praxi“. Plánujete i něco takového?
Ještě doplním, že na hraní s ES existuje do prohlížeče Chrome moc pěkné rozšíření s názvem Sense (https://chrome.google.com/webstore/detail/sense/doinijnbnggojdlcjifpdckfokbbfpbo)
Dík, jsem rád že se článek líbí.
Pokud kooperací s databází myslíte situaci, kdy v databázi máte uložena primární data která chcete replikovat do ES za účelem full-text vyhledávání, tak se o to musíte postarat sám (ES Vám však vyjde vstříc v podobě šikovných API). Možností je celá řada a záleží jen na Vás, co si vyberete. Začít můžete třeba tak, že se podíváte na JDBC River [1], možná bude plně vyhovovat.
Co se týká nějaké „praktické“ ukázky, tak nejste první, kdo tento návrh vznesl. Zvažuji to. Otázkou je „míra“ té praktičnosti. Bude-li ukázka stručná, tak hrozí nebezpečí, že nebude pokrývat skutečně praktické případy užití. Na druhou stranu bude-li moc „praktická“ může být hodně komplexní. To druhé už na GitHubu je, protože tam jsou celé zdrojáky vyhledávače, který implementujeme pro JBoss.org (ale pro začátečníky to je zcela nevhodné a navíc to není zaměřené na češtinu). Každopádně zvažuji, že bych mohl vyrobit ukázku vyhledávání nad českou a slovenskou wikipedií.
[1] https://github.com/jprante/elasticsearch-river-jdbc
Dobrý den,
v textu máte chybu. Výraz CharT filter neexistuje, Char Filter ano.
Pokiaľ bude v analyzéri nastavený tokenizer „standard“ tak nebude sort pre vetu fungovať správne. Je potrebné použiť tokenizér „keyword“ ktorý ako token vráti celý vstup. Viď https://www.elastic.co/guide/en/elasticsearch/guide/current/sorting-collations.html a https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis-keyword-tokenizer.html
V novější verzi ElasticSearch dostávám s tutoriálem chybu:
Za curl příkaz je potřeba přidat parametr: