Files
NLP/lab1/FOLLOWUP_EXPERIMENTS.md
2026-10-26 05:44:25 +03:00

46 KiB
Raw Blame History

Follow-up эксперименты

Документ продолжает EXPERIMENTS.md. Здесь зафиксировано, что показали предварительные прогоны в results/, какие вопросы остались открытыми и какая серия экспериментов их закрывает. Запуск серии — scripts/run_followup_experiments.sh, сводка — scripts/analyze_followup.py.


1. Что показали текущие прогоны

1.1. Важная методическая поправка

В run_metadata.json, benchmark/summary.csv, ablation.csv, lsa_sweep.csv, delta_sweep.csv и length_bins.csv основные числа часто берутся из repeat 0, а не как среднее по повторам. Например, у основного запуска stylometry_pos_lsa/logreg run_metadata.metrics.macro_f1 = 0.1798, но среднее по пяти повторам — 0.1785 ± 0.0060, а по 25 фолдам — 0.1755 ± 0.0189. Разница между repeat 0 и средним может быть больше, чем эффект, который мы пытаемся измерить.

Поэтому в таблице ниже числа пересчитаны по pooled_metrics.csv (среднее по повторам). Новый скрипт scripts/analyze_followup.py делает то же самое для всех follow-up запусков.

1.2. Сводка результатов

Постановка Macro-F1, mean ± std Повторов Комментарий
random_forest по умолчанию 0.2180 ± 0.0053 3 лучший наблюдавшийся результат
random_forest, depth 30, leaf 1 0.2007 ± 0.0040 3 ручная сетка не проверяла max_depth=None
linear_svc, C = 0.3 0.1878 ± 0.0052 3
linear_svc, C = 1 0.1871 ± 0.0048 3
logreg, nested CV 0.1827 1 outer настройка внутри фолдов
stylometry_pos / logreg 0.1815 ± 0.0054 5 без LSA
stylometry_pos_lsa / logreg 0.1785 ± 0.0060 5 LSA не улучшает
logreg, C = 10 0.1843 ± 0.0048 3
lsa_content / logreg 0.1324 ± 0.0011 3 тема даёт сигнал, но меньше полного набора
lsa_char / logreg 0.1127 ± 0.0007 3
stylometry (только ручные) 0.1051 ± 0.0038 5
lsa_word / logreg 0.0943 ± 0.0020 3
fw / logreg 0.0598 ± 0.0039 3
lsa_pos / logreg 0.0478 ± 0.0012 3
length / logreg 0.0354 ± 0.0032 5
char / multinomial_nb 0.0274 ± 0.0018 3
pos / multinomial_nb 0.0203 ± 0.0012 3
length / dummy 0.0038 1

Случайный уровень для 34 классов — около 0.029 по macro-F1. Почти все осмысленные модели выше него, но разброс между повторами и seed-ами сопоставим с разницей между многими конфигурациями.

1.3. Что уже можно утверждать

  1. Символьные и POS n-граммы — основной сигнал. Переход от stylometry (0.105) к stylometry_pos (0.182) даёт около +0.077 macro-F1. Это больше, чем даёт любой LSA-блок.
  2. LSA в текущем виде не оправдан. stylometry_pos (0.1815) и stylometry_pos_lsa (0.1785) статистически неразличимы: McNemar p = 0.86, bootstrap-разница +0.0021 [-0.0108, +0.0157]. LSA стоит либо убрать, либо пересобрать (другие варианты, число компонент, веса).
  3. Random forest по умолчанию лучше линейных моделей. 0.2180 против 0.1815–0.1878. При этом ручная сетка max_depth ∈ {10, 20, 30} не проверяла max_depth=None, поэтому «лучшая» точка сетки (0.2007) хуже дефолта. Нужна сетка вокруг дефолта: max_depth=None, min_samples_leaf, max_features, n_estimators, class_weight.
  4. Абляция намекает на шумные группы. При 2 повторах удаление function_words (+0.0041), pos (+0.0026), lexical (+0.0020) не ухудшало результат. Это в пределах шума, но достаточно, чтобы проверить «худые» наборы на 5 повторах.
  5. Качество зависит от длины. Для основного запуска (repeat 0): 31+ слов — 0.2055, 21–30 — 0.1781, 11–15 — 0.1385. Гипотеза H4 поддерживается для длинных реплик; группа 0–5 слов (n = 42) имеет слишком широкий интервал.
  6. Ошибки группируются. Частые пары: Abigail↔Haley, Emily↔Leah/Penny, Sam↔Sebastian/Alex, Vincent↔Jas. Это согласуется с возрастными и гендерными подгруппами, но не доказывает, что модель обязана их путать.
  7. Класс-уровень неоднороден. Худшие F1 (repeat 0): Kent 0.00, Marnie 0.035, Jodi 0.075, Penny 0.091, Maru 0.093. Лучшие: Willy 0.393, Evelyn 0.367, Vincent 0.333. Редкие классы (Mister Qi — 15 реплик, Sandy — 21, Dwarf — 23, Wizard — 24) дают широкие интервалы.
  8. Верификация и кластеризация слабые. ROC-AUC = 0.630, EER = 0.412, TAR@FAR=1% = 0.035, top-1 = 0.133. Кластеризация: ARI = 0, NMI ≈ 0.31–0.35. Это ограничивает интерпретацию: 34-классовая задача различает персонажей лишь частично, и «идеальных» групп нет.
  9. Near-duplicates почти нет. При cosine ≥ 0.9 по char 3–5 всего 3 пары, при ≥ 0.95 — одна, и все они внутри одного персонажа. Утечки между классами через почти-дубли не видно; StratifiedGroupKFold не обязателен, но проверка включена в follow-up как диагностика.

