Skip to content

Conversation

@ojwb
Copy link
Member

@ojwb ojwb commented Aug 31, 2021

This has been on the web site since 2012, but never actually got
included in the code distribution.

Points to resolve:

  • R1 and RV
  • č suffix in snowball vs če in Java (Snowball seems to have copied typo in Java comment)
  • čtí/ští in Java vs čté/šté in Snowball (again seems to be due to Java comment typo)
  • len- 2 instead of len- 3 for Java ště/šti/ští check. Seems fairly clear improvement.
  • Snowball version removes extra character before calling palatalise.
  • Snowball version doesn't remove final character by default if palatalise doesn't otherwise match.
  • if do_case doesn't make a replacement then do_possessive won't get called, but in the java code, removePossessives is always called. Merge Czech stemmer #151 (comment)
  • Java version leaves the first character of a removed suffix behind when calling palatalise except for -es/-ém/-ím
  • setlimit tomark p1 for ([substring]) vs [substring] R1
  • Look into addressing unhelpful splits from palatalise change
  • -ětem isn't listed by https://en.wikipedia.org/wiki/Czech_declension but seems to be valid from e.g. https://en.wiktionary.org/wiki/hrab%C4%9B https://en.wiktionary.org/wiki/markrab%C4%9B and https://en.wiktionary.org/wiki/ml%C3%A1d%C4%9B
  • -os, -es, -iho, -imu aren't listed by https://en.wikipedia.org/wiki/Czech_declension
  • -ich seems to only be a suffix for two pronouns
  • Should we also remove -ima? Probably not.
  • Should we also remove -ímu (with a diacritic on the i)? Yes.
  • Java light stemmer removes -ěte and -ěti while the aggressive stemmer removes -ete and -eti (no caron on the e). The snowball implementation follows the light stemmer. The older version of the light stemmer listed in the original paper removes all four suffixes. Analysis in Merge Czech stemmer #151 (comment) suggests maybe to leave as-is? Probably this was trying to make the stemmer partly ignore diacritics, see next point.
  • Change stemmer to remove diacritics?
  • { desce desk deska deskami deskou desková deskové deskový deskových desku desky deskách } + { dešti deštích deště } - seems to be conflating "plate" and "rain"; simple tests suggest this (and numerous other conflations due to palatalise) are fixable my imposing some sort of region check on the palatalise step, but need to experiment to determine what region definition is appropriate (and whether it should the same for all palatalise replacements)
  • červencem ("July") stems to červenk but so does červenka ("robin"); also some other forms of "July" such as červencový stem to červenc. There is an inherent ambiguity here as července is a form of both words, but it's bad that we make this worse.
  • Review restoring more limited versions of the removed palatalise rules.

@jan-zajic
Copy link

@ojwb, @jimregan Hi Guys, what is state of this PR? can we help with it somehow? Another implementation of czech snowball stemmer can be found here: https://www.fit.vut.cz/research/product/133/.en (GNU GPL)

@ojwb
Copy link
Member Author

ojwb commented Oct 6, 2023

@jan-zajic It needs the points above resolving, but I think that's just a case of me finding the time to. I'm trying to clear the backlog of Snowball tickets, so hopefully soon.

We couldn't really merge a GNU GPL stemmer as currently Snowball has a BSD-style licence - moving to a mixed licence situation would make things harder to understand and manage for users.

From a quick look this other stemmer appears to the usual R1 definition (which is good), but it is quite a lot more complex (which is bad unless it does a better job as a result).

Do you know how it compares in effectiveness to the one in this PR? If it's better, do you know if the copyright holders might consider relicensing it for inclusion in Snowball releases?

@ojwb ojwb force-pushed the add-czech branch 2 times, most recently from fcc1b03 to 3afee58 Compare October 30, 2023 02:49
@ojwb
Copy link
Member Author

ojwb commented Oct 31, 2023

I had a bit more of a look at the GPL snowball stemmer. I noticed the end_double routine undoubles the exact same consonant pairs that the original English Porter stemmer does, which makes we wonder - it's certainly possible exactly the same constant pairs would need undoubling in two languages, but it seems a bit unlikely when the languages aren't closely related ones.

However fundamentally we can't use this implementation without an agreement to relicense. The source download has Licence.txt which says "Copyright (C) 2010 David Hellebrand" but I failed to locate David or the co-author of the paper. Unless someone can manage to contact them them this just isn't an option.

So I went back to looking at the Dolamic stemmer.

Comparing the snowball implementation with the Java implementations http://members.unine.ch/jacques.savoy/clef/CzechStemmerLight.txt and http://members.unine.ch/jacques.savoy/clef/CzechStemmerAggressive.txt I spotted some inconsistencies (code snippets in same snowball/light/aggressive) order:

      'ci' 'ce' '{c^}i' '{c^}'
      (<- 'k')

vs

		if( buffer.substring( len- 2 ,len).equals("ci")||
		    buffer.substring( len- 2 ,len).equals("ce")||		
		    buffer.substring( len- 2 ,len).equals("\u010di")||  //-či
		    buffer.substring( len- 2 ,len).equals("\u010de")){  //-č 
				
		    buffer.replace(len- 2 ,len, "k");
		    return;
		}

vs

		if( buffer.substring( len- 2 ,len).equals("ci")||
		     buffer.substring( len- 2 ,len).equals("ce")||		
		     buffer.substring( len- 2 ,len).equals("\u010di")||      //-či
		     buffer.substring( len- 2 ,len).equals("\u010de")){   //-če
				
		     buffer.replace(len- 2 ,len, "k");
		     return;
		}

Note that the light stemmer comment says but the code actually checks for -če (the aggressive code is the same, but the comment matches there). This "palatalise" step doesn't seem to be in the original paper, but my guess is that the snowball code followed the incorrect comment here and is wrong.

There's another inconsistency in palatalise:

      '{c^}t{e^}' '{c^}ti' '{c^}t{e'}'
      (<- 'ck')
      '{s^}t{e^}' '{s^}ti' '{s^}t{e'}'
      (<- 'sk')

vs

		if( buffer.substring( len- 3 ,len).equals("\u010dt\u011b")||  //-čtě
		    buffer.substring( len- 3 ,len).equals("\u010dti")||       //-čti  
		    buffer.substring( len- 3 ,len).equals("\u010dt\u00ed")){  //-čté 
						
		    buffer.replace(len- 3 ,len, "ck");
		    return;
		}
		if( buffer.substring( len- 2 ,len).equals("\u0161t\u011b")||  //-ště
		    buffer.substring( len- 2 ,len).equals("\u0161ti")||       //-šti
		    buffer.substring( len- 2 ,len).equals("\u0161t\u00ed")){  //-šté
						
		    buffer.replace(len- 2 ,len, "sk");
		    return;
		}

vs

		if( buffer.substring( len- 3 ,len).equals("\u010dt\u011b")||     //-čtě
		     buffer.substring( len- 3 ,len).equals("\u010dti")||   //-čti
		     buffer.substring( len- 3 ,len).equals("\u010dt\u00ed")){   //-čtí
						
		     buffer.replace(len- 3 ,len, "ck");
		     return;
		}
		if( buffer.substring( len- 2 ,len).equals("\u0161t\u011b")||   //-ště
		    buffer.substring( len- 2 ,len).equals("\u0161ti")||   //-šti
		     buffer.substring( len- 2 ,len).equals("\u0161t\u00ed")){  //-ští
						
		     buffer.replace(len- 2 ,len, "sk");
		     return;
		}

Here the comments -čté and -šté in the light version don't match the code since \u00ed is actually "í" not "é". Again the aggressive version has correct comments and the snowball version follows the comments in the light version rather than the code, and again I suspect that's wrong.

I tried changing the first case in the snowball code and the differences look plausible but unfortunately I don't know the Czech language to a useful extent. I didn't try the second case yet.

@jimregan @jan-zajic Any thoughts?

@ojwb
Copy link
Member Author

ojwb commented Nov 1, 2023

