windowcapture
исходный код / AUTOCORRECT_REVIEW.md

AUTOCORRECT_REVIEW.md

95 строк · 17,360 байт · модуль Docs
 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.