Follow-up пересмотрел пункты 2, 3 и 5: LSA оказался полезен в варианте content и при k = 300–400, ExtraTrees обошёл random forest, а зависимость качества от длины реплики оказалась немонотонной. Подробности и числа — в §7.


2. Открытые вопросы и гипотезы follow-up

Обозначение Гипотеза Как проверяем
F-H1 Настроенный random forest устойчиво лучше линейных моделей на ≥ 0.02 macro-F1 F1, F2, F7; парные сравнения по повторам
F-H2 LSA-блок не улучшает stylometry_pos; content-LSA отражает тему, а не стиль F3; stylometry_pos vs +content vs +pos vs +word/pos
F-H3 «Худые» наборы без function_words, pos, vocabulary, readability не хуже полного F4; абляция на 5 повторах + lean-конфиги
F-H4 Настройка min_df, max_features, ngram_range для char/POS даёт ≥ 0.01 F5; сетка на 3 повторах, финалисты на 5
F-H5 class_weight="balanced" повышает macro-F1, но снижает accuracy; компромисс нужно измерить F6; balanced vs none для logreg/SVC/RF
F-H6 Ранжирование моделей устойчиво к seed F7; seeds 7, 2024, 1337
F-H7 Ошибки концентрируются внутри возрастных/гендерных групп F8; subgroup-report и анализ confusions

3. Матрица экспериментов

Все запуски идут с folds=5. Опорный seed — 42. Если не указано иное, повторность 5. Полный список команд и параметров — в scripts/run_followup_experiments.sh; ниже — назначение стадий.

F0. Воспроизведение опорных результатов

Label Что запускается Повторов Зачем
ref_logreg stylometry_pos_lsa + logreg 5 опорная точка 0.1785
ref_nested_logreg то же, --nested 1 outer честная оценка с настройкой C
ref_no_lsa stylometry_pos + logreg 5 вклад LSA
ref_stylometry stylometry + logreg 5 вклад n-грамм

F1. Сравнение моделей

Label Модель Повторов
model_rf_default random forest по умолчанию 5
model_et_default extra trees по умолчанию 5
model_svc_default linear SVC, C=1 5
model_knn_default kNN 3
model_rf_nested, model_et_nested, model_svc_nested те же с --nested 1 outer

F2. Сетка вокруг random forest

Сетка строится от дефолта, потому что именно дефолт показал лучший результат.

  • max_depth ∈ {None, 30, 40}, min_samples_leaf ∈ {1, 2, 3};
  • max_features ∈ {sqrt, 0.05, 0.1}, n_estimators ∈ {400, 800};
  • class_weight ∈ {balanced_subsample (default), balanced, none};
  • extra trees: max_depth ∈ {None, 30}, min_samples_leaf ∈ {1, 2}.

Повторность 3. Лучшая точка сетки выбирается автоматически по среднему pooled macro-F1, затем перезапускается на REPEATS_MAIN повторах как rf_grid_best_5 и сравнивается с model_rf_default парно. Выбор точки по тестовым метрикам остаётся разведочным: честная оценка — только по final_rf_nested.

F3. LSA: тема против стиля

Label Конфигурация Повторов
lsa_sweep варианты content/char/word/pos/fw, k ∈ {100, 200, 300, 400} 3
lsa_content_k300, lsa_content_k400 LSA content отдельно 5
lsa_char_k300, lsa_pos_k300 LSA char/pos отдельно 5
stylometry_pos_content300 stylometry_pos + content-LSA(k=300) 5
stylometry_pos_pos300 stylometry_pos + POS-LSA(k=300) 5
stylometry_pos_lsa_k300 stylometry_pos + word/pos-LSA(k=300) 5

Ключевое сравнение — stylometry_pos_content300 vs ref_no_lsa. Если content-LSA не даёт прироста, LSA-блок можно исключить из финальной модели. Если даёт, это скорее тематический сигнал, и его нужно обсуждать отдельно, а не выдавать за «стиль».

F4. Абляция и lean-наборы