Here's a scripted analysis of the effects of the various changes to palatalise I covered above:

  • A total of 234 words changed stem
  • 131 words changed stem but aren't interesting
  • 48 merges of groups of stems:
    { chladiče } + { chladič }
    { hasiče hasiči } + { hasičů }
    { hlídače } + { hlídač }
    { holiči } + { holič }
    { hráče hráči hráčí hráčích } + { hráč hráčů }
    { klíče klíči klíčí } + { klíč klíčovou klíčová klíčové klíčového klíčový klíčových klíčovým klíčovými klíčů }
    { koláče } + { koláč }
    { kotouče } + { kotouč kotoučové }
    { lupiče lupiči } + { lupič lupičů }
    { měniče } + { měnič }
    { nosiče nosiči nosičích } + { nosič nosičů }
    { ovladače ovladači } + { ovladač ovladačů }
    { označí } + { označovat }
    { pláče } + { pláč }
    { polovodiče } + { polovodičové polovodičových polovodičů }
    { posluchače posluchači } + { posluchač posluchačů }
    { potápěče potápěči } + { potápěčů }
    { počítače počítači počítačích } + { počítač počítačovou počítačová počítačové počítačového počítačovém počítačový počítačových počítačovým počítačově počítačů }
    { prarodiče prarodiči } + { prarodičů }
    { prohlížeče prohlížeči } + { prohlížeč prohlížečů }
    { přehrávače } + { přehrávač přehrávačů }
    { překladače } + { překladač }
    { přepínače } + { přepínač }
    { přijímače } + { přijímač přijímačů }
    { přivaděče } + { přivaděč }
    { rodiče rodiči rodičích } + { rodič rodičů }
    { sběrače sběrači } + { sběrač sběračů }
    { snímače } + { snímač snímačů }
    { spoluhráče spoluhráči } + { spoluhráč spoluhráčů }
    { spotřebiče } + { spotřebičů }
    { stíhače stíhači } + { stíhač stíhačů }
    { tahače } + { tahač tahačů }
    { tlumiče tlumiči } + { tlumič }
    { uchazeče uchazeči } + { uchazeč uchazečů }
    { urychlovače } + { urychlovač }
    { vodiče vodiči } + { vodič vodičů }
    { voliče voliči } + { volič voličů }
    { vrhače } + { vrhač }
    { vyhledávače } + { vyhledávač }
    { vypravěče } + { vypravěč }
    { vysílače vysílači } + { vysílač vysílačů }
    { vyznavači } + { vyznavačů }
    { zaměřovače } + { zaměřovač }
    { zesilovače } + { zesilovač zesilovačů }
    { čediče } + { čedič čedičové }
    { řadiče } + { řadič }
    { řidiče řidiči } + { řidič řidičů }
    { žluči } + { žlučových }
  • 17 splits of groups of stems:
    { buk bukového bukový bukových | bučina bučiny }
    { divokou divoká divoké divokého divoký divokých divokým divokými | divočiny divočině }
    { drak draka draku draky draků | dračí }
    { křik | křičí }
    { předka předkové předky předků | předčí }
    { roztok roztoku roztoky roztoků | roztoči }
    { skok skoku skoky skoků | skočí }
    { skákat | skáče }
    { smrk smrkového smrkový smrkových | smrčiny }
    { stoka stoky | stočí }
    { svědka svědkové svědky svědků | svědčí }
    { tlak tlakovou tlaková tlakové tlakových tlaku tlaky tlaků | tlačí }
    { velikou veliká veliké velikého veliký velikých velikým | veličina veličinami veličinou veličinu veličiny }
    { vysoko vysokou vysoká vysoké vysokého vysokém vysokému vysoký vysokých vysokým vysokými | vysočina vysočinu vysočiny vysočině }
    { výrok výroku výroky výroků | výročí výročím }
    { znak znakovou znaková znakové znakového znakový znakových znaku znaky znaků | značí }
    { zvenku | zvenčí }
  • 3 words moving between stem groups:

    compare

    Cutting and pasting into google translate suggests:

    • all the merges seem to be combining groups where the meaning is the same (good)
    • some of the splits are separating words which have different (or not very related) meanings e.g. "předka" (ancestor) vs "předčí" (surpasses) (good)
    • some of the splits may be reasonable either way, e.g. "divokou" (wild) vs "divočiny" (wilderness) (ok?)
    • but some of the splits separate words which have the same meaning, e.g. "drak" and "dračí" both mean "dragon" (bad)
    • the more complex change in the image seems to be improving a split between twins vs deuces/twos (good)

    Overall it seems like an improvement from this, but I realise google translate is imperfect.

    Also maybe there's scope for tweaking to reduce the number of undesirable splits.

@ojwb
Copy link
Member Author

ojwb commented Nov 1, 2023

There's one remaining inconsistency I've spotted, this one's in do_case.

Here the light stemmer removes -ěte and -ěti while the aggressive stemmer removes -ete and -eti (no caron on the e). The snowball implementation follows the light stemmer.

The older version of the light stemmer listed in the original paper removes all four suffixes.

Changing to removing all 4 gives:

  • A total of 27 words changed stem
  • 8 words changed stem but aren't interesting
  • 6 merges of groups of stems:
    { dostane dostanou } + { dostanete }
    { knížat knížata knížaty knížatům kníže } + { knížete knížeti }
    { nalezne naleznou } + { naleznete }
    { prasat prasata prase } + { prasete }
    { velkokníže } + { velkoknížete }
    { zvířat zvířata zvířatech zvířaty zvířatům zvíře } + { zvířete zvířeti }
  • 11 splits of groups of stems:
    { atlet atletů | atleti }
    { deset desetina desetinu desetiny | deseti }
    { dvaceti | dvacet }
    { interpreti | interpret interpreta interpretovat interprety interpretů }
    { kvetou | kvete }
    { pečetí pečetě | pečeti }
    { pětadvaceti | pětadvacet }
    { rukojeti | rukojetí }
    { skřety skřetů | skřeti }
    { třiceti | třicet }
    { čtyřiceti | čtyřicet }

    Pasting into google translate suggests the merges are all good and the splits all bad, except { interpreti | interpret interpreta interpretovat interprety interpretů } where "interpreti" apparently means "performers" and the rest mean "interpret(er(s))". Changing to remove the suffixes the aggressive version does gives a similarly mixed bag.

    So based on that it seems this is better left as is, but I'd welcome some insights backed by actual knowledge of the Czech language.

ojwb added a commit to ojwb/snowball that referenced this pull request Nov 2, 2023
The Java code removes this ending but it was missing from the Snowball
version.  Looking at the changes resulting from this, it seems a clear
improvement so I've concluded it was an accidental omission.

See snowballstem#151
@ojwb
Copy link
Member Author

ojwb commented Nov 2, 2023

Three more notes:

Comparing the code I noticed that -ům is removed by both the java versions but not the snowball version. I tried adding it, and looking at the changes resulting from this, it seems a clear improvement so I've committed that change (e137bc2). That seems to be the only omission.

I also noticed that there's a bug in the Java versions in one group of palatalise rules:

		if( buffer.substring( len- 2 ,len).equals("\u0161t\u011b")||   //-ště
		    buffer.substring( len- 2 ,len).equals("\u0161ti")||   //-šti
		     buffer.substring( len- 2 ,len).equals("\u0161t\u00ed")){  //-ští
						
		     buffer.replace(len- 2 ,len, "sk");
		     return;
		}

Here we check buffer.substring( len- 2 ,len) which has length 2 against string literals which are all length 3 so it seems these rules can never match. Presumably the intent must have been that all instances of len- 2 in this code snippet should have been len- 3, though any testing done on at least this version of the algorithm will have effectively been without these rules.

The final thing I noticed is that the Snowball version applies the palatalise step rather differently to the Java versions.

E.g. consider -in removal in removePossessives:

                        if( buffer.substring( len- 2 ,len).equals("in")){
                                
                            buffer.delete( len- 1 , len);
                            palatalise(buffer);
                            return;
                        }

This changes -in to -i and then calls palatalise. In the snowball code we instead completely remove -in:

      'in'
      (
        delete
        try palatalise
      )

Almost every case is handled like this in snowball, except for em where we leave the e like the Java versions do:

      'em'
      (
        <- 'e'
        try palatalise
      )

The palatalise code is effectively the same for both Java and snowball versions (aside from the inconsistencies I noted above) except that the Java versions remove the last character if none of the suffixes checked for match:

                buffer.delete( len- 1 , len);

That at least makes things more similar, but fundamentally it seems the palatalise step in snowball will be much less effective as the final character will often have already been removed.

The code in the paper (which seems pseudo-code for an earlier version of the light stemmer) removes the vowels like the snowball version does, then unconditionally performs Normalize (instead of palatalise performed only after certain removed suffixes) which checks for e.g. čt instead of čtě/čti/čté. Presumably that evolved into the current Java versions to reduce false positives.

(This also may mean that the conclusions in the paper about the light vs aggressive stemmers may not entirely apply to the Java versions we have access to, but in the absence of a comparison of the Java versions going with the light stemmer still seems sensible.)

@ojwb
Copy link
Member Author

ojwb commented Nov 3, 2023

A further difference is that in the snowball implementation if do_case doesn't make a replacement then do_possessive won't get called, but in the java code, removePossessives is always called.

It looks like this could be a deliberate change, as the snowball code does try palatalise which means that it doesn't matter whether palatalise makes a replacement.

However, the cursor doesn't get reset before do_possessive so for example an input of proteinem gives a stem of protee - do_possessive starts with the cursor before the final e rather than at the end of the word, and removes in when it's not actually a suffix.

