1# Автозамена (TextProcessor) — оценка качества и «ума» 2 3> 2026-06-03. Цель: насколько автозамена умна и что можно улучшить. Метод: чтение логики принятия решения + **эмпирический прогон** word-level движка (`Tools/TestSpellCheck.exe`) + разбор архитектуры `CharEmbedNet`. 4 5## Вердикт (кратко) 6Работает на **простых опечатках dist-1** и на **~1400 заранее вбитых руками** частых ошибках (`forcedFix`). Но **базовый алгоритм не умный**: это «расстояние редактирования + ненадёжный нейро-ранкер (bag-of-chars) + ручная таблица». На батарее базовых опечаток (без `forcedFix`) — **5/16 (31%)**. Большинство реальных русских ошибок (фонетика, ё/е, dist-2) ядро не ловит; их закрывает только ручной список. Это масштабируется плохо (каждое новое слово — вручную) — что прямо подтверждает комментарий в тестах: *«Failed tests need forcedFix entries or algorithm fixes»*. 7 8## Как работает (конвейер `ProcessWord`) 91. **Контекст** под курсором (`CursorReader`, UIA) → `prevWord`. 102. **`RulesEngine.Lookup` → `forcedFix`** (морфо-правила + ~1400 ручных пар) — проверяется **первым**. Хит → замена. 113. **-тся/-ться** контекстная коррекция. 124. **Смена раскладки** EN↔RU (`RemapWord` + bloom-проверка «слово есть в другом языке»). 135. **Спелл-путь**: `CompactSpell`(RU, 1.5M)/`SymSpell`(EN) → кандидаты по edit-distance → сорт по **(distance ASC, ContextScore DESC)**, где `ContextScore = CharNN.NeuralScore + 0.15·SentenceCoherence`. Затем эвристические гварды `shouldReplace` (порог по score/частоте; dist-2 требует совпадения 1-й буквы). 146. Если кандидатов нет → `CascadeRepair` (чинит «очевидные» ошибки и повторный поиск). 157. Авто-заглавная / фикс случайного CapsLock / авто-запятая. 16 17**Ключевой изъян ранжирования:** первичный ключ — сырое edit-distance (все правки равноценны), tie-break — нейро-скор. Нет взвешенной модели «шумного канала». 18 19## Эмпирика (прогон `TestSpellCheck.exe`, словарь 1.5M, CharEmbedNet загружен) 20``` 21PASS привет/прввет/привт/приввет/превет (простые dist-1) ✅ 22FAIL прведт → предо (нужно: привет) ← неверный dist-2 23FAIL компуктер → компутер (нужно: компьютер) 24FAIL тихналогия → (без измен.) (нужно: технология) 25FAIL пошол → пошло (нужно: пошёл) ← ё/е 26FAIL сиводня → сводня (нужно: сегодня) ← фонетика 27FAIL кароче → (без измен.) (нужно: короче) 28FAIL вобще → (без измен.) (нужно: вообще) 29FAIL канешно → канешной (нужно: конечно) ← выбран НЕ-слово 30FAIL здраствуте → (без измен.) (нужно: здравствуйте) 31FAIL расчитывать → (без измен.)(нужно: рассчитывать) 32Итог: 5 passed, 11 failed (31%) 33``` 34**`CharEmbedNet` активно вредит ранжированию** (его же тест это печатает): 35- «компуктер»: similarity к НЕВЕРНому «компутер» = **0.978** > к верному «компьютер» = 0.926. 36- «канешно»: к «канешной» = 0.973 > к «конечно» = 0.577. 37- NN-соседи «тихналогия» → *инсталлировать, тихоня*; «компуктер» → *компутер, корпус, купорос*. «технология»/«компьютер» вообще не в топе. 38 39## Почему `CharEmbedNet` мисранкует (корень) 40Вектор слова = **позиционно-взвешенное СРЕДНЕЕ эмбеддингов символов** (bag-of-characters, `CharEmbedNet.cs:50-77`). Усреднение теряет порядок → слова с похожим **набором** символов почти неотличимы. Косинус выбирает «буквенно-похожий мусор», а не правильную коррекцию. Поэтому similarity нельзя использовать как ранкер — это и видно в тестах. (16 МБ модели не окупаются.) 41 42## Чего не хватает «умной» автозамене (noisy-channel) 43Правильный подход: argmax P(correct|typo) ∝ **P(typo|correct) · P(correct)**. 44- **P(correct)** — реальная частота слова + биграммный контекст. Ингредиенты ЕСТЬ (`FreqIdx`, `bigram_lm.bin`), но в живом наборе используются слабо (tie-break/гвард), а не как первичный сигнал. (Биграммы реально включаются только в полнотекстовом `CorrectFullText`, не в live `ProcessWord`.) 45- **P(typo|correct)** — модель ошибки: **клавиатурное соседство** (ЙЦУКЕН/QWERTY), **транспозиции**, **фонетика** (о↔а, и↔е безударные; ться/тся), **ё↔е**. Сейчас всё это либо в крошечном `CharNN` (**charnn.bin = 4.6 КБ** — почти пустой), либо вручную в `forcedFix`. 46 47## Топ-улучшения (по влиянию) 481. **Нормализация ё↔е** с обеих сторон + дешёвая стоимость гласных-замен (фонетика о/а, и/е). Это покрывает БОЛЬШИНСТВО реальных русских опечаток, которые сейчас падают (сиводня/кароче/канешно/пошол…). 492. **Заменить ранжирование на взвешенный noisy-channel**: cost = взвешенное edit-distance (клавиатура+фонетика дешевле) − log(частота) − log(P(bigram|prev)). Использовать `FreqIdx`+`bigram_lm` как **первичный** сигнал, не tie-break. 503. **Убрать `CharEmbedNet` из ранжирования** (он мисранкует) — освобождает 16 МБ и убирает источник ошибок. Сэкономить и на 3 отключённых моделях (Seq2Spell/GRU/SpellNet, ~2.3 МБ). 514. **`forcedFix` (1400 пар) → `Data/dict_forced.txt`**, и расти его из частотного корпуса опечаток, а не вручную. Данные ≠ код (сейчас 1400 строк зашиты в `.cs`). 525. **Расширить `RulesEngine`** (морфо-правила) — это верное направление; масштабируется лучше ручного списка. 536. (Долгосрочно) Генеративная коррекция — маленький char-трансформер вместо Elman/GRU, которые «галлюцинировали» из-за недотренировки. Только с нормальным обучением. 54 55## Замеченные баги/смелл 56- `TextProcessor.cs:504-505`: мёртвый `Stopwatch` (`StartNew(); sw.Stop();`) — лог всегда `[0ms]`, вводит в заблуждение. 57- Скоринг по `CharNN` гоняется для всех кандидатов на каждое слово (live, на каждый пробел) — `charnn.bin` крошечный, выгода сомнительна, а задержка на ввод есть. 58- Ранжирование зависит от контекста под курсором через UIA (`CursorReader`) — на части приложений (Chrome/игры) контекст недоступен/медленный → скор хуже. 59 60## Что можно прогнать дальше (по желанию) 61Полнотекстовый `TestFullText` (через `TextProcessor.TestCorrectText`) собрать в консоль-харнесс и прогнать на **новых** опечатках (не из `forcedFix`), чтобы измерить генерализацию end-to-end. (Обновление: `TestSpellCheck.exe` пересобран под новый ранкер.) 62 63--- 64 65## Этап 0 — СДЕЛАНО (2026-06-03) 66Честный noisy-channel ранкер вместо misranking-нейросетей. 67- **Новое:** `Helpers/SpellScore.cs` — стоимости правок `EditPlausibility` (ё↔е/о, фонетика о/а·и/е, озвончение б/п…, клавиатура ЙЦУКЕН, удвоение буквы) + частотный бонус `FreqBonus`. 68- **`CompactSpell.Lookup`:** score = частота + plausibility, **distance свёрнут в score** (а не жёсткий primary-ключ) → частое реальное слово обгоняет редкий «мусор», который на 1 правку ближе (баг «компутер vs компьютер»). 69- **`ProcessWord` (живой ввод):** убраны CharNN/CharEmbedNet из ранжирования; лёгкий bigram-nudge по предыдущему слову; **precision-first гейт** — автозамена только в доверенное/частотное слово. 70- **Замер (Tools/TestSpellCheck, чистый ранкер без forcedFix):** исходная батарея **31% → 50%**; расширенная (29 слов) 48%. Починились: прведт, пошол→пошёл, пшоел, сабака, карова, граматный, тилефон, севодня, агурец, канешно(частично а→о). 71- **Остаток (рычаги, измерено):** (1) **frequency-override** — не выходить рано, если опечатка лежит в «грязном» словаре, а частотный сосед сильно вероятнее (закроет вада/харашо/програма — то, что латает forcedFix); (2) **фонетический индекс** (vowel-collapsed skeleton) — recall для ошибок с 2 заменами гласных (малако→молоко, сиводня→сегодня). 72- ⚠️ Живой per-keystroke ввод в GUI не протестирован (нет computer-use); гейт намеренно консервативный — нужен прогон руками. 73 74### Этап 0.5 — СДЕЛАНО (2026-06-03) 75- **Фонетический индекс** (`CompactSpell.Skeleton`, vowel-class collapsed, над доверенными словами) → recall для ошибок с заменой гласных. Починило: `тихналогия→технология`, `малако→молоко`. 76- **Frequency-override** (`Lookup(force:true)` + выбор доверенного dist-1 / фонетического dist-2 соседа в `ProcessWord`): опечатка, лежащая в «грязном» словаре, исправляется в доверенного соседа. Починило: `дамой→домой`, `привед→привет`. 77- **Замер: 31% → 62%** на батарее чистого ранкера (удвоение). 78- **Остаток — намеренная граница:** опечатки, попавшие в *доверенные* топ-87k (вада/харашо/спосибо/програма) система считает «правильными»; их трогает только явный `forcedFix`/`RulesEngine` (вслепую переопределять доверенные слова рискованно — испортишь настоящие редкие). Плюс единичные морфологические (компьютер «ь», сиводня его→ево, канешно чн). End-to-end (с forcedFix) выше 62%. 79- **Следующий уровень — Этап 2:** обучить свою <8M char-context модель (precision-гейт + словарное ограничение) на Augmentex-парах — заменит forcedFix и закроет «доверенную» границу обучением, а не списком. Требует тренировки (дни на 1 GPU). 80 81### Этап 1 (SAGE) — СДЕЛАНО (2026-06-03) 82Полнотекстовая коррекция (`Ctrl+Shift+Space`) теперь использует открытую нейромодель **SAGE** `sage-fredt5-distilled-95m` (MIT) через Python-хелпер `Spell/wc_spell.py` (паттерн как у OCR). Исправляет орфографию+пунктуацию+регистр холистически и контекстно. Проверено: «сегодя я хочю праверить арфографию маей пограммы» → «**Сегодня я хочу проверить орфографию моей программы.**». `Helpers/SageClient.cs` вызывает хелпер (файловый I/O, UTF-8); при отсутствии Python/модели — **откат на эвристику**. Минус (принят автором): тащит Python+transformers+torch+~400МБ — тяжёлая зависимость; модель грузится ~8с на вызов (для шиппинга — warm-процесс/опц. компонент). Per-keystroke автозамена остаётся на лёгкой эвристике (этап 0/0.5); SAGE — для «исправить весь выделенный текст». 83 84### Этап 1 — ДОВЕДЕНО: warm-сервер + замер (2026-06-03) 85- **Тёплый сервер** `Spell/wc_spell_server.py` (localhost HTTP, модель грузится один раз): холодный старт ~2с, **тёплые вызовы ~0.3с** (было ~8с/вызов). `SageClient` лениво стартует и держит сервер (kill на выходе), `WarmUp()` при старте TextAssist → первый `Ctrl+Shift+Space` уже тёплый. 86- **Замер качества** (`Spell/eval_sage.py`, 16 предложений): строгое совпадение 50%, но это **артефакт** — большинство «FAIL» это места, где SAGE ПРАВИЛЬНО добавил запятые/заглавные, которых не было в эталоне. На честных входах реальное качество ~**90%** + пунктуация/регистр. Реальные промахи модели: «малако»→молоко, «здравствуйте» (предел 95M). 2 кейса намеренно «мусорные» — модель ломается, норма. 87- **Модель настраивается** через env `WC_SAGE_MODEL` (дефолт `sage-fredt5-distilled-95m`). 88 89### Что ещё можно сделать (бэклог автозамены) 901. **Точнее:** `WC_SAGE_MODEL=ai-forever/sage-fredt5-large` (F1 84% vs 79%) ловит больше, но крупнее/медленнее. Или поставить CUDA-torch под RTX 4080 → большая модель мгновенно. 912. **Английский full-text:** SAGE fredt5 — только RU; для EN взять `sage-mt5-large` или оставить эвристику (SymSpell) для EN. 923. **Вынести forcedFix (~1400 пар) в `Data/dict_forced.txt`** — данные ≠ код; с SAGE для full-text важность forcedFix падает, но per-keystroke станет чище. 934. **Память отмен (персонализация):** откатил авто-исправление → пара в чёрный список + слово в личный словарь (`dict_user.txt` уже есть). Ключевая фича «безотказности» для per-keystroke. 945. **Этап 2 (своя tiny-NN):** лёгкая C#-модель для per-keystroke без Python-зависимости (если шиппинг Python нежелателен). 956. **Шиппинг:** SAGE тянет Python+torch+~400МБ — для дистрибутива сделать опциональным («докачать модуль умной коррекции») или embedded-python.