Label Что исключено
ablate_full по одной группе, 5 повторов
groups_lean1 function_words, pos
groups_lean2 function_words, pos, vocabulary, readability
groups_lean2_content300 то же + content-LSA(k=300)
groups_lean1_rf, groups_lean2_rf лучшие lean-наборы с random forest

F5. Символьные и POS n-граммы

  • char: min_df ∈ {2, 5} (общий параметр для всех TF-IDF/LSA-блоков, не только для char), char_max_features ∈ {5000, 10000}, ngram_range ∈ {(2,4), (3,5)} при базовых (2,5);
  • POS: pos_ngram_range ∈ {(1,2), (1,4)}, pos_max_features ∈ {500, 1000}.

Сетка на logreg, 3 повтора. Два лучших варианта проверяются с random forest на 5 повторах (min_df2_rf, char_max10000_rf).

F6. Балансировка классов

class_weight="balanced" (по умолчанию) против None для logreg, linear SVC и random forest; для RF дополнительно "balanced" вместо "balanced_subsample". 5 повторов. Смотреть не только macro-F1, но и accuracy, balanced accuracy и F1 по редким классам.

F7. Устойчивость к seed

ref_logreg и model_rf_default с seed ∈ {7, 2024, 1337} (seed 42 уже есть в F0/F1). Для RF дополнительно меняется random_state. 5 повторов. Если преимущество RF сохраняется в 3/3 дополнительных seed-ах, вывод устойчив.

F8. Финальная модель и отчёты

  • final_rf: random forest, 10 повторов;
  • final_rf_nested: nested CV для честной оценки;
  • length-report, subgroup-report по gender/age_group/species;
  • compare с ref_logreg и ref_no_lsa;
  • verify для stylometry_pos_lsa с seed 42/7/2024;
  • delta по cosine для 500/1000/2000 признаков;
  • cluster для k = 3, 5, 8.

F9. Контроли

  • control_length_logreg и control_length_dummy — нижние границы в той же схеме CV;
  • near-duplicates по char cosine ≥ 0.8 сохраняются в results/followup/negative_control/near_duplicates.csv;
  • контроль перестановкой меток: та же модель и CV на перемешанных метках, seeds 42/7/2024. Эмпирический chance-level должен быть около 0.02–0.03; если он заметно выше, в пайплайне есть утечка или артефакт.

4. Как считать результат

  1. Основная метрика — среднее pooled macro-F1 по повторам, а не repeat 0. Рядом указывать std, min/max и bootstrap-интервал (последний в текущем коде считается по repeat 0 — это описательная оценка, не замена разбросу по повторам).

  2. Парные сравнения — по общим повторам: разница macro-F1, McNemar на каждом повторе, bootstrap-разница. В comparisons.csv есть n_positive, mean_mcnemar_p, n_ci_excludes_zero. Сравнение корректно только при одинаковых разбиениях (тот же seed и число фолдов); analyze_followup.py сопоставляет строки по line_id и берёт только общие номера повторов.

  3. Правило решения (консервативное): улучшение считаем убедительным, если одновременно:

    • средняя разница ≥ 0.01;
    • разница положительна минимум в 4 из 5 повторов (или в 3 из 3, если повторов 3);
    • McNemar даёт p < 0.05 в большинстве повторов;
    • bootstrap-интервал разницы не содержит ноль в большинстве повторов.

    Если эффект меньше 0.01 или знак скачет, выбираем более простую модель.

  4. Множественные сравнения. Для итоговых утверждений применять Holm—Bonferroni к семейству парных p-value; в сводке сырые p-value.

  5. Не выбирать признаки/гиперпараметры по тестовым метрикам. Сетка F2/F5 — разведочная; финальные числа берутся из nested CV или из конфигурации, выбранной до просмотра теста.

  6. Вторичные метрики: accuracy, balanced accuracy, top-3/top-5, per-class F1, length bins, subgroup F1, confusions. Macro-F1 — главная, но не единственная.


5. Запуск

Из каталога lab1:

# Полная follow-up серия (около 4–4.5 часов на 12 ядрах)
PYTHON=.venv/bin/python bash scripts/run_followup_experiments.sh

# Быстрая проверка пайплайна: 1 повтор, без сетки и seed-робастности
QUICK=1 PYTHON=.venv/bin/python bash scripts/run_followup_experiments.sh

# Без тяжёлой сетки и seed-ов, но с финальной моделью
RUN_GRID=0 RUN_SEEDS=0 PYTHON=.venv/bin/python bash scripts/run_followup_experiments.sh

Переменные:

Переменная По умолчанию Значение
PYTHON python интерпретатор с зависимостями
FOLLOWUP_DIR results/followup куда складывать результаты (удобно для дымового теста)
RUN_ID текущее время суффикс имени лога
QUICK 0 1 — все повторы 1, без сетки и seed-ов
REPEATS_MAIN 5 опорные запуски и финалисты
REPEATS_GRID 3 сетка RF/ET, kNN, delta
REPEATS_SWEEP 3 LSA-sweep
REPEATS_NGRAM 3 сетка n-грамм
REPEATS_FINAL 10 финальный RF
RUN_GRID 1 стадия F2
RUN_SEEDS 1 стадия F7
RUN_FINAL 1 стадия F8
RUN_CONTROLS 1 стадия F9
SKIP_TESTS 0 1 — не запускать pytest
FORCE 0 1 — перезаписать существующие label-каталоги
SEED 42 опорный seed

