--- language: - fi license: other license_name: mit-and-cc-by-4.0-and-gpl-3.0 license_link: https://huggingface.co/samuli/juurio/blob/main/ATTRIBUTION.md library_name: pytorch tags: - finnish - morphology - inflection - lemmatization - dependency-parsing --- # Juurio Open-vocabulary Finnish morphology over raw text. Give it a sentence and an operation (`past`, `passive`, `plural`, …) and it returns the transformed sentence or an explicit abstention. It also inflects out-of-vocabulary and loan words that a closed-lexicon morphology cannot (`striimata` → `striimannee`, `striimaaja` → `striimaajillanikin`). ```python from juurio import transform, realize_np, realize_verbform transform("past", "Koira haukkuu pihalla.").text # 'Koira haukkui pihalla.' transform("passive", "Mies osti talon.").text # 'Ostettiin talo.' transform("pluperfect","Hän lukee kirjan.").text # 'Hän oli lukenut kirjan.' (compound tense) transform("neg_imperative","Osta talo.").text # 'Älä osta taloa.' (imperative/2nd-person source) transform("potential", "Nuori striimaa peliä.", lemma="striimata").text # 'Nuori striimannee peliä.' (OOV verb) transform("plural+poss_1sg+clitic_kin", "Koira haukkuu pihalla.").text # 'Koiranikin haukkuvat pihalla.' # phrase-level morphology utilities realize_np("talo", case="Ine", number="Plur", px="1Sg").text # 'taloissani' realize_np("striimaaja", case="Ade", number="Plur", px="1Sg", clitic="kin").text # 'striimaajillanikin' (OOV) realize_verbform("mennä", form="inf3", case="Ade").text # 'menemällä' realize_verbform("striimata", form="va").text # 'striimaava' (OOV participle) ``` Every call returns a `Result` (`ok`, `text`, `reason`, `trace`, `warnings`). Input that is out of scope or that the operation does not apply to abstains, with `ok=False` and a documented reason. Forms produced by a neural model rather than the exception lexicon are listed in `trace["neural_unverified"]` and in `warnings`. ## Install ```bash pip install https://huggingface.co/samuli/juurio/resolve/main/juurio-0.1.0-py3-none-any.whl juurio setup # download and verify the model checkpoints and data ``` Python 3.12 or newer. The install pulls in `torch`, `transformers`, `numpy` and `huggingface-hub`. On a machine with a GPU, that installs the CUDA build of PyTorch. On a CPU-only machine the default PyTorch wheel also bundles CUDA, a download of several GB you can't use. To get the small CPU-only build instead, install it first: ```bash pip install torch --index-url https://download.pytorch.org/whl/cpu pip install https://huggingface.co/samuli/juurio/resolve/main/juurio-0.1.0-py3-none-any.whl ``` The wheel contains only the code. `juurio setup` downloads the model checkpoints and data from this repository (about 190 MB), checks their sha256 hashes and caches them. Without it, they are downloaded on first use. `juurio doctor` shows what is present and what is missing. ## Background Finnish is richly inflecting, and the standard open-source tools for it are excellent but closed-vocabulary and heavy at runtime. A finite-state morphology ([Giella `fin`](https://github.com/giellalt/lang-fin) and [Omorfi](https://github.com/flammie/omorfi), also the engine behind [Voikko](https://voikko.puimula.org/)) inflects only words already in its lexicon, and a neural dependency parser ([Stanza](https://stanfordnlp.github.io/stanza/)) brings a large dependency stack. A loan or slang word the lexicon has never seen (`striimata`, `striimaaja`) cannot be inflected. Juurio distills those tools into small neural models and a compact exception lexicon, so at runtime it needs only PyTorch and it can inflect words the finite-state morphology does not know. The heavy tools were used only to build the models and data: Giella `fin` is the morphology the neural models learned from, Stanza produced silver parses for the parser, and Voikko was used to check generated forms. ## Architecture | concern | component | |---|---| | structure and scope (which clause, subject, object) | neural UD dependency parser | | morphology of known words | exception lexicon (the forms the neural realizers get wrong) | | open-vocabulary morphology (OOV and loan words) | small neural realizers: nominal, verb-form and finite-verb | The rule engine handles all non-local structure (agreement, object case, subject drop). The neural realizers are called per word, only when the lexicon has no entry for the form. ## Entry points - `transform(op, text, lemma=None)`: finite-clause transforms that preserve grammar, such as negation, tense and mood (including the potential and the compound tenses), passive, question, the full imperative paradigm (with the negative `älä` series), number with agreement, degree, enclitics, possessives and `+`-composed nominal morphology. - `realize_np(np, …)`: phrase-level nominal morphology, with the full case system, possessive suffixes, clitics and modifier agreement. Arbitrary case lives here because it is syntactically governed. - `realize_verbform(verb, …)`: phrase-level non-finite verb morphology. Participles (VA, NUT, TAVA, TU and the agent participle MA) and infinitives, inflected in case and number with an optional possessive suffix. - `inflect(word, …)`, `analyze(text)` and `lemmatize(text)`: single-word inflection, analysis and lemmatization. Anything semantic, such as existential framing, is deliberately out of scope. ## CLI Every library function has a command of the same name. The CLI is a thin wrapper over the same `Transformer`. ``` # one command per library function juurio transform "" [--lemma L] [--trace] [--json] # transform() juurio inflect "" [spec…] [--case C] [--number N] [--degree D] [--px P] [--clitic K] [--pos P] [--like W] [--proper] [--json] # inflect() juurio analyze "" [--json] # analyze() juurio lemmatize "" [--json] # lemmatize() juurio realize-np "" [--case Ine] [--number Plur] [--px 1Sg] [--clitic kin] # realize_np() juurio realize-verbform "" [--form va|nut|tava|tu|inf2|inf2_pass|inf3|ma] [--case Ine] [--px 3] # realize_verbform() # infrastructure (no library counterpart) juurio doctor # read-only environment diagnostics (dependencies, checkpoints + sha256) juurio setup # download and verify the checkpoints and data juurio list-ops # documented operation catalog juurio batch < lines # or per-line "optext[lemma]" juurio demo | bench | repl ``` `inflect` takes a case, number and degree phrase positionally (`juurio inflect talo mon gen` → `talojen`) or the equivalent `--case`, `--number` and `--degree` flags. Exit status is `0` on success, `2` if any transform abstained (useful for scripting) and `1` on setup failure. ## Files | path | what | |---|---| | `juurio-0.1.0-py3-none-any.whl` | the Python package | | `juurio/`, `pyproject.toml` | source code of the package | | `docs/api.md` | API and CLI contract, scope boundary, reason codes | | `checkpoints/` | parser, analyser and realizer models with their tokenizer and vocabularies | | `data/` | exception lexicon, morphology tag table and vocabularies | | `ATTRIBUTION.md` | licences and sources, file by file | ## Training data - The realizers learned from inflected forms generated with the Giella `fin` transducer, for lemmas found in a Finnish web corpus. - The exception lexicon holds the Giella forms the realizers get wrong. - The parser was trained on [UD_Finnish-TDT](https://universaldependencies.org/treebanks/fi_tdt/index.html) and on silver parses of Finnish web text made with Stanza. - The morphology tag table lists Giella's tags for the 100,000 most frequent web-text word forms where the neural analyser disagrees with Giella. E-mail addresses, @handles, URLs, phone numbers and hashtags were removed from it. ## Limitations - `transform` handles one finite main clause in the active indicative, within limits of about twelve words and one finite verb. Subordinate and coordinated clauses, already negated clauses and multi-verb fragments abstain. - Forms from the neural realizers are model guesses. They are flagged in `warnings` but not verified, and some are wrong: `realize_np("bugisovellus", case="Ine", px="1Pl", clitic="kin")` gives *bugisovelludessammekin* instead of *bugisovelluksessammekin*. - The parser makes mistakes, and a wrong lemma or analysis can lead to a wrong form or an abstention. The `lemma=` argument overrides the lemma of the main verb. ## License Three parts with separate licences (details in [`ATTRIBUTION.md`](ATTRIBUTION.md)): | part | licence | |---|---| | code (`juurio/`, the wheel) | [MIT](LICENSE) | | models (`checkpoints/`, `data/char_vocab.json`) | [CC BY 4.0](LICENSE-CC-BY-4.0) | | data derived from Giella `fin` (`data/wordlist.json`, `data/fst_table.jsonl`, `data/fst_tag_vocab.json`) | [GPL-3.0](LICENSE-GPL-3.0), the licence of [GiellaLT `lang-fin`](https://github.com/giellalt/lang-fin) (© UiT The Arctic University of Norway / Giellatekno) |