Compare commits
4 Commits
master
...
cursor_tes
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
79f51105fd | ||
|
|
84ae1c2408 | ||
|
|
b00e3c964e | ||
|
|
c131815fc7 |
1
.gitignore
vendored
@@ -10,3 +10,4 @@ lab1/data/processed/
|
||||
lab1/features/
|
||||
lab1/results/*
|
||||
!lab1/results/README.md
|
||||
|
||||
|
||||
10
README.md
@@ -53,15 +53,5 @@ nlp/
|
||||
методическое описание метрик и классификаторов.
|
||||
- [`lab1/FEATURES.md`](lab1/FEATURES.md) — признаки и формулы.
|
||||
- [`lab1/TUNING.md`](lab1/TUNING.md) — настройка параметров и перебор.
|
||||
- [`lab1/RESULTS_ANALYSIS.md`](lab1/RESULTS_ANALYSIS.md) — анализ результатов
|
||||
follow-up серии и выводы.
|
||||
- [`lab1/FINAL_REPORT.md`](lab1/FINAL_REPORT.md) — итоговый отчёт по всей
|
||||
работе и экспериментам.
|
||||
- [`lab1/FINAL_RESULTS.csv`](lab1/FINAL_RESULTS.csv) — сводная таблица
|
||||
результатов.
|
||||
- [`lab1/FOLLOWUP_EXPERIMENTS.md`](lab1/FOLLOWUP_EXPERIMENTS.md) — протокол
|
||||
follow-up экспериментов.
|
||||
- [`lab1/PRESENTATION_OUTLINE.md`](lab1/PRESENTATION_OUTLINE.md) — структура
|
||||
презентации.
|
||||
- [`lab1/presentation/README.md`](lab1/presentation/README.md) — готовая
|
||||
презентация, сборка и исходные изображения.
|
||||
|
||||
14
lab1/.gitignore
vendored
@@ -7,17 +7,3 @@ __pycache__/
|
||||
/features/
|
||||
/results/*
|
||||
!/results/README.md
|
||||
|
||||
/presentation/preview/
|
||||
|
||||
/presentation/latex/*.aux
|
||||
/presentation/latex/*.log
|
||||
/presentation/latex/*.nav
|
||||
/presentation/latex/*.out
|
||||
/presentation/latex/*.snm
|
||||
/presentation/latex/*.toc
|
||||
/presentation/latex/*.synctex.gz
|
||||
/presentation/latex/preview/
|
||||
|
||||
/report/build/
|
||||
/dist/stage/
|
||||
|
||||
@@ -1,529 +0,0 @@
|
||||
# Архив лабораторной работы №1
|
||||
|
||||
**Тема:** классификация текстов на основе стилометрических характеристик.
|
||||
**Материал:** реплики персонажей игры Stardew Valley.
|
||||
**Задача:** по одной реплике определить, какому из 34 персонажей она
|
||||
принадлежит, и проверить, какие стилометрические признаки несут этот сигнал.
|
||||
|
||||
Это не авторская атрибуция: все реплики написаны одним автором. Мы различаем
|
||||
**речевые маски персонажей** — намеренно созданные автором различия в лексике,
|
||||
синтаксисе, пунктуации и разговорных маркерах.
|
||||
|
||||
---
|
||||
|
||||
## 1. Что в архиве
|
||||
|
||||
| Путь | Что это |
|
||||
|---|---|
|
||||
| `lab1/report/REPORT.pdf` | Итоговый отчёт, 21 страница. Главный документ. |
|
||||
| `lab1/report/REPORT.docx` | Редактируемая версия отчёта. |
|
||||
| `lab1/report/REPORT.md` | Исходник отчёта в Markdown. |
|
||||
| `lab1/report/figures/` | Рисунки отчёта. |
|
||||
| `lab1/presentation/stardew_stylometry.pdf` | Презентация для защиты. |
|
||||
| `lab1/presentation/stardew_stylometry.pptx` | Редактируемая презентация. |
|
||||
| `lab1/presentation/latex/` | LaTeX-исходник и PDF презентации. |
|
||||
| `lab1/presentation/images/`, `generated/` | Исходные изображения и диаграммы. |
|
||||
| `lab1/stylometry/` | Основной код проекта. |
|
||||
| `lab1/scripts/` | Проверка окружения, запуск серий, сборка отчёта, упаковка. |
|
||||
| `lab1/tests/` | 33 теста (`pytest`). |
|
||||
| `lab1/configs/default.toml` | Конфигурация по умолчанию. |
|
||||
| `lab1/data/dialogues.csv` | Корпус: 2005 реплик, cp1251, разделитель `;`. |
|
||||
| `lab1/data/dialogues.xlsx` | Тот же корпус в Excel. |
|
||||
| `lab1/data/stats_per_character.xlsx` | Статистика по персонажам. |
|
||||
| `lab1/data/metadata/` | Метаданные персонажей для анализа ошибок. |
|
||||
| `lab1/data/corpus_report_*.txt` | Отчёты о состоянии корпуса до/после очистки. |
|
||||
| `lab1/FINAL_RESULTS.csv` | Сводная таблица финальных результатов. |
|
||||
| `lab1/FINAL_REPORT.md` | Расширенная версия отчёта с деталями экспериментов. |
|
||||
| `lab1/EXPERIMENTS.md` | Протокол экспериментов и команд. |
|
||||
| `lab1/FEATURES.md` | Признаки, формулы, обоснование. |
|
||||
| `lab1/METRICS_AND_CLASSIFIERS.md` | Метрики и классификаторы. |
|
||||
| `lab1/TUNING.md` | Настройка гиперпараметров. |
|
||||
| `lab1/RUNBOOK.md` | Подробная инструкция по запуску полной серии. |
|
||||
| `lab1/RESULTS_ANALYSIS.md` | Анализ результатов и ошибок. |
|
||||
| `lab1/FOLLOWUP_EXPERIMENTS.md` | Дополнительные эксперименты. |
|
||||
| `lab1/PRESENTATION_OUTLINE.md` | Структура презентации. |
|
||||
| `lab1/exports/nlp_lab1_final_results_20261026_120000.tar.gz` | Финальные результаты в компактном виде. |
|
||||
| `lab1/requirements.txt` | Основные зависимости. |
|
||||
| `lab1/requirements-report.txt` | Зависимости для пересборки отчёта. |
|
||||
|
||||
Полный список файлов с размерами и SHA-256 — в `MANIFEST.txt`.
|
||||
Контрольные суммы ключевых файлов — в `CHECKSUMS.sha256`.
|
||||
|
||||
---
|
||||
|
||||
## 2. Требования
|
||||
|
||||
### 2.1. Обязательные
|
||||
|
||||
- Python **3.11+** (проверено на 3.14);
|
||||
- Linux, macOS или Windows с WSL;
|
||||
- около 1 ГБ свободного места с виртуальным окружением и NLTK;
|
||||
- интернет — только для установки пакетов и скачивания NLTK.
|
||||
|
||||
### 2.2. Для пересборки отчёта
|
||||
|
||||
- `pandoc` 3.x;
|
||||
- LibreOffice (`libreoffice --headless`);
|
||||
- Python-пакеты `python-docx`, `PyMuPDF` (см. `requirements-report.txt`);
|
||||
- шрифты с кириллицей: Times New Roman или Liberation Serif, Liberation Mono.
|
||||
|
||||
### 2.3. Для пересборки LaTeX-презентации
|
||||
|
||||
- XeLaTeX;
|
||||
- пакеты Beamer, tcolorbox, TikZ, fontspec, babel-russian;
|
||||
- шрифт Liberation Sans.
|
||||
|
||||
---
|
||||
|
||||
## 3. Установка
|
||||
|
||||
Все команды выполняются из каталога `lab1`:
|
||||
|
||||
```bash
|
||||
cd lab1
|
||||
```
|
||||
|
||||
### 3.1. Виртуальное окружение
|
||||
|
||||
```bash
|
||||
python -m venv .venv
|
||||
source .venv/bin/activate
|
||||
```
|
||||
|
||||
Windows PowerShell:
|
||||
|
||||
```powershell
|
||||
python -m venv .venv
|
||||
.venv\Scripts\activate
|
||||
```
|
||||
|
||||
Если окружение уже есть в корне проекта, можно использовать его:
|
||||
|
||||
```bash
|
||||
source ../.venv/bin/activate
|
||||
```
|
||||
|
||||
### 3.2. Зависимости
|
||||
|
||||
```bash
|
||||
python -m pip install --upgrade pip
|
||||
python -m pip install -r requirements.txt
|
||||
```
|
||||
|
||||
Для пересборки отчёта дополнительно:
|
||||
|
||||
```bash
|
||||
python -m pip install -r requirements-report.txt
|
||||
```
|
||||
|
||||
### 3.3. Ресурсы NLTK
|
||||
|
||||
```bash
|
||||
python scripts/download_nltk_data.py
|
||||
```
|
||||
|
||||
Скрипт скачивает `punkt`, `punkt_tab`, `averaged_perceptron_tagger`,
|
||||
`averaged_perceptron_tagger_eng`, `stopwords` в `lab1/nltk_data`.
|
||||
|
||||
Без этих ресурсов POS-признаки отключатся, а результаты будут неполными.
|
||||
Если машина без интернета, скопируйте готовый каталог `nltk_data` в `lab1/`.
|
||||
|
||||
### 3.4. Проверка
|
||||
|
||||
```bash
|
||||
python scripts/check_environment.py
|
||||
python -m pytest -q
|
||||
```
|
||||
|
||||
Ожидается:
|
||||
|
||||
```text
|
||||
Окружение готово.
|
||||
33 passed
|
||||
```
|
||||
|
||||
`check_environment.py` печатает версии библиотек, SHA-256 корпуса, число
|
||||
реплик, персонажей и словоупотреблений, проверяет токенизатор и POS-теггер.
|
||||
|
||||
---
|
||||
|
||||
## 4. Быстрый запуск
|
||||
|
||||
### 4.1. Аудит корпуса
|
||||
|
||||
```bash
|
||||
python -m stylometry.cli audit
|
||||
```
|
||||
|
||||
Создаёт:
|
||||
|
||||
- `data/processed/audit.md` — отчёт по корпусу;
|
||||
- `data/processed/lines.csv` — нормализованные реплики;
|
||||
- `data/processed/figures/class_distribution.png`.
|
||||
|
||||
Проверьте: 2005 реплик, 34 персонажа, 38 207 словоупотреблений, точных
|
||||
дублей нет.
|
||||
|
||||
### 4.2. Ручные признаки
|
||||
|
||||
```bash
|
||||
python -m stylometry.cli features
|
||||
```
|
||||
|
||||
Создаёт:
|
||||
|
||||
- `features/stylometric.csv` — матрица 2005 × 454;
|
||||
- `features/feature_dictionary.csv` — описание признаков.
|
||||
|
||||
### 4.3. Одна модель
|
||||
|
||||
```bash
|
||||
python -m stylometry.cli run \
|
||||
--preset stylometry_pos_lsa \
|
||||
--model logreg \
|
||||
--folds 5 \
|
||||
--repeats 5
|
||||
```
|
||||
|
||||
Результаты появятся в `results/<preset>_<model>_<timestamp>/`:
|
||||
`summary.md`, `summary.json`, `fold_metrics.csv`, `pooled_metrics.csv`,
|
||||
`per_class_report.csv`, `confusion_matrix.csv`, `oof_predictions.csv`.
|
||||
|
||||
### 4.4. Полная серия
|
||||
|
||||
```bash
|
||||
bash scripts/run_all_experiments.sh
|
||||
```
|
||||
|
||||
Скрипт последовательно запускает все стадии и пишет лог в
|
||||
`results/logs/run_all_<timestamp>.log`. Ориентировочное время — 1,5–3 часа
|
||||
на обычном ноутбуке. Быстрый режим без финальных чисел:
|
||||
|
||||
```bash
|
||||
QUICK=1 bash scripts/run_all_experiments.sh
|
||||
```
|
||||
|
||||
### 4.5. Финальные серии
|
||||
|
||||
```bash
|
||||
python scripts/run_final_experiments.py \
|
||||
--stages nested topic calibration ensemble extra \
|
||||
--out results/final
|
||||
```
|
||||
|
||||
Эта команда воспроизводит вложенный выбор конфигурации, контроль содержания,
|
||||
коррекцию приоров, ансамбли и дополнительные конфигурации. Именно эти
|
||||
результаты сведены в `FINAL_RESULTS.csv` и в отчёте.
|
||||
|
||||
---
|
||||
|
||||
## 5. Что делает каждый модуль
|
||||
|
||||
| Модуль | Назначение |
|
||||
|---|---|
|
||||
| `stylometry/io.py` | Чтение CSV/XLSX, проверка колонок, SHA-256 корпуса. |
|
||||
| `stylometry/cleaning.py` | Unicode-нормализация, плейсхолдеры, пробелы. |
|
||||
| `stylometry/tokenization.py` | NLTK: токенизация, предложения, POS; резервный режим. |
|
||||
| `stylometry/features/` | 454 ручных признака в 11 группах. |
|
||||
| `stylometry/lsa.py` | TF-IDF + TruncatedSVD для word/content/fw/char/pos. |
|
||||
| `stylometry/pipelines.py` | Объединение блоков признаков, модели, сетки параметров. |
|
||||
| `stylometry/cv.py` | Повторная стратифицированная и вложенная кросс-валидация. |
|
||||
| `stylometry/evaluation.py` | Macro-F1, accuracy, balanced accuracy, MCC, каппа, top-k, log loss, Brier, бутстрэп. |
|
||||
| `stylometry/stats.py` | Мак-Немар, Уилкоксон, Фридман, поправка Холма. |
|
||||
| `stylometry/delta.py` | Дельта Барроуза: MFW, z-нормирование, манхэттенское и косинусное расстояние. |
|
||||
| `stylometry/verification.py` | Верификация «свой/чужой»: ROC-AUC, EER, TAR@FAR. |
|
||||
| `stylometry/clustering.py` | Иерархическая и k-means кластеризация, ARI, NMI, силуэт. |
|
||||
| `stylometry/plotting.py` | Диаграммы классов, scree, матрицы ошибок, PCA, t-SNE. |
|
||||
| `stylometry/metadata.py` | Метаданные персонажей, анализ подгрупп. |
|
||||
| `stylometry/cli.py` | Единая точка входа для всех команд. |
|
||||
| `main.py` | Короткая обёртка над `stylometry.cli`. |
|
||||
|
||||
### Группы ручных признаков
|
||||
|
||||
1. Длина.
|
||||
2. Лексика.
|
||||
3. Словарное богатство.
|
||||
4. Символы.
|
||||
5. Пунктуация.
|
||||
6. Функциональные слова.
|
||||
7. Местоимения.
|
||||
8. Части речи.
|
||||
9. Синтаксис.
|
||||
10. Читаемость.
|
||||
11. Разговорные маркеры.
|
||||
|
||||
Формулы и обоснование — в `FEATURES.md`.
|
||||
|
||||
---
|
||||
|
||||
## 6. Данные и разметка
|
||||
|
||||
### 6.1. Формат
|
||||
|
||||
`data/dialogues.csv`:
|
||||
|
||||
- кодировка cp1251;
|
||||
- разделитель `;`;
|
||||
- колонки `Реплика` и `Персонаж`;
|
||||
- 2005 строк, 34 класса.
|
||||
|
||||
`data/dialogues.xlsx` — тот же корпус для Excel.
|
||||
`data/stats_per_character.xlsx` — число реплик и длины по персонажам.
|
||||
`data/metadata/` — пол, возраст, вид и роль для анализа ошибок; в признаки
|
||||
классификатора эти поля не подаются.
|
||||
|
||||
### 6.2. Правила разметки
|
||||
|
||||
1. Одна реплика — один объект.
|
||||
2. Метка — имя говорящего.
|
||||
3. Один объект относится ровно к одному классу.
|
||||
4. Соответствие реплики и метки проверено вручную.
|
||||
5. Служебные метки `<PLAYER>`, `<FARM>`, `<PET>`, `<SPOUSE>`, `<TIME>`,
|
||||
`<NOUN>`, `<NAME>` заменены нейтральными словами; их число сохранено
|
||||
отдельным признаком.
|
||||
|
||||
### 6.3. Очистка и шум
|
||||
|
||||
Нормализуется только Unicode: варианты кавычек, тире, многоточий и пробелов.
|
||||
Пунктуация, регистр и стоп-слова **сохраняются** — это стилометрический
|
||||
сигнал.
|
||||
|
||||
Шумом считались точные дубликаты, технические артефакты, общие короткие
|
||||
формулы и плейсхолдеры. Корпус прошёл ручную фильтрацию; точные правила
|
||||
фильтрации не сохранились, поэтому исходное распределение восстановить
|
||||
нельзя. Это ограничение зафиксировано в отчёте.
|
||||
|
||||
### 6.4. Ограничение по объёму
|
||||
|
||||
Формальные требования: не менее 90 текстов по 30 предложений и примерно
|
||||
равные классы. В этом корпусе единица анализа — реплика (в среднем 19 слов),
|
||||
классы неравномерны (15–139 реплик). По согласованию с преподавателем
|
||||
эксперимент выполнен на уровне реплик; в отчёте раздел 2.5 честно описывает
|
||||
это отклонение. Для компенсации используются веса классов и macro-F1.
|
||||
|
||||
---
|
||||
|
||||
## 7. Метрики и результаты
|
||||
|
||||
Основная метрика — **macro-F1**: средняя F-мера по 34 классам, каждый
|
||||
персонаж весит одинаково. Дополнительно считаются accuracy, balanced
|
||||
accuracy, top-3/top-5, MCC, каппа Коэна, log loss, Brier, матрица ошибок,
|
||||
бутстрэп-интервалы.
|
||||
|
||||
| Конфигурация | Macro-F1 |
|
||||
|---|---:|
|
||||
| Мягкое голосование трёх моделей | **0,3036 ± 0,0189** |
|
||||
| ExtraTrees, `char_max_features=10000` | 0,2757 ± 0,0219 |
|
||||
| Вложенный выбор конфигурации | 0,2746 ± 0,0233 |
|
||||
| ExtraTrees, полный набор | 0,2499 ± 0,0209 |
|
||||
| Логистическая регрессия, lean + content-LSA | 0,2466 ± 0,0191 |
|
||||
| Случайный лес, `char_max_features=10000` | 0,2583 ± 0,0066 |
|
||||
| Только ручные признаки | 0,1051 ± 0,0038 |
|
||||
| Случайный уровень | 0,029 |
|
||||
|
||||
На отложенной выборке 80/20 мягкое голосование дало 0,3333.
|
||||
|
||||
Контроль содержания: после замены существительных, глаголов, прилагательных,
|
||||
наречий и числительных маркером `CONTENT` macro-F1 падает с 0,2483 до 0,1129.
|
||||
Остаток всё ещё выше случайного уровня, но основной сигнал —
|
||||
лексико-тематический.
|
||||
|
||||
---
|
||||
|
||||
## 8. Как пересобрать отчёт
|
||||
|
||||
Отчёт собирается из `report/REPORT.md`:
|
||||
|
||||
```bash
|
||||
cd lab1
|
||||
python scripts/build_report.py
|
||||
```
|
||||
|
||||
Скрипт:
|
||||
|
||||
1. вызывает `pandoc` и делает `report/build/REPORT_raw.docx`;
|
||||
2. оформляет стили, поля, колонтитулы, таблицы и рисунки;
|
||||
3. сохраняет `report/REPORT.docx`;
|
||||
4. конвертирует DOCX в `report/REPORT.pdf` через LibreOffice;
|
||||
5. проверяет, что в PDF есть все основные разделы.
|
||||
|
||||
Только DOCX:
|
||||
|
||||
```bash
|
||||
python scripts/build_report.py --skip-pdf
|
||||
```
|
||||
|
||||
Требования: `pandoc`, `libreoffice`, `python-docx`, `PyMuPDF`.
|
||||
Рисунки лежат в `report/figures/`; Markdown ссылается на них относительно
|
||||
`report/`.
|
||||
|
||||
---
|
||||
|
||||
## 9. Как пересобрать презентацию
|
||||
|
||||
### 9.1. PPTX
|
||||
|
||||
В архиве лежит **финальная** версия `stardew_stylometry.pptx` (30 слайдов)
|
||||
после ручных правок. Скрипт `build_deck.py` — генератор исходной версии
|
||||
презентации; он создаёт 32 слайда и **перезапишет** финальный файл, если его
|
||||
запустить. Не запускайте скрипт, если хотите сохранить ручные правки: для
|
||||
изменений открывайте PPTX в PowerPoint или LibreOffice.
|
||||
|
||||
Если нужно пересобрать исходную версию с нуля:
|
||||
|
||||
```bash
|
||||
cd lab1/presentation
|
||||
python build_deck.py
|
||||
```
|
||||
|
||||
Скрипт использует `python-pptx`, `Pillow` и `matplotlib`. Диаграммы уже лежат
|
||||
в `presentation/generated/`; для их перегенерации нужны результаты серий
|
||||
(`results/final/...`), которых нет в архиве — сначала запустите эксперименты
|
||||
из раздела 4.
|
||||
|
||||
### 9.2. LaTeX/PDF
|
||||
|
||||
```bash
|
||||
cd lab1/presentation/latex
|
||||
xelatex stardew_stylometry.tex
|
||||
xelatex stardew_stylometry.tex
|
||||
```
|
||||
|
||||
Нужны XeLaTeX, Beamer, tcolorbox, TikZ и шрифт Liberation Sans. Готовый PDF —
|
||||
`stardew_stylometry.pdf`. Изображения подтягиваются из `../images/` и
|
||||
`../generated/`.
|
||||
|
||||
---
|
||||
|
||||
## 10. Где смотреть результаты
|
||||
|
||||
- `lab1/report/REPORT.pdf` — итоговый отчёт со всеми числами и выводами.
|
||||
- `lab1/FINAL_RESULTS.csv` — сводная таблица всех финальных конфигураций.
|
||||
- `lab1/FINAL_REPORT.md` — расширенное описание экспериментов.
|
||||
- `lab1/RESULTS_ANALYSIS.md` — анализ ошибок, классов-аттракторов и длины.
|
||||
- `lab1/exports/nlp_lab1_final_results_20261026_120000.tar.gz` — исходные
|
||||
`summary.json`, `fold_metrics.csv`, `confusion_matrix.csv` и предсказания
|
||||
финальных серий.
|
||||
- `lab1/presentation/generated/` — диаграммы для отчёта и презентации.
|
||||
|
||||
---
|
||||
|
||||
## 11. Частые проблемы
|
||||
|
||||
### `check_environment.py` не находит POS-теггер
|
||||
|
||||
Повторите:
|
||||
|
||||
```bash
|
||||
python scripts/download_nltk_data.py
|
||||
```
|
||||
|
||||
Проверьте, что каталог `lab1/nltk_data` существует и не пуст. Если сеть
|
||||
недоступна, скопируйте `nltk_data` с другой машины.
|
||||
|
||||
### `pytest` не устанавливается
|
||||
|
||||
Убедитесь, что активировано виртуальное окружение и версия Python не ниже
|
||||
3.11:
|
||||
|
||||
```bash
|
||||
python --version
|
||||
python -m pip install -r requirements.txt
|
||||
```
|
||||
|
||||
### `python -m stylometry.cli` не находит модуль
|
||||
|
||||
Команда должна выполняться из каталога `lab1`, а не из корня репозитория:
|
||||
|
||||
```bash
|
||||
cd lab1
|
||||
python -m stylometry.cli audit
|
||||
```
|
||||
|
||||
### LibreOffice не собирает PDF
|
||||
|
||||
Проверьте:
|
||||
|
||||
```bash
|
||||
libreoffice --version
|
||||
```
|
||||
|
||||
На сервере без графической сессии используйте именно `--headless`
|
||||
(это уже делает `build_report.py`). Если LibreOffice нет, отчёт можно
|
||||
открыть в Word/LibreOffice вручную и экспортировать в PDF.
|
||||
|
||||
### Кириллица в PDF выглядит квадратами
|
||||
|
||||
Установите шрифты Liberation Serif/Sans и Liberation Mono. В отчёте
|
||||
используются Times New Roman (с запасным Liberation Serif) и Liberation Mono.
|
||||
|
||||
### Полная серия идёт слишком долго
|
||||
|
||||
Запускайте в `tmux` или `nohup`, см. `RUNBOOK.md`, раздел 5.4. Для проверки
|
||||
работоспособности используйте `QUICK=1`.
|
||||
|
||||
---
|
||||
|
||||
## 12. Что исключено из архива
|
||||
|
||||
Чтобы архив оставался компактным и воспроизводимым, в него не включены:
|
||||
|
||||
- `.venv/` — виртуальное окружение;
|
||||
- `.git/` — история репозитория;
|
||||
- `nltk_data/` — скачивается скриптом;
|
||||
- `data/processed/` — аудит и промежуточные таблицы;
|
||||
- `features/` — матрица признаков (создаётся `features`);
|
||||
- `results/` — промежуточные результаты серий;
|
||||
- большие `exports/*.tar.gz` — кроме компактного финального архива;
|
||||
- `presentation/preview/` — PNG-превью слайдов;
|
||||
- `__pycache__/`, `.pytest_cache/` — кэши;
|
||||
- `presentation/stardew_stylometry.bak` — резервная копия;
|
||||
- служебные файлы LaTeX (`.aux`, `.log`, `.nav`, `.out`, `.snm`, `.toc`).
|
||||
|
||||
Всё это восстанавливается командами из разделов 3–4.
|
||||
|
||||
---
|
||||
|
||||
## 13. Проверка целостности архива
|
||||
|
||||
```bash
|
||||
sha256sum -c CHECKSUMS.sha256
|
||||
```
|
||||
|
||||
`MANIFEST.txt` содержит список всех файлов архива с размерами и SHA-256.
|
||||
Если контрольные суммы совпали, архив распакован полностью.
|
||||
|
||||
Минимальная проверка после распаковки:
|
||||
|
||||
```bash
|
||||
cd lab1
|
||||
python scripts/check_environment.py
|
||||
python -m pytest -q
|
||||
python -m stylometry.cli audit
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 14. Атрибуция
|
||||
|
||||
Диалоги Stardew Valley принадлежат ConcernedApe. Корпус используется
|
||||
исключительно в учебных и исследовательских целях. Код проекта можно
|
||||
использовать и изменять для учебных задач.
|
||||
|
||||
---
|
||||
|
||||
## 15. Шпаргалка команд
|
||||
|
||||
```bash
|
||||
# из lab1
|
||||
python scripts/check_environment.py # проверить окружение
|
||||
python -m pytest -q # 33 теста
|
||||
python -m stylometry.cli audit # аудит корпуса
|
||||
python -m stylometry.cli features # 454 ручных признака
|
||||
python -m stylometry.cli run \
|
||||
--preset stylometry_pos_lsa --model logreg --repeats 5
|
||||
bash scripts/run_all_experiments.sh # полная серия
|
||||
python scripts/run_final_experiments.py \
|
||||
--stages nested topic calibration ensemble extra --out results/final
|
||||
python scripts/build_report.py # отчёт DOCX + PDF
|
||||
```
|
||||
@@ -1,674 +0,0 @@
|
||||
# Итоговый отчёт по лабораторной работе
|
||||
|
||||
## Стилометрическая классификация реплик персонажей Stardew Valley
|
||||
|
||||
---
|
||||
|
||||
## Аннотация
|
||||
|
||||
Работа посвящена проверке того, можно ли стилометрически различить персонажей
|
||||
игры Stardew Valley по отдельным репликам, если все реплики написаны одним
|
||||
автором. Корпус содержит 2005 реплик, принадлежащих 34 персонажам; классы
|
||||
неравномерны (от 15 до 139 реплик). Основная задача — многоклассовая
|
||||
классификация; основная метрика — макро-усреднённая F-мера (macro-F1).
|
||||
|
||||
В работе построены ручные стилометрические признаки, символьные и
|
||||
частеречные n-граммы, а также несколько вариантов латентно-семантического
|
||||
анализа (LSA). Сравнивались логистическая регрессия, линейный метод опорных
|
||||
векторов, наивный байесовский классификатор, случайный лес, сверхслучайные
|
||||
деревья, метод ближайших соседей и дельта Барроуза. Использована повторная
|
||||
стратифицированная перекрёстная проверка, вложенная настройка гиперпараметров,
|
||||
контроль перестановкой меток и анализ ошибок.
|
||||
|
||||
Лучший наблюдаемый результат — мягкое голосование трёх моделей: macro-F1
|
||||
около **0,304** при 5-блочной перекрёстной проверке и **0,333** на отдельной
|
||||
стратифицированной отложенной выборке. Однако состав ансамбля выбирался по
|
||||
результатам предыдущих серий, поэтому более консервативная оценка —
|
||||
**0,275** для модели ExtraTrees с расширенным символьным словарём при
|
||||
вложенном выборе конфигурации. Случайный уровень для 34 классов составляет
|
||||
около **0,029**.
|
||||
|
||||
Главный содержательный вывод: персонажи действительно различимы, но
|
||||
основной вклад дают лексико-тематические признаки и символьные n-граммы, а не
|
||||
функциональные слова и синтаксис. При замене содержательных слов на
|
||||
нейтральный маркер качество ExtraTrees падает с 0,248 до 0,113. Следовательно,
|
||||
результат корректно интерпретировать как различимость речевых масок по
|
||||
лексико-тематическому профилю, а не как доказательство «чистого» авторского
|
||||
или персонажного идиолекта.
|
||||
|
||||
---
|
||||
|
||||
## 1. Введение
|
||||
|
||||
### 1.1. Постановка задачи
|
||||
|
||||
Рассматривается задача классификации коротких текстов — игровых реплик — по
|
||||
принадлежности персонажу. Каждая реплика относится ровно к одному из 34
|
||||
классов. Требуется:
|
||||
|
||||
1. построить признаки, отражающие стилометрические свойства реплик;
|
||||
2. сравнить несколько семейств классификаторов;
|
||||
3. оценить качество с учётом дисбаланса классов и малого объёма данных;
|
||||
4. проверить, обусловлен ли результат стилем или содержанием;
|
||||
5. сформулировать ограничения и направления дальнейшей работы.
|
||||
|
||||
### 1.2. Исследовательский вопрос
|
||||
|
||||
**Вопрос:** различимы ли 34 персонажа Stardew Valley по стилометрическим
|
||||
признакам отдельных реплик, если все реплики написаны одним автором?
|
||||
|
||||
Это не авторская атрибуция. Автор один; речь идёт о классификации **речевых
|
||||
масок персонажей** (character idiolect). Положительный результат означает,
|
||||
что созданные автором речевые маски статистически различимы, но не означает,
|
||||
что у персонажей разные авторы.
|
||||
|
||||
### 1.3. Гипотезы
|
||||
|
||||
| Обозначение | Гипотеза |
|
||||
|---|---|
|
||||
| H1 | Функциональные слова, частеречные признаки и символьные n-граммы различают персонажей выше случайного уровня |
|
||||
| H2 | На коротких репликах частеречные и символьные признаки устойчивее словарного богатства и читаемости |
|
||||
| H3 | LSA по всем словам отражает тему, а LSA по частеречным последовательностям — стиль |
|
||||
| H4 | Качество зависит от длины реплики |
|
||||
| H5 | Ошибки концентрируются вокруг персонажей со сходным возрастом, полом или ролью |
|
||||
|
||||
---
|
||||
|
||||
## 2. Корпус
|
||||
|
||||
### 2.1. Объём и разметка
|
||||
|
||||
| Параметр | Значение |
|
||||
|---|---|
|
||||
| Реплик | 2005 |
|
||||
| Персонажей | 34 |
|
||||
| Словоупотреблений | около 38,2 тыс. |
|
||||
| Средняя длина реплики | 19,1 слова |
|
||||
| Медиана | 17 слов |
|
||||
| Минимум / максимум | 3 / 78 слов |
|
||||
| Классы | 15–139 реплик |
|
||||
| Точные дубли реплик | 0 |
|
||||
|
||||
Корпус хранится в кодировке cp1251; предусмотрено чтение XLSX. Разметка
|
||||
содержит только текст реплики и имя персонажа. Метаданные о поле, возрасте,
|
||||
виде и роли персонажей хранятся отдельно и используются только для анализа
|
||||
ошибок, не как признаки классификатора.
|
||||
|
||||
### 2.2. Ограничения корпуса
|
||||
|
||||
- Корпус прошёл ручную фильтрацию: короткие и общие фразы удалены. Точные
|
||||
правила очистки не сохранились, поэтому воспроизвести исходное
|
||||
распределение невозможно.
|
||||
- Реплики короткие; признаки словарного богатства и читаемости на них
|
||||
нестабильны.
|
||||
- Классы неравномерны; редкие персонажи имеют широкие доверительные
|
||||
интервалы.
|
||||
- Нет идентификаторов сцен и диалогов; нельзя учесть корреляцию реплик
|
||||
внутри одной сцены.
|
||||
- Все тексты написаны одним автором.
|
||||
|
||||
---
|
||||
|
||||
## 3. Методика
|
||||
|
||||
### 3.1. Предобработка
|
||||
|
||||
1. Приведение Unicode: многоточия, кавычки, тире, неразрывные пробелы.
|
||||
2. Замена плейсхолдеров `<PLAYER>`, `<FARM>`, `<PET>`, `<SPOUSE>`, `<TIME>`,
|
||||
`<NOUN>`, `<NAME>` на нейтральные слова; количество плейсхолдеров
|
||||
сохраняется отдельным признаком.
|
||||
3. Токенизация и разбиение на предложения средствами NLTK.
|
||||
4. Частеречная разметка Penn Treebank с переводом в Universal POS.
|
||||
5. Схлопывание подряд идущих знаков препинания в один тег `PUNCT` для
|
||||
частеречных последовательностей.
|
||||
|
||||
Не выполнялись: удаление стоп-слов, удаление пунктуации, лемматизация,
|
||||
удаление коротких реплик. Эти элементы рассматриваются как стилометрический
|
||||
сигнал.
|
||||
|
||||
### 3.2. Признаковое пространство
|
||||
|
||||
Ручные признаки сгруппированы в 11 семейств; всего около 454 признака.
|
||||
|
||||
| Группа | Примеры | Статус |
|
||||
|---|---|---|
|
||||
| Длина | число токенов, слов, символов, предложений; длина предложения | Основная, но чувствительная к длине |
|
||||
| Лексика | распределение длин слов, доля слов с апострофом, заглавных, цифр | Основная |
|
||||
| Словарное богатство | TTR, Guiraud, Herdan, Yule's K, Simpson's D, hapax/dis, Honoré | Предварительная |
|
||||
| Символы | частоты букв, регистр, цифры, пунктуация | Основная |
|
||||
| Пунктуация | запятые, точки, восклицательные, вопросительные, многоточия | Основная |
|
||||
| Функциональные слова | около 150 закрытых слов и 12 категорий | Основная |
|
||||
| Местоимения | 1-е, 2-е, 3-е лицо, возвратные, притяжательные | Основная |
|
||||
| Части речи | Universal POS, Penn Treebank, время, модальность, герундий | Основная |
|
||||
| Синтаксис-прокси | число клауз, подчинительные союзы, вопросы, пассив-прокси | Основная |
|
||||
| Читаемость | Flesch, FK grade, Gunning Fog, SMOG, Coleman-Liau, ARI | Предварительная |
|
||||
| Разговорные маркеры | междометия, смех, хеджи, интенсификаторы, вежливость, сокращения | Основная |
|
||||
|
||||
Дополнительно используются:
|
||||
|
||||
- TF-IDF по символьным n-граммам (`char_wb`, 2–5, до 5000–10000 признаков);
|
||||
- TF-IDF по последовательностям Universal POS (1–3, до 500 признаков);
|
||||
- LSA по вариантам `word`, `word2`, `content`, `fw`, `char`, `pos`.
|
||||
|
||||
### 3.3. Латентно-семантический анализ
|
||||
|
||||
LSA реализован как последовательность:
|
||||
|
||||
```text
|
||||
TF-IDF -> усечённое сингулярное разложение (TruncatedSVD) -> L2-нормализация
|
||||
```
|
||||
|
||||
Варианты:
|
||||
|
||||
| Вариант | Что отражает |
|
||||
|---|---|
|
||||
| `word` | лексику и тему |
|
||||
| `word2` | лексику и устойчивые сочетания |
|
||||
| `content` | содержание после удаления стоп-слов |
|
||||
| `fw` | функциональные слова и синтаксис |
|
||||
| `char` | морфологию и пунктуацию |
|
||||
| `pos` | частеречные последовательности |
|
||||
|
||||
Число компонент подбиралось по объяснённой дисперсии и внешней метрике.
|
||||
Для оценки использовались 50–400 компонент; для `content`-LSA наблюдался
|
||||
монотонный рост качества до 400 компонент.
|
||||
|
||||
### 3.4. Классификаторы
|
||||
|
||||
| Модель | Краткая характеристика |
|
||||
|---|---|
|
||||
| Dummy | нижняя граница; предсказывает самый частый класс |
|
||||
| MultinomialNB | вероятностная модель для неотрицательных частотных признаков |
|
||||
| LogisticRegression | линейная модель с L2-регуляризацией и весами классов |
|
||||
| LinearSVC | линейный метод опорных векторов с максимизацией зазора |
|
||||
| RandomForest | ансамбль решающих деревьев |
|
||||
| ExtraTrees | ансамбль деревьев со случайными порогами |
|
||||
| kNN | классификация по ближайшим объектам с косинусным расстоянием |
|
||||
| Delta | дельта Барроуза по наиболее частотным словам |
|
||||
|
||||
Для логистической регрессии, SVM и деревьев использовались веса классов.
|
||||
Для MultinomialNB LSA-блоки исключались, поскольку компоненты SVD могут быть
|
||||
отрицательными.
|
||||
|
||||
### 3.5. Схема валидации
|
||||
|
||||
- Основная схема: `RepeatedStratifiedKFold(n_splits=5, n_repeats=3–5)`.
|
||||
- Все обучаемые преобразования (TF-IDF, SVD, масштабирование) настраивались
|
||||
только на обучающих блоках.
|
||||
- Для настройки гиперпараметров использовалась вложенная перекрёстная
|
||||
проверка: внешние 5 блоков, внутренние 3 блока.
|
||||
- Дополнительно выполнены:
|
||||
- вложенный выбор конфигурации признаков и модели;
|
||||
- стратифицированная отложенная выборка 80/20;
|
||||
- контроль перестановкой меток;
|
||||
- анализ почти-дублей;
|
||||
- маскирование содержательных слов;
|
||||
- коррекция априорных вероятностей классов;
|
||||
- мягкое голосование и стековое объединение.
|
||||
|
||||
### 3.6. Метрики
|
||||
|
||||
Основная метрика — macro-F1. Дополнительно использовались:
|
||||
|
||||
- доля правильных ответов (accuracy);
|
||||
- сбалансированная доля правильных ответов;
|
||||
- коэффициент корреляции Мэтьюса (MCC);
|
||||
- каппа Коэна;
|
||||
- точность попадания истинного класса в тройку и пятёрку лучших;
|
||||
- логарифмическая потеря и оценка Брайера для вероятностных моделей;
|
||||
- точность, полнота и F-мера по каждому классу;
|
||||
- матрица ошибок;
|
||||
- бутстрэп-интервалы;
|
||||
- критерий Мак-Немара, критерий Уилкоксона, критерий Фридмана и поправка
|
||||
Холма—Бонферрони.
|
||||
|
||||
Случайный уровень для 34 классов: macro-F1 ≈ 0,029, top-3 ≈ 0,088,
|
||||
top-5 ≈ 0,147.
|
||||
|
||||
---
|
||||
|
||||
## 4. Эксперименты
|
||||
|
||||
### 4.1. Исходная серия
|
||||
|
||||
Исходная серия включала:
|
||||
|
||||
- нижние границы: длина, символьные n-граммы, POS, функциональные слова;
|
||||
- ручную стилометрию;
|
||||
- объединение ручных признаков с символьными и частеречными n-граммами;
|
||||
- варианты LSA;
|
||||
- сравнение классификаторов;
|
||||
- поочерёдное исключение групп признаков;
|
||||
- дельту Барроуза;
|
||||
- верификацию «свой/чужой»;
|
||||
- кластеризацию;
|
||||
- анализ длины и подгрупп.
|
||||
|
||||
### 4.2. Follow-up серия коллеги
|
||||
|
||||
В follow-up серии было выполнено 68 запусков. Основные результаты:
|
||||
|
||||
| Конфигурация | Macro-F1 | Доля правильных ответов | Top-3 |
|
||||
|---|---:|---:|---:|
|
||||
| `char_max10000_rf` | 0,2583 ± 0,0066 | 0,2391 | 0,4197 |
|
||||
| `model_et_default` | 0,2551 ± 0,0073 | 0,2387 | 0,4170 |
|
||||
| `groups_lean2_content300` | 0,2510 ± 0,0040 | 0,2379 | 0,4111 |
|
||||
| `stylometry_pos_content300` | 0,2485 ± 0,0042 | 0,2337 | 0,4175 |
|
||||
| `ref_logreg` | 0,1785 ± 0,0060 | 0,1679 | 0,3322 |
|
||||
| `stylometry` | 0,1051 ± 0,0038 | 0,1012 | 0,2338 |
|
||||
| `lsa_content_k400` | 0,2114 ± 0,0029 | 0,2059 | 0,3767 |
|
||||
| `lsa_pos_k300` | 0,0667 ± 0,0040 | 0,0663 | 0,1556 |
|
||||
|
||||
Контроль перестановкой меток дал 0,0270–0,0282, то есть случайный уровень.
|
||||
Почти-дубликаты (13 пар с косинусной близостью ≥ 0,8) оказались внутри одного
|
||||
персонажа; межклассовых почти-дублей не обнаружено.
|
||||
|
||||
### 4.3. Новые эксперименты
|
||||
|
||||
#### 4.3.1. Вложенный выбор конфигурации
|
||||
|
||||
Во внешнем цикле из 5 блоков и внутреннем цикле из 3 блоков сравнивались
|
||||
восемь конфигураций:
|
||||
|
||||
- ExtraTrees на полном наборе;
|
||||
- ExtraTrees без LSA;
|
||||
- ExtraTrees с lean-набором и `content`-LSA (300 и 400 компонент);
|
||||
- логистическая регрессия с lean-набором и `content`-LSA(300);
|
||||
- случайный лес с `char_max_features` 10000 и 15000;
|
||||
- ExtraTrees с `char_max_features=10000`.
|
||||
|
||||
Выбор выполнялся только по внутренним блокам. Результат:
|
||||
|
||||
- macro-F1 = **0,2746 ± 0,0233**;
|
||||
- в 14 из 15 внешних блоков выбрана конфигурация ExtraTrees с
|
||||
`char_max_features=10000`;
|
||||
- в 1 из 15 — ExtraTrees с lean-набором и `content`-LSA(400).
|
||||
|
||||
Это наиболее консервативная оценка, поскольку конфигурация выбирается внутри
|
||||
обучающей части каждого внешнего блока.
|
||||
|
||||
#### 4.3.2. Финальные кандидаты
|
||||
|
||||
| Конфигурация | Macro-F1 | Доля правильных ответов | Top-3 |
|
||||
|---|---:|---:|---:|
|
||||
| ExtraTrees, `char_max_features=10000` | **0,2757 ± 0,0219** | 0,2633 | 0,4554 |
|
||||
| ExtraTrees, полный набор | 0,2499 ± 0,0209 | 0,2374 | 0,4234 |
|
||||
| ExtraTrees, lean + content-LSA(300) | 0,2539 ± 0,0237 | 0,2354 | 0,4339 |
|
||||
| ExtraTrees, lean + content-LSA(400) | 0,2511 ± 0,0212 | 0,2279 | 0,4219 |
|
||||
| LogisticRegression, lean + content-LSA(300) | 0,2466 ± 0,0191 | 0,2349 | 0,4030 |
|
||||
|
||||
#### 4.3.3. Мягкое голосование и стековое объединение
|
||||
|
||||
Ансамбль из трёх моделей:
|
||||
|
||||
- ExtraTrees на полном наборе;
|
||||
- ExtraTrees с `char_max_features=10000`;
|
||||
- логистическая регрессия с lean-набором и `content`-LSA(300).
|
||||
|
||||
Результат мягкого голосования:
|
||||
|
||||
- macro-F1 = **0,3036 ± 0,0189**;
|
||||
- доля правильных ответов = 0,2808;
|
||||
- top-3 = 0,4823.
|
||||
|
||||
Вариант с четырьмя моделями (добавлен случайный лес с
|
||||
`char_max_features=10000`) дал 0,2983 ± 0,0187; статистически он не лучше
|
||||
трёхмодельного варианта.
|
||||
|
||||
Стековое объединение с мета-классификатором логистической регрессии дало
|
||||
0,2259 ± 0,0163, то есть заметно хуже мягкого голосования. Это указывает на
|
||||
переобучение мета-классификатора при малом числе объектов.
|
||||
|
||||
#### 4.3.4. Контроль содержания
|
||||
|
||||
Для проверки того, обусловлен ли результат темой, содержательные слова
|
||||
(существительные, глаголы, прилагательные, наречия, числительные) были
|
||||
заменены на нейтральный маркер `CONTENT`. Служебные слова, пунктуация и
|
||||
частеречные теги сохранялись.
|
||||
|
||||
| Условие | Модель | Macro-F1 |
|
||||
|---|---|---:|
|
||||
| Исходный текст, стилевой набор | ExtraTrees | 0,2483 ± 0,0201 |
|
||||
| Маскированный текст, стилевой набор | ExtraTrees | 0,1129 ± 0,0162 |
|
||||
| Исходный текст, стилевой набор | LogisticRegression | 0,1467 ± 0,0213 |
|
||||
| Маскированный текст, стилевой набор | LogisticRegression | 0,1067 ± 0,0125 |
|
||||
|
||||
Снижение для ExtraTrees составляет около 0,135 macro-F1, то есть более
|
||||
половины качества. Стилевой сигнал после маскирования сохраняется, но он
|
||||
существенно слабее содержательного.
|
||||
|
||||
#### 4.3.5. Коррекция априорных вероятностей
|
||||
|
||||
Проверена коррекция вероятностей вида
|
||||
|
||||
\[
|
||||
p'_{ic} \propto p_{ic} \cdot \pi_c^{-\alpha},
|
||||
\]
|
||||
|
||||
где \(\pi_c\) — априорная частота класса. Параметр \(\alpha\) выбирался по
|
||||
схеме «один повтор — отложенный, остальные — обучающие».
|
||||
|
||||
Среднее качество:
|
||||
|
||||
- без коррекции: 0,2529;
|
||||
- с коррекцией: 0,2507;
|
||||
- среднее изменение: −0,0022.
|
||||
|
||||
Простая коррекция априорных вероятностей не улучшает macro-F1. Это означает,
|
||||
что проблема редких классов не решается только изменением приоров; нужны
|
||||
более тонкие правила принятия решений или дополнительные данные.
|
||||
|
||||
#### 4.3.6. Дополнительные конфигурации
|
||||
|
||||
| Конфигурация | Macro-F1 |
|
||||
|---|---:|
|
||||
| RandomForest, `char_max_features=15000` | 0,2570 ± 0,0139 |
|
||||
| LogisticRegression, lean + content-LSA(400) | 0,2532 ± 0,0188 |
|
||||
| LogisticRegression, lean + content-LSA(500) | 0,2542 ± 0,0180 |
|
||||
| ExtraTrees с весами блоков | 0,2478 ± 0,0208 |
|
||||
|
||||
Увеличение символьного словаря с 10000 до 15000 не дало прироста.
|
||||
Увеличение числа компонент `content`-LSA с 300 до 500 также не дало
|
||||
устойчивого улучшения. Веса блоков не улучшили результат.
|
||||
|
||||
#### 4.3.7. Отложенная выборка
|
||||
|
||||
На стратифицированной отложенной выборке 80/20 (401 объект):
|
||||
|
||||
| Модель | Macro-F1 |
|
||||
|---|---:|
|
||||
| Мягкое голосование трёх моделей | **0,3333** |
|
||||
| ExtraTrees, полный набор | 0,2793 |
|
||||
| RandomForest, `char_max_features=10000` | 0,2554 |
|
||||
| LogisticRegression, lean + content-LSA(300) | 0,2459 |
|
||||
|
||||
Отложенная выборка даёт более высокие значения, чем перекрёстная проверка,
|
||||
но это одно разбиение; его интервал широкий, поэтому результаты нельзя
|
||||
считать окончательными.
|
||||
|
||||
---
|
||||
|
||||
## 5. Результаты и обсуждение
|
||||
|
||||
### 5.1. Основной результат
|
||||
|
||||
Персонажи статистически различимы. Лучшая оценка по перекрёстной проверке —
|
||||
0,304 macro-F1 для мягкого голосования; консервативная оценка с вложенным
|
||||
выбором конфигурации — 0,275. Случайный уровень — 0,029.
|
||||
|
||||
Для интерпретации удобно перевести macro-F1 в кратность превышения
|
||||
случайного уровня:
|
||||
|
||||
- 0,275 / 0,029 ≈ 9,5;
|
||||
- 0,304 / 0,029 ≈ 10,5.
|
||||
|
||||
Это заметный сигнал, но не достаточный для практической системы
|
||||
автоматического определения персонажа по одной реплике.
|
||||
|
||||
### 5.2. Что даёт основной вклад
|
||||
|
||||
Сравнение условий:
|
||||
|
||||
| Условие | Macro-F1 |
|
||||
|---|---:|
|
||||
| Только длина | 0,035 |
|
||||
| Только ручные стилометрические признаки | 0,105 |
|
||||
| Ручные + символьные и частеречные n-граммы | 0,182 |
|
||||
| То же + word/pos-LSA(100) | 0,1785 |
|
||||
| То же + content-LSA(300) | 0,2485 |
|
||||
| ExtraTrees с расширенным символьным словарём | 0,276 |
|
||||
| Мягкое голосование | 0,304 |
|
||||
|
||||
Основной прирост дают:
|
||||
|
||||
1. символьные n-граммы;
|
||||
2. `content`-LSA, то есть содержание и тема;
|
||||
3. нелинейная модель (деревья) для совместного использования признаков.
|
||||
|
||||
Функциональные слова и POS-LSA сами по себе слабы. Это важный
|
||||
отрицательный результат: «чистый синтаксис» в текущем виде недостаточен.
|
||||
|
||||
### 5.3. Стиль или содержание
|
||||
|
||||
Контроль содержания показывает:
|
||||
|
||||
- исходный стилевой набор: 0,2483;
|
||||
- после маскирования содержательных слов: 0,1129.
|
||||
|
||||
Следовательно, более половины качества связано с лексическим содержанием.
|
||||
Остающийся сигнал 0,113 в 3,9 раза выше случайного уровня 0,029, то есть
|
||||
некоторый стилевой компонент присутствует, но он существенно слабее
|
||||
тематического.
|
||||
|
||||
Корректная формулировка:
|
||||
|
||||
> Персонажи различимы по лексико-тематическому профилю и символьным
|
||||
> n-граммам; наличие собственно стилистического сигнала подтверждается лишь
|
||||
> частично и требует дальнейшего тематического контроля.
|
||||
|
||||
### 5.4. Ошибки модели
|
||||
|
||||
Для лучшей одиночной модели ExtraTrees с `char_max_features=10000`
|
||||
(первый повтор, 2005 реплик):
|
||||
|
||||
- Abigail получает 17,7% всех предсказаний при истинной доле 6,9%;
|
||||
precision 0,099, recall 0,252, F1 0,142;
|
||||
- худшие F1: Kent 0,000, Wizard 0,000, Penny 0,021, Mister Qi 0,087,
|
||||
Leah 0,110, Abigail 0,142, Linus 0,146, Jodi 0,147;
|
||||
- лучшие F1: Evelyn 0,646, Willy 0,581, Pam 0,522, Pierre 0,513,
|
||||
Leo 0,508, Vincent 0,438.
|
||||
|
||||
Мягкое голосование улучшает часть редких классов: Wizard F1 возрастает до
|
||||
0,30, Dwarf — до 0,30, Vincent — до 0,46. Однако Kent по-прежнему имеет
|
||||
F1 = 0, а Abigail остаётся «hub»-классом.
|
||||
|
||||
Основные потоки ошибок: Sebastian → Abigail (31), Sam → Abigail (22),
|
||||
Alex → Abigail (22), Elliott → Abigail (21). Это не демографическая
|
||||
путаница, а коллапс редких классов в несколько популярных.
|
||||
|
||||
### 5.5. Длина реплики
|
||||
|
||||
Для ExtraTrees с расширенным символьным словарём:
|
||||
|
||||
| Длина | Macro-F1 |
|
||||
|---|---:|
|
||||
| 0–5 слов | 0,097 |
|
||||
| 6–10 | 0,244 |
|
||||
| 11–15 | 0,233 |
|
||||
| 16–20 | 0,239 |
|
||||
| 21–30 | **0,295** |
|
||||
| 31+ | 0,271 |
|
||||
|
||||
Зависимость немонотонная: максимум приходится на 21–30 слов, после 30 слов
|
||||
качество снижается. Гипотеза «чем длиннее, тем лучше» не подтверждается.
|
||||
|
||||
### 5.6. Подгруппы
|
||||
|
||||
Для ExtraTrees с расширенным символьным словарём:
|
||||
|
||||
- возраст: elder 0,440, child 0,424, adult 0,249, teen 0,201;
|
||||
- пол: male 0,282, unknown 0,242, female 0,236;
|
||||
- вид: monster 0,287, human 0,263, unknown 0,033.
|
||||
|
||||
Это F1 внутри группы персонажей, а не качество предсказания группы.
|
||||
Различия описательные; они не доказывают, что модель обязана путать
|
||||
персонажей внутри группы.
|
||||
|
||||
### 5.7. Верификация и кластеризация
|
||||
|
||||
- Верификация: ROC-AUC 0,630–0,633, EER 0,412–0,415, top-1 0,133–0,141.
|
||||
- Кластеризация: ARI = 0, NMI 0,14–0,53 для k = 3/5/8.
|
||||
|
||||
Обе постановки показывают, что в признаковом пространстве нет устойчивых
|
||||
кластеров, соответствующих персонажам. Это ограничивает интерпретацию
|
||||
результатов классификации.
|
||||
|
||||
### 5.8. Статистическая надёжность
|
||||
|
||||
Мягкое голосование статистически лучше одиночной модели ExtraTrees с
|
||||
`char_max_features=10000` на общих повторах:
|
||||
|
||||
| Повтор | Разница macro-F1 | McNemar p |
|
||||
|---|---:|---:|
|
||||
| 0 | +0,0287 | 0,012 |
|
||||
| 1 | +0,0380 | < 0,00001 |
|
||||
| 2 | +0,0163 | 0,0012 |
|
||||
|
||||
Разница положительна во всех трёх повторах. Однако состав ансамбля
|
||||
выбирался по предыдущим результатам, поэтому окончательная оценка должна
|
||||
быть подтверждена внешней выборкой.
|
||||
|
||||
При 46 парных сравнениях необходима поправка Холма—Бонферрони. Эффекты
|
||||
+0,04…+0,08 её переживают; малые различия +0,003…+0,009 — нет.
|
||||
|
||||
---
|
||||
|
||||
## 6. Ограничения
|
||||
|
||||
1. **Один корпус.** Все результаты получены на 2005 репликах одной игры.
|
||||
Внешняя валидация на другом корпусе не проводилась.
|
||||
2. **Один автор.** Работа измеряет различимость речевых масок, а не
|
||||
авторскую атрибуцию.
|
||||
3. **Выбор признаков.** `char_max_features=10000`, `content`-LSA(k=300–400) и
|
||||
lean-наборы выбирались по результатам на тестовых блоках предыдущих серий.
|
||||
Вложенный выбор конфигурации снижает этот риск (0,275), но не устраняет
|
||||
его полностью.
|
||||
4. **Дисбаланс классов.** От 15 до 139 реплик; редкие классы имеют широкие
|
||||
интервалы, а hub-классы забирают чужие предсказания.
|
||||
5. **Короткие тексты.** Признаки словарного богатства и читаемости
|
||||
нестабильны.
|
||||
6. **Тематический конфликт.** `content`-LSA и символьные n-граммы могут
|
||||
кодировать тему, сезоны, подарки, работу и имена. Маскирование содержания
|
||||
подтверждает сильный тематический вклад.
|
||||
7. **Нет идентификаторов сцен.** Разбиение по отдельным репликам не
|
||||
учитывает корреляцию внутри сцен и диалогов.
|
||||
8. **Статистическая мощность.** При 34 классах и 2005 объектах даже
|
||||
доверительные интервалы по редким классам остаются широкими.
|
||||
9. **Калибровка.** Простая коррекция априорных вероятностей не улучшила
|
||||
macro-F1; более сложные методы (temperature scaling, оптимизация порогов)
|
||||
не проверялись.
|
||||
10. **Стековое объединение** показало худший результат, вероятно, из-за
|
||||
переобучения мета-классификатора на малом числе объектов.
|
||||
|
||||
---
|
||||
|
||||
## 7. Выводы
|
||||
|
||||
1. **Гипотеза о различимости персонажей подтверждается частично.**
|
||||
Консервативная оценка — 0,275 macro-F1; лучшая наблюдаемая — 0,304.
|
||||
Случайный уровень — 0,029.
|
||||
|
||||
2. **Основной сигнал — лексико-тематический.** Символьные n-граммы и
|
||||
`content`-LSA дают основной прирост; функциональные слова и POS-LSA сами
|
||||
по себе слабы. Маскирование содержательных слов снижает качество более
|
||||
чем вдвое.
|
||||
|
||||
3. **Наиболее устойчивая одиночная модель — ExtraTrees с
|
||||
`char_max_features=10000`.** Она выбрана в 14 из 15 внешних блоков
|
||||
вложенной процедуры и даёт 0,2757 ± 0,0219.
|
||||
|
||||
4. **Мягкое голосование трёх моделей — лучший наблюдаемый результат:**
|
||||
0,3036 ± 0,0189 при перекрёстной проверке и 0,3333 на отложенной
|
||||
выборке. Однако состав ансамбля выбирался по предыдущим данным; для
|
||||
строгого вывода нужна внешняя валидация.
|
||||
|
||||
5. **`content`-LSA — сильный, но тематический сигнал.** Рост до k=400
|
||||
устойчив, но интерпретировать его как «стиль» нельзя без тематического
|
||||
контроля.
|
||||
|
||||
6. **Главная проблема — редкие классы и hub-коллапс.** Abigail получает
|
||||
21–23% предсказаний при истинной доле 6,9%; Kent, Wizard, Penny и
|
||||
Mister Qi почти не находятся. Простая коррекция приоров не помогает.
|
||||
|
||||
7. **Для строгого стилометрического вывода необходимы:**
|
||||
внешний корпус, тематически сбалансированные подвыборки, вложенный выбор
|
||||
признаков и более тонкая калибровка решающих правил.
|
||||
|
||||
---
|
||||
|
||||
## 8. Воспроизводимость
|
||||
|
||||
### 8.1. Основные документы
|
||||
|
||||
- `README.md` — установка и быстрый старт;
|
||||
- `RUNBOOK.md` — пошаговая инструкция для полной серии;
|
||||
- `EXPERIMENTS.md` — протокол экспериментов;
|
||||
- `FEATURES.md` — описание признаков;
|
||||
- `METRICS_AND_CLASSIFIERS.md` — описание метрик и классификаторов;
|
||||
- `TUNING.md` — параметры и порядок настройки;
|
||||
- `FOLLOWUP_EXPERIMENTS.md` — follow-up серия коллеги;
|
||||
- `RESULTS_ANALYSIS.md` — анализ follow-up результатов;
|
||||
- `FINAL_RESULTS.csv` — сводная таблица ключевых результатов;
|
||||
- этот отчёт — `FINAL_REPORT.md`.
|
||||
|
||||
### 8.2. Команды
|
||||
|
||||
```bash
|
||||
cd lab1
|
||||
|
||||
# базовая серия
|
||||
bash scripts/run_all_experiments.sh
|
||||
|
||||
# follow-up серия
|
||||
python scripts/run_followup_experiments.py # если доступен в ветке результатов
|
||||
# или
|
||||
bash scripts/run_followup_experiments.sh
|
||||
|
||||
# новые эксперименты
|
||||
python scripts/run_final_experiments.py \
|
||||
--stages nested topic calibration ensemble extra \
|
||||
--out results/final
|
||||
|
||||
# финалисты и ансамбли
|
||||
python scripts/run_finalists.py
|
||||
```
|
||||
|
||||
### 8.3. Артефакты
|
||||
|
||||
- `lab1/exports/nlp_lab1_results_20261026_054117.tar.gz` — архив follow-up
|
||||
серии;
|
||||
- `results/final/` — новые эксперименты;
|
||||
- `results/final/nested_3repeats/` — вложенный выбор конфигурации;
|
||||
- `results/final/finalists/` — финалисты и ансамбли;
|
||||
- `results/final/topic_control/` — контроль содержания;
|
||||
- `results/final/calibration/` — коррекция приоров;
|
||||
- `results/final/extra/` — дополнительные конфигурации и отложенная выборка.
|
||||
|
||||
---
|
||||
|
||||
## 9. Направления дальнейшей работы
|
||||
|
||||
1. **Внешняя валидация** на другом корпусе, другой игре или другом разбиении
|
||||
по сценам и диалогам.
|
||||
2. **Тематический контроль:** сбалансированные по темам подвыборки, удаление
|
||||
тематических слов, adversarial-классификатор темы.
|
||||
3. **Вложенный выбор признаков и ансамблей** внутри внешнего цикла, включая
|
||||
`char_max_features`, `content`-LSA(k) и состав ансамбля.
|
||||
4. **Калибровка редких классов:** temperature scaling, bias correction,
|
||||
оптимизация порогов и правил декодирования по macro-F1.
|
||||
5. **Ансамбли:** мягкое голосование с весами, stacking с регуляризацией,
|
||||
отбор базовых моделей по внутренней проверке.
|
||||
6. **Расширение корпуса:** больше реплик на редких персонажей, разметка сцен
|
||||
и диалогов, возможно, другие игры или авторы для внешней проверки.
|
||||
7. **Улучшение частеречной разметки:** сравнение теггеров, ручная проверка
|
||||
ошибок на разговорных конструкциях.
|
||||
8. **Содержательная интерпретация:** анализ тем, характерных для персонажей,
|
||||
и отделение тематического сигнала от собственно стилистического.
|
||||
|
||||
---
|
||||
|
||||
## Приложение A. Сводная таблица результатов
|
||||
|
||||
См. `FINAL_RESULTS.csv`.
|
||||
|
||||
## Приложение B. Основные метрики
|
||||
|
||||
См. `METRICS_AND_CLASSIFIERS.md`.
|
||||
|
||||
## Приложение C. Признаки
|
||||
|
||||
См. `FEATURES.md` и `features/feature_dictionary.csv`.
|
||||
|
||||
## Приложение D. Протокол экспериментов
|
||||
|
||||
См. `EXPERIMENTS.md` и `FOLLOWUP_EXPERIMENTS.md`.
|
||||
@@ -1,30 +0,0 @@
|
||||
stage,label,model,folds,repeats,macro_f1_mean,macro_f1_std,accuracy,top3,note
|
||||
previous,ref_logreg,logreg,5,5,0.1785,0.0060,0.1679,0.3322,базовый объединённый набор
|
||||
previous,ref_no_lsa,logreg,5,5,0.1815,0.0054,0.1708,0.3395,без LSA
|
||||
previous,stylometry,logreg,5,5,0.1051,0.0038,0.1012,0.2338,только ручные признаки
|
||||
previous,char_max10000_rf,random_forest,5,5,0.2583,0.0066,0.2391,0.4197,предыдущий максимум
|
||||
previous,model_et_default,extra_trees,5,5,0.2551,0.0073,0.2387,0.4170,деревья по умолчанию
|
||||
previous,groups_lean2_content300,logreg,5,5,0.2510,0.0040,0.2379,0.4111,lean + content-LSA
|
||||
previous,lsa_content_k400,logreg,5,5,0.2114,0.0029,0.2059,0.3767,только content-LSA
|
||||
previous,lsa_pos_k300,logreg,5,5,0.0667,0.0040,0.0663,0.1556,только POS-LSA
|
||||
nested,nested_feature_selection,mixed,5,3,0.2746,0.0233,0.2603,NA,выбор конфигурации внутри outer-фолдов
|
||||
finalists,et_char10000,extra_trees,5,5,0.2757,0.0219,0.2633,0.4554,char_max_features=10000
|
||||
finalists,et_full,extra_trees,5,5,0.2499,0.0209,0.2374,0.4234,полный набор
|
||||
finalists,et_lean_content300,extra_trees,5,5,0.2539,0.0237,0.2354,0.4339,lean + content-LSA 300
|
||||
finalists,et_lean_content400,extra_trees,5,5,0.2511,0.0212,0.2279,0.4219,lean + content-LSA 400
|
||||
finalists,logreg_lean_content300,logreg,5,5,0.2466,0.0191,0.2349,0.4030,lean + content-LSA 300
|
||||
ensemble,voting_et_char_lean,soft_voting,5,3,0.3036,0.0189,0.2808,0.4823,ET full + ET char10000 + logreg lean content300
|
||||
ensemble,voting_four,soft_voting,5,3,0.2983,0.0187,0.2823,0.4813,то же + RF char10000
|
||||
ensemble,stacking,stacking,5,1,0.2259,0.0163,0.2264,0.3865,мета-классификатор logreg
|
||||
topic,topic_original_et,extra_trees,5,3,0.2483,0.0201,0.2379,0.4208,исходный текст; стилевой набор
|
||||
topic,topic_masked_et,extra_trees,5,3,0.1129,0.0162,0.1220,0.2683,содержательные слова заменены на CONTENT
|
||||
topic,topic_original_logreg,logreg,5,3,0.1467,0.0213,0.1440,0.3017,исходный текст; стилевой набор
|
||||
topic,topic_masked_logreg,logreg,5,3,0.1067,0.0125,0.1046,0.2362,содержательные слова заменены на CONTENT
|
||||
extra,rf_char15000,random_forest,5,3,0.2570,0.0139,0.2468,NA,увеличение char-словаря не даёт прироста
|
||||
extra,logreg_lean_content400,logreg,5,3,0.2532,0.0188,0.2389,NA,content-LSA 400
|
||||
extra,logreg_lean_content500,logreg,5,3,0.2542,0.0180,0.2401,NA,content-LSA 500
|
||||
extra,et_block_weights,extra_trees,5,3,0.2478,0.0208,0.2366,NA,веса блоков не улучшают
|
||||
holdout,voting_holdout,soft_voting,1,1,0.3333,NA,0.2968,NA,стратифицированная отложенная выборка 80/20
|
||||
holdout,et_full_holdout,extra_trees,1,1,0.2793,NA,0.2469,NA,стратифицированная отложенная выборка 80/20
|
||||
holdout,rf_char10000_holdout,random_forest,1,1,0.2554,NA,0.2369,NA,стратифицированная отложенная выборка 80/20
|
||||
holdout,logreg_lean_content300_holdout,logreg,1,1,0.2459,NA,0.2269,NA,стратифицированная отложенная выборка 80/20
|
||||
|
@@ -1,636 +0,0 @@
|
||||
# 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`:
|
||||
|
||||
```bash
|
||||
# Полная 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 |
|
||||
|
||||
Результаты:
|
||||
|
||||
```text
|
||||
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
|
||||
```
|
||||
|
||||
Сводка пересчитывается отдельно:
|
||||
|
||||
```bash
|
||||
.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 внутри группы
|
||||
персонажей, а не качество предсказания самой группы.
|
||||
@@ -136,51 +136,6 @@ python -m stylometry.cli subgroup-report \
|
||||
| `lsa_word`, `lsa_pos` | только соответствующий вариант LSA |
|
||||
| `length` | только признаки длины (нижняя граница) |
|
||||
|
||||
## Отчёт
|
||||
|
||||
Итоговый отчёт:
|
||||
|
||||
- **`report/REPORT.pdf`** — основной документ, 21 страница;
|
||||
- **`report/REPORT.docx`** — редактируемая версия;
|
||||
- **`report/REPORT.md`** — исходник Markdown;
|
||||
- **`report/figures/`** — рисунки.
|
||||
|
||||
Пересборка:
|
||||
|
||||
```bash
|
||||
python scripts/build_report.py
|
||||
```
|
||||
|
||||
Для сборки нужны `pandoc`, LibreOffice и пакеты из
|
||||
`requirements-report.txt`. Скрипт сам оформляет титул, колонтитулы, таблицы,
|
||||
нумерацию рисунков и проверяет, что в PDF есть все разделы.
|
||||
|
||||
## Презентация
|
||||
|
||||
Есть две версии:
|
||||
|
||||
- **`presentation/stardew_stylometry.pptx`** — редактируемая PPTX-версия
|
||||
(30 слайдов после ручных правок);
|
||||
- **`presentation/stardew_stylometry.pdf`** — PDF этой версии;
|
||||
- **`presentation/latex/stardew_stylometry.tex`** — академическая
|
||||
Beamer-версия;
|
||||
- **`presentation/latex/stardew_stylometry.pdf`** — собранный PDF LaTeX-версии
|
||||
(30 страниц).
|
||||
|
||||
LaTeX-версия содержит три части на трёх докладчиков, фон и курицу на каждом
|
||||
слайде, титульную заставку, изображение персонажей, две круговые диаграммы,
|
||||
матрицы ошибок, F-меры по персонажам, приложение с полной матрицей и финальный
|
||||
мем. Формул почти нет: одна компактная запись обозначений на слайде постановки
|
||||
задачи.
|
||||
|
||||
Сборка LaTeX:
|
||||
|
||||
```bash
|
||||
cd presentation/latex
|
||||
xelatex stardew_stylometry.tex
|
||||
xelatex stardew_stylometry.tex
|
||||
```
|
||||
|
||||
## Конфигурация
|
||||
|
||||
Файл `configs/default.toml` задаёт параметры по умолчанию:
|
||||
@@ -199,10 +154,6 @@ python -m stylometry.cli run --config configs/default.toml \
|
||||
|
||||
## Основные документы
|
||||
|
||||
- **`START_HERE.md`** — краткая инструкция: что открыть и как проверить
|
||||
окружение за три минуты.
|
||||
- **`ARCHIVE_README.md`** — подробное описание архива: состав, установка,
|
||||
запуск, структура модулей, пересборка отчёта и презентации.
|
||||
- **`RUNBOOK.md`** — пошаговая инструкция для запуска полной серии
|
||||
экспериментов: установка, проверка окружения, все команды, упаковка
|
||||
результатов и типичные ошибки.
|
||||
@@ -213,14 +164,6 @@ python -m stylometry.cli run --config configs/default.toml \
|
||||
выбора; дополняет машинно-читаемый `features/feature_dictionary.csv`.
|
||||
- **`TUNING.md`** — где менять параметры классификаторов, признаки, LSA и веса
|
||||
блоков; рекомендуемый порядок улучшения результатов.
|
||||
- **`RESULTS_ANALYSIS.md`** — независимый анализ follow-up результатов:
|
||||
ранжирование моделей, статистические сравнения, ошибки, ограничения и
|
||||
выводы.
|
||||
- **`FINAL_REPORT.md`** — итоговый отчёт по всей работе: методика, все серии
|
||||
экспериментов, результаты, обсуждение и выводы.
|
||||
- **`FINAL_RESULTS.csv`** — сводная таблица ключевых результатов.
|
||||
- **`FOLLOWUP_EXPERIMENTS.md`** — протокол и результаты follow-up серии,
|
||||
подготовленные коллегой.
|
||||
- **`METRICS_AND_CLASSIFIERS.md`** — методическое описание метрик и
|
||||
классификаторов: что показывает каждая метрика, как она считается, как
|
||||
интерпретировать значения, чем отличаются модели и как объяснять это на
|
||||
|
||||
@@ -1,389 +0,0 @@
|
||||
# Анализ результатов follow-up серии
|
||||
|
||||
Дата анализа: 26.10.2026.
|
||||
|
||||
> Дополнительные эксперименты, вложенный выбор конфигурации, контроль
|
||||
> содержания, калибровка и ансамбли описаны в итоговом отчёте
|
||||
> `FINAL_REPORT.md`; сводная таблица — в `FINAL_RESULTS.csv`.
|
||||
|
||||
Источник результатов:
|
||||
|
||||
- ветка `origin/opencode_test`, коммит `ac7c6f0`;
|
||||
- основной архив `lab1/exports/nlp_lab1_results_20261026_054117.tar.gz`;
|
||||
- сводки `results/followup/summary.csv`, `summary.md`, `comparisons.csv`,
|
||||
`comparisons.md`;
|
||||
- документ коллеги `FOLLOWUP_EXPERIMENTS.md`.
|
||||
|
||||
Контрольные суммы архивов совпали. В основном архиве — 68 запусков; ключевые
|
||||
конфигурации выполнены с 5 повторами 5-блочной стратифицированной
|
||||
перекрёстной проверки, seed 42. Дополнительно есть nested-запуски, контроли
|
||||
перестановкой меток, анализ почти-дублей, длины реплик и подгрупп.
|
||||
|
||||
Настоящий документ — независимая сводка: что можно утверждать, что нельзя,
|
||||
и какие эксперименты нужны дальше.
|
||||
|
||||
---
|
||||
|
||||
## 1. Краткий ответ
|
||||
|
||||
1. **Персонажей можно различать, но сигнал умеренный.** Лучшая конфигурация
|
||||
даёт macro-F1 около **0,258** при случайном уровне **0,029**; top-3 —
|
||||
около **0,42** при случайном **0,088**. Это в 7–9 раз выше случайного
|
||||
уровня, но не «почти безошибочная» классификация.
|
||||
2. **Основной сигнал — лексико-тематический, а не чисто стилистический.**
|
||||
Ручные стилометрические признаки дают 0,105; добавление символьных и
|
||||
частеречных n-грамм поднимает до 0,182; `content`-LSA — до 0,248–0,258.
|
||||
Функциональные слова и POS-LSA сами по себе слабы (0,06–0,07).
|
||||
3. **Лучшие кандидаты — `char_max10000_rf` и `model_et_default`.** Они
|
||||
статистически неразличимы: +0,0031, 3/5 повторов, p = 0,57,
|
||||
доверительный интервал накрывает ноль. `groups_lean2_content300` тоже
|
||||
близок (0,251), но опирается на `content`-LSA, то есть скорее на тему.
|
||||
4. **`final_rf` — не лучшая модель.** Дефолтный random forest даёт 0,222,
|
||||
тогда как ExtraTrees — 0,255, а RF с `char_max_features=10000` — 0,258.
|
||||
В отчёте `final_rf` нужно переименовать в `baseline_rf` или заменить.
|
||||
5. **Ошибки — это коллапс в «hub»-классы, а не демографическая путаница.**
|
||||
Abigail получает 21–23% всех предсказаний при истинной доле 6,9%;
|
||||
precision 0,087, recall 0,266. Редкие классы (Kent, Wizard, Penny,
|
||||
Mister Qi) почти не находятся.
|
||||
6. **Строгий вывод о стиле пока невозможен.** `content`-LSA и широкие
|
||||
символьные n-граммы могут кодировать содержание и тему. Нужен
|
||||
тематический контроль и внешняя валидация.
|
||||
|
||||
---
|
||||
|
||||
## 2. Ранжирование моделей
|
||||
|
||||
`Δ` — разница с `ref_logreg` (0,1785). Для nested-запусков указан один
|
||||
внешний цикл из 5 блоков, поэтому std по повторам равен нулю.
|
||||
|
||||
| Label | Модель | Повторов | Macro-F1 | Δ | Accuracy | Top-3 |
|
||||
|---|---|---:|---:|---:|---:|---:|
|
||||
| `char_max10000_rf` | random forest | 5 | **0,2583 ± 0,0066** | +0,0798 | 0,2391 | 0,4197 |
|
||||
| `char_max10000_rf_nested` | random forest, nested | 1×5 | **0,2579** | +0,0794 | 0,2394 | 0,4075 |
|
||||
| `groups_lean2_content300_nested` | logreg, nested | 1×5 | **0,2572** | +0,0788 | 0,2369 | 0,4065 |
|
||||
| `model_et_default` | extra trees | 5 | **0,2551 ± 0,0073** | +0,0767 | 0,2387 | 0,4170 |
|
||||
| `et_dNone_l2` | extra trees | 3 | 0,2538 ± 0,0089 | +0,0753 | 0,2416 | 0,4308 |
|
||||
| `groups_lean2_content300` | logreg | 5 | 0,2510 ± 0,0040 | +0,0725 | 0,2379 | 0,4111 |
|
||||
| `model_et_nested` | extra trees, nested | 1×5 | 0,2486 | +0,0701 | 0,2344 | 0,4259 |
|
||||
| `stylometry_pos_content300` | logreg | 5 | 0,2485 ± 0,0042 | +0,0700 | 0,2337 | 0,4175 |
|
||||
| `rf_grid_best_5` | random forest | 5 | 0,2294 ± 0,0082 | +0,0509 | 0,2253 | 0,4004 |
|
||||
| `model_rf_default` | random forest | 5 | 0,2213 ± 0,0065 | +0,0428 | 0,2108 | 0,3893 |
|
||||
| `final_rf` | random forest, 10 повторов | 10 | 0,2216 ± 0,0062 | +0,0431 | 0,2094 | 0,3865 |
|
||||
| `model_svc_default` | linear SVC | 5 | 0,1880 ± 0,0067 | +0,0096 | 0,1840 | 0,3386 |
|
||||
| `ref_no_lsa` | logreg, `stylometry_pos` | 5 | 0,1815 ± 0,0054 | +0,0031 | 0,1708 | 0,3395 |
|
||||
| `ref_logreg` | logreg, `stylometry_pos_lsa` | 5 | 0,1785 ± 0,0060 | 0 | 0,1679 | 0,3322 |
|
||||
| `stylometry` | logreg, только ручные признаки | 5 | 0,1051 ± 0,0038 | −0,0733 | 0,1012 | 0,2338 |
|
||||
| `lsa_content_k400` | logreg, только content-LSA | 5 | 0,2114 ± 0,0029 | +0,0329 | 0,2059 | 0,3767 |
|
||||
| `lsa_pos_k300` | logreg, только POS-LSA | 5 | 0,0667 ± 0,0040 | −0,1118 | 0,0663 | 0,1556 |
|
||||
| `control_length_logreg` | logreg, только длина | 5 | 0,0354 ± 0,0032 | −0,1430 | 0,0395 | 0,1345 |
|
||||
| `control_length_dummy` | dummy | 1 | 0,0038 | −0,1747 | 0,0693 | 0,1022 |
|
||||
|
||||
Случайный уровень: macro-F1 ≈ 0,029, accuracy ≈ 0,029, top-3 ≈ 0,088.
|
||||
Контроль перестановкой меток дал 0,0270–0,0282, то есть ровно случайный
|
||||
уровень. Признаков утечки через метки не видно.
|
||||
|
||||
---
|
||||
|
||||
## 3. Статистическая надёжность
|
||||
|
||||
Главные парные сравнения:
|
||||
|
||||
| Сравнение | Δ macro-F1 | Повторов в плюс | McNemar p | Bootstrap CI |
|
||||
|---|---:|---:|---:|---:|
|
||||
| `char_max10000_rf` vs `ref_logreg` | +0,0798 | 5/5 | < 0,0001 | [+0,053, +0,099] |
|
||||
| `model_et_default` vs `ref_logreg` | +0,0767 | 5/5 | < 0,0001 | [+0,054, +0,099] |
|
||||
| `groups_lean2_content300` vs `ref_logreg` | +0,0725 | 5/5 | < 0,0001 | [+0,053, +0,092] |
|
||||
| `stylometry_pos_content300` vs `ref_no_lsa` | +0,0669 | 5/5 | < 0,0001 | [+0,050, +0,083] |
|
||||
| `char_max10000_rf` vs `model_rf_default` | +0,0369 | 5/5 | 0,0032 | [+0,020, +0,053] |
|
||||
| `model_et_default` vs `model_rf_default` | +0,0338 | 5/5 | 0,0113 | [+0,018, +0,050] |
|
||||
| `char_max10000_rf` vs `model_et_default` | +0,0031 | 3/5 | 0,5732 | [−0,014, +0,021] |
|
||||
| `char_max10000_rf` vs `groups_lean2_content300` | +0,0073 | 5/5 | 0,6593 | [−0,017, +0,030] |
|
||||
|
||||
Выводы:
|
||||
|
||||
- преимущество деревьев над логистической регрессией устойчиво и велико;
|
||||
- ExtraTrees устойчиво лучше дефолтного random forest;
|
||||
- `char_max10000_rf`, ExtraTrees и `groups_lean2_content300` между собой
|
||||
статистически неразличимы;
|
||||
- малые различия (+0,003…+0,009) не переживают множественную поправку и
|
||||
не должны трактоваться как выигрыш.
|
||||
|
||||
Для 46 парных сравнений нужна поправка Холма—Бонферрони. Эффекты
|
||||
+0,04…+0,08 с p < 0,001 её переживут; пограничные +0,003…+0,009 — нет.
|
||||
|
||||
---
|
||||
|
||||
## 4. Что именно даёт качество
|
||||
|
||||
| Компонент | Macro-F1 | Интерпретация |
|
||||
|---|---:|---|
|
||||
| Только длина | 0,035 | Слабая нижняя граница |
|
||||
| Только ручные стилометрические признаки | 0,105 | Стиль/структура без лексических n-грамм |
|
||||
| Ручные + char/POS n-граммы | 0,1815 | Основной прирост +0,077 |
|
||||
| То же + word/pos-LSA(k=100) | 0,1785 | LSA по умолчанию не помогает |
|
||||
| То же + content-LSA(k=300) | 0,2485 | Сильный прирост, но это тема/лексика |
|
||||
| Только content-LSA(k=400) | 0,2114 | Контент сам по себе сильнее ручной стилометрии |
|
||||
| Только POS-LSA(k=300) | 0,0667 | Синтаксис в таком виде слаб |
|
||||
| Только функциональные слова | 0,0598 | Стиль без лексики слаб |
|
||||
| RF + char_max_features=10000 | 0,2583 | Лучший результат |
|
||||
|
||||
Это главный содержательный вывод: **модель различает персонажей в первую
|
||||
очередь по лексико-тематическому профилю и символьным n-граммам**, а не по
|
||||
«чистому» синтаксису или функциональным словам. Формулировка «модель выучила
|
||||
идиолект» слишком сильная. Корректнее: «модель различает персонажей по
|
||||
устойчивым лексическим и тематическим маркерам, которые автор закрепил за
|
||||
речевыми масками».
|
||||
|
||||
Дополнительное подтверждение: lean-набор без `function_words`, `pos`,
|
||||
`vocabulary` и `readability` даёт 0,1808, то есть **не хуже** полного
|
||||
`stylometry_pos_lsa` (0,1785). Удаление «стилевых» групп не ухудшает
|
||||
результат, потому что основной сигнал несут char- n-граммы и лексика.
|
||||
|
||||
---
|
||||
|
||||
## 5. LSA: тема или стиль
|
||||
|
||||
Зависимость от числа компонент для `content`-LSA монотонная:
|
||||
|
||||
| k | Macro-F1 |
|
||||
|---:|---:|
|
||||
| 100 | 0,1334 |
|
||||
| 200 | 0,1796 |
|
||||
| 300 | 0,1984 |
|
||||
| 400 | 0,2128 |
|
||||
|
||||
Для сравнения: `word` при k=400 — 0,1781, `char` при k=400 — 0,1787,
|
||||
`pos` — около 0,071, `fw` — около 0,074.
|
||||
|
||||
Интерпретация:
|
||||
|
||||
- `content`-LSA строится по содержательным словам и их биграммам; это
|
||||
кодирует **о чём говорит персонаж**, а не только **как**;
|
||||
- сильный рост при k=300–400 означает, что тематическая вариативность
|
||||
богата и хорошо разделяет персонажей;
|
||||
- `word+pos` LSA при k=100 была нейтральна, но это не значит, что «LSA
|
||||
вообще не работает»: важен вариант и число компонент;
|
||||
- POS-LSA при добавлении к полному набору **ухудшает** результат
|
||||
(`stylometry_pos_pos300` = 0,1685 против 0,1815), то есть синтаксический
|
||||
LSA в текущем виде шумит.
|
||||
|
||||
Пока нет тематического контроля, прирост `content`-LSA нельзя записывать в
|
||||
стилометрию. Нужны: сбалансированные по темам подвыборки, удаление
|
||||
тематических слов или adversarial-контроль темы.
|
||||
|
||||
---
|
||||
|
||||
## 6. Ошибки модели
|
||||
|
||||
Для лучшей модели `char_max10000_rf` (таблицы `per_class_report.csv` и
|
||||
`confusion_matrix.csv` относятся к repeat 0; доли предсказаний проверены по
|
||||
всем пяти повторам):
|
||||
|
||||
- Abigail получает **21–23%** всех предсказаний при истинной доле **6,9%**;
|
||||
precision 0,087, recall 0,266, F1 0,131.
|
||||
- Alex получает 11–12% при истинной доле 6,0%.
|
||||
- Худшие F1: Kent 0,000, Wizard 0,048, Penny 0,050, Mister Qi 0,069,
|
||||
Leah 0,083, Jodi 0,098, Linus 0,105.
|
||||
- Лучшие F1: Evelyn 0,645, Willy 0,578, Pam 0,534, Pierre 0,518,
|
||||
Leo 0,440, Vincent 0,393, Krobus 0,392.
|
||||
- Крупнейшие потоки ошибок: Sebastian → Abigail (40), Haley → Abigail (31),
|
||||
Alex → Abigail (28), Penny → Abigail (27), Sam → Abigail (25).
|
||||
- Это **hub-коллапс**: редкие и средние классы стягиваются в несколько
|
||||
популярных, а не путаются по возрасту или полу.
|
||||
|
||||
`groups_lean2_content300` ведёт себя иначе: Abigail получает только 3,6%
|
||||
предсказаний, а Wizard F1 = 0,449. Ошибки моделей комплементарны, поэтому
|
||||
ансамбль может быть полезен.
|
||||
|
||||
### 6.1. Длина реплики
|
||||
|
||||
Для `char_max10000_rf`, repeat 0:
|
||||
|
||||
| Длина | Macro-F1 | Доверительный интервал |
|
||||
|---|---:|---:|
|
||||
| 0–5 слов | 0,088 | [0,033; 0,122] |
|
||||
| 6–10 | 0,218 | [0,153; 0,258] |
|
||||
| 11–15 | 0,222 | [0,170; 0,258] |
|
||||
| 16–20 | 0,239 | [0,190; 0,271] |
|
||||
| 21–30 | **0,285** | [0,238; 0,315] |
|
||||
| 31+ | 0,240 | [0,180; 0,273] |
|
||||
|
||||
Зависимость немонотонная: после 30 слов качество падает. Гипотеза «чем
|
||||
длиннее, тем лучше» не подтверждается; длинные реплики накапливают
|
||||
тематические и синтаксические различия.
|
||||
|
||||
### 6.2. Подгруппы
|
||||
|
||||
Для `char_max10000_rf`, repeat 0:
|
||||
|
||||
- возраст: elder 0,406, child 0,378, adult 0,232, teen 0,196;
|
||||
- пол: male 0,266, unknown 0,244, female 0,218;
|
||||
- вид: monster 0,295, human 0,246, unknown 0,056.
|
||||
|
||||
Это F1 **внутри группы персонажей**, а не качество предсказания группы.
|
||||
Различия описательные; они не доказывают, что модель обязана путать
|
||||
персонажей внутри группы.
|
||||
|
||||
### 6.3. Верификация и кластеризация
|
||||
|
||||
- Верификация: ROC-AUC 0,630–0,633, EER 0,412–0,415, TAR@FAR=1% 0,035–0,038,
|
||||
top-1 0,133–0,141. Выше случайного, но практически слабо.
|
||||
- Кластеризация: ARI = 0 для k = 3/5/8, NMI 0,14–0,53. Устойчивых кластеров
|
||||
персонажей в этом пространстве нет.
|
||||
|
||||
---
|
||||
|
||||
## 7. Ансамбль: предварительный результат
|
||||
|
||||
По out-of-fold предсказаниям (одинаковые повторы и seed) я проверил простое
|
||||
голосование:
|
||||
|
||||
| Комбинация | Macro-F1 |
|
||||
|---|---:|
|
||||
| `char_max10000_rf` + `model_et_default` | 0,2583 |
|
||||
| + `groups_lean2_content300` | **0,2728 ± 0,0024** |
|
||||
| + `stylometry_pos_content300` | 0,2751 ± 0,0036 |
|
||||
|
||||
Это **не** валидная финальная модель: голосование построено постфактум по
|
||||
тем же OOF-предсказаниям, которые использовались для выбора конфигураций.
|
||||
Но результат показывает, что ошибки моделей комплементарны и что
|
||||
`VotingClassifier`/stacking — перспективное направление. Проверять его нужно
|
||||
в отдельном nested-эксперименте, а не по этим числам.
|
||||
|
||||
---
|
||||
|
||||
## 8. Методологические ограничения
|
||||
|
||||
1. **Выбор признаков на тех же данных.** `char_max_features=10000`,
|
||||
`content`-LSA(k=300/400) и lean-наборы выбирались по результатам на
|
||||
тестовых фолдах. Nested CV в текущем виде настраивает только `C`,
|
||||
`max_depth` и `min_samples_leaf`; выбор признаков остаётся снаружи.
|
||||
Поэтому 0,258 может быть оптимистичной оценкой. Нужен внешний holdout или
|
||||
nested-выбор признаков.
|
||||
2. **Множественные сравнения.** 46 пар; мелкие эффекты не переживают
|
||||
поправку Холма—Бонферрони.
|
||||
3. **Один корпус.** Все 2005 реплик — из одной игры; внешней валидации нет.
|
||||
Разбиение по репликам не учитывает сцены и диалоги.
|
||||
4. **Один автор.** Это классификация речевых масок, а не авторская
|
||||
атрибуция.
|
||||
5. **Короткие тексты и дисбаланс.** 15–139 реплик на класс; редкие классы
|
||||
имеют широкие интервалы, а hub-классы забирают предсказания.
|
||||
6. **Тематический конфликт.** `content`-LSA и char- n-граммы могут кодировать
|
||||
тему, сезоны, подарки, работу и имена. Без контроля темы нельзя утверждать,
|
||||
что модель выучила стиль.
|
||||
7. **McNemar и macro-F1 не всегда согласованы.** Например, отключение
|
||||
`class_weight` у logreg снижает macro-F1 на 0,029, но McNemar даёт
|
||||
p = 0,65, потому что общая accuracy почти не меняется. Для выводов о
|
||||
macro-F1 нужны повторные разницы и bootstrap, а не только McNemar.
|
||||
8. **`final_rf` — не финальная модель.** Это дефолтный RF с 0,222; он
|
||||
проигрывает ET и char-RF.
|
||||
|
||||
---
|
||||
|
||||
## 9. Выводы
|
||||
|
||||
1. **Гипотеза о различимости персонажей подтверждается частично.**
|
||||
Лучшая модель: macro-F1 ≈ 0,258, top-3 ≈ 0,42, accuracy ≈ 0,239 при
|
||||
34 классах. Это заметно выше случайного уровня, но далеко от надёжной
|
||||
практической классификации.
|
||||
2. **Основной вклад дают лексика, символьные n-граммы и content-LSA.**
|
||||
Ручные стилометрические признаки и POS/функциональные слова сами по себе
|
||||
слабы. Поэтому результат нельзя интерпретировать как «чистый идиолект».
|
||||
3. **Лучшие модели — ExtraTrees и RF с расширенным char-словарём.**
|
||||
Они статистически неразличимы. ET проще и не требует выбора
|
||||
`char_max_features`; char-RF даёт максимум, но его конфигурация выбрана
|
||||
на тех же данных.
|
||||
4. **`content`-LSA — сильный, но тематический сигнал.** Он улучшает
|
||||
результат, однако требует тематического контроля; POS-LSA в текущем виде
|
||||
вредит.
|
||||
5. **Главная проблема — не seed, а hub-коллапс и редкие классы.**
|
||||
Abigail, Alex и ещё несколько классов забирают чужие предсказания.
|
||||
Калибровка prior/порогов и работа с редкими классами перспективнее
|
||||
дальнейшего перебора глубины деревьев.
|
||||
6. **Ансамбль моделей перспективен, но пока не проверен корректно.**
|
||||
Предварительное голосование даёт 0,273; нужен отдельный nested-эксперимент.
|
||||
7. **Для строгого вывода о стиле нужны внешняя валидация и тематический
|
||||
контроль.** Без них корректная формулировка: «персонажи различимы по
|
||||
лексико-тематическому профилю и символьным n-граммам».
|
||||
|
||||
---
|
||||
|
||||
## 10. Что делать дальше
|
||||
|
||||
### Приоритет 0 — честная финальная оценка
|
||||
|
||||
1. Выбрать одну финальную модель: `model_et_default` (проще) или
|
||||
`char_max10000_rf` (максимум).
|
||||
2. Провести nested CV, в котором внутри внешнего цикла выбираются не только
|
||||
гиперпараметры, но и `char_max_features`, `min_df`, `ngram_range` и
|
||||
`content`-LSA(k).
|
||||
3. Либо зафиксировать конфигурацию до анализа и проверить на отдельном
|
||||
holdout/новом корпусе.
|
||||
4. Заменить `final_rf` на реального лидера или переименовать его в
|
||||
`baseline_rf`.
|
||||
|
||||
### Приоритет 1 — тематический контроль
|
||||
|
||||
- сбалансировать реплики по темам/сезонам;
|
||||
- удалить тематические слова и проверить падение `content`-LSA;
|
||||
- обучить классификатор темы и проверить, не объясняет ли она результат;
|
||||
- сравнить `content`-LSA с `pos`/`fw` на тематически однородных подвыборках.
|
||||
|
||||
### Приоритет 2 — ошибки и калибровка
|
||||
|
||||
- prior correction / temperature scaling для hub-классов;
|
||||
- пороги на OOF, оптимизирующие macro-F1, а не только accuracy;
|
||||
- анализ `Abigail`, `Alex`, `Kent`, `Wizard`, `Penny`;
|
||||
- отдельный отчёт по редким классам.
|
||||
|
||||
### Приоритет 3 — ансамбли и признаки
|
||||
|
||||
- `VotingClassifier`/stacking по OOF-вероятностям;
|
||||
- `content`-LSA k=400/500 с lean-набором;
|
||||
- char `max_features` 10000–15000, `min_df` 2–3;
|
||||
- block weights;
|
||||
- внешняя валидация на другом корпусе или разбиении по сценам.
|
||||
|
||||
---
|
||||
|
||||
## 11. Артефакты
|
||||
|
||||
```text
|
||||
lab1/exports/nlp_lab1_results_20261026_054117.tar.gz
|
||||
lab1/FOLLOWUP_EXPERIMENTS.md
|
||||
lab1/scripts/analyze_followup.py
|
||||
lab1/scripts/run_followup_experiments.sh
|
||||
```
|
||||
|
||||
После распаковки архива:
|
||||
|
||||
```text
|
||||
results/followup/summary.csv
|
||||
results/followup/summary.md
|
||||
results/followup/comparisons.csv
|
||||
results/followup/comparisons.md
|
||||
results/followup/<label>/
|
||||
```
|
||||
|
||||
Команда для повторного анализа:
|
||||
|
||||
```bash
|
||||
python scripts/analyze_followup.py \
|
||||
--followup-dir results/followup \
|
||||
--pairs char_max10000_rf:ref_logreg \
|
||||
char_max10000_rf:model_et_default \
|
||||
groups_lean2_content300:ref_logreg
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 12. Итоговая формулировка для отчёта
|
||||
|
||||
> Персонажи Stardew Valley статистически различимы по отдельным репликам:
|
||||
> лучшая модель достигает macro-F1 ≈ 0,26 и top-3 ≈ 0,42 при 34 классах
|
||||
> (случайный уровень 0,029 и 0,088 соответственно). Однако основной вклад
|
||||
> дают лексико-тематические признаки и символьные n-граммы, а не
|
||||
> функциональные слова и синтаксис. Поэтому результат корректно
|
||||
> интерпретировать как различимость речевых масок по лексико-тематическому
|
||||
> профилю; для утверждения о «чистом стиле» необходимы тематический контроль
|
||||
> и внешняя валидация.
|
||||
@@ -464,34 +464,6 @@ python -m stylometry.cli compare \
|
||||
|
||||
---
|
||||
|
||||
## 6.16. Дополнительная серия для итогового отчёта
|
||||
|
||||
После основной и follow-up серий можно запустить дополнительные эксперименты:
|
||||
вложенный выбор конфигурации, контроль содержания, калибровку, ансамбли и
|
||||
дополнительные конфигурации.
|
||||
|
||||
```bash
|
||||
python scripts/run_final_experiments.py \
|
||||
--stages nested topic calibration ensemble extra \
|
||||
--out results/final
|
||||
|
||||
python scripts/run_finalists.py
|
||||
```
|
||||
|
||||
Что делают стадии:
|
||||
|
||||
- `nested` — выбирает конфигурацию признаков и модель внутри внешних блоков
|
||||
перекрёстной проверки;
|
||||
- `topic` — заменяет содержательные слова на нейтральный маркер и проверяет,
|
||||
сохраняется ли качество;
|
||||
- `calibration` — проверяет коррекцию априорных вероятностей классов;
|
||||
- `ensemble` — мягкое голосование и стековое объединение;
|
||||
- `extra` — дополнительные конфигурации и стратифицированная отложенная
|
||||
выборка.
|
||||
|
||||
Результаты сохраняются в `results/final/`; сводка — `FINAL_RESULTS.csv`,
|
||||
итоговый отчёт — `FINAL_REPORT.md`.
|
||||
|
||||
## 7. Если серия прервалась
|
||||
|
||||
Скрипт останавливается при первой ошибке. Уже созданные каталоги результатов
|
||||
|
||||
@@ -1,81 +0,0 @@
|
||||
# С чего начать
|
||||
|
||||
Это архив лабораторной работы №1 по курсу «Автоматический анализ текстов»:
|
||||
стилометрическая классификация реплик персонажей Stardew Valley.
|
||||
|
||||
## Что внутри
|
||||
|
||||
- `lab1/report/REPORT.pdf` — **главный отчёт** (21 страница).
|
||||
- `lab1/report/REPORT.docx` — редактируемая версия отчёта.
|
||||
- `lab1/report/REPORT.md` — исходник отчёта.
|
||||
- `lab1/presentation/stardew_stylometry.pdf` — презентация для защиты.
|
||||
- `lab1/presentation/stardew_stylometry.pptx` — редактируемая презентация.
|
||||
- `lab1/presentation/latex/` — LaTeX-исходник презентации.
|
||||
- `lab1/stylometry/` — код: чтение корпуса, признаки, LSA, модели,
|
||||
кросс-валидация, метрики, верификация.
|
||||
- `lab1/data/` — корпус и метаданные.
|
||||
- `lab1/tests/` — 33 теста.
|
||||
- `lab1/FINAL_RESULTS.csv` — сводная таблица результатов.
|
||||
- `lab1/exports/nlp_lab1_final_results_20261026_120000.tar.gz` — компактный
|
||||
архив финальных результатов (без промежуточных матриц).
|
||||
|
||||
## Проверка за три минуты
|
||||
|
||||
Все команды выполняются из каталога `lab1`:
|
||||
|
||||
```bash
|
||||
cd lab1
|
||||
|
||||
python -m venv .venv
|
||||
source .venv/bin/activate # Windows: .venv\Scripts\activate
|
||||
python -m pip install --upgrade pip
|
||||
python -m pip install -r requirements.txt
|
||||
|
||||
python scripts/download_nltk_data.py
|
||||
python scripts/check_environment.py
|
||||
python -m pytest -q
|
||||
```
|
||||
|
||||
Ожидаемый результат:
|
||||
|
||||
- `check_environment.py` заканчивается строкой `Окружение готово.`;
|
||||
- `pytest` печатает `33 passed`;
|
||||
- в корпусе 2005 реплик и 34 персонажа.
|
||||
|
||||
Если всё совпало, отчёт и презентацию можно открывать сразу. Для повторного
|
||||
запуска экспериментов смотрите `ARCHIVE_README.md`.
|
||||
|
||||
## Главный результат
|
||||
|
||||
| Что | Значение |
|
||||
|---|---:|
|
||||
| Корпус | 2005 реплик, 34 персонажа |
|
||||
| Основная метрика | macro-F1 |
|
||||
| Мягкое голосование трёх моделей | **0,3036 ± 0,0189** |
|
||||
| Консервативная оценка (вложенный выбор) | **0,2746 ± 0,0233** |
|
||||
| Случайный уровень | **0,029** |
|
||||
|
||||
Основной сигнал — лексико-тематический. После замены содержательных слов
|
||||
нейтральным маркером macro-F1 падает с 0,248 до 0,113. Поэтому в отчёте
|
||||
результат сформулирован как различимость **речевых масок по
|
||||
лексико-тематическому профилю**, а не как доказательство «чистого» стиля.
|
||||
|
||||
## Что читать дальше
|
||||
|
||||
1. `ARCHIVE_README.md` — подробная инструкция: установка, запуск, структура
|
||||
проекта, что делает каждый модуль, как пересобрать отчёт и презентацию.
|
||||
2. `lab1/RUNBOOK.md` — пошаговый запуск полной серии экспериментов.
|
||||
3. `lab1/README.md` — краткое описание проекта и команд.
|
||||
4. `lab1/FINAL_REPORT.md` — расширенная версия отчёта с деталями.
|
||||
5. `lab1/EXPERIMENTS.md` — протокол экспериментов.
|
||||
6. `lab1/FEATURES.md` — формулы и обоснование признаков.
|
||||
7. `lab1/METRICS_AND_CLASSIFIERS.md` — метрики и классификаторы.
|
||||
|
||||
## Важно
|
||||
|
||||
- Ресурсы NLTK (`nltk_data`, около 80 МБ) в архив не включены: они
|
||||
скачиваются командой `python scripts/download_nltk_data.py`.
|
||||
- Виртуальное окружение, промежуточные матрицы признаков и сырые результаты
|
||||
серий в архив не включены: они восстанавливаются скриптами.
|
||||
- Диалоги Stardew Valley принадлежат ConcernedApe; корпус используется
|
||||
исключительно в учебных и исследовательских целях.
|
||||
407
lab1/TUNING_EXPERIMENTS.md
Normal file
@@ -0,0 +1,407 @@
|
||||
# Серия настройки (T1–T11)
|
||||
|
||||
Документ описывает **дополнительные** эксперименты поверх полной серии
|
||||
`scripts/run_all_experiments.sh`. Они не заменяют протокол E0–E16 из
|
||||
`EXPERIMENTS.md` и не повторяют его запуски с теми же параметрами.
|
||||
|
||||
Полная серия измеряет качество на **значениях по умолчанию**. Эта серия
|
||||
проверяет, меняется ли macro-F1, если изменить регуляризацию, состав
|
||||
признаков, n-граммы, веса блоков, число компонент LSA или гиперпараметры
|
||||
деревьев.
|
||||
|
||||
Справочник «где в коде лежит параметр» — `TUNING.md`. Здесь — что запускать,
|
||||
зачем и как интерпретировать.
|
||||
|
||||
---
|
||||
|
||||
## 1. Зачем эта серия
|
||||
|
||||
Исследовательский вопрос тот же: различимы ли 34 персонажа по одной реплике.
|
||||
Базовый ответ даёт `stylometry_pos_lsa` + `logreg` из полной серии.
|
||||
|
||||
Дополнительно нужно понять:
|
||||
|
||||
- не слишком ли слабая или сильная регуляризация при 2005 объектах и 34 классах;
|
||||
- не вредят ли нестабильные группы (`vocabulary`, `readability`) и признаки длины;
|
||||
- не переобучаются ли широкие символьные n-граммы;
|
||||
- не давит ли один блок признаков на остальные после L2-нормировки;
|
||||
- не лучше ли другое число компонент LSA, чем `k=100`;
|
||||
- не лучше ли более мелкие деревья, чем лес без ограничения глубины;
|
||||
- даёт ли дельта Барроуза что-то на краях диапазона MFW (50 и 1000 слов).
|
||||
|
||||
Главная метрика по-прежнему **macro-F1** с бутстрэп-интервалом. Разница в
|
||||
1–2 процентных пункта часто шум: сравнивайте запуски через `compare`
|
||||
(Мак-Немар и бутстрэп-разница), а не «на глаз» по одному числу.
|
||||
|
||||
---
|
||||
|
||||
## 2. Что нельзя путать с полной серией
|
||||
|
||||
| Полная серия (`run_all_experiments.sh`) | Эта серия |
|
||||
|---|---|
|
||||
| Базовые пресеты, абляции leave-one-out, LSA 50/100/200, benchmark моделей, nested CV | Перебор **внутри** лучшего пресета |
|
||||
| Logreg/SVM с `C=1`, `class_weight="balanced"` | Другие `C`, отключение весов классов |
|
||||
| Char `[2,5]` / 5000, POS `[1,3]` / 500, `min_df=3` | Узкие и широкие n-граммы |
|
||||
| Равные веса блоков | Заданные веса |
|
||||
| Дельта 100/200/500 MFW | Дельта 50 и 1000 MFW |
|
||||
| Extra trees нет | Один контролируемый запуск extra trees |
|
||||
|
||||
Не запускайте эту серию вместо E0–E16. Сначала нужна база: каталог
|
||||
`results/stylometry_pos_lsa_logreg_<timestamp>/` с 5 повторами (или хотя бы 3).
|
||||
|
||||
---
|
||||
|
||||
## 3. Как запускать
|
||||
|
||||
Все команды — из каталога `lab1`. Схема CV та же, что в протоколе:
|
||||
5 блоков, seed `42`. Для черновика достаточно 1 повтора, для отчёта — 3
|
||||
(лучшие 1–2 конфигурации потом перепроверьте с 5).
|
||||
|
||||
### 3.1. Вся серия скриптом
|
||||
|
||||
```bash
|
||||
cd lab1
|
||||
bash scripts/run_tuning_experiments.sh
|
||||
```
|
||||
|
||||
Быстрый прогон:
|
||||
|
||||
```bash
|
||||
REPEATS=1 bash scripts/run_tuning_experiments.sh
|
||||
```
|
||||
|
||||
Другой интерпретатор:
|
||||
|
||||
```bash
|
||||
PYTHON=../.venv/bin/python bash scripts/run_tuning_experiments.sh
|
||||
```
|
||||
|
||||
Лог: `results/logs/tune_<timestamp>.log`.
|
||||
Каждый `run` создаёт **новый** каталог `results/<preset>_<model>_<timestamp>/`.
|
||||
У двух запусков с одним пресетом имена отличаются только меткой времени;
|
||||
какой это был вариант, смотрите в `run_metadata.json` (`model_params`,
|
||||
`feature_config`).
|
||||
|
||||
### 3.2. Один эксперимент вручную
|
||||
|
||||
Конфиги лежат в `configs/tune_*.toml`. Каждый файл — **одна** точка перебора
|
||||
признаков. Модель и пресет задаются в команде, не в имени файла.
|
||||
|
||||
```bash
|
||||
python -m stylometry.cli run \
|
||||
--config configs/tune_no_vocab_readability.toml \
|
||||
--preset stylometry_pos_lsa \
|
||||
--model logreg \
|
||||
--repeats 3 \
|
||||
--seed 42
|
||||
```
|
||||
|
||||
Параметры классификатора toml не требуют — их передаёт `--model-params`.
|
||||
|
||||
### 3.3. Сравнение с базой
|
||||
|
||||
```bash
|
||||
python -m stylometry.cli compare \
|
||||
--run-a results/stylometry_pos_lsa_logreg_<база> \
|
||||
--run-b results/stylometry_pos_lsa_logreg_<тюнинг>
|
||||
```
|
||||
|
||||
Оба запуска должны иметь одинаковые `--folds` и `--seed`. Иначе парное
|
||||
сравнение некорректно.
|
||||
|
||||
---
|
||||
|
||||
## 4. Конфиги признаков
|
||||
|
||||
Файлы не «включают серию целиком»: каждый подменяет только указанные поля
|
||||
`FeatureConfig`. Остальное берётся из пресета `stylometry_pos_lsa`.
|
||||
|
||||
| Файл | Что меняет |
|
||||
|---|---|
|
||||
| `configs/tune_no_vocab_readability.toml` | группы без `vocabulary` и `readability` |
|
||||
| `configs/tune_no_vocab_readability_length.toml` | ещё без `length` |
|
||||
| `configs/tune_char_narrow.toml` | char `[2,4]`, 3000 признаков, POS 500, `min_df=5` |
|
||||
| `configs/tune_char_wide.toml` | char `[2,6]`, 8000, POS `[1,4]` / 800, `min_df=2` |
|
||||
| `configs/tune_block_weights.toml` | веса `stylo/char/pos/lsa_word/lsa_pos` |
|
||||
| `configs/tune_lsa_k150.toml` | `lsa_components = 150` |
|
||||
| `configs/tune_lsa_k300.toml` | `lsa_components = 300` |
|
||||
|
||||
---
|
||||
|
||||
## 5. Эксперименты
|
||||
|
||||
Базовый пресет везде `stylometry_pos_lsa`, если не сказано иное.
|
||||
Ориентир для сравнения — pooled macro-F1 базового logreg из полной серии.
|
||||
|
||||
### T1. Логистическая регрессия: `C`
|
||||
|
||||
**Зачем.** `C` — обратная сила L2-регуляризации. На широком признаковом
|
||||
пространстве (ручные признаки + n-граммы + LSA) модель с `C=1` может
|
||||
переобучаться или, наоборот, быть слишком простой. Нужно найти диапазон,
|
||||
а не одно «магическое» число.
|
||||
|
||||
**Что меняется.** `C ∈ {0.03, 0.1, 0.3, 1, 3, 10}`. Остальное как в базе
|
||||
(`class_weight="balanced"`). Значение `C=1` совпадает с полной серией;
|
||||
его можно не гонять, если база уже есть.
|
||||
|
||||
**Как запускать.**
|
||||
|
||||
```bash
|
||||
for c in 0.03 0.1 0.3 1 3 10; do
|
||||
python -m stylometry.cli run --preset stylometry_pos_lsa --model logreg \
|
||||
--repeats 3 --seed 42 --model-params "{\"C\": $c}"
|
||||
done
|
||||
```
|
||||
|
||||
**Как читать.** Меньше `C` — проще модель. Если macro-F1 растёт при уменьшении
|
||||
`C`, база переобучалась. Если растёт только при `C ≥ 3`, регуляризации в базе
|
||||
было слишком много. Смотрите также log loss: он показывает калибровку, не
|
||||
только точность меток.
|
||||
|
||||
### T2. Линейный SVM: `C`
|
||||
|
||||
**Зачем.** Та же логика, другой критерий (зазор вместо логистической потери).
|
||||
SVM в полной серии тоже шёл с `C=1`. Если лучший `C` у SVM и logreg близки,
|
||||
вывод про регуляризацию устойчив.
|
||||
|
||||
**Как запускать.**
|
||||
|
||||
```bash
|
||||
for c in 0.03 0.1 0.3 1 3 10; do
|
||||
python -m stylometry.cli run --preset stylometry_pos_lsa --model linear_svc \
|
||||
--repeats 3 --seed 42 --model-params "{\"C\": $c}"
|
||||
done
|
||||
```
|
||||
|
||||
**Ограничение.** У `linear_svc` нет вероятностей: log loss не считается,
|
||||
top-k строится по решающей функции.
|
||||
|
||||
### T3. Logreg без балансировки классов
|
||||
|
||||
**Зачем.** В корпусе 15–139 реплик на персонажа. `class_weight="balanced"`
|
||||
почти наверняка нужен, но это надо показать: иначе macro-F1 может казаться
|
||||
низким «из-за модели», хотя виноват сдвиг к частым классам (Abigail и т.д.).
|
||||
|
||||
**Как запускать.**
|
||||
|
||||
```bash
|
||||
python -m stylometry.cli run --preset stylometry_pos_lsa --model logreg \
|
||||
--repeats 3 --seed 42 --model-params '{"class_weight": null}'
|
||||
```
|
||||
|
||||
**Как читать.** Если accuracy почти не падает, а macro-F1 и balanced accuracy
|
||||
падают, модель без весов игнорирует редких персонажей. Это аргумент в пользу
|
||||
macro-F1 как основной метрики.
|
||||
|
||||
### T4. Без `vocabulary` и `readability`
|
||||
|
||||
**Зачем.** На репликах ~19 слов TTR, индексы Honore/Yule и формулы читаемости
|
||||
нестабильны (`FEATURES.md`, гипотеза H2). Полная серия делает leave-one-out
|
||||
(`ablate`): удаляет **по одной** группе. Здесь обе шумные группы снимаются
|
||||
сразу — ближе к тому, как собирали бы «очищенный» основной набор.
|
||||
|
||||
**Конфиг.** `configs/tune_no_vocab_readability.toml`
|
||||
|
||||
```bash
|
||||
python -m stylometry.cli run --config configs/tune_no_vocab_readability.toml \
|
||||
--preset stylometry_pos_lsa --model logreg --repeats 3 --seed 42
|
||||
```
|
||||
|
||||
**Как читать.** Рост или отсутствие падения macro-F1 значит, что эти группы
|
||||
не несут устойчивого сигнала (или мешают). Сильное падение — в протоколе их
|
||||
нельзя вычёркивать без оговорки.
|
||||
|
||||
### T5. Ещё без `length`
|
||||
|
||||
**Зачем.** Гипотеза H4: длинные реплики классифицируются лучше. Если качество
|
||||
не падает после удаления length-признаков, различимость персонажей не сводится
|
||||
к «кто говорит длиннее». Если падает — часть результата объясняется длиной,
|
||||
это нужно явно написать в отчёте.
|
||||
|
||||
**Конфиг.** `configs/tune_no_vocab_readability_length.toml`
|
||||
|
||||
```bash
|
||||
python -m stylometry.cli run --config configs/tune_no_vocab_readability_length.toml \
|
||||
--preset stylometry_pos_lsa --model logreg --repeats 3 --seed 42
|
||||
```
|
||||
|
||||
Имеет смысл сопоставить с `length-report` по базовому запуску.
|
||||
|
||||
### T6. Символьные и POS n-граммы: узкие vs широкие
|
||||
|
||||
**Зачем.** В полной серии фиксированы char 2–5 / 5000 и POS 1–3 / 500.
|
||||
Узкий вариант (короче n-граммы, выше `min_df`) проверяет переобучение на
|
||||
редких орфографических шаблонах. Широкий (2–6, больше признаков, `min_df=2`)
|
||||
проверяет, не теряем ли морфологию и пунктуационные привычки.
|
||||
|
||||
**Конфиги.** `tune_char_narrow.toml`, `tune_char_wide.toml`
|
||||
|
||||
```bash
|
||||
python -m stylometry.cli run --config configs/tune_char_narrow.toml \
|
||||
--preset stylometry_pos_lsa --model logreg --repeats 3 --seed 42
|
||||
|
||||
python -m stylometry.cli run --config configs/tune_char_wide.toml \
|
||||
--preset stylometry_pos_lsa --model logreg --repeats 3 --seed 42
|
||||
```
|
||||
|
||||
**Как читать.** Если wide лучше на обучении блоков, но pooled macro-F1 не выше
|
||||
базы (или хуже на редких классах) — переобучение. Если narrow не хуже базы —
|
||||
лишняя размерность не нужна.
|
||||
|
||||
### T7. Веса блоков
|
||||
|
||||
**Зачем.** После L2-нормировки каждый блок имеет единичную норму и конкурирует
|
||||
наравне, даже если один из них шумный. В базе веса равны. Здесь ослабляются
|
||||
char и особенно `lsa_word` (тематический риск, гипотеза H3), POS и стилометрия
|
||||
остаются сильнее.
|
||||
|
||||
**Конфиг.** `configs/tune_block_weights.toml`
|
||||
|
||||
```text
|
||||
stylo = 1.0
|
||||
char = 0.5
|
||||
pos = 0.7
|
||||
lsa_word = 0.3
|
||||
lsa_pos = 0.5
|
||||
```
|
||||
|
||||
```bash
|
||||
python -m stylometry.cli run --config configs/tune_block_weights.toml \
|
||||
--preset stylometry_pos_lsa --model logreg --repeats 3 --seed 42
|
||||
```
|
||||
|
||||
**Как читать.** Веса — такой же гиперпараметр, как `C`. Один удачный набор не
|
||||
доказывает оптимум. Если macro-F1 вырос и `lsa_word` при этом ослаблен, это
|
||||
дополнительный довод, что тема мешала стилю.
|
||||
|
||||
### T8. Число компонент LSA: 150 и 300
|
||||
|
||||
**Зачем.** В полной серии `lsa-sweep` смотрит `k ∈ {50, 100, 200}` по
|
||||
**отдельным** вариантам LSA. В основном пресете всегда `k=100` сразу для
|
||||
`word` и `pos`. Здесь проверяется, помогает ли больше компонент **в
|
||||
объединённом** наборе, плюс недостающие точки 150 и 300 в переборе вариантов.
|
||||
|
||||
**Конфиги.** `tune_lsa_k150.toml`, `tune_lsa_k300.toml`
|
||||
|
||||
```bash
|
||||
python -m stylometry.cli run --config configs/tune_lsa_k150.toml \
|
||||
--preset stylometry_pos_lsa --model logreg --repeats 3 --seed 42
|
||||
|
||||
python -m stylometry.cli run --config configs/tune_lsa_k300.toml \
|
||||
--preset stylometry_pos_lsa --model logreg --repeats 3 --seed 42
|
||||
|
||||
python -m stylometry.cli lsa-sweep \
|
||||
--variants word,content,fw,char,pos \
|
||||
--components 150,300 \
|
||||
--model logreg --repeats 1 --seed 42
|
||||
```
|
||||
|
||||
**Как читать.** Рост `k` без роста macro-F1 — лишние компоненты ловят шум.
|
||||
Сравнивайте `word`/`content` с `pos`/`fw`: если выигрыш только у word-LSA,
|
||||
это снова тема, а не стиль.
|
||||
|
||||
### T9. Случайный лес: глубина и лист
|
||||
|
||||
**Зачем.** В `benchmark` лес идёт с дефолтами (`n_estimators=400`,
|
||||
`max_depth=None`, `min_samples_leaf=1`). На 2005×34 такая модель легко
|
||||
запоминает обучающие блоки. Сначала ограничивают глубину и увеличивают лист,
|
||||
а не число деревьев.
|
||||
|
||||
**Как запускать.**
|
||||
|
||||
```bash
|
||||
for depth in 10 20 30; do
|
||||
for leaf in 2 3 5; do
|
||||
python -m stylometry.cli run --preset stylometry_pos_lsa --model random_forest \
|
||||
--repeats 3 --seed 42 \
|
||||
--model-params "{\"max_depth\": $depth, \"min_samples_leaf\": $leaf}"
|
||||
done
|
||||
done
|
||||
|
||||
python -m stylometry.cli run --preset stylometry_pos_lsa --model random_forest \
|
||||
--repeats 3 --seed 42 \
|
||||
--model-params '{"max_depth": 20, "min_samples_leaf": 3, "max_features": 0.1}'
|
||||
```
|
||||
|
||||
**Как читать.** Если мелкие деревья не хуже (или лучше) дефолтного леса из
|
||||
benchmark, сложность в базе была лишней. Сравнивайте с logreg, а не только
|
||||
друг с другом: лес может выигрывать accuracy и проигрывать macro-F1.
|
||||
|
||||
### T10. Сверхслучайные деревья
|
||||
|
||||
**Зачем.** Extra trees не входили в `benchmark` полной серии. Случайные пороги
|
||||
обычно снижают дисперсию относительно RF. Один контролируемый запуск
|
||||
(не полный перебор) проверяет, есть ли смысл включать модель в сравнение.
|
||||
|
||||
```bash
|
||||
python -m stylometry.cli run --preset stylometry_pos_lsa --model extra_trees \
|
||||
--repeats 3 --seed 42 \
|
||||
--model-params '{"max_depth": 20, "min_samples_leaf": 3}'
|
||||
```
|
||||
|
||||
Если результат заметно хуже logreg, модель достаточно упомянуть как проверку,
|
||||
не раздувая сетку.
|
||||
|
||||
### T11. Дельта Барроуза: 50 и 1000 MFW
|
||||
|
||||
**Зачем.** Классическая стилометрия на самых частотных словах. Полная серия
|
||||
уже смотрит 100/200/500. Края диапазона: 50 слов — очень компактный профиль,
|
||||
1000 — больше лексики и больше темы. На коротких репликах дельта обычно
|
||||
слабее ML; это независимая нижняя граница, не конкурент основному пресету.
|
||||
|
||||
```bash
|
||||
python -m stylometry.cli delta \
|
||||
--features 50,1000 --metrics manhattan,cosine --repeats 2 --seed 42
|
||||
```
|
||||
|
||||
`zscore` в CLI не переключается (в коде по умолчанию включён), поэтому его
|
||||
здесь нет.
|
||||
|
||||
---
|
||||
|
||||
## 6. Рекомендуемый порядок, если запускаете вручную
|
||||
|
||||
Не гоните все циклы сразу, если времени мало. Порядок совпадает с `TUNING.md`:
|
||||
|
||||
1. T4, T5 — состав групп (дешево и часто полезно).
|
||||
2. T6 — n-граммы.
|
||||
3. T8 — `k` LSA в объединённом наборе.
|
||||
4. T7 — веса, если после T4–T6 остался шумный блок.
|
||||
5. T1, затем T2 — `C`.
|
||||
6. T3 — один раз, для отчёта про дисбаланс.
|
||||
7. T9, T10 — только если линейные модели уже сняты.
|
||||
8. T11 — короткая классическая проверка.
|
||||
|
||||
После отбора 1–2 лучших конфигураций:
|
||||
|
||||
```bash
|
||||
python -m stylometry.cli run --preset stylometry_pos_lsa --model logreg \
|
||||
--repeats 5 --seed 42 --nested
|
||||
```
|
||||
|
||||
Вложенная CV с расширенной сеткой честнее, чем выбор `C` по тем же OOF,
|
||||
что потом публикуете. Узкая сетка `{0.1, 1, 10}` уже есть в полной серии;
|
||||
имеет смысл `--nested` только если T1 сдвинул разумный диапазон (например,
|
||||
к `{0.03, 0.1, 0.3}`). Для этого нужно изменить `PARAM_GRIDS` в
|
||||
`stylometry/pipelines.py` — это уже правка кода, не конфиг.
|
||||
|
||||
---
|
||||
|
||||
## 7. Как фиксировать и чего не делать
|
||||
|
||||
Для каждого запуска сохраняются стандартные файлы: `run_metadata.json`,
|
||||
`pooled_metrics.csv`, `bootstrap_ci.json`, `per_class_report.csv`,
|
||||
матрица ошибок. Перед сравнением откройте `run_metadata.json` и убедитесь,
|
||||
что отличаются только задуманные поля.
|
||||
|
||||
Нельзя:
|
||||
|
||||
- выбирать победителя по тестовой выборке вне CV;
|
||||
- менять `--folds` или `--seed` «чтобы стало лучше»;
|
||||
- сравнивать `QUICK=1` / `REPEATS=1` с финальными 5 повторами как равные;
|
||||
- объявлять улучшение без интервала и `compare`;
|
||||
- подменять macro-F1 долей правильных ответов.
|
||||
|
||||
Ограничения корпуса те же, что в `EXPERIMENTS.md`: короткие реплики, один
|
||||
автор, ручная фильтрация, дисбаланс 15–139. Настройка модели эти ограничения
|
||||
не снимает.
|
||||
13
lab1/configs/tune_block_weights.toml
Normal file
@@ -0,0 +1,13 @@
|
||||
# Ослабить потенциально шумные блоки после L2-нормировки.
|
||||
|
||||
[cv]
|
||||
folds = 5
|
||||
repeats = 3
|
||||
seed = 42
|
||||
|
||||
[features.block_weights]
|
||||
stylo = 1.0
|
||||
char = 0.5
|
||||
pos = 0.7
|
||||
lsa_word = 0.3
|
||||
lsa_pos = 0.5
|
||||
13
lab1/configs/tune_char_narrow.toml
Normal file
@@ -0,0 +1,13 @@
|
||||
# Более короткие char n-граммы, меньше признаков, строже min_df.
|
||||
|
||||
[cv]
|
||||
folds = 5
|
||||
repeats = 3
|
||||
seed = 42
|
||||
|
||||
[features]
|
||||
char_ngram_range = [2, 4]
|
||||
char_max_features = 3000
|
||||
pos_ngram_range = [1, 3]
|
||||
pos_max_features = 500
|
||||
min_df = 5
|
||||
13
lab1/configs/tune_char_wide.toml
Normal file
@@ -0,0 +1,13 @@
|
||||
# Более длинные n-граммы и больше признаков: риск переобучения.
|
||||
|
||||
[cv]
|
||||
folds = 5
|
||||
repeats = 3
|
||||
seed = 42
|
||||
|
||||
[features]
|
||||
char_ngram_range = [2, 6]
|
||||
char_max_features = 8000
|
||||
pos_ngram_range = [1, 4]
|
||||
pos_max_features = 800
|
||||
min_df = 2
|
||||
7
lab1/configs/tune_lsa_k150.toml
Normal file
@@ -0,0 +1,7 @@
|
||||
[cv]
|
||||
folds = 5
|
||||
repeats = 3
|
||||
seed = 42
|
||||
|
||||
[features]
|
||||
lsa_components = 150
|
||||
7
lab1/configs/tune_lsa_k300.toml
Normal file
@@ -0,0 +1,7 @@
|
||||
[cv]
|
||||
folds = 5
|
||||
repeats = 3
|
||||
seed = 42
|
||||
|
||||
[features]
|
||||
lsa_components = 300
|
||||
20
lab1/configs/tune_no_vocab_readability.toml
Normal file
@@ -0,0 +1,20 @@
|
||||
# Без нестабильных групп на коротких репликах.
|
||||
# Остальные поля пресета (n-граммы, LSA) не переопределяются.
|
||||
|
||||
[cv]
|
||||
folds = 5
|
||||
repeats = 3
|
||||
seed = 42
|
||||
|
||||
[features]
|
||||
groups = [
|
||||
"length",
|
||||
"lexical",
|
||||
"characters",
|
||||
"punctuation",
|
||||
"function_words",
|
||||
"pronouns",
|
||||
"pos",
|
||||
"syntax",
|
||||
"dialogue",
|
||||
]
|
||||
18
lab1/configs/tune_no_vocab_readability_length.toml
Normal file
@@ -0,0 +1,18 @@
|
||||
# То же + без length: проверка, не объясняется ли сигнал длиной реплики.
|
||||
|
||||
[cv]
|
||||
folds = 5
|
||||
repeats = 3
|
||||
seed = 42
|
||||
|
||||
[features]
|
||||
groups = [
|
||||
"lexical",
|
||||
"characters",
|
||||
"punctuation",
|
||||
"function_words",
|
||||
"pronouns",
|
||||
"pos",
|
||||
"syntax",
|
||||
"dialogue",
|
||||
]
|
||||
BIN
lab1/dist/nlp_lab1_submission_20261101.zip
vendored
@@ -1 +0,0 @@
|
||||
635ed9d56653259be30733fe8b80316e8ce1f3b32ac178170f5deef0f3e39d79 nlp_lab1_submission_20261101.zip
|
||||
@@ -1 +0,0 @@
|
||||
fd4aecdf5f7ecfe74b55324f0b4700e25adaa9f6bdf035edbd3017e58152838f exports/nlp_lab1_final_results_20261026_120000.tar.gz
|
||||
@@ -1,8 +0,0 @@
|
||||
Дата: 26.10.2026
|
||||
Git commit: 73c32d10c4ef7689704d8114aa22fd6f4a2e870f
|
||||
Архив: exports/nlp_lab1_final_results_20261026_120000.tar.gz
|
||||
Содержимое: results/final
|
||||
Серии: nested (вложенный выбор конфигурации), topic (контроль содержания),
|
||||
calibration (коррекция приоров), ensemble (мягкое голосование и stacking),
|
||||
extra (дополнительные конфигурации и отложенная выборка), finalists.
|
||||
Ключевые результаты: см. FINAL_RESULTS.csv и FINAL_REPORT.md.
|
||||
@@ -1 +0,0 @@
|
||||
fac6cd6286209a20afa732dd8da257fe39208e00cdc8542e73496800ca0aff5e /home/folen/Homework/NLP/lab1/exports/nlp_lab1_results_20261025_173103.tar.gz
|
||||
@@ -1,254 +0,0 @@
|
||||
Дата: Вс 25 окт 2026 17:31:03 MSK
|
||||
Каталог: /home/folen/Homework/NLP/lab1
|
||||
Интерпретатор: python
|
||||
|
||||
=== Проверка окружения ===
|
||||
Python: 3.11.2 (/home/folen/Homework/NLP/.venv/bin/python)
|
||||
Каталог проекта: /home/folen/Homework/NLP/lab1
|
||||
Версии библиотек:
|
||||
numpy: 2.4.6
|
||||
pandas: 3.1.0
|
||||
scipy: 1.17.1
|
||||
sklearn: 1.9.1
|
||||
nltk: 3.10.4
|
||||
matplotlib: 3.11.2
|
||||
Корпус:
|
||||
файл: /home/folen/Homework/NLP/lab1/data/dialogues.csv
|
||||
SHA-256: 8946f08159daddb9d89c9d323205d4a7fb4bc617d16e300ff6c7606cb05030f7
|
||||
реплик: 2005
|
||||
персонажей: 34
|
||||
словоупотреблений: 38207
|
||||
NLTK:
|
||||
токенизатор: NLTK
|
||||
POS-теггер: доступен
|
||||
пример токенов: ['Oh', ',', 'that', "'s", 'right', '...', 'I', 'heard']
|
||||
пример POS: ['INTJ', 'PUNCT', 'DET', 'AUX', 'ADJ', 'PUNCT', 'PRON', 'VERB']
|
||||
|
||||
Окружение готово.
|
||||
{"versions": {"numpy": "2.4.6", "pandas": "3.1.0", "scipy": "1.17.1", "sklearn": "1.9.1", "nltk": "3.10.4", "matplotlib": "3.11.2"}, "n_lines": 2005}
|
||||
|
||||
Git commit: 10e2444d2342e2d02c94a06de884ad7bbee83bca
|
||||
|
||||
Содержимое results:
|
||||
results/ablation_stylometry_pos_lsa_20261025_170116/ablation.csv
|
||||
results/benchmark_stylometry_pos_lsa_20261025_165348/summary.csv
|
||||
results/char_multinomial_nb_20261025_155832/bootstrap_ci.json
|
||||
results/char_multinomial_nb_20261025_155832/confusion_matrix.csv
|
||||
results/char_multinomial_nb_20261025_155832/confusion_matrix_normalized.csv
|
||||
results/char_multinomial_nb_20261025_155832/fold_metrics.csv
|
||||
results/char_multinomial_nb_20261025_155832/fold_summary.csv
|
||||
results/char_multinomial_nb_20261025_155832/oof_predictions.csv
|
||||
results/char_multinomial_nb_20261025_155832/per_class_report.csv
|
||||
results/char_multinomial_nb_20261025_155832/pooled_metrics.csv
|
||||
results/char_multinomial_nb_20261025_155832/run_metadata.json
|
||||
results/char_multinomial_nb_20261025_155832/summary.md
|
||||
results/char_multinomial_nb_20261025_155832/top_confusions.csv
|
||||
results/clustering_stylometry_pos_lsa_20261025_172241/character_centroids.csv
|
||||
results/clustering_stylometry_pos_lsa_20261025_172241/clustering_metrics.csv
|
||||
results/delta_20261025_172113/delta_sweep.csv
|
||||
results/fw_logreg_20261025_160058/bootstrap_ci.json
|
||||
results/fw_logreg_20261025_160058/confusion_matrix.csv
|
||||
results/fw_logreg_20261025_160058/confusion_matrix_normalized.csv
|
||||
results/fw_logreg_20261025_160058/fold_metrics.csv
|
||||
results/fw_logreg_20261025_160058/fold_summary.csv
|
||||
results/fw_logreg_20261025_160058/oof_predictions.csv
|
||||
results/fw_logreg_20261025_160058/per_class_report.csv
|
||||
results/fw_logreg_20261025_160058/pooled_metrics.csv
|
||||
results/fw_logreg_20261025_160058/run_metadata.json
|
||||
results/fw_logreg_20261025_160058/summary.md
|
||||
results/fw_logreg_20261025_160058/top_confusions.csv
|
||||
results/length_dummy_20261025_155747/bootstrap_ci.json
|
||||
results/length_dummy_20261025_155747/confusion_matrix.csv
|
||||
results/length_dummy_20261025_155747/confusion_matrix_normalized.csv
|
||||
results/length_dummy_20261025_155747/fold_metrics.csv
|
||||
results/length_dummy_20261025_155747/fold_summary.csv
|
||||
results/length_dummy_20261025_155747/oof_predictions.csv
|
||||
results/length_dummy_20261025_155747/per_class_report.csv
|
||||
results/length_dummy_20261025_155747/pooled_metrics.csv
|
||||
results/length_dummy_20261025_155747/run_metadata.json
|
||||
results/length_dummy_20261025_155747/summary.md
|
||||
results/length_dummy_20261025_155747/top_confusions.csv
|
||||
results/length_logreg_20261025_155719/bootstrap_ci.json
|
||||
results/length_logreg_20261025_155719/confusion_matrix.csv
|
||||
results/length_logreg_20261025_155719/confusion_matrix_normalized.csv
|
||||
results/length_logreg_20261025_155719/fold_metrics.csv
|
||||
results/length_logreg_20261025_155719/fold_summary.csv
|
||||
results/length_logreg_20261025_155719/oof_predictions.csv
|
||||
results/length_logreg_20261025_155719/per_class_report.csv
|
||||
results/length_logreg_20261025_155719/pooled_metrics.csv
|
||||
results/length_logreg_20261025_155719/run_metadata.json
|
||||
results/length_logreg_20261025_155719/summary.md
|
||||
results/length_logreg_20261025_155719/top_confusions.csv
|
||||
results/length_logreg_20261025_155813/bootstrap_ci.json
|
||||
results/length_logreg_20261025_155813/confusion_matrix.csv
|
||||
results/length_logreg_20261025_155813/confusion_matrix_normalized.csv
|
||||
results/length_logreg_20261025_155813/fold_metrics.csv
|
||||
results/length_logreg_20261025_155813/fold_summary.csv
|
||||
results/length_logreg_20261025_155813/oof_predictions.csv
|
||||
results/length_logreg_20261025_155813/per_class_report.csv
|
||||
results/length_logreg_20261025_155813/pooled_metrics.csv
|
||||
results/length_logreg_20261025_155813/run_metadata.json
|
||||
results/length_logreg_20261025_155813/summary.md
|
||||
results/length_logreg_20261025_155813/top_confusions.csv
|
||||
results/logs/run_all_20261025_150915.log
|
||||
results/logs/run_all_20261025_151312.log
|
||||
results/logs/run_all_20261025_152356.log
|
||||
results/logs/run_all_20261025_155738.log
|
||||
results/lsa_char_logreg_20261025_164836/bootstrap_ci.json
|
||||
results/lsa_char_logreg_20261025_164836/confusion_matrix.csv
|
||||
results/lsa_char_logreg_20261025_164836/confusion_matrix_normalized.csv
|
||||
results/lsa_char_logreg_20261025_164836/fold_metrics.csv
|
||||
results/lsa_char_logreg_20261025_164836/fold_summary.csv
|
||||
results/lsa_char_logreg_20261025_164836/oof_predictions.csv
|
||||
results/lsa_char_logreg_20261025_164836/per_class_report.csv
|
||||
results/lsa_char_logreg_20261025_164836/pooled_metrics.csv
|
||||
results/lsa_char_logreg_20261025_164836/run_metadata.json
|
||||
results/lsa_char_logreg_20261025_164836/summary.md
|
||||
results/lsa_char_logreg_20261025_164836/top_confusions.csv
|
||||
results/lsa_content_logreg_20261025_164717/bootstrap_ci.json
|
||||
results/lsa_content_logreg_20261025_164717/confusion_matrix.csv
|
||||
results/lsa_content_logreg_20261025_164717/confusion_matrix_normalized.csv
|
||||
results/lsa_content_logreg_20261025_164717/fold_metrics.csv
|
||||
results/lsa_content_logreg_20261025_164717/fold_summary.csv
|
||||
results/lsa_content_logreg_20261025_164717/oof_predictions.csv
|
||||
results/lsa_content_logreg_20261025_164717/per_class_report.csv
|
||||
results/lsa_content_logreg_20261025_164717/pooled_metrics.csv
|
||||
results/lsa_content_logreg_20261025_164717/run_metadata.json
|
||||
results/lsa_content_logreg_20261025_164717/summary.md
|
||||
results/lsa_content_logreg_20261025_164717/top_confusions.csv
|
||||
results/lsa_fw_logreg_20261025_164750/bootstrap_ci.json
|
||||
results/lsa_fw_logreg_20261025_164750/confusion_matrix.csv
|
||||
results/lsa_fw_logreg_20261025_164750/confusion_matrix_normalized.csv
|
||||
results/lsa_fw_logreg_20261025_164750/fold_metrics.csv
|
||||
results/lsa_fw_logreg_20261025_164750/fold_summary.csv
|
||||
results/lsa_fw_logreg_20261025_164750/oof_predictions.csv
|
||||
results/lsa_fw_logreg_20261025_164750/per_class_report.csv
|
||||
results/lsa_fw_logreg_20261025_164750/pooled_metrics.csv
|
||||
results/lsa_fw_logreg_20261025_164750/run_metadata.json
|
||||
results/lsa_fw_logreg_20261025_164750/summary.md
|
||||
results/lsa_fw_logreg_20261025_164750/top_confusions.csv
|
||||
results/lsa_pos_logreg_20261025_164912/bootstrap_ci.json
|
||||
results/lsa_pos_logreg_20261025_164912/confusion_matrix.csv
|
||||
results/lsa_pos_logreg_20261025_164912/confusion_matrix_normalized.csv
|
||||
results/lsa_pos_logreg_20261025_164912/fold_metrics.csv
|
||||
results/lsa_pos_logreg_20261025_164912/fold_summary.csv
|
||||
results/lsa_pos_logreg_20261025_164912/oof_predictions.csv
|
||||
results/lsa_pos_logreg_20261025_164912/per_class_report.csv
|
||||
results/lsa_pos_logreg_20261025_164912/pooled_metrics.csv
|
||||
results/lsa_pos_logreg_20261025_164912/run_metadata.json
|
||||
results/lsa_pos_logreg_20261025_164912/summary.md
|
||||
results/lsa_pos_logreg_20261025_164912/top_confusions.csv
|
||||
results/lsa_sweep_20261025_164924/lsa_sweep.csv
|
||||
results/lsa_word2_logreg_20261025_164641/bootstrap_ci.json
|
||||
results/lsa_word2_logreg_20261025_164641/confusion_matrix.csv
|
||||
results/lsa_word2_logreg_20261025_164641/confusion_matrix_normalized.csv
|
||||
results/lsa_word2_logreg_20261025_164641/fold_metrics.csv
|
||||
results/lsa_word2_logreg_20261025_164641/fold_summary.csv
|
||||
results/lsa_word2_logreg_20261025_164641/oof_predictions.csv
|
||||
results/lsa_word2_logreg_20261025_164641/per_class_report.csv
|
||||
results/lsa_word2_logreg_20261025_164641/pooled_metrics.csv
|
||||
results/lsa_word2_logreg_20261025_164641/run_metadata.json
|
||||
results/lsa_word2_logreg_20261025_164641/summary.md
|
||||
results/lsa_word2_logreg_20261025_164641/top_confusions.csv
|
||||
results/lsa_word_logreg_20261025_164608/bootstrap_ci.json
|
||||
results/lsa_word_logreg_20261025_164608/confusion_matrix.csv
|
||||
results/lsa_word_logreg_20261025_164608/confusion_matrix_normalized.csv
|
||||
results/lsa_word_logreg_20261025_164608/fold_metrics.csv
|
||||
results/lsa_word_logreg_20261025_164608/fold_summary.csv
|
||||
results/lsa_word_logreg_20261025_164608/oof_predictions.csv
|
||||
results/lsa_word_logreg_20261025_164608/per_class_report.csv
|
||||
results/lsa_word_logreg_20261025_164608/pooled_metrics.csv
|
||||
results/lsa_word_logreg_20261025_164608/run_metadata.json
|
||||
results/lsa_word_logreg_20261025_164608/summary.md
|
||||
results/lsa_word_logreg_20261025_164608/top_confusions.csv
|
||||
results/nested_stylometry_pos_lsa_linear_svc_20261025_172442/best_params.json
|
||||
results/nested_stylometry_pos_lsa_linear_svc_20261025_172442/bootstrap_ci.json
|
||||
results/nested_stylometry_pos_lsa_linear_svc_20261025_172442/confusion_matrix.csv
|
||||
results/nested_stylometry_pos_lsa_linear_svc_20261025_172442/confusion_matrix_normalized.csv
|
||||
results/nested_stylometry_pos_lsa_linear_svc_20261025_172442/fold_metrics.csv
|
||||
results/nested_stylometry_pos_lsa_linear_svc_20261025_172442/fold_summary.csv
|
||||
results/nested_stylometry_pos_lsa_linear_svc_20261025_172442/oof_predictions.csv
|
||||
results/nested_stylometry_pos_lsa_linear_svc_20261025_172442/per_class_report.csv
|
||||
results/nested_stylometry_pos_lsa_linear_svc_20261025_172442/pooled_metrics.csv
|
||||
results/nested_stylometry_pos_lsa_linear_svc_20261025_172442/run_metadata.json
|
||||
results/nested_stylometry_pos_lsa_linear_svc_20261025_172442/summary.md
|
||||
results/nested_stylometry_pos_lsa_linear_svc_20261025_172442/top_confusions.csv
|
||||
results/nested_stylometry_pos_lsa_logreg_20261025_172348/best_params.json
|
||||
results/nested_stylometry_pos_lsa_logreg_20261025_172348/bootstrap_ci.json
|
||||
results/nested_stylometry_pos_lsa_logreg_20261025_172348/confusion_matrix.csv
|
||||
results/nested_stylometry_pos_lsa_logreg_20261025_172348/confusion_matrix_normalized.csv
|
||||
results/nested_stylometry_pos_lsa_logreg_20261025_172348/fold_metrics.csv
|
||||
results/nested_stylometry_pos_lsa_logreg_20261025_172348/fold_summary.csv
|
||||
results/nested_stylometry_pos_lsa_logreg_20261025_172348/oof_predictions.csv
|
||||
results/nested_stylometry_pos_lsa_logreg_20261025_172348/per_class_report.csv
|
||||
results/nested_stylometry_pos_lsa_logreg_20261025_172348/pooled_metrics.csv
|
||||
results/nested_stylometry_pos_lsa_logreg_20261025_172348/run_metadata.json
|
||||
results/nested_stylometry_pos_lsa_logreg_20261025_172348/summary.md
|
||||
results/nested_stylometry_pos_lsa_logreg_20261025_172348/top_confusions.csv
|
||||
results/pos_multinomial_nb_20261025_155848/bootstrap_ci.json
|
||||
results/pos_multinomial_nb_20261025_155848/confusion_matrix.csv
|
||||
results/pos_multinomial_nb_20261025_155848/confusion_matrix_normalized.csv
|
||||
results/pos_multinomial_nb_20261025_155848/fold_metrics.csv
|
||||
results/pos_multinomial_nb_20261025_155848/fold_summary.csv
|
||||
results/pos_multinomial_nb_20261025_155848/oof_predictions.csv
|
||||
results/pos_multinomial_nb_20261025_155848/per_class_report.csv
|
||||
results/pos_multinomial_nb_20261025_155848/pooled_metrics.csv
|
||||
results/pos_multinomial_nb_20261025_155848/run_metadata.json
|
||||
results/pos_multinomial_nb_20261025_155848/summary.md
|
||||
results/pos_multinomial_nb_20261025_155848/top_confusions.csv
|
||||
results/README.md
|
||||
results/stylometry_linear_svc_20261025_164018/bootstrap_ci.json
|
||||
results/stylometry_linear_svc_20261025_164018/confusion_matrix.csv
|
||||
results/stylometry_linear_svc_20261025_164018/confusion_matrix_normalized.csv
|
||||
results/stylometry_linear_svc_20261025_164018/fold_metrics.csv
|
||||
results/stylometry_linear_svc_20261025_164018/fold_summary.csv
|
||||
results/stylometry_linear_svc_20261025_164018/oof_predictions.csv
|
||||
results/stylometry_linear_svc_20261025_164018/per_class_report.csv
|
||||
results/stylometry_linear_svc_20261025_164018/pooled_metrics.csv
|
||||
results/stylometry_linear_svc_20261025_164018/run_metadata.json
|
||||
results/stylometry_linear_svc_20261025_164018/summary.md
|
||||
results/stylometry_linear_svc_20261025_164018/top_confusions.csv
|
||||
results/stylometry_logreg_20261025_160722/bootstrap_ci.json
|
||||
results/stylometry_logreg_20261025_160722/confusion_matrix.csv
|
||||
results/stylometry_logreg_20261025_160722/confusion_matrix_normalized.csv
|
||||
results/stylometry_logreg_20261025_160722/fold_metrics.csv
|
||||
results/stylometry_logreg_20261025_160722/fold_summary.csv
|
||||
results/stylometry_logreg_20261025_160722/oof_predictions.csv
|
||||
results/stylometry_logreg_20261025_160722/per_class_report.csv
|
||||
results/stylometry_logreg_20261025_160722/pooled_metrics.csv
|
||||
results/stylometry_logreg_20261025_160722/run_metadata.json
|
||||
results/stylometry_logreg_20261025_160722/summary.md
|
||||
results/stylometry_logreg_20261025_160722/top_confusions.csv
|
||||
results/stylometry_pos_logreg_20261025_164246/bootstrap_ci.json
|
||||
results/stylometry_pos_logreg_20261025_164246/confusion_matrix.csv
|
||||
results/stylometry_pos_logreg_20261025_164246/confusion_matrix_normalized.csv
|
||||
results/stylometry_pos_logreg_20261025_164246/fold_metrics.csv
|
||||
results/stylometry_pos_logreg_20261025_164246/fold_summary.csv
|
||||
results/stylometry_pos_logreg_20261025_164246/oof_predictions.csv
|
||||
results/stylometry_pos_logreg_20261025_164246/per_class_report.csv
|
||||
results/stylometry_pos_logreg_20261025_164246/pooled_metrics.csv
|
||||
results/stylometry_pos_logreg_20261025_164246/run_metadata.json
|
||||
results/stylometry_pos_logreg_20261025_164246/summary.md
|
||||
results/stylometry_pos_logreg_20261025_164246/top_confusions.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_164534/bootstrap_ci.json
|
||||
results/stylometry_pos_lsa_logreg_20261025_164534/comparison_main_vs_pos.json
|
||||
results/stylometry_pos_lsa_logreg_20261025_164534/confusion_matrix.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_164534/confusion_matrix_normalized.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_164534/fold_metrics.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_164534/fold_summary.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_164534/length_bins.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_164534/oof_predictions.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_164534/per_class_report.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_164534/pooled_metrics.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_164534/run_metadata.json
|
||||
results/stylometry_pos_lsa_logreg_20261025_164534/subgroup_age_group_confusions.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_164534/subgroup_age_group_metrics.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_164534/subgroup_gender_confusions.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_164534/subgroup_gender_metrics.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_164534/subgroup_species_confusions.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_164534/subgroup_species_metrics.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_164534/summary.md
|
||||
results/stylometry_pos_lsa_logreg_20261025_164534/top_confusions.csv
|
||||
results/verification_stylometry_pos_lsa_20261025_172237/fold_metrics.csv
|
||||
results/verification_stylometry_pos_lsa_20261025_172237/pair_scores.csv
|
||||
results/verification_stylometry_pos_lsa_20261025_172237/summary.json
|
||||
@@ -1 +0,0 @@
|
||||
866cbec84ad3c79cdf7cdee14a534d736894037f68af88e4629262a5821cbefb /home/folen/Homework/NLP/lab1/exports/nlp_lab1_results_20261025_192444.tar.gz
|
||||
@@ -1,486 +0,0 @@
|
||||
Дата: Вс 25 окт 2026 19:24:44 MSK
|
||||
Каталог: /home/folen/Homework/NLP/lab1
|
||||
Интерпретатор: python
|
||||
|
||||
=== Проверка окружения ===
|
||||
Python: 3.11.2 (/home/folen/Homework/NLP/.venv/bin/python)
|
||||
Каталог проекта: /home/folen/Homework/NLP/lab1
|
||||
Версии библиотек:
|
||||
numpy: 2.4.6
|
||||
pandas: 3.1.0
|
||||
scipy: 1.17.1
|
||||
sklearn: 1.9.1
|
||||
nltk: 3.10.4
|
||||
matplotlib: 3.11.2
|
||||
Корпус:
|
||||
файл: /home/folen/Homework/NLP/lab1/data/dialogues.csv
|
||||
SHA-256: 8946f08159daddb9d89c9d323205d4a7fb4bc617d16e300ff6c7606cb05030f7
|
||||
реплик: 2005
|
||||
персонажей: 34
|
||||
словоупотреблений: 38207
|
||||
NLTK:
|
||||
токенизатор: NLTK
|
||||
POS-теггер: доступен
|
||||
пример токенов: ['Oh', ',', 'that', "'s", 'right', '...', 'I', 'heard']
|
||||
пример POS: ['INTJ', 'PUNCT', 'DET', 'AUX', 'ADJ', 'PUNCT', 'PRON', 'VERB']
|
||||
|
||||
Окружение готово.
|
||||
{"versions": {"numpy": "2.4.6", "pandas": "3.1.0", "scipy": "1.17.1", "sklearn": "1.9.1", "nltk": "3.10.4", "matplotlib": "3.11.2"}, "n_lines": 2005}
|
||||
|
||||
Git commit: 5a4a19e0df4cae19348ac76d242afb451d8e6aa0
|
||||
|
||||
Содержимое results:
|
||||
results/ablation_stylometry_pos_lsa_20261025_170116/ablation.csv
|
||||
results/benchmark_stylometry_pos_lsa_20261025_165348/summary.csv
|
||||
results/char_multinomial_nb_20261025_155832/bootstrap_ci.json
|
||||
results/char_multinomial_nb_20261025_155832/confusion_matrix.csv
|
||||
results/char_multinomial_nb_20261025_155832/confusion_matrix_normalized.csv
|
||||
results/char_multinomial_nb_20261025_155832/fold_metrics.csv
|
||||
results/char_multinomial_nb_20261025_155832/fold_summary.csv
|
||||
results/char_multinomial_nb_20261025_155832/oof_predictions.csv
|
||||
results/char_multinomial_nb_20261025_155832/per_class_report.csv
|
||||
results/char_multinomial_nb_20261025_155832/pooled_metrics.csv
|
||||
results/char_multinomial_nb_20261025_155832/run_metadata.json
|
||||
results/char_multinomial_nb_20261025_155832/summary.md
|
||||
results/char_multinomial_nb_20261025_155832/top_confusions.csv
|
||||
results/clustering_stylometry_pos_lsa_20261025_172241/character_centroids.csv
|
||||
results/clustering_stylometry_pos_lsa_20261025_172241/clustering_metrics.csv
|
||||
results/delta_20261025_172113/delta_sweep.csv
|
||||
results/fw_logreg_20261025_160058/bootstrap_ci.json
|
||||
results/fw_logreg_20261025_160058/confusion_matrix.csv
|
||||
results/fw_logreg_20261025_160058/confusion_matrix_normalized.csv
|
||||
results/fw_logreg_20261025_160058/fold_metrics.csv
|
||||
results/fw_logreg_20261025_160058/fold_summary.csv
|
||||
results/fw_logreg_20261025_160058/oof_predictions.csv
|
||||
results/fw_logreg_20261025_160058/per_class_report.csv
|
||||
results/fw_logreg_20261025_160058/pooled_metrics.csv
|
||||
results/fw_logreg_20261025_160058/run_metadata.json
|
||||
results/fw_logreg_20261025_160058/summary.md
|
||||
results/fw_logreg_20261025_160058/top_confusions.csv
|
||||
results/length_dummy_20261025_155747/bootstrap_ci.json
|
||||
results/length_dummy_20261025_155747/confusion_matrix.csv
|
||||
results/length_dummy_20261025_155747/confusion_matrix_normalized.csv
|
||||
results/length_dummy_20261025_155747/fold_metrics.csv
|
||||
results/length_dummy_20261025_155747/fold_summary.csv
|
||||
results/length_dummy_20261025_155747/oof_predictions.csv
|
||||
results/length_dummy_20261025_155747/per_class_report.csv
|
||||
results/length_dummy_20261025_155747/pooled_metrics.csv
|
||||
results/length_dummy_20261025_155747/run_metadata.json
|
||||
results/length_dummy_20261025_155747/summary.md
|
||||
results/length_dummy_20261025_155747/top_confusions.csv
|
||||
results/length_logreg_20261025_155719/bootstrap_ci.json
|
||||
results/length_logreg_20261025_155719/confusion_matrix.csv
|
||||
results/length_logreg_20261025_155719/confusion_matrix_normalized.csv
|
||||
results/length_logreg_20261025_155719/fold_metrics.csv
|
||||
results/length_logreg_20261025_155719/fold_summary.csv
|
||||
results/length_logreg_20261025_155719/oof_predictions.csv
|
||||
results/length_logreg_20261025_155719/per_class_report.csv
|
||||
results/length_logreg_20261025_155719/pooled_metrics.csv
|
||||
results/length_logreg_20261025_155719/run_metadata.json
|
||||
results/length_logreg_20261025_155719/summary.md
|
||||
results/length_logreg_20261025_155719/top_confusions.csv
|
||||
results/length_logreg_20261025_155813/bootstrap_ci.json
|
||||
results/length_logreg_20261025_155813/confusion_matrix.csv
|
||||
results/length_logreg_20261025_155813/confusion_matrix_normalized.csv
|
||||
results/length_logreg_20261025_155813/fold_metrics.csv
|
||||
results/length_logreg_20261025_155813/fold_summary.csv
|
||||
results/length_logreg_20261025_155813/oof_predictions.csv
|
||||
results/length_logreg_20261025_155813/per_class_report.csv
|
||||
results/length_logreg_20261025_155813/pooled_metrics.csv
|
||||
results/length_logreg_20261025_155813/run_metadata.json
|
||||
results/length_logreg_20261025_155813/summary.md
|
||||
results/length_logreg_20261025_155813/top_confusions.csv
|
||||
results/logs/run_all_20261025_150915.log
|
||||
results/logs/run_all_20261025_151312.log
|
||||
results/logs/run_all_20261025_152356.log
|
||||
results/logs/run_all_20261025_155738.log
|
||||
results/lsa_char_logreg_20261025_164836/bootstrap_ci.json
|
||||
results/lsa_char_logreg_20261025_164836/confusion_matrix.csv
|
||||
results/lsa_char_logreg_20261025_164836/confusion_matrix_normalized.csv
|
||||
results/lsa_char_logreg_20261025_164836/fold_metrics.csv
|
||||
results/lsa_char_logreg_20261025_164836/fold_summary.csv
|
||||
results/lsa_char_logreg_20261025_164836/oof_predictions.csv
|
||||
results/lsa_char_logreg_20261025_164836/per_class_report.csv
|
||||
results/lsa_char_logreg_20261025_164836/pooled_metrics.csv
|
||||
results/lsa_char_logreg_20261025_164836/run_metadata.json
|
||||
results/lsa_char_logreg_20261025_164836/summary.md
|
||||
results/lsa_char_logreg_20261025_164836/top_confusions.csv
|
||||
results/lsa_content_logreg_20261025_164717/bootstrap_ci.json
|
||||
results/lsa_content_logreg_20261025_164717/confusion_matrix.csv
|
||||
results/lsa_content_logreg_20261025_164717/confusion_matrix_normalized.csv
|
||||
results/lsa_content_logreg_20261025_164717/fold_metrics.csv
|
||||
results/lsa_content_logreg_20261025_164717/fold_summary.csv
|
||||
results/lsa_content_logreg_20261025_164717/oof_predictions.csv
|
||||
results/lsa_content_logreg_20261025_164717/per_class_report.csv
|
||||
results/lsa_content_logreg_20261025_164717/pooled_metrics.csv
|
||||
results/lsa_content_logreg_20261025_164717/run_metadata.json
|
||||
results/lsa_content_logreg_20261025_164717/summary.md
|
||||
results/lsa_content_logreg_20261025_164717/top_confusions.csv
|
||||
results/lsa_fw_logreg_20261025_164750/bootstrap_ci.json
|
||||
results/lsa_fw_logreg_20261025_164750/confusion_matrix.csv
|
||||
results/lsa_fw_logreg_20261025_164750/confusion_matrix_normalized.csv
|
||||
results/lsa_fw_logreg_20261025_164750/fold_metrics.csv
|
||||
results/lsa_fw_logreg_20261025_164750/fold_summary.csv
|
||||
results/lsa_fw_logreg_20261025_164750/oof_predictions.csv
|
||||
results/lsa_fw_logreg_20261025_164750/per_class_report.csv
|
||||
results/lsa_fw_logreg_20261025_164750/pooled_metrics.csv
|
||||
results/lsa_fw_logreg_20261025_164750/run_metadata.json
|
||||
results/lsa_fw_logreg_20261025_164750/summary.md
|
||||
results/lsa_fw_logreg_20261025_164750/top_confusions.csv
|
||||
results/lsa_pos_logreg_20261025_164912/bootstrap_ci.json
|
||||
results/lsa_pos_logreg_20261025_164912/confusion_matrix.csv
|
||||
results/lsa_pos_logreg_20261025_164912/confusion_matrix_normalized.csv
|
||||
results/lsa_pos_logreg_20261025_164912/fold_metrics.csv
|
||||
results/lsa_pos_logreg_20261025_164912/fold_summary.csv
|
||||
results/lsa_pos_logreg_20261025_164912/oof_predictions.csv
|
||||
results/lsa_pos_logreg_20261025_164912/per_class_report.csv
|
||||
results/lsa_pos_logreg_20261025_164912/pooled_metrics.csv
|
||||
results/lsa_pos_logreg_20261025_164912/run_metadata.json
|
||||
results/lsa_pos_logreg_20261025_164912/summary.md
|
||||
results/lsa_pos_logreg_20261025_164912/top_confusions.csv
|
||||
results/lsa_sweep_20261025_164924/lsa_sweep.csv
|
||||
results/lsa_sweep_20261025_185049/lsa_sweep.csv
|
||||
results/lsa_word2_logreg_20261025_164641/bootstrap_ci.json
|
||||
results/lsa_word2_logreg_20261025_164641/confusion_matrix.csv
|
||||
results/lsa_word2_logreg_20261025_164641/confusion_matrix_normalized.csv
|
||||
results/lsa_word2_logreg_20261025_164641/fold_metrics.csv
|
||||
results/lsa_word2_logreg_20261025_164641/fold_summary.csv
|
||||
results/lsa_word2_logreg_20261025_164641/oof_predictions.csv
|
||||
results/lsa_word2_logreg_20261025_164641/per_class_report.csv
|
||||
results/lsa_word2_logreg_20261025_164641/pooled_metrics.csv
|
||||
results/lsa_word2_logreg_20261025_164641/run_metadata.json
|
||||
results/lsa_word2_logreg_20261025_164641/summary.md
|
||||
results/lsa_word2_logreg_20261025_164641/top_confusions.csv
|
||||
results/lsa_word_logreg_20261025_164608/bootstrap_ci.json
|
||||
results/lsa_word_logreg_20261025_164608/confusion_matrix.csv
|
||||
results/lsa_word_logreg_20261025_164608/confusion_matrix_normalized.csv
|
||||
results/lsa_word_logreg_20261025_164608/fold_metrics.csv
|
||||
results/lsa_word_logreg_20261025_164608/fold_summary.csv
|
||||
results/lsa_word_logreg_20261025_164608/oof_predictions.csv
|
||||
results/lsa_word_logreg_20261025_164608/per_class_report.csv
|
||||
results/lsa_word_logreg_20261025_164608/pooled_metrics.csv
|
||||
results/lsa_word_logreg_20261025_164608/run_metadata.json
|
||||
results/lsa_word_logreg_20261025_164608/summary.md
|
||||
results/lsa_word_logreg_20261025_164608/top_confusions.csv
|
||||
results/nested_stylometry_pos_lsa_linear_svc_20261025_172442/best_params.json
|
||||
results/nested_stylometry_pos_lsa_linear_svc_20261025_172442/bootstrap_ci.json
|
||||
results/nested_stylometry_pos_lsa_linear_svc_20261025_172442/confusion_matrix.csv
|
||||
results/nested_stylometry_pos_lsa_linear_svc_20261025_172442/confusion_matrix_normalized.csv
|
||||
results/nested_stylometry_pos_lsa_linear_svc_20261025_172442/fold_metrics.csv
|
||||
results/nested_stylometry_pos_lsa_linear_svc_20261025_172442/fold_summary.csv
|
||||
results/nested_stylometry_pos_lsa_linear_svc_20261025_172442/oof_predictions.csv
|
||||
results/nested_stylometry_pos_lsa_linear_svc_20261025_172442/per_class_report.csv
|
||||
results/nested_stylometry_pos_lsa_linear_svc_20261025_172442/pooled_metrics.csv
|
||||
results/nested_stylometry_pos_lsa_linear_svc_20261025_172442/run_metadata.json
|
||||
results/nested_stylometry_pos_lsa_linear_svc_20261025_172442/summary.md
|
||||
results/nested_stylometry_pos_lsa_linear_svc_20261025_172442/top_confusions.csv
|
||||
results/nested_stylometry_pos_lsa_logreg_20261025_172348/best_params.json
|
||||
results/nested_stylometry_pos_lsa_logreg_20261025_172348/bootstrap_ci.json
|
||||
results/nested_stylometry_pos_lsa_logreg_20261025_172348/confusion_matrix.csv
|
||||
results/nested_stylometry_pos_lsa_logreg_20261025_172348/confusion_matrix_normalized.csv
|
||||
results/nested_stylometry_pos_lsa_logreg_20261025_172348/fold_metrics.csv
|
||||
results/nested_stylometry_pos_lsa_logreg_20261025_172348/fold_summary.csv
|
||||
results/nested_stylometry_pos_lsa_logreg_20261025_172348/oof_predictions.csv
|
||||
results/nested_stylometry_pos_lsa_logreg_20261025_172348/per_class_report.csv
|
||||
results/nested_stylometry_pos_lsa_logreg_20261025_172348/pooled_metrics.csv
|
||||
results/nested_stylometry_pos_lsa_logreg_20261025_172348/run_metadata.json
|
||||
results/nested_stylometry_pos_lsa_logreg_20261025_172348/summary.md
|
||||
results/nested_stylometry_pos_lsa_logreg_20261025_172348/top_confusions.csv
|
||||
results/pos_multinomial_nb_20261025_155848/bootstrap_ci.json
|
||||
results/pos_multinomial_nb_20261025_155848/confusion_matrix.csv
|
||||
results/pos_multinomial_nb_20261025_155848/confusion_matrix_normalized.csv
|
||||
results/pos_multinomial_nb_20261025_155848/fold_metrics.csv
|
||||
results/pos_multinomial_nb_20261025_155848/fold_summary.csv
|
||||
results/pos_multinomial_nb_20261025_155848/oof_predictions.csv
|
||||
results/pos_multinomial_nb_20261025_155848/per_class_report.csv
|
||||
results/pos_multinomial_nb_20261025_155848/pooled_metrics.csv
|
||||
results/pos_multinomial_nb_20261025_155848/run_metadata.json
|
||||
results/pos_multinomial_nb_20261025_155848/summary.md
|
||||
results/pos_multinomial_nb_20261025_155848/top_confusions.csv
|
||||
results/README.md
|
||||
results/stylometry_linear_svc_20261025_164018/bootstrap_ci.json
|
||||
results/stylometry_linear_svc_20261025_164018/confusion_matrix.csv
|
||||
results/stylometry_linear_svc_20261025_164018/confusion_matrix_normalized.csv
|
||||
results/stylometry_linear_svc_20261025_164018/fold_metrics.csv
|
||||
results/stylometry_linear_svc_20261025_164018/fold_summary.csv
|
||||
results/stylometry_linear_svc_20261025_164018/oof_predictions.csv
|
||||
results/stylometry_linear_svc_20261025_164018/per_class_report.csv
|
||||
results/stylometry_linear_svc_20261025_164018/pooled_metrics.csv
|
||||
results/stylometry_linear_svc_20261025_164018/run_metadata.json
|
||||
results/stylometry_linear_svc_20261025_164018/summary.md
|
||||
results/stylometry_linear_svc_20261025_164018/top_confusions.csv
|
||||
results/stylometry_logreg_20261025_160722/bootstrap_ci.json
|
||||
results/stylometry_logreg_20261025_160722/confusion_matrix.csv
|
||||
results/stylometry_logreg_20261025_160722/confusion_matrix_normalized.csv
|
||||
results/stylometry_logreg_20261025_160722/fold_metrics.csv
|
||||
results/stylometry_logreg_20261025_160722/fold_summary.csv
|
||||
results/stylometry_logreg_20261025_160722/oof_predictions.csv
|
||||
results/stylometry_logreg_20261025_160722/per_class_report.csv
|
||||
results/stylometry_logreg_20261025_160722/pooled_metrics.csv
|
||||
results/stylometry_logreg_20261025_160722/run_metadata.json
|
||||
results/stylometry_logreg_20261025_160722/summary.md
|
||||
results/stylometry_logreg_20261025_160722/top_confusions.csv
|
||||
results/stylometry_pos_logreg_20261025_164246/bootstrap_ci.json
|
||||
results/stylometry_pos_logreg_20261025_164246/confusion_matrix.csv
|
||||
results/stylometry_pos_logreg_20261025_164246/confusion_matrix_normalized.csv
|
||||
results/stylometry_pos_logreg_20261025_164246/fold_metrics.csv
|
||||
results/stylometry_pos_logreg_20261025_164246/fold_summary.csv
|
||||
results/stylometry_pos_logreg_20261025_164246/oof_predictions.csv
|
||||
results/stylometry_pos_logreg_20261025_164246/per_class_report.csv
|
||||
results/stylometry_pos_logreg_20261025_164246/pooled_metrics.csv
|
||||
results/stylometry_pos_logreg_20261025_164246/run_metadata.json
|
||||
results/stylometry_pos_logreg_20261025_164246/summary.md
|
||||
results/stylometry_pos_logreg_20261025_164246/top_confusions.csv
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_184431/bootstrap_ci.json
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_184431/confusion_matrix.csv
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_184431/confusion_matrix_normalized.csv
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_184431/fold_metrics.csv
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_184431/fold_summary.csv
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_184431/oof_predictions.csv
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_184431/per_class_report.csv
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_184431/pooled_metrics.csv
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_184431/run_metadata.json
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_184431/summary.md
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_184431/top_confusions.csv
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_184538/bootstrap_ci.json
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_184538/confusion_matrix.csv
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_184538/confusion_matrix_normalized.csv
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_184538/fold_metrics.csv
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_184538/fold_summary.csv
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_184538/oof_predictions.csv
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_184538/per_class_report.csv
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_184538/pooled_metrics.csv
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_184538/run_metadata.json
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_184538/summary.md
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_184538/top_confusions.csv
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_184646/bootstrap_ci.json
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_184646/confusion_matrix.csv
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_184646/confusion_matrix_normalized.csv
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_184646/fold_metrics.csv
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_184646/fold_summary.csv
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_184646/oof_predictions.csv
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_184646/per_class_report.csv
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_184646/pooled_metrics.csv
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_184646/run_metadata.json
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_184646/summary.md
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_184646/top_confusions.csv
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_184759/bootstrap_ci.json
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_184759/confusion_matrix.csv
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_184759/confusion_matrix_normalized.csv
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_184759/fold_metrics.csv
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_184759/fold_summary.csv
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_184759/oof_predictions.csv
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_184759/per_class_report.csv
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_184759/pooled_metrics.csv
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_184759/run_metadata.json
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_184759/summary.md
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_184759/top_confusions.csv
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_184916/bootstrap_ci.json
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_184916/confusion_matrix.csv
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_184916/confusion_matrix_normalized.csv
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_184916/fold_metrics.csv
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_184916/fold_summary.csv
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_184916/oof_predictions.csv
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_184916/per_class_report.csv
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_184916/pooled_metrics.csv
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_184916/run_metadata.json
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_184916/summary.md
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_184916/top_confusions.csv
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_185037/bootstrap_ci.json
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_185037/confusion_matrix.csv
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_185037/confusion_matrix_normalized.csv
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_185037/fold_metrics.csv
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_185037/fold_summary.csv
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_185037/oof_predictions.csv
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_185037/per_class_report.csv
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_185037/pooled_metrics.csv
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_185037/run_metadata.json
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_185037/summary.md
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_185037/top_confusions.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_164534/bootstrap_ci.json
|
||||
results/stylometry_pos_lsa_logreg_20261025_164534/comparison_main_vs_pos.json
|
||||
results/stylometry_pos_lsa_logreg_20261025_164534/confusion_matrix.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_164534/confusion_matrix_normalized.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_164534/fold_metrics.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_164534/fold_summary.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_164534/length_bins.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_164534/oof_predictions.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_164534/per_class_report.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_164534/pooled_metrics.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_164534/run_metadata.json
|
||||
results/stylometry_pos_lsa_logreg_20261025_164534/subgroup_age_group_confusions.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_164534/subgroup_age_group_metrics.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_164534/subgroup_gender_confusions.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_164534/subgroup_gender_metrics.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_164534/subgroup_species_confusions.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_164534/subgroup_species_metrics.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_164534/summary.md
|
||||
results/stylometry_pos_lsa_logreg_20261025_164534/top_confusions.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_183404/bootstrap_ci.json
|
||||
results/stylometry_pos_lsa_logreg_20261025_183404/confusion_matrix.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_183404/confusion_matrix_normalized.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_183404/fold_metrics.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_183404/fold_summary.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_183404/oof_predictions.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_183404/per_class_report.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_183404/pooled_metrics.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_183404/run_metadata.json
|
||||
results/stylometry_pos_lsa_logreg_20261025_183404/summary.md
|
||||
results/stylometry_pos_lsa_logreg_20261025_183404/top_confusions.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_183524/bootstrap_ci.json
|
||||
results/stylometry_pos_lsa_logreg_20261025_183524/confusion_matrix.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_183524/confusion_matrix_normalized.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_183524/fold_metrics.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_183524/fold_summary.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_183524/oof_predictions.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_183524/per_class_report.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_183524/pooled_metrics.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_183524/run_metadata.json
|
||||
results/stylometry_pos_lsa_logreg_20261025_183524/summary.md
|
||||
results/stylometry_pos_lsa_logreg_20261025_183524/top_confusions.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_183653/bootstrap_ci.json
|
||||
results/stylometry_pos_lsa_logreg_20261025_183653/confusion_matrix.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_183653/confusion_matrix_normalized.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_183653/fold_metrics.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_183653/fold_summary.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_183653/oof_predictions.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_183653/per_class_report.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_183653/pooled_metrics.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_183653/run_metadata.json
|
||||
results/stylometry_pos_lsa_logreg_20261025_183653/summary.md
|
||||
results/stylometry_pos_lsa_logreg_20261025_183653/top_confusions.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_183839/bootstrap_ci.json
|
||||
results/stylometry_pos_lsa_logreg_20261025_183839/confusion_matrix.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_183839/confusion_matrix_normalized.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_183839/fold_metrics.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_183839/fold_summary.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_183839/oof_predictions.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_183839/per_class_report.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_183839/pooled_metrics.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_183839/run_metadata.json
|
||||
results/stylometry_pos_lsa_logreg_20261025_183839/summary.md
|
||||
results/stylometry_pos_lsa_logreg_20261025_183839/top_confusions.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_184052/bootstrap_ci.json
|
||||
results/stylometry_pos_lsa_logreg_20261025_184052/confusion_matrix.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_184052/confusion_matrix_normalized.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_184052/fold_metrics.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_184052/fold_summary.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_184052/oof_predictions.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_184052/per_class_report.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_184052/pooled_metrics.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_184052/run_metadata.json
|
||||
results/stylometry_pos_lsa_logreg_20261025_184052/summary.md
|
||||
results/stylometry_pos_lsa_logreg_20261025_184052/top_confusions.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_184325/bootstrap_ci.json
|
||||
results/stylometry_pos_lsa_logreg_20261025_184325/confusion_matrix.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_184325/confusion_matrix_normalized.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_184325/fold_metrics.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_184325/fold_summary.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_184325/oof_predictions.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_184325/per_class_report.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_184325/pooled_metrics.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_184325/run_metadata.json
|
||||
results/stylometry_pos_lsa_logreg_20261025_184325/summary.md
|
||||
results/stylometry_pos_lsa_logreg_20261025_184325/top_confusions.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_190013/bootstrap_ci.json
|
||||
results/stylometry_pos_lsa_random_forest_20261025_190013/confusion_matrix.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_190013/confusion_matrix_normalized.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_190013/fold_metrics.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_190013/fold_summary.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_190013/oof_predictions.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_190013/per_class_report.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_190013/pooled_metrics.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_190013/run_metadata.json
|
||||
results/stylometry_pos_lsa_random_forest_20261025_190013/summary.md
|
||||
results/stylometry_pos_lsa_random_forest_20261025_190013/top_confusions.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_190125/bootstrap_ci.json
|
||||
results/stylometry_pos_lsa_random_forest_20261025_190125/confusion_matrix.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_190125/confusion_matrix_normalized.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_190125/fold_metrics.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_190125/fold_summary.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_190125/oof_predictions.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_190125/per_class_report.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_190125/pooled_metrics.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_190125/run_metadata.json
|
||||
results/stylometry_pos_lsa_random_forest_20261025_190125/summary.md
|
||||
results/stylometry_pos_lsa_random_forest_20261025_190125/top_confusions.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_190237/bootstrap_ci.json
|
||||
results/stylometry_pos_lsa_random_forest_20261025_190237/confusion_matrix.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_190237/confusion_matrix_normalized.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_190237/fold_metrics.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_190237/fold_summary.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_190237/oof_predictions.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_190237/per_class_report.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_190237/pooled_metrics.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_190237/run_metadata.json
|
||||
results/stylometry_pos_lsa_random_forest_20261025_190237/summary.md
|
||||
results/stylometry_pos_lsa_random_forest_20261025_190237/top_confusions.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_190354/bootstrap_ci.json
|
||||
results/stylometry_pos_lsa_random_forest_20261025_190354/confusion_matrix.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_190354/confusion_matrix_normalized.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_190354/fold_metrics.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_190354/fold_summary.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_190354/oof_predictions.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_190354/per_class_report.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_190354/pooled_metrics.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_190354/run_metadata.json
|
||||
results/stylometry_pos_lsa_random_forest_20261025_190354/summary.md
|
||||
results/stylometry_pos_lsa_random_forest_20261025_190354/top_confusions.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_190510/bootstrap_ci.json
|
||||
results/stylometry_pos_lsa_random_forest_20261025_190510/confusion_matrix.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_190510/confusion_matrix_normalized.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_190510/fold_metrics.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_190510/fold_summary.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_190510/oof_predictions.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_190510/per_class_report.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_190510/pooled_metrics.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_190510/run_metadata.json
|
||||
results/stylometry_pos_lsa_random_forest_20261025_190510/summary.md
|
||||
results/stylometry_pos_lsa_random_forest_20261025_190510/top_confusions.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_190626/bootstrap_ci.json
|
||||
results/stylometry_pos_lsa_random_forest_20261025_190626/confusion_matrix.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_190626/confusion_matrix_normalized.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_190626/fold_metrics.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_190626/fold_summary.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_190626/oof_predictions.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_190626/per_class_report.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_190626/pooled_metrics.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_190626/run_metadata.json
|
||||
results/stylometry_pos_lsa_random_forest_20261025_190626/summary.md
|
||||
results/stylometry_pos_lsa_random_forest_20261025_190626/top_confusions.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_190749/bootstrap_ci.json
|
||||
results/stylometry_pos_lsa_random_forest_20261025_190749/confusion_matrix.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_190749/confusion_matrix_normalized.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_190749/fold_metrics.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_190749/fold_summary.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_190749/oof_predictions.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_190749/per_class_report.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_190749/pooled_metrics.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_190749/run_metadata.json
|
||||
results/stylometry_pos_lsa_random_forest_20261025_190749/summary.md
|
||||
results/stylometry_pos_lsa_random_forest_20261025_190749/top_confusions.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_190910/bootstrap_ci.json
|
||||
results/stylometry_pos_lsa_random_forest_20261025_190910/confusion_matrix.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_190910/confusion_matrix_normalized.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_190910/fold_metrics.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_190910/fold_summary.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_190910/oof_predictions.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_190910/per_class_report.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_190910/pooled_metrics.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_190910/run_metadata.json
|
||||
results/stylometry_pos_lsa_random_forest_20261025_190910/summary.md
|
||||
results/stylometry_pos_lsa_random_forest_20261025_190910/top_confusions.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_191031/bootstrap_ci.json
|
||||
results/stylometry_pos_lsa_random_forest_20261025_191031/confusion_matrix.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_191031/confusion_matrix_normalized.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_191031/fold_metrics.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_191031/fold_summary.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_191031/oof_predictions.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_191031/per_class_report.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_191031/pooled_metrics.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_191031/run_metadata.json
|
||||
results/stylometry_pos_lsa_random_forest_20261025_191031/summary.md
|
||||
results/stylometry_pos_lsa_random_forest_20261025_191031/top_confusions.csv
|
||||
results/verification_stylometry_pos_lsa_20261025_172237/fold_metrics.csv
|
||||
results/verification_stylometry_pos_lsa_20261025_172237/pair_scores.csv
|
||||
results/verification_stylometry_pos_lsa_20261025_172237/summary.json
|
||||
@@ -0,0 +1 @@
|
||||
5f48946e4ecf6b1888df5bd99796457b9f49dacffd4da835ce8b6ed69895e2c4 /home/folen/homework/NLP/lab1/exports/nlp_lab1_results_20261025_210212.tar.gz
|
||||
389
lab1/exports/nlp_lab1_results_20261025_210212_manifest.txt
Normal file
@@ -0,0 +1,389 @@
|
||||
Дата: Вс 25 окт 2026 21:02:12 MSK
|
||||
Каталог: /home/folen/homework/NLP/lab1
|
||||
Интерпретатор: python
|
||||
|
||||
=== Проверка окружения ===
|
||||
Python: 3.11.2 (/home/folen/homework/NLP/lab1/.venv/bin/python)
|
||||
Каталог проекта: /home/folen/homework/NLP/lab1
|
||||
Версии библиотек:
|
||||
numpy: 2.4.6
|
||||
pandas: 3.1.0
|
||||
scipy: 1.17.1
|
||||
sklearn: 1.9.1
|
||||
nltk: 3.10.4
|
||||
matplotlib: 3.11.2
|
||||
Корпус:
|
||||
файл: /home/folen/homework/NLP/lab1/data/dialogues.csv
|
||||
SHA-256: 8946f08159daddb9d89c9d323205d4a7fb4bc617d16e300ff6c7606cb05030f7
|
||||
реплик: 2005
|
||||
персонажей: 34
|
||||
словоупотреблений: 38207
|
||||
NLTK:
|
||||
токенизатор: NLTK
|
||||
POS-теггер: доступен
|
||||
пример токенов: ['Oh', ',', 'that', "'s", 'right', '...', 'I', 'heard']
|
||||
пример POS: ['INTJ', 'PUNCT', 'DET', 'AUX', 'ADJ', 'PUNCT', 'PRON', 'VERB']
|
||||
|
||||
Окружение готово.
|
||||
{"versions": {"numpy": "2.4.6", "pandas": "3.1.0", "scipy": "1.17.1", "sklearn": "1.9.1", "nltk": "3.10.4", "matplotlib": "3.11.2"}, "n_lines": 2005}
|
||||
|
||||
Git commit: c131815fc7b4c941d64d9ca7c7a00ea15df20df3
|
||||
|
||||
Содержимое results:
|
||||
results/ablation_stylometry_pos_lsa_20261025_185002/ablation.csv
|
||||
results/delta_20261025_201339/delta_sweep.csv
|
||||
results/length_logreg_20261025_172732/bootstrap_ci.json
|
||||
results/length_logreg_20261025_172732/confusion_matrix.csv
|
||||
results/length_logreg_20261025_172732/confusion_matrix_normalized.csv
|
||||
results/length_logreg_20261025_172732/fold_metrics.csv
|
||||
results/length_logreg_20261025_172732/fold_summary.csv
|
||||
results/length_logreg_20261025_172732/oof_predictions.csv
|
||||
results/length_logreg_20261025_172732/per_class_report.csv
|
||||
results/length_logreg_20261025_172732/pooled_metrics.csv
|
||||
results/length_logreg_20261025_172732/run_metadata.json
|
||||
results/length_logreg_20261025_172732/summary.md
|
||||
results/length_logreg_20261025_172732/top_confusions.csv
|
||||
results/logs/tune_20261025_191658.log
|
||||
results/lsa_sweep_20261025_195217/lsa_sweep.csv
|
||||
results/README.md
|
||||
results/stylometry_pos_lsa_extra_trees_20261025_201326/bootstrap_ci.json
|
||||
results/stylometry_pos_lsa_extra_trees_20261025_201326/confusion_matrix.csv
|
||||
results/stylometry_pos_lsa_extra_trees_20261025_201326/confusion_matrix_normalized.csv
|
||||
results/stylometry_pos_lsa_extra_trees_20261025_201326/fold_metrics.csv
|
||||
results/stylometry_pos_lsa_extra_trees_20261025_201326/fold_summary.csv
|
||||
results/stylometry_pos_lsa_extra_trees_20261025_201326/oof_predictions.csv
|
||||
results/stylometry_pos_lsa_extra_trees_20261025_201326/per_class_report.csv
|
||||
results/stylometry_pos_lsa_extra_trees_20261025_201326/pooled_metrics.csv
|
||||
results/stylometry_pos_lsa_extra_trees_20261025_201326/run_metadata.json
|
||||
results/stylometry_pos_lsa_extra_trees_20261025_201326/summary.md
|
||||
results/stylometry_pos_lsa_extra_trees_20261025_201326/top_confusions.csv
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_192936/bootstrap_ci.json
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_192936/confusion_matrix.csv
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_192936/confusion_matrix_normalized.csv
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_192936/fold_metrics.csv
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_192936/fold_summary.csv
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_192936/oof_predictions.csv
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_192936/per_class_report.csv
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_192936/pooled_metrics.csv
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_192936/run_metadata.json
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_192936/summary.md
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_192936/top_confusions.csv
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_193051/bootstrap_ci.json
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_193051/confusion_matrix.csv
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_193051/confusion_matrix_normalized.csv
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_193051/fold_metrics.csv
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_193051/fold_summary.csv
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_193051/oof_predictions.csv
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_193051/per_class_report.csv
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_193051/pooled_metrics.csv
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_193051/run_metadata.json
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_193051/summary.md
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_193051/top_confusions.csv
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_193207/bootstrap_ci.json
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_193207/confusion_matrix.csv
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_193207/confusion_matrix_normalized.csv
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_193207/fold_metrics.csv
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_193207/fold_summary.csv
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_193207/oof_predictions.csv
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_193207/per_class_report.csv
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_193207/pooled_metrics.csv
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_193207/run_metadata.json
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_193207/summary.md
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_193207/top_confusions.csv
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_193328/bootstrap_ci.json
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_193328/confusion_matrix.csv
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_193328/confusion_matrix_normalized.csv
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_193328/fold_metrics.csv
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_193328/fold_summary.csv
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_193328/oof_predictions.csv
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_193328/per_class_report.csv
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_193328/pooled_metrics.csv
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_193328/run_metadata.json
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_193328/summary.md
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_193328/top_confusions.csv
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_193455/bootstrap_ci.json
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_193455/confusion_matrix.csv
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_193455/confusion_matrix_normalized.csv
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_193455/fold_metrics.csv
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_193455/fold_summary.csv
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_193455/oof_predictions.csv
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_193455/per_class_report.csv
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_193455/pooled_metrics.csv
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_193455/run_metadata.json
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_193455/summary.md
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_193455/top_confusions.csv
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_193625/bootstrap_ci.json
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_193625/confusion_matrix.csv
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_193625/confusion_matrix_normalized.csv
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_193625/fold_metrics.csv
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_193625/fold_summary.csv
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_193625/oof_predictions.csv
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_193625/per_class_report.csv
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_193625/pooled_metrics.csv
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_193625/run_metadata.json
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_193625/summary.md
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_193625/top_confusions.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_191805/bootstrap_ci.json
|
||||
results/stylometry_pos_lsa_logreg_20261025_191805/confusion_matrix.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_191805/confusion_matrix_normalized.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_191805/fold_metrics.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_191805/fold_summary.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_191805/oof_predictions.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_191805/per_class_report.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_191805/pooled_metrics.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_191805/run_metadata.json
|
||||
results/stylometry_pos_lsa_logreg_20261025_191805/summary.md
|
||||
results/stylometry_pos_lsa_logreg_20261025_191805/top_confusions.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_191938/bootstrap_ci.json
|
||||
results/stylometry_pos_lsa_logreg_20261025_191938/confusion_matrix.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_191938/confusion_matrix_normalized.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_191938/fold_metrics.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_191938/fold_summary.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_191938/oof_predictions.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_191938/per_class_report.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_191938/pooled_metrics.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_191938/run_metadata.json
|
||||
results/stylometry_pos_lsa_logreg_20261025_191938/summary.md
|
||||
results/stylometry_pos_lsa_logreg_20261025_191938/top_confusions.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_192116/bootstrap_ci.json
|
||||
results/stylometry_pos_lsa_logreg_20261025_192116/confusion_matrix.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_192116/confusion_matrix_normalized.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_192116/fold_metrics.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_192116/fold_summary.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_192116/oof_predictions.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_192116/per_class_report.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_192116/pooled_metrics.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_192116/run_metadata.json
|
||||
results/stylometry_pos_lsa_logreg_20261025_192116/summary.md
|
||||
results/stylometry_pos_lsa_logreg_20261025_192116/top_confusions.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_192311/bootstrap_ci.json
|
||||
results/stylometry_pos_lsa_logreg_20261025_192311/confusion_matrix.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_192311/confusion_matrix_normalized.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_192311/fold_metrics.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_192311/fold_summary.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_192311/oof_predictions.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_192311/per_class_report.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_192311/pooled_metrics.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_192311/run_metadata.json
|
||||
results/stylometry_pos_lsa_logreg_20261025_192311/summary.md
|
||||
results/stylometry_pos_lsa_logreg_20261025_192311/top_confusions.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_192535/bootstrap_ci.json
|
||||
results/stylometry_pos_lsa_logreg_20261025_192535/confusion_matrix.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_192535/confusion_matrix_normalized.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_192535/fold_metrics.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_192535/fold_summary.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_192535/oof_predictions.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_192535/per_class_report.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_192535/pooled_metrics.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_192535/run_metadata.json
|
||||
results/stylometry_pos_lsa_logreg_20261025_192535/summary.md
|
||||
results/stylometry_pos_lsa_logreg_20261025_192535/top_confusions.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_192822/bootstrap_ci.json
|
||||
results/stylometry_pos_lsa_logreg_20261025_192822/confusion_matrix.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_192822/confusion_matrix_normalized.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_192822/fold_metrics.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_192822/fold_summary.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_192822/oof_predictions.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_192822/per_class_report.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_192822/pooled_metrics.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_192822/run_metadata.json
|
||||
results/stylometry_pos_lsa_logreg_20261025_192822/summary.md
|
||||
results/stylometry_pos_lsa_logreg_20261025_192822/top_confusions.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_193844/bootstrap_ci.json
|
||||
results/stylometry_pos_lsa_logreg_20261025_193844/confusion_matrix.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_193844/confusion_matrix_normalized.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_193844/fold_metrics.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_193844/fold_summary.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_193844/oof_predictions.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_193844/per_class_report.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_193844/pooled_metrics.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_193844/run_metadata.json
|
||||
results/stylometry_pos_lsa_logreg_20261025_193844/summary.md
|
||||
results/stylometry_pos_lsa_logreg_20261025_193844/top_confusions.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_194037/bootstrap_ci.json
|
||||
results/stylometry_pos_lsa_logreg_20261025_194037/confusion_matrix.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_194037/confusion_matrix_normalized.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_194037/fold_metrics.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_194037/fold_summary.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_194037/oof_predictions.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_194037/per_class_report.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_194037/pooled_metrics.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_194037/run_metadata.json
|
||||
results/stylometry_pos_lsa_logreg_20261025_194037/summary.md
|
||||
results/stylometry_pos_lsa_logreg_20261025_194037/top_confusions.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_194222/bootstrap_ci.json
|
||||
results/stylometry_pos_lsa_logreg_20261025_194222/confusion_matrix.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_194222/confusion_matrix_normalized.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_194222/fold_metrics.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_194222/fold_summary.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_194222/oof_predictions.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_194222/per_class_report.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_194222/pooled_metrics.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_194222/run_metadata.json
|
||||
results/stylometry_pos_lsa_logreg_20261025_194222/summary.md
|
||||
results/stylometry_pos_lsa_logreg_20261025_194222/top_confusions.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_194415/bootstrap_ci.json
|
||||
results/stylometry_pos_lsa_logreg_20261025_194415/confusion_matrix.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_194415/confusion_matrix_normalized.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_194415/fold_metrics.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_194415/fold_summary.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_194415/oof_predictions.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_194415/per_class_report.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_194415/pooled_metrics.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_194415/run_metadata.json
|
||||
results/stylometry_pos_lsa_logreg_20261025_194415/summary.md
|
||||
results/stylometry_pos_lsa_logreg_20261025_194415/top_confusions.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_194613/bootstrap_ci.json
|
||||
results/stylometry_pos_lsa_logreg_20261025_194613/confusion_matrix.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_194613/confusion_matrix_normalized.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_194613/fold_metrics.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_194613/fold_summary.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_194613/oof_predictions.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_194613/per_class_report.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_194613/pooled_metrics.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_194613/run_metadata.json
|
||||
results/stylometry_pos_lsa_logreg_20261025_194613/summary.md
|
||||
results/stylometry_pos_lsa_logreg_20261025_194613/top_confusions.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_194802/bootstrap_ci.json
|
||||
results/stylometry_pos_lsa_logreg_20261025_194802/confusion_matrix.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_194802/confusion_matrix_normalized.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_194802/fold_metrics.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_194802/fold_summary.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_194802/oof_predictions.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_194802/per_class_report.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_194802/pooled_metrics.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_194802/run_metadata.json
|
||||
results/stylometry_pos_lsa_logreg_20261025_194802/summary.md
|
||||
results/stylometry_pos_lsa_logreg_20261025_194802/top_confusions.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_194959/bootstrap_ci.json
|
||||
results/stylometry_pos_lsa_logreg_20261025_194959/confusion_matrix.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_194959/confusion_matrix_normalized.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_194959/fold_metrics.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_194959/fold_summary.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_194959/oof_predictions.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_194959/per_class_report.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_194959/pooled_metrics.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_194959/run_metadata.json
|
||||
results/stylometry_pos_lsa_logreg_20261025_194959/summary.md
|
||||
results/stylometry_pos_lsa_logreg_20261025_194959/top_confusions.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_195203/bootstrap_ci.json
|
||||
results/stylometry_pos_lsa_logreg_20261025_195203/confusion_matrix.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_195203/confusion_matrix_normalized.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_195203/fold_metrics.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_195203/fold_summary.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_195203/oof_predictions.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_195203/per_class_report.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_195203/pooled_metrics.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_195203/run_metadata.json
|
||||
results/stylometry_pos_lsa_logreg_20261025_195203/summary.md
|
||||
results/stylometry_pos_lsa_logreg_20261025_195203/top_confusions.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_195744/bootstrap_ci.json
|
||||
results/stylometry_pos_lsa_random_forest_20261025_195744/confusion_matrix.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_195744/confusion_matrix_normalized.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_195744/fold_metrics.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_195744/fold_summary.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_195744/oof_predictions.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_195744/per_class_report.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_195744/pooled_metrics.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_195744/run_metadata.json
|
||||
results/stylometry_pos_lsa_random_forest_20261025_195744/summary.md
|
||||
results/stylometry_pos_lsa_random_forest_20261025_195744/top_confusions.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_195906/bootstrap_ci.json
|
||||
results/stylometry_pos_lsa_random_forest_20261025_195906/confusion_matrix.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_195906/confusion_matrix_normalized.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_195906/fold_metrics.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_195906/fold_summary.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_195906/oof_predictions.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_195906/per_class_report.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_195906/pooled_metrics.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_195906/run_metadata.json
|
||||
results/stylometry_pos_lsa_random_forest_20261025_195906/summary.md
|
||||
results/stylometry_pos_lsa_random_forest_20261025_195906/top_confusions.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_200028/bootstrap_ci.json
|
||||
results/stylometry_pos_lsa_random_forest_20261025_200028/confusion_matrix.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_200028/confusion_matrix_normalized.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_200028/fold_metrics.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_200028/fold_summary.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_200028/oof_predictions.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_200028/per_class_report.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_200028/pooled_metrics.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_200028/run_metadata.json
|
||||
results/stylometry_pos_lsa_random_forest_20261025_200028/summary.md
|
||||
results/stylometry_pos_lsa_random_forest_20261025_200028/top_confusions.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_200155/bootstrap_ci.json
|
||||
results/stylometry_pos_lsa_random_forest_20261025_200155/confusion_matrix.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_200155/confusion_matrix_normalized.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_200155/fold_metrics.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_200155/fold_summary.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_200155/oof_predictions.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_200155/per_class_report.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_200155/pooled_metrics.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_200155/run_metadata.json
|
||||
results/stylometry_pos_lsa_random_forest_20261025_200155/summary.md
|
||||
results/stylometry_pos_lsa_random_forest_20261025_200155/top_confusions.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_200320/bootstrap_ci.json
|
||||
results/stylometry_pos_lsa_random_forest_20261025_200320/confusion_matrix.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_200320/confusion_matrix_normalized.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_200320/fold_metrics.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_200320/fold_summary.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_200320/oof_predictions.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_200320/per_class_report.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_200320/pooled_metrics.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_200320/run_metadata.json
|
||||
results/stylometry_pos_lsa_random_forest_20261025_200320/summary.md
|
||||
results/stylometry_pos_lsa_random_forest_20261025_200320/top_confusions.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_200445/bootstrap_ci.json
|
||||
results/stylometry_pos_lsa_random_forest_20261025_200445/confusion_matrix.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_200445/confusion_matrix_normalized.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_200445/fold_metrics.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_200445/fold_summary.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_200445/oof_predictions.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_200445/per_class_report.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_200445/pooled_metrics.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_200445/run_metadata.json
|
||||
results/stylometry_pos_lsa_random_forest_20261025_200445/summary.md
|
||||
results/stylometry_pos_lsa_random_forest_20261025_200445/top_confusions.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_200617/bootstrap_ci.json
|
||||
results/stylometry_pos_lsa_random_forest_20261025_200617/confusion_matrix.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_200617/confusion_matrix_normalized.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_200617/fold_metrics.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_200617/fold_summary.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_200617/oof_predictions.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_200617/per_class_report.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_200617/pooled_metrics.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_200617/run_metadata.json
|
||||
results/stylometry_pos_lsa_random_forest_20261025_200617/summary.md
|
||||
results/stylometry_pos_lsa_random_forest_20261025_200617/top_confusions.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_200748/bootstrap_ci.json
|
||||
results/stylometry_pos_lsa_random_forest_20261025_200748/confusion_matrix.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_200748/confusion_matrix_normalized.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_200748/fold_metrics.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_200748/fold_summary.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_200748/oof_predictions.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_200748/per_class_report.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_200748/pooled_metrics.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_200748/run_metadata.json
|
||||
results/stylometry_pos_lsa_random_forest_20261025_200748/summary.md
|
||||
results/stylometry_pos_lsa_random_forest_20261025_200748/top_confusions.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_200916/bootstrap_ci.json
|
||||
results/stylometry_pos_lsa_random_forest_20261025_200916/confusion_matrix.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_200916/confusion_matrix_normalized.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_200916/fold_metrics.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_200916/fold_summary.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_200916/oof_predictions.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_200916/per_class_report.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_200916/pooled_metrics.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_200916/run_metadata.json
|
||||
results/stylometry_pos_lsa_random_forest_20261025_200916/summary.md
|
||||
results/stylometry_pos_lsa_random_forest_20261025_200916/top_confusions.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_201210/bootstrap_ci.json
|
||||
results/stylometry_pos_lsa_random_forest_20261025_201210/confusion_matrix.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_201210/confusion_matrix_normalized.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_201210/fold_metrics.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_201210/fold_summary.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_201210/oof_predictions.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_201210/per_class_report.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_201210/pooled_metrics.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_201210/run_metadata.json
|
||||
results/stylometry_pos_lsa_random_forest_20261025_201210/summary.md
|
||||
results/stylometry_pos_lsa_random_forest_20261025_201210/top_confusions.csv
|
||||
@@ -1 +0,0 @@
|
||||
f9566b75e0474a72271263bf203f892979e638750cddc980be9a69f690aaa884 /home/folen/Homework/NLP/lab1/exports/nlp_lab1_results_20261026_054117.tar.gz
|
||||
@@ -1,493 +0,0 @@
|
||||
Дата: Пн 26 окт 2026 05:41:17 MSK
|
||||
Каталог: /home/folen/Homework/NLP/lab1
|
||||
Интерпретатор: python
|
||||
|
||||
=== Проверка окружения ===
|
||||
Python: 3.11.2 (/home/folen/Homework/NLP/.venv/bin/python)
|
||||
Каталог проекта: /home/folen/Homework/NLP/lab1
|
||||
Версии библиотек:
|
||||
numpy: 2.4.6
|
||||
pandas: 3.1.0
|
||||
scipy: 1.17.1
|
||||
sklearn: 1.9.1
|
||||
nltk: 3.10.4
|
||||
matplotlib: 3.11.2
|
||||
Корпус:
|
||||
файл: /home/folen/Homework/NLP/lab1/data/dialogues.csv
|
||||
SHA-256: 8946f08159daddb9d89c9d323205d4a7fb4bc617d16e300ff6c7606cb05030f7
|
||||
реплик: 2005
|
||||
персонажей: 34
|
||||
словоупотреблений: 38207
|
||||
NLTK:
|
||||
токенизатор: NLTK
|
||||
POS-теггер: доступен
|
||||
пример токенов: ['Oh', ',', 'that', "'s", 'right', '...', 'I', 'heard']
|
||||
пример POS: ['INTJ', 'PUNCT', 'DET', 'AUX', 'ADJ', 'PUNCT', 'PRON', 'VERB']
|
||||
|
||||
Окружение готово.
|
||||
{"versions": {"numpy": "2.4.6", "pandas": "3.1.0", "scipy": "1.17.1", "sklearn": "1.9.1", "nltk": "3.10.4", "matplotlib": "3.11.2"}, "n_lines": 2005}
|
||||
|
||||
Git commit: 2c6bf4653adc6c356e4af205857dfdb0f144775b
|
||||
|
||||
Содержимое results:
|
||||
results/ablation_stylometry_pos_lsa_20261025_170116/ablation.csv
|
||||
results/benchmark_stylometry_pos_lsa_20261025_165348/summary.csv
|
||||
results/char_multinomial_nb_20261025_155832/bootstrap_ci.json
|
||||
results/char_multinomial_nb_20261025_155832/confusion_matrix.csv
|
||||
results/char_multinomial_nb_20261025_155832/confusion_matrix_normalized.csv
|
||||
results/char_multinomial_nb_20261025_155832/fold_metrics.csv
|
||||
results/char_multinomial_nb_20261025_155832/fold_summary.csv
|
||||
results/char_multinomial_nb_20261025_155832/oof_predictions.csv
|
||||
results/char_multinomial_nb_20261025_155832/per_class_report.csv
|
||||
results/char_multinomial_nb_20261025_155832/pooled_metrics.csv
|
||||
results/char_multinomial_nb_20261025_155832/run_metadata.json
|
||||
results/char_multinomial_nb_20261025_155832/summary.md
|
||||
results/char_multinomial_nb_20261025_155832/top_confusions.csv
|
||||
results/clustering_stylometry_pos_lsa_20261025_172241/character_centroids.csv
|
||||
results/clustering_stylometry_pos_lsa_20261025_172241/clustering_metrics.csv
|
||||
results/delta_20261025_172113/delta_sweep.csv
|
||||
results/followup/comparisons.csv
|
||||
results/followup/comparisons.md
|
||||
results/followup/manifest.tsv
|
||||
results/followup/summary.csv
|
||||
results/followup/summary.md
|
||||
results/fw_logreg_20261025_160058/bootstrap_ci.json
|
||||
results/fw_logreg_20261025_160058/confusion_matrix.csv
|
||||
results/fw_logreg_20261025_160058/confusion_matrix_normalized.csv
|
||||
results/fw_logreg_20261025_160058/fold_metrics.csv
|
||||
results/fw_logreg_20261025_160058/fold_summary.csv
|
||||
results/fw_logreg_20261025_160058/oof_predictions.csv
|
||||
results/fw_logreg_20261025_160058/per_class_report.csv
|
||||
results/fw_logreg_20261025_160058/pooled_metrics.csv
|
||||
results/fw_logreg_20261025_160058/run_metadata.json
|
||||
results/fw_logreg_20261025_160058/summary.md
|
||||
results/fw_logreg_20261025_160058/top_confusions.csv
|
||||
results/length_dummy_20261025_155747/bootstrap_ci.json
|
||||
results/length_dummy_20261025_155747/confusion_matrix.csv
|
||||
results/length_dummy_20261025_155747/confusion_matrix_normalized.csv
|
||||
results/length_dummy_20261025_155747/fold_metrics.csv
|
||||
results/length_dummy_20261025_155747/fold_summary.csv
|
||||
results/length_dummy_20261025_155747/oof_predictions.csv
|
||||
results/length_dummy_20261025_155747/per_class_report.csv
|
||||
results/length_dummy_20261025_155747/pooled_metrics.csv
|
||||
results/length_dummy_20261025_155747/run_metadata.json
|
||||
results/length_dummy_20261025_155747/summary.md
|
||||
results/length_dummy_20261025_155747/top_confusions.csv
|
||||
results/length_logreg_20261025_155719/bootstrap_ci.json
|
||||
results/length_logreg_20261025_155719/confusion_matrix.csv
|
||||
results/length_logreg_20261025_155719/confusion_matrix_normalized.csv
|
||||
results/length_logreg_20261025_155719/fold_metrics.csv
|
||||
results/length_logreg_20261025_155719/fold_summary.csv
|
||||
results/length_logreg_20261025_155719/oof_predictions.csv
|
||||
results/length_logreg_20261025_155719/per_class_report.csv
|
||||
results/length_logreg_20261025_155719/pooled_metrics.csv
|
||||
results/length_logreg_20261025_155719/run_metadata.json
|
||||
results/length_logreg_20261025_155719/summary.md
|
||||
results/length_logreg_20261025_155719/top_confusions.csv
|
||||
results/length_logreg_20261025_155813/bootstrap_ci.json
|
||||
results/length_logreg_20261025_155813/confusion_matrix.csv
|
||||
results/length_logreg_20261025_155813/confusion_matrix_normalized.csv
|
||||
results/length_logreg_20261025_155813/fold_metrics.csv
|
||||
results/length_logreg_20261025_155813/fold_summary.csv
|
||||
results/length_logreg_20261025_155813/oof_predictions.csv
|
||||
results/length_logreg_20261025_155813/per_class_report.csv
|
||||
results/length_logreg_20261025_155813/pooled_metrics.csv
|
||||
results/length_logreg_20261025_155813/run_metadata.json
|
||||
results/length_logreg_20261025_155813/summary.md
|
||||
results/length_logreg_20261025_155813/top_confusions.csv
|
||||
results/logs/run_all_20261025_150915.log
|
||||
results/logs/run_all_20261025_151312.log
|
||||
results/logs/run_all_20261025_152356.log
|
||||
results/logs/run_all_20261025_155738.log
|
||||
results/logs/run_followup_20261025_210825.log
|
||||
results/logs/run_followup_smoke.log
|
||||
results/lsa_char_logreg_20261025_164836/bootstrap_ci.json
|
||||
results/lsa_char_logreg_20261025_164836/confusion_matrix.csv
|
||||
results/lsa_char_logreg_20261025_164836/confusion_matrix_normalized.csv
|
||||
results/lsa_char_logreg_20261025_164836/fold_metrics.csv
|
||||
results/lsa_char_logreg_20261025_164836/fold_summary.csv
|
||||
results/lsa_char_logreg_20261025_164836/oof_predictions.csv
|
||||
results/lsa_char_logreg_20261025_164836/per_class_report.csv
|
||||
results/lsa_char_logreg_20261025_164836/pooled_metrics.csv
|
||||
results/lsa_char_logreg_20261025_164836/run_metadata.json
|
||||
results/lsa_char_logreg_20261025_164836/summary.md
|
||||
results/lsa_char_logreg_20261025_164836/top_confusions.csv
|
||||
results/lsa_content_logreg_20261025_164717/bootstrap_ci.json
|
||||
results/lsa_content_logreg_20261025_164717/confusion_matrix.csv
|
||||
results/lsa_content_logreg_20261025_164717/confusion_matrix_normalized.csv
|
||||
results/lsa_content_logreg_20261025_164717/fold_metrics.csv
|
||||
results/lsa_content_logreg_20261025_164717/fold_summary.csv
|
||||
results/lsa_content_logreg_20261025_164717/oof_predictions.csv
|
||||
results/lsa_content_logreg_20261025_164717/per_class_report.csv
|
||||
results/lsa_content_logreg_20261025_164717/pooled_metrics.csv
|
||||
results/lsa_content_logreg_20261025_164717/run_metadata.json
|
||||
results/lsa_content_logreg_20261025_164717/summary.md
|
||||
results/lsa_content_logreg_20261025_164717/top_confusions.csv
|
||||
results/lsa_fw_logreg_20261025_164750/bootstrap_ci.json
|
||||
results/lsa_fw_logreg_20261025_164750/confusion_matrix.csv
|
||||
results/lsa_fw_logreg_20261025_164750/confusion_matrix_normalized.csv
|
||||
results/lsa_fw_logreg_20261025_164750/fold_metrics.csv
|
||||
results/lsa_fw_logreg_20261025_164750/fold_summary.csv
|
||||
results/lsa_fw_logreg_20261025_164750/oof_predictions.csv
|
||||
results/lsa_fw_logreg_20261025_164750/per_class_report.csv
|
||||
results/lsa_fw_logreg_20261025_164750/pooled_metrics.csv
|
||||
results/lsa_fw_logreg_20261025_164750/run_metadata.json
|
||||
results/lsa_fw_logreg_20261025_164750/summary.md
|
||||
results/lsa_fw_logreg_20261025_164750/top_confusions.csv
|
||||
results/lsa_pos_logreg_20261025_164912/bootstrap_ci.json
|
||||
results/lsa_pos_logreg_20261025_164912/confusion_matrix.csv
|
||||
results/lsa_pos_logreg_20261025_164912/confusion_matrix_normalized.csv
|
||||
results/lsa_pos_logreg_20261025_164912/fold_metrics.csv
|
||||
results/lsa_pos_logreg_20261025_164912/fold_summary.csv
|
||||
results/lsa_pos_logreg_20261025_164912/oof_predictions.csv
|
||||
results/lsa_pos_logreg_20261025_164912/per_class_report.csv
|
||||
results/lsa_pos_logreg_20261025_164912/pooled_metrics.csv
|
||||
results/lsa_pos_logreg_20261025_164912/run_metadata.json
|
||||
results/lsa_pos_logreg_20261025_164912/summary.md
|
||||
results/lsa_pos_logreg_20261025_164912/top_confusions.csv
|
||||
results/lsa_sweep_20261025_164924/lsa_sweep.csv
|
||||
results/lsa_sweep_20261025_185049/lsa_sweep.csv
|
||||
results/lsa_word2_logreg_20261025_164641/bootstrap_ci.json
|
||||
results/lsa_word2_logreg_20261025_164641/confusion_matrix.csv
|
||||
results/lsa_word2_logreg_20261025_164641/confusion_matrix_normalized.csv
|
||||
results/lsa_word2_logreg_20261025_164641/fold_metrics.csv
|
||||
results/lsa_word2_logreg_20261025_164641/fold_summary.csv
|
||||
results/lsa_word2_logreg_20261025_164641/oof_predictions.csv
|
||||
results/lsa_word2_logreg_20261025_164641/per_class_report.csv
|
||||
results/lsa_word2_logreg_20261025_164641/pooled_metrics.csv
|
||||
results/lsa_word2_logreg_20261025_164641/run_metadata.json
|
||||
results/lsa_word2_logreg_20261025_164641/summary.md
|
||||
results/lsa_word2_logreg_20261025_164641/top_confusions.csv
|
||||
results/lsa_word_logreg_20261025_164608/bootstrap_ci.json
|
||||
results/lsa_word_logreg_20261025_164608/confusion_matrix.csv
|
||||
results/lsa_word_logreg_20261025_164608/confusion_matrix_normalized.csv
|
||||
results/lsa_word_logreg_20261025_164608/fold_metrics.csv
|
||||
results/lsa_word_logreg_20261025_164608/fold_summary.csv
|
||||
results/lsa_word_logreg_20261025_164608/oof_predictions.csv
|
||||
results/lsa_word_logreg_20261025_164608/per_class_report.csv
|
||||
results/lsa_word_logreg_20261025_164608/pooled_metrics.csv
|
||||
results/lsa_word_logreg_20261025_164608/run_metadata.json
|
||||
results/lsa_word_logreg_20261025_164608/summary.md
|
||||
results/lsa_word_logreg_20261025_164608/top_confusions.csv
|
||||
results/nested_stylometry_pos_lsa_linear_svc_20261025_172442/best_params.json
|
||||
results/nested_stylometry_pos_lsa_linear_svc_20261025_172442/bootstrap_ci.json
|
||||
results/nested_stylometry_pos_lsa_linear_svc_20261025_172442/confusion_matrix.csv
|
||||
results/nested_stylometry_pos_lsa_linear_svc_20261025_172442/confusion_matrix_normalized.csv
|
||||
results/nested_stylometry_pos_lsa_linear_svc_20261025_172442/fold_metrics.csv
|
||||
results/nested_stylometry_pos_lsa_linear_svc_20261025_172442/fold_summary.csv
|
||||
results/nested_stylometry_pos_lsa_linear_svc_20261025_172442/oof_predictions.csv
|
||||
results/nested_stylometry_pos_lsa_linear_svc_20261025_172442/per_class_report.csv
|
||||
results/nested_stylometry_pos_lsa_linear_svc_20261025_172442/pooled_metrics.csv
|
||||
results/nested_stylometry_pos_lsa_linear_svc_20261025_172442/run_metadata.json
|
||||
results/nested_stylometry_pos_lsa_linear_svc_20261025_172442/summary.md
|
||||
results/nested_stylometry_pos_lsa_linear_svc_20261025_172442/top_confusions.csv
|
||||
results/nested_stylometry_pos_lsa_logreg_20261025_172348/best_params.json
|
||||
results/nested_stylometry_pos_lsa_logreg_20261025_172348/bootstrap_ci.json
|
||||
results/nested_stylometry_pos_lsa_logreg_20261025_172348/confusion_matrix.csv
|
||||
results/nested_stylometry_pos_lsa_logreg_20261025_172348/confusion_matrix_normalized.csv
|
||||
results/nested_stylometry_pos_lsa_logreg_20261025_172348/fold_metrics.csv
|
||||
results/nested_stylometry_pos_lsa_logreg_20261025_172348/fold_summary.csv
|
||||
results/nested_stylometry_pos_lsa_logreg_20261025_172348/oof_predictions.csv
|
||||
results/nested_stylometry_pos_lsa_logreg_20261025_172348/per_class_report.csv
|
||||
results/nested_stylometry_pos_lsa_logreg_20261025_172348/pooled_metrics.csv
|
||||
results/nested_stylometry_pos_lsa_logreg_20261025_172348/run_metadata.json
|
||||
results/nested_stylometry_pos_lsa_logreg_20261025_172348/summary.md
|
||||
results/nested_stylometry_pos_lsa_logreg_20261025_172348/top_confusions.csv
|
||||
results/pos_multinomial_nb_20261025_155848/bootstrap_ci.json
|
||||
results/pos_multinomial_nb_20261025_155848/confusion_matrix.csv
|
||||
results/pos_multinomial_nb_20261025_155848/confusion_matrix_normalized.csv
|
||||
results/pos_multinomial_nb_20261025_155848/fold_metrics.csv
|
||||
results/pos_multinomial_nb_20261025_155848/fold_summary.csv
|
||||
results/pos_multinomial_nb_20261025_155848/oof_predictions.csv
|
||||
results/pos_multinomial_nb_20261025_155848/per_class_report.csv
|
||||
results/pos_multinomial_nb_20261025_155848/pooled_metrics.csv
|
||||
results/pos_multinomial_nb_20261025_155848/run_metadata.json
|
||||
results/pos_multinomial_nb_20261025_155848/summary.md
|
||||
results/pos_multinomial_nb_20261025_155848/top_confusions.csv
|
||||
results/README.md
|
||||
results/stylometry_linear_svc_20261025_164018/bootstrap_ci.json
|
||||
results/stylometry_linear_svc_20261025_164018/confusion_matrix.csv
|
||||
results/stylometry_linear_svc_20261025_164018/confusion_matrix_normalized.csv
|
||||
results/stylometry_linear_svc_20261025_164018/fold_metrics.csv
|
||||
results/stylometry_linear_svc_20261025_164018/fold_summary.csv
|
||||
results/stylometry_linear_svc_20261025_164018/oof_predictions.csv
|
||||
results/stylometry_linear_svc_20261025_164018/per_class_report.csv
|
||||
results/stylometry_linear_svc_20261025_164018/pooled_metrics.csv
|
||||
results/stylometry_linear_svc_20261025_164018/run_metadata.json
|
||||
results/stylometry_linear_svc_20261025_164018/summary.md
|
||||
results/stylometry_linear_svc_20261025_164018/top_confusions.csv
|
||||
results/stylometry_logreg_20261025_160722/bootstrap_ci.json
|
||||
results/stylometry_logreg_20261025_160722/confusion_matrix.csv
|
||||
results/stylometry_logreg_20261025_160722/confusion_matrix_normalized.csv
|
||||
results/stylometry_logreg_20261025_160722/fold_metrics.csv
|
||||
results/stylometry_logreg_20261025_160722/fold_summary.csv
|
||||
results/stylometry_logreg_20261025_160722/oof_predictions.csv
|
||||
results/stylometry_logreg_20261025_160722/per_class_report.csv
|
||||
results/stylometry_logreg_20261025_160722/pooled_metrics.csv
|
||||
results/stylometry_logreg_20261025_160722/run_metadata.json
|
||||
results/stylometry_logreg_20261025_160722/summary.md
|
||||
results/stylometry_logreg_20261025_160722/top_confusions.csv
|
||||
results/stylometry_pos_logreg_20261025_164246/bootstrap_ci.json
|
||||
results/stylometry_pos_logreg_20261025_164246/confusion_matrix.csv
|
||||
results/stylometry_pos_logreg_20261025_164246/confusion_matrix_normalized.csv
|
||||
results/stylometry_pos_logreg_20261025_164246/fold_metrics.csv
|
||||
results/stylometry_pos_logreg_20261025_164246/fold_summary.csv
|
||||
results/stylometry_pos_logreg_20261025_164246/oof_predictions.csv
|
||||
results/stylometry_pos_logreg_20261025_164246/per_class_report.csv
|
||||
results/stylometry_pos_logreg_20261025_164246/pooled_metrics.csv
|
||||
results/stylometry_pos_logreg_20261025_164246/run_metadata.json
|
||||
results/stylometry_pos_logreg_20261025_164246/summary.md
|
||||
results/stylometry_pos_logreg_20261025_164246/top_confusions.csv
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_184431/bootstrap_ci.json
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_184431/confusion_matrix.csv
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_184431/confusion_matrix_normalized.csv
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_184431/fold_metrics.csv
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_184431/fold_summary.csv
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_184431/oof_predictions.csv
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_184431/per_class_report.csv
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_184431/pooled_metrics.csv
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_184431/run_metadata.json
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_184431/summary.md
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_184431/top_confusions.csv
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_184538/bootstrap_ci.json
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_184538/confusion_matrix.csv
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_184538/confusion_matrix_normalized.csv
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_184538/fold_metrics.csv
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_184538/fold_summary.csv
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_184538/oof_predictions.csv
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_184538/per_class_report.csv
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_184538/pooled_metrics.csv
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_184538/run_metadata.json
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_184538/summary.md
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_184538/top_confusions.csv
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_184646/bootstrap_ci.json
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_184646/confusion_matrix.csv
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_184646/confusion_matrix_normalized.csv
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_184646/fold_metrics.csv
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_184646/fold_summary.csv
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_184646/oof_predictions.csv
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_184646/per_class_report.csv
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_184646/pooled_metrics.csv
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_184646/run_metadata.json
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_184646/summary.md
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_184646/top_confusions.csv
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_184759/bootstrap_ci.json
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_184759/confusion_matrix.csv
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_184759/confusion_matrix_normalized.csv
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_184759/fold_metrics.csv
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_184759/fold_summary.csv
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_184759/oof_predictions.csv
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_184759/per_class_report.csv
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_184759/pooled_metrics.csv
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_184759/run_metadata.json
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_184759/summary.md
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_184759/top_confusions.csv
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_184916/bootstrap_ci.json
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_184916/confusion_matrix.csv
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_184916/confusion_matrix_normalized.csv
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_184916/fold_metrics.csv
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_184916/fold_summary.csv
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_184916/oof_predictions.csv
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_184916/per_class_report.csv
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_184916/pooled_metrics.csv
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_184916/run_metadata.json
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_184916/summary.md
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_184916/top_confusions.csv
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_185037/bootstrap_ci.json
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_185037/confusion_matrix.csv
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_185037/confusion_matrix_normalized.csv
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_185037/fold_metrics.csv
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_185037/fold_summary.csv
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_185037/oof_predictions.csv
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_185037/per_class_report.csv
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_185037/pooled_metrics.csv
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_185037/run_metadata.json
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_185037/summary.md
|
||||
results/stylometry_pos_lsa_linear_svc_20261025_185037/top_confusions.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_164534/bootstrap_ci.json
|
||||
results/stylometry_pos_lsa_logreg_20261025_164534/comparison_main_vs_pos.json
|
||||
results/stylometry_pos_lsa_logreg_20261025_164534/confusion_matrix.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_164534/confusion_matrix_normalized.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_164534/fold_metrics.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_164534/fold_summary.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_164534/length_bins.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_164534/oof_predictions.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_164534/per_class_report.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_164534/pooled_metrics.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_164534/run_metadata.json
|
||||
results/stylometry_pos_lsa_logreg_20261025_164534/subgroup_age_group_confusions.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_164534/subgroup_age_group_metrics.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_164534/subgroup_gender_confusions.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_164534/subgroup_gender_metrics.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_164534/subgroup_species_confusions.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_164534/subgroup_species_metrics.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_164534/summary.md
|
||||
results/stylometry_pos_lsa_logreg_20261025_164534/top_confusions.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_183404/bootstrap_ci.json
|
||||
results/stylometry_pos_lsa_logreg_20261025_183404/confusion_matrix.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_183404/confusion_matrix_normalized.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_183404/fold_metrics.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_183404/fold_summary.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_183404/oof_predictions.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_183404/per_class_report.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_183404/pooled_metrics.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_183404/run_metadata.json
|
||||
results/stylometry_pos_lsa_logreg_20261025_183404/summary.md
|
||||
results/stylometry_pos_lsa_logreg_20261025_183404/top_confusions.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_183524/bootstrap_ci.json
|
||||
results/stylometry_pos_lsa_logreg_20261025_183524/confusion_matrix.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_183524/confusion_matrix_normalized.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_183524/fold_metrics.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_183524/fold_summary.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_183524/oof_predictions.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_183524/per_class_report.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_183524/pooled_metrics.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_183524/run_metadata.json
|
||||
results/stylometry_pos_lsa_logreg_20261025_183524/summary.md
|
||||
results/stylometry_pos_lsa_logreg_20261025_183524/top_confusions.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_183653/bootstrap_ci.json
|
||||
results/stylometry_pos_lsa_logreg_20261025_183653/confusion_matrix.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_183653/confusion_matrix_normalized.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_183653/fold_metrics.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_183653/fold_summary.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_183653/oof_predictions.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_183653/per_class_report.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_183653/pooled_metrics.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_183653/run_metadata.json
|
||||
results/stylometry_pos_lsa_logreg_20261025_183653/summary.md
|
||||
results/stylometry_pos_lsa_logreg_20261025_183653/top_confusions.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_183839/bootstrap_ci.json
|
||||
results/stylometry_pos_lsa_logreg_20261025_183839/confusion_matrix.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_183839/confusion_matrix_normalized.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_183839/fold_metrics.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_183839/fold_summary.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_183839/oof_predictions.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_183839/per_class_report.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_183839/pooled_metrics.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_183839/run_metadata.json
|
||||
results/stylometry_pos_lsa_logreg_20261025_183839/summary.md
|
||||
results/stylometry_pos_lsa_logreg_20261025_183839/top_confusions.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_184052/bootstrap_ci.json
|
||||
results/stylometry_pos_lsa_logreg_20261025_184052/confusion_matrix.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_184052/confusion_matrix_normalized.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_184052/fold_metrics.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_184052/fold_summary.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_184052/oof_predictions.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_184052/per_class_report.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_184052/pooled_metrics.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_184052/run_metadata.json
|
||||
results/stylometry_pos_lsa_logreg_20261025_184052/summary.md
|
||||
results/stylometry_pos_lsa_logreg_20261025_184052/top_confusions.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_184325/bootstrap_ci.json
|
||||
results/stylometry_pos_lsa_logreg_20261025_184325/confusion_matrix.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_184325/confusion_matrix_normalized.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_184325/fold_metrics.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_184325/fold_summary.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_184325/oof_predictions.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_184325/per_class_report.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_184325/pooled_metrics.csv
|
||||
results/stylometry_pos_lsa_logreg_20261025_184325/run_metadata.json
|
||||
results/stylometry_pos_lsa_logreg_20261025_184325/summary.md
|
||||
results/stylometry_pos_lsa_logreg_20261025_184325/top_confusions.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_190013/bootstrap_ci.json
|
||||
results/stylometry_pos_lsa_random_forest_20261025_190013/confusion_matrix.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_190013/confusion_matrix_normalized.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_190013/fold_metrics.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_190013/fold_summary.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_190013/oof_predictions.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_190013/per_class_report.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_190013/pooled_metrics.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_190013/run_metadata.json
|
||||
results/stylometry_pos_lsa_random_forest_20261025_190013/summary.md
|
||||
results/stylometry_pos_lsa_random_forest_20261025_190013/top_confusions.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_190125/bootstrap_ci.json
|
||||
results/stylometry_pos_lsa_random_forest_20261025_190125/confusion_matrix.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_190125/confusion_matrix_normalized.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_190125/fold_metrics.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_190125/fold_summary.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_190125/oof_predictions.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_190125/per_class_report.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_190125/pooled_metrics.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_190125/run_metadata.json
|
||||
results/stylometry_pos_lsa_random_forest_20261025_190125/summary.md
|
||||
results/stylometry_pos_lsa_random_forest_20261025_190125/top_confusions.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_190237/bootstrap_ci.json
|
||||
results/stylometry_pos_lsa_random_forest_20261025_190237/confusion_matrix.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_190237/confusion_matrix_normalized.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_190237/fold_metrics.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_190237/fold_summary.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_190237/oof_predictions.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_190237/per_class_report.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_190237/pooled_metrics.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_190237/run_metadata.json
|
||||
results/stylometry_pos_lsa_random_forest_20261025_190237/summary.md
|
||||
results/stylometry_pos_lsa_random_forest_20261025_190237/top_confusions.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_190354/bootstrap_ci.json
|
||||
results/stylometry_pos_lsa_random_forest_20261025_190354/confusion_matrix.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_190354/confusion_matrix_normalized.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_190354/fold_metrics.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_190354/fold_summary.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_190354/oof_predictions.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_190354/per_class_report.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_190354/pooled_metrics.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_190354/run_metadata.json
|
||||
results/stylometry_pos_lsa_random_forest_20261025_190354/summary.md
|
||||
results/stylometry_pos_lsa_random_forest_20261025_190354/top_confusions.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_190510/bootstrap_ci.json
|
||||
results/stylometry_pos_lsa_random_forest_20261025_190510/confusion_matrix.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_190510/confusion_matrix_normalized.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_190510/fold_metrics.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_190510/fold_summary.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_190510/oof_predictions.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_190510/per_class_report.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_190510/pooled_metrics.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_190510/run_metadata.json
|
||||
results/stylometry_pos_lsa_random_forest_20261025_190510/summary.md
|
||||
results/stylometry_pos_lsa_random_forest_20261025_190510/top_confusions.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_190626/bootstrap_ci.json
|
||||
results/stylometry_pos_lsa_random_forest_20261025_190626/confusion_matrix.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_190626/confusion_matrix_normalized.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_190626/fold_metrics.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_190626/fold_summary.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_190626/oof_predictions.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_190626/per_class_report.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_190626/pooled_metrics.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_190626/run_metadata.json
|
||||
results/stylometry_pos_lsa_random_forest_20261025_190626/summary.md
|
||||
results/stylometry_pos_lsa_random_forest_20261025_190626/top_confusions.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_190749/bootstrap_ci.json
|
||||
results/stylometry_pos_lsa_random_forest_20261025_190749/confusion_matrix.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_190749/confusion_matrix_normalized.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_190749/fold_metrics.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_190749/fold_summary.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_190749/oof_predictions.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_190749/per_class_report.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_190749/pooled_metrics.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_190749/run_metadata.json
|
||||
results/stylometry_pos_lsa_random_forest_20261025_190749/summary.md
|
||||
results/stylometry_pos_lsa_random_forest_20261025_190749/top_confusions.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_190910/bootstrap_ci.json
|
||||
results/stylometry_pos_lsa_random_forest_20261025_190910/confusion_matrix.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_190910/confusion_matrix_normalized.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_190910/fold_metrics.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_190910/fold_summary.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_190910/oof_predictions.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_190910/per_class_report.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_190910/pooled_metrics.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_190910/run_metadata.json
|
||||
results/stylometry_pos_lsa_random_forest_20261025_190910/summary.md
|
||||
results/stylometry_pos_lsa_random_forest_20261025_190910/top_confusions.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_191031/bootstrap_ci.json
|
||||
results/stylometry_pos_lsa_random_forest_20261025_191031/confusion_matrix.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_191031/confusion_matrix_normalized.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_191031/fold_metrics.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_191031/fold_summary.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_191031/oof_predictions.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_191031/per_class_report.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_191031/pooled_metrics.csv
|
||||
results/stylometry_pos_lsa_random_forest_20261025_191031/run_metadata.json
|
||||
results/stylometry_pos_lsa_random_forest_20261025_191031/summary.md
|
||||
results/stylometry_pos_lsa_random_forest_20261025_191031/top_confusions.csv
|
||||
results/verification_stylometry_pos_lsa_20261025_172237/fold_metrics.csv
|
||||
results/verification_stylometry_pos_lsa_20261025_172237/pair_scores.csv
|
||||
results/verification_stylometry_pos_lsa_20261025_172237/summary.json
|
||||
@@ -1,65 +0,0 @@
|
||||
# Презентация
|
||||
|
||||
Готовая презентация: `stardew_stylometry.pptx` (16:9, 30 слайдов в финальной версии; исходная автоматическая сборка — 32 слайда).
|
||||
PDF-версия для просмотра: `stardew_stylometry.pdf`.
|
||||
|
||||
## Структура
|
||||
|
||||
- **Часть 1. Корпус** — 2005 реплик, 34 персонажа, сбор, очистка, разметка,
|
||||
распределение реплик и длин, ограничения.
|
||||
- **Часть 2. Методология** — признаки, LSA, логистическая регрессия, SVM,
|
||||
деревья, ансамбли, перекрёстная проверка и метрики.
|
||||
- **Часть 3. Результаты** — сравнение моделей, вклад признаков, контроль
|
||||
содержания, матрицы ошибок, классы-аттракторы, выводы.
|
||||
|
||||
На каждом слайде есть фон `images/background.jpg` и маленькая курица
|
||||
`images/chicken.png` как пасхалка. На титульном слайде — `images/title.jpg`;
|
||||
на слайде о персонажах — `images/characters.webp`; на последнем слайде — мем
|
||||
`images/meme.webp`. Круговые диаграммы — `images/replics-count.png` и
|
||||
`images/median.png`.
|
||||
|
||||
## Сборка
|
||||
|
||||
```bash
|
||||
cd lab1
|
||||
python -m pip install -r presentation/requirements.txt
|
||||
python presentation/make_slide_figures.py
|
||||
python presentation/build_deck.py
|
||||
```
|
||||
|
||||
`make_slide_figures.py` строит матрицы ошибок и сравнительные диаграммы из
|
||||
`results/final/finalists/voting_et_char_lean/oof_predictions.csv` и
|
||||
`FINAL_RESULTS.csv`. `build_deck.py` собирает PPTX, добавляет заметки
|
||||
докладчика, фон, курицу и изображения.
|
||||
|
||||
## Редактирование
|
||||
|
||||
Текст и порядок слайдов задаются в `build_deck.py`. После изменения текста
|
||||
достаточно повторно запустить сборку. Готовый PPTX можно править и напрямую
|
||||
в PowerPoint/LibreOffice; заметки докладчика находятся в заметках слайдов.
|
||||
|
||||
Изображения игры используются в учебных целях; права принадлежат их
|
||||
правообладателям.
|
||||
|
||||
## Текущая версия и автоматическая сборка
|
||||
|
||||
Актуальная версия — `stardew_stylometry.pptx`: она правится вручную и содержит
|
||||
имена докладчиков и сокращённый набор слайдов. Скрипт `build_deck.py` — это
|
||||
первоначальная автоматическая сборка; повторный запуск перезапишет файл и
|
||||
потеряет ручные правки. Для точечной переработки первого блока использован
|
||||
`rework_block1.py`; перед повторным применением скрипта сделайте резервную
|
||||
копию PPTX.
|
||||
|
||||
## LaTeX-версия
|
||||
|
||||
Академическая Beamer-презентация:
|
||||
|
||||
- `latex/stardew_stylometry.tex` — исходник;
|
||||
- `latex/stardew_stylometry.pdf` — собранный PDF, 30 страниц;
|
||||
- сборка: `cd latex && xelatex stardew_stylometry.tex` (два прохода).
|
||||
|
||||
В LaTeX-версии три части на трёх докладчиков, фон и курица на каждом слайде,
|
||||
титульная заставка, изображение персонажей, две круговые диаграммы, матрицы
|
||||
ошибок, F-меры по персонажам, приложение с полной матрицей и финальный мем.
|
||||
Формул почти нет: одна компактная запись обозначений на слайде постановки
|
||||
задачи.
|
||||
@@ -1,809 +0,0 @@
|
||||
"""Сборка презентации по лабораторной работе.
|
||||
|
||||
Требования:
|
||||
- 16:9, фон на каждом слайде;
|
||||
- три содержательных блока на трёх рассказчиков;
|
||||
- курица на каждом слайде;
|
||||
- титульная картинка, персонажи, две круговые диаграммы, матрицы ошибок,
|
||||
мем на последнем слайде;
|
||||
- тёмный текст на светлых панелях.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
from PIL import Image
|
||||
from pptx import Presentation
|
||||
from pptx.dml.color import RGBColor
|
||||
from pptx.enum.shapes import MSO_SHAPE
|
||||
from pptx.enum.text import MSO_ANCHOR, PP_ALIGN
|
||||
from pptx.util import Inches, Pt
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
IMAGES = ROOT / "presentation" / "images"
|
||||
GEN = ROOT / "presentation" / "generated"
|
||||
OUT = ROOT / "presentation" / "stardew_stylometry.pptx"
|
||||
|
||||
SW, SH = 13.333, 7.5
|
||||
FONT = "Arial"
|
||||
|
||||
CREAM = RGBColor(248, 241, 220)
|
||||
CREAM_LIGHT = RGBColor(255, 252, 240)
|
||||
DARK = RGBColor(30, 45, 33)
|
||||
GREEN = RGBColor(49, 93, 58)
|
||||
GREEN_DARK = RGBColor(28, 63, 47)
|
||||
GOLD = RGBColor(204, 153, 51)
|
||||
BROWN = RGBColor(126, 80, 42)
|
||||
RED = RGBColor(166, 55, 43)
|
||||
WHITE = RGBColor(255, 255, 255)
|
||||
BORDER = RGBColor(181, 158, 111)
|
||||
|
||||
|
||||
def add_background(slide) -> None:
|
||||
slide.shapes.add_picture(
|
||||
str(IMAGES / "background.jpg"), 0, 0, width=Inches(SW), height=Inches(SH)
|
||||
)
|
||||
|
||||
|
||||
def add_panel(slide, x, y, w, h, fill=CREAM, line=BORDER, radius: float = 0.06):
|
||||
shape = slide.shapes.add_shape(
|
||||
MSO_SHAPE.ROUNDED_RECTANGLE, Inches(x), Inches(y), Inches(w), Inches(h)
|
||||
)
|
||||
shape.fill.solid()
|
||||
shape.fill.fore_color.rgb = fill
|
||||
shape.line.color.rgb = line
|
||||
shape.line.width = Pt(1.1)
|
||||
shape.shadow.inherit = False
|
||||
try:
|
||||
shape.adjustments[0] = radius
|
||||
except Exception:
|
||||
pass
|
||||
return shape
|
||||
|
||||
|
||||
def add_text(
|
||||
slide,
|
||||
x,
|
||||
y,
|
||||
w,
|
||||
h,
|
||||
text: str,
|
||||
size: int = 18,
|
||||
color=DARK,
|
||||
bold: bool = False,
|
||||
align=PP_ALIGN.LEFT,
|
||||
anchor=MSO_ANCHOR.TOP,
|
||||
margin: float = 0.08,
|
||||
):
|
||||
box = slide.shapes.add_textbox(Inches(x), Inches(y), Inches(w), Inches(h))
|
||||
frame = box.text_frame
|
||||
frame.word_wrap = True
|
||||
frame.vertical_anchor = anchor
|
||||
frame.margin_left = Inches(margin)
|
||||
frame.margin_right = Inches(margin)
|
||||
frame.margin_top = Inches(0.04)
|
||||
frame.margin_bottom = Inches(0.04)
|
||||
paragraph = frame.paragraphs[0]
|
||||
paragraph.alignment = align
|
||||
run = paragraph.add_run()
|
||||
run.text = text
|
||||
run.font.name = FONT
|
||||
run.font.size = Pt(size)
|
||||
run.font.bold = bold
|
||||
run.font.color.rgb = color
|
||||
return box
|
||||
|
||||
|
||||
def add_bullets(slide, x, y, w, h, items, size: int = 16, color=DARK, title: str | None = None):
|
||||
box = slide.shapes.add_textbox(Inches(x), Inches(y), Inches(w), Inches(h))
|
||||
frame = box.text_frame
|
||||
frame.word_wrap = True
|
||||
frame.margin_left = Inches(0.08)
|
||||
frame.margin_right = Inches(0.08)
|
||||
frame.margin_top = Inches(0.04)
|
||||
first = True
|
||||
if title:
|
||||
paragraph = frame.paragraphs[0]
|
||||
paragraph.space_after = Pt(7)
|
||||
run = paragraph.add_run()
|
||||
run.text = title
|
||||
run.font.name = FONT
|
||||
run.font.size = Pt(size + 3)
|
||||
run.font.bold = True
|
||||
run.font.color.rgb = GREEN_DARK
|
||||
first = False
|
||||
for item in items:
|
||||
if isinstance(item, tuple):
|
||||
text, level = item
|
||||
else:
|
||||
text, level = item, 0
|
||||
paragraph = frame.paragraphs[0] if first else frame.add_paragraph()
|
||||
first = False
|
||||
prefix = "• " if level == 0 else "– "
|
||||
paragraph.text = prefix + text
|
||||
paragraph.level = level
|
||||
paragraph.space_after = Pt(5)
|
||||
paragraph.line_spacing = 1.0
|
||||
for run in paragraph.runs:
|
||||
run.font.name = FONT
|
||||
run.font.size = Pt(size if level == 0 else size - 1)
|
||||
run.font.color.rgb = color
|
||||
return box
|
||||
|
||||
|
||||
def add_table(slide, x, y, w, h, rows, col_widths=None, font_size: int = 11, header: bool = True):
|
||||
shape = slide.shapes.add_table(len(rows), len(rows[0]), Inches(x), Inches(y), Inches(w), Inches(h))
|
||||
table = shape.table
|
||||
if col_widths:
|
||||
total = sum(col_widths)
|
||||
for index, value in enumerate(col_widths):
|
||||
table.columns[index].width = Inches(w * value / total)
|
||||
for row_index, row in enumerate(rows):
|
||||
for col_index, value in enumerate(row):
|
||||
cell = table.cell(row_index, col_index)
|
||||
cell.margin_left = Inches(0.05)
|
||||
cell.margin_right = Inches(0.05)
|
||||
cell.margin_top = Inches(0.02)
|
||||
cell.margin_bottom = Inches(0.02)
|
||||
cell.fill.solid()
|
||||
if header and row_index == 0:
|
||||
cell.fill.fore_color.rgb = GREEN
|
||||
else:
|
||||
cell.fill.fore_color.rgb = CREAM_LIGHT if row_index % 2 else CREAM
|
||||
paragraph = cell.text_frame.paragraphs[0]
|
||||
paragraph.alignment = PP_ALIGN.LEFT if col_index == 0 else PP_ALIGN.CENTER
|
||||
run = paragraph.add_run()
|
||||
run.text = str(value)
|
||||
run.font.name = FONT
|
||||
run.font.size = Pt(font_size)
|
||||
run.font.bold = header and row_index == 0
|
||||
run.font.color.rgb = WHITE if header and row_index == 0 else DARK
|
||||
return table
|
||||
|
||||
|
||||
def add_image_fit(slide, path: Path, x, y, max_w, max_h, panel: bool = False):
|
||||
with Image.open(path) as image:
|
||||
width, height = image.size
|
||||
aspect = width / height
|
||||
if max_w / max_h > aspect:
|
||||
draw_h = max_h
|
||||
draw_w = draw_h * aspect
|
||||
else:
|
||||
draw_w = max_w
|
||||
draw_h = draw_w / aspect
|
||||
draw_x = x + (max_w - draw_w) / 2
|
||||
draw_y = y + (max_h - draw_h) / 2
|
||||
if panel:
|
||||
add_panel(slide, draw_x - 0.07, draw_y - 0.07, draw_w + 0.14, draw_h + 0.14, fill=CREAM_LIGHT)
|
||||
slide.shapes.add_picture(str(path), Inches(draw_x), Inches(draw_y), width=Inches(draw_w), height=Inches(draw_h))
|
||||
|
||||
|
||||
def add_chicken(slide, x: float = 12.96, y: float = 7.12, size: float = 0.24):
|
||||
slide.shapes.add_picture(
|
||||
str(IMAGES / "chicken.png"), Inches(x), Inches(y), width=Inches(size), height=Inches(size)
|
||||
)
|
||||
|
||||
|
||||
def add_notes(slide, text: str) -> None:
|
||||
slide.notes_slide.notes_text_frame.text = text
|
||||
|
||||
|
||||
def new_slide():
|
||||
slide = Presentation().slides.add_slide # placeholder, replaced below
|
||||
return slide
|
||||
|
||||
|
||||
def add_title_bar(slide, title: str, block: str = "", subtitle: str | None = None):
|
||||
add_panel(slide, 0.42, 0.28, 12.49, 0.86, fill=CREAM)
|
||||
add_text(slide, 0.58, 0.34, 10.2, 0.45, title, size=25, bold=True, color=GREEN_DARK)
|
||||
if block:
|
||||
add_text(slide, 10.0, 0.42, 2.65, 0.35, block, size=13, color=BROWN, align=PP_ALIGN.RIGHT)
|
||||
if subtitle:
|
||||
add_text(slide, 0.58, 0.76, 11.8, 0.3, subtitle, size=12, color=BROWN)
|
||||
|
||||
|
||||
def add_footer(slide, block: str, number: int):
|
||||
add_text(slide, 0.45, 7.12, 7.8, 0.24, block, size=10, color=DARK)
|
||||
add_text(slide, 12.15, 7.12, 0.55, 0.24, str(number), size=10, color=DARK, align=PP_ALIGN.RIGHT)
|
||||
|
||||
|
||||
def content_slide(prs, title: str, block: str, number: int, subtitle: str | None = None):
|
||||
slide = prs.slides.add_slide(prs.slide_layouts[6])
|
||||
add_background(slide)
|
||||
add_title_bar(slide, title, block, subtitle)
|
||||
add_footer(slide, block, number)
|
||||
add_chicken(slide)
|
||||
return slide
|
||||
|
||||
|
||||
def section_slide(prs, number: int, block: str, title: str, subtitle: str, questions: list[str], notes: str, slide_number: int):
|
||||
slide = prs.slides.add_slide(prs.slide_layouts[6])
|
||||
add_background(slide)
|
||||
add_panel(slide, 0.65, 0.9, 12.0, 5.6, fill=CREAM)
|
||||
add_text(slide, 0.95, 1.15, 2.6, 1.0, block, size=22, bold=True, color=GOLD)
|
||||
add_text(slide, 0.95, 2.05, 11.2, 0.9, title, size=38, bold=True, color=GREEN_DARK)
|
||||
add_text(slide, 0.95, 3.05, 11.0, 0.6, subtitle, size=20, color=BROWN)
|
||||
add_bullets(slide, 0.95, 3.9, 11.0, 1.9, questions, size=17, color=DARK)
|
||||
add_text(slide, 0.95, 5.9, 7.0, 0.35, f"Рассказчик {number}", size=15, bold=True, color=RED)
|
||||
add_footer(slide, block, slide_number)
|
||||
add_chicken(slide)
|
||||
add_notes(slide, notes)
|
||||
return slide
|
||||
|
||||
|
||||
def main() -> int:
|
||||
prs = Presentation()
|
||||
prs.slide_width = Inches(SW)
|
||||
prs.slide_height = Inches(SH)
|
||||
slide_no = 0
|
||||
|
||||
# 1. Титульный слайд
|
||||
slide_no += 1
|
||||
slide = prs.slides.add_slide(prs.slide_layouts[6])
|
||||
add_background(slide)
|
||||
add_panel(slide, 0.55, 1.15, 6.15, 4.25, fill=CREAM)
|
||||
add_image_fit(slide, IMAGES / "title.jpg", 0.7, 1.3, 5.85, 3.9, panel=False)
|
||||
add_panel(slide, 6.95, 1.15, 5.85, 5.45, fill=CREAM)
|
||||
add_text(slide, 7.2, 1.45, 5.3, 0.35, "Лабораторная работа по анализу текстов", size=13, color=BROWN)
|
||||
add_text(slide, 7.2, 1.9, 5.3, 1.55, "Можно ли различить голоса персонажей?", size=32, bold=True, color=GREEN_DARK)
|
||||
add_text(slide, 7.2, 3.45, 5.25, 1.4, "Стилометрическая классификация 34 персонажей Stardew Valley по 2005 репликам", size=18, color=DARK)
|
||||
add_text(slide, 7.2, 4.9, 5.3, 0.6, "Три блока · три рассказчика", size=16, bold=True, color=RED)
|
||||
add_text(slide, 7.2, 5.28, 5.3, 0.45, "Корпус → методология → результаты", size=15, color=BROWN)
|
||||
add_panel(slide, 0.55, 5.75, 12.25, 0.8, fill=CREAM_LIGHT)
|
||||
add_text(slide, 0.75, 5.9, 11.9, 0.45, "2005 реплик · 34 персонажа · один автор · 5-блочная перекрёстная проверка", size=14, color=DARK, align=PP_ALIGN.CENTER)
|
||||
add_chicken(slide, 12.95, 7.12, 0.26)
|
||||
add_notes(slide, "Приветствие. Тема — стилометрия реплик Stardew Valley. Сразу уточнить: автор один, задача — различить речевые маски персонажей, а не установить авторство. Три блока: корпус, методология, результаты.")
|
||||
|
||||
# 2. Структура
|
||||
slide_no += 1
|
||||
slide = content_slide(prs, "Структура доклада", "Введение", slide_no)
|
||||
blocks = [
|
||||
("01", "Корпус", "Рассказчик 1", ["2005 реплик", "34 персонажа", "сбор, очистка, разметка", "ограничения данных"]),
|
||||
("02", "Методология", "Рассказчик 2", ["признаки и LSA", "классификаторы", "перекрёстная проверка", "метрики"]),
|
||||
("03", "Результаты", "Рассказчик 3", ["лучшие модели", "матрицы ошибок", "кого путает модель", "выводы"]),
|
||||
]
|
||||
for index, (num, title, narrator, lines) in enumerate(blocks):
|
||||
x = 0.45 + index * 4.27
|
||||
add_panel(slide, x, 1.5, 4.0, 5.15, fill=CREAM)
|
||||
add_text(slide, x + 0.25, 1.7, 0.9, 0.5, num, size=30, bold=True, color=GOLD)
|
||||
add_text(slide, x + 0.25, 2.3, 3.5, 0.5, title, size=24, bold=True, color=GREEN_DARK)
|
||||
add_text(slide, x + 0.25, 2.9, 3.5, 0.35, narrator, size=13, color=RED)
|
||||
add_bullets(slide, x + 0.25, 3.45, 3.5, 2.7, lines, size=14, color=DARK)
|
||||
add_notes(slide, "Показать три блока и передать слово первому рассказчику. Подчеркнуть: сначала данные, затем методы, затем интерпретация.")
|
||||
|
||||
# 3. Блок 1
|
||||
slide_no += 1
|
||||
section_slide(
|
||||
prs, 1, "Блок 1", "Корпус: как собрали и разметили",
|
||||
"Что именно мы классифицируем и почему это сложная задача",
|
||||
["Откуда данные и какова единица анализа", "Кто в корпусе и как распределены реплики", "Длина реплик и очистка", "Паспорт корпуса и ограничения"],
|
||||
"Передать слово рассказчику 1. Блок отвечает на вопрос: что представляет собой выборка и можно ли ей доверять.",
|
||||
slide_no,
|
||||
)
|
||||
|
||||
# 4. Что анализируем
|
||||
slide_no += 1
|
||||
slide = content_slide(prs, "Что анализируем", "Блок 1 · Корпус", slide_no)
|
||||
add_panel(slide, 0.45, 1.4, 5.95, 5.4, fill=CREAM)
|
||||
add_bullets(slide, 0.7, 1.6, 5.45, 4.9, [
|
||||
"Единица анализа — одна реплика персонажа.",
|
||||
"Всего 2005 реплик; 34 персонажа; один автор.",
|
||||
"Класс — имя персонажа; один объект относится ровно к одному классу.",
|
||||
"Реплики короткие: в среднем 19 слов, медиана 17.",
|
||||
"Корпус англоязычный; разметка — только текст и персонаж.",
|
||||
], size=17, title="Постановка задачи")
|
||||
add_panel(slide, 6.65, 1.4, 6.2, 5.4, fill=CREAM_LIGHT)
|
||||
add_text(slide, 6.85, 1.55, 5.8, 0.35, "Примеры разных голосов", size=16, bold=True, color=GREEN_DARK)
|
||||
quotes = [
|
||||
("Abigail (139 реплик)", "Oh, that's right... I heard someone new was moving onto that old farm."),
|
||||
("Jas (34 реплики)", "Wow, a birthday present! I love it!"),
|
||||
("Krobus (28 реплик)", "I wanted to give you this as a 'thank you' for letting me live here with you."),
|
||||
("Wizard (24 реплики)", "Ah, yes. I have predicted your arrival a long time ago, young PLAYER."),
|
||||
]
|
||||
y = 1.98
|
||||
for name, quote in quotes:
|
||||
add_text(slide, 6.85, y, 5.8, 0.3, name, size=13, bold=True, color=BROWN)
|
||||
add_text(slide, 6.85, y + 0.28, 5.75, 0.55, f"«{quote}»", size=13, color=DARK)
|
||||
y += 1.08
|
||||
add_notes(slide, "Объяснить: одна реплика — один объект. Показать четыре голоса: длинная речь Abigail, короткая детская реплика Jas, нечеловеческая вежливость Krobus, архаичный Wizard. Это иллюстрирует, что сигнал может быть в лексике, длине, пунктуации и синтаксисе.")
|
||||
|
||||
# 5. Кто в корпусе
|
||||
slide_no += 1
|
||||
slide = content_slide(prs, "Кто в корпусе", "Блок 1 · Корпус", slide_no)
|
||||
add_panel(slide, 0.4, 1.35, 8.8, 5.55, fill=CREAM_LIGHT)
|
||||
add_image_fit(slide, GEN / "characters.png", 0.5, 1.42, 8.6, 5.4)
|
||||
add_panel(slide, 9.3, 1.35, 3.6, 5.55, fill=CREAM)
|
||||
add_bullets(slide, 9.48, 1.55, 3.25, 5.1, [
|
||||
"34 персонажа: подростки, взрослые, пожилые, дети и нелюди.",
|
||||
"Все реплики написаны одним разработчиком; многие — в одних и тех же обновлениях и сюжетных контекстах.",
|
||||
"Классы сильно неравномерны: от 15 до 139 реплик.",
|
||||
"Метаданные о поле, возрасте и виде используются только для анализа ошибок, не как признаки.",
|
||||
], size=14, title="Состав")
|
||||
add_notes(slide, "Показать портреты. Подчеркнуть: один автор и общие сюжетные контексты — поэтому сходство голосов ожидаемо. Метаданные не подаются классификатору; они нужны только для интерпретации ошибок.")
|
||||
|
||||
# 6. Количество реплик
|
||||
slide_no += 1
|
||||
slide = content_slide(prs, "Сколько реплик у каждого", "Блок 1 · Корпус", slide_no, "Радиальная диаграмма: число реплик по персонажам")
|
||||
add_panel(slide, 0.4, 1.35, 7.2, 5.55, fill=CREAM_LIGHT)
|
||||
add_image_fit(slide, IMAGES / "replics-count.png", 0.5, 1.42, 7.0, 5.4)
|
||||
add_panel(slide, 7.85, 1.35, 5.05, 5.55, fill=CREAM)
|
||||
add_bullets(slide, 8.02, 1.55, 4.7, 5.0, [
|
||||
"Максимум — Abigail: 139 реплик; минимум — Mister Qi: 15.",
|
||||
"Разница между классами почти десятикратная.",
|
||||
"При 34 классах случайное угадывание даёт около 2,9%.",
|
||||
"Поэтому доля правильных ответов обманчива: модель может выбирать частые классы.",
|
||||
"Основная метрика — макро-усреднённая F-мера: каждый персонаж весит одинаково.",
|
||||
], size=16, title="Дисбаланс классов")
|
||||
add_notes(slide, "Диаграмма показывает дисбаланс. Сразу объяснить, почему accuracy не подходит: модель, всегда выбирающая Abigail, получит около 7% правильных ответов, но почти нулевую macro-F1 по остальным 33 классам.")
|
||||
|
||||
# 7. Длина реплик
|
||||
slide_no += 1
|
||||
slide = content_slide(prs, "Длина реплик", "Блок 1 · Корпус", slide_no, "Радиальная диаграмма: медианная длина предложения в символах")
|
||||
add_panel(slide, 0.4, 1.35, 7.2, 5.55, fill=CREAM_LIGHT)
|
||||
add_image_fit(slide, IMAGES / "median.png", 0.5, 1.42, 7.0, 5.4)
|
||||
add_panel(slide, 7.85, 1.35, 5.05, 5.55, fill=CREAM)
|
||||
add_bullets(slide, 8.02, 1.55, 4.7, 5.0, [
|
||||
"Средняя длина — 19,1 слова; медиана — 17 слов.",
|
||||
"Диапазон — от 3 до 78 слов.",
|
||||
"Медианная длина предложения в символах: от 52 у Vincent до 114 у Sandy.",
|
||||
"Короткие реплики дают мало свидетельств; стилометрические оценки на них нестабильны.",
|
||||
"Длина сама по себе — сильный, но нежелательный признак: она может стать прокси персонажа.",
|
||||
], size=15, title="Короткие тексты")
|
||||
add_notes(slide, "Числа у подписей — медианная длина предложения в символах. Показать контраст Vincent/Jas и Sandy/Wizard. Сказать, что длина — законный признак, но её вклад нужно проверять отдельно, иначе модель будет различать не стиль, а «кто говорит длиннее».")
|
||||
|
||||
# 8. Очистка и разметка
|
||||
slide_no += 1
|
||||
slide = content_slide(prs, "Очистка и разметка", "Блок 1 · Корпус", slide_no)
|
||||
add_panel(slide, 0.45, 1.35, 6.2, 5.55, fill=CREAM)
|
||||
add_bullets(slide, 0.65, 1.55, 5.85, 5.1, [
|
||||
"Источник — игровые реплики; CSV в cp1251, XLSX как резерв.",
|
||||
"Нормализация Unicode: многоточия, кавычки, тире, неразрывные пробелы.",
|
||||
"Плейсхолдеры <PLAYER>, <FARM> и другие заменены нейтральными словами; их количество сохранено отдельным признаком.",
|
||||
"Токенизация и разбиение на предложения — NLTK; частеречная разметка — Penn Treebank → Universal POS.",
|
||||
"Стоп-слова, пунктуация и регистр не удалялись: это стилометрический сигнал.",
|
||||
], size=15, title="Что сделали")
|
||||
add_panel(slide, 6.9, 1.35, 5.95, 5.55, fill=CREAM_LIGHT)
|
||||
add_bullets(slide, 7.1, 1.55, 5.55, 5.1, [
|
||||
"Шум: дубликаты, технические артефакты, общие короткие фразы.",
|
||||
"Ручная фильтрация уже удалила короткие и общие реплики; правила не сохранились.",
|
||||
"В текущем CSV 0 точных дублей; 13 пар с косинусной близостью ≥ 0,8 — все внутри одного персонажа.",
|
||||
"Нет идентификаторов сцен и диалогов; это ограничение анализа.",
|
||||
"Разметка — только имя персонажа; никакой ручной стилистической разметки нет.",
|
||||
], size=15, title="Что считаем шумом")
|
||||
add_notes(slide, "Объяснить различие между шумом и стилем. Удалять пунктуацию или стоп-слова нельзя: это носители стиля. Ручная фильтрация — ограничение: она могла удалить как раз короткие характерные реплики. Отметить, что почти-дубликаты не пересекают классы, поэтому утечки через них нет.")
|
||||
|
||||
# 9. Паспорт корпуса
|
||||
slide_no += 1
|
||||
slide = content_slide(prs, "Паспорт корпуса", "Блок 1 · Корпус", slide_no)
|
||||
add_panel(slide, 0.45, 1.35, 7.1, 5.55, fill=CREAM_LIGHT)
|
||||
rows = [
|
||||
["Показатель", "Значение"],
|
||||
["Реплик", "2005"],
|
||||
["Персонажей", "34"],
|
||||
["Словоупотреблений", "38 207"],
|
||||
["Средняя длина", "19,1 слова"],
|
||||
["Медиана длины", "17 слов"],
|
||||
["Минимум / максимум", "3 / 78 слов"],
|
||||
["Классы", "15–139 реплик"],
|
||||
["Точные дубли", "0"],
|
||||
["Почти-дубли (cos ≥ 0,8)", "13 пар, все внутри класса"],
|
||||
]
|
||||
add_table(slide, 0.7, 1.6, 6.6, 4.9, rows, col_widths=[3.4, 3.2], font_size=13)
|
||||
add_panel(slide, 7.85, 1.35, 5.05, 5.55, fill=CREAM)
|
||||
add_bullets(slide, 8.02, 1.6, 4.7, 5.0, [
|
||||
"Корпус пригоден для классификации, но не для сильных выводов о «чистом» стиле.",
|
||||
"Ограничения: короткие тексты, дисбаланс, один автор, отсутствие сцен.",
|
||||
"Все дальнейшие результаты нужно сравнивать с случайным уровнем 2,9%, а не с нулём.",
|
||||
], size=16, title="Что важно")
|
||||
add_notes(slide, "Этот слайд — «паспорт» данных. Подчеркнуть: 2005 объектов и 34 класса — небольшая выборка; 15 реплик у Mister Qi дают очень широкий интервал. Поэтому дальше используем повторную перекрёстную проверку и доверительные интервалы.")
|
||||
|
||||
# 10. Блок 2
|
||||
slide_no += 1
|
||||
section_slide(
|
||||
prs, 2, "Блок 2", "Методология: как измеряем стиль",
|
||||
"Признаки, модели и проверка — на понятном языке",
|
||||
["Какие свойства текста считаем", "Как работает LSA", "Как устроены классификаторы", "Как не обмануть себя при оценке"],
|
||||
"Передать слово рассказчику 2. Блок объясняет, что именно подаётся модели и почему выбранным методам можно доверять.",
|
||||
slide_no,
|
||||
)
|
||||
|
||||
# 11. Общая схема
|
||||
slide_no += 1
|
||||
slide = content_slide(prs, "Общая схема", "Блок 2 · Методология", slide_no)
|
||||
steps = [
|
||||
("Реплика", "текст персонажа"),
|
||||
("Очистка", "Unicode, плейсхолдеры, пробелы"),
|
||||
("Токены и POS", "NLTK, Universal POS"),
|
||||
("Признаки", "ручные, n-граммы, LSA"),
|
||||
("Модель", "вероятности по 34 классам"),
|
||||
("Проверка", "5 блоков, повторы, метрики"),
|
||||
]
|
||||
x = 0.55
|
||||
for index, (title, subtitle) in enumerate(steps):
|
||||
add_panel(slide, x, 2.0, 1.85, 2.35, fill=CREAM)
|
||||
add_text(slide, x + 0.12, 2.18, 1.6, 0.45, title, size=15, bold=True, color=GREEN_DARK, align=PP_ALIGN.CENTER)
|
||||
add_text(slide, x + 0.1, 2.7, 1.65, 1.1, subtitle, size=12, color=DARK, align=PP_ALIGN.CENTER)
|
||||
if index < len(steps) - 1:
|
||||
add_text(slide, x + 1.82, 2.7, 0.35, 0.4, "→", size=26, bold=True, color=RED, align=PP_ALIGN.CENTER)
|
||||
x += 2.12
|
||||
add_panel(slide, 0.55, 4.75, 12.15, 1.55, fill=CREAM_LIGHT)
|
||||
add_bullets(slide, 0.75, 4.9, 11.75, 1.2, [
|
||||
"Обучение и преобразования выполняются только внутри обучающих блоков; тест не участвует в настройке.",
|
||||
"Вектор признаков — это не «понимание» текста, а числовое описание его статистических свойств.",
|
||||
], size=15)
|
||||
add_notes(slide, "Провести по схеме слева направо. Главная мысль: модель не «читает» реплику как человек, а считает частоты и комбинации признаков. Отдельно подчеркнуть, что все преобразования обучаются внутри блоков, иначе возникает утечка.")
|
||||
|
||||
# 12. Признаки
|
||||
slide_no += 1
|
||||
slide = content_slide(prs, "Признаки: что именно считаем", "Блок 2 · Методология", slide_no)
|
||||
add_panel(slide, 0.45, 1.35, 6.1, 5.55, fill=CREAM)
|
||||
add_bullets(slide, 0.65, 1.55, 5.7, 5.1, [
|
||||
"454 ручных признака в 11 группах.",
|
||||
"Длина: слова, символы, предложения, длина слова.",
|
||||
"Лексика и орфография: распределение длин, апострофы, заглавные, цифры.",
|
||||
"Словарное богатство: TTR, Guiraud, Yule's K, hapax; на коротких текстах нестабильно.",
|
||||
"Пунктуация: запятые, многоточия, восклицания, вопросы, повторы.",
|
||||
"Разговорные маркеры: междометия, смех, хеджи, вежливость, сокращения.",
|
||||
], size=14, title="Ручные признаки")
|
||||
add_panel(slide, 6.75, 1.35, 6.1, 5.55, fill=CREAM_LIGHT)
|
||||
add_bullets(slide, 6.95, 1.55, 5.7, 5.1, [
|
||||
"Функциональные слова: около 150 закрытых слов и 12 категорий.",
|
||||
"Местоимения: 1-е, 2-е, 3-е лицо; отношение «я/ты».",
|
||||
"Части речи: Universal POS и Penn Treebank, время, модальность, герундий.",
|
||||
"Синтаксис-прокси: клаузы, подчинительные союзы, пассив, вопросы.",
|
||||
"Символьные n-граммы 2–5 и POS n-граммы 1–3.",
|
||||
"LSA: сжатые векторы по словам, содержанию, POS и символам.",
|
||||
], size=14, title="Синтаксис и векторные блоки")
|
||||
add_notes(slide, "Не перечислять все 454 признака. Объяснить логику: закрытые классы слов и пунктуация — классическая стилометрия; n-граммы — морфология и синтаксис; LSA — сжатое представление. Отметить, что словарное богатство и читаемость помечены как предварительные из-за коротких текстов.")
|
||||
|
||||
# 13. LSA
|
||||
slide_no += 1
|
||||
slide = content_slide(prs, "LSA простыми словами", "Блок 2 · Методология", slide_no)
|
||||
add_panel(slide, 0.45, 1.35, 6.0, 5.55, fill=CREAM)
|
||||
add_bullets(slide, 0.65, 1.55, 5.6, 5.1, [
|
||||
"Строим таблицу «документ × термин» и взвешиваем частоты через TF-IDF.",
|
||||
"Сжимаем таблицу сингулярным разложением до k скрытых осей.",
|
||||
"Каждая реплика превращается в вектор из k чисел.",
|
||||
"Оси не обязаны быть темами; их знак и порядок произвольны.",
|
||||
"k выбирается по объяснённой дисперсии и внешней метрике.",
|
||||
], size=16, title="Идея")
|
||||
add_panel(slide, 6.65, 1.35, 6.2, 5.55, fill=CREAM_LIGHT)
|
||||
add_bullets(slide, 6.85, 1.55, 5.8, 5.1, [
|
||||
"word — лексика и тема.",
|
||||
"content — содержание после удаления стоп-слов.",
|
||||
"fw — функциональные слова и синтаксис.",
|
||||
"char — морфология и пунктуация.",
|
||||
"pos — частеречные последовательности.",
|
||||
"В работе k = 50–400; для content-LSA качество росло до 400.",
|
||||
], size=15, title="Варианты")
|
||||
add_panel(slide, 0.65, 5.65, 11.9, 0.8, fill=CREAM_LIGHT)
|
||||
add_text(slide, 0.8, 5.78, 11.6, 0.5, "Ключевой вопрос: LSA по содержательным словам ловит тему, а не только стиль.", size=15, bold=True, color=RED, align=PP_ALIGN.CENTER)
|
||||
add_notes(slide, "Объяснить SVD без формул: было много столбцов, стало несколько скрытых осей; это сжатие, а не «понимание». Вариант content может кодировать тему, поэтому его нельзя автоматически называть стилем. В конце блока результатов покажем контроль содержания.")
|
||||
|
||||
# 14. Логистическая регрессия
|
||||
slide_no += 1
|
||||
slide = content_slide(prs, "Логистическая регрессия", "Блок 2 · Методология", slide_no)
|
||||
add_panel(slide, 0.45, 1.35, 6.0, 5.55, fill=CREAM)
|
||||
add_text(slide, 0.7, 1.55, 5.5, 0.35, "Как работает", size=18, bold=True, color=GREEN_DARK)
|
||||
add_bullets(slide, 0.7, 1.95, 5.5, 4.7, [
|
||||
"Для каждого класса считает линейную оценку: сумма «признак × вес» плюс сдвиг.",
|
||||
"Преобразует оценки в вероятности через softmax: они неотрицательны и суммируются в 1.",
|
||||
"Обучение минимизирует перекрёстную энтропию — штраф за низкую вероятность истинного класса.",
|
||||
"L2-регуляризация удерживает веса малыми; параметр C задаёт силу регуляризации.",
|
||||
"class_weight='balanced' повышает вес редких классов.",
|
||||
], size=15)
|
||||
add_panel(slide, 6.65, 1.35, 6.2, 5.55, fill=CREAM_LIGHT)
|
||||
add_bullets(slide, 6.85, 1.55, 5.8, 5.1, [
|
||||
"Плюсы: устойчива при большом числе признаков, даёт вероятности, коэффициенты интерпретируемы.",
|
||||
"Минусы: только линейные границы; при сильной корреляции признаков веса нестабильны.",
|
||||
"В работе — основная линейная базовая модель.",
|
||||
"Сравнение C: 0,03–10; балансировка классов критична.",
|
||||
], size=15, title="Что важно")
|
||||
add_notes(slide, "Объяснить на пальцах: модель складывает голоса признаков с весами; softmax превращает их в вероятности. C — обратная сила штрафа: меньше C — проще модель. Отметить, что без балансировки редкие персонажи почти не находятся; это было видно в эксперименте.")
|
||||
|
||||
# 15. SVM и деревья
|
||||
slide_no += 1
|
||||
slide = content_slide(prs, "SVM, деревья и ансамбли", "Блок 2 · Методология", slide_no)
|
||||
add_panel(slide, 0.45, 1.35, 6.05, 5.55, fill=CREAM)
|
||||
add_bullets(slide, 0.65, 1.55, 5.65, 5.1, [
|
||||
"Линейный SVM ищет границу с максимальным зазором; C штрафует ошибки.",
|
||||
"SVM не даёт вероятностей: только решающую функцию; тройка лучших считается по её значениям.",
|
||||
"Случайный лес: много деревьев, каждое на своей подвыборке; голосование.",
|
||||
"Сверхслучайные деревья (ExtraTrees): пороги в узлах выбираются случайно; меньше дисперсии, быстрее.",
|
||||
"Мягкое голосование усредняет вероятности нескольких моделей.",
|
||||
"Стековое объединение обучает мета-классификатор на предсказаниях базовых моделей.",
|
||||
], size=14, title="Идеи моделей")
|
||||
add_panel(slide, 6.7, 1.35, 6.15, 5.55, fill=CREAM_LIGHT)
|
||||
add_bullets(slide, 6.9, 1.55, 5.75, 5.1, [
|
||||
"Деревья ловят нелинейные взаимодействия: «если многоточий и короткая реплика — это Jas».",
|
||||
"Легко переобучаются на 2005 объектах; нужны ограничения глубины и листьев.",
|
||||
"Дельта Барроуза: расстояние до центроида по частотным словам — классическая проверка.",
|
||||
"Ансамбль полезен, если ошибки моделей не полностью совпадают.",
|
||||
"Стековое объединение в наших данных переобучилось и оказалось хуже голосования.",
|
||||
], size=14, title="Сильные и слабые стороны")
|
||||
add_notes(slide, "Не углубляться в оптимизацию. Главное: SVM — линейная граница с зазором; деревья — нелинейные правила; ExtraTrees — случайные пороги; голосование усредняет вероятности. Дельта Барроуза — независимая классическая проверка, а не ещё одна нейросеть.")
|
||||
|
||||
# 16. Проверка
|
||||
slide_no += 1
|
||||
slide = content_slide(prs, "Как проверяем", "Блок 2 · Методология", slide_no)
|
||||
add_panel(slide, 0.45, 1.35, 6.25, 5.55, fill=CREAM)
|
||||
add_bullets(slide, 0.65, 1.55, 5.85, 5.1, [
|
||||
"Стратифицированная перекрёстная проверка: 5 блоков, доли классов сохраняются.",
|
||||
"Повторяем 3–5 раз с разными разбиениями — оцениваем разброс.",
|
||||
"Вложенная проверка: внешние блоки — оценка, внутренние — настройка.",
|
||||
"Отложенная выборка 80/20 — независимая проверка финальных кандидатов.",
|
||||
], size=16, title="Схема")
|
||||
add_panel(slide, 6.9, 1.35, 5.95, 5.55, fill=CREAM_LIGHT)
|
||||
add_bullets(slide, 7.1, 1.55, 5.55, 5.1, [
|
||||
"TF-IDF, SVD, масштабирование и отбор признаков обучаются только на обучающей выборке.",
|
||||
"Тестовая выборка не участвует в выборе гиперпараметров или признаков.",
|
||||
"Проверка перестановкой меток дала 0,027–0,028 — случайный уровень.",
|
||||
"Почти-дубликаты внутри одного класса не пересекают обучение и тест.",
|
||||
"Метрики: макро-F1, сбалансированная доля, тройка лучших, MCC, каппа, матрица ошибок.",
|
||||
], size=15, title="Контроль утечек")
|
||||
add_notes(slide, "Объяснить, почему нельзя настраивать на тесте. Привести аналогию: если подсматривать ответы, оценка будет завышена. Перестановка меток — контроль: если модель на случайных метках даёт 0,027, пайплайн не течёт.")
|
||||
|
||||
# 17. Метрики
|
||||
slide_no += 1
|
||||
slide = content_slide(prs, "Метрики: что и зачем", "Блок 2 · Методология", slide_no)
|
||||
add_panel(slide, 0.45, 1.35, 6.15, 5.55, fill=CREAM_LIGHT)
|
||||
rows = [
|
||||
["Метрика", "Что показывает"],
|
||||
["Макро-F1", "Средняя F-мера по 34 классам; все персонажи равны"],
|
||||
["Доля правильных", "Доля совпавших ответов; обманчива при дисбалансе"],
|
||||
["Сбалансированная доля", "Средняя полнота по классам"],
|
||||
["Тройка лучших", "Истинный класс в тройке лучших"],
|
||||
["MCC / каппа", "Согласие с поправкой на случай"],
|
||||
["Матрица ошибок", "Кто с кем путается"],
|
||||
]
|
||||
add_table(slide, 0.65, 1.55, 5.75, 4.9, rows, col_widths=[2.2, 3.6], font_size=12)
|
||||
add_panel(slide, 6.8, 1.35, 6.05, 5.55, fill=CREAM)
|
||||
add_bullets(slide, 7.0, 1.55, 5.65, 5.1, [
|
||||
"Случайный уровень при 34 классах: macro-F1 ≈ 0,029, top-3 ≈ 0,088.",
|
||||
"Модель, всегда выбирающая Abigail: доля правильных ответов ≈ 6,9%, macro-F1 ≈ 0,004.",
|
||||
"Поэтому основной вывод делаем по macro-F1 и доверительным интервалам.",
|
||||
"Дополнительно смотрим F-меру по каждому персонажу и матрицу ошибок.",
|
||||
], size=16, title="Почему не доля правильных ответов")
|
||||
add_notes(slide, "Главный методический слайд: объяснить, что accuracy при 34 классах может вводить в заблуждение. Пример Dummy: 6,9% accuracy, но почти нулевая macro-F1. Поэтому все выводы — по macro-F1, а per-class F1 показывает, кого именно модель не находит.")
|
||||
|
||||
# 18. Блок 3
|
||||
slide_no += 1
|
||||
section_slide(
|
||||
prs, 3, "Блок 3", "Результаты и интерпретация",
|
||||
"Что получилось, кого модель путает и что это значит",
|
||||
["Лучшие модели и масштаб качества", "Стиль или содержание", "Матрицы ошибок и пары персонажей", "Ограничения и выводы"],
|
||||
"Передать слово рассказчику 3. Здесь важно не только назвать число, но и объяснить, что оно означает и чего оно не означает.",
|
||||
slide_no,
|
||||
)
|
||||
|
||||
# 19. Главный результат
|
||||
slide_no += 1
|
||||
slide = content_slide(prs, "Главный результат", "Блок 3 · Результаты", slide_no)
|
||||
add_panel(slide, 0.4, 1.35, 9.0, 5.55, fill=CREAM_LIGHT)
|
||||
add_image_fit(slide, GEN / "model_comparison.png", 0.5, 1.45, 8.8, 5.35)
|
||||
add_panel(slide, 9.55, 1.35, 3.35, 5.55, fill=CREAM)
|
||||
add_bullets(slide, 9.72, 1.55, 3.05, 5.1, [
|
||||
"Мягкое голосование: 0,304.",
|
||||
"ExtraTrees, char=10000: 0,276.",
|
||||
"Вложенный выбор: 0,275.",
|
||||
"Случайный уровень: 0,029.",
|
||||
"На отложенной выборке: 0,333.",
|
||||
], size=15, title="Макро-F1")
|
||||
add_notes(slide, "Назвать числа и сразу масштаб: 0,30 при 34 классах — это примерно в 10 раз выше случайного. Не говорить «низкая точность» без сравнения с 0,029. Оговорить, что 0,333 на отложенной выборке — одно разбиение и широкий интервал.")
|
||||
|
||||
# 20. Что добавляет качество
|
||||
slide_no += 1
|
||||
slide = content_slide(prs, "Что добавляет качество", "Блок 3 · Результаты", slide_no)
|
||||
add_panel(slide, 0.4, 1.35, 8.95, 5.55, fill=CREAM_LIGHT)
|
||||
add_image_fit(slide, GEN / "feature_progression.png", 0.5, 1.45, 8.75, 5.35)
|
||||
add_panel(slide, 9.5, 1.35, 3.4, 5.55, fill=CREAM)
|
||||
add_bullets(slide, 9.67, 1.55, 3.1, 5.1, [
|
||||
"Ручные признаки: 0,105.",
|
||||
"+ char/POS n-граммы: 0,182.",
|
||||
"+ content-LSA: 0,249.",
|
||||
"ExtraTrees + char=10000: 0,276.",
|
||||
"Мягкое голосование: 0,304.",
|
||||
"Основной прирост — лексика и n-граммы, не «чистый синтаксис».",
|
||||
], size=14, title="По шагам")
|
||||
add_notes(slide, "Показать, что качество растёт не от «магии модели», а от добавления лексических и символьных признаков. Отметить: функциональные слова и POS- LSA сами по себе дают только 0,06–0,07.")
|
||||
|
||||
# 21. Стиль или содержание
|
||||
slide_no += 1
|
||||
slide = content_slide(prs, "Стиль или содержание?", "Блок 3 · Результаты", slide_no)
|
||||
add_panel(slide, 0.4, 1.35, 8.4, 5.55, fill=CREAM_LIGHT)
|
||||
add_image_fit(slide, GEN / "topic_control.png", 0.5, 1.45, 8.2, 5.35)
|
||||
add_panel(slide, 8.95, 1.35, 3.95, 5.55, fill=CREAM)
|
||||
add_bullets(slide, 9.12, 1.55, 3.65, 5.1, [
|
||||
"Исходный текст: 0,248.",
|
||||
"После замены содержательных слов: 0,113.",
|
||||
"Падение более чем вдвое.",
|
||||
"Остаток 0,113 всё ещё в 3,9 раза выше случайного.",
|
||||
"Значит, стиль есть, но содержание доминирует.",
|
||||
], size=15, title="Контроль")
|
||||
add_notes(slide, "Это ключевой слайд для честной интерпретации. Содержательные слова заменили на CONTENT, оставив служебные слова, POS и пунктуацию. Качество упало с 0,248 до 0,113. Значит, нельзя утверждать, что модель выучила «чистый идиолект»: она во многом различает, о чём персонаж говорит.")
|
||||
|
||||
# 22. Как читать матрицу
|
||||
slide_no += 1
|
||||
slide = content_slide(prs, "Как читать матрицу ошибок", "Блок 3 · Результаты", slide_no)
|
||||
add_panel(slide, 0.45, 1.35, 6.2, 5.55, fill=CREAM_LIGHT)
|
||||
add_text(slide, 0.7, 1.55, 5.7, 0.4, "Маленький пример", size=17, bold=True, color=GREEN_DARK)
|
||||
mini = [
|
||||
["истина \\ прогноз", "A", "B", "C"],
|
||||
["A", "70", "20", "10"],
|
||||
["B", "15", "75", "10"],
|
||||
["C", "5", "25", "70"],
|
||||
]
|
||||
add_table(slide, 0.7, 2.05, 5.6, 2.5, mini, col_widths=[2.2, 1.1, 1.1, 1.1], font_size=14)
|
||||
add_text(slide, 0.7, 4.7, 5.7, 1.8, "Строка — истинный персонаж. Столбец — предсказание модели. Диагональ — правильные ответы. Вне диагонали — ошибки. Числа — проценты от строки.", size=14, color=DARK)
|
||||
add_panel(slide, 6.85, 1.35, 6.0, 5.55, fill=CREAM)
|
||||
add_bullets(slide, 7.05, 1.55, 5.6, 5.1, [
|
||||
"Полная матрица 34×34 на доске нечитаема.",
|
||||
"Поэтому на слайде — 12 самых частых персонажей и столбец «прочие».",
|
||||
"Отдельно показываем 18 самых частых пар ошибок.",
|
||||
"Полная матрица 34×34 — в раздатке/приложении.",
|
||||
"На доске смотрим диагональ, крупные внедиагональные клетки и «прочие».",
|
||||
], size=15, title="Как показываем")
|
||||
add_notes(slide, "Объяснить матрицу на маленьком примере. Сказать: полная матрица 34×34 на доске превращается в кашу; поэтому используем три уровня: top-12 матрица, top-18 пар, полная матрица в раздатке. Это методически честно и читаемо.")
|
||||
|
||||
# 23. Матрица top12
|
||||
slide_no += 1
|
||||
slide = content_slide(prs, "Матрица ошибок: 12 персонажей", "Блок 3 · Результаты", slide_no)
|
||||
add_panel(slide, 0.35, 1.35, 9.05, 5.55, fill=CREAM_LIGHT)
|
||||
add_image_fit(slide, GEN / "confusion_top12.png", 0.45, 1.45, 8.85, 5.35)
|
||||
add_panel(slide, 9.6, 1.35, 3.3, 5.55, fill=CREAM)
|
||||
add_bullets(slide, 9.77, 1.55, 3.0, 5.1, [
|
||||
"Диагональ: доля правильных ответов внутри класса.",
|
||||
"Abigail: 21% верно, 18% уходит в «прочие».",
|
||||
"Alex: 37% верно, 11% → Abigail.",
|
||||
"Sebastian: 19% верно, 20% → Abigail.",
|
||||
"У многих строк крупная доля «прочих»: модель не находит редкие классы.",
|
||||
], size=13, title="Что видно")
|
||||
add_notes(slide, "Провести по строкам: Abigail, Alex, Sebastian. Обратить внимание на столбец «прочие»: он большой, потому что 22 класса не поместились. Это не «мусор», а важная часть ошибок. Сказать, что диагональ далеко не 100%, но и не 0.")
|
||||
|
||||
# 24. Кого путает
|
||||
slide_no += 1
|
||||
slide = content_slide(prs, "Кого модель путает", "Блок 3 · Результаты", slide_no)
|
||||
add_panel(slide, 0.35, 1.35, 9.0, 5.55, fill=CREAM_LIGHT)
|
||||
add_image_fit(slide, GEN / "confusion_pairs.png", 0.45, 1.45, 8.8, 5.35)
|
||||
add_panel(slide, 9.55, 1.35, 3.35, 5.55, fill=CREAM)
|
||||
add_bullets(slide, 9.72, 1.55, 3.05, 5.1, [
|
||||
"Sebastian → Abigail: 65.",
|
||||
"Sebastian → Alex: 61.",
|
||||
"Abigail → Sebastian: 59.",
|
||||
"Abigail → Alex: 56.",
|
||||
"Основной клубок — подростки/молодые взрослые: Abigail, Alex, Sebastian, Haley, Sam.",
|
||||
], size=13, title="Частые пары")
|
||||
add_notes(slide, "Показать, что ошибки не случайны: они концентрируются в одном круге персонажей. Это ожидаемо: один автор, общие темы, сходная эмоциональная окраска, короткие реплики. Это не доказательство «автор не старался», а наоборот — свидетельство близких речевых масок внутри группы.")
|
||||
|
||||
# 25. Классы-аттракторы
|
||||
slide_no += 1
|
||||
slide = content_slide(prs, "Классы-аттракторы", "Блок 3 · Результаты", slide_no)
|
||||
add_panel(slide, 0.4, 1.35, 9.0, 5.55, fill=CREAM_LIGHT)
|
||||
add_image_fit(slide, GEN / "hub_shares.png", 0.5, 1.45, 8.8, 5.35)
|
||||
add_panel(slide, 9.55, 1.35, 3.35, 5.55, fill=CREAM)
|
||||
add_bullets(slide, 9.72, 1.55, 3.05, 5.1, [
|
||||
"Alex: 11,4% предсказаний при истинной доле 6,0%.",
|
||||
"Abigail: 10,9% при 6,9%.",
|
||||
"Редкие классы недопредставлены.",
|
||||
"Это не «демографическая» путаница, а стягивание к популярным голосам.",
|
||||
"Коррекция приоров не улучшила macro-F1.",
|
||||
], size=13, title="Кто перетягивает")
|
||||
add_notes(slide, "Объяснить «класс-аттрактор»: модель чаще выбирает Alex и Abigail, потому что их признаки часто встречаются и они похожи на многих. Редкие персонажи проигрывают. Простая коррекция приоров не помогла; нужны более тонкие правила или больше данных.")
|
||||
|
||||
# 26. Per-class F1
|
||||
slide_no += 1
|
||||
slide = content_slide(prs, "Персонажи: кто различается", "Блок 3 · Результаты", slide_no)
|
||||
add_panel(slide, 0.3, 1.35, 9.25, 5.55, fill=CREAM_LIGHT)
|
||||
add_image_fit(slide, GEN / "per_class_f1.png", 0.4, 1.45, 9.05, 5.35)
|
||||
add_panel(slide, 9.75, 1.35, 3.15, 5.55, fill=CREAM)
|
||||
add_bullets(slide, 9.9, 1.55, 2.85, 5.1, [
|
||||
"Лучшие: Willy, Evelyn, Pierre, Leo, Pam.",
|
||||
"Худшие: Kent, Mister Qi, Penny, Abigail, Leah.",
|
||||
"Редкие классы — широкие интервалы.",
|
||||
"Мягкое голосование улучшает Wizard, Dwarf, Vincent.",
|
||||
], size=13, title="Факты")
|
||||
add_notes(slide, "Показать, что качество по персонажам неоднородно. Evelyn/Willy/Pierre различаются лучше; Kent/Mister Qi/Penny — хуже. Это связано и с числом реплик, и с их речевой функцией. Не делать вывод, что персонаж «плохой»: у редких классов просто мало данных.")
|
||||
|
||||
# 27. Почему путаются
|
||||
slide_no += 1
|
||||
slide = content_slide(prs, "Почему персонажи путаются", "Блок 3 · Результаты", slide_no)
|
||||
add_panel(slide, 0.45, 1.35, 6.15, 5.55, fill=CREAM)
|
||||
add_bullets(slide, 0.65, 1.55, 5.75, 5.1, [
|
||||
"Один автор и общие сюжетные контексты: персонажи говорят об одних и тех же событиях.",
|
||||
"Сходные возраст и роль: подростки/молодые взрослые используют похожие темы и интонации.",
|
||||
"Короткие реплики: мало слов, чтобы проявить индивидуальный синтаксис.",
|
||||
"Дисбаланс: редкие классы реже выбираются моделью.",
|
||||
"Общие формулы вежливости, шутки, междометия.",
|
||||
], size=15, title="Причины")
|
||||
add_panel(slide, 6.8, 1.35, 6.05, 5.55, fill=CREAM_LIGHT)
|
||||
add_bullets(slide, 7.0, 1.55, 5.65, 5.1, [
|
||||
"Клубок: Abigail, Alex, Sebastian, Haley, Sam.",
|
||||
"Нелюди (Krobus, Dwarf, Wizard) различаются лучше, но Wizard и Dwarf редки.",
|
||||
"Дети (Jas, Vincent) имеют короткие реплики; их легче узнать по длине.",
|
||||
"Это не доказательство сходства «характеров», а свойство текстовых признаков.",
|
||||
"Нужны сцены и больше данных, чтобы отделить стиль от контекста.",
|
||||
], size=15, title="Что это значит")
|
||||
add_notes(slide, "Ответить на вопрос «почему одни и те же персонажи путаются»: один автор, общие темы, сходный возраст/роль, короткие реплики, дисбаланс. Подчеркнуть: мы не делаем психологических выводов о персонажах; мы объясняем статистику ошибок.")
|
||||
|
||||
# 28. Что означают оценки
|
||||
slide_no += 1
|
||||
slide = content_slide(prs, "Что означают оценки", "Блок 3 · Результаты", slide_no)
|
||||
add_panel(slide, 0.45, 1.35, 12.4, 5.55, fill=CREAM)
|
||||
add_bullets(slide, 0.7, 1.55, 11.9, 5.1, [
|
||||
"0,30 макро-F1 при 34 классах — это примерно в 10 раз выше случайного уровня 0,029.",
|
||||
"Абсолютное значение умеренное: по одной короткой реплике персонажа действительно трудно определить.",
|
||||
"Это не означает, что автор «не старался» или что стиль неразличим. Наоборот, устойчивое превышение случайного уровня говорит о намеренно различных голосах.",
|
||||
"Умеренность объясняется короткими текстами, дисбалансом классов, общими темами и формулами, а не отсутствием стиля.",
|
||||
"Мы не решаем задачу «он или не он писал»: все реплики одного автора. Мы решаем, какой персонаж говорит.",
|
||||
], size=17, title="Уточнение интерпретации")
|
||||
add_notes(slide, "Здесь важно поправить исходную формулировку. Нельзя говорить «низкие оценки хорошие, потому что автор не старался». Корректно: оценки выше случайного уровня, но умеренные; это свидетельствует о частичной различимости голосов. Один автор — значит, различия созданы намеренно, а не разными авторами. Низкие абсолютные значения — следствие трудности задачи, а не приговор стилю.")
|
||||
|
||||
# 29. Ограничения
|
||||
slide_no += 1
|
||||
slide = content_slide(prs, "Ограничения", "Блок 3 · Результаты", slide_no)
|
||||
add_panel(slide, 0.45, 1.35, 6.15, 5.55, fill=CREAM)
|
||||
add_bullets(slide, 0.65, 1.55, 5.75, 5.1, [
|
||||
"Один корпус, одна игра, один автор.",
|
||||
"Нет внешней валидации на другом корпусе.",
|
||||
"Короткие реплики и дисбаланс классов.",
|
||||
"Ручная фильтрация корпуса не воспроизводится.",
|
||||
"Нет идентификаторов сцен и диалогов.",
|
||||
], size=16, title="Данные")
|
||||
add_panel(slide, 6.8, 1.35, 6.05, 5.55, fill=CREAM_LIGHT)
|
||||
add_bullets(slide, 7.0, 1.55, 5.65, 5.1, [
|
||||
"Выбор признаков и ансамбля опирался на предыдущие результаты.",
|
||||
"Вложенная проверка снижает, но не устраняет оптимизм.",
|
||||
"Содержательные слова и тема вносят большой вклад.",
|
||||
"Простая калибровка приоров не помогла.",
|
||||
"Стековое объединение переобучается на малом числе объектов.",
|
||||
], size=16, title="Методология")
|
||||
add_notes(slide, "Перечислить ограничения честно. Главное: нельзя переносить выводы на другие игры/авторов без внешней проверки. И нельзя называть результат «чистым стилем», пока не отделена тема.")
|
||||
|
||||
# 30. Выводы
|
||||
slide_no += 1
|
||||
slide = content_slide(prs, "Выводы", "Блок 3 · Результаты", slide_no)
|
||||
add_panel(slide, 0.45, 1.35, 12.4, 5.55, fill=CREAM)
|
||||
add_bullets(slide, 0.75, 1.55, 11.8, 5.1, [
|
||||
"Персонажи статистически различимы: лучшая модель — 0,304 макро-F1, консервативная оценка — 0,275 при случайном уровне 0,029.",
|
||||
"Основной вклад дают лексика, символьные n-граммы и LSA по содержательным словам; функциональные слова и синтаксис сами по себе слабы.",
|
||||
"После замены содержательных слов качество падает с 0,248 до 0,113: содержание доминирует, но стилевой сигнал сохраняется.",
|
||||
"Главная проблема — редкие классы и классы-аттракторы; мягкое голосование помогает, но не решает её полностью.",
|
||||
"Корректный вывод: различимы речевые маски по лексико-тематическому профилю; для утверждения о «чистом стиле» нужны внешняя валидация и тематический контроль.",
|
||||
], size=17, title="Пять тезисов")
|
||||
add_notes(slide, "Сформулировать выводы без переубеждения аудитории. Главное: не «мы всё доказали», а «мы получили устойчивый выше случайного уровня сигнал; основной вклад — лексика/тема; для стиля нужны дальнейшие проверки».")
|
||||
|
||||
# 31. Приложение: полная матрица
|
||||
slide_no += 1
|
||||
slide = content_slide(prs, "Приложение: полная матрица 34×34", "Приложение", slide_no)
|
||||
add_panel(slide, 0.35, 1.35, 7.4, 5.55, fill=CREAM_LIGHT)
|
||||
add_image_fit(slide, GEN / "confusion_full34.png", 0.45, 1.45, 7.2, 5.35)
|
||||
add_panel(slide, 7.95, 1.35, 4.95, 5.55, fill=CREAM)
|
||||
add_bullets(slide, 8.12, 1.55, 4.6, 5.1, [
|
||||
"Полная матрица 34×34 — для раздаточного материала.",
|
||||
"На проекторе она слишком плотная; в докладе используем top-12 и top-18 пар.",
|
||||
"Строки — истинные персонажи; столбцы — предсказания; цвет — доля строки.",
|
||||
"Диагональ — правильные ответы; тёмные внедиагональные клетки — систематические ошибки.",
|
||||
], size=15, title="Как использовать")
|
||||
add_notes(slide, "Этот слайд можно не показывать, а раздать. Если показывать, не читать все клетки: показать диагональ и несколько тёмных пятен.")
|
||||
|
||||
# 32. Финал: мем
|
||||
slide_no += 1
|
||||
slide = prs.slides.add_slide(prs.slide_layouts[6])
|
||||
add_background(slide)
|
||||
add_panel(slide, 0.55, 1.2, 6.2, 5.45, fill=CREAM)
|
||||
add_text(slide, 0.85, 1.5, 5.6, 1.0, "Спасибо за внимание!", size=34, bold=True, color=GREEN_DARK)
|
||||
add_text(slide, 0.85, 2.55, 5.5, 1.5, "Вопросы, обсуждение и предложения по экспериментам", size=18, color=DARK)
|
||||
add_text(slide, 0.85, 4.2, 5.5, 1.2, "«Один автор — разные голоса»", size=20, bold=True, color=RED)
|
||||
add_panel(slide, 7.0, 0.9, 5.8, 6.0, fill=CREAM_LIGHT)
|
||||
add_image_fit(slide, GEN / "meme.png", 7.15, 1.05, 5.5, 5.7)
|
||||
add_chicken(slide, 0.25, 7.05, 0.28)
|
||||
add_notes(slide, "Финальный слайд с мемом. Поблагодарить, перейти к вопросам. Курица спрятана слева внизу — пасхалка.")
|
||||
|
||||
prs.save(OUT)
|
||||
print(f"Готово: {OUT}")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
|
Before Width: | Height: | Size: 2.4 MiB |
|
Before Width: | Height: | Size: 178 KiB |
|
Before Width: | Height: | Size: 170 KiB |
|
Before Width: | Height: | Size: 238 KiB |
|
Before Width: | Height: | Size: 141 KiB |
|
Before Width: | Height: | Size: 96 KiB |
|
Before Width: | Height: | Size: 678 KiB |
|
Before Width: | Height: | Size: 132 KiB |
|
Before Width: | Height: | Size: 240 KiB |
|
Before Width: | Height: | Size: 104 KiB |
|
Before Width: | Height: | Size: 238 KiB |
|
Before Width: | Height: | Size: 314 KiB |
|
Before Width: | Height: | Size: 411 B |
|
Before Width: | Height: | Size: 680 KiB |
|
Before Width: | Height: | Size: 66 KiB |
|
Before Width: | Height: | Size: 795 KiB |
|
Before Width: | Height: | Size: 66 KiB |
@@ -1,985 +0,0 @@
|
||||
% !TeX program = xelatex
|
||||
% Стилометрическая классификация реплик Stardew Valley — академическая Beamer-презентация.
|
||||
% Сборка: xelatex stardew_stylometry.tex (два прохода) в каталоге latex/.
|
||||
\documentclass[aspectratio=169,10pt]{beamer}
|
||||
|
||||
\usepackage{fontspec}
|
||||
\usepackage[russian]{babel}
|
||||
\babelfont{rm}{Liberation Sans}
|
||||
\babelfont{sf}{Liberation Sans}
|
||||
\usepackage{amsmath}
|
||||
\usepackage{tikz}
|
||||
\usetikzlibrary{calc}
|
||||
\usepackage[most]{tcolorbox}
|
||||
\usepackage{graphicx}
|
||||
\usepackage{etoolbox}
|
||||
|
||||
\graphicspath{{../images/}{../generated/}}
|
||||
|
||||
% ---------- Цвета: сдержанная академическая гамма ----------
|
||||
\definecolor{cream}{RGB}{253,246,231}
|
||||
\definecolor{inkbrown}{RGB}{62,42,26}
|
||||
\definecolor{accent}{RGB}{140,95,45}
|
||||
|
||||
% ---------- Общий вид ----------
|
||||
\usefonttheme{professionalfonts}
|
||||
\setbeamertemplate{navigation symbols}{}
|
||||
\setbeamertemplate{caption}{\raggedright\insertcaption\par}
|
||||
\setbeamercolor{normal text}{fg=inkbrown}
|
||||
\setbeamercolor{frametitle}{fg=inkbrown}
|
||||
\setbeamercolor{structure}{fg=accent}
|
||||
\setbeamercolor{itemize item}{fg=accent}
|
||||
\setbeamercolor{itemize subitem}{fg=accent}
|
||||
\setbeamercolor{itemize/enumerate body}{fg=inkbrown}
|
||||
\setbeamercolor{enumerate item}{fg=accent}
|
||||
\setbeamersize{text margin left=0.55cm, text margin right=0.55cm}
|
||||
\setbeamertemplate{itemize item}{\textbullet}
|
||||
\setbeamertemplate{itemize subitem}{--}
|
||||
|
||||
% ---------- Нижний колонтитул: номер слайда и сколько осталось ----------
|
||||
% Рисуется в конце каждой рамки поверх содержимого и не меняет вёрстку.
|
||||
\newcommand{\slidefooter}{%
|
||||
\begin{tikzpicture}[remember picture, overlay]
|
||||
\node[anchor=north east, xshift=-0.1cm, yshift=-0.1cm]
|
||||
at (current page.north east)
|
||||
{\colorbox{cream}{\scriptsize\color{inkbrown}%
|
||||
\insertframenumber\slash\inserttotalframenumber
|
||||
\quad}};
|
||||
\end{tikzpicture}%
|
||||
}
|
||||
|
||||
|
||||
% ---------- Фон: изображение + маленькая курица на каждом слайде ----------
|
||||
\usebackgroundtemplate{%
|
||||
\begin{tikzpicture}[remember picture, overlay]
|
||||
\node at (current page.center)
|
||||
{\includegraphics[width=\paperwidth,height=\paperheight]{background.jpg}};
|
||||
\node[anchor=south east, xshift=-2.5mm, yshift=1.5mm]
|
||||
at (current page.south east)
|
||||
{\includegraphics[height=0.65cm]{chicken.png}};
|
||||
\end{tikzpicture}}
|
||||
|
||||
% ---------- Заголовок слайда: кремовая полоса во всю ширину ----------
|
||||
\newlength{\sidemargin}
|
||||
\setlength{\sidemargin}{0.55cm}
|
||||
\setbeamertemplate{frametitle}{%
|
||||
\nointerlineskip
|
||||
\vspace*{-0.2mm}%
|
||||
\hspace*{-\sidemargin}%
|
||||
\begin{tcolorbox}[enhanced, width=\paperwidth, colback=cream, colframe=cream,
|
||||
opacityback=0.90, arc=0mm, boxrule=0pt,
|
||||
left=\sidemargin, right=\sidemargin, top=1.2mm, bottom=1.2mm,
|
||||
before skip=0pt, after skip=2mm]
|
||||
{\usebeamerfont{frametitle}\usebeamercolor[fg]{frametitle}\large\bfseries\insertframetitle}
|
||||
\end{tcolorbox}}
|
||||
|
||||
% ---------- Кремовые панели для содержимого ----------
|
||||
\tcbset{panelstyle/.style={enhanced, colback=cream, opacityback=0.90,
|
||||
colframe=accent, opacityframe=0.65, boxrule=0.6pt, arc=1.4mm,
|
||||
boxsep=1.2mm, top=1.4mm, bottom=1.4mm, left=2.4mm, right=2.4mm,
|
||||
before skip=1.6mm, after skip=1.6mm}}
|
||||
\newtcolorbox{panel}[1][]{panelstyle, #1}
|
||||
|
||||
% ---------- Кремовый бокс с изображением ----------
|
||||
\newtcolorbox{imgbox}[1][]{enhanced, colback=cream, opacityback=0.90,
|
||||
colframe=accent, opacityframe=0.65, boxrule=0.6pt, arc=1.4mm,
|
||||
boxsep=0.8mm, top=0.8mm, bottom=0.8mm, left=0.8mm, right=0.8mm,
|
||||
before skip=1.6mm, after skip=1.6mm, halign=center, #1}
|
||||
|
||||
% ---------- Титульный шаблон ----------
|
||||
\title[Стилометрия реплик Stardew Valley]{Стилометрическая классификация реплик персонажей Stardew Valley}
|
||||
\author{А.~А.~Аладьина \and В.~Н.~Копнин \and М.~В.~Савостиков}
|
||||
\institute{ПИЭ-13МО}
|
||||
|
||||
\setbeamerfont{note page}{size=\footnotesize}
|
||||
|
||||
\begin{document}
|
||||
|
||||
% ==================== СЛАЙД: ТИТУЛ ====================
|
||||
\begin{frame}[plain]
|
||||
\begin{tikzpicture}[remember picture, overlay]
|
||||
\node at (current page.center)
|
||||
{\includegraphics[width=\paperwidth,height=\paperheight]{title.jpg}};
|
||||
\node[anchor=south east, xshift=-2.5mm, yshift=1.5mm]
|
||||
at (current page.south east)
|
||||
{\includegraphics[height=0.65cm]{chicken.png}};
|
||||
\end{tikzpicture}
|
||||
\vspace*{-2.2cm}
|
||||
\begin{center}
|
||||
\begin{tcolorbox}[enhanced, width=0.92\paperwidth, colback=cream, opacityback=0.92,
|
||||
colframe=cream, boxrule=0pt, arc=2mm, halign=center, boxsep=2mm]
|
||||
{\large\bfseries Стилометрическая классификация реплик персонажей Stardew Valley}\\[2mm]
|
||||
{\normalsize Можно ли различить голоса персонажей, \\ если все реплики написаны одним автором?}\\[2.4mm]
|
||||
А.~А.~Аладьина \quad·\quad В.~Н.~Копнин \quad·\quad М.~В.~Савостиков\\[0.8mm]
|
||||
\footnotesize ПИЭ-13МО
|
||||
\end{tcolorbox}
|
||||
\end{center}
|
||||
\slidefooter
|
||||
\end{frame}
|
||||
|
||||
% ==================== БЛОК 1 ====================
|
||||
\begin{frame}{Часть 1\ \ ·\ \ Задача и корпус}
|
||||
\begin{center}
|
||||
\begin{tcolorbox}[enhanced, width=0.86\paperwidth, colback=cream, opacityback=0.9,
|
||||
colframe=cream, boxrule=0pt, arc=2mm, halign=center, boxsep=3mm]
|
||||
{\large Часть 1}\\[1.5mm]
|
||||
{\Large\bfseries Решаемая задача и корпус}\\[2mm]
|
||||
Постановка задачи · сбор и очистка данных · характеристики корпуса\\[2.5mm]
|
||||
{\bfseries Докладчик: А.~А.~Аладьина}
|
||||
\end{tcolorbox}
|
||||
\end{center}
|
||||
\note{Часть\,1. Говорит А. А. Аладьина. План части: что за задача мы решаем,
|
||||
её формальная постановка, как собирали и чистили корпус, и какими
|
||||
характеристиками он обладает.}
|
||||
\slidefooter
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}{Задача и постановка}
|
||||
\begin{columns}[T]
|
||||
\begin{column}{0.57\textwidth}
|
||||
\begin{panel}
|
||||
|
||||
\textbf{Решаемая задача}
|
||||
\begin{itemize}
|
||||
\item Дано: 2005 коротких реплик, 34 персонажа, один автор.
|
||||
\item Требуется: по одной реплике определить персонажа.
|
||||
\item Каждый объект относится ровно к одному из 34 классов.
|
||||
\item Это не авторская атрибуция: мы различаем \emph{речевые маски персонажей}, а не авторов.
|
||||
\item Проверяется, статистически различимы ли эти маски.
|
||||
\end{itemize}
|
||||
\end{panel}
|
||||
\end{column}
|
||||
\begin{column}{0.41\textwidth}
|
||||
\begin{panel}
|
||||
|
||||
\textbf{Обозначения}\\[1mm]
|
||||
$D=\{(x_i,\,y_i)\}_{i=1}^{n}$,\quad $n=2005$;\\
|
||||
$x_i$ — реплика;\\
|
||||
$y_i\in Y$, \ $|Y|=34$ — персонаж;\\
|
||||
$f$ — классификатор.\\[1.5mm]
|
||||
Критерий качества — макро-F1: средняя F-мера по 34 классам.
|
||||
\end{panel}
|
||||
\end{column}
|
||||
\end{columns}
|
||||
\note{Говорит А. А. Аладьина. Задача — многоклассовая классификация коротких
|
||||
текстов. Формула одна: D — корпус, x — реплика, y — персонаж, f —
|
||||
классификатор. Дальше все определения даются словами. Подчеркнуть: это не
|
||||
авторская атрибуция, автор один; мы различаем речевые маски.}
|
||||
\slidefooter
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}{Корпус: сбор, разметка, очистка}
|
||||
\begin{columns}[T]
|
||||
\begin{column}{0.33\textwidth}
|
||||
\begin{panel}
|
||||
\footnotesize\textbf{1. Сбор}
|
||||
\begin{itemize}
|
||||
\item Реплики из диалогов игры.
|
||||
\item Единица анализа — одна реплика.
|
||||
\item 34 персонажа, тексты на английском.
|
||||
\end{itemize}
|
||||
\end{panel}
|
||||
\end{column}
|
||||
\begin{column}{0.33\textwidth}
|
||||
\begin{panel}
|
||||
\footnotesize\textbf{2. Разметка}
|
||||
\begin{itemize}
|
||||
\item Метка — имя говорящего персонажа.
|
||||
\item Ручная проверка реплики и метки.
|
||||
\item Метаданные (пол, возраст, вид) хранятся отдельно и не подаются модели.
|
||||
\end{itemize}
|
||||
\end{panel}
|
||||
\end{column}
|
||||
\begin{column}{0.33\textwidth}
|
||||
\begin{panel}
|
||||
\footnotesize\textbf{3. Очистка}
|
||||
\begin{itemize}
|
||||
\item \textbf{Unicode-очистка}: разные варианты кавычек, тире,
|
||||
многоточий и пробелов приводятся к единому виду.
|
||||
\item Плейсхолдеры вида \texttt{<PLAYER>} заменены нейтральными словами.
|
||||
\item Пунктуация и регистр сохранены: это стилометрический сигнал.
|
||||
\end{itemize}
|
||||
\end{panel}
|
||||
\end{column}
|
||||
\end{columns}
|
||||
\begin{panel}
|
||||
\footnotesize \textbf{Ограничения данных:} ручная фильтрация; нет идентификаторов сцен.
|
||||
\end{panel}
|
||||
\slidefooter
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}{Игровые персонажи}
|
||||
\begin{center}
|
||||
\begin{imgbox}
|
||||
\includegraphics[height=0.65\textheight]{characters.png}
|
||||
\end{imgbox}
|
||||
\end{center}
|
||||
\slidefooter
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}{Характеристики корпуса: классы}
|
||||
\begin{columns}[T]
|
||||
\begin{column}{0.42\textwidth}
|
||||
\begin{center}
|
||||
\begin{imgbox}
|
||||
\includegraphics[width=\linewidth]{replics-count.png}
|
||||
\end{imgbox}
|
||||
\end{center}
|
||||
\end{column}
|
||||
\begin{column}{0.57\textwidth}
|
||||
\begin{panel}
|
||||
|
||||
\begin{tabular}{@{}p{3.2cm}p{3.6cm}@{}}
|
||||
\textbf{Реплик} & 2005\\[0.8mm]
|
||||
\textbf{Персонажей} & 34\\[0.8mm]
|
||||
\textbf{Максимум класса} & Abigail, 139\\[0.8mm]
|
||||
\textbf{Минимум класса} & Mister Qi, 15\\[0.8mm]
|
||||
\textbf{Точные дубли} & 0\\[0.8mm]
|
||||
\textbf{Почти-дубли} & 13 пар, но только внутри класса\\
|
||||
\end{tabular}
|
||||
\end{panel}
|
||||
\begin{panel}
|
||||
Дисбаланс почти десятикратный: от 15 до 139 реплик. Поэтому
|
||||
доля правильных ответов обманчива; основная метрика — макро усреднённая
|
||||
F-мера по классам.
|
||||
\end{panel}
|
||||
\end{column}
|
||||
\end{columns}
|
||||
\slidefooter
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}{Характеристики корпуса: длина реплик}
|
||||
\begin{columns}[T]
|
||||
\begin{column}{0.42\textwidth}
|
||||
\begin{imgbox}
|
||||
\includegraphics[width=\linewidth]{median.png}
|
||||
\end{imgbox}
|
||||
\end{column}
|
||||
\begin{column}{0.57\textwidth}
|
||||
\begin{panel}
|
||||
\begin{tabular}{@{}p{4.2cm}p{2.9cm}@{}}
|
||||
\textbf{Средняя длина} & 19,1 слова\\[0.8mm]
|
||||
\textbf{Медиана} & 17 слов\\[0.8mm]
|
||||
\textbf{Диапазон} & 3–78 слов\\
|
||||
\end{tabular}
|
||||
\end{panel}
|
||||
\begin{panel}
|
||||
\begin{itemize}
|
||||
\item Короткие реплики дают мало информации для стилометрических характеристик.
|
||||
\item Длина сама по себе — сильный, но опасный признак: она может
|
||||
подменять «стиль» просто узнаванием персонажа.
|
||||
\item Поэтому её вклад проверялся отдельно: с исключением
|
||||
признаков длины и по группам длины.
|
||||
\end{itemize}
|
||||
\end{panel}
|
||||
\end{column}
|
||||
\end{columns}
|
||||
\note{Говорит А. А. Аладьина. Средняя реплика — всего 19 слов, медиана 17,
|
||||
диапазон 3–78. На таких коротких текстах признаки словарного богатства и
|
||||
читаемости нестабильны — это одна из причин низких абсолютных значений
|
||||
качества, о которых расскажет третий докладчик.}
|
||||
\slidefooter
|
||||
\end{frame}
|
||||
|
||||
% ==================== БЛОК 2 ====================
|
||||
\begin{frame}{Часть 2\ \ ·\ \ Методология}
|
||||
\begin{center}
|
||||
\begin{tcolorbox}[enhanced, width=0.86\paperwidth, colback=cream, opacityback=0.9,
|
||||
colframe=cream, boxrule=0pt, arc=2mm, halign=center, boxsep=3mm]
|
||||
{\large Часть 2}\\[1.5mm]
|
||||
{\Large\bfseries Методология: как измеряем стиль}\\[2mm]
|
||||
Признаки · LSA · классификаторы · схема проверки · метрики\\[2.5mm]
|
||||
{\bfseries Докладчик: В.~Н.~Копнин}
|
||||
\end{tcolorbox}
|
||||
\end{center}
|
||||
\note{Часть\,2. Говорит В. Н. Копнин. План части: общая схема конвейера,
|
||||
какие признаки строим, как работает LSA, какие модели сравнивали и как устроена
|
||||
проверка, чтобы не обмануть себя.}
|
||||
\slidefooter
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}{Термины, которые встретятся дальше}
|
||||
\begin{columns}[T]
|
||||
\begin{column}{0.49\textwidth}
|
||||
\begin{panel}
|
||||
\footnotesize
|
||||
\textbf{Фолд} — одна из частей разбиения выборки при перекрёстной
|
||||
проверке. При 5 фолдах модель обучается на 4 и проверяется на 1,
|
||||
затем разбиение меняется.\\[0.8mm]
|
||||
\textbf{POS (part of speech)} — часть речи; POS-тег — её
|
||||
обозначение; POS-грамма — последовательность тегов.\\[0.8mm]
|
||||
\textbf{Universal POS} — универсальный набор тегов: NOUN, VERB,
|
||||
ADJ, ADV, PRON, DET, ADP, CONJ, NUM, PRT, INTJ, PUNCT, X.
|
||||
Единый набор для разных языков.\\[0.8mm]
|
||||
\textbf{Клауза} — синтаксическая единица с подлежащим и
|
||||
сказуемым; в репликах оценивается приблизительно.\\[0.8mm]
|
||||
\textbf{Синтаксис-прокси} — приблизительная оценка синтаксиса
|
||||
без полного разбора: число союзов, длина предложения, порядок слов.
|
||||
\end{panel}
|
||||
\end{column}
|
||||
\begin{column}{0.49\textwidth}
|
||||
\begin{panel}
|
||||
\footnotesize
|
||||
\textbf{n-грамм} — последовательность $n$ соседних единиц:
|
||||
символов, слов или POS-тегов.\\[0.8mm]
|
||||
\textbf{LSA} — сжатие таблицы «документ $\times$ признак» в
|
||||
несколько скрытых компонент.\\[0.8mm]
|
||||
\textbf{Макро-F1} — средняя F-мера по классам; каждый персонаж
|
||||
весит одинаково.\\[0.8mm]
|
||||
\textbf{Unicode-очистка} — приведение разных вариантов записи
|
||||
символов к единому виду: кавычки, тире, многоточия, пробелы.\\[0.8mm]
|
||||
\textbf{Класс-аттрактор} — класс, к которому модель ошибочно
|
||||
относит чужие реплики.\\[0.8mm]
|
||||
\textbf{Фолд и группа признаков} — разные вещи: фолд — часть
|
||||
проверки, группа — набор признаков.
|
||||
\end{panel}
|
||||
\end{column}
|
||||
\end{columns}
|
||||
\note{Говорит В. Н. Копнин. Этот слайд — словарь терминов. Важно различать
|
||||
фолд (часть кросс-валидации) и группу признаков (набор характеристик текста).
|
||||
Universal POS — не «ещё одна модель», а универсальный набор частеречных
|
||||
тегов; POS-граммы — последовательности этих тегов. Клауза и
|
||||
синтаксис-прокси — приблизительные, но интерпретируемые оценки синтаксиса.}
|
||||
\slidefooter
|
||||
\end{frame}
|
||||
|
||||
|
||||
\begin{frame}{Общая схема}
|
||||
\begin{panel}
|
||||
Реплика (текст персонажа) $\to$ очистка (Unicode, плейсхолдеры)
|
||||
$\to$ токены и частеречная разметка (NLTK, Universal POS)
|
||||
$\to$ признаки (ручные, n-граммы, LSA)
|
||||
$\to$ модель (вероятности по 34 классам)
|
||||
$\to$ проверка (5 фолдов, повторы, метрики).
|
||||
\end{panel}
|
||||
\begin{columns}[T]
|
||||
\begin{column}{0.49\textwidth}
|
||||
\begin{panel}
|
||||
\textbf{Что важно про схему}
|
||||
\begin{itemize}
|
||||
\item Все обучаемые преобразования настраиваются только внутри
|
||||
обучающих фолдах.
|
||||
\item Тестовые фолды не участвуют ни в настройке признаков, ни в
|
||||
выборе параметров модели.
|
||||
\end{itemize}
|
||||
\end{panel}
|
||||
\end{column}
|
||||
\begin{column}{0.49\textwidth}
|
||||
\begin{panel}
|
||||
\textbf{Что такое «вектор признаков»}
|
||||
\begin{itemize}
|
||||
\item Это не «понимание» текста, а числовое описание его
|
||||
статистических свойств: длины, частоты, пунктуация, темы.
|
||||
\end{itemize}
|
||||
\end{panel}
|
||||
\end{column}
|
||||
\end{columns}
|
||||
\note{Говорит В. Н. Копнин. Общий конвейер: текст превращается в цепочку
|
||||
преобразований и в конце — в вектор чисел, по которому модель выбирает
|
||||
класс. Ключевое правило: всё, что «учится» на данных, учится только на
|
||||
обучающей части каждой проверки.}
|
||||
\slidefooter
|
||||
\end{frame}
|
||||
|
||||
|
||||
\begin{frame}{454 ручных признака: 11 групп}
|
||||
\begin{panel}
|
||||
\begin{tabular}{@{}p{4cm}p{9.7cm}@{}}
|
||||
\textbf{Группа} & \textbf{Примеры признаков} \\ \hline
|
||||
Длина & число слов, символов, предложений; длина предложения \\
|
||||
Лексика & длины слов, апострофы, заглавные буквы, цифры \\
|
||||
Словарное богатство & TTR, Guiraud, Yule's K, hapax; на коротких текстах нестабильно \\
|
||||
Символы & частоты букв, регистр, пунктуация \\
|
||||
Пунктуация & запятые, многоточия, вопросы, восклицания, повторы \\
|
||||
Функциональные слова & артикли, предлоги, союзы, местоимения \\
|
||||
Местоимения & 1-е, 2-е, 3-е лицо; отношение «я/ты» \\
|
||||
Части речи & Universal POS, Penn Treebank, время, модальность \\
|
||||
Синтаксис & клаузы, подчинительные союзы, пассивные конструкции \\
|
||||
Читаемость & Flesch, FK, SMOG; на коротких репликах приблизительна \\
|
||||
Разговорные маркеры & междометия, смех, хеджи, вежливость, сокращения \\
|
||||
\end{tabular}
|
||||
\end{panel}
|
||||
\note{Говорит В. Н. Копнин. Это расшифровка одиннадцати групп. Не нужно
|
||||
запоминать все 454 признака: важнее логика — длина, лексика, пунктуация,
|
||||
служебные слова, части речи, синтаксис, читаемость и разговорные маркеры.
|
||||
Словарное богатство и читаемость на коротких репликах нестабильны.}
|
||||
\slidefooter
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}{Автоматические признаки: n-граммы и LSA}
|
||||
\begin{columns}[T]
|
||||
\begin{column}{0.49\textwidth}
|
||||
\begin{panel}
|
||||
\textbf{n-граммы}
|
||||
\begin{itemize}
|
||||
\item \textbf{n-грамм} — последовательность $n$ соседних
|
||||
единиц: символов, слов или POS-тегов.
|
||||
\item Символьные n-граммы 2–5: морфология, пунктуация,
|
||||
орфография.
|
||||
\item n-граммы POS-тегов 1–3: последовательности частей речи,
|
||||
синтаксические шаблоны.
|
||||
\item TF-IDF: вес признака с учётом частоты в реплике и
|
||||
редкости в корпусе.
|
||||
\end{itemize}
|
||||
\end{panel}
|
||||
\end{column}
|
||||
\begin{column}{0.49\textwidth}
|
||||
\begin{panel}
|
||||
\textbf{LSA}
|
||||
\begin{itemize}
|
||||
\item Сжатие таблицы «реплика $\times$ признак» в $k$ скрытых
|
||||
компонент.
|
||||
\item Варианты: \texttt{word}, \texttt{content}, \texttt{fw},
|
||||
\texttt{char}, \texttt{pos}.
|
||||
\item $k=50$–400; для \texttt{content}-LSA качество росло до 400.
|
||||
\item LSA — сжатое представление, а не «понимание» текста.
|
||||
\end{itemize}
|
||||
\end{panel}
|
||||
\end{column}
|
||||
\end{columns}
|
||||
\begin{panel}
|
||||
Символьные n-граммы чувствительны к морфологии и пунктуации;
|
||||
n-граммы POS-тегов — к порядку частей речи; LSA сжимает разреженные
|
||||
признаки в несколько чисел. Это три разных взгляда на стиль.
|
||||
\end{panel}
|
||||
\note{Говорит В. Н. Копнин. n-грамма — это просто «окно» из n соседних
|
||||
единиц. Символьные n-граммы ловят морфологию и пунктуацию, n-граммы POS-тегов —
|
||||
синтаксические шаблоны. LSA не «понимает» текст: он сжимает таблицу
|
||||
признаков в несколько скрытых компонент.}
|
||||
\slidefooter
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}{LSA простыми словами}
|
||||
\begin{columns}[T]
|
||||
\begin{column}{0.49\textwidth}
|
||||
\begin{panel}
|
||||
\textbf{Идея}
|
||||
\begin{itemize}
|
||||
\item Строим таблицу «реплика $\times$ слово», взвешиваем
|
||||
частоты через TF-IDF.
|
||||
\item Сжимаем таблицу до $k$ скрытых осей: каждая реплика
|
||||
превращается в короткий вектор.
|
||||
\item Оси не обязаны быть «темами»: их порядок и знак произвольны.
|
||||
\end{itemize}
|
||||
\end{panel}
|
||||
\end{column}
|
||||
\begin{column}{0.49\textwidth}
|
||||
\begin{panel}
|
||||
\textbf{Варианты LSA}
|
||||
\begin{itemize}
|
||||
\item \texttt{word} — лексика и тема.
|
||||
\item \texttt{content} — содержание без служебных слов.
|
||||
\item \texttt{fw} — функциональные слова и синтаксис.
|
||||
\item \texttt{char} — морфология и пунктуация.
|
||||
\item \texttt{pos} — частеречные последовательности.
|
||||
\end{itemize}
|
||||
\end{panel}
|
||||
\end{column}
|
||||
\end{columns}
|
||||
\begin{panel}
|
||||
$k$ подбирался в диапазоне 50–400; для \texttt{content}-LSA качество
|
||||
устойчиво росло до 400.
|
||||
Ключевой вопрос: ловит ли LSA по содержательным словам \emph{тему}, а не
|
||||
только стиль?
|
||||
\end{panel}
|
||||
\note{Говорит В. Н. Копнин. LSA объясняем без формул: таблица частот —
|
||||
взвешивание — сжатие до k осей — короткий вектор для каждой реплики.
|
||||
Важно ограничение метода: оси формально не интерпретируются, а LSA по
|
||||
содержательным словам может кодировать тему. Для этого мы и делали
|
||||
несколько вариантов LSA, чтобы сравнить тематический и стилистический
|
||||
вклад.}
|
||||
\slidefooter
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}{Классификаторы}
|
||||
\begin{columns}[T]
|
||||
\begin{column}{0.49\textwidth}
|
||||
\begin{panel}
|
||||
\textbf{Линейные модели}
|
||||
\begin{itemize}
|
||||
\item Логистическая регрессия: «признак $\times$ вес», затем
|
||||
вероятности; редкие классы получают повышенные веса.
|
||||
\item Линейный SVM: граница с максимальным зазором; часто силён
|
||||
на тексте, но вероятностей не выдаёт.
|
||||
\end{itemize}
|
||||
\end{panel}
|
||||
\end{column}
|
||||
\begin{column}{0.49\textwidth}
|
||||
\begin{panel}
|
||||
\textbf{Деревья и ансамбли}
|
||||
\begin{itemize}
|
||||
\item Случайный лес и ExtraTrees: сотни деревьев; у ExtraTrees
|
||||
пороги выбираются случайно — меньше разброс.
|
||||
\item Мягкое голосование: усредняем вероятности моделей.
|
||||
\item Объединение через мета-классификатор (стекинг) у нас переобучилось и оказалось хуже.
|
||||
\end{itemize}
|
||||
\end{panel}
|
||||
\end{column}
|
||||
\end{columns}
|
||||
\begin{panel}
|
||||
Сравнивали несколько семейств, чтобы вывод не зависел от одного
|
||||
алгоритма: близкие результаты у разных моделей делают вывод надёжнее.
|
||||
\end{panel}
|
||||
\note{Говорит В. Н. Копнин. Логистическая регрессия — интерпретируемая
|
||||
линейная база; SVM — сила на текстовых признаках, но без вероятностей;
|
||||
деревья ловят нелинейные комбинации («многоточие + короткая реплика»), но
|
||||
легко переобучаются на 2005 объектах. Лучший результат дало мягкое
|
||||
голосование трёх моделей; объединение через мета-классификатор было хуже — переобучилось.}
|
||||
\slidefooter
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}{Как проверяем}
|
||||
\begin{columns}[T]
|
||||
\begin{column}{0.49\textwidth}
|
||||
\begin{panel}
|
||||
\textbf{Схема оценки}
|
||||
\begin{itemize}
|
||||
\item Стратифицированная перекрёстная проверка: 5 фолдов
|
||||
(частей разбиения); на 4 фолдах обучаемся, на 1 проверяем.
|
||||
Доли классов внутри фолдов сохраняются.
|
||||
\item Повторяем 3–5 раз с разными разбиениями — оцениваем разброс.
|
||||
\item Вложенная схема: внешние фолды — оценка, внутренние —
|
||||
настройка параметров модели.
|
||||
\item Отдельная отложенная выборка 80/20 для финальных кандидатов.
|
||||
\end{itemize}
|
||||
\end{panel}
|
||||
\end{column}
|
||||
\begin{column}{0.49\textwidth}
|
||||
\begin{panel}
|
||||
\textbf{Контроль ошибок исследователя}
|
||||
\begin{itemize}
|
||||
\item TF-IDF, сжатие и масштабирование обучаются только на
|
||||
обучающих фолдах — борьба с утечкой данных.
|
||||
\item Контроль перестановкой меток дал 0,027–0,028 — воспроизведён
|
||||
случайный уровень.
|
||||
\item Почти-дубликаты находятся только внутри одного класса и
|
||||
не пересекают обучение и тест.
|
||||
\end{itemize}
|
||||
\end{panel}
|
||||
\end{column}
|
||||
\end{columns}
|
||||
\vspace{-1mm}
|
||||
\note{Говорит В. Н. Копнин. Схема: 5-фолдовая стратифицированная
|
||||
перекрёстная проверка с повторами; вложенная — для честной настройки
|
||||
параметров; отложенная выборка — дополнительная проверка. Отдельно
|
||||
переставляли метки: качество упало до случайного уровня, значит сигнал
|
||||
не в «утечке».}
|
||||
\slidefooter
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}{Метрики: что и зачем}
|
||||
\begin{panel}
|
||||
|
||||
\begin{tabular}{@{}p{3.9cm}p{9.6cm}@{}}
|
||||
\textbf{Метрика} & \textbf{Что показывает}\\[1mm]
|
||||
\textbf{Macro-F1} & Средняя F-мера по 34 классам: каждый персонаж важен одинаково, включая редких. Основная метрика.\\[1mm]
|
||||
\textbf{Доля правильных ответов} & Доля совпавших предсказаний; при дисбалансе обманчива: модель, всегда выбирающая частый класс, выглядит неплохо.\\[1mm]
|
||||
\textbf{Сбалансированная доля} & Средняя полнота по классам: находит ли модель редких персонажей.\\[1mm]
|
||||
\textbf{Тройка лучших (top-3)} & Истинный персонаж оказался в тройке наиболее вероятных ответов.\\[1mm]
|
||||
\textbf{MCC / каппа} & Согласие с поправкой на случайное угадывание.\\[1mm]
|
||||
\end{tabular}
|
||||
\end{panel}
|
||||
\begin{panel}
|
||||
Ориентиры при 34 классах: случайный уровень macro-F1 $\approx$ 0,029,
|
||||
top-3 $\approx$ 0,088.
|
||||
\end{panel}
|
||||
\note{Говорит В. Н. Копнин. Главная метрика — macro-F1: она равно
|
||||
взвешивает все 34 класса, включая редких. Доля правильных ответов
|
||||
обманчива при дисбалансе. Смысл всех остальных метрик — словесный, без
|
||||
формул. Ориентиры случайного уровня уже озвучены — они пригодятся в
|
||||
части 3.}
|
||||
\slidefooter
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}{Гипотезы исследования}
|
||||
\begin{columns}[T]
|
||||
\begin{column}{0.57\textwidth}
|
||||
\begin{panel}
|
||||
\textbf{H1.} Функциональные слова, части речи и символьные
|
||||
n-граммы (последовательности соседних символов) различают
|
||||
персонажей выше случайного уровня.\\[0.7mm]
|
||||
\textbf{H2.} На коротких репликах части речи и символьные
|
||||
n-граммы устойчивее словарного богатства и читаемости.\\[0.7mm]
|
||||
\textbf{H3.} LSA по всем словам отражает тему, а LSA по частям
|
||||
речи и функциональным словам — стиль.\\[0.7mm]
|
||||
\textbf{H4.} Качество зависит от длины реплики.\\[0.7mm]
|
||||
\textbf{H5.} Ошибки группируются вокруг персонажей со сходным
|
||||
возрастом, полом, ролью или видом.
|
||||
\end{panel}
|
||||
\end{column}
|
||||
\begin{column}{0.41\textwidth}
|
||||
\begin{panel}
|
||||
\textbf{Как проверяем}
|
||||
\begin{itemize}
|
||||
\item H1 — сравнение моделей и поочерёдное исключение групп.
|
||||
\item H2 — исключение групп и анализ по длине.
|
||||
\item H3 — варианты LSA и контроль содержания.
|
||||
\item H4 — метрики по группам длины.
|
||||
\item H5 — анализ ошибок по метаданным.
|
||||
\end{itemize}
|
||||
\end{panel}
|
||||
\end{column}
|
||||
\end{columns}
|
||||
\note{Говорит А. А. Аладьина. Гипотезы сформулированы до экспериментов.
|
||||
H1 и H2 проверяют, есть ли стилометрический сигнал; H3 — стиль или тему;
|
||||
H4 — влияние длины; H5 — социальные подгруппы. Термин «фолд» появится в
|
||||
методологической части: это часть разбиения при перекрёстной проверке.}
|
||||
\slidefooter
|
||||
\end{frame}
|
||||
|
||||
% ==================== БЛОК 3 ====================
|
||||
\begin{frame}{Часть 3\ \ ·\ \ Результаты}
|
||||
\begin{center}
|
||||
\begin{tcolorbox}[enhanced, width=0.86\paperwidth, colback=cream, opacityback=0.9,
|
||||
colframe=cream, boxrule=0pt, arc=2mm, halign=center, boxsep=3mm]
|
||||
{\large Часть 3}\\[1.5mm]
|
||||
{\Large\bfseries Результаты и интерпретация}\\[2mm]
|
||||
Оценки качества · вклад признаков · матрицы ошибок · ограничения · выводы\\[2.5mm]
|
||||
{\bfseries Докладчик: М.~В.~Савостиков}
|
||||
\end{tcolorbox}
|
||||
\end{center}
|
||||
\note{Часть\,3. Говорит М. В. Савостиков. План части: главные числа, что даёт
|
||||
качество, стиль или содержание, кого модель путает, как корректно читать
|
||||
оценки, ограничения и выводы.}
|
||||
\slidefooter
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}{Главный результат}
|
||||
\begin{columns}[T]
|
||||
\begin{column}{0.47\textwidth}
|
||||
\begin{panel}
|
||||
|
||||
\begin{tabular}{@{}p{5cm}r@{}}
|
||||
\textbf{Мягкое голосование (5 фолдов)} & 0,304\\
|
||||
\textbf{ExtraTrees, char = 10\,000} & 0,276\\
|
||||
\textbf{Вложенный выбор} & 0,275\\
|
||||
\textbf{Отложенная выборка} & 0,333\\
|
||||
\textbf{Перестановка меток} & 0,029\\
|
||||
\end{tabular}
|
||||
\end{panel}
|
||||
\begin{panel}
|
||||
Числа — macro-F1. Случайный уровень для 34 классов — 0,029;
|
||||
контроль перестановкой меток вышел на этот уровень, значит в извлеченных характеристиках есть информация.
|
||||
\end{panel}
|
||||
\end{column}
|
||||
\begin{column}{0.52\textwidth}
|
||||
\begin{imgbox}
|
||||
\includegraphics[width=\linewidth]{model_comparison.png}
|
||||
\end{imgbox}
|
||||
\end{column}
|
||||
\end{columns}
|
||||
\note{Говорит М. В. Савостиков. Главная цифра — macro-F1 0,304 для мягкого
|
||||
голосования при 5-фолдовой проверке. Более консервативная оценка — 0,275:
|
||||
там конфигурация выбиралась честно, внутри обучающей части каждого фолда.
|
||||
На отложенной выборке 0,333, но это одно разбиение. Суть: персонажи
|
||||
действительно различимы.}
|
||||
\slidefooter
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}{Что добавляет качество}
|
||||
\begin{columns}[T]
|
||||
\begin{column}{0.52\textwidth}
|
||||
\begin{imgbox}
|
||||
\includegraphics[width=\linewidth, height=0.55\textheight, keepaspectratio]{feature_progression.png}
|
||||
\end{imgbox}
|
||||
\end{column}
|
||||
\begin{column}{0.47\textwidth}
|
||||
\begin{panel}
|
||||
|
||||
\begin{itemize}
|
||||
\item Только длина: 0,035.
|
||||
\item Ручные стилометрические признаки: 0,105.
|
||||
\item $+$ символьные и частеречные n-граммы: 0,182.
|
||||
\item $+$ content-LSA: 0,249.
|
||||
\item ExtraTrees, char = 10\,000: 0,276.
|
||||
\item Мягкое голосование: 0,304.
|
||||
\end{itemize}
|
||||
Итого: основной прирост — лексика, n-граммы и content-LSA;
|
||||
«чистый синтаксис» (функциональные слова и POS-LSA) сам по себе
|
||||
слаб — важный отрицательный результат.
|
||||
\end{panel}
|
||||
\end{column}
|
||||
\end{columns}
|
||||
\note{Говорит М. В. Савостиков. Прогрессия: вручную посчитанные признаки
|
||||
дают 0,105, добавляются символьные n-граммы — 0,182, LSA по содержанию —
|
||||
0,249, лучшая одиночная модель — 0,276, ансамбль — 0,304. Обратный вывод
|
||||
важен тоже: признаки «чистого синтаксиса» сами по себе почти ничего не
|
||||
дают.}
|
||||
\slidefooter
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}{Стиль или содержание?}
|
||||
\begin{columns}[T]
|
||||
\begin{column}{0.5\textwidth}
|
||||
\begin{imgbox}
|
||||
\includegraphics[width=\linewidth, height=0.55\textheight, keepaspectratio]{topic_control.png}
|
||||
\end{imgbox}
|
||||
\end{column}
|
||||
\begin{column}{0.49\textwidth}
|
||||
\begin{panel}
|
||||
|
||||
\begin{itemize}
|
||||
\item Проверка: содержательные слова (существительные, глаголы,
|
||||
прилагательные, наречия) заменены нейтральным маркером.
|
||||
\item Исходный текст: 0,248; после маскирования: 0,113.
|
||||
\item Падение более чем вдвое, но остаток 0,113 — в 3,9 раза
|
||||
выше случайного уровня.
|
||||
\end{itemize}
|
||||
\textbf{Вывод контроля:} более половины качества даёт лексическое
|
||||
\emph{содержание}; стилевой сигнал сохраняется, но слабее
|
||||
тематического.
|
||||
\end{panel}
|
||||
\end{column}
|
||||
\end{columns}
|
||||
\note{Говорит М. В. Савостиков. Ключевой эксперимент: заменяем все
|
||||
содержательные слова нейтральным маркером. Качество резко падает (0,248
|
||||
становится 0,113). Тем не менее остаток — почти в четыре раза выше
|
||||
случайного: какой-то стилевой компонент есть. Из этого следует главная
|
||||
оговорка работы: мы не можем утверждать, что модель выучила «чистый
|
||||
стиль» персонажей — основной вклад даёт тематический профиль.}
|
||||
\slidefooter
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}{Матрица ошибок: как читать}
|
||||
\begin{columns}[T]
|
||||
\begin{column}{0.55\textwidth}
|
||||
\begin{imgbox}
|
||||
\includegraphics[width=\linewidth, height=0.66\textheight, keepaspectratio]{confusion_top12.png}
|
||||
\end{imgbox}
|
||||
\end{column}
|
||||
\begin{column}{0.44\textwidth}
|
||||
\begin{panel}
|
||||
\begin{itemize}
|
||||
\item \textbf{Строки} — истинный персонаж, \textbf{столбцы} —
|
||||
предсказание; \textbf{диагональ} — верные ответы,
|
||||
вне диагонали — ошибки.
|
||||
\item Полная матрица 34$\times$34 нечитаема: показываем top-12.
|
||||
\end{itemize}
|
||||
\end{panel}
|
||||
\begin{panel}
|
||||
\textbf{Что видно:} крупная доля вне диагонали у многих строк —
|
||||
редкие классы «утекают» в популярные.
|
||||
\end{panel}
|
||||
\end{column}
|
||||
\end{columns}
|
||||
\note{Говорит М. В. Савостиков. Обязательно проговорить, как читать матрицу:
|
||||
строка — кто говорил на самом деле, столбец — кого предсказала модель,
|
||||
диагональ — верные ответы, всё вне диагонали — ошибки. Полные 34 строки не
|
||||
читаются на проекторе, поэтому top-12 + пары + полная матрица в приложении.}
|
||||
\slidefooter
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}{Кого модель путает чаще всего}
|
||||
\begin{columns}[T]
|
||||
\begin{column}{0.52\textwidth}
|
||||
\begin{imgbox}
|
||||
\includegraphics[width=\linewidth, height=0.45\textheight, keepaspectratio]{confusion_pairs.png}
|
||||
\end{imgbox}
|
||||
\end{column}
|
||||
\begin{column}{0.47\textwidth}
|
||||
\begin{panel}
|
||||
\textbf{Частые пары ошибок (число перепутанных реплик)}
|
||||
\begin{itemize}
|
||||
\item Sebastian $\to$ Abigail: 65
|
||||
\item Sebastian $\to$ Alex: 61
|
||||
\item Abigail $\to$ Sebastian: 59
|
||||
\item Abigail $\to$ Alex: 56
|
||||
\end{itemize}
|
||||
Основной клубок — молодые персонажи: Abigail, Alex, Sebastian,
|
||||
Haley, Sam. Один автор и общие сюжетные контексты делают сходство
|
||||
голосов ожидаемым. Это коллапс редких классов
|
||||
в классы-аттракторы.
|
||||
\end{panel}
|
||||
\vspace{-1mm}
|
||||
\end{column}
|
||||
\end{columns}
|
||||
\note{Говорит М. В. Савостиков. Самые частые пары: Sebastian и Abigail
|
||||
путаются в обе стороны (65 и 59), Sebastian и Alex (61), Abigail и Alex
|
||||
(56). Ошибки сконцентрированы вокруг группы молодых персонажей. Важно: это
|
||||
стягивание предсказаний к популярным классам, а не доказательство, что эти
|
||||
персонажи «говорят одинаково» — хотя перекрёстные путаницы Sebastian —
|
||||
Abigail — Alex согласуются с их сходными масками.}
|
||||
\slidefooter
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}{Классы-аттракторы}
|
||||
\begin{columns}[T]
|
||||
\begin{column}{0.47\textwidth}
|
||||
\begin{panel}
|
||||
\textbf{Классы-аттракторы}
|
||||
\begin{itemize}
|
||||
\item Alex и Abigail собирают заметную долю \emph{чужих}
|
||||
предсказаний: на разных повторах Abigail получает
|
||||
порядка 10–18\,\% всех ответов при истинной доле
|
||||
класса $\approx$ 6,9\,\%.
|
||||
\item Коррекция априорных вероятностей классов
|
||||
macro-F1 не улучшила и проблему не сняла.
|
||||
\end{itemize}
|
||||
\end{panel}
|
||||
\end{column}
|
||||
\begin{column}{0.52\textwidth}
|
||||
\begin{imgbox}
|
||||
\includegraphics[width=\linewidth, height=0.55\textheight, keepaspectratio]{hub_shares.png}
|
||||
\end{imgbox}
|
||||
\end{column}
|
||||
\end{columns}
|
||||
\note{Говорит М. В. Савостиков. Проблема редких классов: модель
|
||||
предсказывает популярные классы слишком часто — Abigail получает десятки
|
||||
процентов всех ответов при истинной доле около 7\,\%. Простая коррекция
|
||||
приоров не помогла.}
|
||||
\slidefooter
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}{Персонажи: кто различается}
|
||||
\begin{columns}[T]
|
||||
\begin{column}{0.47\textwidth}
|
||||
\begin{panel}
|
||||
\textbf{Лучшие и худшие по F-мере}
|
||||
\begin{itemize}
|
||||
\item Лучшие: Evelyn, Willy, Pam, Pierre, Leo.
|
||||
\item Худшие: Kent, Wizard, Penny, Mister Qi, Leah, Abigail.
|
||||
\item У редких классов — широкие интервалы оценок.
|
||||
\item Мягкое голосование улучшает часть редких классов
|
||||
(Wizard, Dwarf, Vincent), но Kent остаётся F1 = 0.
|
||||
\end{itemize}
|
||||
\end{panel}
|
||||
\end{column}
|
||||
\begin{column}{0.52\textwidth}
|
||||
\begin{imgbox}
|
||||
\includegraphics[width=\linewidth, height=0.6\textheight, keepaspectratio]{per_class_f1.png}
|
||||
\end{imgbox}
|
||||
\end{column}
|
||||
\end{columns}
|
||||
\note{Говорит М. В. Савостиков. Диаграмма: F-мера по каждому персонажу.
|
||||
Лучшие — Evelyn, Willy, Pam, Pierre, Leo; худшие — Kent, Wizard, Penny,
|
||||
Mister Qi, Leah, Abigail. Редкие классы имеют широкие доверительные
|
||||
интервалы: для 15 реплик разброс оценок велик. Ансамбль прибавляет
|
||||
отдельным редким классам, но Kent не находит никто.}
|
||||
\slidefooter
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}{Что означают полученные оценки}
|
||||
\begin{panel}
|
||||
|
||||
\begin{itemize}
|
||||
\item 0,30 macro-F1 при 34 классах — примерно \textbf{в 10 раз выше
|
||||
случайного уровня} 0,029: устойчивый сигнал различимости.
|
||||
\item Значение умеренное и это ожидаемо: реплики по $\sim$19 слов,
|
||||
дисбаланс классов и общие темы не позволяют «узнать» персонажа
|
||||
по одной фразе.
|
||||
\item Умеренность объясняется \emph{условиями задачи}, а не тем, что
|
||||
автор «не старался»: превышение случайного уровня как раз
|
||||
говорит о намеренно созданных речевых масках.
|
||||
\end{itemize}
|
||||
\end{panel}
|
||||
\begin{panel}
|
||||
|
||||
\begin{itemize}
|
||||
\item Мы решаем задачу «\emph{какой персонаж} говорит», а не
|
||||
«\emph{он или не он} писал»: автор один.
|
||||
\item И о «чистых» идиолектах говорить нельзя: основной вклад дают
|
||||
лексико-тематический профиль и символьные n-граммы, а после
|
||||
маскирования содержательных слов качество падает.
|
||||
\end{itemize}
|
||||
\end{panel}
|
||||
\note{Говорит М. В. Савостиков — ключевой слайд по интерпретации. Проговорить
|
||||
три пункта. (1) 0,304 при случайном 0,029 — это примерно десятикратное
|
||||
превышение, то есть сигнал реальный. (2) Абсолютная величина умеренная из-за
|
||||
коротких реплик, дисбаланса и общих тем — это свойства задачи, а не
|
||||
«автор не пытался»; напротив, различимость масок подтверждает, что автор
|
||||
сознательно делал разные голоса. (3) Мы определяем персонажа, а не
|
||||
авторскую принадлежность: автор один. И мы не утверждаем, что выучили
|
||||
«чистый стиль»: при маскировании содержания качество сильно падает.}
|
||||
\slidefooter
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}{Ограничения}
|
||||
\begin{columns}[T]
|
||||
\begin{column}{0.49\textwidth}
|
||||
\begin{panel}
|
||||
\textbf{Данные}
|
||||
\begin{itemize}
|
||||
\item Один корпус, одна игра, один автор; внешняя валидация
|
||||
не проводилась.
|
||||
\item Короткие реплики и сильный дисбаланс классов.
|
||||
\item Ручная фильтрация корпуса невоспроизводима.
|
||||
\item Нет идентификаторов сцен и диалогов.
|
||||
\end{itemize}
|
||||
\end{panel}
|
||||
\end{column}
|
||||
\begin{column}{0.49\textwidth}
|
||||
\begin{panel}
|
||||
\textbf{Методология}
|
||||
\begin{itemize}
|
||||
\item Выбор признаков и ансамбля опирался на прошлые серии;
|
||||
вложенная проверка снижает этот риск, но не устраняет.
|
||||
\item Основной вклад — содержательные слова и тема.
|
||||
\item Простая коррекция приоров не помогла редким классам.
|
||||
\item Объединение через мета-классификатор переобучается на малой выборке.
|
||||
\end{itemize}
|
||||
\end{panel}
|
||||
\end{column}
|
||||
\end{columns}
|
||||
\note{Говорит М. В. Савостиков. Ограничения делим на две группы: свойства
|
||||
данных (один корпус и один автор, дисбаланс, короткие реплики,
|
||||
невоспроизводимая ручная фильтрация, нет сцен) и свойства метода
|
||||
(частичный выбор конфигураций по прошлым сериям, тематический сигнал в
|
||||
признаках, неудачи простых калибровок). Честное перечисление — часть
|
||||
верификации, а не признание провала.}
|
||||
\slidefooter
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}{Выводы}
|
||||
\begin{panel}
|
||||
\begin{itemize}
|
||||
\item \textbf{Персонажи статистически различимы:} лучшая модель — 0,304
|
||||
macro-F1, консервативная оценка — 0,275; случайный уровень — 0,029.
|
||||
\item \textbf{Основной сигнал — лексико-тематический:} символьные
|
||||
n-граммы и content-LSA дают основной прирост; функциональные слова
|
||||
и частеречная LSA сами по себе слабы.
|
||||
\item \textbf{После замены содержательных слов} качество падает с 0,248
|
||||
до 0,113: содержание доминирует, но стилевой сигнал сохраняется.
|
||||
\item \textbf{Главная проблема — редкие классы и классы-аттракторы;}
|
||||
мягкое голосование помогает, но не решает её полностью.
|
||||
\item \textbf{Корректная формулировка результата:} различимы речевые
|
||||
маски по лексико-тематическому профилю; утверждение о «чистом
|
||||
стиле» требует внешней валидации и тематического контроля.
|
||||
\end{itemize}
|
||||
\end{panel}
|
||||
\note{Говорит М. В. Савостиков. Пять итоговых тезисов. Финальную формулировку
|
||||
оставить точной: мы показали статистическую различимость речевых масок по
|
||||
лексико-тематическому профилю; для более сильных утверждений — разные идиолекты,
|
||||
применимость как инструмент — нужны внешний корпус, тематический контроль
|
||||
и разметка сцен.}
|
||||
\slidefooter
|
||||
\end{frame}
|
||||
|
||||
% ==================== ПРИЛОЖЕНИЕ ====================
|
||||
\begin{frame}{Приложение: полная матрица ошибок 34$\times$34}
|
||||
\begin{imgbox}
|
||||
\includegraphics[width=\linewidth, height=0.8\textheight, keepaspectratio]{confusion_full34.png}
|
||||
\end{imgbox}
|
||||
\note{Приложение для вопросов и раздаточного материала. Если спросят про
|
||||
конкретную пару персонажей — искать её в полной матрице: строки сортированы
|
||||
по размеру класса, цвет кодирует долю строки. Кто говорит: отвечает
|
||||
М. В. Савостиков (можно подключить В. Н. Копнина по вопросам валидации).}
|
||||
\slidefooter
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}{Спасибо за внимание}
|
||||
\begin{imgbox}
|
||||
\includegraphics[width=\linewidth, height=0.8\textheight, keepaspectratio]{meme.png}
|
||||
\end{imgbox}
|
||||
\begin{tikzpicture}[remember picture, overlay]
|
||||
\node[anchor=south east, xshift=-2.5mm, yshift=1.5mm]
|
||||
at (current page.south east)
|
||||
{\includegraphics[height=0.65cm]{chicken.png}};
|
||||
\end{tikzpicture}
|
||||
\note{Финальный слайд; мем как пасхалка. Говорят по очереди все:
|
||||
А. А. Аладьина благодарит и передаёт слово для вопросов; по вопросам
|
||||
корпуса отвечает Аладьина, по методологии — Копнин, по результатам —
|
||||
Савостиков.}
|
||||
\slidefooter
|
||||
\end{frame}
|
||||
|
||||
\end{document}
|
||||
@@ -1,263 +0,0 @@
|
||||
"""Графики для презентации: матрицы ошибок, сравнение моделей, ошибки по классам."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
import matplotlib
|
||||
matplotlib.use("Agg")
|
||||
import matplotlib.pyplot as plt
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
from matplotlib.colors import LinearSegmentedColormap
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
OUT = ROOT / "presentation" / "generated"
|
||||
OUT.mkdir(parents=True, exist_ok=True)
|
||||
OOF = ROOT / "results" / "final" / "finalists" / "voting_et_char_lean" / "oof_predictions.csv"
|
||||
METRICS = ROOT / "FINAL_RESULTS.csv"
|
||||
|
||||
plt.rcParams.update({
|
||||
"font.family": "DejaVu Sans",
|
||||
"font.size": 13,
|
||||
"axes.titlesize": 18,
|
||||
"axes.labelsize": 13,
|
||||
"savefig.facecolor": "#F8F1DC",
|
||||
"figure.facecolor": "#F8F1DC",
|
||||
})
|
||||
|
||||
CREAM = "#F8F1DC"
|
||||
DARK = "#203A2B"
|
||||
GREEN = "#4D7C4A"
|
||||
BROWN = "#8B5A2B"
|
||||
RED = "#B23A2E"
|
||||
GOLD = "#D8A23A"
|
||||
|
||||
CMAP = LinearSegmentedColormap.from_list("stardew_blue", ["#FFFDF4", "#BFD9C0", "#2E6B57", "#173A34"])
|
||||
|
||||
|
||||
def load_oof() -> pd.DataFrame:
|
||||
frame = pd.read_csv(OOF)
|
||||
frame["y_true"] = frame["y_true"].astype(str)
|
||||
frame["y_pred"] = frame["y_pred"].astype(str)
|
||||
return frame
|
||||
|
||||
|
||||
def top_confusions(frame: pd.DataFrame, n: int = 18) -> pd.Series:
|
||||
errors = frame[frame["y_true"] != frame["y_pred"]]
|
||||
pairs = errors.groupby(["y_true", "y_pred"]).size().sort_values(ascending=False).head(n)
|
||||
pairs.index = [f"{a} → {b}" for a, b in pairs.index]
|
||||
return pairs
|
||||
|
||||
|
||||
def confusion_top12(frame: pd.DataFrame) -> None:
|
||||
labels = frame["y_true"].value_counts().head(12).index.tolist()
|
||||
matrix = pd.crosstab(frame["y_true"], frame["y_pred"]).reindex(index=labels, fill_value=0)
|
||||
shown = matrix[[c for c in labels if c in matrix.columns]].copy()
|
||||
shown["прочие"] = matrix.drop(columns=[c for c in labels if c in matrix.columns]).sum(axis=1)
|
||||
shown = shown.div(shown.sum(axis=1), axis=0) * 100
|
||||
|
||||
fig, ax = plt.subplots(figsize=(12.5, 7.4))
|
||||
image = ax.imshow(shown.to_numpy(), cmap=CMAP, vmin=0, vmax=60, aspect="auto")
|
||||
ax.set_xticks(np.arange(shown.shape[1]))
|
||||
ax.set_yticks(np.arange(shown.shape[0]))
|
||||
ax.set_xticklabels(shown.columns, rotation=42, ha="right", fontsize=10)
|
||||
ax.set_yticklabels(shown.index, fontsize=11)
|
||||
ax.set_xlabel("Предсказанный персонаж", fontsize=14, color=DARK)
|
||||
ax.set_ylabel("Истинный персонаж", fontsize=14, color=DARK)
|
||||
ax.set_title("Матрица ошибок: 12 самых частых персонажей, % строки", fontsize=18, color=DARK, pad=12)
|
||||
for row in range(shown.shape[0]):
|
||||
for col in range(shown.shape[1]):
|
||||
value = shown.iloc[row, col]
|
||||
if value >= 0.5:
|
||||
color = "white" if value > 32 else DARK
|
||||
ax.text(col, row, f"{value:.0f}", ha="center", va="center", fontsize=9, color=color)
|
||||
cbar = fig.colorbar(image, ax=ax, fraction=0.035, pad=0.02)
|
||||
cbar.set_label("% строки", color=DARK)
|
||||
fig.tight_layout()
|
||||
fig.savefig(OUT / "confusion_top12.png", dpi=220, bbox_inches="tight")
|
||||
plt.close(fig)
|
||||
|
||||
|
||||
def confusion_full(frame: pd.DataFrame) -> None:
|
||||
labels = sorted(frame["y_true"].unique())
|
||||
matrix = pd.crosstab(frame["y_true"], frame["y_pred"]).reindex(index=labels, columns=labels, fill_value=0)
|
||||
matrix = matrix.div(matrix.sum(axis=1), axis=0) * 100
|
||||
fig, ax = plt.subplots(figsize=(13.5, 11.5))
|
||||
image = ax.imshow(matrix.to_numpy(), cmap=CMAP, vmin=0, vmax=35, aspect="auto")
|
||||
ax.set_xticks(np.arange(len(labels)))
|
||||
ax.set_yticks(np.arange(len(labels)))
|
||||
ax.set_xticklabels(labels, rotation=90, fontsize=5.5)
|
||||
ax.set_yticklabels(labels, fontsize=5.5)
|
||||
ax.set_xlabel("Предсказанный персонаж", fontsize=12, color=DARK)
|
||||
ax.set_ylabel("Истинный персонаж", fontsize=12, color=DARK)
|
||||
ax.set_title("Полная матрица ошибок, % строки (раздаточный материал)", fontsize=16, color=DARK)
|
||||
fig.colorbar(image, ax=ax, fraction=0.03, pad=0.015).set_label("% строки")
|
||||
fig.tight_layout()
|
||||
fig.savefig(OUT / "confusion_full34.png", dpi=220, bbox_inches="tight")
|
||||
plt.close(fig)
|
||||
|
||||
|
||||
def pairs_chart(frame: pd.DataFrame) -> None:
|
||||
pairs = top_confusions(frame, 18).sort_values()
|
||||
fig, ax = plt.subplots(figsize=(12.6, 7.2))
|
||||
colors = [RED if i >= len(pairs) - 5 else BROWN for i in range(len(pairs))]
|
||||
ax.barh(pairs.index, pairs.values, color=colors, edgecolor="white", linewidth=0.6)
|
||||
for index, value in enumerate(pairs.values):
|
||||
ax.text(value + 0.6, index, str(value), va="center", fontsize=10, color=DARK)
|
||||
ax.set_xlabel("Число ошибок за 3 повтора", fontsize=13, color=DARK)
|
||||
ax.set_title("Самые частые пары «истинный → предсказанный»", fontsize=18, color=DARK)
|
||||
ax.tick_params(axis="y", labelsize=10)
|
||||
ax.grid(axis="x", alpha=0.2, color=DARK)
|
||||
ax.set_xlim(0, pairs.max() * 1.16)
|
||||
fig.tight_layout()
|
||||
fig.savefig(OUT / "confusion_pairs.png", dpi=220, bbox_inches="tight")
|
||||
plt.close(fig)
|
||||
|
||||
|
||||
def per_class_chart(frame: pd.DataFrame) -> None:
|
||||
first = frame[frame["repeat"] == frame["repeat"].min()]
|
||||
labels = sorted(first["y_true"].unique())
|
||||
rows = []
|
||||
for label in labels:
|
||||
mask = first["y_true"] == label
|
||||
tp = int(((first["y_true"] == label) & (first["y_pred"] == label)).sum())
|
||||
fp = int(((first["y_true"] != label) & (first["y_pred"] == label)).sum())
|
||||
fn = int(((first["y_true"] == label) & (first["y_pred"] != label)).sum())
|
||||
precision = tp / (tp + fp) if tp + fp else 0.0
|
||||
recall = tp / (tp + fn) if tp + fn else 0.0
|
||||
f1 = 2 * precision * recall / (precision + recall) if precision + recall else 0.0
|
||||
rows.append((label, f1, int(mask.sum())))
|
||||
table = pd.DataFrame(rows, columns=["class", "f1", "support"]).sort_values("f1")
|
||||
worst = table.head(12).sort_values("f1", ascending=False)
|
||||
best = table.tail(12).sort_values("f1")
|
||||
|
||||
fig, axes = plt.subplots(1, 2, figsize=(13.2, 7.0), gridspec_kw={"width_ratios": [1, 1]})
|
||||
axes[0].barh(worst["class"], worst["f1"], color=RED, edgecolor="white")
|
||||
axes[0].set_title("Худшие 12 персонажей", fontsize=15, color=DARK)
|
||||
axes[0].set_xlim(0, 0.72)
|
||||
axes[0].grid(axis="x", alpha=0.2)
|
||||
for index, (value, support) in enumerate(zip(worst["f1"], worst["support"])):
|
||||
axes[0].text(value + 0.008, index, f"{value:.2f} (n={support})", va="center", fontsize=9, color=DARK)
|
||||
axes[1].barh(best["class"], best["f1"], color=GREEN, edgecolor="white")
|
||||
axes[1].set_title("Лучшие 12 персонажей", fontsize=15, color=DARK)
|
||||
axes[1].set_xlim(0, 0.72)
|
||||
axes[1].grid(axis="x", alpha=0.2)
|
||||
for index, (value, support) in enumerate(zip(best["f1"], best["support"])):
|
||||
axes[1].text(value + 0.008, index, f"{value:.2f} (n={support})", va="center", fontsize=9, color=DARK)
|
||||
fig.suptitle("F-мера по персонажам: мягкое голосование, первый повтор", fontsize=17, color=DARK, y=0.99)
|
||||
fig.tight_layout()
|
||||
fig.savefig(OUT / "per_class_f1.png", dpi=220, bbox_inches="tight")
|
||||
plt.close(fig)
|
||||
|
||||
|
||||
def model_comparison() -> None:
|
||||
table = pd.read_csv(METRICS)
|
||||
wanted = [
|
||||
("voting_et_char_lean", "Мягкое голосование"),
|
||||
("et_char10000", "ExtraTrees, char=10000"),
|
||||
("nested_feature_selection", "Вложенный выбор"),
|
||||
("et_full", "ExtraTrees, полный"),
|
||||
("logreg_lean_content300", "Logreg, lean + content"),
|
||||
("stacking", "Стековое объединение"),
|
||||
]
|
||||
rows = []
|
||||
for key, label in wanted:
|
||||
row = table[table["label"] == key]
|
||||
if not row.empty:
|
||||
rows.append((label, float(row.iloc[0]["macro_f1_mean"]), float(row.iloc[0]["macro_f1_std"])))
|
||||
rows.append(("Случайный уровень", 0.029, 0.0))
|
||||
frame = pd.DataFrame(rows, columns=["label", "mean", "std"]).sort_values("mean")
|
||||
fig, ax = plt.subplots(figsize=(12.4, 6.8))
|
||||
colors = [GOLD if label == "Мягкое голосование" else (RED if label == "Случайный уровень" else GREEN) for label in frame["label"]]
|
||||
ax.barh(frame["label"], frame["mean"], xerr=frame["std"], color=colors, edgecolor="white", capsize=4)
|
||||
for index, (value, std) in enumerate(zip(frame["mean"], frame["std"])):
|
||||
offset = value + max(std, 0.0) + 0.008
|
||||
ax.text(offset, index, f"{value:.3f}", va="center", fontsize=11, color=DARK)
|
||||
ax.set_xlim(0, 0.38)
|
||||
ax.set_xlabel("Макро-усреднённая F-мера", fontsize=13, color=DARK)
|
||||
ax.set_title("Сравнение моделей: среднее и стандартное отклонение", fontsize=18, color=DARK)
|
||||
ax.grid(axis="x", alpha=0.2)
|
||||
fig.tight_layout()
|
||||
fig.savefig(OUT / "model_comparison.png", dpi=220, bbox_inches="tight")
|
||||
plt.close(fig)
|
||||
|
||||
|
||||
def feature_progression() -> None:
|
||||
labels = [
|
||||
"Только длина",
|
||||
"Ручные признаки",
|
||||
"Ручные + char/POS",
|
||||
"+ content-LSA",
|
||||
"ExtraTrees, char=10000",
|
||||
"Мягкое голосование",
|
||||
]
|
||||
values = [0.035, 0.105, 0.182, 0.249, 0.276, 0.304]
|
||||
fig, ax = plt.subplots(figsize=(12.4, 6.6))
|
||||
colors = [BROWN, BROWN, GREEN, GREEN, GOLD, RED]
|
||||
ax.bar(labels, values, color=colors, edgecolor="white")
|
||||
for index, value in enumerate(values):
|
||||
ax.text(index, value + 0.008, f"{value:.3f}", ha="center", fontsize=12, color=DARK)
|
||||
ax.set_ylim(0, 0.35)
|
||||
ax.set_ylabel("Макро-усреднённая F-мера", fontsize=13, color=DARK)
|
||||
ax.set_title("Что добавляет качество", fontsize=18, color=DARK)
|
||||
ax.tick_params(axis="x", rotation=18, labelsize=11)
|
||||
ax.grid(axis="y", alpha=0.2)
|
||||
fig.tight_layout()
|
||||
fig.savefig(OUT / "feature_progression.png", dpi=220, bbox_inches="tight")
|
||||
plt.close(fig)
|
||||
|
||||
|
||||
def topic_control() -> None:
|
||||
labels = ["Исходный\nExtraTrees", "Маскированный\nExtraTrees", "Исходный\nLogreg", "Маскированный\nLogreg"]
|
||||
values = [0.2483, 0.1129, 0.1467, 0.1067]
|
||||
fig, ax = plt.subplots(figsize=(10.8, 6.2))
|
||||
colors = [GREEN, RED, GREEN, RED]
|
||||
ax.bar(labels, values, color=colors, edgecolor="white")
|
||||
for index, value in enumerate(values):
|
||||
ax.text(index, value + 0.007, f"{value:.3f}", ha="center", fontsize=12, color=DARK)
|
||||
ax.set_ylim(0, 0.30)
|
||||
ax.set_ylabel("Макро-усреднённая F-мера", fontsize=13, color=DARK)
|
||||
ax.set_title("Контроль содержания: замена содержательных слов на CONTENT", fontsize=17, color=DARK)
|
||||
ax.tick_params(axis="x", labelsize=11)
|
||||
ax.grid(axis="y", alpha=0.2)
|
||||
fig.tight_layout()
|
||||
fig.savefig(OUT / "topic_control.png", dpi=220, bbox_inches="tight")
|
||||
plt.close(fig)
|
||||
|
||||
|
||||
def hub_shares(frame: pd.DataFrame) -> None:
|
||||
true = frame["y_true"].value_counts(normalize=True)
|
||||
pred = frame["y_pred"].value_counts(normalize=True)
|
||||
labels = pred.head(10).index.tolist()
|
||||
x = np.arange(len(labels))
|
||||
fig, ax = plt.subplots(figsize=(12.4, 6.2))
|
||||
ax.bar(x - 0.2, [true.get(label, 0) * 100 for label in labels], width=0.4, label="Истинная доля", color=GREEN)
|
||||
ax.bar(x + 0.2, [pred.get(label, 0) * 100 for label in labels], width=0.4, label="Доля предсказаний", color=RED)
|
||||
ax.set_xticks(x)
|
||||
ax.set_xticklabels(labels, rotation=25, ha="right", fontsize=11)
|
||||
ax.set_ylabel("% реплик", fontsize=13, color=DARK)
|
||||
ax.set_title("Классы-аттракторы: кого модель выбирает слишком часто", fontsize=17, color=DARK)
|
||||
ax.legend(frameon=False)
|
||||
ax.grid(axis="y", alpha=0.2)
|
||||
fig.tight_layout()
|
||||
fig.savefig(OUT / "hub_shares.png", dpi=220, bbox_inches="tight")
|
||||
plt.close(fig)
|
||||
|
||||
|
||||
def main() -> int:
|
||||
frame = load_oof()
|
||||
confusion_top12(frame)
|
||||
confusion_full(frame)
|
||||
pairs_chart(frame)
|
||||
per_class_chart(frame)
|
||||
model_comparison()
|
||||
feature_progression()
|
||||
topic_control()
|
||||
hub_shares(frame)
|
||||
print("Готово:", OUT)
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -1,3 +0,0 @@
|
||||
python-pptx>=1.0,<2
|
||||
Pillow>=10
|
||||
matplotlib>=3.8
|
||||
@@ -1,251 +0,0 @@
|
||||
"""Переработка первого блока текущей презентации.
|
||||
|
||||
Скрипт не пересобирает весь файл: он читает уже отредактированный PPTX,
|
||||
удаляет старые слайды первого блока и вставляет новую академическую
|
||||
последовательность: задача -> строгая постановка -> корпус -> очистка ->
|
||||
характеристики.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
from pptx import Presentation
|
||||
from pptx.enum.text import PP_ALIGN
|
||||
from pptx.util import Inches
|
||||
|
||||
HERE = Path(__file__).resolve().parent
|
||||
if str(HERE) not in sys.path:
|
||||
sys.path.insert(0, str(HERE))
|
||||
|
||||
from build_deck import ( # noqa: E402
|
||||
DARK,
|
||||
GEN,
|
||||
GREEN_DARK,
|
||||
RED,
|
||||
add_background,
|
||||
add_bullets,
|
||||
add_chicken,
|
||||
add_footer,
|
||||
add_image_fit,
|
||||
add_notes,
|
||||
add_panel,
|
||||
add_table,
|
||||
add_text,
|
||||
add_title_bar,
|
||||
)
|
||||
|
||||
|
||||
PPTX = HERE / "stardew_stylometry.pptx"
|
||||
BLOCK = "Блок 1 · Корпус"
|
||||
NARRATOR = "А. А. Аладьина"
|
||||
|
||||
|
||||
def delete_slide(prs, index: int) -> None:
|
||||
slide_id = prs.slides._sldIdLst[index]
|
||||
rel_id = slide_id.rId
|
||||
prs.part.drop_rel(rel_id)
|
||||
del prs.slides._sldIdLst[index]
|
||||
|
||||
|
||||
def move_slide(prs, old_index: int, new_index: int) -> None:
|
||||
slide_id = prs.slides._sldIdLst[old_index]
|
||||
prs.slides._sldIdLst.remove(slide_id)
|
||||
prs.slides._sldIdLst.insert(new_index, slide_id)
|
||||
|
||||
|
||||
def base_slide(prs, title: str, subtitle: str | None = None):
|
||||
slide = prs.slides.add_slide(prs.slide_layouts[6])
|
||||
add_background(slide)
|
||||
add_title_bar(slide, title, NARRATOR, subtitle)
|
||||
add_footer(slide, BLOCK, 0)
|
||||
add_chicken(slide)
|
||||
return slide
|
||||
|
||||
|
||||
def slide_task(prs):
|
||||
slide = base_slide(prs, "Решаемая задача")
|
||||
add_panel(slide, 0.45, 1.35, 6.15, 5.55)
|
||||
add_bullets(slide, 0.65, 1.55, 5.75, 5.1, [
|
||||
"Дано: 2005 коротких реплик, 34 персонажа, один автор.",
|
||||
"Требуется: по одной реплике определить персонажа.",
|
||||
"Класс — имя персонажа; один объект относится ровно к одному классу.",
|
||||
"Реплики короткие: в среднем 19 слов, медиана 17.",
|
||||
"Классы неравномерны: от 15 до 139 реплик.",
|
||||
], size=16, title="Постановка")
|
||||
add_panel(slide, 6.8, 1.35, 6.05, 5.55)
|
||||
add_bullets(slide, 7.0, 1.55, 5.65, 5.1, [
|
||||
"Проверяется, различимы ли речевые маски персонажей статистически.",
|
||||
"Это не авторская атрибуция: все реплики написаны одним автором.",
|
||||
"Ошибки между персонажами ожидаемы: общий автор, общие темы и контексты.",
|
||||
"Практическая ценность — методика стилометрии для коротких текстов.",
|
||||
"Ограничение: один корпус, одна игра, один автор.",
|
||||
], size=16, title="Что именно проверяется")
|
||||
add_notes(slide, "Рассказчик 1: Аладьина Анна Андреевна. Сформулировать задачу без рекламных формулировок: есть корпус коротких реплик, нужно предсказать персонажа. Сразу отделить это от авторской атрибуции: автор один, задача — различимость речевых масок.")
|
||||
|
||||
|
||||
def slide_formal(prs):
|
||||
slide = base_slide(prs, "Строгая постановка задачи", "Формальная запись и протокол оценки")
|
||||
add_panel(slide, 0.45, 1.35, 7.55, 5.55)
|
||||
add_bullets(slide, 0.65, 1.55, 7.15, 5.1, [
|
||||
"D = {(x_i, y_i)}, i = 1..n; n = 2005.",
|
||||
"x_i — одна реплика (короткий текст).",
|
||||
"y_i ∈ Y, |Y| = 34 — имя персонажа.",
|
||||
"φ(x_i) ∈ R^d — вектор признаков реплики.",
|
||||
"f_θ: R^d → R^34 — классификатор с параметрами θ.",
|
||||
"ŷ_i = argmax_{c ∈ Y} f_θ(φ(x_i))_c — предсказание.",
|
||||
], size=16, title="Дано")
|
||||
add_panel(slide, 8.2, 1.35, 4.65, 5.55)
|
||||
add_bullets(slide, 8.38, 1.55, 4.3, 5.1, [
|
||||
"Обучение: минимизация перекрёстной энтропии + L2-регуляризация.",
|
||||
"Веса классов: balanced, чтобы редкие персонажи не игнорировались.",
|
||||
"Оценка: macro-F1 = (1/|Y|) Σ_c 2P_cR_c / (P_c + R_c).",
|
||||
"Схема: стратифицированная 5-блочная CV, повторы, вложенная настройка.",
|
||||
"Случайный уровень: 0,029; top-3: 0,088.",
|
||||
"Один объект — одна реплика; обучение и оценка разделены.",
|
||||
], size=14, title="Критерий и проверка")
|
||||
add_notes(slide, "Рассказчик 1. Это ключевой слайд строгой постановки. Проговорить обозначения: объект — реплика, метка — персонаж, признаковое описание — φ(x), классификатор — f. Отдельно сказать, что оцениваем не accuracy, а macro-F1, потому что классов 34 и они неравномерны.")
|
||||
|
||||
|
||||
def slide_corpus(prs):
|
||||
slide = base_slide(prs, "Корпус: сбор и разметка")
|
||||
add_panel(slide, 0.45, 1.35, 7.05, 5.55)
|
||||
add_text(slide, 0.68, 1.55, 6.6, 0.3, "1. Сбор", size=17, bold=True, color=GREEN_DARK)
|
||||
add_bullets(slide, 0.68, 1.9, 6.55, 1.35, [
|
||||
"Реплики извлечены из игровых диалогов Stardew Valley.",
|
||||
"Единица анализа — одна реплика; всего 2005 объектов.",
|
||||
"Корпус охватывает 34 персонажа; все тексты на английском языке.",
|
||||
], size=14)
|
||||
add_text(slide, 0.68, 3.2, 6.6, 0.3, "2. Разметка", size=17, bold=True, color=GREEN_DARK)
|
||||
add_bullets(slide, 0.68, 3.55, 6.55, 1.35, [
|
||||
"Метка — имя говорящего персонажа; один объект — один класс.",
|
||||
"Ручная проверка соответствия реплики и персонажа.",
|
||||
"Метаданные (пол, возраст, вид) хранятся отдельно и не подаются модели.",
|
||||
], size=14)
|
||||
add_text(slide, 0.68, 4.85, 6.6, 0.3, "3. Ограничения", size=17, bold=True, color=GREEN_DARK)
|
||||
add_bullets(slide, 0.68, 5.18, 6.55, 1.4, [
|
||||
"Нет идентификаторов сцен и диалогов.",
|
||||
"Классы неравномерны: 15–139 реплик.",
|
||||
"Часть коротких и общих фраз удалена вручную; правила не сохранились.",
|
||||
], size=14)
|
||||
add_panel(slide, 7.7, 1.35, 5.15, 5.55)
|
||||
add_image_fit(slide, GEN / "characters.png", 7.85, 1.5, 4.85, 3.9, panel=False)
|
||||
add_text(slide, 7.9, 5.55, 4.75, 1.1, "34 персонажа: подростки, взрослые, пожилые, дети и нелюди. Все реплики написаны одним разработчиком.", size=13, color=DARK, align=PP_ALIGN.CENTER)
|
||||
add_notes(slide, "Рассказчик 1. Описать три шага: сбор, разметка, ограничения. Не утверждать, что корпус идеален: ручная фильтрация и отсутствие сцен — существенные ограничения. Метаданные не являются признаками.")
|
||||
|
||||
|
||||
def slide_cleaning(prs):
|
||||
slide = base_slide(prs, "Очистка и шум")
|
||||
add_panel(slide, 0.45, 1.35, 6.1, 5.55)
|
||||
add_bullets(slide, 0.65, 1.55, 5.7, 5.1, [
|
||||
"Нормализация Unicode: многоточия, кавычки, тире, неразрывные пробелы.",
|
||||
"Плейсхолдеры <PLAYER>, <FARM> и другие заменены нейтральными словами.",
|
||||
"Количество плейсхолдеров сохранено отдельным признаком.",
|
||||
"Токенизация, предложения и POS-разметка — NLTK; Universal POS.",
|
||||
"Пунктуация, регистр и стоп-слова сохранены: это стилометрический сигнал.",
|
||||
], size=15, title="Что сделали")
|
||||
add_panel(slide, 6.75, 1.35, 6.1, 5.55)
|
||||
add_bullets(slide, 6.95, 1.55, 5.7, 5.1, [
|
||||
"Шум: дубликаты, технические артефакты, общие короткие формулы.",
|
||||
"Ручная фильтрация удалила короткие и общие фразы; правила не сохранены.",
|
||||
"В текущем CSV 0 точных дублей; 13 пар с косинусной близостью ≥ 0,8 — внутри класса.",
|
||||
"Нет сцен и диалогов: нельзя учесть корреляцию реплик внутри сцены.",
|
||||
"Разметка только по имени; ручной стилистической разметки нет.",
|
||||
], size=15, title="Что считаем шумом")
|
||||
add_notes(slide, "Рассказчик 1. Объяснить, что очистка не должна удалять стиль: пунктуация, регистр и служебные слова сохранены. Ручная фильтрация — ограничение; почти-дубликаты не пересекают классы, поэтому утечки через них нет. Отметить отсутствие сцен.")
|
||||
|
||||
|
||||
def slide_class_stats(prs):
|
||||
slide = base_slide(prs, "Характеристики корпуса: классы", "Распределение реплик по персонажам")
|
||||
add_panel(slide, 0.4, 1.35, 7.2, 5.55)
|
||||
add_image_fit(slide, GEN.parent / "images" / "replics-count.png", 0.5, 1.45, 7.0, 5.35)
|
||||
add_panel(slide, 7.8, 1.35, 5.1, 5.55)
|
||||
rows = [
|
||||
["Показатель", "Значение"],
|
||||
["Реплик", "2005"],
|
||||
["Персонажей", "34"],
|
||||
["Максимум", "Abigail, 139"],
|
||||
["Минимум", "Mister Qi, 15"],
|
||||
["Разброс классов", "15–139"],
|
||||
["Случайный уровень", "2,9%"],
|
||||
]
|
||||
add_table(slide, 8.0, 1.55, 4.7, 3.2, rows, col_widths=[2.4, 2.3], font_size=12)
|
||||
add_bullets(slide, 8.0, 4.9, 4.7, 1.8, [
|
||||
"Дисбаланс почти десятикратный.",
|
||||
"Поэтому основная метрика — macro-F1, а не доля правильных ответов.",
|
||||
], size=13)
|
||||
add_notes(slide, "Рассказчик 1. Показать дисбаланс: максимум Abigail 139, минимум Mister Qi 15. При 34 классах случайное угадывание — 2,9%. Отсюда macro-F1 и веса классов; accuracy может обманывать.")
|
||||
|
||||
|
||||
def slide_length_stats(prs):
|
||||
slide = base_slide(prs, "Характеристики корпуса: длина реплик", "Медианная длина предложения в символах")
|
||||
add_panel(slide, 0.4, 1.35, 7.2, 5.55)
|
||||
add_image_fit(slide, GEN.parent / "images" / "median.png", 0.5, 1.45, 7.0, 5.35)
|
||||
add_panel(slide, 7.8, 1.35, 5.1, 5.55)
|
||||
rows = [
|
||||
["Показатель", "Значение"],
|
||||
["Средняя длина", "19,1 слова"],
|
||||
["Медиана", "17 слов"],
|
||||
["Диапазон", "3–78 слов"],
|
||||
["Медиана в символах", "52–114"],
|
||||
]
|
||||
add_table(slide, 8.0, 1.55, 4.7, 2.5, rows, col_widths=[2.4, 2.3], font_size=12)
|
||||
add_bullets(slide, 8.0, 4.25, 4.7, 2.35, [
|
||||
"Короткие реплики дают мало стилометрических свидетельств.",
|
||||
"Длина — сильный признак, но может стать прокси персонажа.",
|
||||
"Её вклад проверяется отдельно и по группам длины.",
|
||||
], size=13)
|
||||
add_notes(slide, "Рассказчик 1. Числа у подписей — медианная длина предложения в символах. Контраст Vincent/Jas и Sandy/Wizard. Длина — законный, но опасный признак: она может отражать не стиль, а привычную длину реплик персонажа.")
|
||||
|
||||
|
||||
def renumber(prs) -> None:
|
||||
for index, slide in enumerate(prs.slides, start=1):
|
||||
for shape in slide.shapes:
|
||||
if not shape.has_text_frame:
|
||||
continue
|
||||
text = shape.text_frame.text.strip()
|
||||
if text.isdigit() and shape.top > Inches(6.9) and shape.width < Inches(0.8):
|
||||
shape.text_frame.text = str(index)
|
||||
|
||||
|
||||
def replace_narrators(prs) -> None:
|
||||
mapping = {
|
||||
"Рассказчик 2": "В. Н. Копнин",
|
||||
"Рассказчик 3": "М. В. Савостиков",
|
||||
}
|
||||
for slide in prs.slides:
|
||||
for shape in slide.shapes:
|
||||
if shape.has_text_frame and shape.text_frame.text.strip() in mapping:
|
||||
shape.text_frame.text = mapping[shape.text_frame.text.strip()]
|
||||
|
||||
|
||||
def main() -> int:
|
||||
prs = Presentation(PPTX)
|
||||
# Сначала создаём новые слайды в конце, чтобы их части получили
|
||||
# уникальные имена и не конфликтовали с удаляемыми слайдами.
|
||||
base = len(prs.slides)
|
||||
slide_task(prs)
|
||||
slide_formal(prs)
|
||||
slide_corpus(prs)
|
||||
slide_cleaning(prs)
|
||||
slide_class_stats(prs)
|
||||
slide_length_stats(prs)
|
||||
|
||||
# Переносим новые слайды сразу после титульного.
|
||||
for offset in range(6):
|
||||
move_slide(prs, base + offset, 1 + offset)
|
||||
|
||||
# Теперь старые слайды первого блока находятся на позициях 7–12.
|
||||
for _ in range(6):
|
||||
delete_slide(prs, 7)
|
||||
|
||||
replace_narrators(prs)
|
||||
renumber(prs)
|
||||
prs.save(PPTX)
|
||||
print(f"Готово: {PPTX}; слайдов: {len(prs.slides)}")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -1,652 +0,0 @@
|
||||
---
|
||||
title: "Отчёт по лабораторной работе №1"
|
||||
subtitle: "Классификация текстов на основе стилометрических характеристик"
|
||||
author: "А. А. Аладьина, В. Н. Копнин, М. В. Савостиков"
|
||||
date: "01.11.2026"
|
||||
lang: ru-RU
|
||||
---
|
||||
|
||||
# Аннотация
|
||||
|
||||
Работа посвящена стилометрической классификации реплик персонажей игры
|
||||
Stardew Valley. Корпус содержит 2005 реплик, принадлежащих 34 персонажам;
|
||||
все реплики написаны одним автором. Проверяется, различимы ли речевые маски
|
||||
персонажей по отдельной реплике, если исключить авторскую атрибуцию как
|
||||
таковую.
|
||||
|
||||
Построены 454 ручных стилометрических признака, символьные и частеречные
|
||||
n-граммы, а также несколько вариантов латентно-семантического анализа.
|
||||
Сравнивались логистическая регрессия, линейный метод опорных векторов,
|
||||
наивный байесовский классификатор, случайный лес, сверхслучайные деревья,
|
||||
метод ближайших соседей и дельта Барроуза. Использована повторная
|
||||
стратифицированная перекрёстная проверка, вложенная настройка параметров и
|
||||
отдельная отложенная выборка.
|
||||
|
||||
Лучший наблюдаемый результат — мягкое голосование трёх моделей: macro-F1
|
||||
около **0,304** при перекрёстной проверке и **0,333** на отложенной выборке.
|
||||
Консервативная оценка при вложенном выборе конфигурации — **0,275**.
|
||||
Случайный уровень для 34 классов — **0,029**. Основной вклад дают
|
||||
лексико-тематические признаки и символьные n-граммы; после замены
|
||||
содержательных слов нейтральным маркером качество падает с 0,248 до 0,113.
|
||||
Следовательно, результат корректно интерпретировать как различимость речевых
|
||||
масок по лексико-тематическому профилю, а не как доказательство «чистого»
|
||||
идиолекта.
|
||||
|
||||
# 1. Постановка задачи
|
||||
|
||||
## 1.1. Направление исследования
|
||||
|
||||
Направление работы — классификация текстов по стилометрическим
|
||||
характеристикам. В отличие от классической авторской атрибуции, все тексты
|
||||
корпуса принадлежат одному автору. Исследуется другой вопрос:
|
||||
|
||||
> различаются ли речевые маски 34 персонажей по стилометрическим признакам
|
||||
> отдельных реплик, если автор у них общий?
|
||||
|
||||
Такая постановка позволяет проверить, насколько автор намеренно создаёт
|
||||
разные голоса персонажей, и какие признаки несут этот сигнал.
|
||||
|
||||
## 1.2. Гипотезы
|
||||
|
||||
| Обозначение | Гипотеза |
|
||||
|---|---|
|
||||
| H1 | Функциональные слова, части речи и символьные n-граммы различают персонажей выше случайного уровня |
|
||||
| H2 | На коротких репликах частеречные и символьные признаки устойчивее словарного богатства и читаемости |
|
||||
| H3 | LSA по всем словам отражает тему, а LSA по частям речи и функциональным словам — стиль |
|
||||
| H4 | Качество зависит от длины реплики |
|
||||
| H5 | Ошибки группируются вокруг персонажей со сходным возрастом, полом, ролью или видом |
|
||||
|
||||
## 1.3. Формальная постановка
|
||||
|
||||
Пусть \(D=\{(x_i,y_i)\}_{i=1}^{n}\) — корпус, где \(n=2005\),
|
||||
\(x_i\) — одна реплика, \(y_i\in Y\) — имя персонажа,
|
||||
\(|Y|=34\). Каждый объект относится ровно к одному классу.
|
||||
|
||||
Требуется построить классификатор
|
||||
|
||||
\[
|
||||
f\colon \Phi(x_i)\to Y,
|
||||
\]
|
||||
|
||||
где \(\Phi(x_i)\) — вектор признаков реплики. Качество оценивается
|
||||
макро-усреднённой F-мерой:
|
||||
|
||||
\[
|
||||
\operatorname{macroF1}=\frac{1}{|Y|}\sum_{c\in Y}
|
||||
\frac{2P_cR_c}{P_c+R_c}.
|
||||
\]
|
||||
|
||||
Макро-усреднение выбрано потому, что все 34 персонажа важны одинаково,
|
||||
а классы неравномерны.
|
||||
|
||||
# 2. Корпус
|
||||
|
||||
## 2.1. Источник и единица анализа
|
||||
|
||||
Корпус собран из диалогов игры Stardew Valley. Единица анализа — одна
|
||||
реплика персонажа; тексты на английском языке. Метка — имя говорящего,
|
||||
определённое по игровым диалогам и проверенное вручную.
|
||||
|
||||
Метаданные о поле, возрасте, виде и роли персонажа хранятся отдельно и
|
||||
используются только для анализа ошибок. В признаки классификатора они не
|
||||
подаются, чтобы не подменять стилометрическую задачу демографической.
|
||||
|
||||
## 2.2. Сбор и разметка
|
||||
|
||||
Правила формирования корпуса:
|
||||
|
||||
1. Из игровых диалогов извлекаются реплики персонажей.
|
||||
2. Каждая реплика становится отдельным объектом.
|
||||
3. Метка — имя говорящего; один объект относится ровно к одному классу.
|
||||
4. Проводится ручная проверка соответствия реплики и метки.
|
||||
5. Метаданные персонажей ведутся отдельной таблицей.
|
||||
6. Служебные метки игрового движка (`<PLAYER>`, `<FARM>`, `<PET>`,
|
||||
`<SPOUSE>`, `<TIME>`, `<NOUN>`, `<NAME>`) заменяются нейтральными словами;
|
||||
их количество сохраняется отдельным признаком.
|
||||
|
||||
Корпус хранится в CSV с разделителем `;` в кодировке cp1251; обязательные
|
||||
колонки — «Реплика» и «Персонаж». Метаданные персонажей ведутся отдельным
|
||||
XLSX-файлом. Такое разделение позволяет менять признаки, не затрагивая
|
||||
разметку, и проверять корпус независимо от моделей.
|
||||
|
||||
## 2.3. Очистка и шум
|
||||
|
||||
Выполнялась минимальная нормализация:
|
||||
|
||||
- Unicode-очистка: разные варианты кавычек, тире, многоточий и пробелов
|
||||
приводятся к единому виду;
|
||||
- замена плейсхолдеров нейтральными словами;
|
||||
- токенизация и разбиение на предложения средствами NLTK;
|
||||
- частеречная разметка Penn Treebank с переводом в Universal POS.
|
||||
|
||||
Не удалялись:
|
||||
|
||||
- пунктуация;
|
||||
- регистр;
|
||||
- стоп-слова;
|
||||
- короткие реплики, оставшиеся после ручной фильтрации.
|
||||
|
||||
Пунктуация, регистр и служебные слова рассматриваются как стилометрический
|
||||
сигнал, а не как шум.
|
||||
|
||||
Шумом считались:
|
||||
|
||||
- точные дубликаты;
|
||||
- технические артефакты;
|
||||
- общие короткие формулы;
|
||||
- плейсхолдеры игрового движка.
|
||||
|
||||
Корпус прошёл ручную фильтрацию: короткие и общие фразы удалены. Точные
|
||||
правила этой фильтрации не сохранились, поэтому исходное распределение
|
||||
восстановить нельзя. Это ограничение зафиксировано в выводах.
|
||||
|
||||
## 2.4. Характеристики корпуса
|
||||
|
||||
| Показатель | Значение |
|
||||
|---|---:|
|
||||
| Реплик | 2005 |
|
||||
| Персонажей | 34 |
|
||||
| Словоупотреблений | 38 207 |
|
||||
| Средняя длина реплики | 19,1 слова |
|
||||
| Медиана длины | 17 слов |
|
||||
| Минимум / максимум | 3 / 78 слов |
|
||||
| Классы | 15–139 реплик |
|
||||
| Точные дубли реплик | 0 |
|
||||
| Почти-дубли (косинусная близость ≥ 0,8) | 13 пар, все внутри одного класса |
|
||||
| Реплик с плейсхолдерами | 172 |
|
||||
|
||||
{width=70%}
|
||||
|
||||
{width=82%}
|
||||
|
||||
{width=92%}
|
||||
|
||||
Классы неравномерны: максимум — Abigail (139 реплик), минимум — Mister Qi
|
||||
(15 реплик). Разница почти десятикратная. Поэтому доля правильных ответов
|
||||
может вводить в заблуждение: модель, всегда выбирающая частый класс, получает
|
||||
заметную accuracy, но низкую macro-F1.
|
||||
|
||||
## 2.5. Соответствие формальным требованиям
|
||||
|
||||
Задание предусматривает не менее 90 текстов объёмом не менее 30 предложений
|
||||
и примерно равные классы. В данном корпусе единица анализа — реплика; она
|
||||
короче формального «текста» и содержит в среднем 19 слов. По согласованию с
|
||||
преподавателем эксперимент выполнен на уровне реплик: это позволяет
|
||||
использовать все 2005 объектов и корректно поставить задачу для коротких
|
||||
игровых реплик. Классы не равны по объёму; для компенсации использованы
|
||||
веса классов и macro-F1.
|
||||
|
||||
Это ограничение важно учитывать при сравнении с работами, где документы
|
||||
содержат десятки предложений.
|
||||
|
||||
# 3. Стилометрические признаки
|
||||
|
||||
## 3.1. Ручные признаки: 454 признака в 11 группах
|
||||
|
||||
| Группа | Примеры |
|
||||
|---|---|
|
||||
| Длина | число слов, символов, предложений; длина предложения |
|
||||
| Лексика | длины слов, апострофы, заглавные буквы, цифры |
|
||||
| Словарное богатство | TTR (доля уникальных слов), Guiraud, Herdan, Yule's K, Simpson's D, доля гапаксов и дислегомен |
|
||||
| Символы | частоты букв, регистр, цифры, пунктуация |
|
||||
| Пунктуация | запятые, точки, многоточия, вопросы, восклицания, повторы |
|
||||
| Функциональные слова | артикли, предлоги, союзы, местоимения, вспомогательные глаголы |
|
||||
| Местоимения | 1-е, 2-е, 3-е лицо; отношение «я/ты» |
|
||||
| Части речи | Universal POS, Penn Treebank, время, модальность, герундий |
|
||||
| Синтаксис | клаузы (элементарные предложения), подчинительные союзы, пассивные конструкции, вопросы |
|
||||
| Читаемость | Flesch, Flesch—Kincaid, Gunning Fog, SMOG, Coleman—Liau, ARI |
|
||||
| Разговорные маркеры | междометия, смех, осторожные формулировки (хеджи), интенсификаторы, вежливость, сокращения |
|
||||
|
||||
Словарное богатство и читаемость помечены как предварительные: на репликах
|
||||
длиной 10–20 слов они нестабильны. Основные группы для коротких текстов —
|
||||
функциональные слова, части речи, пунктуация, символьные n-граммы и
|
||||
разговорные маркеры.
|
||||
|
||||
### Обоснование выбора
|
||||
|
||||
- Функциональные слова — классическое ядро стилометрии: закрытый класс мало
|
||||
зависит от темы.
|
||||
- Части речи и синтаксические прокси отражают структуру фразы.
|
||||
- Символьные n-граммы устойчивы к теме и фиксируют морфологию, пунктуацию и
|
||||
орфографию.
|
||||
- Разговорные маркеры важны для игровых реплик: междометия, многоточия,
|
||||
смех и вежливость различают персонажей.
|
||||
- LSA сжимает разреженное пространство и позволяет сравнить тематический и
|
||||
стилистический вклады.
|
||||
|
||||
## 3.2. Символьные и частеречные n-граммы
|
||||
|
||||
- Символьные n-граммы: TF-IDF по символьным n-граммам в границах слов
|
||||
(`char_wb`), диапазон 2–5, `min_df=3`, до 5000–10 000 признаков,
|
||||
L2-нормализация.
|
||||
- Частеречные n-граммы: TF-IDF по последовательностям Universal POS,
|
||||
диапазон 1–3, до 500 признаков.
|
||||
|
||||
TF-IDF — вес признака, который растёт с частотой внутри реплики и падает с
|
||||
частотой в корпусе: частые общие n-граммы получают меньший вес, редкие
|
||||
характерные — больший.
|
||||
|
||||
Символьные n-граммы фиксируют морфологию и оформление; n-граммы частей речи —
|
||||
синтаксические шаблоны. Оба блока устойчивее к теме, чем словарные n-граммы.
|
||||
|
||||
## 3.3. Латентно-семантический анализ
|
||||
|
||||
LSA реализован как:
|
||||
|
||||
```text
|
||||
TF-IDF -> усечённое сингулярное разложение (TruncatedSVD) -> L2-нормализация
|
||||
```
|
||||
|
||||
Варианты:
|
||||
|
||||
| Вариант | Что отражает |
|
||||
|---|---|
|
||||
| `word` | лексику и тему |
|
||||
| `word2` | лексику и устойчивые сочетания |
|
||||
| `content` | содержание после удаления стоп-слов |
|
||||
| `fw` | функциональные слова и синтаксис |
|
||||
| `char` | морфологию и пунктуацию |
|
||||
| `pos` | частеречные последовательности |
|
||||
|
||||
Число компонент подбиралось в диапазоне 50–400 по объяснённой дисперсии и
|
||||
внешней метрике. Для `content`-LSA качество росло до 400 компонент.
|
||||
Ключевое ограничение: LSA по содержательным словам может отражать тему, а не
|
||||
только стиль. Поэтому варианты сравнивались между собой, а не выбирались
|
||||
заранее.
|
||||
|
||||
# 4. Классификаторы
|
||||
|
||||
В работе использовано шесть семейств ML-классификаторов и классическая дельта
|
||||
Барроуза — всего семь подходов; требование «не менее трёх» выполнено с
|
||||
запасом.
|
||||
|
||||
## 4.1. Логистическая регрессия
|
||||
|
||||
Линейная модель: для каждого класса вычисляется оценка
|
||||
\(s_c(x)=w_c^\top x+b_c\), затем вероятности через softmax. Обучение
|
||||
минимизирует перекрёстную энтропию с L2-регуляризацией. Параметр \(C\)
|
||||
управляет силой регуляризации; `class_weight="balanced"` повышает вес редких
|
||||
классов.
|
||||
|
||||
Сильные стороны: устойчивость при большом числе признаков, вероятности,
|
||||
интерпретируемые коэффициенты. Ограничение: только линейные границы.
|
||||
|
||||
## 4.2. Линейный метод опорных векторов
|
||||
|
||||
Ищет линейную границу с максимальным зазором; параметр \(C\) штрафует
|
||||
нарушения зазора. Не выдаёт вероятностей: для тройки лучших используется
|
||||
решающая функция. Силён на текстовых признаках, но требует настройки \(C\).
|
||||
|
||||
## 4.3. Случайный лес
|
||||
|
||||
Ансамбль решающих деревьев: каждое дерево обучается на подвыборке,
|
||||
разбиения используют случайное подмножество признаков. Класс определяется
|
||||
голосованием. Лес ловит нелинейные взаимодействия, но склонен к
|
||||
переобучению на 2005 объектах; ограничивались глубина и размер листьев.
|
||||
|
||||
## 4.4. Сверхслучайные деревья (ExtraTrees)
|
||||
|
||||
Похожи на случайный лес, но пороги разбиения выбираются случайно. Это
|
||||
уменьшает дисперсию и ускоряет обучение. В работе ExtraTrees с расширенным
|
||||
символьным словарём показали лучший результат среди одиночных моделей.
|
||||
|
||||
## 4.5. Наивный байесовский классификатор
|
||||
|
||||
MultinomialNB использует теорему Байеса и предположение об условной
|
||||
независимости признаков; сглаживание Лапласа задаётся параметром \(\alpha\).
|
||||
Быстрый baseline для частотных признаков; требует неотрицательных значений,
|
||||
поэтому LSA-компоненты для него исключались.
|
||||
|
||||
## 4.6. Метод ближайших соседей
|
||||
|
||||
Класс определяется по \(k\) ближайшим объектам; использовалось косинусное
|
||||
расстояние и веса, обратные расстоянию. Простая локальная модель; чувствительна
|
||||
к размерности и масштабу признаков.
|
||||
|
||||
## 4.7. Дельта Барроуза
|
||||
|
||||
Классический стилометрический метод: выбираются наиболее частотные слова,
|
||||
частоты z-нормируются по обучающей выборке, документ относится к классу с
|
||||
минимальным расстоянием до центроида. Использовались манхэттенское и
|
||||
косинусное расстояния. Дельта — независимая проверка, не заменяющая
|
||||
ML-классификаторы.
|
||||
|
||||
## 4.8. Ансамбли
|
||||
|
||||
- **Мягкое голосование** усредняет вероятности нескольких моделей.
|
||||
- **Объединение через мета-классификатор (стекинг)** обучает второй уровень
|
||||
на предсказаниях базовых моделей. В наших данных оно переобучилось и
|
||||
оказалось хуже мягкого голосования.
|
||||
|
||||
# 5. Схема верификации
|
||||
|
||||
## 5.1. Перекрёстная проверка
|
||||
|
||||
Основная схема:
|
||||
|
||||
```text
|
||||
RepeatedStratifiedKFold(n_splits=5, n_repeats=3–5)
|
||||
```
|
||||
|
||||
При 5 фолдах модель обучается на 4 фолдах и проверяется на 1; доли классов
|
||||
сохраняются внутри фолдов. Повторность позволяет оценить разброс.
|
||||
|
||||
## 5.2. Вложенная проверка
|
||||
|
||||
Внешние фолды используются для оценки качества, внутренние — для настройки
|
||||
гиперпараметров. Дополнительно выполнен вложенный выбор конфигурации
|
||||
признаков и модели: в каждом внешнем фолде конфигурация выбиралась только по
|
||||
внутренним фолдам.
|
||||
|
||||
## 5.3. Контроль утечек
|
||||
|
||||
- TF-IDF, SVD, масштабирование и отбор признаков обучаются только на
|
||||
обучающих фолдах.
|
||||
- Тестовая выборка не участвует в выборе гиперпараметров или признаков.
|
||||
- Контроль перестановкой меток дал macro-F1 0,0270–0,0282 — случайный
|
||||
уровень.
|
||||
- Все 13 почти-дубликатов находятся внутри одного класса; межклассовых
|
||||
почти-дублей не обнаружено, поэтому прямой утечки метки нет. При более
|
||||
строгой оценке такие пары всё равно следует держать в одном фолде.
|
||||
|
||||
## 5.4. Метрики
|
||||
|
||||
| Метрика | Что показывает |
|
||||
|---|---|
|
||||
| Macro-F1 | Основная метрика: средняя F-мера по 34 классам |
|
||||
| Доля правильных ответов | Доля совпавших ответов; обманчива при дисбалансе |
|
||||
| Сбалансированная доля | Средняя полнота по классам |
|
||||
| Top-3 | Истинный класс попал в тройку лучших |
|
||||
| MCC (коэффициент корреляции Мэтьюза) | Корреляция истинных и предсказанных меток |
|
||||
| Каппа Коэна | Согласие с поправкой на случай |
|
||||
| Log loss / Brier | Качество вероятностей |
|
||||
| Матрица ошибок | Какие персонажи перепутываются |
|
||||
|
||||
Дополнительно использованы бутстрэп-интервалы, критерий Мак-Немара для
|
||||
парных сравнений, критерий Уилкоксона и поправка Холма—Бонферрони.
|
||||
|
||||
# 6. Результаты
|
||||
|
||||
## 6.1. Сравнение моделей
|
||||
|
||||
| Конфигурация | Macro-F1 | Доля правильных ответов | Top-3 |
|
||||
|---|---:|---:|---:|
|
||||
| Мягкое голосование трёх моделей | **0,3036 ± 0,0189** | 0,2808 | 0,4823 |
|
||||
| ExtraTrees, `char_max_features=10000` | 0,2757 ± 0,0219 | 0,2633 | 0,4554 |
|
||||
| Вложенный выбор конфигурации | 0,2746 ± 0,0233 | 0,2603 | — |
|
||||
| Случайный лес, `char_max_features=10000` | 0,2583 ± 0,0066 | 0,2391 | 0,4197 |
|
||||
| ExtraTrees, полный набор | 0,2499 ± 0,0209 | 0,2374 | 0,4234 |
|
||||
| Логистическая регрессия, lean + content-LSA | 0,2466 ± 0,0191 | 0,2349 | 0,4030 |
|
||||
| Логистическая регрессия, `stylometry_pos_lsa` | 0,1785 ± 0,0060 | 0,1679 | 0,3322 |
|
||||
| Только ручные признаки | 0,1051 ± 0,0038 | 0,1012 | 0,2338 |
|
||||
| Случайный уровень | 0,029 | 0,029 | 0,088 |
|
||||
|
||||
На отложенной выборке 80/20 мягкое голосование дало **0,3333**; ExtraTrees
|
||||
на полном наборе — 0,2793; случайный лес с `char_max_features=10000` —
|
||||
0,2554; логистическая регрессия с content-LSA — 0,2459.
|
||||
|
||||
{width=82%}
|
||||
|
||||
## 6.2. Вклад признаков
|
||||
|
||||
| Условие | Macro-F1 |
|
||||
|---|---:|
|
||||
| Только длина | 0,035 |
|
||||
| Только ручные стилометрические признаки | 0,105 |
|
||||
| Ручные + символьные и частеречные n-граммы | 0,182 |
|
||||
| То же + `content`-LSA | 0,249 |
|
||||
| ExtraTrees с расширенным символьным словарём | 0,276 |
|
||||
| Мягкое голосование | 0,304 |
|
||||
|
||||
{width=72%}
|
||||
|
||||
Основной прирост дают символьные n-граммы, лексика и `content`-LSA.
|
||||
Функциональные слова и POS-LSA сами по себе слабы (0,06–0,07): это важный
|
||||
отрицательный результат.
|
||||
|
||||
## 6.3. Стиль или содержание
|
||||
|
||||
Содержательные слова (существительные, глаголы, прилагательные, наречия,
|
||||
числительные) заменялись нейтральным маркером `CONTENT`; служебные слова,
|
||||
пунктуация и частеречные теги сохранялись.
|
||||
|
||||
| Условие | Macro-F1 |
|
||||
|---|---:|
|
||||
| Исходный текст, стилевой набор, ExtraTrees | 0,2483 |
|
||||
| Маскированный текст, стилевой набор, ExtraTrees | 0,1129 |
|
||||
| Исходный текст, стилевой набор, LogReg | 0,1467 |
|
||||
| Маскированный текст, стилевой набор, LogReg | 0,1067 |
|
||||
|
||||
{width=68%}
|
||||
|
||||
Падение более чем вдвое показывает, что значительная часть качества связана
|
||||
с лексическим содержанием. Остаток 0,113 всё ещё в 3,9 раза выше случайного
|
||||
уровня: некоторый стилевой сигнал сохраняется, но он слабее тематического.
|
||||
|
||||
## 6.4. Матрицы ошибок
|
||||
|
||||
Строки матрицы — истинный персонаж, столбцы — предсказание модели, диагональ
|
||||
— правильные ответы, вне диагонали — ошибки. Полная матрица 34×34 на доске
|
||||
нечитаема; поэтому в основном тексте показаны 12 самых частых персонажей и
|
||||
столбец «прочие», а также 18 самых частых пар ошибок.
|
||||
|
||||
{width=92%}
|
||||
|
||||
{width=92%}
|
||||
|
||||
Основные пары ошибок (за три повтора): Sebastian → Abigail (65),
|
||||
Sebastian → Alex (61), Abigail → Sebastian (59), Abigail → Alex (56).
|
||||
Крупнейший клубок — молодые персонажи Abigail, Alex, Sebastian, Haley, Sam.
|
||||
Многие из них писались в одних и тех же обновлениях и сюжетных контекстах,
|
||||
поэтому сходство голосов ожидаемо; это не разные авторы.
|
||||
|
||||
## 6.5. Ошибки по персонажам
|
||||
|
||||
{width=92%}
|
||||
|
||||
Лучшие результаты по F-мере: Willy, Evelyn, Pierre, Pam, Leo, Vincent.
|
||||
Худшие: Kent, Mister Qi, Penny, Abigail, Leah, Jodi. Редкие классы имеют
|
||||
широкие интервалы; у Mister Qi всего 15 реплик.
|
||||
|
||||
Abigail и Alex являются классами-аттракторами: модель ошибочно относит к ним
|
||||
чужие реплики. Abigail получает 10–18% всех предсказаний при истинной доле
|
||||
около 6,9%. Простая коррекция априорных вероятностей классов не улучшила
|
||||
macro-F1.
|
||||
|
||||
{width=88%}
|
||||
|
||||
## 6.6. Длина реплики
|
||||
|
||||
| Длина | Macro-F1 |
|
||||
|---|---:|
|
||||
| 0–5 слов | 0,097 |
|
||||
| 6–10 | 0,244 |
|
||||
| 11–15 | 0,233 |
|
||||
| 16–20 | 0,239 |
|
||||
| 21–30 | 0,295 |
|
||||
| 31+ | 0,271 |
|
||||
|
||||
Зависимость немонотонная: максимум приходится на 21–30 слов, после 30 слов
|
||||
качество снижается. Гипотеза «чем длиннее, тем лучше» не подтверждается.
|
||||
|
||||
## 6.7. Верификация и кластеризация
|
||||
|
||||
- Верификация «свой/чужой»: ROC-AUC 0,630–0,633; EER (равная ошибка
|
||||
ложного принятия и ложного отказа) 0,412–0,415; top-1 0,133–0,141.
|
||||
Выше случайного, но практически слабо.
|
||||
- Кластеризация: ARI (скорректированный индекс Рэнда) = 0 для k = 3/5/8;
|
||||
NMI (нормированная взаимная информация) 0,14–0,53. Устойчивых кластеров,
|
||||
соответствующих персонажам, нет.
|
||||
|
||||
## 6.8. Калибровка и ансамбли
|
||||
|
||||
Коррекция априорных вероятностей вида
|
||||
\(p'_{ic}\propto p_{ic}\pi_c^{-\alpha}\) проверялась по схеме
|
||||
«один повтор — отложенный, остальные — обучающие». Среднее изменение
|
||||
macro-F1 составило **−0,0022**: простой пересчёт приоров не помог.
|
||||
|
||||
Мягкое голосование трёх моделей (ExtraTrees на полном наборе, ExtraTrees с
|
||||
`char_max_features=10000`, логистическая регрессия с lean + content-LSA)
|
||||
дало **0,3036 ± 0,0189**. Объединение через мета-классификатор оказалось хуже
|
||||
(0,2259 ± 0,0163) из-за переобучения на малом числе объектов.
|
||||
|
||||
# 7. Обсуждение
|
||||
|
||||
1. **Персонажи статистически различимы, но сигнал умеренный.** 0,304
|
||||
macro-F1 при 34 классах — примерно в 10 раз выше случайного уровня 0,029.
|
||||
Абсолютное значение ограничено короткими репликами, дисбалансом и общими
|
||||
темами.
|
||||
|
||||
2. **Основной вклад — лексико-тематический.** Символьные n-граммы и
|
||||
`content`-LSA дают основной прирост; функциональные слова и POS-LSA сами
|
||||
по себе слабы. После маскирования содержательных слов качество падает
|
||||
более чем вдвое.
|
||||
|
||||
3. **Наиболее устойчивая одиночная модель — ExtraTrees с
|
||||
`char_max_features=10000`.** Она выбрана в 14 из 15 внешних фолдов
|
||||
вложенной процедуры и даёт 0,2757 ± 0,0219.
|
||||
|
||||
4. **Мягкое голосование — лучший наблюдаемый результат**, но состав
|
||||
ансамбля выбирался по предыдущим сериям. Для строгого вывода нужна
|
||||
внешняя валидация.
|
||||
|
||||
5. **Ошибки — это коллапс редких классов в классы-аттракторы**, а не
|
||||
доказательство сходства характеров. Abigail, Alex и ещё несколько
|
||||
популярных классов забирают чужие реплики.
|
||||
|
||||
6. **Один автор и общие контексты** делают путаницу между персонажами
|
||||
ожидаемой. Это не означает, что автор «не старался»: превышение
|
||||
случайного уровня как раз говорит о намеренно созданных речевых масках.
|
||||
|
||||
# 8. Ограничения
|
||||
|
||||
- Один корпус, одна игра, один автор; внешняя валидация не проводилась.
|
||||
- Единица анализа — реплика, а не текст из 30 предложений; это согласовано,
|
||||
но ограничивает сравнение с документными корпусами.
|
||||
- Классы неравномерны; редкие классы имеют широкие интервалы.
|
||||
- Ручная фильтрация корпуса невоспроизводима.
|
||||
- Нет идентификаторов сцен и диалогов.
|
||||
- Выбор признаков и ансамбля частично опирался на предыдущие серии;
|
||||
вложенная проверка снижает, но не устраняет оптимизм.
|
||||
- Содержательные слова и тема вносят большой вклад; «чистый стиль» не
|
||||
доказан.
|
||||
- Простая коррекция приоров не помогла; калибровка редких классов требует
|
||||
дальнейшей работы.
|
||||
|
||||
# 9. Выводы
|
||||
|
||||
1. Речевые маски 34 персонажей статистически различимы по отдельным
|
||||
репликам: лучшая модель — 0,304 macro-F1, консервативная оценка — 0,275
|
||||
при случайном уровне 0,029.
|
||||
2. Основной сигнал — лексико-тематический: символьные n-граммы и
|
||||
`content`-LSA дают основной прирост; функциональные слова и частеречная
|
||||
LSA сами по себе слабы.
|
||||
3. После замены содержательных слов качество падает с 0,248 до 0,113:
|
||||
содержание доминирует, но стилевой сигнал сохраняется.
|
||||
4. Главная проблема — редкие классы и классы-аттракторы; мягкое голосование
|
||||
помогает, но не решает её полностью.
|
||||
5. Корректная формулировка результата: различимы речевые маски по
|
||||
лексико-тематическому профилю; утверждение о «чистом стиле» требует
|
||||
внешней валидации и тематического контроля.
|
||||
|
||||
# 10. Воспроизводимость
|
||||
|
||||
## 10.1. Структура проекта
|
||||
|
||||
```text
|
||||
lab1/
|
||||
├── stylometry/ # чтение, признаки, LSA, модели, метрики
|
||||
├── scripts/ # проверка окружения, запуск серий, упаковка
|
||||
├── tests/ # pytest
|
||||
├── configs/ # конфигурация
|
||||
├── data/ # корпус и метаданные
|
||||
├── report/ # этот отчёт
|
||||
├── presentation/ # PPTX/PDF и LaTeX-версия
|
||||
├── README.md
|
||||
├── RUNBOOK.md
|
||||
├── EXPERIMENTS.md
|
||||
├── FEATURES.md
|
||||
├── METRICS_AND_CLASSIFIERS.md
|
||||
├── TUNING.md
|
||||
├── FINAL_REPORT.md
|
||||
├── FINAL_RESULTS.csv
|
||||
├── requirements.txt
|
||||
└── requirements-report.txt
|
||||
```
|
||||
|
||||
Краткая и подробная инструкции к архиву (`START_HERE.md`,
|
||||
`ARCHIVE_README.md`) лежат в его корне, рядом с каталогом `lab1`.
|
||||
|
||||
## 10.2. Установка и запуск
|
||||
|
||||
```bash
|
||||
cd lab1
|
||||
python -m venv .venv
|
||||
source .venv/bin/activate
|
||||
python -m pip install -r requirements.txt
|
||||
python scripts/download_nltk_data.py
|
||||
|
||||
python scripts/check_environment.py
|
||||
python -m pytest -q
|
||||
|
||||
# аудит и признаки
|
||||
python -m stylometry.cli audit
|
||||
python -m stylometry.cli features
|
||||
|
||||
# основной эксперимент
|
||||
python -m stylometry.cli run \
|
||||
--preset stylometry_pos_lsa --model logreg --repeats 5
|
||||
|
||||
# полная серия
|
||||
bash scripts/run_all_experiments.sh
|
||||
|
||||
# дополнительные эксперименты
|
||||
python scripts/run_final_experiments.py \
|
||||
--stages nested topic calibration ensemble extra \
|
||||
--out results/final
|
||||
|
||||
# пересборка отчёта (pandoc + LibreOffice)
|
||||
python scripts/build_report.py
|
||||
```
|
||||
|
||||
## 10.3. Программное обеспечение
|
||||
|
||||
- Python 3.11+;
|
||||
- NumPy, pandas, SciPy, scikit-learn, matplotlib;
|
||||
- NLTK (`punkt`, `punkt_tab`, `averaged_perceptron_tagger_eng`, `stopwords`);
|
||||
- python-pptx и Pillow — для презентации;
|
||||
- pandoc, LibreOffice, python-docx и PyMuPDF — для сборки отчёта;
|
||||
- XeLaTeX и Beamer — для LaTeX-версии презентации.
|
||||
|
||||
# Приложение A. Полная матрица ошибок
|
||||
|
||||
{width=92%}
|
||||
|
||||
# Приложение B. Сводная таблица результатов
|
||||
|
||||
См. `FINAL_RESULTS.csv` в корне проекта.
|
||||
|
||||
# Приложение C. Состав архива
|
||||
|
||||
В архив включены код, тесты, конфигурации, корпус, метаданные, документация,
|
||||
отчёт, презентация (PPTX и LaTeX), сводные результаты и инструкции запуска.
|
||||
Виртуальное окружение, кэш NLTK и промежуточные матрицы признаков в архив не
|
||||
включены: они восстанавливаются скриптами.
|
||||
|
||||
# Приложение D. Источники и программные средства
|
||||
|
||||
1. Burrows, J. F. «Delta»: a Measure of Stylistic Difference and a Guide to
|
||||
Likely Authorship // Literary and Linguistic Computing. 2002. Vol. 17,
|
||||
no. 3. P. 267–287.
|
||||
2. Eder, M., Rybicki, J., Kestemont, M. Stylometry with R: A Package for
|
||||
Computational Text Analysis // The R Journal. 2016. Vol. 8, no. 1.
|
||||
P. 107–121.
|
||||
3. Mosteller, F., Wallace, D. L. Inference and Disputed Authorship:
|
||||
The Federalist. Reading, MA: Addison-Wesley, 1964.
|
||||
4. Stamatatos, E. A Survey of Modern Authorship Attribution Methods // Journal of the American Society for Information Science and Technology. 2009. Vol. 60, no. 3. P. 538–556.
|
||||
5. Pedregosa, F. et al. Scikit-learn: Machine Learning in Python //
|
||||
Journal of Machine Learning Research. 2011. Vol. 12. P. 2825–2830.
|
||||
6. Bird, S., Klein, E., Loper, E. Natural Language Processing with Python.
|
||||
Sebastopol, CA: O'Reilly Media, 2009.
|
||||
7. ConcernedApe. Stardew Valley. Video game. 2016.
|
||||
|
||||
|
Before Width: | Height: | Size: 2.4 MiB |
|
Before Width: | Height: | Size: 178 KiB |
|
Before Width: | Height: | Size: 170 KiB |
|
Before Width: | Height: | Size: 238 KiB |
|
Before Width: | Height: | Size: 141 KiB |
|
Before Width: | Height: | Size: 96 KiB |
|
Before Width: | Height: | Size: 680 KiB |
|
Before Width: | Height: | Size: 132 KiB |
|
Before Width: | Height: | Size: 240 KiB |
|
Before Width: | Height: | Size: 795 KiB |
|
Before Width: | Height: | Size: 104 KiB |
@@ -1,5 +0,0 @@
|
||||
# Дополнительные зависимости для пересборки отчёта.
|
||||
# Основные зависимости проекта — в requirements.txt.
|
||||
python-docx>=1.1,<2
|
||||
PyMuPDF>=1.24,<2
|
||||
Pillow>=10,<13
|
||||
@@ -8,4 +8,3 @@ nltk>=3.9,<4
|
||||
joblib>=1.3,<2
|
||||
tabulate>=0.9,<1
|
||||
pytest>=8.0,<10
|
||||
python-pptx>=1.0,<2
|
||||
|
||||
@@ -1,336 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
"""Сводка follow-up экспериментов и парные сравнения по повторам.
|
||||
|
||||
Скрипт читает каталоги results/followup/<label>/, созданные
|
||||
scripts/run_followup_experiments.sh, и формирует:
|
||||
|
||||
- summary.csv / summary.md — среднее macro-F1 по повторам, а не только repeat 0;
|
||||
- comparisons.csv / comparisons.md — парные сравнения с McNemar и bootstrap;
|
||||
- comparisons/<A>__vs__<B>.json — детали по каждому повтору.
|
||||
|
||||
Пример:
|
||||
python scripts/analyze_followup.py \
|
||||
--followup-dir results/followup \
|
||||
--pairs final_rf:ref_logreg final_rf:ref_no_lsa
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
|
||||
PROJECT_ROOT = Path(__file__).resolve().parents[1]
|
||||
sys.path.insert(0, str(PROJECT_ROOT))
|
||||
|
||||
from stylometry.evaluation import classification_metrics # noqa: E402
|
||||
from stylometry.stats import bootstrap_difference, mcnemar_exact # noqa: E402
|
||||
|
||||
|
||||
def _json_load(path: Path) -> dict[str, Any]:
|
||||
return json.loads(path.read_text(encoding="utf-8"))
|
||||
|
||||
|
||||
def _read_run(run_dir: Path) -> tuple[dict[str, Any], pd.DataFrame]:
|
||||
metadata = _json_load(run_dir / "run_metadata.json")
|
||||
pooled_path = run_dir / "pooled_metrics.csv"
|
||||
if pooled_path.exists():
|
||||
pooled = pd.read_csv(pooled_path)
|
||||
else:
|
||||
pooled = pd.DataFrame([metadata.get("metrics", {})])
|
||||
return metadata, pooled
|
||||
|
||||
|
||||
def _feature_summary(config: dict[str, Any]) -> str:
|
||||
keys = (
|
||||
"groups",
|
||||
"lsa_variants",
|
||||
"lsa_components",
|
||||
"min_df",
|
||||
"char_max_features",
|
||||
"pos_max_features",
|
||||
"char_ngram_range",
|
||||
"pos_ngram_range",
|
||||
"block_weights",
|
||||
)
|
||||
features = config.get("feature_config", {})
|
||||
return json.dumps({key: features.get(key) for key in keys if key in features}, ensure_ascii=False)
|
||||
|
||||
|
||||
def build_summary(followup_dir: Path) -> pd.DataFrame:
|
||||
rows: list[dict[str, Any]] = []
|
||||
for run_dir in sorted(followup_dir.iterdir()):
|
||||
if not run_dir.is_dir() or not (run_dir / "run_metadata.json").exists():
|
||||
continue
|
||||
metadata, pooled = _read_run(run_dir)
|
||||
config = metadata.get("config", {})
|
||||
bootstrap = metadata.get("bootstrap", {}).get("macro_f1", {})
|
||||
macro = pd.to_numeric(pooled.get("macro_f1"), errors="coerce")
|
||||
accuracy = pd.to_numeric(pooled.get("accuracy"), errors="coerce")
|
||||
balanced = pd.to_numeric(pooled.get("balanced_accuracy"), errors="coerce")
|
||||
top3 = pd.to_numeric(pooled.get("top3_accuracy"), errors="coerce")
|
||||
command_path = run_dir / "command.txt"
|
||||
rows.append(
|
||||
{
|
||||
"label": run_dir.name,
|
||||
"preset": config.get("preset"),
|
||||
"model": config.get("model"),
|
||||
"folds": config.get("folds"),
|
||||
"n_repeats": int(len(pooled)),
|
||||
"n_samples": config.get("n_samples"),
|
||||
"mean_macro_f1": float(macro.mean()),
|
||||
"std_macro_f1": float(macro.std(ddof=1)) if len(macro) > 1 else 0.0,
|
||||
"min_macro_f1": float(macro.min()),
|
||||
"max_macro_f1": float(macro.max()),
|
||||
"repeat0_macro_f1": float(macro.iloc[0]),
|
||||
"mean_accuracy": float(accuracy.mean()),
|
||||
"mean_balanced_accuracy": float(balanced.mean()),
|
||||
"mean_top3_accuracy": float(top3.mean()),
|
||||
"ci_lower_repeat0": bootstrap.get("lower"),
|
||||
"ci_upper_repeat0": bootstrap.get("upper"),
|
||||
"model_params": json.dumps(config.get("model_params", {}), ensure_ascii=False),
|
||||
"feature_config": _feature_summary(config),
|
||||
"command": command_path.read_text(encoding="utf-8").strip()
|
||||
if command_path.exists()
|
||||
else "",
|
||||
}
|
||||
)
|
||||
summary = pd.DataFrame(rows)
|
||||
if summary.empty:
|
||||
return summary
|
||||
reference = summary.loc[summary["label"] == "ref_logreg", "mean_macro_f1"]
|
||||
if not reference.empty:
|
||||
summary["delta_vs_ref_logreg"] = summary["mean_macro_f1"] - float(reference.iloc[0])
|
||||
else:
|
||||
summary["delta_vs_ref_logreg"] = np.nan
|
||||
summary = summary.sort_values("mean_macro_f1", ascending=False).reset_index(drop=True)
|
||||
summary.insert(0, "rank", np.arange(1, len(summary) + 1))
|
||||
return summary
|
||||
|
||||
|
||||
def _compare_pair(
|
||||
followup_dir: Path,
|
||||
label_a: str,
|
||||
label_b: str,
|
||||
n_boot: int,
|
||||
seed: int,
|
||||
) -> tuple[dict[str, Any] | None, list[dict[str, Any]]]:
|
||||
dir_a = followup_dir / label_a
|
||||
dir_b = followup_dir / label_b
|
||||
if not (dir_a / "oof_predictions.csv").exists() or not (dir_b / "oof_predictions.csv").exists():
|
||||
print(f"[compare] пропуск {label_a} vs {label_b}: нет oof_predictions.csv")
|
||||
return None, []
|
||||
|
||||
oof_a = pd.read_csv(dir_a / "oof_predictions.csv")
|
||||
oof_b = pd.read_csv(dir_b / "oof_predictions.csv")
|
||||
common_repeats = sorted(set(oof_a["repeat"].unique()) & set(oof_b["repeat"].unique()))
|
||||
if not common_repeats:
|
||||
print(f"[compare] пропуск {label_a} vs {label_b}: нет общих повторов")
|
||||
return None, []
|
||||
|
||||
details: list[dict[str, Any]] = []
|
||||
for repeat in common_repeats:
|
||||
first = oof_a.loc[oof_a["repeat"] == repeat, ["line_id", "y_true", "y_pred"]]
|
||||
second = oof_b.loc[oof_b["repeat"] == repeat, ["line_id", "y_true", "y_pred"]]
|
||||
merged = first.merge(
|
||||
second,
|
||||
on=["line_id", "y_true"],
|
||||
suffixes=("_a", "_b"),
|
||||
how="inner",
|
||||
)
|
||||
if merged.empty:
|
||||
continue
|
||||
labels = sorted(merged["y_true"].unique())
|
||||
metrics_a = classification_metrics(merged["y_true"], merged["y_pred_a"], labels=labels)
|
||||
metrics_b = classification_metrics(merged["y_true"], merged["y_pred_b"], labels=labels)
|
||||
mcnemar = mcnemar_exact(merged["y_true"], merged["y_pred_a"], merged["y_pred_b"])
|
||||
difference = bootstrap_difference(
|
||||
merged["y_true"],
|
||||
merged["y_pred_a"],
|
||||
merged["y_pred_b"],
|
||||
metric="macro_f1",
|
||||
labels=labels,
|
||||
n_boot=n_boot,
|
||||
seed=seed + int(repeat),
|
||||
)
|
||||
details.append(
|
||||
{
|
||||
"repeat": int(repeat),
|
||||
"n_samples": int(len(merged)),
|
||||
"macro_f1_a": metrics_a["macro_f1"],
|
||||
"macro_f1_b": metrics_b["macro_f1"],
|
||||
"macro_f1_diff": metrics_a["macro_f1"] - metrics_b["macro_f1"],
|
||||
"accuracy_a": metrics_a["accuracy"],
|
||||
"accuracy_b": metrics_b["accuracy"],
|
||||
"accuracy_diff": metrics_a["accuracy"] - metrics_b["accuracy"],
|
||||
"mcnemar_p": mcnemar["p_value"],
|
||||
"mcnemar_b": mcnemar["b"],
|
||||
"mcnemar_c": mcnemar["c"],
|
||||
"bootstrap_mean_diff": difference["mean_difference"],
|
||||
"bootstrap_lower": difference["lower"],
|
||||
"bootstrap_upper": difference["upper"],
|
||||
}
|
||||
)
|
||||
|
||||
if not details:
|
||||
print(f"[compare] пропуск {label_a} vs {label_b}: не удалось сопоставить строки")
|
||||
return None, []
|
||||
|
||||
diffs = np.asarray([row["macro_f1_diff"] for row in details], dtype=float)
|
||||
p_values = np.asarray([row["mcnemar_p"] for row in details], dtype=float)
|
||||
ci_lower = np.asarray([row["bootstrap_lower"] for row in details], dtype=float)
|
||||
ci_upper = np.asarray([row["bootstrap_upper"] for row in details], dtype=float)
|
||||
summary = {
|
||||
"pair": f"{label_a} vs {label_b}",
|
||||
"label_a": label_a,
|
||||
"label_b": label_b,
|
||||
"n_repeats": len(details),
|
||||
"mean_macro_f1_a": float(np.mean([row["macro_f1_a"] for row in details])),
|
||||
"mean_macro_f1_b": float(np.mean([row["macro_f1_b"] for row in details])),
|
||||
"mean_diff": float(diffs.mean()),
|
||||
"std_diff": float(diffs.std(ddof=1)) if len(diffs) > 1 else 0.0,
|
||||
"min_diff": float(diffs.min()),
|
||||
"max_diff": float(diffs.max()),
|
||||
"n_positive": int((diffs > 0).sum()),
|
||||
"n_negative": int((diffs < 0).sum()),
|
||||
"mean_mcnemar_p": float(p_values.mean()),
|
||||
"min_mcnemar_p": float(p_values.min()),
|
||||
"mean_ci_lower": float(ci_lower.mean()),
|
||||
"mean_ci_upper": float(ci_upper.mean()),
|
||||
"n_ci_excludes_zero": int(((ci_lower > 0) | (ci_upper < 0)).sum()),
|
||||
}
|
||||
return summary, details
|
||||
|
||||
|
||||
def _format_markdown(summary: pd.DataFrame) -> str:
|
||||
columns = [
|
||||
"rank",
|
||||
"label",
|
||||
"model",
|
||||
"preset",
|
||||
"n_repeats",
|
||||
"mean_macro_f1",
|
||||
"std_macro_f1",
|
||||
"mean_accuracy",
|
||||
"mean_top3_accuracy",
|
||||
"delta_vs_ref_logreg",
|
||||
]
|
||||
table = summary[columns].copy()
|
||||
return table.to_markdown(index=False, floatfmt=".4f")
|
||||
|
||||
|
||||
def main(argv: list[str] | None = None) -> int:
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument(
|
||||
"--followup-dir",
|
||||
default=str(PROJECT_ROOT / "results" / "followup"),
|
||||
help="Каталог с подкаталогами запусков.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--pairs",
|
||||
nargs="*",
|
||||
default=[],
|
||||
help="Пары для сравнения в формате label_a:label_b.",
|
||||
)
|
||||
parser.add_argument("--n-boot", type=int, default=1000)
|
||||
parser.add_argument("--seed", type=int, default=42)
|
||||
args = parser.parse_args(argv)
|
||||
|
||||
followup_dir = Path(args.followup_dir)
|
||||
if not followup_dir.exists():
|
||||
raise FileNotFoundError(f"Нет каталога: {followup_dir}")
|
||||
|
||||
summary = build_summary(followup_dir)
|
||||
if summary.empty:
|
||||
print(f"[summary] в {followup_dir} нет запусков с run_metadata.json")
|
||||
return 1
|
||||
|
||||
summary_path = followup_dir / "summary.csv"
|
||||
summary.to_csv(summary_path, index=False, encoding="utf-8-sig")
|
||||
markdown = [
|
||||
"# Follow-up: сводка запусков",
|
||||
"",
|
||||
"Среднее и стандартное отклонение посчитаны по pooled-метрикам каждого",
|
||||
"повтора (`pooled_metrics.csv`), а не только по repeat 0.",
|
||||
"",
|
||||
_format_markdown(summary),
|
||||
"",
|
||||
]
|
||||
(followup_dir / "summary.md").write_text("\n".join(markdown), encoding="utf-8")
|
||||
print(f"[summary] {summary_path}")
|
||||
print(
|
||||
summary[
|
||||
["rank", "label", "model", "n_repeats", "mean_macro_f1", "std_macro_f1"]
|
||||
].to_string(index=False)
|
||||
)
|
||||
|
||||
comparison_rows: list[dict[str, Any]] = []
|
||||
comparisons_dir = followup_dir / "comparisons"
|
||||
comparisons_dir.mkdir(exist_ok=True)
|
||||
for pair in args.pairs:
|
||||
if ":" not in pair:
|
||||
print(f"[compare] некорректная пара: {pair}")
|
||||
continue
|
||||
label_a, label_b = pair.split(":", 1)
|
||||
summary_row, details = _compare_pair(
|
||||
followup_dir,
|
||||
label_a.strip(),
|
||||
label_b.strip(),
|
||||
n_boot=args.n_boot,
|
||||
seed=args.seed,
|
||||
)
|
||||
if summary_row is None:
|
||||
continue
|
||||
comparison_rows.append(summary_row)
|
||||
payload = {"summary": summary_row, "details": details}
|
||||
output = comparisons_dir / f"{label_a.strip()}__vs__{label_b.strip()}.json"
|
||||
output.write_text(json.dumps(payload, ensure_ascii=False, indent=2), encoding="utf-8")
|
||||
print(
|
||||
f"[compare] {summary_row['pair']}: "
|
||||
f"diff={summary_row['mean_diff']:+.4f}, "
|
||||
f"{summary_row['n_positive']}/{summary_row['n_repeats']} повторов в плюс, "
|
||||
f"mean McNemar p={summary_row['mean_mcnemar_p']:.4f}"
|
||||
)
|
||||
|
||||
if comparison_rows:
|
||||
comparisons = pd.DataFrame(comparison_rows).sort_values("mean_diff", ascending=False)
|
||||
comparisons.to_csv(followup_dir / "comparisons.csv", index=False, encoding="utf-8-sig")
|
||||
columns = [
|
||||
"pair",
|
||||
"n_repeats",
|
||||
"mean_macro_f1_a",
|
||||
"mean_macro_f1_b",
|
||||
"mean_diff",
|
||||
"std_diff",
|
||||
"n_positive",
|
||||
"mean_mcnemar_p",
|
||||
"mean_ci_lower",
|
||||
"mean_ci_upper",
|
||||
"n_ci_excludes_zero",
|
||||
]
|
||||
comparison_markdown = [
|
||||
"# Follow-up: парные сравнения",
|
||||
"",
|
||||
"`mean_diff` — средняя разница macro-F1 (A − B) по общим повторам.",
|
||||
"`n_positive` — сколько повторов дали положительную разницу.",
|
||||
"`n_ci_excludes_zero` — сколько повторов имеют bootstrap-интервал,",
|
||||
"не содержащий ноль (описательно, без поправки на множественность).",
|
||||
"",
|
||||
comparisons[columns].to_markdown(index=False, floatfmt=".4f"),
|
||||
"",
|
||||
]
|
||||
(followup_dir / "comparisons.md").write_text(
|
||||
"\n".join(comparison_markdown), encoding="utf-8"
|
||||
)
|
||||
print(f"[compare] {followup_dir / 'comparisons.csv'}")
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -1,570 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Сборка отчёта: Markdown -> DOCX -> PDF.
|
||||
|
||||
Скрипт рассчитан на запуск из каталога lab1:
|
||||
|
||||
python scripts/build_report.py
|
||||
|
||||
Требуются pandoc, python-docx и libreoffice. PDF собирается через
|
||||
LibreOffice, чтобы не зависеть от LaTeX-шаблона и корректно передать
|
||||
кириллицу, таблицы и рисунки.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
import docx
|
||||
from docx.enum.section import WD_SECTION
|
||||
from docx.enum.table import WD_TABLE_ALIGNMENT
|
||||
from docx.enum.text import WD_ALIGN_PARAGRAPH, WD_BREAK, WD_LINE_SPACING
|
||||
from docx.oxml import OxmlElement
|
||||
from docx.oxml.ns import qn
|
||||
from docx.shared import Cm, Pt, RGBColor
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
REPORT_MD = ROOT / "report" / "REPORT.md"
|
||||
BUILD = ROOT / "report" / "build"
|
||||
DOCX_OUT = ROOT / "report" / "REPORT.docx"
|
||||
PDF_OUT = ROOT / "report" / "REPORT.pdf"
|
||||
|
||||
BODY_FONT = "Times New Roman"
|
||||
HEAD_FONT = "Times New Roman"
|
||||
MONO_FONT = "Liberation Mono"
|
||||
INK = RGBColor(0x1A, 0x1A, 0x1A)
|
||||
MUTED = RGBColor(0x55, 0x55, 0x55)
|
||||
ACCENT = RGBColor(0x5B, 0x3A, 0x1E)
|
||||
|
||||
TABLE_WIDTHS = [
|
||||
(2.8, 13.8),
|
||||
(11.6, 5.0),
|
||||
(4.6, 12.0),
|
||||
(3.4, 13.2),
|
||||
(5.6, 11.0),
|
||||
(7.6, 3.4, 3.4, 2.2),
|
||||
(9.6, 7.0),
|
||||
(10.0, 6.6),
|
||||
(11.6, 5.0),
|
||||
]
|
||||
|
||||
TABLE_CAPTIONS = [
|
||||
"Гипотезы исследования и способ их проверки",
|
||||
"Характеристики корпуса",
|
||||
"Группы ручных стилометрических признаков",
|
||||
"Варианты латентно-семантического анализа",
|
||||
"Метрики качества и их назначение",
|
||||
"Сравнение конфигураций классификаторов",
|
||||
"Вклад групп признаков",
|
||||
"Контроль содержания: исходный и маскированный текст",
|
||||
"Качество классификации по длине реплики",
|
||||
]
|
||||
|
||||
|
||||
def run(cmd: list[str], cwd: Path | None = None) -> None:
|
||||
print("+", " ".join(cmd))
|
||||
subprocess.run(cmd, cwd=cwd, check=True)
|
||||
|
||||
|
||||
def set_run_font(run, name: str, size: float | None = None, bold: bool | None = None,
|
||||
italic: bool | None = None, color: RGBColor | None = None) -> None:
|
||||
run.font.name = name
|
||||
run._element.rPr.rFonts.set(qn("w:eastAsia"), name)
|
||||
if size is not None:
|
||||
run.font.size = Pt(size)
|
||||
if bold is not None:
|
||||
run.bold = bold
|
||||
if italic is not None:
|
||||
run.italic = italic
|
||||
if color is not None:
|
||||
run.font.color.rgb = color
|
||||
|
||||
|
||||
def set_style_font(style, name: str, size: float, bold: bool = False,
|
||||
color: RGBColor = INK, italic: bool | None = None) -> None:
|
||||
style.font.name = name
|
||||
style.font.size = Pt(size)
|
||||
style.font.bold = bold
|
||||
style.font.color.rgb = color
|
||||
if italic is not None:
|
||||
style.font.italic = italic
|
||||
rpr = style.element.get_or_add_rPr()
|
||||
rfonts = rpr.find(qn("w:rFonts"))
|
||||
if rfonts is None:
|
||||
rfonts = OxmlElement("w:rFonts")
|
||||
rpr.append(rfonts)
|
||||
for attr in ("w:ascii", "w:hAnsi", "w:eastAsia", "w:cs"):
|
||||
rfonts.set(qn(attr), name)
|
||||
|
||||
|
||||
def set_paragraph_spacing(style, before: float = 0, after: float = 0,
|
||||
line: float = 1.5) -> None:
|
||||
pf = style.paragraph_format
|
||||
pf.space_before = Pt(before)
|
||||
pf.space_after = Pt(after)
|
||||
pf.line_spacing = line
|
||||
|
||||
|
||||
def shade(cell, fill: str) -> None:
|
||||
tc_pr = cell._tc.get_or_add_tcPr()
|
||||
shd = OxmlElement("w:shd")
|
||||
shd.set(qn("w:val"), "clear")
|
||||
shd.set(qn("w:color"), "auto")
|
||||
shd.set(qn("w:fill"), fill)
|
||||
tc_pr.append(shd)
|
||||
|
||||
|
||||
def set_cell_margins(table, top=60, bottom=60, left=90, right=90) -> None:
|
||||
tbl_pr = table._tbl.tblPr
|
||||
mar = OxmlElement("w:tblCellMar")
|
||||
for edge, value in (("top", top), ("left", left), ("bottom", bottom), ("right", right)):
|
||||
el = OxmlElement(f"w:{edge}")
|
||||
el.set(qn("w:w"), str(value))
|
||||
el.set(qn("w:type"), "dxa")
|
||||
mar.append(el)
|
||||
tbl_pr.append(mar)
|
||||
|
||||
|
||||
def set_repeat_header(row) -> None:
|
||||
tr_pr = row._tr.get_or_add_trPr()
|
||||
el = OxmlElement("w:tblHeader")
|
||||
el.set(qn("w:val"), "true")
|
||||
tr_pr.append(el)
|
||||
cant_split = OxmlElement("w:cantSplit")
|
||||
cant_split.set(qn("w:val"), "true")
|
||||
tr_pr.append(cant_split)
|
||||
|
||||
|
||||
def set_table_borders(table, color: str = "B8A88F", size: int = 4) -> None:
|
||||
tbl_pr = table._tbl.tblPr
|
||||
borders = OxmlElement("w:tblBorders")
|
||||
for edge in ("top", "left", "bottom", "right", "insideH", "insideV"):
|
||||
el = OxmlElement(f"w:{edge}")
|
||||
el.set(qn("w:val"), "single")
|
||||
el.set(qn("w:sz"), str(size))
|
||||
el.set(qn("w:space"), "0")
|
||||
el.set(qn("w:color"), color)
|
||||
borders.append(el)
|
||||
tbl_pr.append(borders)
|
||||
|
||||
|
||||
def add_page_field(paragraph, total: bool = False) -> None:
|
||||
run = paragraph.add_run()
|
||||
fld_char_begin = OxmlElement("w:fldChar")
|
||||
fld_char_begin.set(qn("w:fldCharType"), "begin")
|
||||
instr_text = OxmlElement("w:instrText")
|
||||
instr_text.set(qn("xml:space"), "preserve")
|
||||
instr_text.text = "NUMPAGES" if total else "PAGE"
|
||||
fld_char_end = OxmlElement("w:fldChar")
|
||||
fld_char_end.set(qn("w:fldCharType"), "end")
|
||||
run._r.append(fld_char_begin)
|
||||
run._r.append(instr_text)
|
||||
run._r.append(fld_char_end)
|
||||
|
||||
|
||||
def add_bottom_border(paragraph, color: str = "5B3A1E", size: int = 6) -> None:
|
||||
p_pr = paragraph._p.get_or_add_pPr()
|
||||
borders = OxmlElement("w:pBdr")
|
||||
bottom = OxmlElement("w:bottom")
|
||||
bottom.set(qn("w:val"), "single")
|
||||
bottom.set(qn("w:sz"), str(size))
|
||||
bottom.set(qn("w:space"), "4")
|
||||
bottom.set(qn("w:color"), color)
|
||||
borders.append(bottom)
|
||||
p_pr.append(borders)
|
||||
|
||||
|
||||
def add_left_border(paragraph, color: str = "B8A88F", size: int = 18) -> None:
|
||||
p_pr = paragraph._p.get_or_add_pPr()
|
||||
borders = OxmlElement("w:pBdr")
|
||||
left = OxmlElement("w:left")
|
||||
left.set(qn("w:val"), "single")
|
||||
left.set(qn("w:sz"), str(size))
|
||||
left.set(qn("w:space"), "8")
|
||||
left.set(qn("w:color"), color)
|
||||
borders.append(left)
|
||||
p_pr.append(borders)
|
||||
|
||||
|
||||
def add_page_break(paragraph) -> None:
|
||||
run = paragraph.add_run()
|
||||
run.add_break(WD_BREAK.PAGE)
|
||||
|
||||
|
||||
def style_document(doc) -> None:
|
||||
section = doc.sections[0]
|
||||
section.page_width = Cm(21.0)
|
||||
section.page_height = Cm(29.7)
|
||||
section.left_margin = Cm(2.6)
|
||||
section.right_margin = Cm(1.8)
|
||||
section.top_margin = Cm(2.0)
|
||||
section.bottom_margin = Cm(2.0)
|
||||
section.header_distance = Cm(1.0)
|
||||
section.footer_distance = Cm(1.0)
|
||||
|
||||
styles = doc.styles
|
||||
|
||||
normal = styles["Normal"]
|
||||
set_style_font(normal, BODY_FONT, 12)
|
||||
normal.paragraph_format.alignment = WD_ALIGN_PARAGRAPH.JUSTIFY
|
||||
normal.paragraph_format.first_line_indent = Cm(1.25)
|
||||
normal.paragraph_format.line_spacing = 1.5
|
||||
normal.paragraph_format.space_before = Pt(0)
|
||||
normal.paragraph_format.space_after = Pt(0)
|
||||
|
||||
for name in ("Body Text", "First Paragraph"):
|
||||
st = styles[name]
|
||||
set_style_font(st, BODY_FONT, 12)
|
||||
st.paragraph_format.alignment = WD_ALIGN_PARAGRAPH.JUSTIFY
|
||||
st.paragraph_format.first_line_indent = Cm(1.25)
|
||||
st.paragraph_format.line_spacing = 1.5
|
||||
st.paragraph_format.space_before = Pt(0)
|
||||
st.paragraph_format.space_after = Pt(6)
|
||||
|
||||
styles["First Paragraph"].paragraph_format.space_before = Pt(0)
|
||||
|
||||
title = styles["Title"]
|
||||
set_style_font(title, HEAD_FONT, 24, bold=True, color=ACCENT)
|
||||
title.paragraph_format.alignment = WD_ALIGN_PARAGRAPH.CENTER
|
||||
title.paragraph_format.space_before = Pt(150)
|
||||
title.paragraph_format.space_after = Pt(12)
|
||||
title.paragraph_format.line_spacing = 1.1
|
||||
|
||||
subtitle = styles["Subtitle"]
|
||||
set_style_font(subtitle, HEAD_FONT, 15, bold=False, color=INK)
|
||||
subtitle.paragraph_format.alignment = WD_ALIGN_PARAGRAPH.CENTER
|
||||
subtitle.paragraph_format.space_before = Pt(0)
|
||||
subtitle.paragraph_format.space_after = Pt(28)
|
||||
subtitle.paragraph_format.line_spacing = 1.2
|
||||
|
||||
author = styles["Author"]
|
||||
set_style_font(author, BODY_FONT, 13, color=INK)
|
||||
author.paragraph_format.alignment = WD_ALIGN_PARAGRAPH.CENTER
|
||||
author.paragraph_format.space_before = Pt(0)
|
||||
author.paragraph_format.space_after = Pt(6)
|
||||
|
||||
date = styles["Date"]
|
||||
set_style_font(date, BODY_FONT, 13, color=MUTED)
|
||||
date.paragraph_format.alignment = WD_ALIGN_PARAGRAPH.CENTER
|
||||
date.paragraph_format.space_before = Pt(0)
|
||||
date.paragraph_format.space_after = Pt(0)
|
||||
|
||||
for name, size, before, after in (
|
||||
("Heading 1", 17, 0, 10),
|
||||
("Heading 2", 14, 14, 6),
|
||||
("Heading 3", 12.5, 12, 4),
|
||||
):
|
||||
st = styles[name]
|
||||
set_style_font(st, HEAD_FONT, size, bold=True, color=INK)
|
||||
st.paragraph_format.alignment = WD_ALIGN_PARAGRAPH.LEFT
|
||||
st.paragraph_format.first_line_indent = Cm(0)
|
||||
st.paragraph_format.space_before = Pt(before)
|
||||
st.paragraph_format.space_after = Pt(after)
|
||||
st.paragraph_format.line_spacing = 1.15
|
||||
st.paragraph_format.keep_with_next = True
|
||||
|
||||
styles["Heading 1"].paragraph_format.page_break_before = False
|
||||
styles["Heading 1"].paragraph_format.space_before = Pt(20)
|
||||
|
||||
block = styles["Block Text"]
|
||||
set_style_font(block, BODY_FONT, 11.5, italic=False)
|
||||
block.paragraph_format.left_indent = Cm(1.0)
|
||||
block.paragraph_format.right_indent = Cm(0.5)
|
||||
block.paragraph_format.first_line_indent = Cm(0)
|
||||
block.paragraph_format.line_spacing = 1.3
|
||||
block.paragraph_format.space_before = Pt(6)
|
||||
block.paragraph_format.space_after = Pt(8)
|
||||
block.paragraph_format.keep_together = True
|
||||
|
||||
source = styles["Source Code"]
|
||||
set_style_font(source, MONO_FONT, 9)
|
||||
source.paragraph_format.left_indent = Cm(0.6)
|
||||
source.paragraph_format.first_line_indent = Cm(0)
|
||||
source.paragraph_format.line_spacing = 1.0
|
||||
source.paragraph_format.space_before = Pt(4)
|
||||
source.paragraph_format.space_after = Pt(4)
|
||||
source.paragraph_format.alignment = WD_ALIGN_PARAGRAPH.LEFT
|
||||
source.paragraph_format.keep_together = True
|
||||
|
||||
try:
|
||||
verbatim = styles["Verbatim Char"]
|
||||
set_style_font(verbatim, MONO_FONT, 9.5)
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
for name in ("Image Caption", "Caption"):
|
||||
st = styles[name]
|
||||
set_style_font(st, BODY_FONT, 10, italic=True, color=MUTED)
|
||||
st.paragraph_format.alignment = WD_ALIGN_PARAGRAPH.CENTER
|
||||
st.paragraph_format.first_line_indent = Cm(0)
|
||||
st.paragraph_format.line_spacing = 1.15
|
||||
st.paragraph_format.space_before = Pt(4)
|
||||
st.paragraph_format.space_after = Pt(12)
|
||||
|
||||
for name in ("Table Caption",):
|
||||
st = styles[name]
|
||||
set_style_font(st, BODY_FONT, 10, italic=False, color=INK)
|
||||
st.paragraph_format.alignment = WD_ALIGN_PARAGRAPH.CENTER
|
||||
st.paragraph_format.first_line_indent = Cm(0)
|
||||
st.paragraph_format.line_spacing = 1.15
|
||||
st.paragraph_format.space_before = Pt(4)
|
||||
st.paragraph_format.space_after = Pt(12)
|
||||
|
||||
st.paragraph_format.alignment = WD_ALIGN_PARAGRAPH.LEFT
|
||||
st.paragraph_format.first_line_indent = Cm(0)
|
||||
st.paragraph_format.line_spacing = 1.15
|
||||
st.paragraph_format.space_before = Pt(10)
|
||||
st.paragraph_format.space_after = Pt(4)
|
||||
st.paragraph_format.keep_with_next = True
|
||||
|
||||
|
||||
def add_header_footer(doc) -> None:
|
||||
section = doc.sections[0]
|
||||
|
||||
header = section.header
|
||||
header.is_linked_to_previous = False
|
||||
hp = header.paragraphs[0]
|
||||
hp.alignment = WD_ALIGN_PARAGRAPH.RIGHT
|
||||
hp.paragraph_format.space_after = Pt(0)
|
||||
run = hp.add_run("Стилометрия реплик Stardew Valley · отчёт по лабораторной работе №1")
|
||||
set_run_font(run, BODY_FONT, 8.5, color=MUTED)
|
||||
add_bottom_border(hp, color="D8CBB8", size=4)
|
||||
|
||||
footer = section.footer
|
||||
footer.is_linked_to_previous = False
|
||||
fp = footer.paragraphs[0]
|
||||
fp.alignment = WD_ALIGN_PARAGRAPH.CENTER
|
||||
fp.paragraph_format.space_before = Pt(0)
|
||||
fp.paragraph_format.space_after = Pt(0)
|
||||
run = fp.add_run("Стр. ")
|
||||
set_run_font(run, BODY_FONT, 9, color=MUTED)
|
||||
add_page_field(fp)
|
||||
for r in fp.runs:
|
||||
set_run_font(r, BODY_FONT, 9, color=MUTED)
|
||||
run = fp.add_run(" из ")
|
||||
set_run_font(run, BODY_FONT, 9, color=MUTED)
|
||||
add_page_field(fp, total=True)
|
||||
for r in fp.runs:
|
||||
set_run_font(r, BODY_FONT, 9, color=MUTED)
|
||||
|
||||
|
||||
def add_title_page_notes(doc) -> None:
|
||||
"""Добавляет подпись направления под титульным блоком и разрыв страницы."""
|
||||
date_para = None
|
||||
for p in doc.paragraphs:
|
||||
if p.style.name == "Date":
|
||||
date_para = p
|
||||
break
|
||||
if date_para is None:
|
||||
return
|
||||
anchor = date_para
|
||||
note = anchor.insert_paragraph_before()
|
||||
note.alignment = WD_ALIGN_PARAGRAPH.CENTER
|
||||
note.paragraph_format.first_line_indent = Cm(0)
|
||||
note.paragraph_format.space_before = Pt(18)
|
||||
run = note.add_run("Направление: автоматический анализ текстов\n")
|
||||
set_run_font(run, BODY_FONT, 11, color=MUTED)
|
||||
run = note.add_run("Корпус: Stardew Valley · 2005 реплик · 34 персонажа")
|
||||
set_run_font(run, BODY_FONT, 11, color=MUTED)
|
||||
|
||||
spacer = doc.add_paragraph()
|
||||
spacer.paragraph_format.space_after = Pt(0)
|
||||
add_page_break(spacer)
|
||||
date_para._p.addnext(spacer._p)
|
||||
|
||||
|
||||
def number_figures(doc) -> None:
|
||||
n = 0
|
||||
for p in doc.paragraphs:
|
||||
if p.style.name == "Image Caption" and p.text.strip():
|
||||
n += 1
|
||||
p.text = f"Рисунок {n}. {p.text.strip()}"
|
||||
for run in p.runs:
|
||||
set_run_font(run, BODY_FONT, 10, italic=True, color=MUTED)
|
||||
|
||||
|
||||
def add_table_captions(doc) -> None:
|
||||
for idx, table in enumerate(doc.tables):
|
||||
if idx >= len(TABLE_CAPTIONS):
|
||||
break
|
||||
para = doc.add_paragraph(style="Table Caption")
|
||||
table._tbl.addprevious(para._p)
|
||||
run = para.add_run(f"Таблица {idx + 1}. {TABLE_CAPTIONS[idx]}")
|
||||
set_run_font(run, BODY_FONT, 10, bold=True, color=INK)
|
||||
para.paragraph_format.first_line_indent = Cm(0)
|
||||
para.paragraph_format.space_before = Pt(10)
|
||||
para.paragraph_format.space_after = Pt(4)
|
||||
para.paragraph_format.keep_with_next = True
|
||||
|
||||
|
||||
def set_column_widths(table, widths_cm: tuple[float, ...]) -> None:
|
||||
table.autofit = False
|
||||
tbl_pr = table._tbl.tblPr
|
||||
layout = OxmlElement("w:tblLayout")
|
||||
layout.set(qn("w:type"), "fixed")
|
||||
tbl_pr.append(layout)
|
||||
grid = table._tbl.tblGrid
|
||||
for idx, width in enumerate(widths_cm):
|
||||
if idx < len(grid.gridCol_lst):
|
||||
grid.gridCol_lst[idx].set(qn("w:w"), str(int(Cm(width).twips)))
|
||||
for row in table.rows:
|
||||
for idx, width in enumerate(widths_cm):
|
||||
if idx < len(row.cells):
|
||||
row.cells[idx].width = Cm(width)
|
||||
|
||||
|
||||
def style_tables(doc) -> None:
|
||||
for t_idx, table in enumerate(doc.tables):
|
||||
table.alignment = WD_TABLE_ALIGNMENT.CENTER
|
||||
table.autofit = True
|
||||
set_table_borders(table)
|
||||
set_cell_margins(table)
|
||||
for r_idx, row in enumerate(table.rows):
|
||||
if r_idx == 0:
|
||||
set_repeat_header(row)
|
||||
else:
|
||||
tr_pr = row._tr.get_or_add_trPr()
|
||||
cant_split = OxmlElement("w:cantSplit")
|
||||
cant_split.set(qn("w:val"), "true")
|
||||
tr_pr.append(cant_split)
|
||||
for cell in row.cells:
|
||||
for p in cell.paragraphs:
|
||||
p.paragraph_format.first_line_indent = Cm(0)
|
||||
p.paragraph_format.space_before = Pt(1)
|
||||
p.paragraph_format.space_after = Pt(1)
|
||||
p.paragraph_format.line_spacing = 1.0
|
||||
if r_idx < len(table.rows) - 1:
|
||||
p.paragraph_format.keep_with_next = True
|
||||
for run in p.runs:
|
||||
set_run_font(
|
||||
run, BODY_FONT, 9.5 if len(table.columns) >= 5 else 10,
|
||||
bold=(r_idx == 0),
|
||||
)
|
||||
if r_idx == 0:
|
||||
shade(cell, "F1E8D8")
|
||||
# Ширина таблицы по ширине полей.
|
||||
tbl_pr = table._tbl.tblPr
|
||||
tbl_w = OxmlElement("w:tblW")
|
||||
tbl_w.set(qn("w:type"), "pct")
|
||||
tbl_w.set(qn("w:w"), "100%")
|
||||
tbl_pr.append(tbl_w)
|
||||
if t_idx < len(TABLE_WIDTHS) and len(TABLE_WIDTHS[t_idx]) == len(table.columns):
|
||||
set_column_widths(table, TABLE_WIDTHS[t_idx])
|
||||
# Небольшой отступ между таблицей и следующим абзацем.
|
||||
spacer = doc.add_paragraph()
|
||||
spacer.paragraph_format.space_before = Pt(0)
|
||||
spacer.paragraph_format.space_after = Pt(0)
|
||||
spacer.paragraph_format.line_spacing = 1.0
|
||||
sr = spacer.add_run("")
|
||||
sr.font.size = Pt(4)
|
||||
table._tbl.addnext(spacer._p)
|
||||
|
||||
|
||||
def style_blockquotes(doc) -> None:
|
||||
for p in doc.paragraphs:
|
||||
if p.style.name == "Block Text":
|
||||
add_left_border(p)
|
||||
|
||||
|
||||
def add_math_font_note(doc) -> None:
|
||||
"""OMML-формулы наследуют Cambria Math; явно задаём её для совместимости."""
|
||||
for r in doc.element.body.iter(qn("m:r")):
|
||||
rpr = r.find(qn("w:rPr"))
|
||||
if rpr is None:
|
||||
rpr = OxmlElement("w:rPr")
|
||||
r.insert(0, rpr)
|
||||
rfonts = rpr.find(qn("w:rFonts"))
|
||||
if rfonts is None:
|
||||
rfonts = OxmlElement("w:rFonts")
|
||||
rpr.append(rfonts)
|
||||
rfonts.set(qn("w:ascii"), "Cambria Math")
|
||||
rfonts.set(qn("w:hAnsi"), "Cambria Math")
|
||||
|
||||
|
||||
def build_docx() -> None:
|
||||
BUILD.mkdir(parents=True, exist_ok=True)
|
||||
raw = BUILD / "REPORT_raw.docx"
|
||||
run([
|
||||
"pandoc", str(REPORT_MD),
|
||||
"-f", "markdown+tex_math_dollars+tex_math_single_backslash+raw_tex",
|
||||
"-t", "docx",
|
||||
"-o", str(raw),
|
||||
f"--resource-path={REPORT_MD.parent}",
|
||||
])
|
||||
doc = docx.Document(raw)
|
||||
style_document(doc)
|
||||
section = doc.sections[0]
|
||||
section.different_first_page_header_footer = True
|
||||
add_header_footer(doc)
|
||||
# Титульный лист без колонтитулов.
|
||||
for part in (section.first_page_header, section.first_page_footer):
|
||||
part.is_linked_to_previous = False
|
||||
for paragraph in part.paragraphs:
|
||||
paragraph.text = ""
|
||||
add_title_page_notes(doc)
|
||||
number_figures(doc)
|
||||
add_table_captions(doc)
|
||||
style_tables(doc)
|
||||
style_blockquotes(doc)
|
||||
add_math_font_note(doc)
|
||||
doc.core_properties.title = "Отчёт по лабораторной работе №1"
|
||||
doc.core_properties.subject = "Классификация текстов на основе стилометрических характеристик"
|
||||
doc.core_properties.author = "А. А. Аладьина, В. Н. Копнин, М. В. Савостиков"
|
||||
doc.core_properties.language = "ru-RU"
|
||||
doc.save(DOCX_OUT)
|
||||
print(f"DOCX: {DOCX_OUT}")
|
||||
|
||||
|
||||
def build_pdf() -> None:
|
||||
if shutil.which("libreoffice") is None:
|
||||
raise SystemExit("libreoffice не найден: PDF не собран")
|
||||
run([
|
||||
"libreoffice", "--headless", "--convert-to", "pdf",
|
||||
"--outdir", str(PDF_OUT.parent), str(DOCX_OUT),
|
||||
])
|
||||
produced = PDF_OUT.parent / (DOCX_OUT.stem + ".pdf")
|
||||
if produced != PDF_OUT and produced.exists():
|
||||
produced.replace(PDF_OUT)
|
||||
print(f"PDF: {PDF_OUT}")
|
||||
|
||||
|
||||
def check_pdf() -> None:
|
||||
try:
|
||||
import fitz # PyMuPDF
|
||||
except ImportError:
|
||||
print("PyMuPDF не установлен: пропускаю проверку PDF")
|
||||
return
|
||||
doc = fitz.open(PDF_OUT)
|
||||
print(f"PDF страниц: {doc.page_count}")
|
||||
text = "\n".join(page.get_text() for page in doc)
|
||||
required = [
|
||||
"Аннотация", "Постановка задачи", "Корпус", "Стилометрические признаки",
|
||||
"Классификаторы", "Схема верификации", "Результаты", "Обсуждение",
|
||||
"Ограничения", "Выводы", "Воспроизводимость",
|
||||
]
|
||||
missing = [item for item in required if item.lower() not in text.lower()]
|
||||
if missing:
|
||||
print("Не найдены разделы:", ", ".join(missing))
|
||||
raise SystemExit(1)
|
||||
print("Все основные разделы найдены в PDF")
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("--skip-pdf", action="store_true", help="только DOCX")
|
||||
args = parser.parse_args()
|
||||
|
||||
if not REPORT_MD.exists():
|
||||
raise SystemExit(f"нет файла {REPORT_MD}")
|
||||
build_docx()
|
||||
if not args.skip_pdf:
|
||||
build_pdf()
|
||||
check_pdf()
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
@@ -1,208 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Собирает итоговый ZIP-архив лабораторной работы.
|
||||
|
||||
Запуск из каталога lab1:
|
||||
|
||||
python scripts/package_submission.py
|
||||
|
||||
Скрипт копирует только нужные файлы во временный каталог, формирует
|
||||
MANIFEST.txt и CHECKSUMS.sha256, затем упаковывает всё в
|
||||
dist/nlp_lab1_submission_YYYYMMDD.zip. Рядом кладётся .sha256 самого архива.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import hashlib
|
||||
import os
|
||||
import shutil
|
||||
import zipfile
|
||||
from datetime import date
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
DIST = ROOT / "dist"
|
||||
DEFAULT_NAME = f"nlp_lab1_submission_{date.today():%Y%m%d}"
|
||||
|
||||
EXCLUDE_PREFIXES = (
|
||||
"data/processed/",
|
||||
"features/",
|
||||
"results/",
|
||||
"nltk_data/",
|
||||
".venv/",
|
||||
".git/",
|
||||
"presentation/preview/",
|
||||
"report/build/",
|
||||
".pytest_cache/",
|
||||
"dist/",
|
||||
"exports/nlp_lab1_results_",
|
||||
"exports/nlp_lab1_final_results_20261026_120000.tar.gz.sha256",
|
||||
)
|
||||
|
||||
EXCLUDE_NAMES = {
|
||||
"stardew_stylometry.bak",
|
||||
"START_HERE.md",
|
||||
"ARCHIVE_README.md",
|
||||
}
|
||||
|
||||
LATEX_AUX_SUFFIXES = {".aux", ".log", ".nav", ".out", ".snm", ".toc"}
|
||||
|
||||
KEY_FILES = [
|
||||
"lab1/report/REPORT.pdf",
|
||||
"lab1/report/REPORT.docx",
|
||||
"lab1/report/REPORT.md",
|
||||
"lab1/presentation/stardew_stylometry.pdf",
|
||||
"lab1/presentation/stardew_stylometry.pptx",
|
||||
"lab1/presentation/latex/stardew_stylometry.pdf",
|
||||
"lab1/presentation/latex/stardew_stylometry.tex",
|
||||
"lab1/FINAL_RESULTS.csv",
|
||||
"lab1/data/dialogues.csv",
|
||||
"lab1/exports/nlp_lab1_final_results_20261026_120000.tar.gz",
|
||||
]
|
||||
|
||||
|
||||
def sha256(path: Path) -> str:
|
||||
h = hashlib.sha256()
|
||||
with path.open("rb") as fh:
|
||||
for chunk in iter(lambda: fh.read(1024 * 1024), b""):
|
||||
h.update(chunk)
|
||||
return h.hexdigest()
|
||||
|
||||
|
||||
def should_copy(rel: Path) -> bool:
|
||||
posix = rel.as_posix()
|
||||
if rel.name in EXCLUDE_NAMES:
|
||||
return False
|
||||
if "__pycache__" in rel.parts or rel.suffix == ".pyc":
|
||||
return False
|
||||
if rel.suffix in LATEX_AUX_SUFFIXES:
|
||||
return False
|
||||
if any(posix == prefix.rstrip("/") or posix.startswith(prefix)
|
||||
for prefix in EXCLUDE_PREFIXES):
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def copy_tree(stage: Path) -> int:
|
||||
target_root = stage / "lab1"
|
||||
count = 0
|
||||
for path in sorted(ROOT.rglob("*")):
|
||||
if not path.is_file():
|
||||
continue
|
||||
rel = path.relative_to(ROOT)
|
||||
if not should_copy(rel):
|
||||
continue
|
||||
dst = target_root / rel
|
||||
dst.parent.mkdir(parents=True, exist_ok=True)
|
||||
shutil.copy2(path, dst)
|
||||
count += 1
|
||||
return count
|
||||
|
||||
|
||||
def write_manifest(stage: Path) -> int:
|
||||
files = sorted(p for p in stage.rglob("*") if p.is_file())
|
||||
lines = [
|
||||
"Архив: " + stage.name,
|
||||
f"Файлов в манифесте: {len(files)} (сам MANIFEST.txt не включён)",
|
||||
"",
|
||||
f"{'SHA-256':64} {'Размер':>12} Путь",
|
||||
"-" * 100,
|
||||
]
|
||||
total = 0
|
||||
for path in files:
|
||||
size = path.stat().st_size
|
||||
total += size
|
||||
rel = path.relative_to(stage).as_posix()
|
||||
lines.append(f"{sha256(path)} {size:>12} {rel}")
|
||||
lines.append("-" * 100)
|
||||
lines.append(f"Итого байт: {total}")
|
||||
(stage / "MANIFEST.txt").write_text("\n".join(lines) + "\n", encoding="utf-8")
|
||||
return len(files)
|
||||
|
||||
|
||||
def write_checksums(stage: Path) -> None:
|
||||
lines = []
|
||||
for rel_name in KEY_FILES:
|
||||
path = stage / rel_name
|
||||
if path.exists():
|
||||
lines.append(f"{sha256(path)} {rel_name}")
|
||||
else:
|
||||
print(f"ВНИМАНИЕ: нет ключевого файла {rel_name}")
|
||||
(stage / "CHECKSUMS.sha256").write_text("\n".join(lines) + "\n", encoding="utf-8")
|
||||
|
||||
|
||||
def zip_stage(stage: Path, archive: Path) -> None:
|
||||
archive.parent.mkdir(parents=True, exist_ok=True)
|
||||
with zipfile.ZipFile(archive, "w", compression=zipfile.ZIP_DEFLATED,
|
||||
compresslevel=6) as zf:
|
||||
for path in sorted(stage.rglob("*")):
|
||||
if not path.is_file():
|
||||
continue
|
||||
rel = path.relative_to(stage).as_posix()
|
||||
arcname = f"{stage.name}/{rel}"
|
||||
info = zipfile.ZipInfo.from_file(path, arcname)
|
||||
info.compress_type = zipfile.ZIP_DEFLATED
|
||||
with path.open("rb") as src, zf.open(info, "w") as dst:
|
||||
shutil.copyfileobj(src, dst)
|
||||
|
||||
|
||||
def human(n: int) -> str:
|
||||
for unit in ("Б", "КБ", "МБ", "ГБ"):
|
||||
if n < 1024 or unit == "ГБ":
|
||||
return f"{n:.1f} {unit}" if unit != "Б" else f"{n} {unit}"
|
||||
n /= 1024
|
||||
return f"{n} Б"
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("--name", default=DEFAULT_NAME, help="имя каталога и ZIP")
|
||||
parser.add_argument("--keep-stage", action="store_true",
|
||||
help="не удалять промежуточный каталог dist/stage")
|
||||
args = parser.parse_args()
|
||||
|
||||
name = args.name
|
||||
stage = DIST / "stage" / name
|
||||
if stage.exists():
|
||||
shutil.rmtree(stage)
|
||||
stage.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
print(f"1/4. Копирование файлов в {stage} ...")
|
||||
copied = copy_tree(stage)
|
||||
shutil.copy2(ROOT / "START_HERE.md", stage / "START_HERE.md")
|
||||
shutil.copy2(ROOT / "ARCHIVE_README.md", stage / "ARCHIVE_README.md")
|
||||
|
||||
print("2/4. Формирование CHECKSUMS.sha256 ...")
|
||||
write_checksums(stage)
|
||||
|
||||
print("3/4. Формирование MANIFEST.txt ...")
|
||||
total = write_manifest(stage)
|
||||
|
||||
archive = DIST / f"{name}.zip"
|
||||
print(f"4/4. Упаковка {archive} ...")
|
||||
zip_stage(stage, archive)
|
||||
|
||||
digest = sha256(archive)
|
||||
(DIST / f"{name}.zip.sha256").write_text(
|
||||
f"{digest} {archive.name}\n", encoding="utf-8"
|
||||
)
|
||||
|
||||
print()
|
||||
print(f"Файлов в lab1: {copied}")
|
||||
print(f"Всего файлов в архиве: {total + 1}")
|
||||
print(f"Размер ZIP: {human(archive.stat().st_size)}")
|
||||
print(f"SHA-256 ZIP: {digest}")
|
||||
print(f"Архив: {archive}")
|
||||
print(f"Контрольная сумма: {archive}.sha256")
|
||||
|
||||
if not args.keep_stage:
|
||||
shutil.rmtree(stage, ignore_errors=True)
|
||||
try:
|
||||
(DIST / "stage").rmdir()
|
||||
except OSError:
|
||||
pass
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -1,664 +0,0 @@
|
||||
"""Дополнительные эксперименты для итогового отчёта.
|
||||
|
||||
Стадии:
|
||||
- nested: вложенный выбор конфигурации признаков и модели;
|
||||
- topic: контроль содержания через маскирование содержательных слов;
|
||||
- calibration: коррекция априорных вероятностей классов;
|
||||
- ensemble: мягкое голосование и stacking;
|
||||
- extra: дополнительные конфигурации и отложенная выборка.
|
||||
|
||||
Запуск:
|
||||
python scripts/run_final_experiments.py --stages nested topic calibration ensemble extra
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import re
|
||||
import sys
|
||||
from dataclasses import replace
|
||||
from pathlib import Path
|
||||
from typing import Any, Iterable, Sequence
|
||||
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
from sklearn.ensemble import VotingClassifier, StackingClassifier
|
||||
from sklearn.linear_model import LogisticRegression
|
||||
from sklearn.metrics import accuracy_score, confusion_matrix, f1_score
|
||||
from sklearn.model_selection import StratifiedKFold, train_test_split
|
||||
|
||||
PROJECT_ROOT = Path(__file__).resolve().parents[1]
|
||||
if str(PROJECT_ROOT) not in sys.path:
|
||||
sys.path.insert(0, str(PROJECT_ROOT))
|
||||
|
||||
from stylometry.cv import run_repeated_cv
|
||||
from stylometry.datasets import load_feature_frame
|
||||
from stylometry.evaluation import (
|
||||
bootstrap_ci,
|
||||
classification_metrics,
|
||||
confusion_matrix_frame,
|
||||
per_class_report,
|
||||
top_confusions,
|
||||
)
|
||||
from stylometry.pipelines import (
|
||||
FeatureConfig,
|
||||
build_pipeline,
|
||||
preset_config,
|
||||
)
|
||||
|
||||
DEFAULT_OUT = PROJECT_ROOT / "results" / "final"
|
||||
|
||||
|
||||
def save_json(data: Any, path: Path) -> None:
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
with path.open("w", encoding="utf-8") as stream:
|
||||
json.dump(data, stream, ensure_ascii=False, indent=2, default=str)
|
||||
|
||||
|
||||
def save_cv(result, directory: Path, title: str, config: dict[str, Any]) -> None:
|
||||
directory.mkdir(parents=True, exist_ok=True)
|
||||
result.fold_metrics.to_csv(directory / "fold_metrics.csv", index=False, encoding="utf-8-sig")
|
||||
result.oof.to_csv(directory / "oof_predictions.csv", index=False, encoding="utf-8-sig")
|
||||
pooled, bootstrap = result.pooled_summary()
|
||||
pooled.to_csv(directory / "pooled_metrics.csv", index=False, encoding="utf-8-sig")
|
||||
save_json(bootstrap, directory / "bootstrap_ci.json")
|
||||
first = result.oof[result.oof["repeat"] == result.oof["repeat"].min()]
|
||||
per_class_report(first["y_true"], first["y_pred"], result.labels).to_csv(
|
||||
directory / "per_class_report.csv", index=False, encoding="utf-8-sig"
|
||||
)
|
||||
matrix = confusion_matrix_frame(first["y_true"], first["y_pred"], result.labels)
|
||||
matrix.to_csv(directory / "confusion_matrix.csv", encoding="utf-8-sig")
|
||||
top_confusions(first["y_true"], first["y_pred"], result.labels).to_csv(
|
||||
directory / "top_confusions.csv", index=False, encoding="utf-8-sig"
|
||||
)
|
||||
summary = {
|
||||
"title": title,
|
||||
"config": config,
|
||||
"fold_mean_macro_f1": float(result.fold_metrics["macro_f1"].mean()),
|
||||
"fold_std_macro_f1": float(result.fold_metrics["macro_f1"].std(ddof=1)),
|
||||
"n_splits": result.n_splits,
|
||||
"n_repeats": result.n_repeats,
|
||||
"bootstrap": bootstrap,
|
||||
}
|
||||
save_json(summary, directory / "summary.json")
|
||||
(directory / "summary.md").write_text(
|
||||
f"# {title}\n\n"
|
||||
f"Macro-F1 по фолдам: {summary['fold_mean_macro_f1']:.4f} "
|
||||
f"± {summary['fold_std_macro_f1']:.4f}\n\n"
|
||||
f"Pooled:\n\n{pooled.to_string(index=False)}\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
|
||||
def lean_config(base: FeatureConfig, lsa_components: int = 300) -> FeatureConfig:
|
||||
groups = tuple(
|
||||
group
|
||||
for group in base.groups
|
||||
if group not in {"function_words", "pos", "vocabulary", "readability"}
|
||||
)
|
||||
return replace(
|
||||
base,
|
||||
groups=groups,
|
||||
lsa_variants=("content",),
|
||||
lsa_components=lsa_components,
|
||||
use_char_ngrams=True,
|
||||
use_pos_ngrams=True,
|
||||
include_lsa=True,
|
||||
)
|
||||
|
||||
|
||||
def masked_frame(frame: pd.DataFrame) -> pd.DataFrame:
|
||||
"""Заменяет содержательные слова на CONTENT, сохраняя служебные слова."""
|
||||
content_tags = {"NOUN", "VERB", "ADJ", "ADV", "NUM"}
|
||||
|
||||
def _mask_row(row: pd.Series) -> str:
|
||||
tokens = list(row["tokens"])
|
||||
tags = list(row["upos"])
|
||||
result = []
|
||||
for token, tag in zip(tokens, tags):
|
||||
if tag in content_tags and re.search(r"[A-Za-z]", str(token)):
|
||||
result.append("CONTENT")
|
||||
else:
|
||||
result.append(str(token))
|
||||
return " ".join(result) if result else "__EMPTY__"
|
||||
|
||||
result = frame.copy()
|
||||
result["masked_text"] = result.apply(_mask_row, axis=1)
|
||||
result["text"] = result["masked_text"]
|
||||
return result
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Nested selection
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def nested_selection(
|
||||
frame: pd.DataFrame,
|
||||
y: np.ndarray,
|
||||
out_dir: Path,
|
||||
folds: int = 5,
|
||||
inner_folds: int = 3,
|
||||
repeats: int = 1,
|
||||
seed: int = 42,
|
||||
) -> None:
|
||||
base = preset_config("stylometry_pos_lsa")
|
||||
candidates: list[tuple[str, FeatureConfig, str]] = [
|
||||
("et_full", base, "extra_trees"),
|
||||
("et_no_lsa", preset_config("stylometry_pos"), "extra_trees"),
|
||||
("et_lean_content300", lean_config(base, 300), "extra_trees"),
|
||||
("logreg_lean_content300", lean_config(base, 300), "logreg"),
|
||||
("rf_char10000", replace(base, char_max_features=10000), "random_forest"),
|
||||
("rf_char15000", replace(base, char_max_features=15000), "random_forest"),
|
||||
("et_char10000", replace(base, char_max_features=10000), "extra_trees"),
|
||||
("et_lean_content400", lean_config(base, 400), "extra_trees"),
|
||||
]
|
||||
labels = np.unique(y)
|
||||
rows: list[dict[str, Any]] = []
|
||||
oof_rows: list[dict[str, Any]] = []
|
||||
selection_rows: list[dict[str, Any]] = []
|
||||
|
||||
for repeat in range(repeats):
|
||||
outer = StratifiedKFold(n_splits=folds, shuffle=True, random_state=seed + repeat)
|
||||
for fold, (train_idx, test_idx) in enumerate(outer.split(frame, y), start=1):
|
||||
print(f"[nested] repeat={repeat} fold={fold}: inner selection", flush=True)
|
||||
inner = StratifiedKFold(
|
||||
n_splits=inner_folds,
|
||||
shuffle=True,
|
||||
random_state=seed + repeat * 100 + fold,
|
||||
)
|
||||
inner_scores: dict[str, list[float]] = {name: [] for name, _, _ in candidates}
|
||||
for inner_train, inner_test in inner.split(frame.iloc[train_idx], y[train_idx]):
|
||||
for name, config, model_name in candidates:
|
||||
pipeline = build_pipeline(config, model_name)
|
||||
pipeline.fit(
|
||||
frame.iloc[train_idx].iloc[inner_train],
|
||||
y[train_idx][inner_train],
|
||||
)
|
||||
prediction = pipeline.predict(
|
||||
frame.iloc[train_idx].iloc[inner_test]
|
||||
)
|
||||
inner_scores[name].append(
|
||||
f1_score(
|
||||
y[train_idx][inner_test],
|
||||
prediction,
|
||||
labels=labels,
|
||||
average="macro",
|
||||
zero_division=0,
|
||||
)
|
||||
)
|
||||
means = {
|
||||
name: float(np.mean(values)) for name, values in inner_scores.items()
|
||||
}
|
||||
best_name = max(means, key=means.get)
|
||||
best_config, best_model = next(
|
||||
(config, model) for name, config, model in candidates if name == best_name
|
||||
)
|
||||
selection_rows.append(
|
||||
{
|
||||
"repeat": repeat,
|
||||
"fold": fold,
|
||||
"selected": best_name,
|
||||
"inner_mean": means[best_name],
|
||||
**{f"inner_{name}": value for name, value in means.items()},
|
||||
}
|
||||
)
|
||||
pipeline = build_pipeline(best_config, best_model)
|
||||
pipeline.fit(frame.iloc[train_idx], y[train_idx])
|
||||
prediction = pipeline.predict(frame.iloc[test_idx])
|
||||
metrics = classification_metrics(
|
||||
y[test_idx], prediction, labels=labels
|
||||
)
|
||||
metrics.update({"repeat": repeat, "fold": fold, "selected": best_name})
|
||||
rows.append(metrics)
|
||||
for sample_index, predicted in zip(test_idx, prediction):
|
||||
oof_rows.append(
|
||||
{
|
||||
"repeat": repeat,
|
||||
"fold": fold,
|
||||
"line_id": int(frame.iloc[sample_index]["line_id"]),
|
||||
"y_true": y[sample_index],
|
||||
"y_pred": predicted,
|
||||
"selected": best_name,
|
||||
}
|
||||
)
|
||||
|
||||
fold_metrics = pd.DataFrame(rows)
|
||||
oof = pd.DataFrame(oof_rows).sort_values(["repeat", "line_id"]).reset_index(drop=True)
|
||||
out_dir.mkdir(parents=True, exist_ok=True)
|
||||
fold_metrics.to_csv(out_dir / "fold_metrics.csv", index=False, encoding="utf-8-sig")
|
||||
oof.to_csv(out_dir / "oof_predictions.csv", index=False, encoding="utf-8-sig")
|
||||
pd.DataFrame(selection_rows).to_csv(
|
||||
out_dir / "selection.csv", index=False, encoding="utf-8-sig"
|
||||
)
|
||||
first = oof[oof["repeat"] == oof["repeat"].min()]
|
||||
per_class_report(first["y_true"], first["y_pred"], labels).to_csv(
|
||||
out_dir / "per_class_report.csv", index=False, encoding="utf-8-sig"
|
||||
)
|
||||
confusion_matrix_frame(first["y_true"], first["y_pred"], labels).to_csv(
|
||||
out_dir / "confusion_matrix.csv", encoding="utf-8-sig"
|
||||
)
|
||||
pooled = []
|
||||
for repeat, group in oof.groupby("repeat"):
|
||||
metrics = classification_metrics(group["y_true"], group["y_pred"], labels=labels)
|
||||
metrics["repeat"] = repeat
|
||||
pooled.append(metrics)
|
||||
pooled_frame = pd.DataFrame(pooled)
|
||||
pooled_frame.to_csv(out_dir / "pooled_metrics.csv", index=False, encoding="utf-8-sig")
|
||||
save_json(
|
||||
{
|
||||
"fold_mean_macro_f1": float(fold_metrics["macro_f1"].mean()),
|
||||
"fold_std_macro_f1": float(fold_metrics["macro_f1"].std(ddof=1)),
|
||||
"pooled": pooled_frame.to_dict(orient="records"),
|
||||
"selection_counts": pd.DataFrame(selection_rows)["selected"].value_counts().to_dict(),
|
||||
},
|
||||
out_dir / "summary.json",
|
||||
)
|
||||
print(
|
||||
f"[nested] macro-F1={fold_metrics['macro_f1'].mean():.4f} "
|
||||
f"± {fold_metrics['macro_f1'].std(ddof=1):.4f}"
|
||||
)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Topic control
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def topic_control(
|
||||
frame: pd.DataFrame,
|
||||
y: np.ndarray,
|
||||
out_dir: Path,
|
||||
folds: int = 5,
|
||||
repeats: int = 3,
|
||||
seed: int = 42,
|
||||
) -> None:
|
||||
style_groups = (
|
||||
"length",
|
||||
"punctuation",
|
||||
"function_words",
|
||||
"pronouns",
|
||||
"pos",
|
||||
"syntax",
|
||||
"dialogue",
|
||||
)
|
||||
config = FeatureConfig(
|
||||
groups=style_groups,
|
||||
use_char_ngrams=True,
|
||||
char_ngram_range=(2, 5),
|
||||
char_max_features=5000,
|
||||
use_pos_ngrams=True,
|
||||
pos_ngram_range=(1, 3),
|
||||
pos_max_features=500,
|
||||
lsa_variants=("fw", "pos"),
|
||||
lsa_components=100,
|
||||
stylo_normalize=True,
|
||||
include_stylo=True,
|
||||
include_lsa=True,
|
||||
)
|
||||
masked = masked_frame(frame)
|
||||
for name, data in (("original", frame), ("masked", masked)):
|
||||
for model in ("extra_trees", "logreg"):
|
||||
print(f"[topic] {name} / {model}", flush=True)
|
||||
pipeline = build_pipeline(config, model)
|
||||
result = run_repeated_cv(
|
||||
pipeline,
|
||||
data,
|
||||
y,
|
||||
n_splits=folds,
|
||||
n_repeats=repeats,
|
||||
seed=seed,
|
||||
model_name=model,
|
||||
verbose=False,
|
||||
)
|
||||
save_cv(
|
||||
result,
|
||||
out_dir / f"{name}_{model}",
|
||||
f"Topic control: {name} / {model}",
|
||||
{"condition": name, "model": model, "groups": style_groups},
|
||||
)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Calibration
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def _prior_adjust(scores: np.ndarray, priors: np.ndarray, alpha: float) -> np.ndarray:
|
||||
adjusted = scores / np.power(np.maximum(priors, 1e-12), alpha)[None, :]
|
||||
row_sums = adjusted.sum(axis=1, keepdims=True)
|
||||
return adjusted / np.maximum(row_sums, 1e-12)
|
||||
|
||||
|
||||
def calibration(
|
||||
frame: pd.DataFrame,
|
||||
y: np.ndarray,
|
||||
out_dir: Path,
|
||||
folds: int = 5,
|
||||
repeats: int = 5,
|
||||
seed: int = 42,
|
||||
) -> None:
|
||||
config = preset_config("stylometry_pos_lsa")
|
||||
pipeline = build_pipeline(config, "extra_trees")
|
||||
print("[calibration] base CV", flush=True)
|
||||
result = run_repeated_cv(
|
||||
pipeline,
|
||||
frame,
|
||||
y,
|
||||
n_splits=folds,
|
||||
n_repeats=repeats,
|
||||
seed=seed,
|
||||
model_name="extra_trees",
|
||||
verbose=False,
|
||||
)
|
||||
save_cv(
|
||||
result,
|
||||
out_dir / "base_extra_trees",
|
||||
"Calibration base: extra_trees",
|
||||
{"model": "extra_trees", "preset": "stylometry_pos_lsa"},
|
||||
)
|
||||
labels = result.labels
|
||||
label_to_index = {label: index for index, label in enumerate(labels)}
|
||||
priors = np.bincount(
|
||||
np.array([label_to_index[value] for value in y]), minlength=len(labels)
|
||||
).astype(float)
|
||||
priors /= priors.sum()
|
||||
alphas = [0.0, 0.25, 0.5, 0.75, 1.0]
|
||||
|
||||
matrix: dict[int, np.ndarray] = {}
|
||||
truths: dict[int, np.ndarray] = {}
|
||||
for repeat, group in result.oof.groupby("repeat"):
|
||||
scores = result.scores_by_repeat.get(int(repeat))
|
||||
if scores is None or scores.shape[0] != len(group):
|
||||
continue
|
||||
matrix[int(repeat)] = scores
|
||||
truths[int(repeat)] = group["y_true"].to_numpy()
|
||||
|
||||
rows = []
|
||||
for repeat in sorted(matrix):
|
||||
other = [value for value in matrix if value != repeat]
|
||||
table: dict[float, float] = {}
|
||||
for alpha in alphas:
|
||||
values = []
|
||||
for other_repeat in other:
|
||||
prediction_indices = np.argmax(
|
||||
_prior_adjust(matrix[other_repeat], priors, alpha), axis=1
|
||||
)
|
||||
values.append(
|
||||
f1_score(
|
||||
truths[other_repeat],
|
||||
labels[prediction_indices],
|
||||
labels=labels,
|
||||
average="macro",
|
||||
zero_division=0,
|
||||
)
|
||||
)
|
||||
table[alpha] = float(np.mean(values))
|
||||
best_alpha = max(table, key=table.get)
|
||||
baseline_pred = labels[np.argmax(matrix[repeat], axis=1)]
|
||||
corrected_pred = labels[
|
||||
np.argmax(_prior_adjust(matrix[repeat], priors, best_alpha), axis=1)
|
||||
]
|
||||
baseline = f1_score(
|
||||
truths[repeat], baseline_pred, labels=labels, average="macro", zero_division=0
|
||||
)
|
||||
corrected = f1_score(
|
||||
truths[repeat], corrected_pred, labels=labels, average="macro", zero_division=0
|
||||
)
|
||||
rows.append(
|
||||
{
|
||||
"repeat": repeat,
|
||||
"selected_alpha": best_alpha,
|
||||
"baseline_macro_f1": baseline,
|
||||
"corrected_macro_f1": corrected,
|
||||
"delta": corrected - baseline,
|
||||
**{f"alpha_{alpha}": table[alpha] for alpha in alphas},
|
||||
}
|
||||
)
|
||||
table = pd.DataFrame(rows)
|
||||
out_dir.mkdir(parents=True, exist_ok=True)
|
||||
table.to_csv(out_dir / "calibration.csv", index=False, encoding="utf-8-sig")
|
||||
save_json(
|
||||
{
|
||||
"mean_baseline": float(table["baseline_macro_f1"].mean()),
|
||||
"mean_corrected": float(table["corrected_macro_f1"].mean()),
|
||||
"mean_delta": float(table["delta"].mean()),
|
||||
"selected_alpha": table["selected_alpha"].tolist(),
|
||||
},
|
||||
out_dir / "summary.json",
|
||||
)
|
||||
print(table.to_string(index=False))
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Ensemble
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def _base_estimators() -> list[tuple[str, Any]]:
|
||||
base = preset_config("stylometry_pos_lsa")
|
||||
return [
|
||||
("et_full", build_pipeline(base, "extra_trees")),
|
||||
(
|
||||
"rf_char10000",
|
||||
build_pipeline(replace(base, char_max_features=10000), "random_forest"),
|
||||
),
|
||||
(
|
||||
"logreg_lean_content300",
|
||||
build_pipeline(lean_config(base, 300), "logreg"),
|
||||
),
|
||||
]
|
||||
|
||||
|
||||
def ensemble(
|
||||
frame: pd.DataFrame,
|
||||
y: np.ndarray,
|
||||
out_dir: Path,
|
||||
folds: int = 5,
|
||||
repeats: int = 3,
|
||||
seed: int = 42,
|
||||
with_stacking: bool = True,
|
||||
) -> None:
|
||||
estimators = _base_estimators()
|
||||
voting = VotingClassifier(estimators=estimators, voting="soft", n_jobs=1)
|
||||
print("[ensemble] soft voting", flush=True)
|
||||
result = run_repeated_cv(
|
||||
voting,
|
||||
frame,
|
||||
y,
|
||||
n_splits=folds,
|
||||
n_repeats=repeats,
|
||||
seed=seed,
|
||||
model_name="soft_voting",
|
||||
verbose=False,
|
||||
)
|
||||
save_cv(
|
||||
result,
|
||||
out_dir / "soft_voting",
|
||||
"Ensemble: soft voting",
|
||||
{"estimators": [name for name, _ in estimators], "voting": "soft"},
|
||||
)
|
||||
if with_stacking:
|
||||
print("[ensemble] stacking", flush=True)
|
||||
stacking = StackingClassifier(
|
||||
estimators=estimators,
|
||||
final_estimator=LogisticRegression(
|
||||
max_iter=3000, class_weight="balanced"
|
||||
),
|
||||
stack_method="predict_proba",
|
||||
cv=3,
|
||||
n_jobs=1,
|
||||
passthrough=False,
|
||||
)
|
||||
stacked = run_repeated_cv(
|
||||
stacking,
|
||||
frame,
|
||||
y,
|
||||
n_splits=folds,
|
||||
n_repeats=1,
|
||||
seed=seed,
|
||||
model_name="stacking",
|
||||
verbose=False,
|
||||
)
|
||||
save_cv(
|
||||
stacked,
|
||||
out_dir / "stacking",
|
||||
"Ensemble: stacking",
|
||||
{"estimators": [name for name, _ in estimators], "meta": "logreg"},
|
||||
)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Extra configurations
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def extra(
|
||||
frame: pd.DataFrame,
|
||||
y: np.ndarray,
|
||||
out_dir: Path,
|
||||
folds: int = 5,
|
||||
repeats: int = 3,
|
||||
seed: int = 42,
|
||||
) -> None:
|
||||
base = preset_config("stylometry_pos_lsa")
|
||||
configs: list[tuple[str, FeatureConfig, str]] = [
|
||||
("rf_char15000", replace(base, char_max_features=15000), "random_forest"),
|
||||
("logreg_lean_content400", lean_config(base, 400), "logreg"),
|
||||
("logreg_lean_content500", lean_config(base, 500), "logreg"),
|
||||
(
|
||||
"et_block_weights",
|
||||
replace(
|
||||
base,
|
||||
block_weights={
|
||||
"stylo": 1.0,
|
||||
"char": 0.7,
|
||||
"pos": 0.7,
|
||||
"lsa_word": 0.3,
|
||||
"lsa_pos": 0.3,
|
||||
},
|
||||
),
|
||||
"extra_trees",
|
||||
),
|
||||
]
|
||||
for name, config, model in configs:
|
||||
print(f"[extra] {name}", flush=True)
|
||||
pipeline = build_pipeline(config, model)
|
||||
result = run_repeated_cv(
|
||||
pipeline,
|
||||
frame,
|
||||
y,
|
||||
n_splits=folds,
|
||||
n_repeats=repeats,
|
||||
seed=seed,
|
||||
model_name=model,
|
||||
verbose=False,
|
||||
)
|
||||
save_cv(result, out_dir / name, f"Extra: {name}", {"model": model})
|
||||
|
||||
# Отложенная выборка: одно стратифицированное разбиение 80/20.
|
||||
train_idx, test_idx = train_test_split(
|
||||
np.arange(len(frame)), test_size=0.2, stratify=y, random_state=seed
|
||||
)
|
||||
holdout_rows = []
|
||||
for name, config, model in (
|
||||
("et_full_holdout", base, "extra_trees"),
|
||||
(
|
||||
"rf_char10000_holdout",
|
||||
replace(base, char_max_features=10000),
|
||||
"random_forest",
|
||||
),
|
||||
("logreg_lean_content300_holdout", lean_config(base, 300), "logreg"),
|
||||
):
|
||||
print(f"[extra] holdout {name}", flush=True)
|
||||
pipeline = build_pipeline(config, model)
|
||||
pipeline.fit(frame.iloc[train_idx], y[train_idx])
|
||||
prediction = pipeline.predict(frame.iloc[test_idx])
|
||||
metrics = classification_metrics(y[test_idx], prediction, labels=np.unique(y))
|
||||
metrics["name"] = name
|
||||
metrics["train_n"] = len(train_idx)
|
||||
metrics["test_n"] = len(test_idx)
|
||||
holdout_rows.append(metrics)
|
||||
pd.DataFrame(holdout_rows).to_csv(
|
||||
out_dir / "holdout_metrics.csv", index=False, encoding="utf-8-sig"
|
||||
)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# CLI
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument(
|
||||
"--stages",
|
||||
nargs="+",
|
||||
default=["nested", "topic", "calibration", "ensemble", "extra"],
|
||||
choices=["nested", "topic", "calibration", "ensemble", "extra"],
|
||||
)
|
||||
parser.add_argument("--out", default=str(DEFAULT_OUT))
|
||||
parser.add_argument("--folds", type=int, default=5)
|
||||
parser.add_argument("--repeats", type=int, default=3)
|
||||
parser.add_argument("--seed", type=int, default=42)
|
||||
parser.add_argument("--no-stacking", action="store_true")
|
||||
args = parser.parse_args()
|
||||
|
||||
frame = load_feature_frame(verbose=True)
|
||||
y = frame["character"].to_numpy()
|
||||
output = Path(args.out)
|
||||
output.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
if "nested" in args.stages:
|
||||
nested_selection(
|
||||
frame,
|
||||
y,
|
||||
output / "nested_selection",
|
||||
folds=args.folds,
|
||||
inner_folds=3,
|
||||
repeats=args.repeats,
|
||||
seed=args.seed,
|
||||
)
|
||||
if "topic" in args.stages:
|
||||
topic_control(
|
||||
frame,
|
||||
y,
|
||||
output / "topic_control",
|
||||
folds=args.folds,
|
||||
repeats=args.repeats,
|
||||
seed=args.seed,
|
||||
)
|
||||
if "calibration" in args.stages:
|
||||
calibration(
|
||||
frame,
|
||||
y,
|
||||
output / "calibration",
|
||||
folds=args.folds,
|
||||
repeats=5,
|
||||
seed=args.seed,
|
||||
)
|
||||
if "ensemble" in args.stages:
|
||||
ensemble(
|
||||
frame,
|
||||
y,
|
||||
output / "ensemble",
|
||||
folds=args.folds,
|
||||
repeats=args.repeats,
|
||||
seed=args.seed,
|
||||
with_stacking=not args.no_stacking,
|
||||
)
|
||||
if "extra" in args.stages:
|
||||
extra(
|
||||
frame,
|
||||
y,
|
||||
output / "extra",
|
||||
folds=args.folds,
|
||||
repeats=args.repeats,
|
||||
seed=args.seed,
|
||||
)
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -1,130 +0,0 @@
|
||||
"""Сравнение финалистов и вариантов мягкого голосования."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import sys
|
||||
from dataclasses import replace
|
||||
from pathlib import Path
|
||||
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
from sklearn.ensemble import VotingClassifier
|
||||
|
||||
PROJECT_ROOT = Path(__file__).resolve().parents[1]
|
||||
if str(PROJECT_ROOT) not in sys.path:
|
||||
sys.path.insert(0, str(PROJECT_ROOT))
|
||||
|
||||
from stylometry.cv import run_repeated_cv
|
||||
from stylometry.datasets import load_feature_frame
|
||||
from stylometry.pipelines import FeatureConfig, build_pipeline, preset_config
|
||||
|
||||
OUT = PROJECT_ROOT / "results" / "final" / "finalists"
|
||||
|
||||
|
||||
def lean(base: FeatureConfig, k: int) -> FeatureConfig:
|
||||
groups = tuple(
|
||||
group for group in base.groups
|
||||
if group not in {"function_words", "pos", "vocabulary", "readability"}
|
||||
)
|
||||
return replace(base, groups=groups, lsa_variants=("content",), lsa_components=k)
|
||||
|
||||
|
||||
def save(result, name: str, config: dict) -> None:
|
||||
directory = OUT / name
|
||||
directory.mkdir(parents=True, exist_ok=True)
|
||||
result.fold_metrics.to_csv(directory / "fold_metrics.csv", index=False, encoding="utf-8-sig")
|
||||
result.oof.to_csv(directory / "oof_predictions.csv", index=False, encoding="utf-8-sig")
|
||||
pooled, bootstrap = result.pooled_summary()
|
||||
pooled.to_csv(directory / "pooled_metrics.csv", index=False, encoding="utf-8-sig")
|
||||
with (directory / "summary.json").open("w", encoding="utf-8") as stream:
|
||||
json.dump(
|
||||
{
|
||||
"config": config,
|
||||
"fold_mean_macro_f1": float(result.fold_metrics["macro_f1"].mean()),
|
||||
"fold_std_macro_f1": float(result.fold_metrics["macro_f1"].std(ddof=1)),
|
||||
"pooled": pooled.to_dict(orient="records"),
|
||||
"bootstrap": bootstrap,
|
||||
},
|
||||
stream,
|
||||
ensure_ascii=False,
|
||||
indent=2,
|
||||
default=str,
|
||||
)
|
||||
|
||||
|
||||
def main() -> int:
|
||||
frame = load_feature_frame(verbose=True)
|
||||
y = frame["character"].to_numpy()
|
||||
base = preset_config("stylometry_pos_lsa")
|
||||
configs = [
|
||||
("et_full", base, "extra_trees"),
|
||||
("et_char10000", replace(base, char_max_features=10000), "extra_trees"),
|
||||
("et_lean_content300", lean(base, 300), "extra_trees"),
|
||||
("et_lean_content400", lean(base, 400), "extra_trees"),
|
||||
("logreg_lean_content300", lean(base, 300), "logreg"),
|
||||
]
|
||||
rows = []
|
||||
for name, config, model in configs:
|
||||
print(f"[finalists] {name}", flush=True)
|
||||
result = run_repeated_cv(
|
||||
build_pipeline(config, model), frame, y,
|
||||
n_splits=5, n_repeats=5, seed=42, model_name=model, verbose=False,
|
||||
)
|
||||
save(result, name, {"name": name, "model": model})
|
||||
pooled, bootstrap = result.pooled_summary()
|
||||
first = pooled.iloc[0].to_dict()
|
||||
rows.append({
|
||||
"name": name,
|
||||
"model": model,
|
||||
"macro_f1": first.get("macro_f1"),
|
||||
"accuracy": first.get("accuracy"),
|
||||
"balanced_accuracy": first.get("balanced_accuracy"),
|
||||
"top3_accuracy": first.get("top3_accuracy"),
|
||||
"macro_f1_ci_lower": bootstrap["macro_f1"]["lower"],
|
||||
"macro_f1_ci_upper": bootstrap["macro_f1"]["upper"],
|
||||
})
|
||||
|
||||
voting_sets = {
|
||||
"voting_et_char_lean": [
|
||||
("et_full", build_pipeline(base, "extra_trees")),
|
||||
("et_char10000", build_pipeline(replace(base, char_max_features=10000), "extra_trees")),
|
||||
("logreg_lean_content300", build_pipeline(lean(base, 300), "logreg")),
|
||||
],
|
||||
"voting_four": [
|
||||
("et_full", build_pipeline(base, "extra_trees")),
|
||||
("rf_char10000", build_pipeline(replace(base, char_max_features=10000), "random_forest")),
|
||||
("et_char10000", build_pipeline(replace(base, char_max_features=10000), "extra_trees")),
|
||||
("logreg_lean_content300", build_pipeline(lean(base, 300), "logreg")),
|
||||
],
|
||||
}
|
||||
for name, estimators in voting_sets.items():
|
||||
print(f"[finalists] {name}", flush=True)
|
||||
voting = VotingClassifier(estimators=estimators, voting="soft", n_jobs=1)
|
||||
result = run_repeated_cv(
|
||||
voting, frame, y, n_splits=5, n_repeats=3, seed=42,
|
||||
model_name=name, verbose=False,
|
||||
)
|
||||
save(result, name, {"name": name, "estimators": [key for key, _ in estimators]})
|
||||
pooled, bootstrap = result.pooled_summary()
|
||||
first = pooled.iloc[0].to_dict()
|
||||
rows.append({
|
||||
"name": name,
|
||||
"model": "soft_voting",
|
||||
"macro_f1": first.get("macro_f1"),
|
||||
"accuracy": first.get("accuracy"),
|
||||
"balanced_accuracy": first.get("balanced_accuracy"),
|
||||
"top3_accuracy": first.get("top3_accuracy"),
|
||||
"macro_f1_ci_lower": bootstrap["macro_f1"]["lower"],
|
||||
"macro_f1_ci_upper": bootstrap["macro_f1"]["upper"],
|
||||
})
|
||||
|
||||
table = pd.DataFrame(rows).sort_values("macro_f1", ascending=False)
|
||||
OUT.mkdir(parents=True, exist_ok=True)
|
||||
table.to_csv(OUT / "finalists.csv", index=False, encoding="utf-8-sig")
|
||||
print(table.to_string(index=False))
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -1,824 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
# Follow-up серия экспериментов по итогам предварительных прогонов.
|
||||
#
|
||||
# Запуск из каталога lab1:
|
||||
# bash scripts/run_followup_experiments.sh
|
||||
#
|
||||
# С другим интерпретатором:
|
||||
# PYTHON=../.venv/bin/python bash scripts/run_followup_experiments.sh
|
||||
#
|
||||
# Быстрая проверка (1 повтор, без сетки и seed-робастности):
|
||||
# QUICK=1 bash scripts/run_followup_experiments.sh
|
||||
#
|
||||
# Только воспроизведение и финальная модель, без тяжёлых стадий:
|
||||
# RUN_GRID=0 RUN_SEEDS=0 bash scripts/run_followup_experiments.sh
|
||||
#
|
||||
# Все результаты складываются в results/followup/<label>/, команды и
|
||||
# сгенерированные конфиги сохраняются рядом. Общий лог:
|
||||
# results/logs/run_followup_<timestamp>.log
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
|
||||
cd "${PROJECT_ROOT}"
|
||||
|
||||
PYTHON="${PYTHON:-python}"
|
||||
if [[ "${PYTHON}" == */* ]]; then
|
||||
PYTHON_DIR="$(cd "$(dirname "${PYTHON}")" && pwd)"
|
||||
PYTHON="${PYTHON_DIR}/$(basename "${PYTHON}")"
|
||||
fi
|
||||
|
||||
if ! "${PYTHON}" -c "import sklearn, pandas, nltk" >/dev/null 2>&1; then
|
||||
echo "ОШИБКА: в интерпретаторе ${PYTHON} не установлены зависимости."
|
||||
echo "Выполните: ${PYTHON} -m pip install -r requirements.txt"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
RESULTS_DIR="${PROJECT_ROOT}/results"
|
||||
# FOLLOWUP_DIR можно переопределить, чтобы прогнать серию в отдельный каталог
|
||||
# (например, для дымового теста) и не смешивать с основными результатами.
|
||||
FOLLOWUP_DIR="${FOLLOWUP_DIR:-${RESULTS_DIR}/followup}"
|
||||
CONFIG_DIR="${FOLLOWUP_DIR}/configs"
|
||||
COMPARISON_DIR="${FOLLOWUP_DIR}/comparisons"
|
||||
LOG_DIR="${RESULTS_DIR}/logs"
|
||||
MANIFEST="${FOLLOWUP_DIR}/manifest.tsv"
|
||||
|
||||
REPEATS_MAIN="${REPEATS_MAIN:-5}"
|
||||
REPEATS_GRID="${REPEATS_GRID:-3}"
|
||||
REPEATS_SWEEP="${REPEATS_SWEEP:-3}"
|
||||
REPEATS_NGRAM="${REPEATS_NGRAM:-3}"
|
||||
REPEATS_FINAL="${REPEATS_FINAL:-10}"
|
||||
RUN_GRID="${RUN_GRID:-1}"
|
||||
RUN_SEEDS="${RUN_SEEDS:-1}"
|
||||
RUN_FINAL="${RUN_FINAL:-1}"
|
||||
RUN_CONTROLS="${RUN_CONTROLS:-1}"
|
||||
SKIP_TESTS="${SKIP_TESTS:-0}"
|
||||
FORCE="${FORCE:-0}"
|
||||
SEED="${SEED:-42}"
|
||||
|
||||
if [[ "${QUICK:-0}" == "1" ]]; then
|
||||
REPEATS_MAIN=1
|
||||
REPEATS_GRID=1
|
||||
REPEATS_SWEEP=1
|
||||
REPEATS_NGRAM=1
|
||||
REPEATS_FINAL=1
|
||||
RUN_GRID=0
|
||||
RUN_SEEDS=0
|
||||
fi
|
||||
|
||||
mkdir -p "${FOLLOWUP_DIR}" "${CONFIG_DIR}" "${COMPARISON_DIR}" "${LOG_DIR}"
|
||||
if [[ ! -f "${MANIFEST}" ]]; then
|
||||
printf 'label\toriginal_dir\n' > "${MANIFEST}"
|
||||
fi
|
||||
|
||||
RUN_ID="${RUN_ID:-$(date +%Y%m%d_%H%M%S)}"
|
||||
LOG_FILE="${LOG_DIR}/run_followup_${RUN_ID}.log"
|
||||
exec > >(tee -a "${LOG_FILE}") 2>&1
|
||||
|
||||
step() {
|
||||
echo
|
||||
echo "============================================================"
|
||||
echo "== $*"
|
||||
echo "============================================================"
|
||||
}
|
||||
|
||||
run() {
|
||||
echo
|
||||
echo ">>> $*"
|
||||
"$@"
|
||||
}
|
||||
|
||||
write_config() {
|
||||
local name="$1"
|
||||
cat > "${CONFIG_DIR}/${name}.toml"
|
||||
}
|
||||
|
||||
# Запускает команду, находит созданный ею каталог в results/ и переносит его
|
||||
# в results/followup/<label>. Так у каждого эксперимента появляется
|
||||
# стабильное имя, а command.txt фиксирует точную команду.
|
||||
run_labeled() {
|
||||
local label="$1"
|
||||
shift
|
||||
local before after dir dest
|
||||
before="$(mktemp)"
|
||||
after="$(mktemp)"
|
||||
find "${RESULTS_DIR}" -maxdepth 1 -mindepth 1 -type d -printf '%f\n' | sort > "${before}"
|
||||
run "$@"
|
||||
find "${RESULTS_DIR}" -maxdepth 1 -mindepth 1 -type d -printf '%f\n' | sort > "${after}"
|
||||
dir="$(comm -13 "${before}" "${after}" | head -n 1)"
|
||||
rm -f "${before}" "${after}"
|
||||
if [[ -z "${dir}" ]]; then
|
||||
echo "ОШИБКА: команда не создала каталог результатов (${label})" >&2
|
||||
return 1
|
||||
fi
|
||||
dest="${FOLLOWUP_DIR}/${label}"
|
||||
if [[ -e "${dest}" ]]; then
|
||||
if [[ "${FORCE}" == "1" ]]; then
|
||||
rm -rf "${dest}"
|
||||
else
|
||||
echo "ОШИБКА: ${dest} уже существует. Задайте FORCE=1 или удалите каталог." >&2
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
mv "${RESULTS_DIR}/${dir}" "${dest}"
|
||||
printf '%q ' "$@" > "${dest}/command.txt"
|
||||
printf '\n' >> "${dest}/command.txt"
|
||||
printf '%s\t%s\n' "${label}" "${dir}" >> "${MANIFEST}"
|
||||
echo "[followup] ${label} -> ${dest}"
|
||||
}
|
||||
|
||||
latest_dir() {
|
||||
local prefix="$1"
|
||||
ls -1dt "${RESULTS_DIR}/${prefix}"_* 2>/dev/null | head -n 1 || true
|
||||
}
|
||||
|
||||
echo "Начало follow-up серии: $(date)"
|
||||
echo "Интерпретатор: ${PYTHON}"
|
||||
echo "Повторы: main=${REPEATS_MAIN}, grid=${REPEATS_GRID}, sweep=${REPEATS_SWEEP}, ngram=${REPEATS_NGRAM}, final=${REPEATS_FINAL}"
|
||||
echo "Тяжёлые стадии: grid=${RUN_GRID}, seeds=${RUN_SEEDS}, final=${RUN_FINAL}, controls=${RUN_CONTROLS}"
|
||||
echo "Каталог результатов: ${FOLLOWUP_DIR}"
|
||||
echo "Лог: ${LOG_FILE}"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Конфиги признакового пространства. CLI не умеет менять lsa_components,
|
||||
# min_df, n-gram ranges и groups, поэтому параметры задаются через TOML.
|
||||
# ---------------------------------------------------------------------------
|
||||
step "0. Генерация конфигов"
|
||||
|
||||
write_config lsa_content_k300 <<'EOF'
|
||||
[experiment]
|
||||
preset = "lsa_content"
|
||||
model = "logreg"
|
||||
|
||||
[features]
|
||||
lsa_components = 300
|
||||
EOF
|
||||
|
||||
write_config lsa_content_k400 <<'EOF'
|
||||
[experiment]
|
||||
preset = "lsa_content"
|
||||
model = "logreg"
|
||||
|
||||
[features]
|
||||
lsa_components = 400
|
||||
EOF
|
||||
|
||||
write_config lsa_char_k300 <<'EOF'
|
||||
[experiment]
|
||||
preset = "lsa_char"
|
||||
model = "logreg"
|
||||
|
||||
[features]
|
||||
lsa_components = 300
|
||||
EOF
|
||||
|
||||
write_config lsa_pos_k300 <<'EOF'
|
||||
[experiment]
|
||||
preset = "lsa_pos"
|
||||
model = "logreg"
|
||||
|
||||
[features]
|
||||
lsa_components = 300
|
||||
EOF
|
||||
|
||||
write_config stylometry_pos_content300 <<'EOF'
|
||||
[experiment]
|
||||
preset = "stylometry_pos_lsa"
|
||||
model = "logreg"
|
||||
|
||||
[features]
|
||||
lsa_variants = ["content"]
|
||||
lsa_components = 300
|
||||
EOF
|
||||
|
||||
write_config stylometry_pos_pos300 <<'EOF'
|
||||
[experiment]
|
||||
preset = "stylometry_pos_lsa"
|
||||
model = "logreg"
|
||||
|
||||
[features]
|
||||
lsa_variants = ["pos"]
|
||||
lsa_components = 300
|
||||
EOF
|
||||
|
||||
write_config stylometry_pos_lsa_k300 <<'EOF'
|
||||
[experiment]
|
||||
preset = "stylometry_pos_lsa"
|
||||
model = "logreg"
|
||||
|
||||
[features]
|
||||
lsa_variants = ["word", "pos"]
|
||||
lsa_components = 300
|
||||
EOF
|
||||
|
||||
write_config groups_lean1 <<'EOF'
|
||||
[experiment]
|
||||
preset = "stylometry_pos_lsa"
|
||||
model = "logreg"
|
||||
|
||||
[features]
|
||||
groups = ["length", "lexical", "vocabulary", "characters", "punctuation", "pronouns", "syntax", "readability", "dialogue"]
|
||||
EOF
|
||||
|
||||
write_config groups_lean2 <<'EOF'
|
||||
[experiment]
|
||||
preset = "stylometry_pos_lsa"
|
||||
model = "logreg"
|
||||
|
||||
[features]
|
||||
groups = ["length", "lexical", "characters", "punctuation", "pronouns", "syntax", "dialogue"]
|
||||
EOF
|
||||
|
||||
write_config groups_lean2_content300 <<'EOF'
|
||||
[experiment]
|
||||
preset = "stylometry_pos_lsa"
|
||||
model = "logreg"
|
||||
|
||||
[features]
|
||||
groups = ["length", "lexical", "characters", "punctuation", "pronouns", "syntax", "dialogue"]
|
||||
lsa_variants = ["content"]
|
||||
lsa_components = 300
|
||||
EOF
|
||||
|
||||
write_config min_df2 <<'EOF'
|
||||
[experiment]
|
||||
preset = "stylometry_pos_lsa"
|
||||
model = "logreg"
|
||||
|
||||
[features]
|
||||
min_df = 2
|
||||
EOF
|
||||
|
||||
write_config min_df5 <<'EOF'
|
||||
[experiment]
|
||||
preset = "stylometry_pos_lsa"
|
||||
model = "logreg"
|
||||
|
||||
[features]
|
||||
min_df = 5
|
||||
EOF
|
||||
|
||||
write_config char_max10000 <<'EOF'
|
||||
[experiment]
|
||||
preset = "stylometry_pos_lsa"
|
||||
model = "logreg"
|
||||
|
||||
[features]
|
||||
char_max_features = 10000
|
||||
EOF
|
||||
|
||||
write_config char_ngram_2_4 <<'EOF'
|
||||
[experiment]
|
||||
preset = "stylometry_pos_lsa"
|
||||
model = "logreg"
|
||||
|
||||
[features]
|
||||
char_ngram_range = [2, 4]
|
||||
EOF
|
||||
|
||||
write_config char_ngram_3_5 <<'EOF'
|
||||
[experiment]
|
||||
preset = "stylometry_pos_lsa"
|
||||
model = "logreg"
|
||||
|
||||
[features]
|
||||
char_ngram_range = [3, 5]
|
||||
EOF
|
||||
|
||||
write_config pos_ngram_1_2 <<'EOF'
|
||||
[experiment]
|
||||
preset = "stylometry_pos_lsa"
|
||||
model = "logreg"
|
||||
|
||||
[features]
|
||||
pos_ngram_range = [1, 2]
|
||||
EOF
|
||||
|
||||
write_config pos_ngram_1_4 <<'EOF'
|
||||
[experiment]
|
||||
preset = "stylometry_pos_lsa"
|
||||
model = "logreg"
|
||||
|
||||
[features]
|
||||
pos_ngram_range = [1, 4]
|
||||
EOF
|
||||
|
||||
write_config pos_max1000 <<'EOF'
|
||||
[experiment]
|
||||
preset = "stylometry_pos_lsa"
|
||||
model = "logreg"
|
||||
|
||||
[features]
|
||||
pos_max_features = 1000
|
||||
EOF
|
||||
|
||||
echo "Сгенерировано конфигов: $(find "${CONFIG_DIR}" -name '*.toml' | wc -l)"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# F0. Проверка окружения и воспроизведение опорных результатов.
|
||||
# ---------------------------------------------------------------------------
|
||||
step "F0. Проверка окружения и опорные запуски"
|
||||
|
||||
run "${PYTHON}" scripts/check_environment.py
|
||||
if [[ "${SKIP_TESTS}" != "1" ]]; then
|
||||
run "${PYTHON}" -m pytest -q
|
||||
fi
|
||||
|
||||
run_labeled ref_logreg \
|
||||
"${PYTHON}" -m stylometry.cli run --preset stylometry_pos_lsa \
|
||||
--model logreg --folds 5 --repeats "${REPEATS_MAIN}" --seed "${SEED}"
|
||||
|
||||
run_labeled ref_nested_logreg \
|
||||
"${PYTHON}" -m stylometry.cli run --preset stylometry_pos_lsa \
|
||||
--model logreg --folds 5 --nested --seed "${SEED}"
|
||||
|
||||
run_labeled ref_no_lsa \
|
||||
"${PYTHON}" -m stylometry.cli run --preset stylometry_pos \
|
||||
--model logreg --folds 5 --repeats "${REPEATS_MAIN}" --seed "${SEED}"
|
||||
|
||||
run_labeled ref_stylometry \
|
||||
"${PYTHON}" -m stylometry.cli run --preset stylometry \
|
||||
--model logreg --folds 5 --repeats "${REPEATS_MAIN}" --seed "${SEED}"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# F1. Сравнение моделей на одном признаковом пространстве, 5 повторов.
|
||||
# ---------------------------------------------------------------------------
|
||||
step "F1. Сравнение моделей (5 повторов + nested)"
|
||||
|
||||
run_labeled model_rf_default \
|
||||
"${PYTHON}" -m stylometry.cli run --preset stylometry_pos_lsa \
|
||||
--model random_forest --folds 5 --repeats "${REPEATS_MAIN}" --seed "${SEED}"
|
||||
|
||||
run_labeled model_et_default \
|
||||
"${PYTHON}" -m stylometry.cli run --preset stylometry_pos_lsa \
|
||||
--model extra_trees --folds 5 --repeats "${REPEATS_MAIN}" --seed "${SEED}"
|
||||
|
||||
run_labeled model_svc_default \
|
||||
"${PYTHON}" -m stylometry.cli run --preset stylometry_pos_lsa \
|
||||
--model linear_svc --folds 5 --repeats "${REPEATS_MAIN}" --seed "${SEED}"
|
||||
|
||||
run_labeled model_knn_default \
|
||||
"${PYTHON}" -m stylometry.cli run --preset stylometry_pos_lsa \
|
||||
--model knn --folds 5 --repeats "${REPEATS_GRID}" --seed "${SEED}"
|
||||
|
||||
run_labeled model_rf_nested \
|
||||
"${PYTHON}" -m stylometry.cli run --preset stylometry_pos_lsa \
|
||||
--model random_forest --folds 5 --nested --seed "${SEED}"
|
||||
|
||||
run_labeled model_et_nested \
|
||||
"${PYTHON}" -m stylometry.cli run --preset stylometry_pos_lsa \
|
||||
--model extra_trees --folds 5 --nested --seed "${SEED}"
|
||||
|
||||
run_labeled model_svc_nested \
|
||||
"${PYTHON}" -m stylometry.cli run --preset stylometry_pos_lsa \
|
||||
--model linear_svc --folds 5 --nested --seed "${SEED}"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# F2. Сетка вокруг лучшей модели (random forest по умолчанию).
|
||||
# ---------------------------------------------------------------------------
|
||||
if [[ "${RUN_GRID}" == "1" ]]; then
|
||||
step "F2. Сетка random forest / extra trees"
|
||||
|
||||
for depth in None 30 40; do
|
||||
for leaf in 1 2 3; do
|
||||
if [[ "${depth}" == "None" && "${leaf}" == "1" ]]; then
|
||||
continue # это model_rf_default
|
||||
fi
|
||||
if [[ "${depth}" == "None" ]]; then
|
||||
depth_json="null"
|
||||
else
|
||||
depth_json="${depth}"
|
||||
fi
|
||||
run_labeled "rf_d${depth}_l${leaf}" \
|
||||
"${PYTHON}" -m stylometry.cli run --preset stylometry_pos_lsa \
|
||||
--model random_forest --folds 5 --repeats "${REPEATS_GRID}" --seed "${SEED}" \
|
||||
--model-params "{\"max_depth\": ${depth_json}, \"min_samples_leaf\": ${leaf}}"
|
||||
done
|
||||
done
|
||||
|
||||
for max_features in 0.05 0.1; do
|
||||
for n_estimators in 400 800; do
|
||||
run_labeled "rf_mf${max_features}_n${n_estimators}" \
|
||||
"${PYTHON}" -m stylometry.cli run --preset stylometry_pos_lsa \
|
||||
--model random_forest --folds 5 --repeats "${REPEATS_GRID}" --seed "${SEED}" \
|
||||
--model-params "{\"max_features\": ${max_features}, \"n_estimators\": ${n_estimators}, \"max_depth\": null, \"min_samples_leaf\": 1}"
|
||||
done
|
||||
done
|
||||
|
||||
run_labeled rf_n800_sqrt \
|
||||
"${PYTHON}" -m stylometry.cli run --preset stylometry_pos_lsa \
|
||||
--model random_forest --folds 5 --repeats "${REPEATS_GRID}" --seed "${SEED}" \
|
||||
--model-params '{"n_estimators": 800, "max_depth": null, "min_samples_leaf": 1}'
|
||||
|
||||
run_labeled rf_cw_balanced \
|
||||
"${PYTHON}" -m stylometry.cli run --preset stylometry_pos_lsa \
|
||||
--model random_forest --folds 5 --repeats "${REPEATS_GRID}" --seed "${SEED}" \
|
||||
--model-params '{"class_weight": "balanced"}'
|
||||
|
||||
run_labeled rf_cw_none \
|
||||
"${PYTHON}" -m stylometry.cli run --preset stylometry_pos_lsa \
|
||||
--model random_forest --folds 5 --repeats "${REPEATS_GRID}" --seed "${SEED}" \
|
||||
--model-params '{"class_weight": null}'
|
||||
|
||||
run_labeled et_dNone_l2 \
|
||||
"${PYTHON}" -m stylometry.cli run --preset stylometry_pos_lsa \
|
||||
--model extra_trees --folds 5 --repeats "${REPEATS_GRID}" --seed "${SEED}" \
|
||||
--model-params '{"max_depth": null, "min_samples_leaf": 2}'
|
||||
|
||||
run_labeled et_d30_l1 \
|
||||
"${PYTHON}" -m stylometry.cli run --preset stylometry_pos_lsa \
|
||||
--model extra_trees --folds 5 --repeats "${REPEATS_GRID}" --seed "${SEED}" \
|
||||
--model-params '{"max_depth": 30, "min_samples_leaf": 1}'
|
||||
|
||||
run_labeled et_d30_l2 \
|
||||
"${PYTHON}" -m stylometry.cli run --preset stylometry_pos_lsa \
|
||||
--model extra_trees --folds 5 --repeats "${REPEATS_GRID}" --seed "${SEED}" \
|
||||
--model-params '{"max_depth": 30, "min_samples_leaf": 2}'
|
||||
|
||||
# Лучшая точка сетки проверяется на 5 повторах. Это разведочный выбор:
|
||||
# для честной итоговой оценки нужен nested CV (см. final_rf_nested и
|
||||
# обсуждение в FOLLOWUP_EXPERIMENTS.md).
|
||||
step "F2.1. Лучшая точка сетки RF на 5 повторах"
|
||||
BEST_JSON="$("${PYTHON}" - "${FOLLOWUP_DIR}" <<'PY'
|
||||
import glob
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
|
||||
import pandas as pd
|
||||
|
||||
root = sys.argv[1]
|
||||
candidates = []
|
||||
for path in glob.glob(os.path.join(root, "rf_*")):
|
||||
metadata_path = os.path.join(path, "run_metadata.json")
|
||||
pooled_path = os.path.join(path, "pooled_metrics.csv")
|
||||
if not (os.path.exists(metadata_path) and os.path.exists(pooled_path)):
|
||||
continue
|
||||
with open(metadata_path, encoding="utf-8") as stream:
|
||||
metadata = json.load(stream)
|
||||
pooled = pd.read_csv(pooled_path)
|
||||
params = metadata.get("config", {}).get("model_params") or {}
|
||||
candidates.append(
|
||||
(float(pooled["macro_f1"].mean()), os.path.basename(path), json.dumps(params, ensure_ascii=False))
|
||||
)
|
||||
if candidates:
|
||||
candidates.sort(reverse=True)
|
||||
print(f"{candidates[0][1]}\t{candidates[0][2]}")
|
||||
PY
|
||||
)"
|
||||
if [[ -n "${BEST_JSON}" ]]; then
|
||||
BEST_LABEL="${BEST_JSON%%$'\t'*}"
|
||||
BEST_PARAMS="${BEST_JSON#*$'\t'}"
|
||||
echo "[followup] лучшая точка сетки: ${BEST_LABEL}, параметры: ${BEST_PARAMS}"
|
||||
run_labeled rf_grid_best_5 \
|
||||
"${PYTHON}" -m stylometry.cli run --preset stylometry_pos_lsa \
|
||||
--model random_forest --folds 5 --repeats "${REPEATS_MAIN}" --seed "${SEED}" \
|
||||
--model-params "${BEST_PARAMS}"
|
||||
else
|
||||
echo "[followup] не удалось выбрать лучшую точку сетки RF"
|
||||
fi
|
||||
else
|
||||
echo
|
||||
echo "F2 пропущена (RUN_GRID=${RUN_GRID})."
|
||||
fi
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# F3. LSA: тема против стиля. Перебор k и подтверждение на 5 повторах.
|
||||
# ---------------------------------------------------------------------------
|
||||
step "F3. LSA: варианты, число компонент и объединение с признаками"
|
||||
|
||||
run_labeled lsa_sweep \
|
||||
"${PYTHON}" -m stylometry.cli lsa-sweep \
|
||||
--variants content,char,word,pos,fw --components 100,200,300,400 \
|
||||
--model logreg --folds 5 --repeats "${REPEATS_SWEEP}" --seed "${SEED}"
|
||||
|
||||
run_labeled lsa_content_k300 \
|
||||
"${PYTHON}" -m stylometry.cli run --config "${CONFIG_DIR}/lsa_content_k300.toml" \
|
||||
--model logreg --folds 5 --repeats "${REPEATS_MAIN}" --seed "${SEED}"
|
||||
|
||||
run_labeled lsa_content_k400 \
|
||||
"${PYTHON}" -m stylometry.cli run --config "${CONFIG_DIR}/lsa_content_k400.toml" \
|
||||
--model logreg --folds 5 --repeats "${REPEATS_MAIN}" --seed "${SEED}"
|
||||
|
||||
run_labeled lsa_char_k300 \
|
||||
"${PYTHON}" -m stylometry.cli run --config "${CONFIG_DIR}/lsa_char_k300.toml" \
|
||||
--model logreg --folds 5 --repeats "${REPEATS_MAIN}" --seed "${SEED}"
|
||||
|
||||
run_labeled lsa_pos_k300 \
|
||||
"${PYTHON}" -m stylometry.cli run --config "${CONFIG_DIR}/lsa_pos_k300.toml" \
|
||||
--model logreg --folds 5 --repeats "${REPEATS_MAIN}" --seed "${SEED}"
|
||||
|
||||
run_labeled stylometry_pos_content300 \
|
||||
"${PYTHON}" -m stylometry.cli run --config "${CONFIG_DIR}/stylometry_pos_content300.toml" \
|
||||
--model logreg --folds 5 --repeats "${REPEATS_MAIN}" --seed "${SEED}"
|
||||
|
||||
run_labeled stylometry_pos_pos300 \
|
||||
"${PYTHON}" -m stylometry.cli run --config "${CONFIG_DIR}/stylometry_pos_pos300.toml" \
|
||||
--model logreg --folds 5 --repeats "${REPEATS_MAIN}" --seed "${SEED}"
|
||||
|
||||
run_labeled stylometry_pos_lsa_k300 \
|
||||
"${PYTHON}" -m stylometry.cli run --config "${CONFIG_DIR}/stylometry_pos_lsa_k300.toml" \
|
||||
--model logreg --folds 5 --repeats "${REPEATS_MAIN}" --seed "${SEED}"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# F4. Абляция групп признаков на 5 повторах и «худые» наборы.
|
||||
# ---------------------------------------------------------------------------
|
||||
step "F4. Абляция групп признаков и lean-наборы"
|
||||
|
||||
run_labeled ablate_full \
|
||||
"${PYTHON}" -m stylometry.cli ablate --preset stylometry_pos_lsa \
|
||||
--model logreg --folds 5 --repeats "${REPEATS_MAIN}" --seed "${SEED}"
|
||||
|
||||
for name in groups_lean1 groups_lean2 groups_lean2_content300; do
|
||||
run_labeled "${name}" \
|
||||
"${PYTHON}" -m stylometry.cli run --config "${CONFIG_DIR}/${name}.toml" \
|
||||
--model logreg --folds 5 --repeats "${REPEATS_MAIN}" --seed "${SEED}"
|
||||
done
|
||||
|
||||
run_labeled groups_lean1_rf \
|
||||
"${PYTHON}" -m stylometry.cli run --config "${CONFIG_DIR}/groups_lean1.toml" \
|
||||
--model random_forest --folds 5 --repeats "${REPEATS_MAIN}" --seed "${SEED}"
|
||||
|
||||
run_labeled groups_lean2_rf \
|
||||
"${PYTHON}" -m stylometry.cli run --config "${CONFIG_DIR}/groups_lean2.toml" \
|
||||
--model random_forest --folds 5 --repeats "${REPEATS_MAIN}" --seed "${SEED}"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# F5. Настройка символьных и POS n-грамм.
|
||||
# ---------------------------------------------------------------------------
|
||||
step "F5. Символьные и POS n-граммы"
|
||||
|
||||
for name in min_df2 min_df5 char_max10000 char_ngram_2_4 char_ngram_3_5; do
|
||||
run_labeled "${name}" \
|
||||
"${PYTHON}" -m stylometry.cli run --config "${CONFIG_DIR}/${name}.toml" \
|
||||
--model logreg --folds 5 --repeats "${REPEATS_NGRAM}" --seed "${SEED}"
|
||||
done
|
||||
|
||||
for name in pos_ngram_1_2 pos_ngram_1_4 pos_max1000; do
|
||||
run_labeled "${name}" \
|
||||
"${PYTHON}" -m stylometry.cli run --config "${CONFIG_DIR}/${name}.toml" \
|
||||
--model logreg --folds 5 --repeats "${REPEATS_NGRAM}" --seed "${SEED}"
|
||||
done
|
||||
|
||||
run_labeled min_df2_rf \
|
||||
"${PYTHON}" -m stylometry.cli run --config "${CONFIG_DIR}/min_df2.toml" \
|
||||
--model random_forest --folds 5 --repeats "${REPEATS_MAIN}" --seed "${SEED}"
|
||||
|
||||
run_labeled char_max10000_rf \
|
||||
"${PYTHON}" -m stylometry.cli run --config "${CONFIG_DIR}/char_max10000.toml" \
|
||||
--model random_forest --folds 5 --repeats "${REPEATS_MAIN}" --seed "${SEED}"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# F6. Балансировка классов.
|
||||
# ---------------------------------------------------------------------------
|
||||
step "F6. class_weight: balanced против none"
|
||||
|
||||
run_labeled classweight_logreg_none \
|
||||
"${PYTHON}" -m stylometry.cli run --preset stylometry_pos_lsa \
|
||||
--model logreg --folds 5 --repeats "${REPEATS_MAIN}" --seed "${SEED}" \
|
||||
--model-params '{"class_weight": null}'
|
||||
|
||||
run_labeled classweight_svc_none \
|
||||
"${PYTHON}" -m stylometry.cli run --preset stylometry_pos_lsa \
|
||||
--model linear_svc --folds 5 --repeats "${REPEATS_MAIN}" --seed "${SEED}" \
|
||||
--model-params '{"class_weight": null}'
|
||||
|
||||
run_labeled classweight_rf_none \
|
||||
"${PYTHON}" -m stylometry.cli run --preset stylometry_pos_lsa \
|
||||
--model random_forest --folds 5 --repeats "${REPEATS_MAIN}" --seed "${SEED}" \
|
||||
--model-params '{"class_weight": null}'
|
||||
|
||||
run_labeled classweight_rf_balanced \
|
||||
"${PYTHON}" -m stylometry.cli run --preset stylometry_pos_lsa \
|
||||
--model random_forest --folds 5 --repeats "${REPEATS_MAIN}" --seed "${SEED}" \
|
||||
--model-params '{"class_weight": "balanced"}'
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# F7. Устойчивость к seed.
|
||||
# ---------------------------------------------------------------------------
|
||||
if [[ "${RUN_SEEDS}" == "1" ]]; then
|
||||
step "F7. Устойчивость к seed (logreg и random forest)"
|
||||
for seed in 7 2024 1337; do
|
||||
run_labeled "seed_logreg_${seed}" \
|
||||
"${PYTHON}" -m stylometry.cli run --preset stylometry_pos_lsa \
|
||||
--model logreg --folds 5 --repeats "${REPEATS_MAIN}" --seed "${seed}"
|
||||
|
||||
run_labeled "seed_rf_${seed}" \
|
||||
"${PYTHON}" -m stylometry.cli run --preset stylometry_pos_lsa \
|
||||
--model random_forest --folds 5 --repeats "${REPEATS_MAIN}" --seed "${seed}" \
|
||||
--model-params "{\"random_state\": ${seed}}"
|
||||
done
|
||||
else
|
||||
echo
|
||||
echo "F7 пропущена (RUN_SEEDS=${RUN_SEEDS})."
|
||||
fi
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# F8. Финальная модель, отчёты и независимые проверки.
|
||||
# ---------------------------------------------------------------------------
|
||||
if [[ "${RUN_FINAL}" == "1" ]]; then
|
||||
step "F8. Финальная модель и отчёты"
|
||||
|
||||
run_labeled final_rf \
|
||||
"${PYTHON}" -m stylometry.cli run --preset stylometry_pos_lsa \
|
||||
--model random_forest --folds 5 --repeats "${REPEATS_FINAL}" --seed "${SEED}"
|
||||
|
||||
run_labeled final_rf_nested \
|
||||
"${PYTHON}" -m stylometry.cli run --preset stylometry_pos_lsa \
|
||||
--model random_forest --folds 5 --nested --seed "${SEED}"
|
||||
|
||||
FINAL_DIR="${FOLLOWUP_DIR}/final_rf"
|
||||
if [[ -d "${FINAL_DIR}" ]]; then
|
||||
run "${PYTHON}" -m stylometry.cli length-report \
|
||||
--run-dir "${FINAL_DIR}" --seed "${SEED}"
|
||||
for group in gender age_group species; do
|
||||
run "${PYTHON}" -m stylometry.cli subgroup-report \
|
||||
--run-dir "${FINAL_DIR}" --group "${group}"
|
||||
done
|
||||
run "${PYTHON}" -m stylometry.cli compare \
|
||||
--run-a "${FINAL_DIR}" --run-b "${FOLLOWUP_DIR}/ref_logreg" \
|
||||
--out "${COMPARISON_DIR}/final_rf_vs_ref_logreg.json" --seed "${SEED}"
|
||||
run "${PYTHON}" -m stylometry.cli compare \
|
||||
--run-a "${FINAL_DIR}" --run-b "${FOLLOWUP_DIR}/ref_no_lsa" \
|
||||
--out "${COMPARISON_DIR}/final_rf_vs_ref_no_lsa.json" --seed "${SEED}"
|
||||
fi
|
||||
|
||||
run_labeled verify_full_42 \
|
||||
"${PYTHON}" -m stylometry.cli verify --preset stylometry_pos_lsa \
|
||||
--folds 5 --seed 42
|
||||
run_labeled verify_full_7 \
|
||||
"${PYTHON}" -m stylometry.cli verify --preset stylometry_pos_lsa \
|
||||
--folds 5 --seed 7
|
||||
run_labeled verify_full_2024 \
|
||||
"${PYTHON}" -m stylometry.cli verify --preset stylometry_pos_lsa \
|
||||
--folds 5 --seed 2024
|
||||
|
||||
run_labeled delta_cosine \
|
||||
"${PYTHON}" -m stylometry.cli delta --features 500,1000,2000 \
|
||||
--metrics cosine --folds 5 --repeats "${REPEATS_GRID}" --seed "${SEED}"
|
||||
|
||||
for clusters in 3 5 8; do
|
||||
run_labeled "cluster_k${clusters}" \
|
||||
"${PYTHON}" -m stylometry.cli cluster --preset stylometry_pos_lsa \
|
||||
--clusters "${clusters}" --seed "${SEED}"
|
||||
done
|
||||
else
|
||||
echo
|
||||
echo "F8 пропущена (RUN_FINAL=${RUN_FINAL})."
|
||||
fi
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# F9. Контроли: нижние границы, near-duplicates и перестановка меток.
|
||||
# ---------------------------------------------------------------------------
|
||||
if [[ "${RUN_CONTROLS}" == "1" ]]; then
|
||||
step "F9. Контроли"
|
||||
|
||||
run_labeled control_length_logreg \
|
||||
"${PYTHON}" -m stylometry.cli run --preset length \
|
||||
--model logreg --folds 5 --repeats "${REPEATS_MAIN}" --seed "${SEED}"
|
||||
|
||||
run_labeled control_length_dummy \
|
||||
"${PYTHON}" -m stylometry.cli run --preset length \
|
||||
--model dummy --folds 5 --repeats 1 --seed "${SEED}"
|
||||
|
||||
step "F9.1. Поиск near-duplicates"
|
||||
"${PYTHON}" - "${FOLLOWUP_DIR}" <<'PY'
|
||||
from pathlib import Path
|
||||
import sys
|
||||
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
from sklearn.feature_extraction.text import TfidfVectorizer
|
||||
from sklearn.metrics.pairwise import cosine_similarity
|
||||
|
||||
from stylometry.datasets import load_feature_frame
|
||||
|
||||
frame = load_feature_frame(verbose=False)
|
||||
matrix = TfidfVectorizer(analyzer="char_wb", ngram_range=(3, 5), min_df=1).fit_transform(
|
||||
frame["text"]
|
||||
)
|
||||
similarity = cosine_similarity(matrix)
|
||||
rows = np.triu_indices_from(similarity, k=1)
|
||||
values = similarity[rows]
|
||||
threshold = 0.8
|
||||
mask = values >= threshold
|
||||
pairs = pd.DataFrame(
|
||||
{
|
||||
"line_id_a": frame.iloc[rows[0][mask]]["line_id"].to_numpy(),
|
||||
"character_a": frame.iloc[rows[0][mask]]["character"].to_numpy(),
|
||||
"text_a": frame.iloc[rows[0][mask]]["text"].to_numpy(),
|
||||
"line_id_b": frame.iloc[rows[1][mask]]["line_id"].to_numpy(),
|
||||
"character_b": frame.iloc[rows[1][mask]]["character"].to_numpy(),
|
||||
"text_b": frame.iloc[rows[1][mask]]["text"].to_numpy(),
|
||||
"cosine": values[mask],
|
||||
"same_character": frame.iloc[rows[0][mask]]["character"].to_numpy()
|
||||
== frame.iloc[rows[1][mask]]["character"].to_numpy(),
|
||||
}
|
||||
).sort_values("cosine", ascending=False)
|
||||
out_dir = Path(sys.argv[1]) / "negative_control"
|
||||
out_dir.mkdir(parents=True, exist_ok=True)
|
||||
pairs.to_csv(out_dir / "near_duplicates.csv", index=False, encoding="utf-8-sig")
|
||||
print(f"[near-duplicates] пар с cosine >= {threshold}: {len(pairs)}")
|
||||
if not pairs.empty:
|
||||
print(pairs.head(10).to_string(index=False))
|
||||
PY
|
||||
|
||||
step "F9.2. Контроль перестановкой меток"
|
||||
"${PYTHON}" - "${FOLLOWUP_DIR}" <<'PY'
|
||||
from pathlib import Path
|
||||
import sys
|
||||
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
|
||||
from stylometry.cv import run_repeated_cv
|
||||
from stylometry.datasets import load_feature_frame
|
||||
from stylometry.pipelines import build_pipeline, preset_config
|
||||
|
||||
frame = load_feature_frame(verbose=False)
|
||||
y = frame["character"].to_numpy()
|
||||
config = preset_config("stylometry_pos_lsa")
|
||||
rows = []
|
||||
for seed in (42, 7, 2024):
|
||||
rng = np.random.default_rng(seed)
|
||||
shuffled = rng.permutation(y)
|
||||
pipeline = build_pipeline(config, "logreg")
|
||||
result = run_repeated_cv(
|
||||
pipeline,
|
||||
frame,
|
||||
shuffled,
|
||||
n_splits=5,
|
||||
n_repeats=1,
|
||||
seed=seed,
|
||||
model_name="permuted_logreg",
|
||||
verbose=False,
|
||||
)
|
||||
pooled, _ = result.pooled_summary()
|
||||
row = pooled.iloc[0].to_dict()
|
||||
row["permutation_seed"] = seed
|
||||
rows.append(row)
|
||||
out_dir = Path(sys.argv[1]) / "negative_control"
|
||||
out_dir.mkdir(parents=True, exist_ok=True)
|
||||
table = pd.DataFrame(rows)
|
||||
table.to_csv(out_dir / "permutation_logreg.csv", index=False, encoding="utf-8-sig")
|
||||
print(table[["permutation_seed", "macro_f1", "accuracy", "top3_accuracy"]].to_string(index=False))
|
||||
print(f"[permutation] средний macro-F1: {table['macro_f1'].mean():.4f}")
|
||||
PY
|
||||
else
|
||||
echo
|
||||
echo "F9 пропущена (RUN_CONTROLS=${RUN_CONTROLS})."
|
||||
fi
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Сводка по всем запускам и парные сравнения по повторам.
|
||||
# ---------------------------------------------------------------------------
|
||||
step "Сводка follow-up"
|
||||
|
||||
PAIRS=(
|
||||
"model_rf_default:ref_logreg"
|
||||
"model_rf_default:ref_no_lsa"
|
||||
"model_rf_default:model_svc_default"
|
||||
"rf_grid_best_5:model_rf_default"
|
||||
"model_svc_default:ref_logreg"
|
||||
"ref_no_lsa:ref_logreg"
|
||||
"stylometry_pos_content300:ref_no_lsa"
|
||||
"stylometry_pos_pos300:ref_no_lsa"
|
||||
"stylometry_pos_lsa_k300:ref_logreg"
|
||||
"groups_lean1:ref_logreg"
|
||||
"groups_lean2:ref_logreg"
|
||||
"groups_lean2_content300:ref_logreg"
|
||||
"groups_lean1_rf:model_rf_default"
|
||||
"groups_lean2_rf:model_rf_default"
|
||||
"min_df2:ref_logreg"
|
||||
"char_max10000:ref_logreg"
|
||||
"char_ngram_2_4:ref_logreg"
|
||||
"char_ngram_3_5:ref_logreg"
|
||||
"pos_ngram_1_2:ref_logreg"
|
||||
"pos_ngram_1_4:ref_logreg"
|
||||
"pos_max1000:ref_logreg"
|
||||
"min_df2_rf:model_rf_default"
|
||||
"char_max10000_rf:model_rf_default"
|
||||
"classweight_logreg_none:ref_logreg"
|
||||
"classweight_svc_none:model_svc_default"
|
||||
"classweight_rf_none:model_rf_default"
|
||||
"classweight_rf_balanced:model_rf_default"
|
||||
"final_rf:ref_logreg"
|
||||
"final_rf:ref_no_lsa"
|
||||
"final_rf:model_rf_default"
|
||||
"final_rf:final_rf_nested"
|
||||
"control_length_logreg:ref_logreg"
|
||||
)
|
||||
|
||||
run "${PYTHON}" scripts/analyze_followup.py \
|
||||
--followup-dir "${FOLLOWUP_DIR}" \
|
||||
--pairs "${PAIRS[@]}" \
|
||||
--n-boot 1000 --seed "${SEED}"
|
||||
|
||||
step "Готово"
|
||||
echo "Конец follow-up серии: $(date)"
|
||||
echo
|
||||
echo "Результаты: ${FOLLOWUP_DIR}"
|
||||
echo "Сводка: ${FOLLOWUP_DIR}/summary.csv"
|
||||
echo "Сравнения: ${FOLLOWUP_DIR}/comparisons.csv"
|
||||
echo "Лог: ${LOG_FILE}"
|
||||
140
lab1/scripts/run_tuning_experiments.sh
Executable file
@@ -0,0 +1,140 @@
|
||||
#!/usr/bin/env bash
|
||||
# Тюнинг поверх полной серии scripts/run_all_experiments.sh.
|
||||
# Не повторяет E0–E16: только гиперпараметры, состав признаков, веса, k LSA.
|
||||
#
|
||||
# Запуск из каталога lab1:
|
||||
# bash scripts/run_tuning_experiments.sh
|
||||
#
|
||||
# Быстрый черновик:
|
||||
# REPEATS=1 bash scripts/run_tuning_experiments.sh
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
|
||||
cd "${PROJECT_ROOT}"
|
||||
|
||||
PYTHON="${PYTHON:-python}"
|
||||
REPEATS="${REPEATS:-3}"
|
||||
SEED="${SEED:-42}"
|
||||
PRESET="${PRESET:-stylometry_pos_lsa}"
|
||||
|
||||
LOG_DIR="${PROJECT_ROOT}/results/logs"
|
||||
mkdir -p "${LOG_DIR}"
|
||||
RUN_ID="${RUN_ID:-$(date +%Y%m%d_%H%M%S)}"
|
||||
LOG_FILE="${LOG_DIR}/tune_${RUN_ID}.log"
|
||||
exec > >(tee -a "${LOG_FILE}") 2>&1
|
||||
|
||||
step() {
|
||||
echo
|
||||
echo "============================================================"
|
||||
echo "== $*"
|
||||
echo "============================================================"
|
||||
}
|
||||
|
||||
run() {
|
||||
echo
|
||||
echo ">>> $*"
|
||||
"$@"
|
||||
}
|
||||
|
||||
echo "Тюнинг: $(date)"
|
||||
echo "Интерпретатор: ${PYTHON}"
|
||||
echo "Повторы: ${REPEATS}, seed: ${SEED}, пресет: ${PRESET}"
|
||||
echo "Лог: ${LOG_FILE}"
|
||||
|
||||
step "T1. Logreg: C"
|
||||
for c in 0.03 0.1 0.3 1 3 10; do
|
||||
run "${PYTHON}" -m stylometry.cli run \
|
||||
--preset "${PRESET}" --model logreg \
|
||||
--repeats "${REPEATS}" --seed "${SEED}" \
|
||||
--model-params "{\"C\": ${c}}"
|
||||
done
|
||||
|
||||
step "T2. Linear SVM: C"
|
||||
for c in 0.03 0.1 0.3 1 3 10; do
|
||||
run "${PYTHON}" -m stylometry.cli run \
|
||||
--preset "${PRESET}" --model linear_svc \
|
||||
--repeats "${REPEATS}" --seed "${SEED}" \
|
||||
--model-params "{\"C\": ${c}}"
|
||||
done
|
||||
|
||||
step "T3. Logreg без балансировки классов"
|
||||
run "${PYTHON}" -m stylometry.cli run \
|
||||
--preset "${PRESET}" --model logreg \
|
||||
--repeats "${REPEATS}" --seed "${SEED}" \
|
||||
--model-params '{"class_weight": null}'
|
||||
|
||||
step "T4. Без vocabulary и readability"
|
||||
run "${PYTHON}" -m stylometry.cli run \
|
||||
--config configs/tune_no_vocab_readability.toml \
|
||||
--preset "${PRESET}" --model logreg \
|
||||
--repeats "${REPEATS}" --seed "${SEED}"
|
||||
|
||||
step "T5. Без vocabulary, readability и length"
|
||||
run "${PYTHON}" -m stylometry.cli run \
|
||||
--config configs/tune_no_vocab_readability_length.toml \
|
||||
--preset "${PRESET}" --model logreg \
|
||||
--repeats "${REPEATS}" --seed "${SEED}"
|
||||
|
||||
step "T6. Char/POS n-граммы"
|
||||
run "${PYTHON}" -m stylometry.cli run \
|
||||
--config configs/tune_char_narrow.toml \
|
||||
--preset "${PRESET}" --model logreg \
|
||||
--repeats "${REPEATS}" --seed "${SEED}"
|
||||
run "${PYTHON}" -m stylometry.cli run \
|
||||
--config configs/tune_char_wide.toml \
|
||||
--preset "${PRESET}" --model logreg \
|
||||
--repeats "${REPEATS}" --seed "${SEED}"
|
||||
|
||||
step "T7. Веса блоков"
|
||||
run "${PYTHON}" -m stylometry.cli run \
|
||||
--config configs/tune_block_weights.toml \
|
||||
--preset "${PRESET}" --model logreg \
|
||||
--repeats "${REPEATS}" --seed "${SEED}"
|
||||
|
||||
step "T8. LSA k=150 и k=300 (в полной серии были 50/100/200)"
|
||||
run "${PYTHON}" -m stylometry.cli run \
|
||||
--config configs/tune_lsa_k150.toml \
|
||||
--preset "${PRESET}" --model logreg \
|
||||
--repeats "${REPEATS}" --seed "${SEED}"
|
||||
run "${PYTHON}" -m stylometry.cli run \
|
||||
--config configs/tune_lsa_k300.toml \
|
||||
--preset "${PRESET}" --model logreg \
|
||||
--repeats "${REPEATS}" --seed "${SEED}"
|
||||
run "${PYTHON}" -m stylometry.cli lsa-sweep \
|
||||
--variants word,content,fw,char,pos \
|
||||
--components 150,300 \
|
||||
--model logreg --repeats 1 --seed "${SEED}"
|
||||
|
||||
step "T9. Случайный лес: глубина и лист"
|
||||
for depth in 10 20 30; do
|
||||
for leaf in 2 3 5; do
|
||||
run "${PYTHON}" -m stylometry.cli run \
|
||||
--preset "${PRESET}" --model random_forest \
|
||||
--repeats "${REPEATS}" --seed "${SEED}" \
|
||||
--model-params "{\"max_depth\": ${depth}, \"min_samples_leaf\": ${leaf}}"
|
||||
done
|
||||
done
|
||||
run "${PYTHON}" -m stylometry.cli run \
|
||||
--preset "${PRESET}" --model random_forest \
|
||||
--repeats "${REPEATS}" --seed "${SEED}" \
|
||||
--model-params '{"max_depth": 20, "min_samples_leaf": 3, "max_features": 0.1}'
|
||||
|
||||
step "T10. Extra trees (не было в benchmark полной серии)"
|
||||
run "${PYTHON}" -m stylometry.cli run \
|
||||
--preset "${PRESET}" --model extra_trees \
|
||||
--repeats "${REPEATS}" --seed "${SEED}" \
|
||||
--model-params '{"max_depth": 20, "min_samples_leaf": 3}'
|
||||
|
||||
step "T11. Дельта: 50 и 1000 MFW (100/200/500 уже в полной серии)"
|
||||
run "${PYTHON}" -m stylometry.cli delta \
|
||||
--features 50,1000 --metrics manhattan,cosine \
|
||||
--repeats 2 --seed "${SEED}"
|
||||
|
||||
step "Готово"
|
||||
echo "Конец тюнинга: $(date)"
|
||||
echo "Лог: ${LOG_FILE}"
|
||||
echo
|
||||
echo "Сравнить два каталога:"
|
||||
echo " python -m stylometry.cli compare --run-a results/<A> --run-b results/<B>"
|
||||