Результаты:

results/followup/
  ref_logreg/                  # один каталог на эксперимент
    run_metadata.json
    pooled_metrics.csv
    per_class_report.csv
    ...
    command.txt                # точная команда
  configs/                     # сгенерированные TOML
  comparisons/
  negative_control/
  summary.csv
  summary.md
  comparisons.csv
  comparisons.md
  manifest.tsv
results/logs/run_followup_<timestamp>.log

Сводка пересчитывается отдельно:

.venv/bin/python scripts/analyze_followup.py \
  --followup-dir results/followup \
  --pairs final_rf:ref_logreg final_rf:ref_no_lsa model_rf_default:ref_logreg

5.1. Проверка пайплайна

Серия прогнана целиком в дымовом режиме (1 повтор, 1 seed, временный FOLLOWUP_DIR, 80 минут): все стадии отработали, summary.csv собрал 60 запусков с pooled-метриками, comparisons.md и JSON по парам сформированы, pytest — 33 теста. Контроль перестановкой меток дал macro-F1 0.0270–0.0282 (среднее 0.0276) при ожидаемом случайном уровне ≈ 0.029 — признаков утечки не видно.

Числа дымового прогона в отчёт не идут (1 повтор), но два сигнала стоит проверить в основной серии: extra_trees и char_max_features=10000 на одном разбиении обошли RF, а stylometry_pos + content-LSA(k=300) — прежний stylometry_pos + word/pos-LSA(k=100). Именно это и проверяют F2, F3 и F5 на 3–5 повторах.


6. Время

Полная серия измерена на этой машине (12 ядер, sklearn 1.9.1): старт 25.10.2026 21:08:25 MSK, финиш 26.10.2026 01:19:52 MSK, итого 4 ч 11 мин. Это меньше первоначальной оценки 5–7 часов, которая была получена линейным пересчётом дымового прогона и не учитывала, что заметная часть времени — фиксированные затраты (загрузка корпуса, построение признаков, отчёты), а не повторная CV.

Стадия Время (замер)
F0: окружение, pytest, опорные запуски 13 мин
F1: сравнение моделей + nested 13 мин
F2: сетка RF/ET + F2.1 72 мин
F3: LSA-sweep + подтверждения 33 мин
F4: абляция + lean-наборы 43 мин
F5: символьные и POS n-граммы 19 мин
F6: class_weight 10 мин
F7: устойчивость к seed 17 мин
F8: финал, verify, delta, cluster 10 мин
F9: контроли, near-duplicates, перестановка, сводка 20 мин

QUICK=1 на этой машине занял около 80 минут (1 повтор, без сетки RF/ET и seed-робастности, но со sweep, финалом и отчётами). Время зависит от числа потоков sklearn и от стоимости LSA; на 12 ядрах n_jobs=-1 у GridSearchCV конкурирует с остальными стадиями.


7. Результаты полной серии

Серия прошла 25.10.2026 21:08:25 — 26.10.2026 01:19:52 MSK (4 ч 11 мин, 12 ядер). В results/followup/ лежат 66 запусков; после серии отдельно добавлены два nested-запуска для финалистов (char_max10000_rf_nested, groups_lean2_content300_nested). Всего в summary.csv 68 строк. Сводка — summary.csv / summary.md, парные сравнения — comparisons.csv / comparisons.md, лог — results/logs/run_followup_20261025_210825.log.

7.1. Ранжирование

Δ — разница с ref_logreg (0.1785 по 5 повторам). У nested-запусков в колонке «повторов» стоит 1: это одна вложенная CV из 5 outer-фолдов, а метрика посчитана по pooled OOF-предсказаниям, поэтому std по повторам для них не определён.

