User:TJones (WMF)/Notes/Ukrainian Morfologik Analysis

March 2017 — See TJones_(WMF)/Notes for other projects. See also T160106.

Background and Data
Elasticsearch 5 includes a new Ukrainian Analyzer based on Morfologik, which we've been waiting on for a while (see T148051).

Previous work on Russian and Ukrainian revealed that not-necessarily-linguistic language fallbacks (see T147959, too) were also being used for language analysis, meaning that some Russian-specific language config, like folding Ё to Е needed to be explicitly confined to Russian. Other config, like Ukrainian apostrophe handling (see T146358) needed to be hacked into the Russian config, but still confined to Ukrainian. (Note: Much to my surprise, it seems that the Russian analyzer was better than nothing for Ukrainian. See the discussion on the apostrophe Phab ticket.)

So, our goal here is to investigate the effects of the new Ukrainian analyzer, make sure it doesn't have any significant regressions for specific characters compared to the changes we've hacked into the Russian analyzer.

We also want to unwind the Ukrainian-specific changes to the Russian analyzer that won't do anything anymore if we move to the new Ukrainian analyzer.

The data I'm using for this analysis is a corpus of 5,000 random Ukrainian Wikipedia articles I pulled in October 2016 for a previous analysis.

Elasticsearch 5 Disabled the Russian Analysis Chain
Somewhere in the complex interaction between Elasticsearch's config and Mediawiki's fallback mechanism, the upgrade to ES5 seems to have broken the link between Ukrainian and the Russian language analyzer. I haven't found the specific mechanism, but I believe that Elasticsearch signals that analysis plugins are available (even if not installed), which blocks the Mediawiki fallback.

