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.
| задача | словарь | + контекст | + тэггер | детекция | ложные |
|---|---|---|---|---|---|
clean — чистый текст |
— | — | — | — | 1.75% |
denorm_full — без раскладки |
93.16% | 94.40% | 96.51% | 98.88% | 1.92% |
denorm_mixed — как пишут |
92.59% | 93.65% | 95.87% | 98.97% | 2.08% |
mixed — опечатки поверх |
87.83% | 90.45% | — | 95.76% | 1.81% |
typo — опечатки |
76.26% | 81.32% | — | 87.43% | 1.78% |
real — ошибки из корпуса |
67.36% | 70.46% | — | 65.35% | 2.35% |
Контекстная модель добавляет 5 пунктов на опечатках и 3 на ошибках из корпуса, тэггер — ещё 2 на деноминализации. Обе не стоят ничего в ложных срабатываниях: они переставляют кандидатов и возвращают буквы, а решение «это ошибка» принимает словарь.
Главная колонка — последняя. Ложные срабатывания на заведомо правильном тексте: 1.75% против 8.96% у apertium-sah (LREC 2022) и 4.89% помеченных токенов у bashspell, где ~46% помеченного ложно. Именно эта метрика решает, оставит пользователь спелчекер включённым или нет.
Считается доля правильных слов, которые система подчеркнула зря, — из них подсказку она предлагает для 1.33%, а остальные помечает без вариантов замены. Подчёркивание пользователь видит в любом случае, поэтому метрика по нему; так же меряют и apertium-sah с bashspell.
Скорость на 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 параметров
LM контекст биграммы с откатом: выбирают между кандидатами равной
цены. 963k биграмм, 6.9 МБ, работают на CPU
R правила восемь правил из грамматики: гармония гласных, сочетания
гласных, позиционные ограничения, стечения согласных,
мягкий знак. Помогают ранжированию, доступны отдельно
from sakhaspell import violations, is_wellformed, RULES
is_wellformed("оҕо") # True
is_wellformed("сурэ") # False — после «у» не бывает «э»
violations("уьу") # [soft_sign@2:ь не после д/н]
list(RULES) # все восемь правил по именам
Три решения, которые определили результат:
Первичный акцептор — словоформы, а не FST. У apertium-sah точность 98.52%, но наивное покрытие на газетах 91.04%: FST-акцептор подчёркивает 9% правильного текста. Корпусный лексикон даёт 1.75%.
Фильтр теней искажения. Систематическая ошибка письма без раскладки
проникает в лексикон: сана (378 вхождений), киси (21), ого (17) попали в
словарь, потому что так пишут во всех источниках сразу, и правило «подтверждено
двумя источниками» против этого бессильно. Отдельная проверка находит формы,
которые являются искажением гораздо более частой формы, и убирает их. Это дало
+27.7 пункта F1 и обрушило долю «невидимых без контекста» ошибок
деноминализации с 41.2% до 1.5%.
Контекст решает там, где частота ошибается. Верный вариант исправления уже
находился среди кандидатов в 93.8% случаев на опечатках, но первым мы его
ставили только в 80.5% — тринадцать пунктов терялись в ранжировании. Классика:
сйын → кандидаты ыйын и сайын, побеждает более частотный, хотя бу сайын
встречается 951 раз, а бу ыйын — ни разу. Биграммная модель это чинит.
Книжная грамматика добавляет десятые доли. Восемь правил из грамматики — гармония гласных, сочетания гласных, позиционные ограничения, стечения согласных, мягкий знак — проверены на 300 тысячах словоформ и измерены порознь. Вместе они ловят 14.6% ошибок, невидимых для словаря, но помечать по ним слова не окупается: ложные срабатывания растут с 1.79% до 3.53%. Причина — заимствования: правила исконной фонетики они нарушают законно, а отличить их без словаря нельзя. В умолчаниях остались только бесплатные применения — подсказка ранжированию и восстановление вне словаря. Замер целиком — E15 и E17.
Заодно корпус опроверг часть формулировок. Правило «слово не оканчивается на
б, г, ҕ, д, һ, ч» верно только для ҕ: на «һ» оканчивается тыһ с 14 753
вхождениями, а на б/г/д/ч — заимствования вроде психолог и куб.
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
Свои имена и термины, чтобы их больше не подчёркивало:
sakhaspell dict add Ньургуйаана Хаҥалас
sakhaspell dict add "сахалыы -> саха тылынан" # всегда исправлять
sakhaspell dict list
Из 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.4.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.4.0.tar.gz | 14.6 MB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| sakhaspell-0.4.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 29.2 MB
Release files / sakhaspell-0.4.0.tar.gz
| Download URL | sakhaspell-0.4.0.tar.gz |
|---|---|
| Size | 14.6 MB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
216c1baa192cf0e2385b327ccc747ed274a46a493eea9f6cf27a5417ffff505f
|
|
BLAKE2b-256 checksum How to use checksums |
ecd2d30937788489b6b7a51cff565418c32bc8f5eeaf55f6189feb495c3eeed9
|
| 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.4.0-py3-none-any.whl
| Download URL | sakhaspell-0.4.0-py3-none-any.whl |
|---|---|
| Size | 14.6 MB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
6a282a5a9c3e95d9e7606f41206035de62758e6f1e3d0f0faa27233e592eeb39
|
|
BLAKE2b-256 checksum How to use checksums |
0ae8e02f0613659e1d586147411d49a0f147d314b8dea31f00d61e0dd6784315
|
| 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