Label Модель Повторов Macro-F1, mean ± std Δ Accuracy Balanced acc.
char_max10000_rf random_forest 5 0.2583 ± 0.0066 +0.0798 0.2391 0.2473
char_max10000_rf_nested random_forest 1 (5 folds) 0.2579 +0.0794 0.2394 0.2521
groups_lean2_content300_nested logreg 1 (5 folds) 0.2572 +0.0788 0.2369 0.2587
model_et_default extra_trees 5 0.2551 ± 0.0073 +0.0767 0.2387 0.2418
et_dNone_l2 extra_trees 3 0.2538 ± 0.0089 +0.0753 0.2416 0.2609
groups_lean2_content300 logreg 5 0.2510 ± 0.0040 +0.0725 0.2379 0.2644
model_et_nested extra_trees 1 (5 folds) 0.2486 +0.0701 0.2344 0.2478
stylometry_pos_content300 logreg 5 0.2485 ± 0.0042 +0.0700 0.2337 0.2574
rf_grid_best_5 random_forest 5 0.2294 ± 0.0082 +0.0509 0.2253 0.2317
model_rf_default random_forest 5 0.2213 ± 0.0065 +0.0428 0.2108 0.2114
final_rf random_forest 10 0.2216 ± 0.0062 +0.0431 0.2094 0.2112
final_rf_nested random_forest 1 (5 folds) 0.2163 +0.0378 0.2055 0.2102
model_svc_default linear_svc 5 0.1880 ± 0.0067 +0.0096 0.1840 0.1869
ref_no_lsa logreg 5 0.1815 ± 0.0054 +0.0031 0.1708 0.1988
ref_logreg logreg 5 0.1785 ± 0.0060 0.0000 0.1679 0.1874
classweight_logreg_none logreg 5 0.1496 ± 0.0077 −0.0289 0.1655 0.1367
ref_stylometry logreg 5 0.1051 ± 0.0038 −0.0733 0.1012 0.1023
control_length_logreg logreg 5 0.0354 ± 0.0032 −0.1430 0.0395 0.0585
control_length_dummy dummy 1 0.0038 −0.1747 0.0693 0.0294

Случайный уровень для 34 классов ≈ 0.029; контроль перестановкой меток дал 0.0276. Почти все осмысленные конфигурации выше него, но не все различия между ними значимы.

Главные наблюдения:

  1. Лучшие результаты — деревья с расширенным char-словарём и ExtraTrees. char_max10000_rf (0.2583) и model_et_default (0.2551) идут впереди дефолтного RF (0.2213) на +0.033…+0.037. Nested-оценки подтверждают масштаб: 0.2579 для char-RF и 0.2486 для ET. Разница между этими двумя лидерами мала (≈ +0.003; см. 7.3), поэтому выбирать между ними нужно по простоте и устойчивости, а не по третьему знаку.
  2. Лучший logreg — groups_lean2_content300 (0.2510; nested 0.2572). Он не уступает деревьям, но выигрывает за счёт content-LSA, а не за счёт «стилевых» признаков. Это важно для интерпретации (см. 7.2 и 7.5).
  3. Сетка вокруг RF почти ничего не дала. rf_grid_best_5 = 0.2294, то есть +0.008 к дефолту, и это в пределах шума. Основной прирост дал не max_depth/min_samples_leaf, а char_max_features=10000.
  4. LSA нельзя оценивать одной точкой. word+pos LSA при k=100 был нейтрален, content-LSA при k=300–400 — сильный, POS-LSA — вредный. Зависимость от k монотонная: content 100 → 0.133, 200 → 0.180, 300 → 0.198, 400 → 0.213 (sweep, 3 повтора).
  5. class_weight критичен для logreg. Без балансировки macro-F1 падает на 0.029, а accuracy почти не меняется. Для деревьев эффект < 0.005 и не значим.
  6. Разброс по seed мал, разброс по фолдам — нет. У RF std по повторам 0.003–0.008; у nested logreg отдельные фолды дают 0.219–0.290. Поэтому одна nested-оценка точнее по среднему, но не даёт интервала по повторам. Nested-оценка groups_lean2_content300 (0.2572) даже выше среднего по 5 повторам (0.2510) — это не прирост от настройки C, а следствие конкретного разбиения; сравнивать нужно средние по повторам, а nested использовать как оценку без оптимизма по гиперпараметрам.

7.2. Проверка гипотез F-H1…F-H7

Гипотеза Вердикт Главные числа
F-H1: настроенный RF устойчиво лучше линейных моделей на ≥ 0.02 Подтверждена RF vs logreg: +0.0428 (5/5, p = 0.0006); ET vs logreg: +0.0767; nested: RF +0.0336, ET +0.0659; seed-разброс 0.221–0.222 против 0.178–0.181
F-H2: LSA не улучшает stylometry_pos; content-LSA — тема, не стиль Пересмотрена word+pos k=100: +0.0031 (3/5, p = 0.63); content k=300: +0.0669 (5/5, p < 0.0001); POS-LSA k=300: −0.0130; content k=400 отдельно: 0.2114, выше полного logreg 0.1785
F-H3: lean-наборы без function_words, pos, vocabulary, readability не хуже полного Подтверждена (эффект ≈ 0) lean1 +0.0059, lean2 +0.0023 (p = 0.30/0.46); lean+RF +0.004…+0.006 (p = 0.52/0.58); выигрыша нет, но и потери нет
F-H4: настройка min_df, max_features, ngram_range даёт ≥ 0.01 Частично min_df, char/POS n-граммы, pos_max_features — все < 0.005 и не значимы; char_max_features=10000: logreg +0.0070 (p = 0.051), RF +0.0369 (5/5, p = 0.0032)
F-H5: balanced class weights повышают macro-F1 ценой accuracy Частично logreg balanced vs none: +0.0289 macro-F1, accuracy 0.1679 vs 0.1655 (цены по accuracy нет), balanced accuracy 0.187 vs 0.137; SVC: −0.003; RF: −0.001…−0.004, но balanced accuracy 0.242 vs 0.211
F-H6: ранжирование моделей устойчиво к seed Подтверждена для seed 7 / 2024 / 1337: RF 0.2221 / 0.2206 / 0.2210, logreg 0.1779 / 0.1805 / 0.1800; разрыв 0.040–0.044 во всех seed
F-H7: ошибки концентрируются внутри возрастных/гендерных групп Не подтверждена F1 по подгруппам различается (elder 0.33, child 0.28, adult 0.19, teen 0.16), но доля ошибок внутри своей группы ≈ случайной: gender 0.449 против 0.472, age 0.384 против 0.360; ошибки стягиваются в hub-классы (Abigail)