As of March 23, 2017, the Ukrainian Wikipedia is using ES 5.1.2 and the ICU Normalizer and Standard Tokenizer for "text" analysis. Rusyn, on the other hand, has ES 5.1.2, but still falls back to the non-Russian-specific Russian language config for "text" analysis (that is, the Ёё to Ее mappings aren't present, like they are in Russian).

This has the effect of undoing our processing for apostrophe-like characters (T146358).

I'm going to start with three basic configs, and see how they relate to each other:
 * manually forcing Ukrainian to use the Ukrainian-aware Russian config ("where we thought we were")
 * the current Elasticsearch 5 config ("where we are")
 * the new Ukrainian Morfologik analyzer ("where we want to be")

ES5/ICU Comparisons
I'm only going to briefly compare the pre-ES5 Russian config to the ES5 ICU config, and the ES5 ICU config to the new Ukrainian Morfologik config, just to make sure there isn't something obvious and useful that both the pre-ES5 Russian config and the new Ukrainian Morfologik config are missing that the ES5 ICU config highlights.

Ukrainian-Aware Russian config vs ES5/ICU
This is about what you'd expect. There are significantly more tokens (1,219,147 vs 1,344,151). These are all Russian stop words (212 different words), the top three of which в, на, and у (all prepositions) have ~20K occurrences each.

20.5% of post-analysis types (14.8% of tokens) are involved in a split. Since the percentage of types is larger than the percentage of tokens, these words are, collectively, less frequent than the average word in the corpus. That makes sense, since the Russian analysis chain would work best on Russian words, especially those that are not similar to their Ukrainian counterparts. The large overall impact does indicate that our impression that using the Russian analyzer when there was no Ukrainian analyzer was reasonable, since it does have a sizable impact.

ES5/ICU vs New Ukrainian Morfologik Analyzer
There are many fewer tokens (1,344,151 vs 1,134,880), which are all or mostly Ukrainian stop words (1100 different words). The top five, в, на, і, у, and з (prepositions and conjunctions) each have 15K-20K occurrences. 37.2% of post-analysis types (35.6% of tokens) are involved in new collisions. (The percentage of types and tokens being more similar indicates that these are, collectively, words of about average frequency.)

There are some new splits, though the numbers are very small: 42 types (0.023%) and 44 tokens (0.003%). These splits are caused by undesirable lack of folding for certain Unicode characters.
 * Zero-width non-breaking spaces (U+FEFF), soft hyphens (U+00AD), and left-to-right/right-to-left markers (U+200E / U+200F)
 * General ICU folding is lost: e.g., Edelweiß/Edelweiss, ℓ/l, Nº/No, ℝ/R, ϑ/θ

Ukrainian-Aware Russian config vs New Ukrainian Morfologik Analyzer
There are fewer tokens (1,219,147 vs 1,134,880). About 11.7% of post-analysis types (7.5% of tokens) have new collisions (thanks to added Ukrainian stemming) and 10.3% of types (6.1% of tokens) have new splits (mostly from the loss of Russian stemming, and a bit from loss of ICU folding). These are smaller than the swings from Russian to ICU folding or ICU folding to Ukrainian seen above, because of the similarities between Russian and Ukrainian. (Note that the three most common Russian stop words are also among the top five Ukrainian stop words, for example.)

The lack of ICU folding noted above is still a small problem:
 * Zero-width non-breaking spaces (U+FEFF), soft hyphens (U+00AD), and left-to-right/right-to-left markers (U+200E / U+200F)
 * General ICU folding is lost: e.g., Edelweiß/Edelweiss, ℓ/l, Nº/No, ℝ/R, ϑ/θ

Stemming splits resulting from switching from Russian to Ukrainian
Below is a sample of 50 splits. The word on the left has been removed from the group on the right—they were grouped by the Russian analysis chain, but are not grouped by the Ukrainian analyzer. Each word is preceded by its frequency within the sample.

I'm not including any new collisions because I decided to do a new-analyzer-only analysis, like I did for Polish.
 * [1 анализу] ← [4 Анализ][4 анализ]
 * [7 белізу] ← [5 Беліз]
 * [2 бобо] ← [16 Боб]
 * [1 боберга] ← [7 Боберг]
 * [11 борна] ← [5 Борн]
 * [1 вака] ← [5 ВАК]
 * [3 вара] ← [2 Вар][1 Варах][1 Вари]
 * [1 виділилися] ← [1 Виділів][7 виділ][2 виділи][1 виділу][1 виділі][1 виділів]
 * [6 відступили] ← [4 відступ][1 відступом][8 відступу][2 відступі]
 * [2 воротаре] ← [2 Воротар][2 Воротарі][16 воротар][1 воротареві][5 воротарем][1 воротарю][7 воротаря][1 воротарями][1 воротарі][9 воротарів]
 * [9 гідрохлоротіазиду] ← [23 Гідрохлоротіазид][2 гідрохлоротіазид]
 * [3 гостинець] ← [2 гостинец]
 * [1 готвальда] ← [2 Готвальд]
 * [1 гюйгенса] ← [1 Гюйгенс]
 * [1 джейсоном] ← [12 Джейсон]
 * [1 джобса] ← [2 Джобс]
 * [2 дубовича] ← [4 Дубович]
 * [1 дья́ков] ← [1 дьяк]
 * [3 жовту] ← [7 жовт]
 * [1 інвентаря] ← [1 інвентар][1 інвентарю]
 * [1 кабо] ← [1 Каб]
 * [1 кальдаса] ← [1 Кальдас]
 * [2 клеона] ← [1 Клеон]
 * [1 левинсона] ← [1 Левинсон]
 * [2 луїсом] ← [1 Лу́їс][36 Луїс]
 * [11 людовика] ← [5 Людовик]
 * [1 майоров] ← [4 Майор][67 майор][19 майора][2 майори][1 майорові][6 майором][1 майору][1 майорі]
 * [2 норте] ← [4 Норт]
 * [1 облили] ← [34 обл]
 * [3 орного] ← [11 Орн]
 * [1 орнсворлду] ← [1 Орнсворлд]
 * [1 пейтона] ← [1 Пейтон]
 * [1 переса] ← [4 Перес]
 * [1 плана] ← [16 План][1 Плани][2 Плану][59 план][2 планам][3 планами][6 планах][1 плане][18 плани][13 планом][40 плану][48 плані][18 планів]
 * [8 породами] ← [4 пород]
 * [2 потока] ← [1 По́ток][2 Поток]
 * [1 прайдами] ← [1 Прайд][4 прайд]
 * [1 противников] ← [2 Противник][1 Противники][3 противник][60 противника][5 противниками][8 противником][4 противнику][6 противників]
 * [8 проявив] ← [4 прояв][1 проявам][5 проявами][6 проявах][17 прояви][3 проявом][3 прояву][1 прояві][11 проявів]
 * [1 районная] ← [2 Райо́н][41 Район][14 Райони][5 райо́н][3 райо́ну][568 район][1 район́у][1 районам][19 районами][55 районах][1 районе][32 райони][1 районові][18 районом][790 району][560 районі][66 районів]
 * [1 районне] ← [2 Райо́н][41 Район][14 Райони][5 райо́н][3 райо́ну][568 район][1 район́у][1 районам][19 районами][55 районах][1 районе][32 райони][1 районові][18 районом][790 району][560 районі][66 районів]
 * [2 рапіду] ← [1 Рапід][1 рапід]
 * [23 симона] ← [10 Симон]
 * [1 скита] ← [2 скит][1 скиту][1 скитів]
 * [1 славянской] ← [1 Славянск]
 * [4 спайру] ← [14 Спайр]
 * [1 спенсера] ← [4 Спенсер]
 * [3 хаен] ← [9 Ха][1 ха]
 * [1 хонам] ← [2 Хон]
 * [2 шеремета] ← [1 Шеремет]

New Ukrainian Morfologik Analyzer Only
Looking only at the output of the new Ukrainian analyzer, there are a few red flags, but many fewer than in the Polish analysis, and many turned out to be perfectly fine.

Unexpected groupings
There are a handful of "unexpected groupings", where the groups have no beginning or ending substrings in common. (I'm using "beginning and ending substrings" to keep them distinct from the morphological affix meaning of prefix and suffix.) The unexpected groupings are below, with notes. Each word is preceded by its frequency within the sample.

English Wiktionary—my most favorite dictionary ever—seems a little light on Ukrainian words, so I'm also using Google Translate and the CyberMova dictionaries when no Wiktionary entry is available.
 * [2 Вівці][1 вівця][9 вівці][14 овець] — овець looks to be an irregular genitive plural
 * [4 Вісь][3 Осі][4 віссю][9 вісь][4 осей][4 осями][1 осях][42 осі] — Google translates all of these as axis or axes
 * [1 Жене][2 гнали][1 жене] — These all have a translation of drive, and CyberMova says жене is a form of гнали.
 * [3 І́гор][126 Ігор][10 Іграх][10 Ігри][1 ГРИ][36 Гра][1 Гро][5 Гру][1 Грі][87 гра][211 гри][59 грою][99 гру][94 грі][136 ігор][6 іграми][93 іграх][61 ігри] — an overlap of the Ігор ("Igor") and forms of гра ("game"), including genitive plural ігор
 * [2 вівса][1 овес] - these seem to be different words for "oats"
 * [2 Ока][8 Око][19 Очі][1 вічі][9 ока][14 око][3 оком][1 оку][1 очам][17 очах][25 очей][15 очима][20 очі] — вічі is apparently an alternative form of the accusative plural for око ("eye")
 * [1 Стели][4 стели][3 стелю][3 шлем] — CyberMova has all of these as forms of слати
 * [1 Візьмемо][4 візьме][2 візьмуть][11 узяв][1 узяла][4 узяли][2 узяти][2 узято] — all forms of узяти ("to take"), which is just irregular, as many common verbs are.

Large groupings
In the Polish analysis, there were some ridiculously large groupings that turned out to be instances of the statistical stemmer going off the rails a bit. For Ukrainian, there were no ridiculously large groupings, and only one that was notably larger than others: мати, which turns out to be both the word for "mother" and the verb "to have," so it isn't surprising that it has both noun forms and verb forms.
 * [1 має][1 ма́тері][26 мав][1 май][87 мала][1 мала́][2 мали][12 мало][31 матері][43 мати][1 матиме][4 матір][2 матір'ю][10 мають][75 має][1 маємо][1 маєш][265 мав][194 мала][228 мали][128 мало][1 матерями][66 матері][3 матерів][188 мати][9 матиме][1 матимете][1 матимуть][24 матір][17 матір'ю][2 маю][480 мають][1013 має][13 маємо]

Random sample of groupings
Below is a random sample of 100 groupings made by the new Ukrainian analyzer, for review by someone who speaks Ukrainian. In general, each group should be forms of the same word, or forms of two words that overlap (like мати ("mother" / "to have"). Each word is preceded by its frequency within the sample.
 * [5 Адвокат][2 Адвокати][22 адвокат][6 адвоката][1 адвокатам][1 адвокати][9 адвокатом][2 адвокатів]
 * [1 Атеїстична][1 атеїстичної][1 атеїстичну]
 * [1 бенедиктинця][4 бенедиктинців]
 * [1 богоділень][1 богодільні]
 * [1 бродячих][1 бродячого]
 * [1 Вертольоти][2 Вертоліт][4 вертольота][1 вертольотами][2 вертольотах][7 вертольоти][1 вертольоті][5 вертольотів][13 вертоліт]
 * [1 видобути][2 видобуто]
 * [1 вівторка][3 вівторок]
 * [4 Відбору][2 Відборі][2 Відбір][12 відбору][2 відборі][1 відборів][3 відбір]
 * [7 віддана][2 віддане][10 відданий][1 відданих][2 відданого][1 відданої][5 віддані]
 * [2 Відоме][2 Відомим][32 відоме][40 відомим][74 відомого][13 відомому]
 * [1 вузенька][1 вузенькі]
 * [2 гідродинаміка][1 гідродинаміки]
 * [1 Губернаторство][1 губернаторств][1 губернаторства][1 губернаторство][1 губернаторстві]
 * [1 Дебати][1 дебатах][5 дебати][4 дебатів]
 * [1 Дністрового][5 Дністрової]
 * [1 ДУК][3 дука][5 дуки][1 дукою]
 * [1 Дядько][8 дядька][1 дядьки][16 дядько][1 дядькові][3 дядьком]
 * [2 ЖДУ][1 Жди][1 ждала][1 жди]
 * [1 Живите][1 живила][7 живим][3 живить]
 * [1 Забійна][1 Забійний][1 забійний]
 * [3 Залишався][1 Залишається][36 залишався][5 залишалась][15 залишалася][4 залишались][9 залишалися][4 залишалось][8 залишалося][1 залишатимуться][4 залишатись][8 залишатися][21 залишаються][76 залишається]
 * [3 занурюються][2 занурюється]
 * [1 Затон][1 затон][1 затони]
 * [8 Збірка][3 Збірки][2 Збірку][1 Збірці][42 збірка][3 збірками][7 збірках][46 збірки][27 збірку][20 збірок][6 збірці]
 * [1 Здобуття][39 здобуття][2 здобутті]
 * [4 Земська][1 Земського][4 Земської][1 Земську][2 земська][6 земське][8 земський][2 земським][1 земськими][3 земських][4 земського][1 земському][1 земською][3 земської][2 земську][2 земські][3 земській]
 * [2 зімкнення][1 зімкненням]
 * [1 зривай][1 зриває]
 * [3 іграшки][2 іграшок]
 * [1 Ільницьке][2 Ільницький][2 Ільницького]
 * [4 Ініціатива][1 Ініціативу][4 ініціатив][2 ініціатива][1 ініціативами][58 ініціативи][26 ініціативою][7 ініціативу][1 ініціативі]
 * [2 Каліфорнійський][4 Каліфорнійського][4 Каліфорнійському][1 Каліфорнійської][1 каліфорні́йський][1 каліфорнійська][2 каліфорнійський][1 каліфорнійської]
 * [5 Кваліфікований][1 кваліфікована][1 кваліфікований][3 кваліфікованих][1 кваліфікованою][2 кваліфіковану]
 * [1 кібератак][1 кібератаку][1 кібератаці]
 * [4 Комп'ютер][1 Комп'ютери][23 комп'ютер][16 комп'ютера][1 комп'ютерами][9 комп'ютерах][12 комп'ютери][3 комп'ютером][8 комп'ютері][18 комп'ютерів]
 * [1 Константи][1 констант][2 константа]
 * [1 Конюх][1 Конюхові]
 * [1 кременевий][1 кременеві]
 * [2 кримськотатарського][1 кримськотатарської][1 кримськотатарській]
 * [1 Лабети][1 лабетах]
 * [1 Легка][1 Легка́][2 Легкий][1 Легкого][2 Легкі][12 легка][2 легке][10 легкий][6 легким][1 легкими][11 легких][6 легкого][1 легкому][3 легкою][36 легкої][4 легку][5 легкі][3 легкій]
 * [1 лімфа][1 лімфу]
 * [5 Лірика][2 лірик][3 лірика][6 лірики][1 лірику]
 * [1 ліхтарики][1 ліхтариком]
 * [4 М'ясо][30 м'яса][37 м'ясо][10 м'ясом]
 * [1 Медіальна][1 медіальний][2 медіального][1 медіальну][1 медіальній]
 * [2 мейнстрім][1 мейнстріму]
 * [7 нагромадження][2 нагромадженням][1 нагромадженнях][1 нагромадженні]
 * [6 надійшла][6 надійшли][3 надійшло][5 надійшов]
 * [1 необробленому][1 необроблені]
 * [1 Неоліт][10 неоліт][4 неоліту][1 неоліті]
 * [1 нехарчових][1 нехарчові]
 * [1 нічліжний][2 нічліжних]
 * [1 обирана][1 обираних]
 * [1 однотонним][1 однотонними][1 однотонні]
 * [1 окружності][1 окружність]
 * [1 опустила][1 опустити]
 * [2 орхідея][2 орхідеї]
 * [1 осадка][1 осадку]
 * [3 Панна][1 Панно][8 панно]
 * [19 ПАР][2 Пара][1 Пари][1 Пару][7 Парі][27 пар][20 пара][5 парами][5 парах][35 пари][1 паро][2 парою][8 пару][14 парі]
 * [1 певності][1 певністю]
 * [1 Пенсіонери][3 пенсіонер][279 пенсіонерами][3 пенсіонери][8 пенсіонерів]
 * [1 передовика][1 передовиків]
 * [12 перенесено][1 перенесла][11 перенесли][13 перенести][11 переніс]
 * [1 переносилися][7 переноситься][3 переносяться]
 * [1 перпендикулярами][1 перпендикуляру]
 * [1 підстилкою][2 підстилці]
 * [127 Площа][1 Площею][1 Площі][1 пло́ща][10 площ][147 площа][1 площами][7 площах][58 площею][46 площу][136 площі]
 * [21 покриття][10 покриттям][1 покриттями][1 покритті][2 покриттів]
 * [31 Походив][2 Походила][1 Походили][6 Походить][8 походи][16 походив][10 походила][4 походили][2 походити][112 походить][13 походять]
 * [1 Працююче][1 працюючим][2 працюючими][2 працюючих][1 працюючому]
 * [6 приєднана][1 приєднаний][1 приєднаного][1 приєднаному][1 приєднану][6 приєднані]
 * [17 присуджено][2 присудив][1 присудила][1 присудити]
 * [2 пропагандистських][1 пропагандистському][2 пропагандистської][3 пропагандистську][2 пропагандистські]
 * [1 Раптове][1 раптова][1 раптове][3 раптовий][1 раптовим][1 раптових][1 раптовому][1 раптовою][1 раптової][1 раптовій]
 * [1 Реорганізований][5 реорганізована][1 реорганізоване][4 реорганізований][1 реорганізованого][2 реорганізовану]
 * [1 репаративний][1 репаративних]
 * [1 Родинна][4 Родинне][1 Родинні][1 родинна][3 родинне][2 родинний][1 родинним][2 родинними][7 родинних][5 родинного][5 родинному][1 родинною][4 родинної][3 родинні][1 родинній]
 * [1 Роз'єм][5 роз'єм][1 роз'ємами][4 роз'єму][1 роз'ємів]
 * [4 Розвинена][1 Розвинене][4 Розвинені][3 розвинена][9 розвинене][7 розвинений][5 розвиненим][3 розвиненими][7 розвинених][5 розвиненою][2 розвиненої][2 розвинену][13 розвинені]
 * [1 розігрів][3 розігріві]
 * [1 розлючений][1 розлючені]
 * [2 систематика][2 систематики][2 систематиці]
 * [1 сірникова][2 сірникової][1 сірниковій]
 * [1 скасовуються][1 скасовується]
 * [1 скупчились][1 скупчилися]
 * [1 спритності][3 спритність]
 * [3 стриманості][1 стриманість][1 стриманістю]
 * [2 Сьома][1 Сьомої][2 сьома][1 сьомою][1 сьомої][1 сьомій]
 * [2 Туз][5 туза][1 тузами]
 * [1 утеч][1 утечу]
 * [7 Францію][92 Франція][641 Франції]
 * [3 фундатор][3 фундатора][2 фундатори][3 фундатором][3 фундаторів]
 * [1 Хазяїн][5 хазяїн][9 хазяїна][3 хазяїном]
 * [2 Холдингова][1 холдингові]
 * [6 чартах][7 чарти][4 чартів]
 * [2 чварам][2 чвари]
 * [1 чіпайте][2 чіпали][4 чіпати]

Recommendations & Plan

 * Contingent on confirmation from a review by native speakers, it looks like we should deploy the Ukrainian analyzer. it does have some shortcomings, but overall it should be better than using an analyzer for the wrong language.


 * Unfortunately, the Ukrainian Analyzer doesn't expose any internals (like a stemmer/lemmatizer or stopwords), so it can only be used as-is. We can't, for example, enable ICU folding or word break helper. (Well, you can configure them, but nothing happens.) There are config files inside the jar files for character mappings and stopwords, which could be modified and the jars re-jarred. That's doable, but potentially brittle or a maintenance burden (e.g., easily forgotten about and overwritten by a future plugin update; or forked and not compatible with a future version of ES). Alternatively, we could try to expose those functions and submit upstream patches.

Next Steps
 * On a side note, it'd be nice to add a generic filter to deal with zero-width non-breaking spaces (U+FEFF), soft hyphens (U+00AD), and left-to-right/right-to-left markers (U+200E / U+200F), but it seems that character filters (like word_break_helper) don't work with built in analyzers. So, it would require unpacking all the existing analyzers; that's doable in a day without testing, but would take a lot more time with decent testing for every analyzer.
 * ✓ Get some Ukrainian speakers to look at the splits, collisions, and unexpected results we've got here.—Done (see T160106)
 * If everything looks good, deploy! In progress: Config to be deployed as T160106, plugin/reindex is T162055.
 * If it's not a clear improvement, install the Ukrainian analyzer on the RelForge servers, set up a new index with the new analyzer and let people try a live version.
 * Remove the Ukrainian-specific bits from the Russian analysis chain. In progress as part of T160106.
 * Update Vagrant/Puppet config to make the plugin available.