textmachine/docs/experiments/07-coverage-precision.md

146 lines
25 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Эксперимент 07. Precision (доля ложных срабатываний) excision coverage-гейта
**Дата:** 2026-07-05 · **Зона:** полигон · **Гейтит:** боевой флип `gates.coverage.enabled:true` (D12/Q4).
## TL;DR
- **0 ложных срабатываний excision_suspect на 57 хороших переводах** (реальные zh/ja→ru чанки), в обоих слоях: **26 нарративных + 31 «диалоговый»** (по маркер-прокси), три источника «хорошего перевода» (LLM × 2 модели + канон Рогова/Фельдмана). **Оговорка широты:** источники пересекаются — zh-диалог = один автор (Лу Синь), переведённый 23 стеками на пересекающихся главах; ja — один draft-модель + одна человеческая пара. Distinct-источников мало (см. §методика). 0 флагов — надёжный факт; **широта — узкая**.
- **Диалого-плотный страх (§3.7) НЕ воспроизведён на классической прозе** — но и **не «опровергнут»**: русский **дробит** длинные CJK-предложения → `sent_cov ≥ 1.0` на «диалоговых» чанках (пул-медиана 1.375; разброс 1.051.66). НО «диалоговый» страт здесь — в основном **классическая проза Лу Синя с цитируемыми ТЕРМИНАМИ** (напр. 序-предисловие с 『』-цитатами литературоведческих терминов), НЕ вебновелл-диалог из терсных реплик/звукоподражаний. **Вебновелл-режим остаётся непроверенным** (см. просьбу владельцу). Ближайший к порогу — `sent_cov=0.886` (ja-нарратив), запас 18%.
- **Коридоры len_ratio не слишком узкие:** zh хорошие 2.643.74 (пол 2.2, запас ≥20%), ja хорошие 1.592.35 (пол 1.4, запас ≥14%). Ложных срабатываний по нижней границе len_ratio — ноль.
- **Рекомендация: флип МОЖНО (по precision).** Порог главы: **≥2 флага/главу = «fail», 1 флаг = «attention»** — консервативно к невиданному диалого-плотному вебновелл-корпусу. Коридоры/пороги менять не нужно.
- **Побочно (важно бэкенду): draft-эхо на классическом zh — не единичный баг, а системный.** deepseek-v4-flash эхнул **13/24** zh-чанков Лу Синя, grok-4.20-non-reasoning — **10/24** (лёгкая правка исходника вместо перевода, cjk_share 7885%). Это ловит **интринсик echo-гейт** (не coverage) — но означает, что классическая/литературная zh требует эскалации черновика.
- **Побочно (D§85): `finish_reason=content_filter` не эмитит НИ ОДИН** из 5 провайдеров (deepseek/glm/kimi/gemini/grok) на SFW-violence — все `finish=stop`, 200, перевели. Ветка диспозиции по content_filter практически мертва на переводимом контенте → реальная страховка — refusal-blacklist (подтверждает D2.4).
- **Оракул↔Go:** Go-тесты паритета (`Oracle|Coverage|Split`) зелёные на HEAD → замер Python-оракулом Go-верен.
**Ограничение:** корпус — PD-классика (нарратив-смещённая проза), не современные вебновеллы/ранобэ (плотнее диалогом, звукоподражания, терсные реплики). Механизм («ru дробит → sent_cov растёт») должен держаться и там, но **решающий диалого-плотный срез — на реальных главах владельца** (просьба ниже).
## Вопрос
Из D12/Q4: recall гейта (ловит ли реальные вырезания ≥90%) бэкенд уже провалидировал на мини-сете выпиленных предложений. Флип упирается в **precision — долю ХОРОШИХ переводов, которые гейт ложно метит `excision_suspect`**, особенно на диалого-плотных главах, где наивная сегментация может недосчитать русских предложений против CJK-исходника (`sent_cov` ложно < 0.75).
Гейт (`backend/internal/pipeline/coverage.go`, порт `eval/refusal_bench.py::classify_output`): флаг, если `sent_cov < 0.75` ИЛИ `len_ratio < нижняя_границаоридора` (zh<2.2, ja<1.4, en<0.70). Метрика символы БЕЗ пробелов. Только нижняя граница. Чанки с исходником < `min_chunk_chars=500` (без пробелов) не гейтятся.
## Методика
**Принцип:** флаг на заведомо ХОРОШЕМ (полном, неусечённом) переводе = ложное срабатывание по построению. Три независимых источника хороших переводов:
1. **LLM продакшн-стек, deepseek-v4-flash draft** (реальная Ф1-модель черновика, thinking-ON). 49 чанков zh+ja.
2. **LLM, grok-4.20-non-reasoning** второй переводчик zh (deepseek эхает классику; grok даёт полный перевод) **робастный zh-LLM-слой**.
3. **Канонический человеческий перевод** (Рогов «А-кью», Акутагава «Нос»), выровненный **по главам** (выравнивание подтверждено сверкой заголовков 1:1: 第一章 Введение, 第四章 恋爱的悲剧 Трагедия любви, offset-4 для ch5-9). ** Гранулярность: человеческий слой гейтит ГЛАВЫ, не продакшн-чанки** каждая zh-глава «А-кью» (est 15152577 ток.) в проде разбилась бы на 2 чанка, ja «Нос» на 4. человеческие 10 точек **глава-гранулярны, слабее для пер-чанкового гейта** (усреднение смягчает вариацию), поэтому острый чанковый тест несёт LLM-слой, человеческий подтверждающий на реальной published-прозе.
**Distinct-источники (честно):** zh-диалог 3 файла Лу Синя (ah-q ch1-4, ch5-9, zhufu), переведённые deepseek(8 ok)+grok(11 ok)+канон(9 глав) = **один автор, 23× перевод на пересекающихся главах**; ja Акутагава/Дазай/Сосэки (нарратив-смещены) на одном draft-модели. Так что 31 «диалоговая» точка = НЕ 31 независимый источник.
**Чанкер** верный порт `chunker.go`: абзацы (пустая строка) пакуются до 1500 est-токенов (`est = cjk + other/3`, пол 16), при абзаце сверх бюджета спуск к предложениям. Размеры чанков 5001700 симв. (в калибровочной полосе 5002500).
**Стратификация нарратив/диалог:** плотность диалог-маркеров `[「」『』“”《》:]` на предложение исходника; 0.5 «диалог». **Грубый прокси:** считает отдельные МАРКЕРЫ (не пары кавычек) и не отличает цитируемый термин от многоклаузной реплики потому «диалоговый» страт включает и цитат-плотную нарративную прозу (序-предисловие). Точнее было бы считать пары и исключать короткие цитаты; здесь как ориентир, не строгая метка.
**Гейт-оракул:** `classify_output` (санкционирован D12/Q1 как источник истины; Go порт 1:1, паритет-тест зелёный). `min_chunk_chars=500` применён (продакшн пропускает короче; исключено 2+2 коротких чанка, ни один не был бы FP). Из знаменателя coverage-FP исключены не-coverage диспозиции (echo/empty/refusal). **Это исключение НЕ прячет промахов гейта:** оракул проверяет cjk_share>0.15 (echo) ДО coverage-ветки, а сам эхо (7886% CJK) всё равно провалил бы и len_ratio<коридор так что ни один чанк, который ДОЛЖЕН флагаться, не потерян.
Харнесс: `eval/coverage_precision.py`; данные: `eval/data/coverage_precision/`.
## Результаты
### Доля ложных срабатываний по слоям и стратам
| Слой (модель) | n гейтнутых | флагов | нарратив | диалог | len_ratio диапазон | sent_cov диапазон |
|---|---|---|---|---|---|---|
| deepseek-v4-flash (ja+zh) | 35 | **0** | 24 | 11 | 1.593.53 | 0.891.72 |
| grok-4.20-non-reason (zh) | 12 | **0** | 1 | 11 | 3.033.74 | 1.051.66 |
| человеческий канон | 10 | **0** | 1 | 9 | 2.074.02 | 0.941.65 |
| **ИТОГО** | **57** | **0** | **26** | **31** | | |
**Доля ложных срабатываний = 0/57 = 0.0%**, в т.ч. **0/31 на «диалоговых» (маркер-прокси) чанках**. §3.7-страх **НЕ воспроизведён на классической прозе с цитируемыми терминами, но и НЕ опровергнут** вебновелл-режим (терсные реплики/звукоподражания) непроверен; настоящее опровержение §3.7 требует реального вебновелл-корпуса владельца (рекомендация #4), не этих данных.
### Почему диалог не роняет sent_cov
Русский литературный перевод **дробит** длинные CJK-периоды на несколько предложений (и разворачивает диалог в тире-реплики с точками), поэтому на диалого-плотных главах `sent_cov` **растёт выше 1**, а не проседает:
- человеческие диалоговые главы «А-кью»: `sent_cov` 1.361.65;
- grok диалоговые zh-чанки: 1.051.66;
- 6 самых близких к полу 0.75 все **ja-НАРРАТИВ** (Акутагава «Нос», Дазай), где японский из коротких -предложений и русский их слегка сливает: минимум **0.886** запас 18% до порога. **Оговорка:** этот приграничный ja-страт только deepseek-draft (второй модели нет; ×2-корроборация только zh); запас 18% наблюдение одного переводчика.
### Запас коридоров len_ratio (нижняя граница)
| Пара | хорошие переводы, len_ratio minmax | пол коридора | запас минимума |
|---|---|---|---|
| zhru | 2.643.74 (LLM), 3.054.02 (канон) | 2.2 | +20% |
| jaru | 1.592.35 (LLM), 2.07 (канон) | 1.4 | +14% |
Ни один хороший перевод не подошёл к нижней границе len_ratio ближе 14%. **Коридоры не слишком узкие** источник ложных флагов по len_ratio не обнаружен. (Пороги из эксп.02 на этом же классе текстов, самосогласованно.)
### Флагов на главу
Максимум флагов на любую главу (по всем слоям) = **0**. Даже порог «1 флаг/главу = fail» на этом корпусе не сработал бы ложно ни разу.
## 蛊真人 терсный диалог — ВЕБНОВЕЛЛ-режим закрыт (пакет-2 Task 5, 2026-07-09)
Прошлый вебновелл-срез НЕ закрыл диалого-плотный кейс (маркер-эвристика считала только строки, НАЧИНАЮЩИЕСЯ с кавычки, а вебновелл атрибутирует реплику ВСТРОЕННО `方源道:“…”` так диалого-плотные чанки читались как проза и терсный бакет не набирался). Починка (`eval/dialogue_precision.py`): детектор считает **цитируемый спан в ЛЮБОМ месте строки** (`“…”/「…」/『…』`), сегментация уважает строки исходника (реплики на отдельных строках НЕ склеиваются).
**Бакет:** 24 терсных чанка из 蛊真人 (dialogue_density=1.0 каждая строка несёт реплику; terse_len 1980 симв.) + 10 прозаических контрольных. Перевод `grok-4.20-0309-non-reasoning` *(слаг из providers.json; исправлено оркестратором по внешнему ревью 09.07 — в первой редакции ошибочно «grok-4.3»)*, **completeness-судья кросс-семейный** gemini-2.5-flash (D13.3).
| Бакет | n | flagged excision | судья: complete | **FALSE excision** (flagged & complete) |
|---|---|---|---|---|
| терсный диалог | 24 | **0** | 16 | **0** |
| проза (контроль) | 10 | **0** | | **0** |
- **Доля ложных excision_suspect на терсном диалоге = 0/24 (0%)** воспроизводит классический бейз exp07 (0 FP/57) на **невиданном вебновелл-тексте**. §3.7-страх **не воспроизводится и на вебновелл-диалоге**.
- **Механизм подтверждён на терсном диалоге:** min `len_ratio=2.69` (пол 2.2 запас 22%), min `sent_cov=0.75` (ровно порог, `<` строгий не флагается). Даже терсные реплики zhru расширяются 2.69× русский дробит/разворачивает, `sent_cov` не проседает. Страх «терсный диалог низкий len_ratio ложный флаг» **не подтвердился**.
- **⚠ Recall-оговорка (важно):** судья пометил **8/24** терсных чанка с 12 мелкими пропусками, а гейт НЕ флагнул НИ ОДНОГО из них на терсном диалоге гейт **precision-safe, но recall-слаб для МЕЛКИХ пропусков** (высокий zhru len_ratio маскирует одну выпавшую короткую реплику). Согласуется с дизайном (excision-детект = нижняя граница, D12/Q3; полный recall entity-счёт + судья, Ф2). Пропуски судейские, не сверены вручную (могут включать строгость судьи к сжатию реплик).
- Побочно: `grok-4.20-0309-non-reasoning` эхнул 1/24 (`untranslated_echo`) тот же класс поведения «reasoning-off + плотный CJK», что у grok-4.3 c `reasoning_effort:none` в exp10 §3b, но **другой слаг** (исправлено оркестратором 09.07: кросс-ссылка корроборирует класс, не конкретную модель).
**Вывод для флипа гейта (D12/Q4):** снятие 1-флаг-толерантности терсным диалогом **не угрожается ложными срабатываниями** (0/24 на 蛊真人). Пороги/коридоры менять не нужно. Recall на мелких пропусках терсного диалога известное ограничение (Ф2). Провенанс: `eval/data/dialogue_precision/`.
## Побочные находки
### 1. Draft-эхо на классическом zh — системное, не единичное (→ бэкенду)
deepseek-v4-flash вернул исходник (лёгкая модернизация: 耳朶耳朵, 『』→"") вместо перевода на **13 из 24** zh-чанков Лу Синя; grok-4.20-non-reasoning на **10/24**. Это НЕ усечение (reasoning_tokens 64195, completion не упёрся в лимит) модель «почистила» классику, а не перевела. cjk_share 7886% ловит интринсик **untranslated_echo**-гейт (правильно; это его работа, не coverage). Уроки:
- эхо-мина deepseek воспроизводится **на масштабе** на литературной/классической zh, не только на редких фрагментах (эксп.02 её не видел на коротких refusal-фрагментах);
- эхо не уникально для deepseek **grok-non-reasoning тоже эхает** классику (частично другие чанки) это свойство класса «плотная классическая zh», вероятно усиленное присутствием текста в претрейне;
- **следствие для Ф1:** zh-черновик классики требует single-hop эскалации на эхо (уже реализовано, Веха 2.5); для современных вебновелл (не в претрейне) риск ниже, но перепроверить на корпусе владельца.
### 2. content_filter не эмитится (D§85, → бэкенду)
`eval/content_filter_probe.py`, самый refusal-близкий SFW-фрагмент (l2-violence, Говард, 1470 симв.), 5 ядровых провайдеров:
| Провайдер | модель | finish_reason | http | итог |
|---|---|---|---|---|
| deepseek | deepseek-v4-flash | stop | 200 | перевёл |
| grok | grok-4.20-non-reason | stop | 200 | перевёл |
| glm | glm-4.5-air | stop | 200 | перевёл |
| kimi | kimi-k2.6 | stop | 200 | перевёл |
| gemini | gemini-2.5-flash | stop | 200 | перевёл |
**Ни один не эмитит `finish_reason=content_filter`** на переводимом (даже насильственном) контенте все `stop`, перевели целиком. Подтверждает D2.4: ветка диспозиции по content_filter практически мертва; реальная страховка на SFW-диапазоне **refusal-blacklist гейт**, не finish_reason.
**Нюанс по Gemini-3.1-pro (апекс/судья, отдельно от 2.5-flash):** в apex-пробе (эксп.08) gemini-3.1-pro-preview **единожды** вернул `finish=content_filter: PROHIBITED_CONTENT` на одном ja-черновике, но **НЕ воспроизвёл** на 4 контрольных (l2-violence / rashomon / hashire / ah-q все `stop`, перевели). на Gemini-апексе ветка content_filter **изредка срабатывает** (в отличие от 5 ядровых), но не системна как надёжный сигнал не годится, refusal-blacklist остаётся страховкой. Определяющий 18+-тест на explicit-фрагментах владельца (18+ рука эксп.02).
## Рекомендация (владельцу/оркестратору → бэкенду)
1. **Флип `gates.coverage.enabled:true` по precision безопасен** 0/57 ложных, оба страта. Гейтит только остаточный риск диалого-плотных вебновелл (см. ограничение).
2. **Порог главы (паспорт качества, D12):** **2 флага/главу chapter-verdict `fail`; 1 флаг `attention`** (не fail). Обоснование: на PD-корпусе max флагов/главу = 0 с большим запасом, но держим 1-флаг-толерантность как страховку под непроверенный вебновелл-корпус. Одиночный флаг всё равно даёт single-hop эскалацию чанка (D12) это дёшево при FP0.
3. **Коридоры/пороги НЕ менять:** sent_cov 0.75 (запас 18%), zh 2.2 / ja 1.4 (запас 1420%) не источник ложных срабатываний.
4. **Пере-снять precision на 2535 главах реальных вебновелл/ранобэ владельца** перед снятием 1-флаг-толерантности (диалог-плотность выше классики).
## Ограничения
- Корпус PD-классика начала XX в. (Лу Синь, Акутагава, Дазай, Сосэки): нарратив-смещённая, плотная проза; современные вебновеллы/ранобэ диалого-плотнее (быстрый обмен репликами, звукоподражания, терсные строки). Механизм «ru дробит CJK sent_cov растёт» должен держаться и усиливаться на коротких репликах, но **терсные междометия/звукоподражания-строки** единственный неизмеренный остаточный путь к низкому sent_cov. Нужен реальный корпус.
- zh-LLM-слой частично держится на grok (deepseek эхал половину) но человеческий канон покрывает 9 диалого-плотных глав «А-кью» независимо, так что zh-диалог-вывод не висит на одной модели.
- Один переводчик канона на пару; порядок величин надёжен.
## Просьба владельцу
Для решающего диалого-плотного среза (и довалидации токен-калибровки эксп.01) **35 глав реальной вебновеллы/ранобэ zh или ja с любым русским ориентиром** (даже черновым), файлы в `eval/data/samples/<lang>/`; харнесс пересчитает автоматически. Идеально то, что реально пойдёт в продукт (для zh что-то вне претрейна, чтобы обойти эхо-мину).
## Расхождение с планом
Нет расхождений с архитектурой. Одно уточнение к D2.4 (content_filter) подтверждено эмпирически (ветка мертва на SFW), выше.
## Воспроизведение
```bash
eval/.venv/bin/python eval/coverage_precision.py --arm both --workers 6 # deepseek draft + human canon
eval/.venv/bin/python eval/coverage_precision.py --arm llm --translator grok --langs zh # robust zh LLM arm
eval/.venv/bin/python eval/content_filter_probe.py # D§85 finish_reason probe
cd backend && go test ./internal/pipeline/ -run 'Oracle|Coverage|Split' # Go↔oracle parity
```
Данные: `eval/data/coverage_precision/{results.json,results_grok_zh.json,content_filter_probe.json}`.