F-H2 — главный пересмотр. Исходный вывод «LSA не оправдан» был верен только для конфигурации по умолчанию: word+pos, k=100. При k=300–400 и варианте content LSA даёт большой прирост, а одна content-LSA(k=400) без ручных признаков обходит полный stylometry_pos_lsa с logreg. При этом content-LSA строится по содержательным словам и их биграммам, то есть кодирует лексику и тему (сезоны, работа, подарки, события), а не только идиолект. Поэтому прирост нельзя записывать в «стиль»: это может быть «о чём персонаж говорит» вместо «как он говорит». Прямого тематического контроля в серии нет — это следующий обязательный эксперимент (7.6).

F-H7 — не подтверждена в исходной формулировке. Различия в F1 по подгруппам есть, но они не означают, что модель путает персонажей внутри группы чаще случайного. Фактически ошибки концентрируются не в демографии, а в нескольких «hub»-классах: Abigail получает 23.3% всех предсказаний (её истинная доля — 6.9%), precision 0.09, recall 0.30. Крупнейшие потоки ошибок: Sebastian → Abigail (353 раза за 10 повторов), Haley → Abigail (328), Alex → Abigail (280), Penny → Abigail (266). Это ближе к коллапсу редких классов в один популярный, чем к возрастной путанице.

7.3. Парные сравнения

Правило решения из §4: улучшение убедительно, если Δ ≥ 0.01, знак положителен минимум в 4/5 повторов, McNemar p < 0.05 в большинстве повторов и bootstrap-интервал не содержит ноль. mean_ci — средний интервал по повторам; n_ci — сколько повторов его не содержат.

A vs B Δ macro-F1 Повторов в плюс McNemar p Bootstrap CI CI ∌ 0
model_et_default vs ref_logreg +0.0767 5/5 < 0.0001 [+0.0540, +0.0988] 5/5
groups_lean2_content300_nested vs ref_nested_logreg +0.0745 1/1 < 0.0001 [+0.0531, +0.0954] 1/1
groups_lean2_content300 vs ref_logreg +0.0725 5/5 < 0.0001 [+0.0529, +0.0917] 5/5
stylometry_pos_content300 vs ref_logreg +0.0700 5/5 < 0.0001 [+0.0526, +0.0873] 5/5
stylometry_pos_content300 vs ref_no_lsa +0.0669 5/5 < 0.0001 [+0.0496, +0.0829] 5/5
model_rf_default vs ref_logreg +0.0428 5/5 0.0006 [+0.0199, +0.0652] 5/5
char_max10000_rf_nested vs model_rf_nested +0.0416 1/1 < 0.0001 [+0.0256, +0.0578] 1/1
char_max10000_rf vs model_rf_default +0.0369 5/5 0.0032 [+0.0201, +0.0535] 5/5
model_et_default vs model_rf_default +0.0338 5/5 0.0113 [+0.0176, +0.0497] 5/5
model_et_nested vs model_rf_nested +0.0323 1/1 0.0002 [+0.0175, +0.0473] 1/1
stylometry_pos_lsa_k300 vs ref_logreg +0.0258 5/5 < 0.0001 [+0.0145, +0.0371] 5/5
char_max10000_rf_nested vs model_et_nested +0.0093 1/1 0.5812 [−0.0064, +0.0262] 0/1
rf_grid_best_5 vs model_rf_default +0.0081 4/5 0.2607 [−0.0045, +0.0222] 2/5
char_max10000_rf vs groups_lean2_content300 +0.0073 5/5 0.6593 [−0.0165, +0.0297] 0/5
char_max10000 vs ref_logreg +0.0070 3/3 0.0514 [+0.0016, +0.0125] 2/3
groups_lean2_content300_nested vs groups_lean2_content300 +0.0044 1/1 0.8126 [−0.0037, +0.0117] 0/1
char_max10000_rf vs model_et_default +0.0031 3/5 0.5732 [−0.0145, +0.0212] 0/5
ref_no_lsa vs ref_logreg +0.0031 3/5 0.6317 [−0.0100, +0.0167] 0/5
groups_lean2_content300 vs stylometry_pos_content300 +0.0025 3/5 0.4839 [−0.0115, +0.0171] 0/5
char_max10000_rf_nested vs groups_lean2_content300_nested +0.0006 1/1 0.8440 [−0.0208, +0.0226] 0/1
stylometry_pos_pos300 vs ref_no_lsa −0.0130 0/5 0.0671 [−0.0246, −0.0017] 3/5
classweight_logreg_none vs ref_logreg −0.0289 0/5 0.6541 [−0.0462, −0.0112] 5/5
control_length_logreg vs ref_logreg −0.1430 0/5 < 0.0001 [−0.1611, −0.1215] 5/5