We can fix just the latter with test do_case, or we can fix both with do do_case (if this difference from the java version isn't intentional).

@hauktoma
Copy link

hauktoma commented Aug 1, 2024

Any progress on this issue?

As we understand there is some kind of analysis comparison between two implementations -- one of which cannot be used anyways because of licensing and there are some tradeoffs on both sides? Maybe the original (simpler?) contributed algorithm (with acceptable license) is good enough?

Can we somehow help to move this forward? I reviewed the issues above and at this moment they are too technical for me (not familiar with stemming problem domain), but maybe I could provide a feedback on something as a Czech speaker.

@ojwb
Copy link
Member Author

ojwb commented Aug 8, 2024

Any progress on this issue?

Progress stalled on needing input from someone who knows Czech reasonably well. I thought I'd found someone who could help (this was probably late 2023/early 2024) but they never got back to me and I failed to chase it up. If you're a Czech speaker and wanting to get this resolved, that would definitely be useful.

As we understand there is some kind of analysis comparison between two implementations -- one of which cannot be used anyways because of licensing and there are some tradeoffs on both sides?

There is a GPL implementation of a different algorithm mentioned above, which indeed would need relicensing as Snowball uses a 3-clause BSD licence. That one would also need to be rewritten in Snowball as well as relicensed.

However the comparisons are against a Java implementation that's meant to be of the same algorithm (and this Java implementation is 2-clause BSD so compatible, see: http://members.unine.ch/jacques.savoy/clef/).

Maybe the original (simpler?) contributed algorithm (with acceptable license) is good enough?

We don't want to just merge something with unresolved issues because that's likely to need significant changes later, and those are disruptive in typical users of these stemmers (because you need to rebuild your whole search database).

Can we somehow help to move this forward? I reviewed the issues above and at this moment they are too technical for me (not familiar with stemming problem domain), but maybe I could provide a feedback on something as a Czech speaker.

I'll need to review the discussion as it's been 9 months, but I think we should be able to resolve this together.

@hauktoma
Copy link

Ok thanks for clarification. Count me in if you need help.

@ojwb
Copy link
Member Author

ojwb commented Aug 15, 2024

@hauktoma Great. There are a few points to resolve, so I'll cover one at a time.

The first question is really about syllables in Czech.

I'll try to give some background to what we're doing and why. If you don't follow please say and I can clarify. (I'm also happy to do this on chat or a video or phone call if you think it would be easy to do it interactively.)

We want to avoid the stemming algorithm removing suffixes too aggressively and mapping words to the same stem which aren't actually related (or are somewhat related but really have too different a meaning).

Most of the Snowball stemmers make use of simple idea to help this which is to define regions at the end of the word from which a suffix can be removed. For most languages these are defining by counting the number of spans of vowel, then of non-vowel, etc - https://snowballstem.org/texts/r1r2.html shows some examples. As well as R1 and R2 there's also an RV for some languages which that page doesn't mention.

This is essentially approximating counting syllables, while the original Czech stemming algorithm this implementation is based on used a cruder character-counting approach instead. In his original Snowball implementation jimoregan essentially retrofitted use of R1 and RV which I think was a good idea.

However it seems in Czech that clusters of just consonants can form a syllable, so probably our R1 and RV definitions for Czech ought to take that into account. See my comment above for what led me to this conclusion, but the key point is this quote:

Sonorants /r/, /l/ become syllabic between two consonants or after a consonant at the end of a word.

And the actual question is for the purposes of determining these regions, should we consider r and l preceded by a consonant and not followed by a vowel as effectively implying a vowel?

And if so, should m and n be treated in the same way?

@hauktoma
Copy link

To be honest I am not entirely sure about the idea handling the r, l, m, n as vowels if they are preceded by a consonant and not followed by a vowel.

I'll try to sum the points up here and then provide examples at the end:

  1. Treating r, l seems to be quite reliable for determining where the syllables are, but it sometimes breaks or changes stem/meaning -> even if this works to detect where the syllables are, the overall effect may be negative because meaning is changed (and should not have been)
  2. For treating m, n as vowels
    1. I found almost no examples when it is useful but plenty examples when this break even the syllable detection and also meaning
    2. It often clashes with the e.g. r (both m and r are present in the word next to each other) so that some kind of resolution logic might be needed for this
    3. By doing some digging on the Czech internet and linguistics materials, I got the impression that this is often not even mentioned as option that m/n can be syllabic consonant -- which may mean that having m, n as syllabic consonant is a rare phenomenon statistically. The r and l get plenty of mentions.

My betting/statistical impression is that implementing this may have more negative effect than positive one. Especially for the m and n which just seem to have too many negative cases. The r and l may be viable, not sure statistically however.

@ojwb can you please review my reasoning about this and provide feedback whether it is correct? If you think this may be worth a bit more investigating or that the examples provided below are not good enough to make a decision, I can try to consult some colleagues or dig some more formal materials about this.

@ojwb maybe one quick question and clarification: you mentioned R1, which means that by default the stem approximation default algorithm for language (unless specified otherwise by knowing language and implementing it differently) is to remove one suffix? R2 means remove two suffixes? Can the number of suffixes removed be variable under certain conditions? What is the setting/strategy for Czech (R1 or R2) and where it came from?

Note: have no problem with discussing this real-time on some call but maybe keep it as an option when we hit wall on something or some complex clarification will be needed. As a total layman in stemming/linguistics I am not sure if I would be able to have a real-time conversation on this topic. But if you get feeling that explaining something would be too much trouble in written/async form, let's do it.

Example of more complex word for r

Particular more nasty example of word čtvrt (which is stem) and its variants (split into syllables):

  • čtvrt
  • čtvr-tit
  • roz-čtvr-tit
  • roz-čtvr-ce-ný

Without r as vowel in this case, the suffix for čtvr-tit would be t (the last letter) and therefore stem čtvrti which is bad. But when handling the r as vowel we get čtvrt and it as suffix which is good if I understand correctly -- čtvrt would be the stem.

Other good examples

Following examples are good (stem is first, variant(s) follow):

  • krm, kr-mit, na-kr-mit, kr-mě
  • vlk, vl-ko-va-tět
  • krk, kr-ko-vi-ce

Undesirable meaning change

It seems however that there are examples of words, where the syllables are detected properly but when applying the algorithm (remove e.g. one suffix) it would probably change meaning.

Carrot vs Wink

Having Czech word mrkev which is carrot in English:

  1. split by syllable is mr-kev
  2. by applying R1 we get stem mrk and suffix ev
  3. but the obtained stem mrk is actually another word wink in English
  4. -> we should not have split this, the mrkev is actually a stem

Hrnec

The word hrnec (pot in English) is actually a stem.

  1. split to syllables would be hr-nec
  2. hrn is nothing / no word in Czech -> we actually had the stem hrnec before applying suffix remove

The collision between r and m if we would consider them vowels

Short examples where m and r collide and the words should not be split at all (they are stem and single syllable)

  • smrk
  • šmrnc

Various other examples:

  • mrk-nout -> this has stem mrk which would be probably ok, because m is first letter, just mentioning as edge case
  • zmr-začit -> this is some kind of Czech edge case, not even sure what stem would be here, but it surely is not zmrz
  • zmra-zit -> also some edge case, the stem should be mraz
    • but aplying R1/R2 on m (because after r is a) we would end up with stem zmr (?) which is bad
  • od-mlou-vat -> this is a weird one, stem should be probably mlouv, not sure this will be some Czech edge case maybe
    • (guess) it is probably derived from mluvit and the stem is mluv so some kind of transformation is going on

@jan-zajic
Copy link

jan-zajic commented Aug 28, 2024

Hi @ojwb, @hauktoma,
I'm sorry that unfortunately I don't have much time for this topic, but I am still very interested in having support for the Czech language in the snowball project.

The current discussion in this thread is beyond my time and expertise, so I decided to try to contact and find experts from
the Czech academic environment.

I will try to reach people who could help more with this topic and I will let you know how it turned out.

I think that if there is support for the Czech language in Snowball, it must be done as best as possible, since the impact will be great on a large number of open source projects and solutions above them.

@ojwb
Copy link
Member Author

ojwb commented Aug 29, 2024

Thanks. I need to work through this in detail, but a couple of notes:

2. It often clashes with the e.g. r (both m and r are present in the word next to each other) so that some kind of resolution logic might be needed for this

I think we'd probably just do something like work left to right (or perhaps right to left if that turns out to work better) and if a consonant is determined to be a syllabic consonant then it would not be regarded as a consonant for the letter which follows.

@ojwb maybe one quick question and clarification: you mentioned R1, which means that by default the stem approximation default algorithm for language (unless specified otherwise by knowing language and implementing it differently) is to remove one suffix? R2 means remove two suffixes?

No, they're just different regions, and the region which is appropriate for each suffix is chosen based on considering the language's structure, and also empirically what seems to work better. It's typically better to lean towards being conservative in when to remove since overstemming is more problematic than understemming.

Can the number of suffixes removed be variable under certain conditions? What is the setting/strategy for Czech (R1 or R2) and where it came from?

There are often conditions on whether a particular suffix is removed, and there's often an order suffixes are considered in, so removing one suffix may expose another that can then be removed too.

I think jimregan came up with the current region setting for Czech, presumably based on the Java implementation's cruder character counts.

@ojwb
Copy link
Member Author

ojwb commented Sep 4, 2024

I think trying to resolve some of the simpler points above will help us resolve the others, as they're somewhat interconncted (if nothing else it'll be some progress!)

I also noticed that there's a bug in the Java versions in one group of palatalise rules:

		if( buffer.substring( len- 2 ,len).equals("\u0161t\u011b")||   //-ště
		    buffer.substring( len- 2 ,len).equals("\u0161ti")||   //-šti
		     buffer.substring( len- 2 ,len).equals("\u0161t\u00ed")){  //-ští
						
		     buffer.replace(len- 2 ,len, "sk");
		     return;
		}

