sakhaspell — спелчекер якутского языка
sakhaspell.michill.ru — попробовать в браузере
Проверка орфографии для якутского (саха тыла). Два слоя: словарный работает офлайн на CPU, нейросетевой добавляет контекст.
Спелчекера для якутского до этого не было. Есть морфология —
apertium-sah и
yakutmorph, обе GPL-3 —
но hunspell-словаря, расширения для редакторов и нейрокорректора нет; ни
Яндекс.Спеллер, ни LanguageTool якутский не поддерживают. Разбор поля —
docs/research.md.
Что умеет
$ python -m sakhaspell check "Ого уорэгэ кисиэхэ сана кыагы биэрэр"
1:0 Ого → Оҕо [lexicon] ещё: Ооо, Оо, Ошо
1:4 уорэгэ → үөрэҕэ [lexicon] ещё: үөрэнэ, үөрэрэ, үрэҕэ
1:11 кисиэхэ → киһиэхэ [lexicon] ещё: кимиэхэ, киниэхэ, биһиэхэ
1:19 сана → саҥа [lexicon] ещё: аана, саха, сара
1:24 кыагы → кыаҕы [lexicon] ещё: кыаны, кыргы, кыаһы
Три сценария:
- восстановление ҕ ҥ ө һ ү — текст набран с русской раскладки;
- постобработка OCR и ASR — машинные искажения (см. оговорку ниже);
- проверка при наборе — подчёркивание с подсказками.
Результаты
SakhaSpellBench, отложенный test, 800 предложений на задачу. Метрики пословные, как в SAGE.
| задача | F1 (словарь) | F1 (+ тэггер) | детекция | ложные |
|---|---|---|---|---|
clean — чистый текст |
— | — | — | 1.33% |
denorm_full — без раскладки |
93.05% | 96.51% | 99.44% | 1.51% |
denorm_mixed — как пишут |
92.78% | 95.87% | 99.67% | 1.68% |
mixed — опечатки поверх |
87.68% | 90.23% | 96.13% | 1.46% |
typo — опечатки |
76.30% | 76.00% | 86.39% | 1.35% |
real — ошибки из корпуса |
67.15% | 66.94% | 65.35% | 1.78% |
Тэггер добавляет 3.5 пункта на деноминализации и не стоит ничего в ложных срабатываниях: 1.33% с ним и без него. На задачах опечаток он корректно бездействует — там ошибка не в спецбуквах.
Главная колонка — последняя. Ложные срабатывания на заведомо правильном тексте: 1.33% против 8.96% у apertium-sah (LREC 2022) и 4.89% помеченных токенов у bashspell, где ~46% помеченного ложно. Именно эта метрика решает, оставит пользователь спелчекер включённым или нет.
Скорость на CPU: проверка слова 0.0016 мс (у bashspell 0.17–0.21 мс), подсказки 51 мс медиана (у bashspell 115–132 мс), 392 слова/с на сплошном тексте. Память 470 МБ. Тэггер — 10.8M параметров, посимвольный F1 0.9919.
Оговорка про ASR
Задача постобработки ASR собрана из настоящих выходов распознавателя проекта STT, и чекер на ней проваливается: F1 1.9%. Причина не в чекере — эталонные расшифровки грязнее, чем выход модели. В 50.9% расхождений не является словом именно эталон (против 13.6% у гипотезы), и 19.1% эталонов — деноминализованные написания (против 0.7%).
Полный аудит 8 417 расшифровок: 46.0% содержат орфографическую ошибку,
6.74% всех слов. Артефакты для починки — data/asr_audit/ref_issues.jsonl и
top_bad_words.tsv. Для проекта ASR это значит, что WER завышен: модель
штрафуется за слова, которые написала правильно.
Устройство
L0 нормализация NFC, невидимые символы, чужие кириллические буквы
(ѳ→ө, ң→ҥ, ғ→ҕ), латинские гомоглифы внутри слова
L1 словарь 301k словоформ из корпуса + фильтр теней искажения,
кандидаты через префиксное дерево со взвешенным
расстоянием, CPU, миллисекунды
L2 тэггер посимвольная разметка: для каждой буквы решает, это «г»
или «ҕ». Неавторегрессивный, 10.8M параметров
Три решения, которые определили результат:
Первичный акцептор — словоформы, а не FST. У apertium-sah точность 98.52%, но наивное покрытие на газетах 91.04%: FST-акцептор подчёркивает 9% правильного текста. Корпусный лексикон даёт 1.33%.
Фильтр теней искажения. Систематическая ошибка письма без раскладки
проникает в лексикон: сана (378 вхождений), киси (21), ого (17) попали в
словарь, потому что так пишут во всех источниках сразу, и правило «подтверждено
двумя источниками» против этого бессильно. Отдельная проверка находит формы,
которые являются искажением гораздо более частой формы, и убирает их. Это дало
+27.7 пункта F1 и обрушило долю «невидимых без контекста» ошибок
деноминализации с 41.2% до 1.5%.
L2 — разметка, а не переписывание. Задача восстановления ҕҥөһү посимвольная по своей природе, и разметка ей точно соответствует: модель физически не может изменить символ, которому предсказала «оставить». Seq2seq переписывает текст целиком и правит то, о чём не просили.
Подробности с цифрами — docs/experiments.md.
Установка
pip install sakhaspell
Словарь на 300 304 словоформы встроен в пакет — скачивать и настраивать нечего. У словарного слоя нет зависимостей вообще, только стандартная библиотека.
sakhaspell check "Ого уорэгэ кисиэхэ" # показать ошибки
sakhaspell fix --file статья.txt --in-place # исправить на месте
sakhaspell repl # интерактивно
cat текст.txt | sakhaspell fix > исправлено.txt
Из Python:
from sakhaspell import SpellChecker, Lexicon
checker = SpellChecker(Lexicon.load())
checker.correct("Ого уорэгэ кисиэхэ") # 'Оҕо үөрэҕэ киһиэхэ'
for issue in checker.check("Мин огом онгор"):
print(issue.token.start, issue.token.text, issue.best)
Позиции правок в исходном тексте — для подсветки в редакторе:
from sakhaspell import Pipeline
p = Pipeline()
for c in p.corrections("Ого уорэгэ кисиэхэ"):
print(c.start, c.end, c.before, "→", c.after, c.alternatives)
Контекстная модель
Даёт +3.5 пункта F1 на восстановлении ҕҥөһү. Чекпоинт в пакет не входит:
обучается за 33 минуты на одной H200 через scripts/train_tagger.py.
pip install "sakhaspell[tagger]"
sakhaspell --tagger runs/tagger_v2 check "текст"
HTTP-сервис
pip install "sakhaspell[server]"
uvicorn sakhaspell.server:app --port 8080
POST /check — проверка текста с позициями, POST /spell — быстрый вердикт по
словам для подсветки, POST /suggest — подсказки по требованию. Разнесено
намеренно: проверка стоит 0.0016 мс на слово, подсказки — 51 мс.
Сборка с нуля
Данные и обучение — на поде H200 (docs/experiments.md). Корпус — 8.2 млн
предложений из 18 источников.
python scripts/prep_sentences.py --corpus corpus_sah.jsonl --out data/sent --procs 90
python scripts/build_lexicon.py --sent data/sent --out data/lexicon
python scripts/mine_errors.py --sent data/sent --lexicon data/lexicon --out data/errors
python scripts/find_shadows.py --lexicon data/lexicon --out data/lexicon
python scripts/build_bench.py --lexicon data/lexicon --errors data/errors --out data/bench
python scripts/eval_bench.py --lexicon data/lexicon --bench data/bench --split dev
python scripts/train_tagger.py --sent data/sent --out runs/tagger_v2 --steps 30000
Готовый словарь лежит в sakhaspell/data/ сжатым и подхватывается сам;
Lexicon.load("свой/каталог") берёт другой.
Сборка и выкладка
| Что | Когда | Куда |
|---|---|---|
test.yml |
push и PR | pytest на 3.10–3.13, плюс macOS и Windows |
publish.yml |
релиз с тегом | PyPI через доверенную публикацию |
deploy.yml |
изменения в docs/ |
GitHub Pages и sakhaspell.michill.ru |
Тесты проверяют не только код: отдельный шаг грузит встроенный словарь и прогоняет через него фиксированные пары, чтобы порча данных не проехала молча.
Страница
docs/ — статическая страница: sakhaspell.michill.ru,
зеркало на GitHub Pages. Словарь пережат во
фронт-кодированный формат и весит 864 КБ на все 300 тысяч форм, поэтому
проверка идёт целиком в браузере и текст никуда не отправляется. Пересборка:
python scripts/build_web_assets.py --lexicon data/lexicon --out docs/data
Что не работает
- Постобработка ASR. Не потому, что чекер плох: эталонные расшифровки оказались грязнее выхода модели (см. оговорку выше).
- Постобработка OCR не проверена — нет пар «выход против эталона». Модель ошибок OCR добыта (52 015 пар), но проверить не на чем.
- Опечатки — 76% F1. Ошибку видим в 86% случаев, но верный вариант ставим первым не всегда: ранжирование учитывает цену правки и частоту, но не контекст.
- Пунктуация и регистр не восстанавливаются.
Лицензии
Код проекта свой. apertium-sah и yakutmorph — GPL-3, в рантайме не
используются: лексикон построен из корпуса, поэтому результат не заражается.
Release files for sakhaspell 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| sakhaspell-0.1.0.tar.gz | 7.7 MB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| sakhaspell-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 15.3 MB
Release files / sakhaspell-0.1.0.tar.gz
| Download URL | sakhaspell-0.1.0.tar.gz |
|---|---|
| Size | 7.7 MB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
d3d36db793d391a2c30e71a550425c1a540ae3e02a02a8e04eaadb39394bc007
|
|
BLAKE2b-256 checksum How to use checksums |
b50bfaf34bd25aeb4a54278c9af6f03e1402bf6b89d2379b1aa3cd5bab129df3
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 8, 2026.
Transparency logRelease files / sakhaspell-0.1.0-py3-none-any.whl
| Download URL | sakhaspell-0.1.0-py3-none-any.whl |
|---|---|
| Size | 7.6 MB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
18778c63ab57de1f2ed1c0776c399d0f0348467b002bafbcfef577a6c48a8c90
|
|
BLAKE2b-256 checksum How to use checksums |
00b1026e2bd9ada7a84011e1d28f53712520d453eab7599c969da5624258305c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 8, 2026.
Transparency log