Полные 46 сравнений — в results/followup/comparisons.csv и comparisons.md; в таблице выше оставлены те, на которые опираются выводы.

Что важно в этой таблице:

  • Прирост content-LSA и lean+content — самый большой и устойчивый: +0.067…+0.073, 5/5 повторов, p < 0.0001, интервал далеко от нуля.
  • Деревья обходят logreg устойчиво: +0.043 (RF) и +0.077 (ET) относительно ref_logreg, 5/5 повторов, p ≤ 0.0006. В nested-постановке разрыв сохраняется: char-RF +0.042 относительно model_rf_nested, lean+content +0.075 относительно ref_nested_logreg (оба p < 0.0001).
  • ET значимо лучше дефолтного RF: +0.0338, 5/5, p = 0.011, CI [+0.0176, +0.0497]. Это не шум, и именно ET, а не RF, стоит брать за базу для деревьев.
  • Два лидера между собой неразличимы. char_max10000_rf vs model_et_default: +0.0031, 3/5, p = 0.57, CI накрывает ноль. В nested: char_max10000_rf_nested vs model_et_nested +0.0093 (p = 0.58), vs groups_lean2_content300_nested +0.0006 (p = 0.84). Выбирать между char-RF, ET и lean+content по этим данным нельзя — разница меньше неопределённости разбиения.
  • char_max10000_rf vs дефолтный RF — редкий случай, когда настройка одного параметра признаков даёт > 0.03 и подтверждается на всех повторах.
  • Сетка max_depth/min_samples_leaf даёт лишь +0.0081 (4/5, p = 0.26, CI содержит ноль), то есть подбор глубины деревьев почти бесполезен.
  • Старый LSA(word+pos, k=100) действительно нейтрален (+0.0031, 3/5), но тот же вариант при k=300 даёт +0.0258 (5/5, p < 0.0001). Значит, вопрос не в «LSA вообще», а в числе компонент: при k=100 сигнал теряется. POS-LSA при k=300 по-прежнему вредит (−0.0130, 0/5).
  • class_weight=none для logreg хуже по macro-F1, хотя McNemar не значим: обе модели часто ошибаются, но balanced-версия лучше вытягивает редкие классы. Это видно по balanced accuracy (0.187 против 0.137).

Сырые p-value не поправлены на множественность (46 пар). Эффекты +0.04…+0.07 с p < 0.001 переживут Holm—Bonferroni; пограничные (+0.003…+0.009) — нет, и их не стоит интерпретировать как выигрыш.

7.4. Диагностика финальной модели (final_rf, 10 повторов)

  • Длина реплики. Зависимость немонотонная: 0–5 слов — 0.081 (n = 42), 6–10 — 0.173, 11–15 — 0.182, 16–20 — 0.194, 21–30 — 0.231, 31+ — 0.168. Прежний вывод «чем длиннее, тем лучше» не подтвердился: после 30 слов качество падает, вероятно из-за накопления тематических и синтаксических различий.
  • Подгруппы. Elder 0.331, child 0.280, adult 0.190, teen 0.164; gender: male 0.205, female 0.190, unknown 0.229; species: monster 0.269, human 0.201, unknown 0.035. Это F1 внутри группы, а не качество предсказания группы.
  • Классы. Средний F1 по 10 повторам: медиана 0.193, минимум 0.020 (Wizard), максимум 0.550 (Evelyn). Худшие: Wizard 0.020, Penny 0.056, Jodi 0.069, Maru 0.075, Mister Qi 0.081; лучшие: Evelyn 0.550, Willy 0.494, Pam 0.493, Pierre 0.470, Vincent 0.397. Семь классов имеют F1 < 0.1.
  • Hub-классы. 23.3% предсказаний — Abigail при истинной доле 6.9%; Alex — 12.3% при 6.0%. Abigail: precision 0.09, recall 0.30, F1 0.14. Это главный источник ошибок и главный резерв для калибровки prior/ порогов.
  • Верификация. ROC-AUC 0.630–0.633, EER 0.412–0.415, TAR@FAR=1% 0.035–0.038, top-1 0.133–0.141 для seed 42/7/2024. Выше случайного (1/34 = 0.029), но практически бесполезно для open-set сценария.
  • Кластеризация. ARI = 0 для k = 3/5/8 (и agglomerative, и k-means); NMI 0.14–0.53. Персонажи не образуют устойчивых кластеров в этом признаковом пространстве.
  • Delta (MFW, cosine). 2000 признаков — 0.2426, 1000 — 0.2299, 500 — 0.1604 (3 повтора). Больше признаков лучше; потолок не найден.
  • Near-duplicates. 13 пар с cosine ≥ 0.8, 3 ≥ 0.9, 1 ≥ 0.95; все — внутри одного персонажа, межклассовых почти-дублей нет. Утечки через дубли не видно.
  • Контроль перестановкой. Macro-F1 0.0270 / 0.0274 / 0.0282 для seed 42/7/2024 (среднее 0.0276) при случайном уровне ≈ 0.029. Пайплайн не подглядывает в метки.
  • Нижние границы. Length+logreg 0.0354, dummy 0.0038. Основной результат (0.22–0.26) далеко от обеих границ.