Here we check buffer.substring( len- 2 ,len) which has length 2 against string literals which are all length 3 so it seems these rules can never match. Presumably the intent must have been that all instances of len- 2 in this code snippet should have been len- 3, though any testing done on at least this version of the algorithm will have effectively been without these rules.

I tried comparing the CzechStemmerLight java stemmer as downloaded and with this fix applied:

  • A total of 237 words changed stem
  • 117 words changed stem but aren't interesting
  • 52 merges of groups of stems:
    { amatérskou amatérsky amatérská amatérské amatérského amatérský amatérských amatérským amatérskými } + { amatérští }
    { arabsko arabskou arabsky arabská arabské arabského arabskému arabský arabských arabským arabskými } + { arabština arabštinu arabštiny arabštině arabští }
    { britsko britskou britská britské britského britskému britský britských britským britskými } + { britští }
    { bulharskou bulharsky bulharská bulharské bulharského bulharský bulharských bulharským } + { bulharštiny }
    { císařsko císařskou císařská císařské císařského císařskému císařský císařských císařským císařskými } + { císařští }
    { desce desk deska deskami deskou desková deskové deskový deskových desku desky deskách } + { dešti deštích deště }
    { dánsko dánskou dánsky dánská dánské dánského dánskému dánský dánských dánským } + { dánštiny dánštině }
    { egyptsko egyptskou egyptská egyptské egyptského egyptský egyptských egyptským egyptskými } + { egyptštině }
    { evropskou evropsky evropská evropské evropského evropskému evropský evropských evropským evropskými } + { evropští }
    { finsko finskou finsky finská finské finského finský finských finským } + { finština finštiny finštině }
    { francouzsko francouzskou francouzsky francouzská francouzské francouzského francouzskému francouzský francouzských francouzským francouzskými } + { francouzština francouzštinu francouzštiny francouzštině francouzští }
    { hebrejsky hebrejské hebrejského hebrejský hebrejských hebrejským } + { hebrejština hebrejštinu hebrejštiny hebrejštině }
    { hlediska hledisko } + { hledišti hlediště }
    { irskou irsky irská irské irského irský irských irským irskými } + { irští }
    { italsko italskou italsky italská italské italského italskému italský italských italským italskými } + { italština italštinu italštiny italštině italští }
    { izraelsko izraelskou izraelská izraelské izraelského izraelskému izraelský izraelských izraelským izraelskými } + { izraelští }
    { japonsko japonskou japonsky japonská japonské japonského japonskému japonský japonských japonským japonskými } + { japonštiny japonštině japonští }
    { královskou královská královské královského královskému královský královských královským královskými } + { královští }
    { křesťansko křesťanskou křesťansky křesťanská křesťanské křesťanského křesťanskému křesťanský křesťanských křesťanským křesťanskými } + { křesťanští }
    { litevskou litevsky litevská litevské litevského litevský litevských litevským } + { litevštině }
    { maďarsko maďarskou maďarsky maďarská maďarské maďarského maďarskému maďarský maďarských maďarským maďarskými } + { maďarština maďarštiny maďarštině maďarští }
    { moravsko moravskou moravská moravské moravského moravskému moravský moravských moravským moravskými } + { moravští }
    { mořskou mořská mořské mořského mořský mořských mořským mořskými } + { mořští }
    { muslimskou muslimská muslimské muslimského muslimský muslimských muslimským muslimskými } + { muslimští }
    { mužskou mužská mužské mužského mužský mužských mužským mužskými } + { mužští }
    { nizozemsko nizozemskou nizozemsky nizozemská nizozemské nizozemského nizozemskému nizozemský nizozemských nizozemským nizozemskými } + { nizozemštiny nizozemštině nizozemští }
    { norskou norsky norská norské norského norský norských norským } + { norštiny norštině }
    { ohniska ohniskem ohnisko ohniskovou ohniskové } + { ohniště }
    { palestinskou palestinská palestinské palestinského palestinský palestinských palestinským palestinskými } + { palestinští }
    { perskou persky perská perské perského perský perských perským } + { perštiny perštině }
    { polsko polskou polsky polská polské polského polskému polský polských polským polskými } + { polština polštiny polštině polští }
    { portugalskou portugalsky portugalská portugalské portugalského portugalský portugalských portugalským } + { portugalština portugalštiny portugalštině portugalští }
    { pražskou pražská pražské pražského pražskému pražský pražských pražským pražskými } + { pražští }
    { protestantskou protestantská protestantské protestantského protestantský protestantských protestantským protestantskými } + { protestantští }
    { rakousko rakouskou rakouská rakouské rakouského rakouskému rakouský rakouských rakouským rakouskými } + { rakouští }
    { rumunsko rumunskou rumunsky rumunská rumunské rumunského rumunský rumunských rumunským } + { rumunštině }
    { rusko ruskou rusky ruská ruské ruského ruskému ruský ruských ruským ruskými } + { ruština ruštinu ruštiny ruštině ruští }
    { slovensko slovenskou slovensky slovenská slovenské slovenského slovenskému slovenský slovenských slovenským slovenskými } + { slovenština slovenštiny slovenštině slovenští }
    { slovinskou slovinsky slovinská slovinské slovinského slovinský slovinských slovinským } + { slovinštiny }
    { sovětsko sovětskou sovětská sovětské sovětského sovětskému sovětský sovětských sovětským sovětskými } + { sovětští }
    { srbsko srbskou srbsky srbská srbské srbského srbský srbských srbským srbskými } + { srbštiny srbštině srbští }
    { stanoviska stanovisko stanovisku } + { stanovišti stanovištích stanoviště }
    { uherskou uherská uherské uherského uherskému uherský uherských uherským uherskými } + { uherští }
    { ukrajinsko ukrajinskou ukrajinsky ukrajinská ukrajinské ukrajinského ukrajinskému ukrajinský ukrajinských ukrajinským ukrajinskými } + { ukrajinštiny ukrajinštině ukrajinští }
    { vojensko vojenskou vojensky vojenská vojenské vojenského vojenskému vojenský vojenských vojenským vojenskými } + { vojenští }
    { česko českou česky česká české českého českému český českých českým českými } + { čeština češtinou češtinu češtiny češtině čeští }
    { československo československou československá československé československého československému československý československých československým československými } + { českoslovenští }
    { čínsko čínskou čínsky čínská čínské čínského čínskému čínský čínských čínským čínskými } + { čínština čínštinu čínštiny čínštině čínští }
    { římsko římskou římská římské římského římskému římský římských římským římskými } + { římští }
    { španělsko španělskou španělsky španělská španělské španělského španělskému španělský španělských španělským španělskými } + { španělština španělštinu španělštiny španělštině španělští }
    { švédsko švédskou švédsky švédská švédské švédského švédskému švédský švédských švédským švédskými } + { švédština švédštiny švédštině švédští }
    { židovsko židovskou židovská židovské židovského židovskému židovský židovských židovským židovskými } + { židovští }
  • 3 splits of groups of stems:
    { baště | bašt bašta baštami baštou baštu bašty }
    { poště | pošt pošta poštou poštovním poštu pošty }
    { zvláště | zvláštním }

    Judging by pasting the output into google translate, most of the merges look like clear improvements. Possible problems:

    • { desce desk deska deskami deskou desková deskové deskový deskových desku desky deskách } + { dešti deštích deště } seems to be conflating "plate" and "rain"
    • { stanoviska stanovisko stanovisku } + { stanovišti stanovištích stanoviště } seems to be "standpoints" vs "positions", which seem similar enough in meaning to be OK

    The first two splits are probably undesirable, while the last seems to be "especially" vs "special" so may be a slight improvement.

    Input welcome, but this seems a pretty clear improvement. Conflating unrelated words is a bigger problem than failing to conflate forms of the same word, but there seems to be just one instance on the former introduced. We should recheck the { desce desk deska deskami deskou desková deskové deskový deskových desku desky deskách } + { dešti deštích deště } case after we've worked through everything else and consider if it's still conflating decide if it's worth a special case (better to leave for now as we may find it's part of a pattern).

@ojwb
Copy link
Member Author

ojwb commented Sep 5, 2024

I've compiled a list of things to resolve at the top of the ticket.

č suffix in snowball vs če in Java (Snowball seems to have copied typo in Java comment)

Testing strongly shows če is better, and it seems like this is just from going off incorrect comments in the Java version so I've adjusted the Snowball implementation to match Java here.

čtí/ští in Java vs čté/šté in Snowball (again seems to be due to Java comment typo)

Changing the Snowball implementation makes no difference here (probably due to the oddness around when to remove a character vs calling do_palatalise) but changing Java to use the Snowball suffixes here leads to a clear regression, and again it seems like this is just from going off incorrect comments in the Java version so I've adjusted the Snowball implementation to match Java here too.

@ojwb
Copy link
Member Author

ojwb commented Sep 5, 2024

I noticed another oddity in CzechStemmerLight.java:

This version leaves the first character of a removed suffix behind when calling palatalise except for -es/-ém/-ím. Checking the vocabulary list, this means palatalise will almost never match one of the suffixes, as the only words with this as an ending in the list are these, which look like they're actually English words (except "abies"):

abies
cookies
hippies
series
studies

This means palatalise will just remove the last character, which seems odd.

