From ecb799b75332fe57eb48041af83fd7cfc3d984a7 Mon Sep 17 00:00:00 2001 From: Pavlo Kuchmiichuk Date: Fri, 10 Jul 2026 13:33:10 -0400 Subject: [PATCH 01/16] Adds an overridable dataset-loading hook to UniMorphAdapter and a Ukrainian VESUM adapter subclass. --- bead/resources/adapters/unimorph.py | 20 +++++- .../ukr/argument_structure/utils/__init__.py | 1 + .../argument_structure/utils/vesum_adapter.py | 69 +++++++++++++++++++ tests/resources/adapters/test_unimorph.py | 27 ++++++++ 4 files changed, 116 insertions(+), 1 deletion(-) create mode 100644 gallery/ukr/argument_structure/utils/__init__.py create mode 100644 gallery/ukr/argument_structure/utils/vesum_adapter.py diff --git a/bead/resources/adapters/unimorph.py b/bead/resources/adapters/unimorph.py index 3c7c91b..f65b920 100644 --- a/bead/resources/adapters/unimorph.py +++ b/bead/resources/adapters/unimorph.py @@ -52,6 +52,24 @@ def __init__(self, cache: AdapterCache | None = None) -> None: self.cache = cache self._datasets: dict[str, pd.DataFrame] = {} # Cache datasets by language + def _load_dataset(self, lang_code: str) -> pd.DataFrame: + """Load the raw ``(lemma, form, features)`` DataFrame for a language. + + Override this in a subclass to read a non-standard UniMorph file layout + (e.g. a language whose data ships extra columns). + + Parameters + ---------- + lang_code : str + ISO 639-3 language code. + + Returns + ------- + pd.DataFrame + DataFrame with ``lemma``, ``form``, and ``features`` columns. + """ + return load_dataset(lang_code) + def fetch_items( self, query: str | None = None, @@ -113,7 +131,7 @@ def fetch_items( try: # Load dataset for language (cached at instance level) if lang_code not in self._datasets: - self._datasets[lang_code] = load_dataset(lang_code) + self._datasets[lang_code] = self._load_dataset(lang_code) dataset = self._datasets[lang_code] diff --git a/gallery/ukr/argument_structure/utils/__init__.py b/gallery/ukr/argument_structure/utils/__init__.py new file mode 100644 index 0000000..7ad950b --- /dev/null +++ b/gallery/ukr/argument_structure/utils/__init__.py @@ -0,0 +1 @@ +"""Ukrainian-specific helpers for the argument_structure gallery.""" diff --git a/gallery/ukr/argument_structure/utils/vesum_adapter.py b/gallery/ukr/argument_structure/utils/vesum_adapter.py new file mode 100644 index 0000000..81495b3 --- /dev/null +++ b/gallery/ukr/argument_structure/utils/vesum_adapter.py @@ -0,0 +1,69 @@ +"""UniMorph adapter for the Ukrainian VESUM dataset (``ukr.xz``). + +The VESUM conversion in the ``unimorph/ukr`` repo ships ``ukr.xz`` as a +**4-column** TSV:: + + lemma form inflectional-features inherent-features + +Column 3 holds the features that vary across the paradigm (case, number, tense), +while column 4 holds inherent lexeme features: ``animacy;gender`` for nouns, +``aspect`` for verbs. + +The stock ``unimorph.load_dataset`` assumes the standard 3-column layout and +mis-parses this file (the lemma slides into the DataFrame index and the feature +columns shift). :class:`VesumUniMorphAdapter` reads the file itself and joins +every feature column into a single tag string, so the inherited +``_parse_features`` sees the full tag set (e.g. ``N;ACC;SG;INAN;MASC``) and the +animacy / gender / aspect features are preserved. + +Only :meth:`_load_dataset` is overridden; filtering, feature parsing, +``LexicalItem`` construction, and caching are inherited from +:class:`~bead.resources.adapters.unimorph.UniMorphAdapter` unchanged. +""" + +from __future__ import annotations + +import pandas as pd +import unimorph + +from bead.resources.adapters.unimorph import UniMorphAdapter + + +class VesumUniMorphAdapter(UniMorphAdapter): + """``UniMorphAdapter`` variant that reads the VESUM ``ukr.xz`` file. + + Use exactly like the base adapter:: + + adapter = VesumUniMorphAdapter() + items = adapter.fetch_items(language_code="ukr") # all forms + one = adapter.fetch_items(query="книга", language_code="ukr") # one lemma + """ + + VESUM_FILE = "ukr.xz" + + def _load_dataset(self, lang_code: str) -> pd.DataFrame: + """Read the VESUM file, joining all feature columns into one tag string. + + Parameters + ---------- + lang_code : str + ISO 639-3 language code (``"ukr"``). + + Returns + ------- + pd.DataFrame + DataFrame with ``lemma``, ``form``, and ``features`` columns, where + ``features`` is columns 3..N joined by ``";"`` (empty parts dropped). + """ + unimorph.download_unimorph(lang_code) + path = unimorph.UNIMORPH_DIR / lang_code / self.VESUM_FILE + raw = pd.read_csv( + path, sep="\t", header=None, dtype=str, keep_default_na=False + ) + features = raw.iloc[:, 2:].apply( + lambda row: ";".join(tag for tag in row if isinstance(tag, str) and tag), + axis=1, + ) + return pd.DataFrame( + {"lemma": raw[0], "form": raw[1], "features": features} + ) diff --git a/tests/resources/adapters/test_unimorph.py b/tests/resources/adapters/test_unimorph.py index ba290c3..2202747 100644 --- a/tests/resources/adapters/test_unimorph.py +++ b/tests/resources/adapters/test_unimorph.py @@ -3,6 +3,7 @@ from __future__ import annotations import didactic.api as dx +import pandas as pd import pytest from bead.resources.adapters.cache import AdapterCache @@ -122,3 +123,29 @@ def test_unimorph_adapter_feature_parsing() -> None: features_n = adapter._parse_features("N;PL") assert features_n["pos"] == "N" assert features_n["number"] == "PL" + + +def test_load_dataset_hook_is_overridable() -> None: + """fetch_items loads data through the overridable _load_dataset hook. + + A subclass can supply a custom DataFrame (e.g. for a non-standard file + layout) by overriding _load_dataset; the rest of fetch_items is inherited. + Runs without network access. + """ + + class _FakeAdapter(UniMorphAdapter): + def _load_dataset(self, lang_code: str) -> pd.DataFrame: + return pd.DataFrame( + { + "lemma": ["walk"], + "form": ["walks"], + "features": ["V;PRS;3;SG"], + } + ) + + items = _FakeAdapter().fetch_items(query="walk", language_code="en") + assert len(items) == 1 + assert items[0].lemma == "walk" + assert items[0].form == "walks" + assert items[0].features["tense"] == "PRS" + assert items[0].features["person"] == "3" From b6f34e63d58f925f352ca130340895aa40b49185 Mon Sep 17 00:00:00 2001 From: Pavlo Kuchmiichuk Date: Sat, 11 Jul 2026 12:27:21 -0400 Subject: [PATCH 02/16] Adds the Ukrainian verb-lexicon generator built on the VESUM adapter. --- .../argument_structure/generate_lexicons.py | 117 ++++++++++++++++++ 1 file changed, 117 insertions(+) create mode 100644 gallery/ukr/argument_structure/generate_lexicons.py diff --git a/gallery/ukr/argument_structure/generate_lexicons.py b/gallery/ukr/argument_structure/generate_lexicons.py new file mode 100644 index 0000000..ffa4d84 --- /dev/null +++ b/gallery/ukr/argument_structure/generate_lexicons.py @@ -0,0 +1,117 @@ +#!/usr/bin/env python3 +"""Generate the Ukrainian verb lexicon from VESUM UniMorph data. + +Writes ``lexicons/verbs.jsonl`` with one present-tense 3rd-person-singular form +per imperfective verb (perfective verbs have no present tense in Ukrainian). +""" + +from __future__ import annotations + +import argparse +from pathlib import Path + +from utils.vesum_adapter import VesumUniMorphAdapter + +from bead.cli.display import ( + create_live_status, + display_file_stats, + print_header, + print_info, + print_success, +) +from bead.resources.lexical_item import LexicalItem +from bead.resources.lexicon import Lexicon + +LANGUAGE = "ukr" + + +def build_verb_lexicon(limit: int | None = None) -> list[LexicalItem]: + """Return one present-3sg finite indicative form per imperfective verb. + + Parameters + ---------- + limit : int | None + Stop after this many unique verb lemmas. None means all. + + Returns + ------- + list[LexicalItem] + One item per verb, carrying parsed morphological features. + """ + adapter = VesumUniMorphAdapter() + with create_live_status("Loading VESUM data (ukr.xz, ~7.5M rows)..."): + frame = adapter._load_dataset(LANGUAGE) + + # Narrow the frame before building items to avoid materializing every row. + features = frame["features"].astype(str) + candidates = frame[ + features.str.startswith("V;") & features.str.contains("PRS", regex=False) + ] + + items: list[LexicalItem] = [] + seen: set[str] = set() + for row in candidates.itertuples(index=False): + parsed = adapter._parse_features(str(row.features)) + keep = ( + parsed.get("pos") == "V" + and parsed.get("tense") == "PRS" + and parsed.get("person") == "3" + and parsed.get("number") == "SG" + and parsed.get("mood") != "IMP" + and parsed.get("finiteness") != "NFIN" + ) + if not keep: + continue + lemma = str(row.lemma) + if lemma in seen: + continue + seen.add(lemma) + items.append( + LexicalItem( + lemma=lemma, + form=str(row.form), + language_code=LANGUAGE, + features=parsed, + source="UniMorph:ukr.xz", + ) + ) + if limit is not None and len(items) >= limit: + break + return items + + +def main(limit: int | None = None) -> None: + """Generate the verb lexicon and write it to ``lexicons/verbs.jsonl``. + + Parameters + ---------- + limit : int | None + Truncate to this many unique verb lemmas. None means all. + """ + print_header("Verb Lexicon (VESUM)") + if limit is not None: + print_info(f"Limiting to {limit} verb lemmas") + + items = build_verb_lexicon(limit) + print_success(f"Selected {len(items):,} present-3sg verb forms") + + lexicon = Lexicon(name="verbs", language_code=LANGUAGE).with_items(items) + + output_path = Path(__file__).parent / "lexicons" / "verbs.jsonl" + output_path.parent.mkdir(parents=True, exist_ok=True) + lexicon.to_jsonl(str(output_path)) + display_file_stats(output_path, len(items), "verbs") + + +if __name__ == "__main__": + parser = argparse.ArgumentParser( + description="Generate the Ukrainian verb lexicon from VESUM UniMorph data." + ) + parser.add_argument( + "--limit", + type=int, + default=None, + help="Limit the number of verb lemmas (for testing).", + ) + args = parser.parse_args() + main(limit=args.limit) From 3fd2090ef4ba80747339d2af44cddf2a018b722d Mon Sep 17 00:00:00 2001 From: Pavlo Kuchmiichuk Date: Mon, 13 Jul 2026 23:20:07 -0400 Subject: [PATCH 03/16] Adds the bleached noun lexicon builder and parses VESUM once for both lexicons. --- .../argument_structure/generate_lexicons.py | 191 ++++++++++++++++-- 1 file changed, 177 insertions(+), 14 deletions(-) diff --git a/gallery/ukr/argument_structure/generate_lexicons.py b/gallery/ukr/argument_structure/generate_lexicons.py index ffa4d84..07f94b4 100644 --- a/gallery/ukr/argument_structure/generate_lexicons.py +++ b/gallery/ukr/argument_structure/generate_lexicons.py @@ -1,15 +1,32 @@ #!/usr/bin/env python3 -"""Generate the Ukrainian verb lexicon from VESUM UniMorph data. +"""Generate the Ukrainian lexicons from VESUM UniMorph data. -Writes ``lexicons/verbs.jsonl`` with one present-tense 3rd-person-singular form -per imperfective verb (perfective verbs have no present tense in Ukrainian). +Writes two files: + +``lexicons/verbs.jsonl`` + One present-tense 3rd-person-singular form per imperfective verb + (perfective verbs have no present tense in Ukrainian). + +``lexicons/bleached_nouns.jsonl`` + A small hand-authored set of bleached nouns, expanded from UniMorph into one + form per case each noun's role uses, tagged with ``semantic_class`` and + ``role``. + +The VESUM file (~7.5M rows) is parsed once and both lexicons are built from that +single in-memory frame. + +Some object nouns collapse case forms (e.g. подія shares one form for genitive +and dative, події). Those forms are still emitted, each tagged with its true +case; disambiguating collapsed cases in the rendered stimulus is left for later. """ from __future__ import annotations import argparse +from dataclasses import dataclass from pathlib import Path +import pandas as pd from utils.vesum_adapter import VesumUniMorphAdapter from bead.cli.display import ( @@ -25,11 +42,53 @@ LANGUAGE = "ukr" -def build_verb_lexicon(limit: int | None = None) -> list[LexicalItem]: +@dataclass(frozen=True) +class NounSpec: + """A hand-authored bleached noun and its experimental annotation. + + Parameters + ---------- + lemma : str + Nominative-singular dictionary form. + role : str + Argument role the noun fills (``"subject"`` or ``"object"``). + semantic_class : str + Coarse meaning label carried through the pipeline. + """ + + lemma: str + role: str + semantic_class: str + + +# One animate subject and four objects spanning the core meaning classes. The +# subject only ever appears in the nominative; objects appear in each case an +# object frame governs. +NOUN_SPECS: tuple[NounSpec, ...] = ( + NounSpec("людина", "subject", "animate"), + NounSpec("група", "object", "animate"), + NounSpec("предмет", "object", "inanimate_object"), + NounSpec("місце", "object", "location"), + NounSpec("подія", "object", "event"), +) + +CASES_BY_ROLE: dict[str, tuple[str, ...]] = { + "subject": ("NOM",), + "object": ("ACC", "GEN", "DAT", "INS"), +} + + +def build_verb_lexicon( + adapter: VesumUniMorphAdapter, frame: pd.DataFrame, limit: int | None = None +) -> list[LexicalItem]: """Return one present-3sg finite indicative form per imperfective verb. Parameters ---------- + adapter : VesumUniMorphAdapter + Adapter supplying the feature parser. + frame : pd.DataFrame + The parsed VESUM frame (``lemma``, ``form``, ``features``). limit : int | None Stop after this many unique verb lemmas. None means all. @@ -38,10 +97,6 @@ def build_verb_lexicon(limit: int | None = None) -> list[LexicalItem]: list[LexicalItem] One item per verb, carrying parsed morphological features. """ - adapter = VesumUniMorphAdapter() - with create_live_status("Loading VESUM data (ukr.xz, ~7.5M rows)..."): - frame = adapter._load_dataset(LANGUAGE) - # Narrow the frame before building items to avoid materializing every row. features = frame["features"].astype(str) candidates = frame[ @@ -80,11 +135,66 @@ def build_verb_lexicon(limit: int | None = None) -> list[LexicalItem]: return items -def main(limit: int | None = None) -> None: - """Generate the verb lexicon and write it to ``lexicons/verbs.jsonl``. +def build_noun_lexicon( + adapter: VesumUniMorphAdapter, frame: pd.DataFrame +) -> list[LexicalItem]: + """Return the bleached noun forms for the cases each role's frames use. + + For every noun in :data:`NOUN_SPECS`, its singular forms are read from the + frame and one form is kept per case the role governs, tagged with the parsed + morphology plus ``semantic_class`` and ``role``. + + Parameters + ---------- + adapter : VesumUniMorphAdapter + Adapter supplying the feature parser. + frame : pd.DataFrame + The parsed VESUM frame (``lemma``, ``form``, ``features``). + + Returns + ------- + list[LexicalItem] + One item per (noun, case), carrying parsed features and annotation. + """ + items: list[LexicalItem] = [] + for spec in NOUN_SPECS: + wanted = CASES_BY_ROLE[spec.role] + rows = frame[frame["lemma"] == spec.lemma] + seen: set[str] = set() + for row in rows.itertuples(index=False): + parsed = adapter._parse_features(str(row.features)) + case = parsed.get("case") + if parsed.get("pos") != "N" or parsed.get("number") != "SG": + continue + if case is None or case not in wanted or case in seen: + continue + seen.add(case) + features = dict(parsed) + features["semantic_class"] = spec.semantic_class + features["role"] = spec.role + items.append( + LexicalItem( + lemma=spec.lemma, + form=str(row.form), + language_code=LANGUAGE, + features=features, + source="UniMorph:ukr.xz", + ) + ) + return items + + +def generate_verbs( + adapter: VesumUniMorphAdapter, frame: pd.DataFrame, limit: int | None = None +) -> None: + """Build the verb lexicon and write ``lexicons/verbs.jsonl``. Parameters ---------- + adapter : VesumUniMorphAdapter + Adapter supplying the feature parser. + frame : pd.DataFrame + The parsed VESUM frame. limit : int | None Truncate to this many unique verb lemmas. None means all. """ @@ -92,20 +202,73 @@ def main(limit: int | None = None) -> None: if limit is not None: print_info(f"Limiting to {limit} verb lemmas") - items = build_verb_lexicon(limit) + items = build_verb_lexicon(adapter, frame, limit) print_success(f"Selected {len(items):,} present-3sg verb forms") lexicon = Lexicon(name="verbs", language_code=LANGUAGE).with_items(items) - output_path = Path(__file__).parent / "lexicons" / "verbs.jsonl" output_path.parent.mkdir(parents=True, exist_ok=True) lexicon.to_jsonl(str(output_path)) display_file_stats(output_path, len(items), "verbs") +def generate_nouns(adapter: VesumUniMorphAdapter, frame: pd.DataFrame) -> None: + """Build the bleached noun lexicon and write ``lexicons/bleached_nouns.jsonl``. + + Parameters + ---------- + adapter : VesumUniMorphAdapter + Adapter supplying the feature parser. + frame : pd.DataFrame + The parsed VESUM frame. + """ + print_header("Bleached Noun Lexicon (VESUM)") + + items = build_noun_lexicon(adapter, frame) + print_success(f"Selected {len(items)} noun forms across {len(NOUN_SPECS)} lemmas") + for item in items: + f = item.features + print_info( + f"{item.lemma} -> {item.form} " + f"[{f.get('case')}/{f.get('role')}/{f.get('semantic_class')}]" + ) + + lexicon = Lexicon(name="bleached_nouns", language_code=LANGUAGE).with_items(items) + output_path = Path(__file__).parent / "lexicons" / "bleached_nouns.jsonl" + output_path.parent.mkdir(parents=True, exist_ok=True) + lexicon.to_jsonl(str(output_path)) + display_file_stats(output_path, len(items), "noun forms") + + +def main(target: str = "all", limit: int | None = None) -> None: + """Generate the requested lexicons from a single VESUM load. + + Parameters + ---------- + target : str + Which lexicon to build: ``"verbs"``, ``"nouns"``, or ``"all"``. + limit : int | None + Truncate the verb lexicon to this many lemmas. None means all. + """ + adapter = VesumUniMorphAdapter() + with create_live_status("Loading VESUM data (ukr.xz, ~7.5M rows)..."): + frame = adapter._load_dataset(LANGUAGE) + + if target in ("verbs", "all"): + generate_verbs(adapter, frame, limit) + if target in ("nouns", "all"): + generate_nouns(adapter, frame) + + if __name__ == "__main__": parser = argparse.ArgumentParser( - description="Generate the Ukrainian verb lexicon from VESUM UniMorph data." + description="Generate the Ukrainian lexicons from VESUM UniMorph data." + ) + parser.add_argument( + "--target", + choices=("verbs", "nouns", "all"), + default="all", + help="Which lexicon to generate (default: all).", ) parser.add_argument( "--limit", @@ -114,4 +277,4 @@ def main(limit: int | None = None) -> None: help="Limit the number of verb lemmas (for testing).", ) args = parser.parse_args() - main(limit=args.limit) + main(target=args.target, limit=args.limit) From dbc7063b4d56f17c21507845b1767df01f07f826 Mon Sep 17 00:00:00 2001 From: Pavlo Kuchmiichuk Date: Tue, 14 Jul 2026 00:45:40 -0400 Subject: [PATCH 04/16] Adds the Ukrainian sentence-frame generator and a capitalizing template renderer. --- .../argument_structure/generate_templates.py | 187 ++++++++++++++++++ .../ukr/argument_structure/utils/renderers.py | 70 +++++++ 2 files changed, 257 insertions(+) create mode 100644 gallery/ukr/argument_structure/generate_templates.py create mode 100644 gallery/ukr/argument_structure/utils/renderers.py diff --git a/gallery/ukr/argument_structure/generate_templates.py b/gallery/ukr/argument_structure/generate_templates.py new file mode 100644 index 0000000..7147cdb --- /dev/null +++ b/gallery/ukr/argument_structure/generate_templates.py @@ -0,0 +1,187 @@ +#!/usr/bin/env python3 +"""Generate the Ukrainian sentence frames. + +Writes ``templates/generic_frames.jsonl``: one generic, verb-independent frame +per argument structure. Each frame declares its own word order and its noun +slots, constrained by role and case. Verbs and nouns are supplied by the +lexicons when the frames are filled. +""" + +from __future__ import annotations + +from dataclasses import dataclass +from pathlib import Path + +from bead.cli.display import ( + display_file_stats, + print_header, + print_info, + print_success, +) +from bead.resources.constraints import Constraint +from bead.resources.template import Slot, Template, slots_match_template +from bead.resources.template_collection import TemplateCollection + +LANGUAGE = "ukr" + + +@dataclass(frozen=True) +class ArgSlot: + """A noun slot in a frame, constrained by role and case. + + Parameters + ---------- + name : str + Slot name, matching the placeholder in the frame's template string. + role : str + Argument role the noun fills (e.g. ``"subject"``, ``"object"``). + case : str + Case the noun form must carry (e.g. ``"NOM"``, ``"ACC"``). + """ + + name: str + role: str + case: str + + +@dataclass(frozen=True) +class FrameSpec: + """A sentence frame: a word order plus its argument slots. + + Parameters + ---------- + name : str + Frame identifier. + template_string : str + Word order with ``{slot}`` placeholders, including the verb slot. + args : tuple[ArgSlot, ...] + The frame's noun slots. + description : str + Human-readable description. + """ + + name: str + template_string: str + args: tuple[ArgSlot, ...] + description: str + + +# Reusable argument slots, defined once and shared across frames. +SUBJ_NOM = ArgSlot("subj_nom", "subject", "NOM") +OBJ_ACC = ArgSlot("obj_acc", "object", "ACC") +OBJ_GEN = ArgSlot("obj_gen", "object", "GEN") +OBJ_DAT = ArgSlot("obj_dat", "object", "DAT") +OBJ_INS = ArgSlot("obj_ins", "object", "INS") + +FRAME_SPECS: tuple[FrameSpec, ...] = ( + FrameSpec( + "intransitive", + "{subj_nom} {verb}.", + (SUBJ_NOM,), + "Nominative subject, no object.", + ), + FrameSpec( + "obj_acc", + "{subj_nom} {verb} {obj_acc}.", + (SUBJ_NOM, OBJ_ACC), + "Nominative subject, accusative object.", + ), + FrameSpec( + "obj_gen", + "{subj_nom} {verb} {obj_gen}.", + (SUBJ_NOM, OBJ_GEN), + "Nominative subject, genitive object.", + ), + FrameSpec( + "obj_dat", + "{subj_nom} {verb} {obj_dat}.", + (SUBJ_NOM, OBJ_DAT), + "Nominative subject, dative object.", + ), + FrameSpec( + "obj_ins", + "{subj_nom} {verb} {obj_ins}.", + (SUBJ_NOM, OBJ_INS), + "Nominative subject, instrumental object.", + ), +) + + +def _arg_slot(arg: ArgSlot) -> Slot: + """Return a noun slot constrained to the argument's role and case.""" + expression = ( + "self.features.get('pos') == 'N' " + f"and self.features.get('role') == '{arg.role}' " + f"and self.features.get('case') == '{arg.case}'" + ) + description = f"{arg.role} ({arg.case})" + return Slot( + name=arg.name, + description=description, + constraints=(Constraint(expression=expression, description=description),), + ) + + +def _verb_slot() -> Slot: + """Return a slot constrained to present-tense verbs.""" + expression = ( + "self.features.get('pos') == 'V' and self.features.get('tense') == 'PRS'" + ) + return Slot( + name="verb", + description="present-tense verb", + constraints=( + Constraint(expression=expression, description="Present-tense verb"), + ), + ) + + +def make_frame(spec: FrameSpec) -> Template: + """Build the ``Template`` for one frame specification. + + Parameters + ---------- + spec : FrameSpec + The frame to build. + + Returns + ------- + Template + A frame with a verb slot and one constrained slot per argument. + """ + slots: dict[str, Slot] = {arg.name: _arg_slot(arg) for arg in spec.args} + slots["verb"] = _verb_slot() + + template = Template( + name=spec.name, + template_string=spec.template_string, + slots=slots, + description=spec.description, + language_code=LANGUAGE, + tags=("argument_structure",), + ) + slots_match_template(template) + return template + + +def main() -> None: + """Build the frames and write them to ``templates/generic_frames.jsonl``.""" + print_header("Sentence Frames") + + templates = [make_frame(spec) for spec in FRAME_SPECS] + for template in templates: + print_info(f"{template.name}: {template.template_string}") + + collection = TemplateCollection( + name="generic_frames", language_code=LANGUAGE + ).with_templates(templates) + print_success(f"Built {len(templates)} frames") + + output_path = Path(__file__).parent / "templates" / "generic_frames.jsonl" + output_path.parent.mkdir(parents=True, exist_ok=True) + collection.to_jsonl(str(output_path)) + display_file_stats(output_path, len(templates), "frames") + + +if __name__ == "__main__": + main() diff --git a/gallery/ukr/argument_structure/utils/renderers.py b/gallery/ukr/argument_structure/utils/renderers.py new file mode 100644 index 0000000..3a3cd41 --- /dev/null +++ b/gallery/ukr/argument_structure/utils/renderers.py @@ -0,0 +1,70 @@ +"""Custom template renderer for the Ukrainian argument structure experiment. + +Case is carried in the noun form itself (fusional morphology), so rendering is +plain slot substitution followed by capitalizing the first character of the +sentence. +""" + +from __future__ import annotations + +from collections.abc import Mapping +from typing import TYPE_CHECKING + +from bead.templates.renderers import TemplateRenderer + +if TYPE_CHECKING: + from bead.resources.lexical_item import LexicalItem + from bead.resources.template import Slot + + +class UkrainianRenderer(TemplateRenderer): + """Substitute each slot's form and capitalize the sentence. + + Each placeholder is replaced by the filler's ``form`` (falling back to its + ``lemma``), then the first character is upper-cased so the sentence begins + with a capital letter. + + Examples + -------- + >>> from bead.resources.lexical_item import LexicalItem + >>> from bead.resources.template import Slot + >>> renderer = UkrainianRenderer() + >>> fillers = { + ... "subj_nom": LexicalItem(lemma="людина", language_code="ukr"), + ... "verb": LexicalItem(lemma="читати", form="читає", language_code="ukr"), + ... "obj_acc": LexicalItem(lemma="книга", form="книгу", language_code="ukr"), + ... } + >>> slots = {name: Slot(name=name) for name in fillers} + >>> renderer.render("{subj_nom} {verb} {obj_acc}.", fillers, slots) + 'Людина читає книгу.' + """ + + def render( + self, + template_string: str, + slot_fillers: Mapping[str, LexicalItem], + template_slots: Mapping[str, Slot], + ) -> str: + """Render the template and capitalize the first character. + + Parameters + ---------- + template_string : str + Template string with ``{slot_name}`` placeholders. + slot_fillers : Mapping[str, LexicalItem] + Mapping from slot names to the items that fill them. + template_slots : Mapping[str, Slot] + Mapping from slot names to slot definitions (unused). + + Returns + ------- + str + Rendered sentence with a capitalized first character. + """ + result = template_string + for slot_name, item in slot_fillers.items(): + placeholder = f"{{{slot_name}}}" + if placeholder in result: + surface = item.form if item.form is not None else item.lemma + result = result.replace(placeholder, surface) + return result[:1].upper() + result[1:] From a5b6d10bb4b24008168c9ef9d7e5d57da8595dbc Mon Sep 17 00:00:00 2001 From: Pavlo Kuchmiichuk Date: Tue, 14 Jul 2026 01:14:17 -0400 Subject: [PATCH 05/16] Adds the config-driven exhaustive fill stage for the Ukrainian frames. --- gallery/ukr/argument_structure/config.yaml | 78 ++++++ .../ukr/argument_structure/fill_templates.py | 261 ++++++++++++++++++ 2 files changed, 339 insertions(+) create mode 100644 gallery/ukr/argument_structure/config.yaml create mode 100644 gallery/ukr/argument_structure/fill_templates.py diff --git a/gallery/ukr/argument_structure/config.yaml b/gallery/ukr/argument_structure/config.yaml new file mode 100644 index 0000000..1864023 --- /dev/null +++ b/gallery/ukr/argument_structure/config.yaml @@ -0,0 +1,78 @@ +# ============================================================================ +# Ukrainian argument structure pipeline configuration +# ============================================================================ +# Design principle: test every verb in every frame (full cross-product), so +# both licensed and unlicensed verb-frame combinations are judged. +# ============================================================================ + +project: + name: "argument_structure" + language_code: "ukr" + +paths: + cache_dir: ".cache" + +resources: + templates: + - path: "templates/generic_frames.jsonl" + name: "generic_frames" + description: "Argument-structure frames constructed manually" + + lexicons: + - path: "lexicons/verbs.jsonl" + name: "verbs" + description: "Present-tense verbs from VESUM" + + - path: "lexicons/bleached_nouns.jsonl" + name: "bleached_nouns" + description: "Bleached subject and object nouns in their case forms" + +template: + # Strategy type: "exhaustive", "mlm", or "mixed" + filling_strategy: "mixed" + + # Output path for filled templates + output_path: "items/filled.jsonl" + + # Masked language model settings, used only by slots whose strategy is "mlm". + # Not yet finalized; unused while every slot below is exhaustive. + mlm: + model_name: "youscan/ukr-roberta-base" + beam_size: 5 + top_k: 10 + device: "cpu" + cache_enabled: true + + # Per-slot strategies: + # - exhaustive: use all lexicon items that satisfy the slot's constraints + # - mlm: let the model select contextually appropriate fillers + # max_fills: keep the top-N items by model probability + # enforce_unique: each beam hypothesis uses different items for this slot + slot_strategies: + subj_nom: + strategy: "exhaustive" + description: "The bleached subject in the nominative" + + verb: + strategy: "exhaustive" + description: "All present-tense verbs (cross-product with all frames)" + + obj_acc: + strategy: "exhaustive" + description: "Accusative objects" + + obj_gen: + strategy: "exhaustive" + description: "Genitive objects" + + obj_dat: + strategy: "exhaustive" + description: "Dative objects" + + obj_ins: + strategy: "exhaustive" + description: "Instrumental objects" + +logging: + level: "WARNING" + format: "%(asctime)s - %(name)s - %(levelname)s - %(message)s" diff --git a/gallery/ukr/argument_structure/fill_templates.py b/gallery/ukr/argument_structure/fill_templates.py new file mode 100644 index 0000000..bd5ea08 --- /dev/null +++ b/gallery/ukr/argument_structure/fill_templates.py @@ -0,0 +1,261 @@ +#!/usr/bin/env python3 +"""Fill the Ukrainian frames with lexical items. + +Loads the frames and lexicons, then for each frame produces every combination of +items satisfying the slot constraints, rendered into a sentence, and writes them +to the configured output path. Per-slot strategies come from ``config.yaml``; +every slot is exhaustive by default, but a slot may instead be filled by a +masked language model. +""" + +from __future__ import annotations + +import argparse +import logging +from itertools import islice +from pathlib import Path + +import yaml +from utils.renderers import UkrainianRenderer + +from bead.cli.display import ( + create_progress, + display_file_stats, + print_header, + print_info, + print_success, + print_warning, +) +from bead.data.serialization import write_jsonlines +from bead.resources.lexicon import Lexicon +from bead.resources.template_collection import TemplateCollection +from bead.templates.filler import FilledTemplate +from bead.templates.resolver import ConstraintResolver +from bead.templates.strategies import MixedFillingStrategy, StrategyConfig + +BASE_DIR = Path(__file__).parent +LANGUAGE = "ukr" + + +def load_config(path: Path) -> dict: + """Load the YAML configuration file. + + Parameters + ---------- + path : Path + Path to the configuration file. + + Returns + ------- + dict + Parsed configuration. + """ + with path.open(encoding="utf-8") as f: + return yaml.safe_load(f) + + +def build_strategy(config: dict) -> MixedFillingStrategy: + """Build the filling strategy from the config's per-slot strategies. + + The masked language model is loaded only when some slot requests it, so an + all-exhaustive run needs no model. + + Parameters + ---------- + config : dict + Parsed configuration. + + Returns + ------- + MixedFillingStrategy + Strategy dispatching each slot to its configured filler. + """ + slot_configs = config["template"]["slot_strategies"] + mlm = config["template"]["mlm"] + resolver = ConstraintResolver() + + model_adapter = None + cache = None + if any(sc["strategy"] == "mlm" for sc in slot_configs.values()): + from bead.templates.adapters.cache import ModelOutputCache + from bead.templates.adapters.huggingface import HuggingFaceMLMAdapter + + print_info(f"Loading MLM model: {mlm['model_name']}...") + model_adapter = HuggingFaceMLMAdapter( + model_name=mlm["model_name"], device=mlm.get("device", "cpu") + ) + model_adapter.load_model() + cache = ModelOutputCache(cache_dir=BASE_DIR / config["paths"]["cache_dir"]) + + slot_strategies: dict[str, tuple[str, StrategyConfig]] = {} + for slot_name, slot_config in slot_configs.items(): + if slot_config["strategy"] == "mlm": + settings: StrategyConfig = { + "resolver": resolver, + "model_adapter": model_adapter, + "cache": cache, + "beam_size": mlm.get("beam_size", 5), + "top_k": mlm.get("top_k", 10), + } + if "max_fills" in slot_config: + settings["max_fills"] = slot_config["max_fills"] + if "enforce_unique" in slot_config: + settings["enforce_unique"] = slot_config["enforce_unique"] + slot_strategies[slot_name] = ("mlm", settings) + else: + slot_strategies[slot_name] = (slot_config["strategy"], {}) + + return MixedFillingStrategy(slot_strategies=slot_strategies) + + +def limit_verbs(lexicon: Lexicon, limit: int) -> Lexicon: + """Return a lexicon keeping only the first ``limit`` unique verb lemmas. + + Parameters + ---------- + lexicon : Lexicon + Verb lexicon. + limit : int + Number of unique lemmas to keep. + + Returns + ------- + Lexicon + Lexicon restricted to the first ``limit`` lemmas. + """ + lemmas: list[str] = [] + for item in lexicon.items: + if item.lemma not in lemmas: + lemmas.append(item.lemma) + if len(lemmas) >= limit: + break + allowed = set(lemmas) + return lexicon.filter(lambda item: item.lemma in allowed) + + +def main( + config_path: Path, + limit: int | None = None, + max_per_template: int | None = None, + output: Path | None = None, +) -> None: + """Fill every frame and write the sentences to the output path. + + Parameters + ---------- + config_path : Path + Path to the configuration file. + limit : int | None + Keep only this many unique verb lemmas (for quick runs). + max_per_template : int | None + Keep at most this many sentences per frame. + output : Path | None + Override the output path from the config. + """ + config = load_config(config_path) + logging.basicConfig( + level=getattr(logging, config["logging"]["level"]), + format=config["logging"]["format"], + ) + + print_header("Template Filling") + + templates_config = config["resources"]["templates"][0] + templates = list( + TemplateCollection.from_jsonl( + str(BASE_DIR / templates_config["path"]), templates_config["name"] + ) + ) + print_success(f"Loaded {len(templates)} frames") + + lexicons: list[Lexicon] = [] + for lex_config in config["resources"]["lexicons"]: + lexicon = Lexicon.from_jsonl( + str(BASE_DIR / lex_config["path"]), lex_config["name"] + ) + if limit is not None and lex_config["name"] == "verbs": + lexicon = limit_verbs(lexicon, limit) + print_warning(f"Limited verbs to {limit} lemmas ({len(lexicon)} forms)") + lexicons.append(lexicon) + print_info(f"Loaded {len(lexicon)} items from {lex_config['name']}") + + strategy = build_strategy(config) + renderer = UkrainianRenderer() + + filled: list[FilledTemplate] = [] + counts: dict[str, int] = {} + with create_progress() as progress: + task = progress.add_task("Filling frames", total=len(templates)) + for template in templates: + combos = strategy.generate_from_template( + template=template, lexicons=lexicons, language_code=LANGUAGE + ) + if max_per_template is not None: + combos = islice(combos, max_per_template) + + start = len(filled) + for combo in combos: + rendered = renderer.render( + template.template_string, combo, template.slots + ) + filled.append( + FilledTemplate( + template_id=str(template.id), + template_name=template.name, + slot_fillers=combo, + rendered_text=rendered, + strategy_name="mixed", + template_slots={ + name: slot.required + for name, slot in template.slots.items() + }, + ) + ) + counts[template.name] = len(filled) - start + progress.advance(task) + + for name, count in counts.items(): + print_info(f"{name}: {count:,} sentences") + print_success(f"Filled {len(filled):,} sentences") + + output_path = output or (BASE_DIR / config["template"]["output_path"]) + output_path.parent.mkdir(parents=True, exist_ok=True) + write_jsonlines(filled, output_path) + display_file_stats(output_path, len(filled), "sentences") + + +if __name__ == "__main__": + parser = argparse.ArgumentParser( + description="Fill the Ukrainian frames with lexical items." + ) + parser.add_argument( + "--config", + type=Path, + default=BASE_DIR / "config.yaml", + help="Path to the configuration file.", + ) + parser.add_argument( + "--limit", + type=int, + default=None, + help="Keep only this many unique verb lemmas (for testing).", + ) + parser.add_argument( + "--max-per-template", + type=int, + default=None, + help="Keep at most this many sentences per frame.", + ) + parser.add_argument( + "--output", + type=Path, + default=None, + help="Override the output path from the config.", + ) + args = parser.parse_args() + main( + config_path=args.config, + limit=args.limit, + max_per_template=args.max_per_template, + output=args.output, + ) From 0a576428ef37e11cbb4e69e0dd853b13388c7991 Mon Sep 17 00:00:00 2001 From: Pavlo Kuchmiichuk Date: Tue, 21 Jul 2026 12:54:44 -0400 Subject: [PATCH 06/16] Adds the 2AFC stage with case-contrast pairing and a masked-LM scorer. --- gallery/ukr/argument_structure/config.yaml | 29 ++ .../argument_structure/create_2afc_pairs.py | 349 ++++++++++++++++++ .../ukr/argument_structure/utils/scoring.py | 97 +++++ 3 files changed, 475 insertions(+) create mode 100644 gallery/ukr/argument_structure/create_2afc_pairs.py create mode 100644 gallery/ukr/argument_structure/utils/scoring.py diff --git a/gallery/ukr/argument_structure/config.yaml b/gallery/ukr/argument_structure/config.yaml index 1864023..05992cd 100644 --- a/gallery/ukr/argument_structure/config.yaml +++ b/gallery/ukr/argument_structure/config.yaml @@ -11,6 +11,7 @@ project: paths: cache_dir: ".cache" + 2afc_pairs: "items/2afc_pairs.jsonl" resources: templates: @@ -73,6 +74,34 @@ template: strategy: "exhaustive" description: "Instrumental objects" +items: + judgment_type: "forced_choice" + n_alternatives: 2 + + # Sentence-acceptability scoring models. Exactly one entry sets + # use_for_scoring: true (override with --model). Masked models are scored by + # pseudo-log-likelihood, causal models by sentence log-probability. + models: + - name: "benjamin/roberta-large-wechsel-ukrainian" + type: "masked" + device: "cpu" + use_for_scoring: true + + - name: "lapa-llm/lapa-12b-pt" + type: "causal" + device: "cuda" + use_for_scoring: false + + # Minimal pairs contrast the case of a verb's object while holding the verb + # and the noun fixed. Pairs whose two sentences render identically (case + # syncretism) are dropped. + construction: + group_by: ["verb_lemma", "object_class"] + drop_identical: true + +lists: + quantile_bins: 10 + logging: level: "WARNING" format: "%(asctime)s - %(name)s - %(levelname)s - %(message)s" diff --git a/gallery/ukr/argument_structure/create_2afc_pairs.py b/gallery/ukr/argument_structure/create_2afc_pairs.py new file mode 100644 index 0000000..8773698 --- /dev/null +++ b/gallery/ukr/argument_structure/create_2afc_pairs.py @@ -0,0 +1,349 @@ +#!/usr/bin/env python3 +"""Create 2AFC minimal pairs from the filled sentences. + +Loads the filled sentences, scores each with the selected language model, then +pairs sentences that share a verb and object noun but differ in the object's +case, so each pair isolates the case the verb governs. Pairs are tagged with the +score difference and assigned difficulty quantiles, and written to the configured +output path. +""" + +from __future__ import annotations + +import argparse +import logging +from pathlib import Path +from uuid import UUID + +import yaml +from utils.scoring import MaskedLanguageModelScorer + +from bead.cli.display import ( + create_live_status, + display_file_stats, + print_header, + print_info, + print_success, + print_warning, +) +from bead.items.forced_choice import create_filtered_forced_choice_items +from bead.items.item import Item +from bead.items.scoring import ItemScorer, LanguageModelScorer +from bead.lists.stratification import assign_quantiles_by_uuid +from bead.templates.filler import FilledTemplate + +BASE_DIR = Path(__file__).parent + + +def load_config(path: Path) -> dict: + """Load the YAML configuration file. + + Parameters + ---------- + path : Path + Path to the configuration file. + + Returns + ------- + dict + Parsed configuration. + """ + with path.open(encoding="utf-8") as f: + return yaml.safe_load(f) + + +def load_filled(path: Path, limit: int | None = None) -> list[FilledTemplate]: + """Load filled sentences from JSONL. + + Parameters + ---------- + path : Path + Path to the filled-sentences file. + limit : int | None + Read at most this many lines. + + Returns + ------- + list[FilledTemplate] + The loaded filled sentences. + """ + filled: list[FilledTemplate] = [] + with path.open(encoding="utf-8") as f: + for i, line in enumerate(f): + if limit is not None and i >= limit: + break + filled.append(FilledTemplate.model_validate_json(line)) + return filled + + +def to_items(filled: list[FilledTemplate]) -> list[Item]: + """Convert filled sentences with an object into scorable items. + + Intransitive sentences (no object) are dropped, since the case contrast is + defined only over object-bearing frames. + + Parameters + ---------- + filled : list[FilledTemplate] + The filled sentences. + + Returns + ------- + list[Item] + One item per object-bearing sentence, tagged with verb, object class, + and case. + """ + items: list[Item] = [] + for ft in filled: + verb = ft.slot_fillers.get("verb") + obj = next( + (f for name, f in ft.slot_fillers.items() if name.startswith("obj_")), + None, + ) + if verb is None or obj is None: + continue + items.append( + Item( + item_template_id=UUID(ft.template_id), + rendered_elements={"text": ft.rendered_text}, + item_metadata={ + "filled_template_id": str(ft.id), + "template_name": ft.template_name, + "verb_lemma": verb.lemma, + "object_lemma": obj.lemma, + "object_class": obj.features.get("semantic_class"), + "case": obj.features.get("case"), + }, + ) + ) + return items + + +def select_model(config: dict, override: str | None) -> dict: + """Return the model config to score with. + + Parameters + ---------- + config : dict + Parsed configuration. + override : str | None + Model name to force, ignoring the ``use_for_scoring`` flag. + + Returns + ------- + dict + The selected model's configuration entry. + + Raises + ------ + ValueError + If ``override`` names no configured model. + """ + models = config["items"]["models"] + if override is not None: + for model in models: + if model["name"] == override: + return model + raise ValueError(f"No configured model named '{override}'") + for model in models: + if model.get("use_for_scoring"): + return model + return models[0] + + +def build_scorer(model: dict, cache_dir: Path) -> ItemScorer: + """Build the scorer matching a model's type. + + Parameters + ---------- + model : dict + A model configuration entry (``name``, ``type``, ``device``). + cache_dir : Path + Directory for the model output cache. + + Returns + ------- + ItemScorer + A masked or causal scorer. + """ + device = model.get("device", "cpu") + if model["type"] == "masked": + return MaskedLanguageModelScorer( + model_name=model["name"], cache_dir=cache_dir, device=device + ) + return LanguageModelScorer( + model_name=model["name"], cache_dir=cache_dir, device=device + ) + + +def make_pairs(items: list[Item]) -> list[Item]: + """Pair object-bearing items by verb and object class, contrasting case. + + Groups items sharing a verb and object noun, forms every within-group pair, + and drops pairs whose two sentences render identically (case syncretism). + + Parameters + ---------- + items : list[Item] + Scored, object-bearing items. + + Returns + ------- + list[Item] + Forced-choice pairs tagged with case contrast and score difference. + """ + by_id = {str(item.id): item for item in items} + + def text_of(item: Item) -> str: + """Return an item's rendered sentence text.""" + return item.rendered_elements.get("text", "") + + pairs = create_filtered_forced_choice_items( + items=items, + group_by=lambda item: ( + item.item_metadata["verb_lemma"], + item.item_metadata["object_lemma"], + ), + n_alternatives=2, + combination_filter=lambda combo: text_of(combo[0]) != text_of(combo[1]), + extract_text=text_of, + ) + + enriched: list[Item] = [] + for pair in pairs: + ids = pair.item_metadata.get("source_item_ids", ()) + first, second = by_id.get(ids[0]), by_id.get(ids[1]) + if first is None or second is None: + continue + case_a = first.item_metadata["case"] + case_b = second.item_metadata["case"] + score_a = first.item_metadata["lm_score"] + score_b = second.item_metadata["lm_score"] + pair = pair.with_( + item_metadata={ + **pair.item_metadata, + "pair_type": "case_contrast", + "verb": first.item_metadata["verb_lemma"], + "object_lemma": first.item_metadata["object_lemma"], + "object_class": first.item_metadata["object_class"], + "case_a": case_a, + "case_b": case_b, + "contrast": "-".join(sorted([str(case_a), str(case_b)])), + "lm_score_a": score_a, + "lm_score_b": score_b, + "lm_score_diff": abs(score_a - score_b), + } + ) + enriched.append(pair) + return enriched + + +def main( + config_path: Path, + limit: int | None = None, + output: Path | None = None, + model_override: str | None = None, +) -> None: + """Score sentences, build case-contrast pairs, and write them out. + + Parameters + ---------- + config_path : Path + Path to the configuration file. + limit : int | None + Read at most this many filled sentences. + output : Path | None + Override the output path from the config. + model_override : str | None + Score with this model name instead of the configured default. + """ + config = load_config(config_path) + logging.basicConfig( + level=getattr(logging, config["logging"]["level"]), + format=config["logging"]["format"], + ) + + print_header("2AFC Pair Creation") + + cache_dir = BASE_DIR / config["paths"]["cache_dir"] + filled_path = BASE_DIR / config["template"]["output_path"] + output_path = output or (BASE_DIR / config["paths"]["2afc_pairs"]) + + filled = load_filled(filled_path, limit) + items = to_items(filled) + print_success(f"Loaded {len(items):,} object-bearing sentences") + if not items: + print_warning("No object-bearing sentences to pair.") + return + + model = select_model(config, model_override) + scorer = build_scorer(model, cache_dir) + print_info(f"Scoring with {model['name']} ({model['type']})") + with create_live_status("Scoring sentences..."): + scores = scorer.score_batch(items) + items = [ + item.with_(item_metadata={**item.item_metadata, "lm_score": score}) + for item, score in zip(items, scores, strict=True) + ] + + pairs = make_pairs(items) + print_success(f"Built {len(pairs):,} case-contrast pairs") + if not pairs: + print_warning("No pairs were created.") + return + + n_quantiles = config["lists"]["quantile_bins"] + quantiles = assign_quantiles_by_uuid( + item_ids=[pair.id for pair in pairs], + item_metadata={pair.id: pair.item_metadata for pair in pairs}, + property_key="lm_score_diff", + n_quantiles=n_quantiles, + stratify_by_key="contrast", + ) + pairs = [ + pair.with_(item_metadata={**pair.item_metadata, "quantile": quantiles[pair.id]}) + for pair in pairs + ] + + output_path.parent.mkdir(parents=True, exist_ok=True) + with output_path.open("w", encoding="utf-8") as f: + for pair in pairs: + f.write(pair.model_dump_json() + "\n") + display_file_stats(output_path, len(pairs), "pairs") + + +if __name__ == "__main__": + parser = argparse.ArgumentParser( + description="Create 2AFC case-contrast pairs from the filled sentences." + ) + parser.add_argument( + "--config", + type=Path, + default=BASE_DIR / "config.yaml", + help="Path to the configuration file.", + ) + parser.add_argument( + "--limit", + type=int, + default=None, + help="Read at most this many filled sentences (for testing).", + ) + parser.add_argument( + "--output", + type=Path, + default=None, + help="Override the output path from the config.", + ) + parser.add_argument( + "--model", + type=str, + default=None, + help="Score with this model name instead of the configured default.", + ) + args = parser.parse_args() + main( + config_path=args.config, + limit=args.limit, + output=args.output, + model_override=args.model, + ) diff --git a/gallery/ukr/argument_structure/utils/scoring.py b/gallery/ukr/argument_structure/utils/scoring.py new file mode 100644 index 0000000..999857b --- /dev/null +++ b/gallery/ukr/argument_structure/utils/scoring.py @@ -0,0 +1,97 @@ +"""Masked-language-model scorer. + +bead ships a causal-LM scorer (:class:`bead.items.scoring.LanguageModelScorer`) +but no masked-LM equivalent. This mirrors it for masked models, scoring each item +by the pseudo-log-likelihood of its text (mask each token in turn, sum the +predicted log-probabilities). +""" + +from __future__ import annotations + +from pathlib import Path +from typing import TYPE_CHECKING + +from bead.items.cache import ModelOutputCache +from bead.items.scoring import ItemScorer + +if TYPE_CHECKING: + from bead.items.adapters.huggingface import HuggingFaceMaskedLanguageModel + from bead.items.item import Item + + +class MaskedLanguageModelScorer(ItemScorer): + """Score items by masked-LM pseudo-log-likelihood. + + Parameters + ---------- + model_name : str + HuggingFace masked-LM identifier (e.g. a RoBERTa checkpoint). + cache_dir : Path | str | None + Directory for caching model outputs. Caching matters here because + pseudo-log-likelihood runs one forward pass per token. + device : str + Device to run the model on ("cpu", "cuda", "mps"). + text_key : str + Key in ``item.rendered_elements`` holding the text to score. + model_version : str + Version string for cache tracking. + """ + + def __init__( + self, + model_name: str, + cache_dir: Path | str | None = None, + device: str = "cpu", + text_key: str = "text", + model_version: str = "unknown", + ) -> None: + self.model_name = model_name + self.cache_dir = Path(cache_dir) if cache_dir else None + self.device = device + self.text_key = text_key + self.model_version = model_version + self._model: HuggingFaceMaskedLanguageModel | None = None + + @property + def model(self) -> HuggingFaceMaskedLanguageModel: + """Return the masked-LM adapter, loading it on first use. + + Returns + ------- + HuggingFaceMaskedLanguageModel + The cached masked-language-model adapter. + """ + if self._model is None: + from bead.items.adapters.huggingface import HuggingFaceMaskedLanguageModel + + cache = ModelOutputCache(cache_dir=self.cache_dir or Path(".cache/temp")) + self._model = HuggingFaceMaskedLanguageModel( + model_name=self.model_name, + cache=cache, + device=self.device, + model_version=self.model_version, + ) + return self._model + + def score(self, item: Item) -> float: + """Compute the pseudo-log-likelihood of an item's text. + + Parameters + ---------- + item : Item + Item to score. + + Returns + ------- + float + Pseudo-log-likelihood of the item's text under the model. + + Raises + ------ + KeyError + If ``text_key`` is absent from ``item.rendered_elements``. + """ + text = item.rendered_elements.get(self.text_key) + if text is None: + raise KeyError(f"Key '{self.text_key}' not found in item.rendered_elements") + return self.model.compute_log_probability(text) From fd7945e51a796b84f3d046add5930b11bb99651e Mon Sep 17 00:00:00 2001 From: Pavlo Kuchmiichuk Date: Tue, 21 Jul 2026 12:54:44 -0400 Subject: [PATCH 07/16] Selects unambiguous case forms and broadens the bleached noun set. --- .../argument_structure/generate_lexicons.py | 77 ++++++++++++++----- 1 file changed, 59 insertions(+), 18 deletions(-) diff --git a/gallery/ukr/argument_structure/generate_lexicons.py b/gallery/ukr/argument_structure/generate_lexicons.py index 07f94b4..658e4ee 100644 --- a/gallery/ukr/argument_structure/generate_lexicons.py +++ b/gallery/ukr/argument_structure/generate_lexicons.py @@ -15,14 +15,17 @@ The VESUM file (~7.5M rows) is parsed once and both lexicons are built from that single in-memory frame. -Some object nouns collapse case forms (e.g. подія shares one form for genitive -and dative, події). Those forms are still emitted, each tagged with its true -case; disambiguating collapsed cases in the rendered stimulus is left for later. +Object forms are chosen to be unambiguous: for each case a form is preferred +whose surface realizes no other case that could be read as an object. Two nouns +have no such form for the genitive and are kept deliberately: людина (людини is +also accusative plural) and випадок (випадку is also dative singular). Their +genitive carries a ``case_collides_with`` feature naming the competing reading. """ from __future__ import annotations import argparse +from collections import defaultdict from dataclasses import dataclass from pathlib import Path @@ -54,22 +57,28 @@ class NounSpec: Argument role the noun fills (``"subject"`` or ``"object"``). semantic_class : str Coarse meaning label carried through the pipeline. + gloss : str + English gloss, carried through as ``eng_gloss``. """ lemma: str role: str semantic_class: str + gloss: str -# One animate subject and four objects spanning the core meaning classes. The -# subject only ever appears in the nominative; objects appear in each case an -# object frame governs. +# One subject and a set of objects spanning the core meaning classes. The +# subject appears only in the nominative; objects in each case a frame governs. NOUN_SPECS: tuple[NounSpec, ...] = ( - NounSpec("людина", "subject", "animate"), - NounSpec("група", "object", "animate"), - NounSpec("предмет", "object", "inanimate_object"), - NounSpec("місце", "object", "location"), - NounSpec("подія", "object", "event"), + NounSpec("людина", "subject", "animate", "person"), + NounSpec("гурт", "object", "group", "group"), + NounSpec("людина", "object", "animate", "person"), + NounSpec("інструмент", "object", "inanimate_object", "instrument"), + NounSpec("зразок", "object", "abstract", "sample"), + NounSpec("двір", "object", "location", "yard"), + NounSpec("день", "object", "temporal", "day"), + NounSpec("шматок", "object", "quantity", "piece"), + NounSpec("випадок", "object", "event", "incident"), ) CASES_BY_ROLE: dict[str, tuple[str, ...]] = { @@ -77,6 +86,15 @@ class NounSpec: "object": ("ACC", "GEN", "DAT", "INS"), } +# Readings a bare form could carry in object position, used only to detect +# ambiguity. Plural cells count because a singular form that also spells a +# plural (людини is genitive singular and accusative plural) is ambiguous. +COMPETING_READINGS: frozenset[str] = frozenset( + f"{case}.{number}" + for case in ("ACC", "GEN", "DAT", "INS") + for number in ("SG", "PL") +) + def build_verb_lexicon( adapter: VesumUniMorphAdapter, frame: pd.DataFrame, limit: int | None = None @@ -160,22 +178,45 @@ def build_noun_lexicon( for spec in NOUN_SPECS: wanted = CASES_BY_ROLE[spec.role] rows = frame[frame["lemma"] == spec.lemma] - seen: set[str] = set() + + readings: dict[str, set[str]] = defaultdict(set) + candidates: dict[str, list[str]] = defaultdict(list) + parsed_by_form: dict[tuple[str, str], dict[str, str]] = {} for row in rows.itertuples(index=False): parsed = adapter._parse_features(str(row.features)) - case = parsed.get("case") - if parsed.get("pos") != "N" or parsed.get("number") != "SG": + case_name, number = parsed.get("case"), parsed.get("number") + if parsed.get("pos") != "N" or case_name is None or number is None: continue - if case is None or case not in wanted or case in seen: + form = str(row.form) + readings[form].add(f"{case_name}.{number}") + if number == "SG" and case_name in wanted: + if form not in candidates[case_name]: + candidates[case_name].append(form) + parsed_by_form[case_name, form] = parsed + + for case_name in wanted: + forms = candidates.get(case_name) + if not forms: continue - seen.add(case) - features = dict(parsed) + cell = f"{case_name}.SG" + unambiguous = [ + form + for form in forms + if readings[form] & COMPETING_READINGS == {cell} + ] + form = unambiguous[0] if unambiguous else forms[0] + + features = dict(parsed_by_form[case_name, form]) features["semantic_class"] = spec.semantic_class features["role"] = spec.role + features["eng_gloss"] = spec.gloss + collisions = sorted((readings[form] & COMPETING_READINGS) - {cell}) + if collisions: + features["case_collides_with"] = ",".join(collisions) items.append( LexicalItem( lemma=spec.lemma, - form=str(row.form), + form=form, language_code=LANGUAGE, features=features, source="UniMorph:ukr.xz", From c74580ba002ad5252738e123ddb67af457d5efde Mon Sep 17 00:00:00 2001 From: Pavlo Kuchmiichuk Date: Tue, 21 Jul 2026 20:36:47 -0400 Subject: [PATCH 08/16] Adds frequency-based verb selection with a committed wordfreq-derived table. --- .../argument_structure/build_frequencies.py | 61 + .../ukr/argument_structure/fill_templates.py | 47 +- .../resources/verb_frequencies.csv | 4084 +++++++++++++++++ .../ukr/argument_structure/utils/frequency.py | 64 + 4 files changed, 4244 insertions(+), 12 deletions(-) create mode 100644 gallery/ukr/argument_structure/build_frequencies.py create mode 100644 gallery/ukr/argument_structure/resources/verb_frequencies.csv create mode 100644 gallery/ukr/argument_structure/utils/frequency.py diff --git a/gallery/ukr/argument_structure/build_frequencies.py b/gallery/ukr/argument_structure/build_frequencies.py new file mode 100644 index 0000000..891d08e --- /dev/null +++ b/gallery/ukr/argument_structure/build_frequencies.py @@ -0,0 +1,61 @@ +#!/usr/bin/env python3 +"""Write the verb frequency table used to choose which verbs to test. + +Run once, with wordfreq supplied for the invocation:: + + uv run --project --with wordfreq python build_frequencies.py + +Frequency is the Zipf value of the infinitive, from the wordfreq package +(Ukrainian, which wordfreq keys by the ISO 639-1 code "uk"). Summing over a +verb's whole paradigm was tried and rejected: infinitives in -ти are distinctive, +while finite forms collide with frequent homographs, so колоти collects the count +of the conjunction коли and маяти collects має from мати. + +The generated CSV is committed so the pipeline needs no wordfreq dependency and +the ranking stays fixed across runs. Unattested lemmas are omitted and read +as 0.0. +""" + +from __future__ import annotations + +import csv +from pathlib import Path + +from wordfreq import zipf_frequency + +from bead.cli.display import print_header, print_success +from bead.resources.lexicon import Lexicon + +BASE_DIR = Path(__file__).parent +WORDFREQ_LANGUAGE = "uk" + + +def main() -> None: + """Rank every verb lemma by the Zipf frequency of its infinitive.""" + print_header("Verb Frequencies (wordfreq)") + + lexicon = Lexicon.from_jsonl(str(BASE_DIR / "lexicons" / "verbs.jsonl"), "verbs") + lemmas = {item.lemma for item in lexicon.items} + attested = sorted( + ( + (lemma, zipf_frequency(lemma, WORDFREQ_LANGUAGE)) + for lemma in lemmas + ), + key=lambda row: (-row[1], row[0]), + ) + attested = [(lemma, zipf) for lemma, zipf in attested if zipf > 0.0] + + output_path = BASE_DIR / "resources" / "verb_frequencies.csv" + output_path.parent.mkdir(parents=True, exist_ok=True) + with output_path.open("w", encoding="utf-8", newline="") as f: + writer = csv.writer(f) + writer.writerow(["lemma", "zipf"]) + writer.writerows((lemma, f"{zipf:.2f}") for lemma, zipf in attested) + + print_success( + f"Wrote {len(attested):,} attested lemmas of {len(lemmas):,} to {output_path}" + ) + + +if __name__ == "__main__": + main() diff --git a/gallery/ukr/argument_structure/fill_templates.py b/gallery/ukr/argument_structure/fill_templates.py index bd5ea08..93601f0 100644 --- a/gallery/ukr/argument_structure/fill_templates.py +++ b/gallery/ukr/argument_structure/fill_templates.py @@ -16,6 +16,7 @@ from pathlib import Path import yaml +from utils.frequency import most_frequent from utils.renderers import UkrainianRenderer from bead.cli.display import ( @@ -108,8 +109,10 @@ def build_strategy(config: dict) -> MixedFillingStrategy: return MixedFillingStrategy(slot_strategies=slot_strategies) -def limit_verbs(lexicon: Lexicon, limit: int) -> Lexicon: - """Return a lexicon keeping only the first ``limit`` unique verb lemmas. +def limit_verbs( + lexicon: Lexicon, limit: int, *, by_frequency: bool = False +) -> Lexicon: + """Return a lexicon keeping only ``limit`` unique verb lemmas. Parameters ---------- @@ -117,19 +120,25 @@ def limit_verbs(lexicon: Lexicon, limit: int) -> Lexicon: Verb lexicon. limit : int Number of unique lemmas to keep. + by_frequency : bool + Keep the most frequent lemmas instead of the first ones, since VESUM is + alphabetical and the first lemmas are rare rather than everyday verbs. Returns ------- Lexicon - Lexicon restricted to the first ``limit`` lemmas. + Lexicon restricted to ``limit`` lemmas. """ - lemmas: list[str] = [] - for item in lexicon.items: - if item.lemma not in lemmas: - lemmas.append(item.lemma) - if len(lemmas) >= limit: - break - allowed = set(lemmas) + if by_frequency: + allowed = set(most_frequent((item.lemma for item in lexicon.items), limit)) + else: + lemmas: list[str] = [] + for item in lexicon.items: + if item.lemma not in lemmas: + lemmas.append(item.lemma) + if len(lemmas) >= limit: + break + allowed = set(lemmas) return lexicon.filter(lambda item: item.lemma in allowed) @@ -138,6 +147,8 @@ def main( limit: int | None = None, max_per_template: int | None = None, output: Path | None = None, + *, + by_frequency: bool = False, ) -> None: """Fill every frame and write the sentences to the output path. @@ -151,6 +162,8 @@ def main( Keep at most this many sentences per frame. output : Path | None Override the output path from the config. + by_frequency : bool + Select the most frequent verbs rather than the first ones. """ config = load_config(config_path) logging.basicConfig( @@ -174,8 +187,12 @@ def main( str(BASE_DIR / lex_config["path"]), lex_config["name"] ) if limit is not None and lex_config["name"] == "verbs": - lexicon = limit_verbs(lexicon, limit) - print_warning(f"Limited verbs to {limit} lemmas ({len(lexicon)} forms)") + lexicon = limit_verbs(lexicon, limit, by_frequency=by_frequency) + selection = "most frequent" if by_frequency else "first" + print_warning( + f"Limited verbs to the {selection} {limit} lemmas " + f"({len(lexicon)} forms)" + ) lexicons.append(lexicon) print_info(f"Loaded {len(lexicon)} items from {lex_config['name']}") @@ -252,10 +269,16 @@ def main( default=None, help="Override the output path from the config.", ) + parser.add_argument( + "--by-frequency", + action="store_true", + help="Select the most frequent verbs rather than the first ones.", + ) args = parser.parse_args() main( config_path=args.config, limit=args.limit, max_per_template=args.max_per_template, output=args.output, + by_frequency=args.by_frequency, ) diff --git a/gallery/ukr/argument_structure/resources/verb_frequencies.csv b/gallery/ukr/argument_structure/resources/verb_frequencies.csv new file mode 100644 index 0000000..c1c3a48 --- /dev/null +++ b/gallery/ukr/argument_structure/resources/verb_frequencies.csv @@ -0,0 +1,4084 @@ +lemma,zipf +бути,6.07 +робити,5.61 +мати,5.45 +знати,5.30 +жити,5.27 +працювати,5.23 +говорити,5.18 +читати,5.13 +йти,5.11 +брати,5.07 +бачити,5.06 +використовувати,5.04 +писати,4.99 +дивитися,4.93 +чекати,4.91 +вести,4.84 +грати,4.84 +шукати,4.84 +їхати,4.83 +думати,4.81 +їсти,4.77 +проводити,4.75 +виконувати,4.74 +спати,4.74 +ходити,4.73 +чути,4.73 +пити,4.70 +платити,4.69 +захищати,4.68 +приймати,4.68 +тримати,4.68 +сидіти,4.66 +слухати,4.66 +казати,4.65 +вважати,4.64 +займатися,4.64 +боротися,4.63 +діяти,4.63 +розуміти,4.59 +вірити,4.56 +отримувати,4.56 +керувати,4.55 +купувати,4.55 +будувати,4.54 +називати,4.54 +відповідати,4.53 +розмовляти,4.53 +воювати,4.52 +підтримувати,4.52 +створювати,4.52 +продовжувати,4.51 +давати,4.49 +вирішувати,4.47 +вбивати,4.46 +виходити,4.46 +стріляти,4.46 +виглядати,4.45 +починати,4.45 +вчити,4.44 +допомагати,4.44 +контролювати,4.44 +бити,4.43 +змінювати,4.43 +надавати,4.41 +нести,4.41 +їздити,4.41 +іти,4.40 +стояти,4.39 +викликати,4.38 +користуватися,4.37 +палати,4.37 +вимагати,4.36 +носити,4.36 +розглядати,4.36 +відчувати,4.35 +розповідати,4.35 +ставити,4.35 +здійснювати,4.34 +вчитися,4.33 +любити,4.33 +пам'ятати,4.33 +спілкуватися,4.32 +готувати,4.31 +показувати,4.31 +продавати,4.30 +спостерігати,4.30 +виступати,4.29 +знімати,4.29 +збирати,4.28 +залишатися,4.27 +служити,4.27 +довіряти,4.26 +вивчати,4.25 +очікувати,4.25 +забувати,4.24 +боятися,4.23 +плакати,4.23 +подавати,4.23 +просити,4.23 +застосовувати,4.22 +звати,4.22 +судити,4.22 +втрачати,4.21 +заробляти,4.21 +існувати,4.21 +зберігати,4.20 +співати,4.20 +витрачати,4.18 +голосувати,4.18 +пояснювати,4.18 +залишати,4.17 +впливати,4.16 +віддавати,4.16 +відкривати,4.16 +почитати,4.15 +обирати,4.14 +радіти,4.14 +терпіти,4.14 +годувати,4.13 +лікувати,4.12 +утримувати,4.12 +вибирати,4.11 +літати,4.11 +поважати,4.11 +згадувати,4.10 +кричати,4.10 +обговорювати,4.10 +перевіряти,4.10 +реагувати,4.10 +встановлювати,4.09 +вживати,4.08 +міняти,4.08 +розслідувати,4.07 +брехати,4.06 +протистояти,4.06 +сприяти,4.06 +вводити,4.05 +розвивати,4.05 +розраховувати,4.05 +сприймати,4.05 +тікати,4.05 +намагатися,4.04 +реалізувати,4.04 +рухатися,4.04 +танцювати,4.04 +порушувати,4.03 +проходити,4.03 +передавати,4.02 +рости,4.02 +красти,4.01 +мовити,4.01 +сподіватися,4.01 +заходити,4.00 +кидати,4.00 +критикувати,4.00 +плювати,4.00 +рятувати,4.00 +визнавати,3.99 +дотримуватися,3.99 +повертатися,3.99 +складати,3.99 +використовуватися,3.98 +гарантувати,3.97 +заважати,3.97 +звертатися,3.97 +малювати,3.97 +палити,3.97 +чинити,3.97 +видавати,3.96 +враховувати,3.96 +дякувати,3.96 +ліквідувати,3.96 +питати,3.96 +сплачувати,3.96 +з'являтися,3.95 +забезпечувати,3.95 +ловити,3.95 +переходити,3.95 +битися,3.94 +виробляти,3.94 +забирати,3.94 +обіймати,3.94 +поводитися,3.94 +святкувати,3.94 +ставитися,3.94 +стверджувати,3.94 +вміти,3.93 +дихати,3.93 +доводити,3.93 +здавати,3.93 +рахувати,3.93 +сходити,3.93 +уникати,3.93 +бігати,3.92 +лежати,3.92 +мовчати,3.92 +повторювати,3.92 +співпрацювати,3.92 +тривати,3.92 +володіти,3.91 +звільняти,3.91 +зустрічатися,3.91 +визначати,3.90 +випускати,3.90 +подорожувати,3.90 +призначати,3.90 +розвиватися,3.90 +розказувати,3.90 +пишатися,3.89 +представляти,3.89 +приховувати,3.89 +стежити,3.89 +тиснути,3.89 +блокувати,3.88 +викладати,3.88 +доглядати,3.88 +повертати,3.88 +сміятися,3.88 +ставати,3.88 +додавати,3.87 +здаватися,3.87 +торгувати,3.87 +відправляти,3.86 +готуватися,3.86 +знаходити,3.86 +реформувати,3.86 +триматися,3.86 +гуляти,3.85 +молитися,3.85 +навчати,3.85 +навчатися,3.85 +піднімати,3.85 +бігти,3.84 +відбуватися,3.84 +коштувати,3.84 +мріяти,3.84 +порівнювати,3.84 +поширювати,3.84 +прибирати,3.83 +приносити,3.83 +вступати,3.82 +коментувати,3.82 +плавати,3.82 +ризикувати,3.82 +відвідувати,3.81 +відпочивати,3.81 +зростати,3.81 +оцінювати,3.81 +копати,3.80 +перебувати,3.80 +повідомляти,3.80 +влаштовувати,3.79 +містити,3.79 +означати,3.79 +срати,3.79 +відстоювати,3.78 +компенсувати,3.78 +лізти,3.78 +аналізувати,3.77 +вказувати,3.77 +закривати,3.77 +сперечатися,3.77 +тягнути,3.77 +інвестувати,3.77 +виховувати,3.76 +карати,3.76 +оплачувати,3.76 +ініціювати,3.76 +входити,3.75 +записувати,3.75 +мити,3.75 +насрати,3.75 +підходити,3.75 +творити,3.75 +вмирати,3.74 +ділити,3.74 +атакувати,3.73 +включати,3.73 +грабувати,3.73 +знищувати,3.73 +купляти,3.73 +плутати,3.73 +пробувати,3.73 +публікувати,3.73 +видаляти,3.72 +вставати,3.72 +досягати,3.72 +займати,3.72 +класти,3.72 +ігнорувати,3.72 +відновлювати,3.71 +правити,3.71 +пускати,3.71 +слідкувати,3.71 +формувати,3.71 +виділяти,3.70 +втручатися,3.70 +економити,3.70 +залежати,3.70 +заперечувати,3.70 +звинувачувати,3.70 +везти,3.69 +пасти,3.69 +чіпати,3.69 +гратися,3.68 +обслуговувати,3.68 +охороняти,3.68 +перевищувати,3.68 +переглядати,3.68 +переживати,3.68 +приходити,3.68 +ремонтувати,3.68 +вносити,3.67 +домовлятися,3.67 +нагадувати,3.67 +підвищувати,3.67 +стрибати,3.67 +дарувати,3.66 +дбати,3.66 +ділитися,3.66 +залучати,3.66 +переслідувати,3.66 +садити,3.66 +управляти,3.66 +ідентифікувати,3.66 +демонструвати,3.65 +дзвонити,3.65 +друкувати,3.65 +мислити,3.65 +надсилати,3.65 +падати,3.65 +планувати,3.65 +свідчити,3.65 +становити,3.65 +хотіти,3.65 +гнати,3.64 +допускати,3.64 +курити,3.64 +ненавидіти,3.64 +постачати,3.64 +піклуватися,3.64 +страждати,3.64 +фінансувати,3.64 +виводити,3.63 +викидати,3.63 +виявляти,3.63 +слідувати,3.63 +берегти,3.62 +відмовлятися,3.62 +дозволяти,3.62 +жартувати,3.62 +жерти,3.62 +звертати,3.62 +перевозити,3.62 +сумувати,3.62 +висловлювати,3.61 +народжувати,3.61 +пропонувати,3.61 +уявляти,3.61 +ховати,3.61 +возити,3.60 +вітати,3.60 +забороняти,3.60 +задавати,3.60 +затримувати,3.60 +конкурувати,3.60 +кохати,3.60 +належати,3.60 +поспішати,3.60 +турбуватися,3.60 +вважатися,3.59 +зводити,3.59 +змінюватися,3.59 +маніпулювати,3.59 +перемагати,3.59 +протидіяти,3.59 +різати,3.59 +вивозити,3.58 +вирощувати,3.58 +відрізати,3.58 +псувати,3.58 +редагувати,3.58 +розбиратися,3.58 +чистити,3.58 +відпускати,3.57 +застосовуватися,3.57 +змушувати,3.57 +помирати,3.57 +тренувати,3.57 +виготовляти,3.56 +випробувати,3.56 +відкликати,3.56 +збиратися,3.56 +зупинятися,3.56 +летіти,3.56 +переносити,3.56 +посилати,3.56 +підписувати,3.56 +розглядатися,3.56 +виносити,3.55 +впроваджувати,3.55 +вішати,3.55 +досліджувати,3.55 +насолоджуватися,3.55 +помічати,3.55 +розробляти,3.55 +руйнувати,3.55 +усвідомлювати,3.55 +вкладати,3.54 +збільшувати,3.54 +припиняти,3.54 +функціонувати,3.54 +ховатися,3.54 +горіти,3.53 +набирати,3.53 +називатися,3.53 +обмежувати,3.53 +постити,3.53 +розводити,3.53 +скаржитися,3.53 +сідати,3.53 +виправляти,3.52 +відступати,3.52 +купатися,3.52 +лікуватися,3.52 +ображати,3.52 +перекладати,3.52 +переконувати,3.52 +хвилюватися,3.52 +змагатися,3.51 +ламати,3.51 +перейматися,3.51 +поводити,3.51 +позбавляти,3.51 +почувати,3.51 +втілювати,3.50 +заповнювати,3.50 +знаходитися,3.50 +зустрічати,3.50 +орендувати,3.50 +виставляти,3.49 +долати,3.49 +заводити,3.49 +захищатися,3.49 +захоплювати,3.49 +легалізувати,3.49 +перетворювати,3.49 +смоктати,3.49 +цілувати,3.49 +варити,3.48 +крутити,3.48 +організовувати,3.48 +прагнути,3.48 +сваритися,3.48 +цінувати,3.48 +відкладати,3.47 +дружити,3.47 +експортувати,3.47 +оптимізувати,3.47 +стимулювати,3.47 +турбувати,3.47 +закінчувати,3.46 +констатувати,3.46 +розширювати,3.46 +балотуватися,3.45 +вигадувати,3.45 +почуватися,3.45 +скликати,3.45 +стримувати,3.45 +відрізнятися,3.44 +закликати,3.44 +злити,3.44 +претендувати,3.44 +втікати,3.43 +завдавати,3.43 +лизати,3.43 +наступати,3.43 +примушувати,3.43 +спалювати,3.43 +вдавати,3.42 +вирізати,3.42 +з'їздити,3.42 +запрошувати,3.42 +нападати,3.42 +пересуватися,3.42 +покидати,3.42 +протестувати,3.42 +проявляти,3.42 +виживати,3.41 +модернізувати,3.41 +роздавати,3.41 +сумніватися,3.41 +бажати,3.40 +дивувати,3.40 +допомогати,3.40 +запускати,3.40 +здійснюватися,3.40 +полювати,3.40 +помилятися,3.40 +скидати,3.40 +супроводжувати,3.40 +розкривати,3.39 +скасовувати,3.39 +вмикати,3.38 +волати,3.38 +генерувати,3.38 +дискутувати,3.38 +забивати,3.38 +зупиняти,3.38 +набувати,3.38 +перешкоджати,3.38 +погрожувати,3.38 +подобатися,3.38 +поєднувати,3.38 +проживати,3.38 +пропускати,3.38 +транслювати,3.38 +укладати,3.38 +виміряти,3.37 +заохочувати,3.37 +лишатися,3.37 +мандрувати,3.37 +миритися,3.37 +мінімізувати,3.37 +надходити,3.37 +радувати,3.37 +саджати,3.37 +судитися,3.37 +вимикати,3.36 +дивуватися,3.36 +жаліти,3.36 +загрожувати,3.36 +кататися,3.36 +наводити,3.36 +розміщувати,3.36 +виплачувати,3.35 +водити,3.35 +відходити,3.35 +голодувати,3.35 +знайомитися,3.35 +ліпити,3.35 +приводити,3.35 +приїжджати,3.35 +розділяти,3.35 +штрафувати,3.35 +відрізняти,3.34 +недооцінювати,3.34 +одягати,3.34 +орієнтуватися,3.34 +популяризувати,3.34 +проводитися,3.34 +підкорятися,3.34 +сіяти,3.34 +валити,3.33 +виникати,3.33 +виїжджати,3.33 +відзначати,3.33 +одержувати,3.33 +регулювати,3.33 +реєструвати,3.33 +тренуватися,3.33 +дестабілізувати,3.32 +обробляти,3.32 +оперувати,3.32 +тягати,3.32 +цікавитися,3.32 +шанувати,3.32 +ганяти,3.31 +координувати,3.31 +лягати,3.31 +наполягати,3.31 +оновлювати,3.31 +розганяти,3.31 +складатися,3.31 +відмовляти,3.30 +забиратися,3.30 +запитувати,3.30 +зважати,3.30 +зрізати,3.30 +лопати,3.30 +наслідувати,3.30 +панувати,3.30 +потрапляти,3.30 +підніматися,3.30 +рекомендувати,3.30 +ухвалювати,3.30 +відбирати,3.29 +доставляти,3.29 +закидати,3.29 +розбирати,3.29 +розповсюджувати,3.29 +розрізняти,3.29 +імпортувати,3.29 +гасити,3.28 +заливати,3.28 +збивати,3.28 +обрізати,3.28 +панікувати,3.28 +прощатися,3.28 +фотографувати,3.28 +хворіти,3.28 +балакати,3.27 +евакуювати,3.27 +посилатися,3.27 +просувати,3.27 +пізнавати,3.27 +штурмувати,3.27 +вдаватися,3.26 +домагатися,3.26 +жертвувати,3.26 +кохатися,3.26 +ночувати,3.26 +оприлюднювати,3.26 +пхати,3.26 +інтегрувати,3.26 +дратувати,3.25 +завантажувати,3.25 +пекти,3.25 +передбачати,3.25 +піддаватися,3.25 +слугувати,3.25 +боронити,3.24 +нарощувати,3.24 +покладатися,3.24 +посміхатися,3.24 +фарбувати,3.24 +бухати,3.23 +давити,3.23 +зміцнювати,3.23 +лякати,3.23 +розголошувати,3.23 +споживати,3.23 +ускладнювати,3.23 +демонтувати,3.22 +емігрувати,3.22 +здобувати,3.22 +покликати,3.22 +придумувати,3.22 +приділяти,3.22 +транспортувати,3.22 +утворювати,3.22 +чхати,3.22 +вирішуватися,3.21 +декларувати,3.21 +копіювати,3.21 +міркувати,3.21 +поливати,3.21 +поширюватися,3.21 +прикривати,3.21 +проникати,3.21 +рвати,3.21 +сцяти,3.21 +будити,3.20 +дізнаватися,3.20 +обіцяти,3.20 +оформляти,3.20 +починатися,3.20 +прислухатися,3.20 +рухати,3.20 +штовхати,3.20 +активувати,3.19 +виконуватися,3.19 +витягувати,3.19 +відбивати,3.19 +дискредитувати,3.19 +капітулювати,3.19 +милуватися,3.19 +переводити,3.19 +продовжуватися,3.19 +старатися,3.19 +трактувати,3.19 +формуватися,3.19 +верстати,3.18 +визволяти,3.18 +висвітлювати,3.18 +відігравати,3.18 +гнити,3.18 +зникати,3.18 +нав'язати,3.18 +покращувати,3.18 +розпоряджатися,3.18 +торкатися,3.18 +убивати,3.18 +відміняти,3.17 +гадати,3.17 +депортувати,3.17 +оголошувати,3.17 +одружуватися,3.17 +інформувати,3.17 +блювати,3.16 +взаємодіяти,3.16 +вибачатися,3.16 +вимовляти,3.16 +доносити,3.16 +перетинати,3.16 +поступати,3.16 +ревнувати,3.16 +сягати,3.16 +шантажувати,3.16 +імітувати,3.16 +активізувати,3.15 +експериментувати,3.15 +експлуатувати,3.15 +зволікати,3.15 +прокидатися,3.15 +харчуватися,3.15 +вболівати,3.14 +витримувати,3.14 +вчиняти,3.14 +відволікати,3.14 +кусати,3.14 +мучити,3.14 +накопичувати,3.14 +нити,3.14 +обходити,3.14 +перекривати,3.14 +перетворюватися,3.14 +покривати,3.14 +презентувати,3.14 +провокувати,3.14 +утилізувати,3.14 +впадати,3.13 +переїжджати,3.13 +походити,3.13 +практикувати,3.13 +стосуватися,3.13 +стукати,3.13 +тягти,3.13 +узурпувати,3.13 +хвилювати,3.13 +вдягати,3.12 +вислухати,3.12 +наймати,3.12 +опускати,3.12 +очолювати,3.12 +спостерігатися,3.12 +бачитися,3.11 +нейтралізувати,3.11 +радити,3.11 +стягувати,3.11 +телефонувати,3.11 +фіксувати,3.11 +боліти,3.10 +вертатися,3.10 +висувати,3.10 +відтворювати,3.10 +клеїти,3.10 +прати,3.10 +принижувати,3.10 +розмножуватися,3.10 +снідати,3.10 +тратити,3.10 +вечеряти,3.09 +видобувати,3.09 +задовольняти,3.09 +звикати,3.09 +знижуватися,3.09 +зніматися,3.09 +ображатися,3.09 +опиратися,3.09 +перераховувати,3.09 +прикладати,3.09 +підкреслювати,3.09 +тикати,3.09 +бомбити,3.08 +ввозити,3.08 +вставляти,3.08 +гордитися,3.08 +долучатися,3.08 +завершувати,3.08 +запроваджувати,3.08 +зберігатися,3.08 +злітати,3.08 +зносити,3.08 +обмінюватися,3.08 +поступатися,3.08 +піддавати,3.08 +ухилятися,3.08 +вистачати,3.07 +відключати,3.07 +добувати,3.07 +керуватися,3.07 +кликати,3.07 +командувати,3.07 +надаватися,3.07 +переміщатися,3.07 +просуватися,3.07 +спиратися,3.07 +інтерпретувати,3.07 +виключати,3.06 +випікати,3.06 +відображати,3.06 +допитувати,3.06 +жалкувати,3.06 +зав'язувати,3.06 +збільшуватися,3.06 +звітувати,3.06 +зменшувати,3.06 +об'єднуватися,3.06 +позичати,3.06 +бойкотувати,3.05 +висаджувати,3.05 +дурити,3.05 +класифікувати,3.05 +мотивувати,3.05 +натискати,3.05 +обертатися,3.05 +докладати,3.04 +закуповувати,3.04 +замовляти,3.04 +молоти,3.04 +надихати,3.04 +направляти,3.04 +описувати,3.04 +прикидатися,3.04 +припускати,3.04 +проектувати,3.04 +підбирати,3.04 +ізолювати,3.04 +вестися,3.03 +вигравати,3.03 +відводити,3.03 +віджимати,3.03 +звучати,3.03 +конфіскувати,3.03 +курсувати,3.03 +провадити,3.03 +підозрювати,3.03 +рахуватися,3.03 +шити,3.03 +вдягатися,3.02 +вилазити,3.02 +засуджувати,3.02 +зневажати,3.02 +мешкати,3.02 +мобілізувати,3.02 +повзати,3.02 +вирушати,3.01 +витягати,3.01 +відноситися,3.01 +дорівнювати,3.01 +засмучувати,3.01 +привертати,3.01 +рубати,3.01 +асоціюватися,3.00 +вишивати,3.00 +значити,3.00 +розважати,3.00 +соромитися,3.00 +співчувати,3.00 +шпигувати,3.00 +в'язати,2.99 +виражати,2.99 +диктувати,2.99 +зав'язати,2.99 +заміняти,2.99 +махати,2.99 +оберігати,2.99 +прикрашати,2.99 +сприйматися,2.99 +утримуватися,2.99 +формулювати,2.99 +вдосконалювати,2.98 +виганяти,2.98 +відстежувати,2.98 +добиватися,2.98 +збагачувати,2.98 +знайомити,2.98 +ковтати,2.98 +обстрілювати,2.98 +погоджуватися,2.98 +приміряти,2.98 +розслаблятися,2.98 +аплодувати,2.97 +вимірювати,2.97 +лаяти,2.97 +нудьгувати,2.97 +поставляти,2.97 +проповідувати,2.97 +стікати,2.97 +сушити,2.97 +банити,2.96 +виділятися,2.96 +вилучати,2.96 +виписувати,2.96 +відносити,2.96 +відпрацьовувати,2.96 +завойовувати,2.96 +переказувати,2.96 +переписувати,2.96 +стригти,2.96 +цікавити,2.96 +гризти,2.95 +добиратися,2.95 +домінувати,2.95 +діставати,2.95 +наповнювати,2.95 +нищити,2.95 +патякати,2.95 +передаватися,2.95 +призводити,2.95 +ратифікувати,2.95 +сортувати,2.95 +утворюватися,2.95 +вернути,2.94 +вражати,2.94 +відкидати,2.94 +митися,2.94 +опікуватися,2.94 +програвати,2.94 +розподіляти,2.94 +синтезувати,2.94 +списувати,2.94 +сповідувати,2.94 +спонукати,2.94 +викривати,2.93 +висіти,2.93 +гріти,2.93 +змішувати,2.93 +прощати,2.93 +споглядати,2.93 +узагальнювати,2.93 +чіплятися,2.93 +благати,2.92 +запобігати,2.92 +знущатися,2.92 +лаятися,2.92 +лити,2.92 +накладати,2.92 +помагати,2.92 +справлятися,2.92 +спускатися,2.92 +висловлюватися,2.91 +витирати,2.91 +встановлюватися,2.91 +відновлюватися,2.91 +з'ясовувати,2.91 +заправляти,2.91 +косити,2.91 +розпускати,2.91 +топтати,2.91 +змивати,2.90 +качати,2.90 +ліквідовувати,2.90 +мірятися,2.90 +надягати,2.90 +пакувати,2.90 +розширюватися,2.90 +усувати,2.90 +зв'язуватися,2.89 +мутити,2.89 +наближатися,2.89 +нагрівати,2.89 +націоналізувати,2.89 +підіймати,2.89 +рекламувати,2.89 +торгуватися,2.89 +вакцинуватися,2.88 +впускати,2.88 +відбувати,2.88 +відставати,2.88 +гаяти,2.88 +гинути,2.88 +заспокоювати,2.88 +попереджати,2.88 +реанімувати,2.88 +розпізнавати,2.88 +стартувати,2.88 +створюватися,2.88 +трахати,2.88 +бикувати,2.87 +видаватися,2.87 +нав'язувати,2.87 +плисти,2.87 +шкодити,2.87 +вибивати,2.86 +залучатися,2.86 +катувати,2.86 +клацати,2.86 +прокладати,2.86 +реалізовувати,2.86 +рити,2.86 +розрізати,2.86 +випробовувати,2.85 +жалітися,2.85 +захоплюватися,2.85 +канати,2.85 +міряти,2.85 +ототожнювати,2.85 +перебільшувати,2.85 +пристосовуватися,2.85 +ридати,2.85 +стабілізувати,2.85 +встигати,2.84 +заздрити,2.84 +заморожувати,2.84 +кваліфікувати,2.84 +маневрувати,2.84 +наглядати,2.84 +обманювати,2.84 +побоюватися,2.84 +повчати,2.84 +пред'являти,2.84 +приєднуватися,2.84 +розташовуватися,2.84 +текти,2.84 +братися,2.83 +виправдовувати,2.83 +доплачувати,2.83 +коритися,2.83 +нервувати,2.83 +переробляти,2.83 +тонути,2.83 +цитувати,2.83 +вдихати,2.82 +зображати,2.82 +мстити,2.82 +позначати,2.82 +практикуватися,2.82 +притягувати,2.82 +проявлятися,2.82 +розбивати,2.82 +розплачуватися,2.82 +розцінювати,2.82 +топити,2.82 +шкодувати,2.82 +бувати,2.81 +відсилати,2.81 +заарештовувати,2.81 +засідати,2.81 +контактувати,2.81 +наказувати,2.81 +нехтувати,2.81 +переривати,2.81 +пов'язувати,2.81 +подаватися,2.81 +прибувати,2.81 +травмувати,2.81 +евакуюватися,2.80 +мерзнути,2.80 +поклонятися,2.80 +реконструювати,2.80 +спрямовувати,2.80 +учитися,2.80 +виселяти,2.79 +жувати,2.79 +залякувати,2.79 +записуватися,2.79 +кормити,2.79 +обороняти,2.79 +опускатися,2.79 +продаватися,2.79 +розривати,2.79 +розсилати,2.79 +святити,2.79 +спішити,2.79 +трусити,2.79 +будуватися,2.78 +замінювати,2.78 +зображувати,2.78 +кидатися,2.78 +кружляти,2.78 +лазити,2.78 +розлучатися,2.78 +рушити,2.78 +спускати,2.78 +затверджувати,2.77 +каятися,2.77 +переміщуватися,2.77 +поглинати,2.77 +підносити,2.77 +підштовхувати,2.77 +слухатися,2.77 +веселитися,2.76 +ветувати,2.76 +ворушитися,2.76 +вшановувати,2.76 +відправлятися,2.76 +доходити,2.76 +звалювати,2.76 +обговорюватися,2.76 +памятати,2.76 +розгортати,2.76 +тішити,2.76 +важити,2.75 +відслідковувати,2.75 +гладити,2.75 +злитися,2.75 +коливатися,2.75 +культивувати,2.75 +мститися,2.75 +мінятися,2.75 +пильнувати,2.75 +пробачати,2.75 +підробляти,2.75 +саботувати,2.75 +шуміти,2.75 +відбудовувати,2.74 +засинати,2.74 +засипати,2.74 +заявляти,2.74 +мішати,2.74 +одягатися,2.74 +переймати,2.74 +повзти,2.74 +програмувати,2.74 +розважатися,2.74 +скорочуватися,2.74 +інтегруватися,2.74 +висміювати,2.73 +заряджати,2.73 +обходитися,2.73 +озвучувати,2.73 +освоювати,2.73 +рибалити,2.73 +спростовувати,2.73 +адаптуватися,2.72 +анулювати,2.72 +блукати,2.72 +закриватися,2.72 +наносити,2.72 +паритися,2.72 +пиляти,2.72 +попадати,2.72 +приваблювати,2.72 +публікуватися,2.72 +підглядати,2.72 +розв'язувати,2.72 +чіпляти,2.72 +глушити,2.71 +завозити,2.71 +закопувати,2.71 +запалювати,2.71 +зганяти,2.71 +зменшуватися,2.71 +поспівати,2.71 +розгрібати,2.71 +розпитувати,2.71 +смикати,2.71 +умовляти,2.71 +варіюватися,2.70 +дрочити,2.70 +засмучуватися,2.70 +перерізати,2.70 +пригнічувати,2.70 +протікати,2.70 +підставляти,2.70 +фільтрувати,2.70 +визначатися,2.69 +вноситися,2.69 +збігатися,2.69 +знижувати,2.69 +об'єднувати,2.69 +обмежуватися,2.69 +обідати,2.69 +охоплювати,2.69 +патрулювати,2.69 +перечитувати,2.69 +розстрілювати,2.69 +світитися,2.69 +червоніти,2.69 +вибудовувати,2.68 +вислуховувати,2.68 +відкриватися,2.68 +господарювати,2.68 +держати,2.68 +накручувати,2.68 +оглядати,2.68 +приватизувати,2.68 +пірнати,2.68 +розшукувати,2.68 +ростити,2.68 +рятуватися,2.68 +співіснувати,2.68 +супроводжуватися,2.68 +вживатися,2.67 +виймати,2.67 +з'єднувати,2.67 +зазнавати,2.67 +надіятися,2.67 +паплюжити,2.67 +придушувати,2.67 +припадати,2.67 +продукувати,2.67 +уточнювати,2.67 +хвалити,2.67 +глузувати,2.66 +орати,2.66 +подихати,2.66 +покладати,2.66 +прориватися,2.66 +прослуховувати,2.66 +різнитися,2.66 +смажити,2.66 +спокутувати,2.66 +спричиняти,2.66 +труїти,2.66 +агітувати,2.65 +адаптувати,2.65 +дослухатися,2.65 +м'яти,2.65 +остерігатися,2.65 +переселятися,2.65 +позбавлятися,2.65 +процвітати,2.65 +руйнуватися,2.65 +слати,2.65 +учити,2.65 +ганьбити,2.64 +дублювати,2.64 +живити,2.64 +заглядати,2.64 +набиратися,2.64 +оборонятися,2.64 +переважати,2.64 +приписувати,2.64 +притягати,2.64 +пісяти,2.64 +розміщуватися,2.64 +розставляти,2.64 +розходитися,2.64 +славити,2.64 +єднатися,2.64 +бродити,2.63 +в'їжджати,2.63 +вибиратися,2.63 +визнаватися,2.63 +відштовхуватися,2.63 +друкуватися,2.63 +ковзати,2.63 +перекидати,2.63 +підводити,2.63 +виривати,2.62 +голити,2.62 +добирати,2.62 +доповідати,2.62 +екстрадувати,2.62 +заварювати,2.62 +заїжджати,2.62 +катати,2.62 +крутитися,2.62 +накривати,2.62 +пробивати,2.62 +підійматися,2.62 +роз'яснювати,2.62 +розпочинати,2.62 +транслюватися,2.62 +бавитися,2.61 +відбиватися,2.61 +віддалятися,2.61 +гастролювати,2.61 +лайкати,2.61 +моделювати,2.61 +моніторити,2.61 +мочити,2.61 +світити,2.61 +суперечити,2.61 +урізати,2.61 +усміхатися,2.61 +бронювати,2.60 +виказувати,2.60 +колоти,2.60 +лишати,2.60 +напрягати,2.60 +обертати,2.60 +приглядати,2.60 +підключати,2.60 +скуповувати,2.60 +асоціювати,2.59 +балувати,2.59 +відволікатися,2.59 +конкретизувати,2.59 +обчислювати,2.59 +підривати,2.59 +репостити,2.59 +розкладати,2.59 +споруджувати,2.59 +винищувати,2.58 +випадати,2.58 +відчиняти,2.58 +запам'ятовувати,2.58 +здогадуватися,2.58 +зосереджуватися,2.58 +зраджувати,2.58 +мацати,2.58 +нагороджувати,2.58 +перебивати,2.58 +перемикати,2.58 +переплачувати,2.58 +перепощувати,2.58 +розкидати,2.58 +трансформувати,2.58 +утікати,2.58 +хрестити,2.58 +являти,2.58 +імпровізувати,2.58 +виростати,2.57 +затримуватися,2.57 +зливати,2.57 +модифікувати,2.57 +плести,2.57 +прийматися,2.57 +спрямовуватися,2.57 +хейтити,2.57 +дражнити,2.56 +зимувати,2.56 +лагодити,2.56 +локалізувати,2.56 +нарізати,2.56 +хвалитися,2.56 +цілуватися,2.56 +базуватися,2.55 +витісняти,2.55 +дівати,2.55 +задовольнятися,2.55 +закладати,2.55 +замислюватися,2.55 +зватися,2.55 +прискорювати,2.55 +пропагувати,2.55 +терти,2.55 +характеризувати,2.55 +відмічати,2.54 +загравати,2.54 +лобіювати,2.54 +накликати,2.54 +обтяжувати,2.54 +посилювати,2.54 +підрізати,2.54 +вироблятися,2.53 +дезертирувати,2.53 +занурюватися,2.53 +масажувати,2.53 +морочити,2.53 +нюхати,2.53 +переміщати,2.53 +плекати,2.53 +скеровувати,2.53 +справляти,2.53 +стискати,2.53 +сяяти,2.53 +випускатися,2.52 +згортати,2.52 +зловживати,2.52 +міститися,2.52 +пливти,2.52 +суміщати,2.52 +уміти,2.52 +висуватися,2.51 +ждати,2.51 +заощаджувати,2.51 +налаштовувати,2.51 +окупувати,2.51 +переховуватися,2.51 +чергувати,2.51 +висилати,2.50 +відхиляти,2.50 +дорікати,2.50 +звітуватися,2.50 +оскаржувати,2.50 +погіршуватися,2.50 +проїжджати,2.50 +розтягуватися,2.50 +свердлити,2.50 +спокушати,2.50 +хапати,2.50 +вмовляти,2.49 +доповнювати,2.49 +живитися,2.49 +замовчувати,2.49 +контролюватися,2.49 +оминати,2.49 +перебирати,2.49 +позначатися,2.49 +позувати,2.49 +поєднуватися,2.49 +притримуватися,2.49 +сповіщати,2.49 +танути,2.49 +тлумачити,2.49 +шокувати,2.49 +адресувати,2.48 +відчуватися,2.48 +гальмувати,2.48 +грішити,2.48 +затягувати,2.48 +зв'язувати,2.48 +концентруватися,2.48 +мазати,2.48 +натякати,2.48 +перемішувати,2.48 +пересилати,2.48 +писатися,2.48 +розбавляти,2.48 +страйкувати,2.48 +схилятися,2.48 +вичавлювати,2.47 +виявлятися,2.47 +гойдатися,2.47 +кувати,2.47 +накопичуватися,2.47 +наражати,2.47 +обнімати,2.47 +поминати,2.47 +пробиватися,2.47 +роздумувати,2.47 +розігрувати,2.47 +виготовлятися,2.46 +викрадати,2.46 +виправдовуватися,2.46 +експлуатуватися,2.46 +користатися,2.46 +налагоджувати,2.46 +опановувати,2.46 +пересувати,2.46 +прогнозувати,2.46 +тремтіти,2.46 +виголошувати,2.45 +вити,2.45 +еволюціонувати,2.45 +засновувати,2.45 +нарікати,2.45 +оброблятися,2.45 +пахнути,2.45 +перебігати,2.45 +позбуватися,2.45 +полонити,2.45 +старіти,2.45 +тягнутися,2.45 +управлятися,2.45 +варіювати,2.44 +вдосконалюватися,2.44 +виливати,2.44 +гнатися,2.44 +додаватися,2.44 +консультувати,2.44 +обиратися,2.44 +обурюватися,2.44 +оснащуватися,2.44 +полегшувати,2.44 +поїдати,2.44 +прохати,2.44 +прямувати,2.44 +підміняти,2.44 +удосконалювати,2.44 +цькувати,2.44 +вилизувати,2.43 +вникати,2.43 +гатити,2.43 +доїти,2.43 +завертати,2.43 +зважувати,2.43 +знатися,2.43 +кінчати,2.43 +ламатися,2.43 +марнувати,2.43 +проливати,2.43 +реготати,2.43 +розбудовувати,2.43 +розмірковувати,2.43 +сканувати,2.43 +трудитися,2.43 +фантазувати,2.43 +хреститися,2.43 +шепотіти,2.43 +іржати,2.43 +ґрунтуватися,2.43 +автоматизувати,2.42 +бруднитися,2.42 +вирубувати,2.42 +впізнавати,2.42 +втягувати,2.42 +контратакувати,2.42 +максимізувати,2.42 +поповнювати,2.42 +присягати,2.42 +реєструватися,2.42 +розігрівати,2.42 +рушати,2.42 +символізувати,2.42 +спрощувати,2.42 +тужити,2.42 +іменувати,2.42 +винити,2.41 +вирубати,2.41 +діагностувати,2.41 +засмагати,2.41 +котити,2.41 +розраховуватися,2.41 +скакати,2.41 +вгадувати,2.40 +вриватися,2.40 +відробляти,2.40 +гнути,2.40 +громити,2.40 +зривати,2.40 +кріпити,2.40 +перемикатися,2.40 +потребувати,2.40 +проголошувати,2.40 +реставрувати,2.40 +розроблятися,2.40 +стерегти,2.40 +траплятися,2.40 +вакцинувати,2.39 +випромінювати,2.39 +вкладатися,2.39 +влаштовуватися,2.39 +вмиватися,2.39 +возитися,2.39 +віддаватися,2.39 +відлякувати,2.39 +гавкати,2.39 +докоряти,2.39 +комбінувати,2.39 +лічити,2.39 +монополізувати,2.39 +мучитися,2.39 +подовжувати,2.39 +полишати,2.39 +привозити,2.39 +підпорядковуватися,2.39 +роздивлятися,2.39 +розповсюджуватися,2.39 +селитися,2.39 +таїти,2.39 +форсувати,2.39 +вловлювати,2.38 +відображатися,2.38 +зациклюватися,2.38 +напружуватися,2.38 +обкладати,2.38 +послаблювати,2.38 +пікетувати,2.38 +стикатися,2.38 +ткати,2.38 +доказувати,2.37 +занепадати,2.37 +зачіпати,2.37 +остигати,2.37 +поділяти,2.37 +приїздити,2.37 +підтримуватися,2.37 +регулюватися,2.37 +скорочувати,2.37 +божеволіти,2.36 +відділяти,2.36 +кваліфікуватися,2.36 +майструвати,2.36 +могти,2.36 +народжуватися,2.36 +переміщувати,2.36 +перетравлювати,2.36 +спотворювати,2.36 +впорядковувати,2.35 +відривати,2.35 +збуджувати,2.35 +коригувати,2.35 +очищати,2.35 +парити,2.35 +паркуватися,2.35 +підписуватися,2.35 +роздягати,2.35 +арештовувати,2.34 +вербувати,2.34 +вершити,2.34 +викопувати,2.34 +дорожчати,2.34 +завідувати,2.34 +зазіхати,2.34 +комплектуватися,2.34 +накидати,2.34 +пестити,2.34 +полягати,2.34 +представлятися,2.34 +приставати,2.34 +проводжати,2.34 +прописувати,2.34 +реорганізувати,2.34 +симулювати,2.34 +товаришувати,2.34 +включатися,2.33 +вправлятися,2.33 +женитися,2.33 +кип'ятити,2.33 +розгадувати,2.33 +хапатися,2.33 +вартувати,2.32 +віднімати,2.32 +відриватися,2.32 +візуалізувати,2.32 +дути,2.32 +забруднювати,2.32 +кашляти,2.32 +нагріватися,2.32 +об'їжджати,2.32 +показуватися,2.32 +пом'якшувати,2.32 +підгодовувати,2.32 +підказувати,2.32 +совіти,2.32 +ступати,2.32 +супроводити,2.32 +вселяти,2.31 +відштовхувати,2.31 +догоджати,2.31 +замикати,2.31 +конструювати,2.31 +лінуватися,2.31 +маркувати,2.31 +мігрувати,2.31 +набивати,2.31 +настроювати,2.31 +носитися,2.31 +обмовляти,2.31 +перейменовувати,2.31 +розташовувати,2.31 +систематизувати,2.31 +ворожити,2.30 +відганяти,2.30 +доторкатися,2.30 +залазити,2.30 +замерзати,2.30 +кипіти,2.30 +купати,2.30 +медитувати,2.30 +породжувати,2.30 +призначатися,2.30 +інфікувати,2.30 +ворушити,2.29 +відвойовувати,2.29 +засвоювати,2.29 +знешкоджувати,2.29 +противитися,2.29 +стискатися,2.29 +бурити,2.28 +задумуватися,2.28 +зливатися,2.28 +зізнаватися,2.28 +консолідувати,2.28 +паркувати,2.28 +переключатися,2.28 +повторюватися,2.28 +поправляти,2.28 +приєднувати,2.28 +підкорювати,2.28 +реструктуризувати,2.28 +родити,2.28 +синхронізувати,2.28 +сповідатися,2.28 +фінішувати,2.28 +бомбардувати,2.27 +враховуватися,2.27 +втомлюватися,2.27 +відати,2.27 +відкачувати,2.27 +заганяти,2.27 +заподіювати,2.27 +зачиняти,2.27 +звозити,2.27 +качатися,2.27 +клонувати,2.27 +клопотати,2.27 +підключатися,2.27 +розносити,2.27 +сипати,2.27 +спливати,2.27 +фліртувати,2.27 +вигулювати,2.26 +виражатися,2.26 +гребти,2.26 +дорослішати,2.26 +забезпечуватися,2.26 +закінчуватися,2.26 +кровоточити,2.26 +набридати,2.26 +обсипати,2.26 +озиратися,2.26 +передувати,2.26 +поганяти,2.26 +потурати,2.26 +роздягатися,2.26 +розділятися,2.26 +сварити,2.26 +співставляти,2.26 +страчувати,2.26 +тестувати,2.26 +трясти,2.26 +упускати,2.26 +боксувати,2.25 +варитися,2.25 +диференціювати,2.25 +звіряти,2.25 +клювати,2.25 +облаштовувати,2.25 +страхувати,2.25 +відроджувати,2.24 +відхилятися,2.24 +мастурбувати,2.24 +натягувати,2.24 +обмінювати,2.24 +прогресувати,2.24 +підганяти,2.24 +підсипати,2.24 +смакувати,2.24 +стукатися,2.24 +тероризувати,2.24 +базікати,2.23 +випалювати,2.23 +вміщувати,2.23 +ганятися,2.23 +мчати,2.23 +повідати,2.23 +цінити,2.23 +адмініструвати,2.22 +бунтувати,2.22 +викачувати,2.22 +відмивати,2.22 +з'єднуватися,2.22 +задихатися,2.22 +запускатися,2.22 +конвертувати,2.22 +лякатися,2.22 +оплакувати,2.22 +осуджувати,2.22 +присвоювати,2.22 +розганятися,2.22 +сердитися,2.22 +стримуватися,2.22 +бісити,2.21 +вередувати,2.21 +вміщати,2.21 +делегувати,2.21 +дорожити,2.21 +насміхатися,2.21 +наставляти,2.21 +оживляти,2.21 +перебудовувати,2.21 +плескати,2.21 +поміщати,2.21 +поставлятися,2.21 +рипатися,2.21 +розумітися,2.21 +рівняти,2.21 +умирати,2.21 +втішати,2.20 +відвертати,2.20 +датувати,2.20 +дертися,2.20 +зближуватися,2.20 +зчитувати,2.20 +капати,2.20 +кривдити,2.20 +оснащувати,2.20 +паралізувати,2.20 +плутатися,2.20 +посилюватися,2.20 +притримувати,2.20 +підлягати,2.20 +реалізуватися,2.20 +розгортатися,2.20 +розпадатися,2.20 +свистіти,2.20 +вручати,2.19 +відзначатися,2.19 +відроджуватися,2.19 +котитися,2.19 +кріпитися,2.19 +одужувати,2.19 +підтверджувати,2.19 +розсипати,2.19 +слабшати,2.19 +узгоджувати,2.19 +вбачати,2.18 +вторгатися,2.18 +дистанціюватися,2.18 +зависати,2.18 +зараховувати,2.18 +користати,2.18 +мочитися,2.18 +нормалізувати,2.18 +поставати,2.18 +пояснюватися,2.18 +проганяти,2.18 +проклинати,2.18 +репетирувати,2.18 +розкладатися,2.18 +уніфікувати,2.18 +хизуватися,2.18 +цвісти,2.18 +видалятися,2.17 +викручуватися,2.17 +відлітати,2.17 +гніватися,2.17 +роздувати,2.17 +розмахувати,2.17 +викликатися,2.16 +дебютувати,2.16 +дерти,2.16 +кусатися,2.16 +монтувати,2.16 +оглядатися,2.16 +опитувати,2.16 +підвищуватися,2.16 +підсилювати,2.16 +пілотувати,2.16 +реалізовуватися,2.16 +скиглити,2.16 +спадати,2.16 +сповідати,2.16 +іменуватися,2.16 +байдикувати,2.15 +відокремлювати,2.15 +душити,2.15 +закохуватися,2.15 +молотити,2.15 +наздоганяти,2.15 +обганяти,2.15 +розпилювати,2.15 +розростатися,2.15 +санкціонувати,2.15 +сертифікувати,2.15 +трансформуватися,2.15 +уповільнювати,2.15 +виношувати,2.14 +зламувати,2.14 +наростати,2.14 +перечити,2.14 +розчаровувати,2.14 +форматувати,2.14 +вводитися,2.13 +голитися,2.13 +заставляти,2.13 +кермувати,2.13 +куховарити,2.13 +молити,2.13 +обзивати,2.13 +організовуватися,2.13 +присвячувати,2.13 +підбадьорювати,2.13 +розхитувати,2.13 +снитися,2.13 +спеціалізуватися,2.13 +спричинювати,2.13 +чаклувати,2.13 +екстраполювати,2.12 +заселяти,2.12 +карбувати,2.12 +копатися,2.12 +присилати,2.12 +розкрадати,2.12 +склеювати,2.12 +анексувати,2.11 +вертати,2.11 +вибухати,2.11 +здіймати,2.11 +перевершувати,2.11 +переноситися,2.11 +приступати,2.11 +скупитися,2.11 +твердити,2.11 +фігурувати,2.11 +викручувати,2.10 +виштовхувати,2.10 +кодувати,2.10 +осідати,2.10 +перетинатися,2.10 +репрезентувати,2.10 +робитися,2.10 +бракувати,2.09 +відстрілювати,2.09 +довбати,2.09 +згасати,2.09 +калічити,2.09 +кланятися,2.09 +обожнювати,2.09 +опрацьовувати,2.09 +переганяти,2.09 +пропонуватися,2.09 +пручатися,2.09 +стирчати,2.09 +акцентувати,2.08 +вагатися,2.08 +валятися,2.08 +викуповувати,2.08 +впроваджуватися,2.08 +відвозити,2.08 +дотримувати,2.08 +оновлюватися,2.08 +поглиблювати,2.08 +пошкоджувати,2.08 +трактуватися,2.08 +відмічатися,2.07 +головувати,2.07 +екранізувати,2.07 +зазирати,2.07 +зачищати,2.07 +оточувати,2.07 +погоджувати,2.07 +сунути,2.07 +тривожити,2.07 +дрімати,2.06 +зароджуватися,2.06 +зводитися,2.06 +крити,2.06 +метушитися,2.06 +нишпорити,2.06 +чувати,2.06 +валитися,2.05 +вбирати,2.05 +вклонятися,2.05 +годуватися,2.05 +докучати,2.05 +зосереджувати,2.05 +квапити,2.05 +концентрувати,2.05 +мітингувати,2.05 +насипати,2.05 +перехоплювати,2.05 +сповільнюватися,2.05 +укріплювати,2.05 +вивчатися,2.04 +збігати,2.04 +легковажити,2.04 +листуватися,2.04 +перекачувати,2.04 +переїздити,2.04 +плодоносити,2.04 +позиціонувати,2.04 +поліпшувати,2.04 +посипати,2.04 +характеризуватися,2.04 +чарувати,2.04 +чесати,2.04 +балансувати,2.03 +засилати,2.03 +кивати,2.03 +колекціонувати,2.03 +опікати,2.03 +переодягатися,2.03 +поглядати,2.03 +пожинати,2.03 +поповнюватися,2.03 +тліти,2.03 +бруднити,2.02 +заражати,2.02 +ладнати,2.02 +оспорювати,2.02 +перти,2.02 +прискорюватися,2.02 +рішати,2.02 +читатися,2.02 +ґвалтувати,2.02 +акумулювати,2.01 +асимілювати,2.01 +вчителювати,2.01 +відтворюватися,2.01 +доповнюватися,2.01 +з'їжджати,2.01 +затикати,2.01 +конфліктувати,2.01 +мітити,2.01 +охолоджувати,2.01 +оцінюватися,2.01 +прилітати,2.01 +радитися,2.01 +свататися,2.01 +уживати,2.01 +виводитися,2.00 +закріплювати,2.00 +змішуватися,2.00 +зміщуватися,2.00 +ладити,2.00 +обшукувати,2.00 +освітлювати,2.00 +плавити,2.00 +присуджувати,2.00 +реабілітувати,2.00 +розчинятися,2.00 +розширятися,2.00 +толерувати,2.00 +уражати,2.00 +апелювати,1.99 +відраховувати,1.99 +драматизувати,1.99 +задаватися,1.99 +замінюватися,1.99 +непокоїтися,1.99 +нівелювати,1.99 +переховувати,1.99 +поліпшуватися,1.99 +прив'язувати,1.99 +розчищати,1.99 +царювати,1.99 +циркулювати,1.99 +черпати,1.99 +групувати,1.98 +залицятися,1.98 +зцілювати,1.98 +липнути,1.98 +насаджувати,1.98 +обкрадати,1.98 +посідати,1.98 +розсмоктувати,1.98 +цілитися,1.98 +випасати,1.97 +випереджати,1.97 +випивати,1.97 +втримувати,1.97 +від'їжджати,1.97 +жбурляти,1.97 +кепкувати,1.97 +комплектувати,1.97 +налічувати,1.97 +перестрибувати,1.97 +промовляти,1.97 +ранити,1.97 +удавати,1.97 +уособлювати,1.97 +індукувати,1.97 +ампутувати,1.96 +вбудовувати,1.96 +викладатися,1.96 +виїздити,1.96 +відловлювати,1.96 +загострювати,1.96 +запізнюватися,1.96 +лупцювати,1.96 +мести,1.96 +метати,1.96 +переставати,1.96 +приділятися,1.96 +проростати,1.96 +розвертатися,1.96 +спонсорувати,1.96 +тинятися,1.96 +уживатися,1.96 +указувати,1.96 +формалізувати,1.96 +добивати,1.95 +дошкуляти,1.95 +зіставляти,1.95 +прищеплювати,1.95 +псуватися,1.95 +розтягувати,1.95 +розчиняти,1.95 +співробітничати,1.95 +трястися,1.95 +щипати,1.95 +ілюструвати,1.95 +інгібувати,1.95 +верещати,1.94 +відсиджуватися,1.94 +деморалізувати,1.94 +квапитися,1.94 +колонізувати,1.94 +пресувати,1.94 +приборкувати,1.94 +пришивати,1.94 +прогулювати,1.94 +реструктурувати,1.94 +розміщати,1.94 +розпивати,1.94 +розрізнятися,1.94 +розшифровувати,1.94 +сигналізувати,1.94 +спізнюватися,1.94 +сходитися,1.94 +чухати,1.94 +буксирувати,1.93 +витікати,1.93 +жати,1.93 +замітати,1.93 +люструвати,1.93 +мотати,1.93 +перебиратися,1.93 +погіршувати,1.93 +прислуховуватися,1.93 +регенерувати,1.93 +сполучатися,1.93 +студіювати,1.93 +схвалювати,1.93 +взувати,1.92 +вибачати,1.92 +висмоктувати,1.92 +документувати,1.92 +зазначати,1.92 +маскуватися,1.92 +обслуговуватися,1.92 +приземлятися,1.92 +пробиратися,1.92 +прославляти,1.92 +смердіти,1.92 +сповільнювати,1.92 +структурувати,1.92 +вабити,1.91 +винаходити,1.91 +врізати,1.91 +деформуватися,1.91 +диміти,1.91 +заселятися,1.91 +копирсатися,1.91 +моргати,1.91 +налагоджуватися,1.91 +осягати,1.91 +переселяти,1.91 +переступати,1.91 +плюндрувати,1.91 +поділятися,1.91 +позиціонуватися,1.91 +пристосовувати,1.91 +ритися,1.91 +ворогувати,1.90 +жалити,1.90 +забудовувати,1.90 +множити,1.90 +номінувати,1.90 +облизувати,1.90 +піарити,1.90 +спустошувати,1.90 +стандартизувати,1.90 +тужитися,1.90 +успадковувати,1.90 +фінансуватися,1.90 +вивертати,1.89 +диверсифікувати,1.89 +закріплюватися,1.89 +згортатися,1.89 +класифікуватися,1.89 +масштабувати,1.89 +нокаутувати,1.89 +обладнуватися,1.89 +опубліковувати,1.89 +перевертати,1.89 +фіксуватися,1.89 +ширитися,1.89 +вимірюватися,1.88 +висихати,1.88 +виставлятися,1.88 +витворяти,1.88 +відшукувати,1.88 +добудовувати,1.88 +звершувати,1.88 +каратися,1.88 +легітимізувати,1.88 +маскувати,1.88 +наділяти,1.88 +нараховувати,1.88 +паразитувати,1.88 +попереджувати,1.88 +привласнювати,1.88 +розпалювати,1.88 +сполучати,1.88 +фотографуватися,1.88 +централізувати,1.88 +ідентифікуватися,1.88 +викрикувати,1.87 +висиджувати,1.87 +вказуватися,1.87 +володарювати,1.87 +відтісняти,1.87 +гарчати,1.87 +дублюватися,1.87 +завдячувати,1.87 +заготовляти,1.87 +заслуговувати,1.87 +засовувати,1.87 +зашивати,1.87 +модерувати,1.87 +навіювати,1.87 +порушуватися,1.87 +пригальмовувати,1.87 +роззброювати,1.87 +розмножувати,1.87 +спочивати,1.87 +встрявати,1.86 +втілюватися,1.86 +відокремлюватися,1.86 +дерибанити,1.86 +згинати,1.86 +клопотатися,1.86 +княжити,1.86 +корегувати,1.86 +прислужувати,1.86 +провідувати,1.86 +протиставляти,1.86 +роздмухати,1.86 +розкриватися,1.86 +розцінюватися,1.86 +симпатизувати,1.86 +спинятися,1.86 +співпереживати,1.86 +шикувати,1.86 +шикуватися,1.86 +вкривати,1.85 +завантажуватися,1.85 +заморочуватися,1.85 +заряджатися,1.85 +компонувати,1.85 +консультуватися,1.85 +марширувати,1.85 +обгрунтовувати,1.85 +пахати,1.85 +перевтілюватися,1.85 +перенаправляти,1.85 +послуговуватися,1.85 +прясти,1.85 +розписувати,1.85 +виборювати,1.84 +випливати,1.84 +вирівнювати,1.84 +відділятися,1.84 +відсіювати,1.84 +заповнюватися,1.84 +примножувати,1.84 +тусуватися,1.84 +удосконалюватися,1.84 +узгоджуватися,1.84 +укладатися,1.84 +бавити,1.83 +випаровуватися,1.83 +декорувати,1.83 +додержуватися,1.83 +досягатися,1.83 +животіти,1.83 +заміщати,1.83 +звільнятися,1.83 +обійматися,1.83 +підпорядковувати,1.83 +соромити,1.83 +чавити,1.83 +бенкетувати,1.82 +витрачатися,1.82 +даватися,1.82 +зачинятися,1.82 +згинатися,1.82 +матюкатися,1.82 +морити,1.82 +няньчити,1.82 +озброювати,1.82 +передивлятися,1.82 +переоцінювати,1.82 +пророкувати,1.82 +підлаштовуватися,1.82 +підпалювати,1.82 +розподілятися,1.82 +тушкувати,1.82 +вібрувати,1.81 +відшкодовувати,1.81 +гасати,1.81 +з'їдати,1.81 +квітнути,1.81 +лунати,1.81 +механізувати,1.81 +мобілізовувати,1.81 +пиячити,1.81 +плюватися,1.81 +подрібнювати,1.81 +підбурювати,1.81 +підшукувати,1.81 +реабілітуватися,1.81 +розсіювати,1.81 +спрацьовувати,1.81 +благословляти,1.80 +виламувати,1.80 +вилітати,1.80 +дописувати,1.80 +дробити,1.80 +запозичувати,1.80 +затверджуватися,1.80 +каструвати,1.80 +покриватися,1.80 +пітніти,1.80 +розщеплювати,1.80 +тлумачитися,1.80 +трощити,1.80 +уважати,1.80 +утверджувати,1.80 +винаймати,1.79 +вирощуватися,1.79 +гамселити,1.79 +гармонізувати,1.79 +дебатувати,1.79 +доживати,1.79 +доручати,1.79 +запасати,1.79 +запрягати,1.79 +наводитися,1.79 +наголошувати,1.79 +обґрунтовувати,1.79 +перевірятися,1.79 +підпадати,1.79 +підпускати,1.79 +стирати,1.79 +транспортуватися,1.79 +фокусуватися,1.79 +інтенсифікувати,1.79 +абстрагуватися,1.78 +вивантажувати,1.78 +викупати,1.78 +гніздитися,1.78 +гуглити,1.78 +комплексувати,1.78 +лупити,1.78 +обстоювати,1.78 +передбачувати,1.78 +переправляти,1.78 +поновлювати,1.78 +прикликати,1.78 +прилягати,1.78 +підкоряти,1.78 +ревіти,1.78 +скликатися,1.78 +установлювати,1.78 +жебрати,1.77 +заповідати,1.77 +згадуватися,1.77 +катапультуватися,1.77 +настоювати,1.77 +нахилятися,1.77 +оселятися,1.77 +охолоджуватися,1.77 +переставляти,1.77 +привчати,1.77 +підслуховувати,1.77 +розвіювати,1.77 +розмежовувати,1.77 +спалахувати,1.77 +хамити,1.77 +шахраювати,1.77 +шифрувати,1.77 +аргументувати,1.76 +вантажити,1.76 +викорінювати,1.76 +виручати,1.76 +віршувати,1.76 +гвалтувати,1.76 +декламувати,1.76 +дискваліфікувати,1.76 +доставлятися,1.76 +зміщувати,1.76 +йтися,1.76 +клясти,1.76 +оспівувати,1.76 +перевозитися,1.76 +переправлятися,1.76 +пересаджувати,1.76 +пліткувати,1.76 +пригадувати,1.76 +прилипати,1.76 +присідати,1.76 +репетувати,1.76 +розливати,1.76 +спорити,1.76 +студити,1.76 +упорядковувати,1.76 +вивішувати,1.75 +вписуватися,1.75 +відчужувати,1.75 +гойдати,1.75 +дозрівати,1.75 +досліджуватися,1.75 +експонувати,1.75 +заглиблюватися,1.75 +загострюватися,1.75 +залагоджувати,1.75 +заносити,1.75 +корчити,1.75 +напихати,1.75 +обстежувати,1.75 +обчислюватися,1.75 +очищувати,1.75 +перевантажувати,1.75 +пересихати,1.75 +промивати,1.75 +пхатися,1.75 +рівнятися,1.75 +стажуватися,1.75 +схиляти,1.75 +гнутися,1.74 +задіювати,1.74 +коректувати,1.74 +лавірувати,1.74 +підслухати,1.74 +розвалюватися,1.74 +розпаковувати,1.74 +інтригувати,1.74 +виписуватися,1.73 +відпливати,1.73 +лестити,1.73 +мусити,1.73 +облітати,1.73 +подумувати,1.73 +прислуговувати,1.73 +репресувати,1.73 +упадати,1.73 +цензурувати,1.73 +вимовлятися,1.72 +демонструватися,1.72 +дряпати,1.72 +застеляти,1.72 +конфісковувати,1.72 +кочувати,1.72 +лідирувати,1.72 +навідуватися,1.72 +оживати,1.72 +оповідати,1.72 +перезавантажувати,1.72 +пожирати,1.72 +принижуватися,1.72 +присипляти,1.72 +пролягати,1.72 +прочісувати,1.72 +підвішувати,1.72 +розмінювати,1.72 +русифікувати,1.72 +топитися,1.72 +ускладнюватися,1.72 +юзати,1.72 +ідеалізувати,1.72 +вештатися,1.71 +вилучатися,1.71 +висвячувати,1.71 +вишукувати,1.71 +всипати,1.71 +доправляти,1.71 +колядувати,1.71 +обладнувати,1.71 +озброюватися,1.71 +орієнтувати,1.71 +перевтомлюватися,1.71 +переростати,1.71 +поздоровляти,1.71 +постачатися,1.71 +потерпати,1.71 +прогулюватися,1.71 +пролітати,1.71 +регламентувати,1.71 +розбігатися,1.71 +розкопувати,1.71 +фокусувати,1.71 +висівати,1.70 +втягуватися,1.70 +відливати,1.70 +гримати,1.70 +запікати,1.70 +засвідчувати,1.70 +збувати,1.70 +какати,1.70 +ліцензувати,1.70 +матися,1.70 +навертати,1.70 +накидатися,1.70 +направлятися,1.70 +осмислювати,1.70 +оформлювати,1.70 +переливати,1.70 +перерозподіляти,1.70 +призупиняти,1.70 +проглядати,1.70 +просочуватися,1.70 +розвантажувати,1.70 +розсіюватися,1.70 +свистати,1.70 +фоткати,1.70 +інтерпретуватися,1.70 +витіснятися,1.69 +волочитися,1.69 +вручатися,1.69 +втрачатися,1.69 +деформувати,1.69 +диригувати,1.69 +жебракувати,1.69 +зближувати,1.69 +колупатися,1.69 +насичувати,1.69 +оволодівати,1.69 +освоюватися,1.69 +передбачатися,1.69 +перекидатися,1.69 +перекриватися,1.69 +пов'язуватися,1.69 +поневірятися,1.69 +поселятися,1.69 +придбавати,1.69 +причащатися,1.69 +прокачувати,1.69 +підвозити,1.69 +римувати,1.69 +розбудовуватися,1.69 +розправлятися,1.69 +фальсифікувати,1.69 +чергуватися,1.69 +зоставатися,1.68 +вимирати,1.67 +виокремлювати,1.67 +виховуватися,1.67 +воскрешати,1.67 +всмоктувати,1.67 +гуртуватися,1.67 +задовільняти,1.67 +заміщувати,1.67 +змащувати,1.67 +крокувати,1.67 +накладатися,1.67 +наливати,1.67 +перекладатися,1.67 +перепрофілювати,1.67 +покращуватися,1.67 +пропадати,1.67 +тицяти,1.67 +блищати,1.66 +боронитися,1.66 +брити,1.66 +виснажувати,1.66 +відлучатися,1.66 +вінчати,1.66 +деградувати,1.66 +зварювати,1.66 +коїти,1.66 +кришувати,1.66 +нагороджуватися,1.66 +наповнюватися,1.66 +освячувати,1.66 +ототожнюватися,1.66 +парирувати,1.66 +пастися,1.66 +поміщатися,1.66 +розриватися,1.66 +телепортуватися,1.66 +тролити,1.66 +тягатися,1.66 +вивільняти,1.65 +вираховувати,1.65 +висовувати,1.65 +грітися,1.65 +гіпнотизувати,1.65 +дмухати,1.65 +доводитися,1.65 +долучати,1.65 +з'їжджатися,1.65 +забавляти,1.65 +заманювати,1.65 +заповняти,1.65 +збагачуватися,1.65 +зміцнюватися,1.65 +коронувати,1.65 +креслити,1.65 +найматися,1.65 +перетягувати,1.65 +присвоюватися,1.65 +простоювати,1.65 +споряджати,1.65 +співвідносити,1.65 +стягуватися,1.65 +вимолювати,1.64 +водитися,1.64 +наїжджати,1.64 +амністувати,1.63 +апроксимувати,1.63 +виплачуватися,1.63 +вистрибувати,1.63 +вихваляти,1.63 +врізатися,1.63 +відгукуватися,1.63 +відстрілюватися,1.63 +гарувати,1.63 +дрейфувати,1.63 +дуріти,1.63 +діставатися,1.63 +зазначатися,1.63 +зумовлювати,1.63 +комбінуватися,1.63 +наноситися,1.63 +переодягати,1.63 +прикріплювати,1.63 +примикати,1.63 +притискати,1.63 +підігрівати,1.63 +розширяти,1.63 +схоплювати,1.63 +тупити,1.63 +тусити,1.63 +тіснити,1.63 +українізувати,1.63 +іммігрувати,1.63 +індексувати,1.63 +інсценувати,1.63 +верифікувати,1.62 +віддирати,1.62 +відрощувати,1.62 +губити,1.62 +дезорієнтувати,1.62 +закачувати,1.62 +засновуватися,1.62 +згладжувати,1.62 +локалізуватися,1.62 +мутувати,1.62 +мішатися,1.62 +наближати,1.62 +навантажувати,1.62 +обдумувати,1.62 +обприскувати,1.62 +прирікати,1.62 +пустувати,1.62 +підживлювати,1.62 +розміщатися,1.62 +розігруватися,1.62 +теревенити,1.62 +хоронити,1.62 +перезнімати,1.61 +перетирати,1.61 +підкрадатися,1.61 +афішувати,1.60 +вигодовувати,1.60 +виманювати,1.60 +висовуватися,1.60 +вписувати,1.60 +відкладатися,1.60 +емулювати,1.60 +занурювати,1.60 +знищуватися,1.60 +кошмарити,1.60 +легалізуватися,1.60 +нараховуватися,1.60 +охоронятися,1.60 +перетікати,1.60 +повставати,1.60 +пригощати,1.60 +пробігати,1.60 +просідати,1.60 +підкидати,1.60 +підстрибувати,1.60 +реінтегрувати,1.60 +розпродавати,1.60 +солити,1.60 +стабілізуватися,1.60 +точити,1.60 +ураховувати,1.60 +іржавіти,1.60 +активуватися,1.59 +блудити,1.59 +брикатися,1.59 +витріщатися,1.59 +групуватися,1.59 +демократизувати,1.59 +зомбувати,1.59 +кліпати,1.59 +нав'язуватися,1.59 +описуватися,1.59 +перепрошувати,1.59 +розстібати,1.59 +скучати,1.59 +вистежувати,1.58 +врегульовувати,1.58 +вітатися,1.58 +госпіталізувати,1.58 +деталізувати,1.58 +зачаровувати,1.58 +канонізувати,1.58 +конденсуватися,1.58 +ласувати,1.58 +налаштовуватися,1.58 +підтягувати,1.58 +скидатися,1.58 +стерилізувати,1.58 +субсидувати,1.58 +схрещувати,1.58 +ширити,1.58 +блокуватися,1.57 +видозмінюватися,1.57 +вирізняти,1.57 +деактивувати,1.57 +декодувати,1.57 +дозувати,1.57 +забудовуватися,1.57 +заплітати,1.57 +згрібати,1.57 +зрікатися,1.57 +клястися,1.57 +князювати,1.57 +консервувати,1.57 +лаштувати,1.57 +монтуватися,1.57 +перебудовуватися,1.57 +погашати,1.57 +приводитися,1.57 +приголомшувати,1.57 +пробуджувати,1.57 +простягатися,1.57 +розоряти,1.57 +розчісувати,1.57 +синтезуватися,1.57 +скачувати,1.57 +слабнути,1.57 +сохнути,1.57 +упаковувати,1.57 +утискати,1.57 +уходити,1.57 +фехтувати,1.57 +погладжувати,1.56 +роїтися,1.56 +активізуватися,1.55 +асимілюватися,1.55 +борсатися,1.55 +відточувати,1.55 +відтягувати,1.55 +диференціюватися,1.55 +замикатися,1.55 +затоплювати,1.55 +кристалізуватися,1.55 +нагромаджувати,1.55 +надсилатися,1.55 +обживати,1.55 +пародіювати,1.55 +перериватися,1.55 +плакатися,1.55 +посвячувати,1.55 +прив'язуватися,1.55 +применшувати,1.55 +проривати,1.55 +прорізати,1.55 +підлаштовувати,1.55 +підраховувати,1.55 +розвертати,1.55 +розтирати,1.55 +розфарбовувати,1.55 +сигналити,1.55 +синхронізуватися,1.55 +скоювати,1.55 +суперничати,1.55 +тріумфувати,1.55 +уловлювати,1.55 +хилитися,1.55 +шануватися,1.55 +акредитуватися,1.54 +валяти,1.54 +висаджуватися,1.54 +відсторонювати,1.54 +генеруватися,1.54 +заростати,1.54 +лінчувати,1.54 +надокучати,1.54 +окуповувати,1.54 +прикріплюватися,1.54 +прикріплятися,1.54 +підколювати,1.54 +вичікувати,1.53 +відлучати,1.53 +денонсувати,1.53 +додержувати,1.53 +допускатися,1.53 +дресирувати,1.53 +загортати,1.53 +замальовувати,1.53 +зараховуватися,1.53 +затьмарювати,1.53 +зачитувати,1.53 +згодовувати,1.53 +змальовувати,1.53 +конфігурувати,1.53 +куштувати,1.53 +мурувати,1.53 +обумовлювати,1.53 +обігравати,1.53 +оздоблювати,1.53 +переглядатися,1.53 +перепливати,1.53 +пересідати,1.53 +плавитися,1.53 +придивлятися,1.53 +припинятися,1.53 +провозити,1.53 +роздруковувати,1.53 +спекулювати,1.53 +хитатися,1.53 +щадити,1.53 +імплантувати,1.53 +вбудовуватися,1.52 +волонтерити,1.52 +впарювати,1.52 +відкидатися,1.52 +детектувати,1.52 +киснути,1.52 +перелітати,1.52 +переобиратися,1.52 +розселятися,1.52 +сапати,1.52 +сторожити,1.52 +торпедувати,1.52 +актуалізувати,1.51 +бешкетувати,1.51 +вливатися,1.51 +вплутуватися,1.51 +вставлятися,1.51 +гуртувати,1.51 +заміряти,1.51 +засівати,1.51 +звужуватися,1.51 +значитися,1.51 +комунікувати,1.51 +марити,1.51 +мінувати,1.51 +намотувати,1.51 +обростати,1.51 +оплачуватися,1.51 +переводитися,1.51 +поборювати,1.51 +продумувати,1.51 +підтверджуватися,1.51 +розмальовувати,1.51 +розплутувати,1.51 +розпорошувати,1.51 +сватати,1.51 +цуратися,1.51 +цінуватися,1.51 +імплементувати,1.51 +інспектувати,1.51 +інсталювати,1.51 +дезорганізувати,1.50 +зариватися,1.50 +перечікувати,1.50 +розорювати,1.50 +стікатися,1.50 +таранити,1.50 +ширяти,1.50 +євангелізувати,1.50 +іонізувати,1.50 +абсорбувати,1.49 +архівувати,1.49 +базувати,1.49 +бентежити,1.49 +видозмінювати,1.49 +виливатися,1.49 +виловлювати,1.49 +винагороджувати,1.49 +втихомирювати,1.49 +відображувати,1.49 +відростати,1.49 +запливати,1.49 +корчувати,1.49 +мобілізуватися,1.49 +нагнітати,1.49 +обмірковувати,1.49 +ознайомлюватися,1.49 +опалювати,1.49 +переманювати,1.49 +перероблятися,1.49 +прирівнювати,1.49 +провалюватися,1.49 +прораховувати,1.49 +проробляти,1.49 +підбивати,1.49 +підспівувати,1.49 +підтягуватися,1.49 +ретранслювати,1.49 +розв'язуватися,1.49 +розквітати,1.49 +розколювати,1.49 +селити,1.49 +співвідноситися,1.49 +стрілятися,1.49 +сікти,1.49 +тріскатися,1.49 +тяжіти,1.49 +урізноманітнювати,1.49 +фільмувати,1.49 +хазяйнувати,1.49 +ігноруватися,1.49 +вистрілювати,1.48 +вселятися,1.48 +втикати,1.48 +деблокувати,1.48 +зсуватися,1.48 +каталізувати,1.48 +компілювати,1.48 +корелювати,1.48 +лапати,1.48 +модулювати,1.48 +нівечити,1.48 +перевидавати,1.48 +перемивати,1.48 +прикрашатися,1.48 +протиставлятися,1.48 +радикалізувати,1.48 +редукувати,1.48 +адсорбувати,1.47 +вирізнятися,1.47 +вичісувати,1.47 +влучати,1.47 +вмикатися,1.47 +волочити,1.47 +відвантажувати,1.47 +завершуватися,1.47 +затуляти,1.47 +заучувати,1.47 +збуватися,1.47 +звужувати,1.47 +згуртовувати,1.47 +здійматися,1.47 +зрошувати,1.47 +кредитувати,1.47 +курирувати,1.47 +награвати,1.47 +нахиляти,1.47 +нумерувати,1.47 +орудувати,1.47 +отруювати,1.47 +паяти,1.47 +перегравати,1.47 +поглинатися,1.47 +подразнювати,1.47 +потіти,1.47 +провадитися,1.47 +підсумовувати,1.47 +резервувати,1.47 +розкручувати,1.47 +сповзати,1.47 +творитися,1.47 +укріпляти,1.47 +барабанити,1.46 +вмішуватися,1.46 +вплутувати,1.46 +відписувати,1.46 +газифікувати,1.46 +доставати,1.46 +заражатися,1.46 +каламутити,1.46 +креативити,1.46 +межувати,1.46 +наговорювати,1.46 +пропливати,1.46 +уражатися,1.46 +шаленіти,1.46 +вигукувати,1.45 +видіти,1.45 +вимагатися,1.45 +виноситися,1.45 +всмоктуватися,1.45 +відводитися,1.45 +віддаляти,1.45 +віддзеркалювати,1.45 +відмежовувати,1.45 +вінчатися,1.45 +гостювати,1.45 +дезінфікувати,1.45 +допрацьовувати,1.45 +жонглювати,1.45 +журитися,1.45 +закладатися,1.45 +заковтувати,1.45 +закручувати,1.45 +запивати,1.45 +заправлятися,1.45 +застарівати,1.45 +звіритися,1.45 +навідувати,1.45 +напружувати,1.45 +наставати,1.45 +нестися,1.45 +опинятися,1.45 +оформлятися,1.45 +переборювати,1.45 +переплітатися,1.45 +переслідуватися,1.45 +проколювати,1.45 +протирати,1.45 +підбиратися,1.45 +підсилюватися,1.45 +ревти,1.45 +реорганізовувати,1.45 +роз'єднувати,1.45 +розпочинатися,1.45 +розчаровуватися,1.45 +скандувати,1.45 +струшувати,1.45 +тиражувати,1.45 +ударяти,1.45 +фарбуватися,1.45 +продавлювати,1.44 +сунутися,1.44 +веліти,1.43 +експортуватися,1.43 +здихати,1.43 +продюсувати,1.43 +розроблювати,1.43 +буксувати,1.42 +вдаряти,1.42 +вертіти,1.42 +виліковувати,1.42 +вирізувати,1.42 +вливати,1.42 +всиновлювати,1.42 +відмирати,1.42 +відряджати,1.42 +вірувати,1.42 +гортати,1.42 +демілітаризувати,1.42 +дискримінувати,1.42 +заготовлювати,1.42 +запроваджуватися,1.42 +заступатися,1.42 +звисати,1.42 +змочувати,1.42 +координуватися,1.42 +місити,1.42 +непокоїти,1.42 +огинати,1.42 +оподатковувати,1.42 +перероблювати,1.42 +превалювати,1.42 +проектуватися,1.42 +протегувати,1.42 +ранжувати,1.42 +розвозити,1.42 +розтікатися,1.42 +розщеплюватися,1.42 +сміти,1.42 +темніти,1.42 +штампувати,1.42 +являтися,1.42 +акомпанувати,1.41 +бурмотіти,1.41 +викидатися,1.41 +замінятися,1.41 +категоризувати,1.41 +лютувати,1.41 +перезаряджати,1.41 +промишляти,1.41 +підставлятися,1.41 +розмиватися,1.41 +сивіти,1.41 +скривати,1.41 +сіпатися,1.41 +ініціалізувати,1.41 +анонсувати,1.40 +блимати,1.40 +в'янути,1.40 +вгамовувати,1.40 +величати,1.40 +виправлятися,1.40 +всихати,1.40 +відмінювати,1.40 +віднаходити,1.40 +відпадати,1.40 +глядіти,1.40 +дезінформувати,1.40 +емітувати,1.40 +забарвлюватися,1.40 +заліковувати,1.40 +заступати,1.40 +зеленіти,1.40 +зобов'язувати,1.40 +контрастувати,1.40 +коригуватися,1.40 +милувати,1.40 +надихатися,1.40 +надівати,1.40 +наспівувати,1.40 +обмивати,1.40 +пасувати,1.40 +подовжуватися,1.40 +полоти,1.40 +прасувати,1.40 +привносити,1.40 +пронизувати,1.40 +проносити,1.40 +просвічувати,1.40 +простежувати,1.40 +підмітати,1.40 +рватися,1.40 +розпиляти,1.40 +сковувати,1.40 +скручувати,1.40 +стихати,1.40 +тішитися,1.40 +вилізати,1.39 +виплавляти,1.39 +десантуватися,1.39 +заміщатися,1.39 +йменувати,1.39 +кодифікувати,1.39 +крапати,1.39 +культивуватися,1.39 +населяти,1.39 +партизанити,1.39 +пустошити,1.39 +розвідувати,1.39 +учителювати,1.39 +цивілізувати,1.39 +агрегатуватися,1.37 +агрегувати,1.37 +акліматизуватися,1.37 +анімувати,1.37 +вбиратися,1.37 +вигинатися,1.37 +видавлювати,1.37 +вимивати,1.37 +виповзати,1.37 +владарювати,1.37 +воскресати,1.37 +вщухати,1.37 +відвертатися,1.37 +відкопувати,1.37 +гарантуватися,1.37 +гукати,1.37 +догравати,1.37 +доробляти,1.37 +епатувати,1.37 +загартовувати,1.37 +задирати,1.37 +затискати,1.37 +збуджуватися,1.37 +зволожувати,1.37 +зрушувати,1.37 +зцілюватися,1.37 +курувати,1.37 +литися,1.37 +лібералізувати,1.37 +маринувати,1.37 +масштабуватися,1.37 +модернізуватися,1.37 +натирати,1.37 +огороджувати,1.37 +окреслювати,1.37 +переосмислювати,1.37 +приручати,1.37 +святкуватися,1.37 +скручуватися,1.37 +скріплювати,1.37 +торувати,1.37 +уподібнюватися,1.37 +усуватися,1.37 +шліфувати,1.37 +єднати,1.37 +інфікуватися,1.37 +бубоніти,1.36 +нависати,1.36 +перезаписувати,1.36 +підгоряти,1.36 +ричати,1.36 +роз'їжджати,1.36 +спадкувати,1.36 +схрещуватися,1.36 +успадковуватися,1.36 +асистувати,1.34 +вдивлятися,1.34 +веслувати,1.34 +викорчовувати,1.34 +викривляти,1.34 +випитувати,1.34 +випрошувати,1.34 +вчинятися,1.34 +відмикати,1.34 +віяти,1.34 +доганяти,1.34 +закручуватися,1.34 +засвоюватися,1.34 +застерігати,1.34 +затягуватися,1.34 +здирати,1.34 +компенсуватися,1.34 +консолідуватися,1.34 +крастися,1.34 +мерехтіти,1.34 +монетизувати,1.34 +облягати,1.34 +перевертатися,1.34 +перекручувати,1.34 +подвоюватися,1.34 +полоскати,1.34 +політизувати,1.34 +поратися,1.34 +порівнюватися,1.34 +постулювати,1.34 +посягати,1.34 +пошкоджуватися,1.34 +прикриватися,1.34 +пришвидшувати,1.34 +прокладатися,1.34 +прослуховуватися,1.34 +під'єднувати,1.34 +підскакувати,1.34 +розбиватися,1.34 +скасовуватися,1.34 +смітити,1.34 +смішити,1.34 +споживатися,1.34 +стогнати,1.34 +тверднути,1.34 +тьмяніти,1.34 +ув'язнювати,1.34 +уводити,1.34 +упроваджувати,1.34 +утверджуватися,1.34 +щедрувати,1.34 +інструктувати,1.34 +викупляти,1.33 +детонувати,1.33 +екіпірувати,1.33 +западати,1.33 +зображуватися,1.33 +йменуватися,1.33 +каталогізувати,1.33 +мерти,1.33 +наймитувати,1.33 +насилати,1.33 +осаджувати,1.33 +присуджуватися,1.33 +підкорюватися,1.33 +підчищати,1.33 +ратифіковувати,1.33 +реквізувати,1.33 +репатріювати,1.33 +розпізнаватися,1.33 +слідити,1.33 +вибудовуватися,1.31 +вириватися,1.31 +висипати,1.31 +витягуватися,1.31 +вкорочувати,1.31 +врівноважувати,1.31 +віщати,1.31 +дисциплінувати,1.31 +діватися,1.31 +евакуйовувати,1.31 +залітати,1.31 +згущуватися,1.31 +злягатися,1.31 +зростатися,1.31 +зшивати,1.31 +зігрівати,1.31 +компостувати,1.31 +криміналізувати,1.31 +линяти,1.31 +мостити,1.31 +надувати,1.31 +назначати,1.31 +об'їздити,1.31 +обводити,1.31 +обгортати,1.31 +облаштовуватися,1.31 +обливати,1.31 +обраховувати,1.31 +обумовлюватися,1.31 +окислюватися,1.31 +отримуватися,1.31 +пережовувати,1.31 +перемальовувати,1.31 +перефарбовувати,1.31 +поривати,1.31 +поїти,1.31 +призивати,1.31 +притягатися,1.31 +проситися,1.31 +під'єднуватися,1.31 +підкріплювати,1.31 +підкуповувати,1.31 +підрозділятися,1.31 +підсвічувати,1.31 +раціоналізувати,1.31 +розпушувати,1.31 +розрізнювати,1.31 +розселяти,1.31 +розсікати,1.31 +різьбити,1.31 +самоорганізовуватися,1.31 +світати,1.31 +скочуватися,1.31 +стилізувати,1.31 +теребити,1.31 +ушкоджувати,1.31 +ґрунтувати,1.31 +вдиратися,1.30 +вияснювати,1.30 +залягати,1.30 +концертувати,1.30 +коронуватися,1.30 +котируватися,1.30 +освідчуватися,1.30 +оцифровувати,1.30 +парафувати,1.30 +приплітати,1.30 +резонувати,1.30 +функціювати,1.30 +акредитувати,1.28 +білити,1.28 +виблискувати,1.28 +витримуватися,1.28 +вичерпуватися,1.28 +відспівувати,1.28 +голосити,1.28 +губитися,1.28 +доливати,1.28 +експропріювати,1.28 +жадати,1.28 +забарвлювати,1.28 +завищувати,1.28 +закидувати,1.28 +занижувати,1.28 +заохочуватися,1.28 +запевняти,1.28 +запрошуватися,1.28 +застигати,1.28 +зумовлюватися,1.28 +зціляти,1.28 +зіштовхуватися,1.28 +комерціалізувати,1.28 +купуватися,1.28 +наближувати,1.28 +наживати,1.28 +намовляти,1.28 +націлювати,1.28 +нормувати,1.28 +обдурювати,1.28 +оголювати,1.28 +окопуватися,1.28 +оскаржуватися,1.28 +патентувати,1.28 +перегортати,1.28 +переобладнувати,1.28 +плануватися,1.28 +понижувати,1.28 +появлятися,1.28 +присипати,1.28 +притягуватися,1.28 +приховуватися,1.28 +провітрювати,1.28 +прокручувати,1.28 +простежуватися,1.28 +проступати,1.28 +протягувати,1.28 +проштовхувати,1.28 +підвішуватися,1.28 +розгойдувати,1.28 +розгойдуватися,1.28 +розряджатися,1.28 +розсипатися,1.28 +розсуватися,1.28 +скупчуватися,1.28 +сплачуватися,1.28 +стиратися,1.28 +тестуватися,1.28 +удаватися,1.28 +удобрювати,1.28 +утілювати,1.28 +худнути,1.28 +хуліганити,1.28 +щепити,1.28 +інкорпорувати,1.28 +відбудовуватися,1.27 +героїзувати,1.27 +демобілізувати,1.27 +дробитися,1.27 +електрифікувати,1.27 +зіставлятися,1.27 +кристалізувати,1.27 +махлювати,1.27 +мусолити,1.27 +нацьковувати,1.27 +няньчитися,1.27 +оброблювати,1.27 +ржати,1.27 +розпродувати,1.27 +хникати,1.27 +штурхати,1.27 +амортизувати,1.25 +аналізуватися,1.25 +артикулювати,1.25 +вибігати,1.25 +вимальовуватися,1.25 +вимикатися,1.25 +вислизати,1.25 +від'єднувати,1.25 +відміряти,1.25 +відшліфовувати,1.25 +гнобити,1.25 +гриміти,1.25 +децентралізувати,1.25 +загадувати,1.25 +задивлятися,1.25 +заключати,1.25 +закупляти,1.25 +заплутувати,1.25 +засліплювати,1.25 +заточувати,1.25 +зафарбовувати,1.25 +злипатися,1.25 +знезаражувати,1.25 +зноситися,1.25 +краяти,1.25 +кропити,1.25 +кульгати,1.25 +маркуватися,1.25 +множитися,1.25 +напувати,1.25 +напускати,1.25 +обсипатися,1.25 +оглушати,1.25 +огортати,1.25 +опромінювати,1.25 +опікувати,1.25 +перегороджувати,1.25 +передплачувати,1.25 +перероджуватися,1.25 +персоналізувати,1.25 +подвоювати,1.25 +пресингувати,1.25 +притулятися,1.25 +проглядатися,1.25 +прогріватися,1.25 +просвіщати,1.25 +пухнути,1.25 +підкладати,1.25 +підмінювати,1.25 +підстригати,1.25 +ректи,1.25 +розкручуватися,1.25 +розноситися,1.25 +сипатися,1.25 +снувати,1.25 +стискувати,1.25 +улаштовувати,1.25 +упиратися,1.25 +урядувати,1.25 +установлюватися,1.25 +шмагати,1.25 +індивідуалізувати,1.25 +пакуватися,1.24 +прирізати,1.24 +вивергати,1.23 +гризтися,1.23 +дешифрувати,1.23 +затухати,1.23 +злизувати,1.23 +зчитуватися,1.23 +кодуватися,1.23 +конвоювати,1.23 +нереститися,1.23 +обвалюватися,1.23 +осушувати,1.23 +перепрофілюватися,1.23 +рекрутувати,1.23 +спамити,1.23 +стикуватися,1.23 +стовбичити,1.23 +абсолютизувати,1.21 +авторизуватися,1.21 +асигнувати,1.21 +багатіти,1.21 +біліти,1.21 +вигинати,1.21 +визрівати,1.21 +виплутуватися,1.21 +виселятися,1.21 +витискати,1.21 +вичерпувати,1.21 +вкидати,1.21 +втомлювати,1.21 +відпускатися,1.21 +відрікатися,1.21 +відіграватися,1.21 +гармоніювати,1.21 +глючити,1.21 +гравірувати,1.21 +довозити,1.21 +дотикатися,1.21 +експонуватися,1.21 +жалувати,1.21 +забуватися,1.21 +завивати,1.21 +запечатувати,1.21 +запилювати,1.21 +заслуховувати,1.21 +збурювати,1.21 +коїтися,1.21 +наживатися,1.21 +наклеювати,1.21 +напиватися,1.21 +натягати,1.21 +націлюватися,1.21 +обмазувати,1.21 +обминати,1.21 +обпалювати,1.21 +обсмажувати,1.21 +оголошуватися,1.21 +ознайомлювати,1.21 +окислювати,1.21 +перебиватися,1.21 +перев'язувати,1.21 +переключати,1.21 +перепродавати,1.21 +плодити,1.21 +позиціювати,1.21 +полірувати,1.21 +порядкувати,1.21 +посівати,1.21 +прикладатися,1.21 +приманювати,1.21 +прогинатися,1.21 +пропускатися,1.21 +рекламуватися,1.21 +ремонтуватися,1.21 +роз'їжджатися,1.21 +розблоковувати,1.21 +роздирати,1.21 +розучувати,1.21 +розігріватися,1.21 +самовідновлюватися,1.21 +спасати,1.21 +спричинятися,1.21 +стругати,1.21 +стягати,1.21 +сурмити,1.21 +сушитися,1.21 +тесати,1.21 +товкти,1.21 +увічнювати,1.21 +ущільнюватися,1.21 +формулюватися,1.21 +хитати,1.21 +щебетати,1.21 +щезати,1.21 +ізолюватися,1.21 +інактивувати,1.21 +інтернувати,1.21 +відкочуватися,1.20 +дощити,1.20 +дутися,1.20 +екранувати,1.20 +житися,1.20 +лабати,1.20 +навертатися,1.20 +переплавляти,1.20 +пересилатися,1.20 +побутувати,1.20 +полонізувати,1.20 +прогортати,1.20 +розколюватися,1.20 +спирати,1.20 +спихати,1.20 +спішитися,1.20 +абстрагувати,1.17 +атрофуватися,1.17 +бичувати,1.17 +блефувати,1.17 +веселити,1.17 +видиратися,1.17 +видихати,1.17 +виключатися,1.17 +вирівнюватися,1.17 +висмикувати,1.17 +висікати,1.17 +вихвалятися,1.17 +вичищати,1.17 +вростати,1.17 +відварювати,1.17 +відсувати,1.17 +ганити,1.17 +дегустувати,1.17 +домальовувати,1.17 +женити,1.17 +жовтіти,1.17 +зав'язуватися,1.17 +завмирати,1.17 +загоювати,1.17 +заноситися,1.17 +занотовувати,1.17 +запасатися,1.17 +започатковувати,1.17 +засмоктувати,1.17 +застрягати,1.17 +здавлювати,1.17 +зрізувати,1.17 +калібрувати,1.17 +конати,1.17 +кооперуватися,1.17 +коптити,1.17 +красуватися,1.17 +кришити,1.17 +кришитися,1.17 +кроїти,1.17 +лихословити,1.17 +метаболізувати,1.17 +миготіти,1.17 +мирити,1.17 +мордувати,1.17 +мудрувати,1.17 +муркотіти,1.17 +наділятися,1.17 +накачувати,1.17 +нанизувати,1.17 +наражатися,1.17 +нудити,1.17 +обдаровувати,1.17 +обігрувати,1.17 +одружувати,1.17 +омолоджувати,1.17 +опонувати,1.17 +ослаблювати,1.17 +ослабляти,1.17 +перевиконувати,1.17 +перегріватися,1.17 +переконуватися,1.17 +переливатися,1.17 +перепродувати,1.17 +перескакувати,1.17 +повідомлятися,1.17 +позиватися,1.17 +позіхати,1.17 +поновлюватися,1.17 +послаблюватися,1.17 +послабляти,1.17 +прибиратися,1.17 +прирівнюватися,1.17 +притискатися,1.17 +провертати,1.17 +пролазити,1.17 +просочувати,1.17 +під'їжджати,1.17 +підмовляти,1.17 +підноситися,1.17 +підпливати,1.17 +ревізувати,1.17 +регресувати,1.17 +реорганізуватися,1.17 +розклеювати,1.17 +розлучати,1.17 +сегментувати,1.17 +секретувати,1.17 +скошувати,1.17 +скрипіти,1.17 +солідаризуватися,1.17 +стелити,1.17 +стимулюватися,1.17 +сідлати,1.17 +терзати,1.17 +тертися,1.17 +точитися,1.17 +травити,1.17 +уповільнюватися,1.17 +усвідомлюватися,1.17 +уступати,1.17 +ущільнювати,1.17 +фасувати,1.17 +фундувати,1.17 +частувати,1.17 +шкребти,1.17 +вивергатися,1.16 +відніматися,1.16 +дислокуватися,1.16 +ексгумувати,1.16 +заломлювати,1.16 +заміщуватися,1.16 +зміщати,1.16 +зондувати,1.16 +надуватися,1.16 +нарощуватися,1.16 +обживатися,1.16 +регенеруватися,1.16 +розчленовувати,1.16 +спаровуватися,1.16 +стрімити,1.16 +сублімувати,1.16 +телепортувати,1.16 +борознити,1.12 +бідувати,1.12 +верзти,1.12 +виварювати,1.12 +вивозитися,1.12 +викреслювати,1.12 +викупатися,1.12 +вимінювати,1.12 +випаровувати,1.12 +висипатися,1.12 +вмивати,1.12 +впиратися,1.12 +від'єднуватися,1.12 +відбиратися,1.12 +відключатися,1.12 +відкушувати,1.12 +відпрацьовуватися,1.12 +відфільтровувати,1.12 +відшаровуватися,1.12 +герметизувати,1.12 +гонити,1.12 +деконструювати,1.12 +добуватися,1.12 +завалюватися,1.12 +заглушати,1.12 +задобрювати,1.12 +закипати,1.12 +закупати,1.12 +засиджуватися,1.12 +засихати,1.12 +засмічувати,1.12 +збовтувати,1.12 +звалюватися,1.12 +звеличувати,1.12 +злазити,1.12 +злословити,1.12 +квасити,1.12 +концептуалізувати,1.12 +матеріалізувати,1.12 +музикувати,1.12 +набрякати,1.12 +напрацьовувати,1.12 +нотувати,1.12 +нявкати,1.12 +оббивати,1.12 +обніматися,1.12 +обрізувати,1.12 +опадати,1.12 +освітлюватися,1.12 +очищатися,1.12 +перевалювати,1.12 +перевдягатися,1.12 +передруковувати,1.12 +перекушувати,1.12 +поглиблюватися,1.12 +пом'якшуватися,1.12 +помішувати,1.12 +поневолювати,1.12 +поринати,1.12 +поселяти,1.12 +поститися,1.12 +постувати,1.12 +прикріпляти,1.12 +примиряти,1.12 +приправляти,1.12 +продувати,1.12 +проставляти,1.12 +пульсувати,1.12 +підводитися,1.12 +піддержувати,1.12 +підмішувати,1.12 +редагуватися,1.12 +ретируватися,1.12 +рефінансувати,1.12 +риніти,1.12 +роз'їдати,1.12 +роздмухувати,1.12 +розміновувати,1.12 +розсаджувати,1.12 +розтинати,1.12 +священнодіяти,1.12 +соціалізуватися,1.12 +спалюватися,1.12 +спотворюватися,1.12 +співпадати,1.12 +ставатися,1.12 +стикувати,1.12 +травмуватися,1.12 +трубити,1.12 +тулитися,1.12 +уважатися,1.12 +усамітнюватися,1.12 +усиновлювати,1.12 +фабрикувати,1.12 +хитрувати,1.12 +чахнути,1.12 +імпортуватися,1.12 +іронізувати,1.12 +вижимати,1.11 +виокремлюватися,1.11 +вироджуватися,1.11 +відкуповуватися,1.11 +демобілізуватися,1.11 +заклинати,1.11 +замотувати,1.11 +звільнювати,1.11 +модифікуватися,1.11 +мікшувати,1.11 +об'їдати,1.11 +офірувати,1.11 +перезаряджатися,1.11 +пересмикувати,1.11 +потикати,1.11 +прибудовувати,1.11 +причалювати,1.11 +режисерувати,1.11 +ригати,1.11 +снити,1.11 +сплавляти,1.11 +споруджуватися,1.11 +страшити,1.11 +титулувати,1.11 +титулуватися,1.11 +тріпотіти,1.11 +холонути,1.11 +баритися,1.07 +бризкати,1.07 +бушувати,1.07 +вдувати,1.07 +вивідувати,1.07 +вимітати,1.07 +випорожнюватися,1.07 +випрямляти,1.07 +вирувати,1.07 +виснажуватися,1.07 +виспівувати,1.07 +вистоювати,1.07 +висушувати,1.07 +витати,1.07 +витися,1.07 +витягатися,1.07 +вмонтовувати,1.07 +вправляти,1.07 +вражатися,1.07 +вслухатися,1.07 +втримуватися,1.07 +відгодовувати,1.07 +відскакувати,1.07 +відстороняти,1.07 +галасувати,1.07 +гальмуватися,1.07 +гаснути,1.07 +девальвувати,1.07 +дезавуювати,1.07 +дезактивувати,1.07 +дислокувати,1.07 +добудовуватися,1.07 +довантажувати,1.07 +домислювати,1.07 +дрижати,1.07 +завалювати,1.07 +заводитися,1.07 +запалюватися,1.07 +запліднювати,1.07 +заповзати,1.07 +заручатися,1.07 +затемнювати,1.07 +затінювати,1.07 +захаращувати,1.07 +заїкатися,1.07 +збиватися,1.07 +зближатися,1.07 +звірятися,1.07 +згорати,1.07 +згоряти,1.07 +здригатися,1.07 +зміщатися,1.07 +зриватися,1.07 +зрівнювати,1.07 +зісковзувати,1.07 +зіштовхувати,1.07 +картати,1.07 +колисати,1.07 +компрометувати,1.07 +комутувати,1.07 +купірувати,1.07 +майоріти,1.07 +модернізовувати,1.07 +міксувати,1.07 +мімікрувати,1.07 +навішувати,1.07 +нагромаджуватися,1.07 +надолужувати,1.07 +надрізати,1.07 +накрапати,1.07 +налічуватися,1.07 +намазувати,1.07 +обнародувати,1.07 +обривати,1.07 +обшивати,1.07 +обігрівати,1.07 +округляти,1.07 +омивати,1.07 +оподатковуватися,1.07 +оскверняти,1.07 +оформлюватися,1.07 +переварювати,1.07 +перевизначати,1.07 +перегодовувати,1.07 +передчувати,1.07 +перелазити,1.07 +перемелювати,1.07 +переорієнтовуватися,1.07 +перерозподілятися,1.07 +перешивати,1.07 +плазувати,1.07 +повторяти,1.07 +поляризувати,1.07 +попадатися,1.07 +прилучатися,1.07 +припускатися,1.07 +причащати,1.07 +приятелювати,1.07 +прокатувати,1.07 +проламувати,1.07 +прослизати,1.07 +простягати,1.07 +прочитувати,1.07 +підготовляти,1.07 +підливати,1.07 +підпирати,1.07 +підроблювати,1.07 +підхоплювати,1.07 +реформуватися,1.07 +рецидивувати,1.07 +реінвестувати,1.07 +розбухати,1.07 +розвантажуватися,1.07 +роздуватися,1.07 +розмивати,1.07 +розряджати,1.07 +розхитуватися,1.07 +розшаровуватися,1.07 +самовдосконалюватися,1.07 +самовідтворюватися,1.07 +смажитися,1.07 +стомлювати,1.07 +строїти,1.07 +транслітерувати,1.07 +тріщати,1.07 +уділяти,1.07 +узаконювати,1.07 +уточнюватися,1.07 +утрачати,1.07 +числитися,1.07 +чорніти,1.07 +ініціюватися,1.07 +акліматизувати,1.06 +вшиватися,1.06 +гілкуватися,1.06 +дуркувати,1.06 +експресувати,1.06 +збиткуватися,1.06 +змикатися,1.06 +конденсувати,1.06 +копіюватися,1.06 +кущитися,1.06 +паруватися,1.06 +переспівувати,1.06 +підтасовувати,1.06 +сповідуватися,1.06 +транскрибувати,1.06 +фігачити,1.06 +шкандибати,1.06 +шкрябати,1.06 +інвертувати,1.06 +адресуватися,1.01 +акумулюватися,1.01 +акцептувати,1.01 +берегтися,1.01 +вивертатися,1.01 +видувати,1.01 +видужувати,1.01 +вимочувати,1.01 +випльовувати,1.01 +випробовуватися,1.01 +висвітлюватися,1.01 +височіти,1.01 +витворювати,1.01 +вичитувати,1.01 +вишколювати,1.01 +вкорінюватися,1.01 +встромляти,1.01 +відгадувати,1.01 +відлічувати,1.01 +відмежовуватися,1.01 +відрубувати,1.01 +відсипати,1.01 +відслідковуватися,1.01 +відступатися,1.01 +відсікати,1.01 +віщувати,1.01 +гнітити,1.01 +городити,1.01 +гострити,1.01 +деполітизувати,1.01 +держатися,1.01 +докуповувати,1.01 +домішувати,1.01 +доопрацьовувати,1.01 +доїдати,1.01 +доїжджати,1.01 +драти,1.01 +душпастирювати,1.01 +діагностуватися,1.01 +екстрагувати,1.01 +етапувати,1.01 +забігати,1.01 +заволодівати,1.01 +загарбувати,1.01 +заговорювати,1.01 +замішувати,1.01 +заперечуватися,1.01 +заплющувати,1.01 +засікати,1.01 +затихати,1.01 +зацікавлювати,1.01 +заявлятися,1.01 +зваблювати,1.01 +звершуватися,1.01 +зголошуватися,1.01 +здобуватися,1.01 +зміняти,1.01 +знецінюватися,1.01 +зріти,1.01 +клеїтися,1.01 +кооптувати,1.01 +кінчатися,1.01 +линути,1.01 +лукавити,1.01 +мастити,1.01 +мислитися,1.01 +молодіти,1.01 +назрівати,1.01 +наряджати,1.01 +насаджуватися,1.01 +начиняти,1.01 +об'єктивувати,1.01 +оббирати,1.01 +обвинувачувати,1.01 +обгороджувати,1.01 +обмотувати,1.01 +окупатися,1.01 +опротестовувати,1.01 +очищуватися,1.01 +перевиховувати,1.01 +перезавантажуватися,1.01 +перекочувати,1.01 +перекочуватися,1.01 +перемовлятися,1.01 +переписуватися,1.01 +переплітати,1.01 +переповзати,1.01 +пересипати,1.01 +переїдати,1.01 +петляти,1.01 +поважатися,1.01 +повелівати,1.01 +посвідчувати,1.01 +потопати,1.01 +препарувати,1.01 +придавати,1.01 +пробуксовувати,1.01 +провіщати,1.01 +програватися,1.01 +прогризати,1.01 +протикати,1.01 +прошивати,1.01 +підпрацьовувати,1.01 +підстерігати,1.01 +підступати,1.01 +підтоплювати,1.01 +підшуковувати,1.01 +ретушувати,1.01 +рисувати,1.01 +розбещувати,1.01 +розливатися,1.01 +розлітатися,1.01 +розповзатися,1.01 +розправляти,1.01 +розсилатися,1.01 +розслабляти,1.01 +розсмоктуватися,1.01 +розставатися,1.01 +розтискати,1.01 +розтрачувати,1.01 +розтріскуватися,1.01 +рулити,1.01 +ручатися,1.01 +свербіти,1.01 +скребти,1.01 +сліпнути,1.01 +соціалізувати,1.01 +сплітати,1.01 +сповняти,1.01 +стригтися,1.01 +супроводитися,1.01 +таврувати,1.01 +тамувати,1.01 +тарабанити,1.01 +теоретизувати,1.01 +укомплектовувати,1.01 +укривати,1.01 +умертвляти,1.01 +чатувати,1.01 +чинитися,1.01 +шипіти,1.01 +імігрувати,1.01 diff --git a/gallery/ukr/argument_structure/utils/frequency.py b/gallery/ukr/argument_structure/utils/frequency.py new file mode 100644 index 0000000..b7179bc --- /dev/null +++ b/gallery/ukr/argument_structure/utils/frequency.py @@ -0,0 +1,64 @@ +"""Verb frequency lookup. + +Frequencies are read from ``resources/verb_frequencies.csv``, a committed table +derived from the wordfreq package. Keeping it as data means the pipeline needs no +wordfreq dependency and the ranking stays fixed across runs. Regenerate it with +``build_frequencies.py``. +""" + +from __future__ import annotations + +import csv +from collections.abc import Iterable +from functools import cache +from pathlib import Path + +FREQUENCY_PATH = Path(__file__).parent.parent / "resources" / "verb_frequencies.csv" + + +@cache +def _frequencies() -> dict[str, float]: + """Return the lemma to Zipf frequency table, read once. + + Returns + ------- + dict[str, float] + Zipf frequency keyed by lemma, for attested lemmas only. + """ + with FREQUENCY_PATH.open(encoding="utf-8") as f: + return {row["lemma"]: float(row["zipf"]) for row in csv.DictReader(f)} + + +def zipf(lemma: str) -> float: + """Return the Zipf frequency of a lemma. + + Parameters + ---------- + lemma : str + Dictionary form to look up. + + Returns + ------- + float + Zipf frequency, or 0.0 when the lemma is unattested. + """ + return _frequencies().get(lemma, 0.0) + + +def most_frequent(lemmas: Iterable[str], limit: int) -> list[str]: + """Return the most frequent lemmas, most frequent first. + + Parameters + ---------- + lemmas : Iterable[str] + Lemmas to rank; duplicates are ignored. + limit : int + Number of lemmas to keep. + + Returns + ------- + list[str] + At most ``limit`` lemmas, ordered by decreasing frequency then + alphabetically so ties are stable. + """ + return sorted(set(lemmas), key=lambda lemma: (-zipf(lemma), lemma))[:limit] From b9b24e9ee587aa1f320bcb751afbed24b8a77c3f Mon Sep 17 00:00:00 2001 From: Pavlo Kuchmiichuk Date: Tue, 21 Jul 2026 23:53:03 -0400 Subject: [PATCH 09/16] Excludes configured verbs from the verb slot via a set-membership constraint. --- gallery/ukr/argument_structure/config.yaml | 6 ++ .../argument_structure/generate_templates.py | 65 ++++++++++++++++--- 2 files changed, 62 insertions(+), 9 deletions(-) diff --git a/gallery/ukr/argument_structure/config.yaml b/gallery/ukr/argument_structure/config.yaml index 05992cd..c0bc9fc 100644 --- a/gallery/ukr/argument_structure/config.yaml +++ b/gallery/ukr/argument_structure/config.yaml @@ -35,6 +35,12 @@ template: # Output path for filled templates output_path: "items/filled.jsonl" + # Excluded from the verb slot. + verb_stoplist: + - "бути" # copula + - "могти" # modal + - "смеркати" # impersonal + # Masked language model settings, used only by slots whose strategy is "mlm". # Not yet finalized; unused while every slot below is exhaustive. mlm: diff --git a/gallery/ukr/argument_structure/generate_templates.py b/gallery/ukr/argument_structure/generate_templates.py index 7147cdb..9090054 100644 --- a/gallery/ukr/argument_structure/generate_templates.py +++ b/gallery/ukr/argument_structure/generate_templates.py @@ -12,19 +12,40 @@ from dataclasses import dataclass from pathlib import Path +import yaml + from bead.cli.display import ( display_file_stats, print_header, print_info, print_success, ) +from bead.resources.constraint_builders import SetMembershipConstraintBuilder from bead.resources.constraints import Constraint from bead.resources.template import Slot, Template, slots_match_template from bead.resources.template_collection import TemplateCollection +BASE_DIR = Path(__file__).parent LANGUAGE = "ukr" +def load_config(path: Path) -> dict: + """Load the YAML configuration file. + + Parameters + ---------- + path : Path + Path to the configuration file. + + Returns + ------- + dict + Parsed configuration. + """ + with path.open(encoding="utf-8") as f: + return yaml.safe_load(f) + + @dataclass(frozen=True) class ArgSlot: """A noun slot in a frame, constrained by role and case. @@ -122,27 +143,48 @@ def _arg_slot(arg: ArgSlot) -> Slot: ) -def _verb_slot() -> Slot: - """Return a slot constrained to present-tense verbs.""" +def _verb_slot(stoplist: tuple[str, ...] = ()) -> Slot: + """Return a slot constrained to present-tense verbs outside the stoplist. + + Parameters + ---------- + stoplist : tuple[str, ...] + Lemmas the slot must not accept. + + Returns + ------- + Slot + The verb slot. + """ expression = ( "self.features.get('pos') == 'V' and self.features.get('tense') == 'PRS'" ) + constraints = [Constraint(expression=expression, description="Present-tense verb")] + if stoplist: + constraints.append( + SetMembershipConstraintBuilder().build( + slot_name="self", + property_path="lemma", + forbidden_values=set(stoplist), + description="Excluded from the verb slot", + ) + ) return Slot( name="verb", description="present-tense verb", - constraints=( - Constraint(expression=expression, description="Present-tense verb"), - ), + constraints=tuple(constraints), ) -def make_frame(spec: FrameSpec) -> Template: +def make_frame(spec: FrameSpec, stoplist: tuple[str, ...] = ()) -> Template: """Build the ``Template`` for one frame specification. Parameters ---------- spec : FrameSpec The frame to build. + stoplist : tuple[str, ...] + Lemmas the verb slot must not accept. Returns ------- @@ -150,7 +192,7 @@ def make_frame(spec: FrameSpec) -> Template: A frame with a verb slot and one constrained slot per argument. """ slots: dict[str, Slot] = {arg.name: _arg_slot(arg) for arg in spec.args} - slots["verb"] = _verb_slot() + slots["verb"] = _verb_slot(stoplist) template = Template( name=spec.name, @@ -168,7 +210,12 @@ def main() -> None: """Build the frames and write them to ``templates/generic_frames.jsonl``.""" print_header("Sentence Frames") - templates = [make_frame(spec) for spec in FRAME_SPECS] + config = load_config(BASE_DIR / "config.yaml") + stoplist = tuple(config["template"].get("verb_stoplist", ())) + if stoplist: + print_info(f"Excluding {len(stoplist)} verbs: {', '.join(stoplist)}") + + templates = [make_frame(spec, stoplist) for spec in FRAME_SPECS] for template in templates: print_info(f"{template.name}: {template.template_string}") @@ -177,7 +224,7 @@ def main() -> None: ).with_templates(templates) print_success(f"Built {len(templates)} frames") - output_path = Path(__file__).parent / "templates" / "generic_frames.jsonl" + output_path = BASE_DIR / "templates" / "generic_frames.jsonl" output_path.parent.mkdir(parents=True, exist_ok=True) collection.to_jsonl(str(output_path)) display_file_stats(output_path, len(templates), "frames") From 0b4d16576576bdcc242b906bc27628a32175ec02 Mon Sep 17 00:00:00 2001 From: Pavlo Kuchmiichuk Date: Tue, 21 Jul 2026 23:53:03 -0400 Subject: [PATCH 10/16] Adds anchor verb comparisons to the Ukrainian pair stage. --- gallery/ukr/argument_structure/config.yaml | 10 ++- .../argument_structure/create_2afc_pairs.py | 83 ++++++++++++++++++- .../ukr/argument_structure/fill_templates.py | 20 ++++- 3 files changed, 105 insertions(+), 8 deletions(-) diff --git a/gallery/ukr/argument_structure/config.yaml b/gallery/ukr/argument_structure/config.yaml index c0bc9fc..d180f58 100644 --- a/gallery/ukr/argument_structure/config.yaml +++ b/gallery/ukr/argument_structure/config.yaml @@ -102,9 +102,17 @@ items: # and the noun fixed. Pairs whose two sentences render identically (case # syncretism) are dropped. construction: - group_by: ["verb_lemma", "object_class"] + group_by: ["verb_lemma", "object_lemma"] drop_identical: true + # Verbs with known government, paired against a test verb in the same frame + # so verbs share a scale. Chosen to combine with every bleached noun. + anchors: + ACC: "вивчати" + GEN: "стосуватися" + DAT: "радіти" + INS: "цікавитися" + lists: quantile_bins: 10 diff --git a/gallery/ukr/argument_structure/create_2afc_pairs.py b/gallery/ukr/argument_structure/create_2afc_pairs.py index 8773698..3fd9a4f 100644 --- a/gallery/ukr/argument_structure/create_2afc_pairs.py +++ b/gallery/ukr/argument_structure/create_2afc_pairs.py @@ -26,7 +26,10 @@ print_success, print_warning, ) -from bead.items.forced_choice import create_filtered_forced_choice_items +from bead.items.forced_choice import ( + create_filtered_forced_choice_items, + create_forced_choice_item, +) from bead.items.item import Item from bead.items.scoring import ItemScorer, LanguageModelScorer from bead.lists.stratification import assign_quantiles_by_uuid @@ -238,6 +241,73 @@ def text_of(item: Item) -> str: return enriched +def make_anchor_pairs(items: list[Item], anchors: dict[str, str]) -> list[Item]: + """Pair each verb against the anchor verb for its case, noun held fixed. + + The anchor's government is known, so the comparison puts test verbs on a + scale shared across verbs and exposes verbs that fit no frame at all. + + Parameters + ---------- + items : list[Item] + Scored, object-bearing items. + anchors : dict[str, str] + Anchor lemma for each case. + + Returns + ------- + list[Item] + Forced-choice pairs contrasting a verb with its case's anchor. + """ + by_key = { + ( + item.item_metadata["verb_lemma"], + item.item_metadata["case"], + item.item_metadata["object_lemma"], + ): item + for item in items + } + + pairs: list[Item] = [] + for item in items: + case_name = item.item_metadata["case"] + verb = item.item_metadata["verb_lemma"] + anchor_lemma = anchors.get(str(case_name)) + if anchor_lemma is None or verb == anchor_lemma: + continue + key = (anchor_lemma, case_name, item.item_metadata["object_lemma"]) + anchor = by_key.get(key) + if anchor is None: + continue + + text = item.rendered_elements.get("text", "") + anchor_text = anchor.rendered_elements.get("text", "") + if text == anchor_text: + continue + score = item.item_metadata["lm_score"] + anchor_score = anchor.item_metadata["lm_score"] + pairs.append( + create_forced_choice_item( + text, + anchor_text, + metadata={ + "pair_type": "anchor_contrast", + "verb": verb, + "anchor": anchor_lemma, + "case": case_name, + "contrast": f"anchor-{case_name}", + "object_lemma": item.item_metadata["object_lemma"], + "object_class": item.item_metadata["object_class"], + "lm_score_a": score, + "lm_score_b": anchor_score, + "lm_score_diff": abs(score - anchor_score), + "source_item_ids": (str(item.id), str(anchor.id)), + }, + ) + ) + return pairs + + def main( config_path: Path, limit: int | None = None, @@ -286,8 +356,15 @@ def main( for item, score in zip(items, scores, strict=True) ] - pairs = make_pairs(items) - print_success(f"Built {len(pairs):,} case-contrast pairs") + case_pairs = make_pairs(items) + anchor_pairs = make_anchor_pairs( + items, config["items"]["construction"]["anchors"] + ) + pairs = case_pairs + anchor_pairs + print_success( + f"Built {len(case_pairs):,} case-contrast and " + f"{len(anchor_pairs):,} anchor pairs" + ) if not pairs: print_warning("No pairs were created.") return diff --git a/gallery/ukr/argument_structure/fill_templates.py b/gallery/ukr/argument_structure/fill_templates.py index 93601f0..3db21a9 100644 --- a/gallery/ukr/argument_structure/fill_templates.py +++ b/gallery/ukr/argument_structure/fill_templates.py @@ -12,6 +12,7 @@ import argparse import logging +from collections.abc import Iterable from itertools import islice from pathlib import Path @@ -110,7 +111,11 @@ def build_strategy(config: dict) -> MixedFillingStrategy: def limit_verbs( - lexicon: Lexicon, limit: int, *, by_frequency: bool = False + lexicon: Lexicon, + limit: int, + *, + by_frequency: bool = False, + keep: Iterable[str] = (), ) -> Lexicon: """Return a lexicon keeping only ``limit`` unique verb lemmas. @@ -123,11 +128,14 @@ def limit_verbs( by_frequency : bool Keep the most frequent lemmas instead of the first ones, since VESUM is alphabetical and the first lemmas are rare rather than everyday verbs. + keep : Iterable[str] + Lemmas retained whatever the limit, for the anchor verbs the pair stage + compares against. Returns ------- Lexicon - Lexicon restricted to ``limit`` lemmas. + Lexicon restricted to ``limit`` lemmas plus ``keep``. """ if by_frequency: allowed = set(most_frequent((item.lemma for item in lexicon.items), limit)) @@ -139,6 +147,7 @@ def limit_verbs( if len(lemmas) >= limit: break allowed = set(lemmas) + allowed |= set(keep) return lexicon.filter(lambda item: item.lemma in allowed) @@ -187,11 +196,14 @@ def main( str(BASE_DIR / lex_config["path"]), lex_config["name"] ) if limit is not None and lex_config["name"] == "verbs": - lexicon = limit_verbs(lexicon, limit, by_frequency=by_frequency) + anchors = config["items"]["construction"]["anchors"].values() + lexicon = limit_verbs( + lexicon, limit, by_frequency=by_frequency, keep=anchors + ) selection = "most frequent" if by_frequency else "first" print_warning( f"Limited verbs to the {selection} {limit} lemmas " - f"({len(lexicon)} forms)" + f"plus {len(set(anchors))} anchors ({len(lexicon)} forms)" ) lexicons.append(lexicon) print_info(f"Loaded {len(lexicon)} items from {lex_config['name']}") From 7be7cbe8cfa469b379caa2912c6acd016dc629ff Mon Sep 17 00:00:00 2001 From: Pavlo Kuchmiichuk Date: Wed, 22 Jul 2026 12:05:58 -0400 Subject: [PATCH 11/16] Adds the list partitioning stage for the Ukrainian pipeline. --- gallery/ukr/argument_structure/config.yaml | 41 +++ .../ukr/argument_structure/generate_lists.py | 320 ++++++++++++++++++ 2 files changed, 361 insertions(+) create mode 100644 gallery/ukr/argument_structure/generate_lists.py diff --git a/gallery/ukr/argument_structure/config.yaml b/gallery/ukr/argument_structure/config.yaml index d180f58..a7ef9f2 100644 --- a/gallery/ukr/argument_structure/config.yaml +++ b/gallery/ukr/argument_structure/config.yaml @@ -12,6 +12,7 @@ project: paths: cache_dir: ".cache" 2afc_pairs: "items/2afc_pairs.jsonl" + experiment_lists: "lists/experiment_lists.jsonl" resources: templates: @@ -114,7 +115,47 @@ items: INS: "цікавитися" lists: + strategy: "balanced" + n_lists: 16 + items_per_list: 50 quantile_bins: 10 + random_seed: 42 + + # "item" is the pair's metadata dict, so properties are read as item['key']. + # The balance target_counts also fix how many of each pair type are drawn. + constraints: + - type: "balance" + property_expression: "item['pair_type']" + target_counts: + case_contrast: 40 + anchor_contrast: 10 + + - type: "uniqueness" + property_expression: "item['verb']" + + batch_constraints: + - type: "coverage" + property_expression: "item['contrast']" + target_values: + - "ACC-GEN" + - "ACC-DAT" + - "ACC-INS" + - "DAT-GEN" + - "GEN-INS" + - "DAT-INS" + - "anchor-ACC" + - "anchor-GEN" + - "anchor-DAT" + - "anchor-INS" + min_coverage: 1.0 + + - type: "min_occurrence" + property_expression: "item['quantile']" + min_occurrences: 20 + + - type: "diversity" + property_expression: "item['verb']" + max_lists_per_value: 6 logging: level: "WARNING" diff --git a/gallery/ukr/argument_structure/generate_lists.py b/gallery/ukr/argument_structure/generate_lists.py new file mode 100644 index 0000000..1ae4d69 --- /dev/null +++ b/gallery/ukr/argument_structure/generate_lists.py @@ -0,0 +1,320 @@ +#!/usr/bin/env python3 +"""Partition the 2AFC pairs into experiment lists. + +Each list is one version of the experiment: a participant judges its pairs, and +several participants can take the same list. Pairs are drawn from the pool in +the proportions the balance constraint declares, then distributed so that no +verb repeats within a list and difficulty stays spread. + +Lists reference pairs by UUID, so the pair text lives only in the 2AFC file. +""" + +from __future__ import annotations + +import argparse +import logging +from pathlib import Path +from random import Random +from uuid import uuid4 + +import yaml + +from bead.cli.display import ( + display_file_stats, + print_header, + print_info, + print_success, + print_warning, +) +from bead.items.item import Item +from bead.lists.constraints import ( + BalanceConstraint, + BatchConstraint, + BatchCoverageConstraint, + BatchDiversityConstraint, + BatchMinOccurrenceConstraint, + DiversityConstraint, + ListConstraint, + UniquenessConstraint, +) +from bead.lists.list_collection import ListCollection +from bead.lists.partitioner import ListPartitioner, MetadataDict + +BASE_DIR = Path(__file__).parent + + +def load_config(path: Path) -> dict: + """Load the YAML configuration file. + + Parameters + ---------- + path : Path + Path to the configuration file. + + Returns + ------- + dict + Parsed configuration. + """ + with path.open(encoding="utf-8") as f: + return yaml.safe_load(f) + + +def load_pairs(path: Path) -> list[Item]: + """Load 2AFC pairs from JSONL. + + Parameters + ---------- + path : Path + Path to the pairs file. + + Returns + ------- + list[Item] + The loaded pairs. + """ + with path.open(encoding="utf-8") as f: + return [Item.model_validate_json(line) for line in f if line.strip()] + + +def build_list_constraints(specs: list[dict]) -> list[ListConstraint]: + """Build per-list constraints from their config entries. + + Parameters + ---------- + specs : list[dict] + Constraint entries, each with a ``type`` key. + + Returns + ------- + list[ListConstraint] + The constructed constraints. + + Raises + ------ + ValueError + If a constraint type is unknown. + """ + constraints: list[ListConstraint] = [] + for spec in specs: + kind = spec["type"] + if kind == "balance": + constraints.append( + BalanceConstraint( + property_expression=spec["property_expression"], + target_counts=spec.get("target_counts"), + ) + ) + elif kind == "uniqueness": + constraints.append( + UniquenessConstraint(property_expression=spec["property_expression"]) + ) + elif kind == "diversity": + constraints.append( + DiversityConstraint( + property_expression=spec["property_expression"], + min_unique_values=spec["min_unique_values"], + ) + ) + else: + raise ValueError(f"Unknown list constraint type: {kind!r}") + return constraints + + +def build_batch_constraints(specs: list[dict]) -> list[BatchConstraint]: + """Build cross-list constraints from their config entries. + + Parameters + ---------- + specs : list[dict] + Constraint entries, each with a ``type`` key. + + Returns + ------- + list[BatchConstraint] + The constructed constraints. + + Raises + ------ + ValueError + If a constraint type is unknown. + """ + constraints: list[BatchConstraint] = [] + for spec in specs: + kind = spec["type"] + if kind == "coverage": + constraints.append( + BatchCoverageConstraint( + property_expression=spec["property_expression"], + target_values=spec["target_values"], + min_coverage=spec.get("min_coverage", 1.0), + ) + ) + elif kind == "min_occurrence": + constraints.append( + BatchMinOccurrenceConstraint( + property_expression=spec["property_expression"], + min_occurrences=spec["min_occurrences"], + ) + ) + elif kind == "diversity": + constraints.append( + BatchDiversityConstraint( + property_expression=spec["property_expression"], + max_lists_per_value=spec["max_lists_per_value"], + ) + ) + else: + raise ValueError(f"Unknown batch constraint type: {kind!r}") + return constraints + + +def draw_pool( + pairs: list[Item], target_counts: dict[str, int], n_lists: int, seed: int +) -> list[Item]: + """Draw the pairs to partition, sampling each type in proportion. + + Sampling is random rather than positional: the pairs file is ordered by + verb and frame, so taking a prefix would cover only a handful of verbs. + + Parameters + ---------- + pairs : list[Item] + All available pairs. + target_counts : dict[str, int] + Pairs of each type per list. + n_lists : int + Number of lists to fill. + seed : int + Seed for the draw. + + Returns + ------- + list[Item] + The sampled pairs. + + Raises + ------ + ValueError + If the pool holds too few pairs of a type. + """ + rng = Random(seed) + by_type: dict[str, list[Item]] = {} + for pair in pairs: + kind = str(pair.item_metadata.get("pair_type")) + by_type.setdefault(kind, []).append(pair) + + drawn: list[Item] = [] + for kind, per_list in target_counts.items(): + wanted = per_list * n_lists + available = by_type.get(kind, []) + if len(available) < wanted: + raise ValueError( + f"Need {wanted} {kind} pairs but only {len(available)} available" + ) + drawn.extend(rng.sample(available, wanted)) + print_info(f"Drew {wanted:,} {kind} pairs from {len(available):,}") + + rng.shuffle(drawn) + return drawn + + +def main(config_path: Path, output: Path | None = None) -> None: + """Partition the pairs and write the lists to the output path. + + Parameters + ---------- + config_path : Path + Path to the configuration file. + output : Path | None + Override the output path from the config. + """ + config = load_config(config_path) + logging.basicConfig( + level=getattr(logging, config["logging"]["level"]), + format=config["logging"]["format"], + ) + + print_header("Experiment Lists") + + list_config = config["lists"] + n_lists = list_config["n_lists"] + items_per_list = list_config["items_per_list"] + strategy = list_config["strategy"] + seed = list_config["random_seed"] + + list_constraints = build_list_constraints(list_config["constraints"]) + batch_constraints = build_batch_constraints(list_config["batch_constraints"]) + print_success( + f"Built {len(list_constraints)} list and " + f"{len(batch_constraints)} batch constraints" + ) + + target_counts = next( + spec["target_counts"] + for spec in list_config["constraints"] + if spec["type"] == "balance" + ) + if sum(target_counts.values()) != items_per_list: + raise ValueError( + f"Balance targets sum to {sum(target_counts.values())}, " + f"but items_per_list is {items_per_list}" + ) + + pairs = load_pairs(BASE_DIR / config["paths"]["2afc_pairs"]) + print_success(f"Loaded {len(pairs):,} pairs") + pool = draw_pool(pairs, target_counts, n_lists, seed) + + metadata: MetadataDict = { + pair.id: dict(pair.item_metadata) for pair in pool + } + partitioner = ListPartitioner(random_seed=seed) + lists = partitioner.partition_with_batch_constraints( + items=[pair.id for pair in pool], + n_lists=n_lists, + list_constraints=list_constraints, + batch_constraints=batch_constraints, + strategy=strategy, + metadata=metadata, + ) + sizes = [len(exp_list.item_refs) for exp_list in lists] + print_success(f"Built {len(lists)} lists of {min(sizes)} to {max(sizes)} pairs") + if min(sizes) != items_per_list: + print_warning(f"Lists are uneven: {sizes}") + + collection = ListCollection( + name="argument_structure_lists", + source_items_id=uuid4(), + lists=tuple(lists), + partitioning_strategy=strategy, + partitioning_config={ + "n_lists": n_lists, + "items_per_list": items_per_list, + "random_seed": seed, + }, + partitioning_stats={"total_items": sum(sizes)}, + ) + + output_path = output or (BASE_DIR / config["paths"]["experiment_lists"]) + collection.to_jsonl(output_path) + display_file_stats(output_path, len(lists), "lists") + + +if __name__ == "__main__": + parser = argparse.ArgumentParser( + description="Partition the 2AFC pairs into experiment lists." + ) + parser.add_argument( + "--config", + type=Path, + default=BASE_DIR / "config.yaml", + help="Path to the configuration file.", + ) + parser.add_argument( + "--output", + type=Path, + default=None, + help="Override the output path from the config.", + ) + args = parser.parse_args() + main(config_path=args.config, output=args.output) From ba45bcdd40ea9f5ca43669c0cd4e24ca4945ea64 Mon Sep 17 00:00:00 2001 From: Pavlo Kuchmiichuk Date: Wed, 22 Jul 2026 12:42:36 -0400 Subject: [PATCH 12/16] Scores Ukrainian pairs with the causal model on the local GPU. --- gallery/ukr/argument_structure/config.yaml | 12 +++++++----- gallery/ukr/argument_structure/create_2afc_pairs.py | 5 ++++- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/gallery/ukr/argument_structure/config.yaml b/gallery/ukr/argument_structure/config.yaml index a7ef9f2..d388fb6 100644 --- a/gallery/ukr/argument_structure/config.yaml +++ b/gallery/ukr/argument_structure/config.yaml @@ -88,15 +88,17 @@ items: # Sentence-acceptability scoring models. Exactly one entry sets # use_for_scoring: true (override with --model). Masked models are scored by # pseudo-log-likelihood, causal models by sentence log-probability. + # Accuracy at picking the correct case for the anchor verbs: lapa 92%, roberta 62%. models: + - name: "lapa-llm/lapa-12b-pt" + type: "causal" + device: "mps" + dtype: "bfloat16" + use_for_scoring: true + - name: "benjamin/roberta-large-wechsel-ukrainian" type: "masked" device: "cpu" - use_for_scoring: true - - - name: "lapa-llm/lapa-12b-pt" - type: "causal" - device: "cuda" use_for_scoring: false # Minimal pairs contrast the case of a verb's object while holding the verb diff --git a/gallery/ukr/argument_structure/create_2afc_pairs.py b/gallery/ukr/argument_structure/create_2afc_pairs.py index 3fd9a4f..fb79cf0 100644 --- a/gallery/ukr/argument_structure/create_2afc_pairs.py +++ b/gallery/ukr/argument_structure/create_2afc_pairs.py @@ -175,7 +175,10 @@ def build_scorer(model: dict, cache_dir: Path) -> ItemScorer: model_name=model["name"], cache_dir=cache_dir, device=device ) return LanguageModelScorer( - model_name=model["name"], cache_dir=cache_dir, device=device + model_name=model["name"], + cache_dir=cache_dir, + device=device, + dtype=model.get("dtype", "auto"), ) From 2609e2bbee6d34213612c8edbe981a7e4b9b0b6d Mon Sep 17 00:00:00 2001 From: Pavlo Kuchmiichuk Date: Wed, 22 Jul 2026 13:33:45 -0400 Subject: [PATCH 13/16] Adds the jsPsych and JATOS deployment stage for the Ukrainian pipeline. --- gallery/ukr/argument_structure/config.yaml | 30 ++ .../argument_structure/generate_deployment.py | 257 ++++++++++++++++++ 2 files changed, 287 insertions(+) create mode 100644 gallery/ukr/argument_structure/generate_deployment.py diff --git a/gallery/ukr/argument_structure/config.yaml b/gallery/ukr/argument_structure/config.yaml index d388fb6..8ca9259 100644 --- a/gallery/ukr/argument_structure/config.yaml +++ b/gallery/ukr/argument_structure/config.yaml @@ -159,6 +159,36 @@ lists: property_expression: "item['verb']" max_lists_per_value: 6 +deployment: + platform: "jatos" + n_lists_to_deploy: 16 + random_seed: 42 + output_dir: "deployment" + + experiment: + title: "Оцінювання прийнятності речень" + description: "Порівняйте два речення й оберіть те, яке звучить природніше" + instructions: >- + У кожному завданні ви побачите два речення. + Оберіть те, яке звучить для вас більш природно. + prompt: "Яке речення звучить природніше?" + estimated_duration_minutes: 15 + + jspsych: + version: "8.0.0" + use_jatos: true + prolific_completion_code: null + randomize_order: true + randomize_choices: true + + participants: + n_per_list: 30 + qualifications: + - "Native Ukrainian speaker" + - "Age 18+" + - "Approval rate > 95%" + payment_usd: 2.50 + logging: level: "WARNING" format: "%(asctime)s - %(name)s - %(levelname)s - %(message)s" diff --git a/gallery/ukr/argument_structure/generate_deployment.py b/gallery/ukr/argument_structure/generate_deployment.py new file mode 100644 index 0000000..2f9fd1b --- /dev/null +++ b/gallery/ukr/argument_structure/generate_deployment.py @@ -0,0 +1,257 @@ +#!/usr/bin/env python3 +"""Generate the jsPsych/JATOS deployment from the experiment lists. + +Builds one jsPsych experiment per selected list in two versions: a standalone +local build for testing in a browser, and a JATOS build packaged as a ``.jzip`` +archive for upload. Text shown to participants comes from the config. +""" + +from __future__ import annotations + +import argparse +from pathlib import Path +from random import Random +from uuid import UUID + +import yaml + +from bead.cli.display import ( + create_progress, + print_header, + print_info, + print_success, +) +from bead.deployment.distribution import ( + DistributionStrategyType, + ListDistributionStrategy, +) +from bead.deployment.jatos.exporter import JATOSExporter +from bead.deployment.jspsych.config import ( + ChoiceConfig, + ExperimentConfig, + InstructionsConfig, +) +from bead.deployment.jspsych.generator import JsPsychExperimentGenerator +from bead.items.item import Item +from bead.items.item_template import ItemTemplate, PresentationSpec, TaskSpec +from bead.lists.experiment_list import ExperimentList + +BASE_DIR = Path(__file__).parent +INSTRUCTIONS_PLUGIN = ( + '' +) + + +def load_config(path: Path) -> dict: + """Load the YAML configuration file. + + Parameters + ---------- + path : Path + Path to the configuration file. + + Returns + ------- + dict + Parsed configuration. + """ + with path.open(encoding="utf-8") as f: + return yaml.safe_load(f) + + +def load_lists(path: Path) -> list[ExperimentList]: + """Load the experiment lists from JSONL. + + Parameters + ---------- + path : Path + Path to the lists file. + + Returns + ------- + list[ExperimentList] + The loaded lists. + """ + with path.open(encoding="utf-8") as f: + return [ExperimentList.model_validate_json(line) for line in f if line.strip()] + + +def load_pairs(path: Path) -> dict[UUID, Item]: + """Load the 2AFC pairs, keyed by the id the lists reference. + + Parameters + ---------- + path : Path + Path to the pairs file. + + Returns + ------- + dict[UUID, Item] + Pairs indexed by their id. + """ + pairs: dict[UUID, Item] = {} + with path.open(encoding="utf-8") as f: + for line in f: + if line.strip(): + item = Item.model_validate_json(line) + pairs[item.id] = item + return pairs + + +def build_item_template(prompt: str) -> ItemTemplate: + """Build the template the generator renders each pair through. + + Parameters + ---------- + prompt : str + Question shown above the two options. + + Returns + ------- + ItemTemplate + Template for the forced-choice items. + """ + return ItemTemplate( + name="2afc_case_contrast", + description="Two-alternative forced choice over object case", + judgment_type="acceptability", + task_type="forced_choice", + task_spec=TaskSpec(prompt=prompt, options=["Речення 1", "Речення 2"]), + presentation_spec=PresentationSpec(mode="static"), + ) + + +def _inject_instructions_plugin(html_path: Path) -> None: + """Add the jsPsych instructions plugin tag when the generator omits it. + + Parameters + ---------- + html_path : Path + Generated ``index.html`` to patch. + """ + html = html_path.read_text(encoding="utf-8") + if "plugin-instructions" in html: + return + preload = '