Self-Distilled HEAD-T: фильтр как учитель
Cascade-v3 + ledger.perception_filter работает: модель предлагает, фильтр отбраковывает. Но в среднем 35% учительских меток (от агентного gold) разойдутся с правилами фильтра — то есть модель училась на «over-labeled» данных. Эксперимент v4: применить фильтр как deterministic label-corrector — где он сказал бы «не на доску», переписать gold в none. Гипотеза: HEAD-T v4 будет калиброванно абстиниться, fewer demotions на инференсе. Результат — измеряется.
1. Что чиним
Гейт прошёл на cascade-v3 благодаря фильтру: модель раздаёт type=action на сомнительных репликах, а фильтр их отбраковывает (29 false-positive action на gold-none → демоут). Это работает, но тратит inference-такты на предложения, которые всё равно умрут на фильтре.
Идея: подравнять обучение к правилам фильтра. Если фильтр говорит «без императива и без адресата это не action», то и модель должна абстиниться. Это сужает расхождение model↔filter и делает каскад эффективнее.
2. Метод
Правила perception_filter — детерминированные, применимы offline:
for each (reply, gold_type) in train:
if filter_would_keep(gold_type, reply) == False:
gold_type ← "none" # relabel
То есть: gold-action без императива / без адресата → переписан в none. Gold-topic короче 12 слов без named-entity → none. И так далее, по тем же правилам, что в admit().
3. Что получилось на корпусе
| metric | значение |
|---|---|
| Train examples | 18,880 |
| Filter согласен с учителем (kept) | 12,263 |
| Filter РАЗОШЁЛСЯ → relabel в none | 6,617 (35%) |
| из них action | 2,961 (учитель over-fired) |
| из них topic | 2,410 |
| из них decision | 749 |
| из них fragment | 497 |
Балансировка (та же MULT, topic×1 decision×4 action×2 fragment×4 none×1) даёт 18,847 sampled примеров:
- none: 63% (vs 49% в v3)
- topic: 22% (vs 19%)
- fragment: 11% (vs 16%)
- decision: 2% (vs 13%)
- action: 3% (vs 4%)
none стало больше; decision/fragment сильно урезаны — это и есть «фильтр-как-учитель» в действии.
4. Обучение
Тот же LoRA-рецепт, что v3 (Qwen3-1.7B, r=8 layers=8, lr 1e-4, mask-prompt, 900 iters). Адаптер: adapters_headT_v4_selfdistill.
Val loss с iter 1 = 6.198 (выше v3's 2.749 — relabel-heavy данные сложнее). Сходится за ~26 минут на M3 Pro.
5. Гейт (пред-регистрация, НЕ двигать)
Тот же, что для v3: на той же held-out (530 реплик, untouched), после cascade-merge + ledger-filter:
- off-F1 ≥ 0.455 — directed не регрессировать (HEAD-D неприкосновенен, by-construction должно держаться).
- type macro-F1 > 0.270 — выше baseline.
- hall_total ≤ 5 — честный гейт (action + directed + decision на gold-none).
Текущий v3-baseline (с фильтром): off-F1 0.476, type 0.339, hall 3. Чтобы v4 имела смысл, она должна не регрессировать + дать какое-то реальное улучшение (например, type-recall на каком-то классе).
6. Гипотезы
H+: v4 better calibrated → filter rejects FEWER cells (model agrees with filter rules already). Inference чище и быстрее. Type-macro может slightly расти.
H0: v4 ≈ v3 после фильтра. Гипотеза: фильтр сам по себе достаточный — переобучение под него ничего не добавляет.
H–: v4 over-abstains. Раз 35% позитивных меток переписаны в none, модель учится молчать слишком часто. Type-macro падает (особенно decision/fragment).
H– — реальный риск. Decision-recall на v3 уже низкий (~5%); если v4 убьёт его в ноль, type-macro макро-F1 упадёт.
7. Результат — H− confirmed
Прогнано 2026-05-26 (auto-сессия). cascade-v4 с v4@200 чекпойнтом против v3 (production):
| | off-F1 | type-macro | per-type (t/d/a/f) | hall | |---|---|---|---|---| | v3 cascade + filter (production) | 0.476 | 0.339 | 0.29/0.07/0.45/0 | 2/407 | | v4 self-distilled + filter | 0.455 | 0.174 ↓↓ | 0/0/0/0 | 2/407 |
Cascade-v4 ОТВЕРГНУТ. Type macro-F1 0.174 — провален гейт (>0.270 нужен). Все per-type F1 на board-классах = 0: HEAD-T v4 предсказывает none почти всегда. H− подтверждена.
8. Что это значит
Filter-as-label-corrector в его текущей форме (hard relabel) — слишком агрессивен. Когда 35% позитивных меток переписаны в none, модель учится не «когда применять правила фильтра» а «всегда абстиниться». В тренировке это даёт val_loss 0.055 (любая none-prediction корректна на 63% none-данных). На инференсе — model = filter = всегда none → доска пустая.
Урок: для перехода от model+filter к model-with-filter-baked-in нужна более мягкая supervision:
- soft labels (label-smoothing на спорных примерах);
- weight×0.3 вместо hard relabel — учитель сохраняет позитивный сигнал, но с пониженной уверенностью;
- лучше — двухголовое обучение: одновременно учить «is-board-worthy?» (бинарный, supervised filter-rules) и «what-type?» (мультикласс, full teacher labels).
Это направление для v5+, не блокер: v3 production уже принят гейтом, cascade-v4 был добавочный эксперимент.
9. Архитектурное подтверждение
Важное: directed-сила сохранена by-construction — off-F1 0.455 у v4 точно равен baseline (HEAD-D неприкосновенен). Это архитектурное свойство каскада: что бы ни случилось с HEAD-T, directed-моат не страдает. Это и есть смысл «model proposes, ledger disposes» — независимые оси.
cortex-lm/scripts/build_typeonly_v4.py · cortex-lm/v0/data_typeonly_v4_bal/ · cortex-lm/v0/adapters_headT_v4_selfdistill/ (не подключён) · cortex-lm/v0/eval_fb/cascade_v4_filtered.jsoncortex-lm/scripts/build_typeonly_v4.py · cortex-lm/v0/data_typeonly_v4_bal/ · cortex-lm/v0/adapters_headT_v4_selfdistill/