Testing changing this to handle these suffixes like others where we call palatalise by removing one character instead of two changes a lot of stems but seems to be an improvement in pretty much every instance I checked in google translate, so I'm going to change that too.

@ojwb
Copy link
Member Author

ojwb commented Sep 9, 2024

To check there weren't any further discrepancies between the Java and Snowball versions, I tried adjusting the Snowball version to use the same stem-length checks as the Java code (with the various fixes) instead of R1 and RV:

routines (
  palatalise_e
  palatalise_ecaron
  palatalise_i
  palatalise_iacute
  mark_regions
  possessive_suffix
  case_suffix
)

externals ( stem )

integers ( p1 )

groupings ( v )

stringescapes {}

stringdef a' '{U+00E1}'
stringdef c^ '{U+010D}'
stringdef d^ '{U+010F}'
stringdef e' '{U+00E9}'
stringdef e^ '{U+011B}'
stringdef i' '{U+00ED}'
stringdef n^ '{U+0148}'
stringdef o' '{U+00F3}'
stringdef r^ '{U+0159}'
stringdef s^ '{U+0161}'
stringdef t^ '{U+0165}'
stringdef u' '{U+00FA}'
stringdef u* '{U+016F}'
stringdef y' '{U+00FD}'
stringdef z^ '{U+017E}'

define v 'aeiouy{a'}{e^}{e'}{i'}{o'}{u'}{u*}{y'}'

define mark_regions as (

    $p1 = limit
    do ( next next next setmark p1 )
)

backwardmode (

  define palatalise_e as (
    [substring] among (
      'c' '{c^}' (<- 'k')
      'z' '{z^}' (<- 'h')
    )
  )

  define palatalise_ecaron as (
    [substring] among (
      '{c^}t' (<- 'ck')
      '{s^}t' (<- 'sk')
    )
  )

  define palatalise_i as (
    [substring] among (
      'c' '{c^}' (<- 'k')
      'z' '{z^}' (<- 'h')
      '{c^}t' (<- 'ck')
      '{s^}t' (<- 'sk')
    )
  )

  define palatalise_iacute as (
    [substring] among (
      '{c^}t' (<- 'ck')
      '{s^}t' (<- 'sk')
    )
  )

  define possessive_suffix as (
    [substring] $p1 < cursor among (
      'ov' '{u*}v'
      (delete)
      'in'
      (
        delete
        try palatalise_i
      )
    )
  )

  define case_suffix as (
    setlimit tomark p1 for ( [substring] ) among (
      'atech'
      'at{u*}m'
      '{a'}ch' '{y'}ch' 'ov{e'}' '{y'}mi'
      'ata' 'aty' 'ama' 'ami' 'ovi'
      'at' '{a'}m' 'os' 'us' '{u*}m' '{y'}m' 'mi' 'ou'
      '{e'}ho' '{e'}m' '{e'}mu'
      'u' 'y' '{u*}' 'a' 'o' '{a'}' '{e'}' '{y'}'
      (delete)
      '{e^}' '{e^}tem' '{e^}mi' '{e^}te' '{e^}ti'
      (
        delete
        try palatalise_ecaron
      )
      'e' 'ech' 'em' 'emi' 'es'
      (
        delete
        try palatalise_e
      )
      'i' 'ich' 'iho' 'imu'
      (
        delete
        try palatalise_i
      )
      '{i'}' '{i'}ch' '{i'}ho' '{i'}m' '{i'}mi'
      (
        delete
        try palatalise_iacute
      )
    )
  )
)

define stem as (
  do mark_regions
  backwards (
    do case_suffix
    do possessive_suffix
  )
)

// Ljiljana Dolamic and Jacques Savoy. 2009.
// Indexing and stemming approaches for the Czech language.
// Inf. Process. Manage. 45, 6 (November 2009), 714-720.
// based on Java code by Ljiljana Dolamic:
// http://members.unine.ch/jacques.savoy/clef/CzechStemmerLight.txt

Doing this, I found we can split palatalise to simplify things.

The main point of note though is

setlimit tomark p1 for ( [substring] ) among ( /*...*/ )

instead of

[substring] R1 among ( /*...*/ )