7.5. Что это меняет в выводах

  1. «Финальная» модель в текущем виде — не лучшая. final_rf — это дефолтный random forest (0.2216), тогда как ExtraTrees (0.2551) и RF с char_max_features=10000 (0.2583) заметно лучше. В отчёте стоит либо переименовать final_rf в baseline_rf, либо заменить финальную модель на ET/char-RF и пересчитать отчёты.
  2. Главный сигнал — лексика, а не «стиль». Сильнее всего работают content-LSA (тема/лексика) и char-ngram-словарь. Ручные стилометрические признаки дают лишь 0.105. Формулировка «модель различает идиолект» слишком сильная; корректнее — «модель различает персонажей по лексико-тематическому профилю и символьным n-граммам».
  3. Вложенная CV подтверждает лидеров, но не закрывает выбор признаков. Nested-оценки (0.2579 / 0.2486 / 0.2572) честнее по гиперпараметрам, но char_max_features=10000 и content-LSA(k=300) выбирались на тех же данных. Для строгого вывода нужна внешняя валидация или вложенный выбор признаков.
  4. Основная неопределённость — не seed, а разбиение и признаки. Разброс по seed ≤ 0.008, а по фолдам nested logreg — до 0.07. Поэтому одна цифра без интервала вводит в заблуждение; нужно указывать mean ± std по повторам и отдельно fold-разброс.
  5. Ошибки — это hub-коллапс, а не демографическая путаница. Калибровка prior/порогов и работа с редкими классами дадут больше, чем дальнейший подбор глубины деревьев.

7.6. Следующие эксперименты

  1. Nested CV с выбором признаков внутри внешнего цикла. Сейчас nested крутит только C/max_depth/min_samples_leaf; char_max_features и k LSA фиксированы. Нужен внешний цикл, в котором выбираются и они, либо отдельный held-out набор для выбора признаков.
  2. content-LSA k=400/500 + lean2. Sweep показывает монотонный рост до k=400; стоит проверить k=400 и 500 с groups_lean2_content* и stylometry_pos_content*, желательно сразу с nested-оценкой.
  3. Тематический контроль. Проверить, сохраняется ли прирост content-LSA, если: (а) сбалансировать реплики по темам/сезонам, (б) удалить тематические слова, (в) обучить adversarial-классификатор темы и убрать её компоненту. Без этого нельзя утверждать, что модель ловит стиль, а не «о чём говорят».
  4. Калибровка hub-классов. Prior correction, temperature scaling или пороги на OOF для Abigail/Alex; оптимизировать macro-F1 декодированием, а не только обучением. Смотреть precision/recall по редким классам.
  5. Отчёты и статистика. Заменить final_rf на реального лидера, добавить Holm—Bonferroni для семейства парных сравнений, считать иерархические интервалы (повтор × фолд), а не только repeat-0 bootstrap.
  6. Внешняя валидация. Другой корпус, другая игра или хотя бы разбиение по сценам/диалогам, а не по отдельным репликам. Только оно покажет, переносится ли лексико-тематический сигнал за пределы этого корпуса.

8. Ограничения

  • Корпус 2005 реплик, 34 класса, минимум 15 реплик на класс. Доверительные интервалы по редким персонажам останутся широкими даже при 10 повторах.
  • Повторная CV с одними и теми же 2005 репликами не заменяет внешнюю валидацию на новом корпусе.
  • content-LSA может отражать тему (сезоны, подарки, работа), а не идиолект. Поэтому его вклад нельзя интерпретировать как «стиль» без отдельного контроля (например, обучения на тематически сбалансированных подвыборках).
  • verify и cluster не имеют параметра repeats; для них меняется только seed/разбиение, поэтому выводы по ним слабее, чем по CV.
  • При интерпретации subgroup-метрик нужно помнить, что это F1 внутри группы персонажей, а не качество предсказания самой группы.