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

25 KiB
Raw Permalink Blame History

Эксперимент 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). (→ сужено D21.9/D22.6: верно для SFW/violence и 5 ядровых; на Gemini 3.x ветка ЖИВА и на ГРАФИЧНОЙ эротике СИСТЕМНА — 8× PROHIBITED_CONTENT СОСТАВНОЙ строкой, точный матчер мёртв (D22.8а). refusal-blacklist остаётся страховкой.)
  • Оракул↔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 пол коридора запас минимума
zh→ru 2.643.74 (LLM), 3.054.02 (канон) 2.2 +20%
ja→ru 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 (ровно порог, < строгий → не флагается). Даже терсные реплики zh→ru расширяются ≥2.69× — русский дробит/разворачивает, sent_cov не проседает. Страх «терсный диалог → низкий len_ratio → ложный флаг» не подтвердился.
  • ⚠ Recall-оговорка (важно): судья пометил 8/24 терсных чанка с 12 мелкими пропусками, а гейт НЕ флагнул НИ ОДНОГО из них → на терсном диалоге гейт precision-safe, но recall-слаб для МЕЛКИХ пропусков (высокий zh→ru 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) — это дёшево при FP≈0.
  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), выше.

Воспроизведение

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}.