The difference is that the former will remove the longest of the suffixes that is in R1, while the latter will find the longest of the suffixes and only remove it if it is in R1 (e.g. chata -> chat with the former but is unchanged by the latter because ata matches but isn't in R1.

Need to actually test which works better, but the former is what the Java code does.

Update: Testing show setlimit tomark p1 for ( [substring] ) among is better - changing to [substring] R1 among gives:

  • A total of 1142 words changed stem
  • 607 words changed stem but aren't interesting
  • 521 splits of groups of stems: all seem like they shouldn't be split

@ojwb
Copy link
Member Author

ojwb commented Sep 9, 2024

I've been looking at using the palatalise approach from the previous comment with R1 based on vowels.

It causes a lot of changes, the vast majority for the better:

  • A total of 4574 words changed stem
  • 1471 words changed stem but aren't interesting
  • 1877 merges of groups of stems:
    [...] I've pasted a lot of these into google translate and they all seemed reasonable
  • 427 splits of groups of stems: These all seem for the worse; most seem to follow one of a few patterns so may be possible to address
{ absencí | absence absenci }
{ abstrakcí | abstrakce abstrakci }
{ adaptací | adaptace adaptaci }
{ aglomerací | aglomerace aglomeraci }
{ aktivací | aktivace aktivaci }
{ aktualizací | aktualizace aktualizaci }
{ ambicí | ambice ambicemi }
{ analýze | analýz analýza analýzou analýzu analýzy analýzách }
{ animací | animace animaci }
{ aplikací aplikacích aplikacím | aplikace aplikacemi aplikaci }
{ arbitráže arbitráži | arbitráž }
{ arcidiecéze arcidiecézi | arcidiecézí }
{ asistencí | asistence asistencemi asistenci }
{ asociací | asociace asociaci }
{ atrakcí | atrakce atrakcemi atrakci }
{ baráže baráži | baráž }
{ bezdomovců | bezdomovce bezdomovci }
{ bilancí | bilance bilanci }
{ blíže | blíž blíží }
{ bohoslovců | bohoslovce }
{ borovic borovicí | borovice borovicemi borovici }
{ býložravců | býložravce býložravci }
{ břidlic břidlicí | břidlice břidlicemi }
{ certifikací | certifikace certifikaci }
{ chodců | chodce chodci }
{ chůze chůzi | chůzí }
{ citací | citace }
{ civilizací | civilizace civilizacemi civilizaci }
{ coververze coververzi | coververzí }
{ cyrilicí | cyrilice cyrilici }
{ databáze databázi | databázové databázový databázových databází databázích }
{ datací | datace dataci }
{ definic definicí | definice definici }
{ deformací | deformace deformaci }
{ deklarací | deklarace deklaraci }
{ dekorací | dekorace dekoraci }
{ delegací | delegace delegaci }
{ demolicí | demolice demolici }
{ demonstrací demonstracích demonstracím | demonstrace demonstraci }
{ denominací | denominace }
{ deportací | deportace deportaci }
{ derivací | derivace derivaci }
{ destilací | destilace destilaci }
{ destinací | destinace }
{ dezinformací | dezinformace }
{ diagnóze | diagnóza diagnózou diagnózu diagnózy }
{ diecéze diecézi | diecézí }
{ dimenze dimenzi | dimenzí }
{ diskriminací | diskriminace diskriminaci }
{ diskuze diskuzi | diskuzí diskuzích }
{ dispozic dispozicí | dispozice dispozici }
{ distribucí | distribuce distribuci }
{ divize divizi | divizí }
{ dlaždic | dlaždice dlaždicemi }
{ dokumentací | dokumentace dokumentaci }
{ dokáže | dokážou dokáží }
{ dominancí | dominance dominanci }
{ domorodců domorodcům | domorodce domorodci }
{ dorostenců | dorostenci }
{ dospělců | dospělce dospělci }
{ dotací | dotace dotaci }
{ dozorců | dozorce dozorci }
{ dravců | dravce dravci }
{ družic družicí | družice družici }
{ drůbeže | drůbež drůbeží }
{ dvojic dvojicí dvojicích | dvojice dvojicemi dvojici }
{ dálnic dálnicí dálnicích | dálnice dálnici }
{ dělnic | dělnice }
{ důchodců | důchodce důchodci }
{ emigrací | emigrace emigraci }
{ evolucí | evoluce evoluci }
{ existencí | existence existenci }
{ expanze expanzi | expanzí }
{ expedic expedicí | expedice expedici }
{ exploze explozi | explozí }
{ expozic expozicí | expozice expozici }
{ federací | federace federaci }
{ financovat financí | finance financemi }
{ formací formacích | formace formaci }
{ formulací | formulace formulaci }
{ frakcí | frakce frakcemi frakci }
{ frekvencí frekvencích | frekvence frekvenci }
{ fráze frázi | frází }
{ garáže garáži | garáž garáží }
{ generací generacích generacím | generace generacemi generaci }
{ gravitací | gravitace gravitaci }
{ hadic | hadice }
{ hasiče hasiči | hasičů }
{ hlavic hlavicí | hlavice hlavicemi hlavici }
{ hlavonožců | hlavonožci }
{ hlodavců | hlodavce hlodavci }
{ hlídače | hlídač }
{ holiči | holič }
{ houfnic | houfnice }
{ hranic hranicí hranicích hranicím | hranice hranicemi hranici }
{ hráze hrázemi hrázi | hráz hrází }
{ hvězdicové hvězdicový hvězdicových hvězdicovým hvězdicovými | hvězdice }
{ hydrolýze | hydrolýzou }
{ hypotéze | hypotéz hypotéza hypotézou hypotézu hypotézy }
{ ilustrací | ilustrace ilustracemi ilustraci }
{ imatrikulací | imatrikulace imatrikulaci }
{ implementací | implementace implementaci }
{ indukcí | indukce indukci }
{ industrializací | industrializace industrializaci }
{ infekcí infekcím | infekce infekcemi infekci }
{ inflací | inflace inflaci }
{ informací informacích informacím | informace informacemi informaci }
{ ingrediencí | ingredience }
{ injekcí | injekce injekci }
{ inovací | inovace inovaci }
{ inscenací inscenacích | inscenace inscenaci }
{ inspirací | inspirace inspiraci }
{ instalací | instalace instalaci }
{ instancí | instance instanci }
{ institucí institucích institucím | instituce institucemi instituci }
{ instrukcí | instrukce instrukcemi instrukci }
{ integrací | integrace integraci }
{ inteligencí | inteligence inteligenci }
{ interakcí | interakce interakci }
{ interpretací | interpretace interpretaci }
{ intervencí | intervence intervenci }
{ invaze invazi | invazí }
{ investic investicí investicím | investice investicemi investici }
{ izolací | izolace izolaci }
{ jednotlivců jednotlivcům | jednotlivce jednotlivci }
{ jehlic | jehlice }
{ jurisdikcí | jurisdikce jurisdikci }
{ kanalizací | kanalizace kanalizaci }
{ kapitulací | kapitulace kapitulaci }
{ klasifikací | klasifikace klasifikaci }
{ klec | klece kleci }
{ klimatizací | klimatizace klimatizaci }
{ klávesnicí | klávesnice klávesnici }
{ koalic koalicí | koalice koalici }
{ koberců | koberce koberci }
{ kojenců | kojence }
{ kolejnic | kolejnice kolejnici }
{ kolekcí | kolekce kolekci }
{ kolize kolizi | kolizí }
{ kolonizací | kolonizace kolonizaci }
{ koláče | koláč }
{ koláže | koláž koláží }
{ kombinací kombinacích | kombinace kombinaci }
{ kompetencí | kompetence kompetenci }
{ kompilací | kompilace kompilaci }
{ komplikací komplikacím | komplikace komplikacemi }
{ kompozic kompozicí kompozicích | kompozice kompozici }
{ komunikací komunikacích | komunikace komunikacemi komunikaci }
{ koncentrací koncentracích | koncentrace koncentraci }
{ koncepcí | koncepce koncepci }
{ kondenzací | kondenzace kondenzaci }
{ konfederací | konfederace konfederaci }
{ konferencí konferencích | konference konferenci }
{ konfigurací | konfigurace konfiguraci }
{ konfiskací konfiskacích | konfiskace konfiskaci }
{ kongregací | kongregace kongregaci }
{ konkurencí | konkurence konkurenci }
{ konstrukcí konstrukcích | konstrukce konstrukcemi konstrukci }
{ kontroverze kontroverzi | kontroverzí }
{ konvencí | konvence konvenci }
{ konverze konverzi | konverzí }
{ konzervativců | konzervativce konzervativci }
{ konzumací | konzumace konzumaci }
{ kooptací | kooptace }
{ korporací korporacích | korporace }
{ korunovací | korunovace korunovaci }
{ korupcí | korupce korupci }
{ kotouče | kotouč kotoučové }
{ krabic | krabice krabici }
{ krize krizi | krizové krizového krizový krizových krizí }
{ kružnic | kružnice kružnici }
{ krádeže krádeži | krádež krádeží }
{ kvalifikací kvalifikacích | kvalifikace kvalifikaci }
{ kytovců | kytovci }
{ kříženců | křížence kříženci }
{ lavic | lavice lavicemi lavici }
{ levicovou levicová levicové levicového levicový levicových levicovým levicovými levicově levicí | levice levici }
{ licencí | licence licenci }
{ lidovců | lidovci }
{ likvidací | likvidace likvidaci }
{ loděnic loděnicí loděnicích | loděnice loděnici }
{ lokací | lokace lokaci }
{ loupeže | loupež loupeží }
{ lupiče lupiči | lupič lupičů }
{ manifestací | manifestace manifestaci }
{ manipulací | manipulace manipulaci }
{ masáže | masáž }
{ matic maticí | matice matici }
{ meditací | meditace meditaci }
{ migrací | migrace migraci }
{ milicí | milice }
{ mládeže mládeži | mládež mládeží }
{ modernizací | modernizace modernizaci }
{ modifikací modifikacích | modifikace modifikaci }
{ montáže montáži | montáž montáží }
{ motivací | motivace motivaci }
{ mravenců | mravence mravenci }
{ mrtvicí | mrtvice mrtvici }
{ municí | munice munici }
{ mutací | mutace mutaci }
{ myslivců | myslivce myslivci }
{ márnicí | márnice }
{ měniče | měnič }
{ mříže mřížemi | mříž mříží }
{ nadací | nadace nadaci }
{ nadšenců | nadšenci }
{ nedokáže | nedokážou nedokáží }
{ nejvíc | nejvíce }
{ nemocnic nemocnicí nemocnicích | nemocnice nemocnici }
{ nemůže | nemůžou }
{ neštovic | neštovice neštovicemi }
{ nominací | nominace nominaci }
{ nosorožců | nosorožce }
{ novorozenců | novorozence }
{ nákaze | nákaza nákazou nákazu nákazy }
{ nálože | nálož náloží }
{ náruče | náručí }
{ nížin nížina nížinou nížinu nížiny nížinách nížině | níž }
{ obratlovců | obratlovce obratlovci }
{ obrazců | obrazce }
{ obtíže obtížemi | obtíží obtížím }
{ odnože | odnož odnoží }
{ okupací | okupace okupaci }
{ operací operacích operacím | operace operacemi operaci }
{ opozicí | opozice opozici }
{ organizací organizacích organizacím | organizace organizacemi organizaci }
{ orientací | orientace orientaci }
{ orlicí | orlice orlici }
{ ostřic | ostřice }
{ ovladače ovladači | ovladač ovladačů }
{ oxidací | oxidace oxidaci }
{ ozbrojenců | ozbrojenci }
{ pastevců | pastevci }
{ pasáže pasážemi pasáži | pasáž pasáží pasážích }
{ penězi | peněz penězích penězům }
{ perzekucí | perzekuce perzekuci }
{ plachetnic | plachetnice }
{ plantáže | plantáží plantážích }
{ plazi | plaza plazy plazů }
{ plodnic | plodnice }
{ ploutvonožců | ploutvonožci }
{ pláče | pláč }
{ pláže plážemi pláži | pláž plážovém plážový pláží plážích }
{ poblíže | poblíž }
{ podnože | podnož podnoží }
{ pohlednic | pohlednice }
{ polovodiče | polovodičové polovodičových polovodičů }
{ pomoc pomocí | pomoci }
{ populací populacích | populace populacemi populaci }
{ potápěče potápěči | potápěčů }
{ potíže potížemi | potíží potížích potížím }
{ povstalců povstalcům | povstalce povstalci }
{ pověřenců | pověřence }
{ pozic pozicí pozicích pozicím | pozice pozicemi pozici }
{ pracovat prací pracích pracím | pracemi }
{ pracovnicí | pracovnice }
{ prarodiče prarodiči | prarodičů }
{ pravicovou pravicová pravicové pravicového pravicový pravicových pravicovým pravicovými pravicově pravicí | pravice pravici }
{ pravomoc pravomocí | pravomoce pravomocemi pravomoci }
{ pražců | pražce }
{ preferencí | preference preferenci }
{ prestiže | prestiž }
{ prevencí | prevence prevenci }
{ prezentací | prezentace prezentaci }
{ privatizací | privatizace privatizaci }
{ prodejců | prodejce prodejci }
{ produkcí produkcích | produkce produkci }
{ prohlížeče prohlížeči | prohlížeč prohlížečů }
{ projekcí | projekce projekci }
{ prominencí | prominence }
{ propagací | propagace propagaci }
{ proporcí | proporce }
{ pryskyřic | pryskyřice pryskyřici }
{ próze | próz próza prózou prózu prózy }
{ publikací publikacích | publikace publikaci }
{ pískovcová pískovcové pískovcového pískovcovém pískovcový pískovcových pískovcovými pískovcích pískovců | pískovce pískovci }
{ přehrávače | přehrávač přehrávačů }
{ překladače | překladač }
{ přepínače | přepínač }
{ přijímače | přijímač přijímačů }
{ přistěhovalců přistěhovalcům | přistěhovalce přistěhovalci }
{ přivaděče | přivaděč }
{ příze přízi | přízí }
{ půlměsíc | půlměsíce }
{ půlnocí | půlnoci }
{ radnicí | radnice radnici }
{ realizací | realizace realizaci }
{ recenze recenzi | recenzí recenzích }
{ redakcí | redakce redakci }
{ redukcí | redukce redukci }
{ referencí | reference }
{ reformací | reformace reformaci }
{ registrací | registrace registraci }
{ regulací | regulace regulaci }
{ rekonstrukcí rekonstrukcích | rekonstrukce rekonstrukcemi rekonstrukci }
{ relací | relace relaci }
{ remíze | remíz remíza remízou remízu remízy }
{ renesancí | renesance renesanci }
{ renovací | renovace renovaci }
{ reorganizací | reorganizace reorganizaci }
{ reparací | reparace }
{ reportáže reportáži | reportáž reportáží }
{ reprezentací reprezentacích | reprezentace reprezentacemi reprezentaci }
{ reprodukcí | reprodukce reprodukci }
{ repríze | repríz reprízy }
{ restaurací restauracích | restaurace restauracemi restauraci }
{ restitucí | restituce restituci }
{ revize revizi | revizí }
{ revolucí | revoluce revoluci }
{ rezervací rezervacích | rezervace rezervaci }
{ rezidencí | rezidence rezidenci }
{ rezignací | rezignace rezignaci }
{ rezolucí | rezoluce rezoluci }
{ rotací | rotace rotaci }
{ rovnic rovnicí | rovnice rovnicemi rovnici }
{ rukavic | rukavice }
{ růžicí | růžice růžici }
{ sabotáže | sabotáž }
{ samic samicí samicím | samice samicemi samici }
{ sazenic | sazenice }
{ sběrače sběrači | sběrač sběračů }
{ schůze schůzi | schůzí }
{ sekvencí | sekvence sekvenci }
{ selekcí | selekce selekci }
{ senzací | senzaci }
{ sestřenicí | sestřenice sestřenici }
{ signalizací | signalizace signalizaci }
{ silic | silice }
{ silnic silnicí silnicích | silnice silnicemi silnici }
{ simulací | simulace simulaci }
{ sinic | sinice }
{ situací situacích situacím | situace situaci }
{ skic | skici }
{ slepic | slepice }
{ sliznic | sliznice sliznici }
{ směrnic | směrnice směrnici }
{ snímače | snímač snímačů }
{ sourozenců sourozencům | sourozence sourozenci }
{ soutěže soutěžemi soutěži | soutěž soutěží soutěžích }
{ souřadnic souřadnicích | souřadnice souřadnicemi }
{ specializací | specializace specializaci }
{ specifikací | specifikace specifikaci }
{ spekulací spekulacím | spekulace }
{ spiklenců | spiklenci }
{ spojnicí | spojnice spojnici }
{ společnicí | společnice }
{ spolupracovnicí | spolupracovnice }
{ spotřebiče | spotřebičů }
{ stanic stanicí stanicích stanicím | stanice stanicemi stanici }
{ stimulací | stimulace stimulaci }
{ stráže stráži | stráž stráží }
{ stupnicí | stupnice stupnici }
{ stáže stáži | stáž stáží }
{ stíhače stíhači | stíhač stíhačů }
{ substitucí | substituce substituci }
{ světců | světce světci }
{ syntéze | syntéza syntézou syntézu syntézy }
{ tahače | tahač tahačů }
{ tajemnicí | tajemnice }
{ tanečnicí | tanečnice }
{ telekomunikací telekomunikacích | telekomunikace }
{ televize televizi | televizí }
{ tendencí tendencím | tendence tendencemi tendenci }
{ tisíc tisících tisíců tisícům | tisíce tisíci }
{ tkalců | tkalce tkalci }
{ tlumiče tlumiči | tlumič }
{ tolerancí | tolerance toleranci }
{ tradic tradicí tradicích tradicím | tradice tradicemi tradici }
{ transakcí | transakce transakci }
{ transformací | transformace transformaci }
{ transkripcí | transkripce transkripci }
{ transplantací | transplantace transplantaci }
{ trojicí | trojice trojici }
{ trubic | trubice trubici }
{ tuberkulóze | tuberkulóza tuberkulózou tuberkulózu tuberkulózy }
{ uchazeče uchazeči | uchazeč uchazečů }
{ urychlovače | urychlovač }
{ učebnic učebnicích | učebnice učebnici }
{ variací | variace variaci }
{ vegetací | vegetace vegetaci }
{ velekněze | velekněz }
{ velkokříže | velkokříž }
{ velmoc velmocí | velmocemi velmoci }
{ velmože velmoži | velmožů }
{ vesnic vesnicí vesnicích vesnicím | vesnice vesnicemi vesnici }
{ vibrací | vibrace }
{ vidlicový | vidlice }
{ vinic vinicích | vinice vinicemi vinici }
{ vitráže vitrážemi | vitráží }
{ vlastenců | vlastence vlastenci }
{ voliče voliči | volič voličů }
{ vrhače | vrhač }
{ vyhledávače | vyhledávač }
{ vyznavači | vyznavačů }
{ vzbouřenců vzbouřencům | vzbouřence vzbouřenci }
{ vzdělanců | vzdělanci }
{ výchozech | výchozy výchozí výchozích výchozího výchozím výchozů }
{ výztuže | výztuž }
{ věznic věznicích | věznice věznici }
{ zajatců | zajatce zajatci }
{ zajíc zajíců | zajíce zajíci }
{ zaměřovače | zaměřovač }
{ zdrojnic zdrojnicí | zdrojnice }
{ zemědělců zemědělcům | zemědělce zemědělci }
{ zesilovače | zesilovač zesilovačů }
{ zkáze | zkáza zkázou zkázu zkázy }
{ zločinců | zločince zločinci }
{ zvonicí | zvonice zvonici }
{ zájemců zájemcům | zájemce zájemci }
{ zákonodárců | zákonodárce zákonodárci }
{ zátěže zátěži | zátěž zátěží }
{ závodnic | závodnice }
{ účastnicí | účastnice }
{ čarodějnic | čarodějnice čarodějnici }
{ čediče | čedič čedičové }
{ členovců | členovci }
{ čtvercovou čtvercová čtvercové čtvercového čtvercovém čtvercový čtvercových čtvercovým čtverců | čtverce čtverci }
{ čtveřicí | čtveřice čtveřici }
{ částic částicové částicí | částice částicemi částici }
{ číslic číslicí | číslice číslicemi číslici }
{ řadiče | řadič }
{ řeholnic | řeholnice }
{ šimpanze šimpanzi | šimpanzů }
{ škůdců škůdcům | škůdce škůdci }
{ špionáže | špionáž }
{ železnic železnicí železnicích | železnice železnici }
{ žluči | žlučových }
  • 300 words moving between stem groups:
    [...] These seem to be cases where forms of a word map to two different stems and we're just shuffling words between the two groups - basically neutral

Based on the above, it seems clear we should adjust palatalise in this way, but then to take a look at the splits and see if we can eliminate most of them.

ojwb added a commit to ojwb/snowball that referenced this pull request Sep 11, 2024
The Java code removes this ending but it was missing from the Snowball
version.  Looking at the changes resulting from this, it seems a clear
improvement so I've concluded it was an accidental omission.

See snowballstem#151
@ojwb
Copy link
Member Author

ojwb commented Sep 20, 2024

Java light stemmer removes -ěte and -ěti while the aggressive stemmer removes -ete and -eti (no caron on the e). The snowball implementation follows the light stemmer. The older version of the light stemmer listed in the original paper removes all four suffixes.

In order to try to better understand this I compared the suffixes with those listed at https://en.wikipedia.org/wiki/Czech_declension (which I'd expect to be a reliable source for something like this, but if there's a better one please point me at it).

Suffixes we remove but which wikipedia's list doesn't seem to support:

  • -ěte and -ěti (the two suffixes which started me looking at this) are not listed by wikipedia but -ete and -eti are.
  • -ětem isn't listed by wikipedia (it does appear at the end of a word in the title of one of the sources listed: "Shoda přísudku s podmětem několikanásobným", but that seems to be "podmět" + "-em"). It is listed as a suffix to remove in the original paper, but there's no explanation as to why AFAICS (for this or any other suffix included) except that it is removed in a RemoveCase function so presumably it's meant to be a case ending.
  • -es, -iho, -imu, -os aren't listed by wikipedia
  • -ich seems to only be a suffix for two pronouns (našich and vašich; jejich would also liked be stripped but is not actually declinable; jich and nich are too short to be stemmed), but pronouns are typically not very useful to stem (and we don't remove other pronoun-only suffixes from what I can see, only suffixes which happen to be pronoun suffixes as well as noun suffixes).

I could perhaps believe -ich and -iho were removed to handle text with missing diacritics (since -ích and -ího are removed) but if that's the explanation why aren't -im and -imi included?

There are also two suffixes we don't remove but wikipedia lists:

  • -ima Instrumental case suffix for some irregular nouns (e.g. očima and ušima, which seem to mean eyes and ears so presumably not really obscure words).
  • -ímu - e.g. mluvčímu, jarnímu (edit: NB with an accent on the i)

@hauktoma Can you help resolve any of these?

ojwb added 20 commits November 7, 2025 15:19
Use a definition of R1 more like the usual Snowball one, but take
syllabic consonants 'l' and 'r' into account.

It seems 'm' and 'n' can also be syllabic consonants but are much
rarer so we ignore these for now at least.

Testing suggests enforcing a minimum of 3 characters before R1 (like
the Danish, Dutch and German stemmers do) helps so we do that here
too.

See snowballstem#151
We can just handle the first character specially - after that we
know the previous character is a consonant because otherwise we'd
have already stopped.

See snowballstem#151
There seems no benefit from having a separate region we can remove
possessive suffixes in.

See snowballstem#151
This is a singular masculine animate instrumental form - e.g.
koněm (horse).  Results in 28 merges on the sample vocabulary
which all look good.
Previously -í* was handled like -ě*, but experimentation shows treating
it the same as -i works much better.
Use {uo} instead of {u*} for ů; use e.g. {ev} instead of {e^} for ě.
This makes no difference to the output, but reduces work for short
words we wouldn't modify anyway, some of which are likely to be
very common.
This improves handling of about 30 words.
This improves handling of about 90 words.
This improves handling of about 360 words.
This improves handling of about 23 words.
This improves handling of about 13 words.
This improves handling of about 22 words.
This improves handling of about 20 words.
This only improves handling of a small number of words (3 in the sample
vocabulary and I know of at least 2 more), but it's a simple rule which
doesn't seem to have false positives.
@ojwb
Copy link
Member Author

ojwb commented Nov 7, 2025

I've come to the conclusion that the current converting of to -k after removing a suffix starting with e, i, or í does slightly more harm than good.

In case a Czech speaker wants to take a look, here's the comparison for removing these two rules:

compare.html

Based on Firefox's machine translation and some looking up of words in wiktionary, to me it seems the merges are almost all improvements, the splits are mostly worse (but some are actually better), and the more complicated cases are mostly better.

It seems the cases these rules help are verb and adjective forms (either getting conflated with other forms of the same verb/adjective, or conflating with a related noun). Perhaps there is scope for more targetted rules which aim to address only these cases.

Based on Firefox's machine translation and some looking up of words in
wiktionary, to me it seems the merges are almost all improvements, the
splits are mostly worse (but some are actually better), and the more
complicated cases are mostly better.

It seems the cases these rules help are verb and adjective forms (either
getting conflated with other forms of the same verb/adjective, or
conflating with a related noun). Perhaps there is scope for more
targetted rules which aim to address only these cases.
Based on Firefox's machine translation and some looking up of words in
wiktionary, to me it seems the merges are almost all improvements, the
splits are mostly worse (but some are actually better), and the more
complicated cases are mostly better.

This seems particular true when the suffix removed starts with `e` -
for those there are hardly any words removing this rule harms.
@ojwb
Copy link
Member Author

ojwb commented Nov 7, 2025

Same conclusion for to -h (especially after removing a suffix starting with e, where it is almost never helpful to change to -h).

Comparison: compare.html

Based on Firefox's machine translation and some looking up of words in
wiktionary, to me it seems the merges are almost all improvements, the
splits are mostly worse (but some are actually better), and the more
complicated cases are mostly better.
@ojwb
Copy link
Member Author

ojwb commented Nov 7, 2025

Similarly for -z to -h: compare.html

The rules for -čt and -št seem almost universally good (I already added 6 exceptions for -št).

The rules for -c to -k are harming some cases, but they improve significantly more than they harm. Some exceptions may be helpful there too.

@ojwb
Copy link
Member Author

ojwb commented Nov 7, 2025

{ desce desk deska deskami deskou desková deskové deskový deskových desku desky deskách } and { dešti deštích deště } are no longer conflated.

červencem (July) and červenka (robin) still are though. There is an inherent ambiguity here as července is a form of both words, but it's bad that we make this worse.

@faileon
Copy link

faileon commented Nov 14, 2025

The merges nearly all seem to be better. One niche exceptions I have noticed:

{ jenže } + { jenž } - but (conjunction) vs. who (male pronoun)

The splits that are definitely better and spotted different meaning of words:

{ klece . kleci | klečí } - cage vs. kneel
{ otok . otoky | otočí } - edema vs. turn around
{ průvodce . průvodcem . průvodci | průvodčí } - guide vs. train conductor
{ předci . předcích . předek . předka . předkem . předkové . předky . předků . předkům | předčí } - ancestor vs. surpasses

Some of the wrong splits seems to be splitting nouns and verbs of the same meaning, which I am not sure if its something desirable.

{ útocích . útok . útokem . útoku . útoky . útoků . útokům | útočí } - attack (noun) vs. attack (verb)

as well as noun vs adjective of the same meaning:

{ ovce . ovcí | ovčí } - sheep (noun) vs. sheep (adjective)
{ opice | opičí } - monkey (noun) vs. monkey (adjective)

I’m not sure how much value I’m adding here, as I’m mainly highlighting points you’ve likely already noticed through the machine translations. However, I was able to get in touch with people at Charles University in Prague, specifically from the Institute of Formal and Applied Linguistics. I will forward this discussion to them, and hopefully they will be able to assist with these nuances and help guide this forward.

@ojwb
Copy link
Member Author

ojwb commented Nov 14, 2025

The merges nearly all seem to be better. One niche exceptions I have noticed:

{ jenže } + { jenž } - but (conjunction) vs. who (male pronoun)

While "wrong", neither word carries much useful meaning in the context of a search query (if you're using a stopword list, both these words would likely be on it). So I'm not too concerned about this one.

The splits that are definitely better and spotted different meaning of words:

{ klece . kleci | klečí } - cage vs. kneel
{ otok . otoky | otočí } - edema vs. turn around
{ průvodce . průvodcem . průvodci | průvodčí } - guide vs. train conductor
{ předci . předcích . předek . předka . předkem . předkové . předky . předků . předkům | předčí } - ancestor vs. surpasses

Some of the wrong splits seems to be splitting nouns and verbs of the same meaning, which I am not sure if its something desirable.

{ útocích . útok . útokem . útoku . útoky . útoků . útokům | útočí } - attack (noun) vs. attack (verb)

as well as noun vs adjective of the same meaning:

{ ovce . ovcí | ovčí } - sheep (noun) vs. sheep (adjective)
{ opice | opičí } - monkey (noun) vs. monkey (adjective)

It is generally desirable to conflate different parts of speech with the same meaning.

However if we have to trade that off against the wrong splits above, I'd probably choose to avoid the wrong splits since they are will tend to be more problematic than the additional merges are helpful (searching for "ancestor" and finding apparently unrelated pages is unhelpful, whereas a document on the subject of about "attack", "sheep" or "monkey" is likely to use multiple parts of speech and so to still be found). If there were very few wrong splits and of very rare words (and/or perhaps of words with a tenuously connected meaning) there might be an argument to be made.

Also even with a (not actually achievable) perfect stemming algorithm, we won't conflate with other words with the same or very similar meaning - "attack" vs "onslaught", "sheep" vs "lamb", "monkey" vs "ape".

This algorithm also doesn't generally aim to handle verb forms (it just happens to handle some because some verb suffixes are the same as some noun suffixes). There is an "aggressive" variant which does try to handle more parts of speech, but apparently it's "known to overstem" so we've gone for the light approach. Perhaps that's worth a revisit at some point (since e.g. the work-in-progress here has a more nuanced definition of the minimum stem than the original paper), but getting this merged has already dragged out for too long so I'm reluctant to broaden the scope significantly when we're pretty close to the finish line.

I’m not sure how much value I’m adding here, as I’m mainly highlighting points you’ve likely already noticed through the machine translations.

If nothing else, it's reassuring to have feedback, and it's definitely appreciated.

However, I was able to get in touch with people at Charles University in Prague, specifically from the Institute of Formal and Applied Linguistics. I will forward this discussion to them, and hopefully they will be able to assist with these nuances and help guide this forward.

Probably the most pertinent question they might be able to help with is coming up with rules about when we should try to change a soft trailing consonant to a hard equivalent after removing certain suffixes.

For example, adding a suffix starting with e, i or í to a stem ending k will change that k to a c, so when removing such a suffix we may want to change c back to k. The problem is that some stems already ended in c before adding a suffix and changing those to k can be problematic (we don't really care what the stem is as it's handled as an opaque token, so it's only actually a problem if it results in a stem collision with something else but if the stem ending c is a valid form of the word it'll end up split from other forms).

So ideally we want to be able to look at a stem ending -k and know what to do with it (and erring on the side of not changing it). The code currently in git always changes it to a -c, which seems to help more words than it hurts but gets quite a lot of cases wrong. One common approach used in other stemmers is to check the character before (e.g. only change if the k is preceded one of bčhkňřsšťuy is a condition I've tried).

And similarly for -z to -h, etc (which I've currently turned off as without conditions they seem more harmful than helpful).

I've been looking at whether we can usefully mine this information from wiktionary - there is machine-readable JSONL available from https://kaikki.org/dictionary/rawdata.html (both the English and Czech versions are potentially useful as each wiktionary includes entries for other languages, e.g. https://en.wiktionary.org/wiki/kancl%C3%A9%C5%99#Czech - there are unsurprisingly more Czech entries in the Czech wiktionary, but being too comprehensive here may be unhelpful and "Czech words someone has bothered to write an English definition for" might be a reasonable way to select a subset of less obscure words).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants