Первичная версия: стилометрия реплик, LSA и протокол экспериментов

This commit is contained in:
2026-10-24 01:17:36 +03:00
commit c390224500
52 changed files with 10099 additions and 0 deletions

View File

@@ -0,0 +1,77 @@
"""Проверка окружения перед серией экспериментов."""
from __future__ import annotations
import json
import sys
from pathlib import Path
PROJECT_ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(PROJECT_ROOT))
from stylometry.io import DEFAULT_DATA_PATH, corpus_fingerprint, read_corpus, validate_corpus # noqa: E402
from stylometry.tokenization import Tokenizer # noqa: E402
def package_versions() -> dict[str, str]:
versions: dict[str, str] = {}
for name in ("numpy", "pandas", "scipy", "sklearn", "nltk", "matplotlib"):
try:
module = __import__(name)
versions[name] = getattr(module, "__version__", "unknown")
except Exception:
versions[name] = "missing"
return versions
def main() -> int:
print("=== Проверка окружения ===")
print(f"Python: {sys.version.split()[0]} ({sys.executable})")
print(f"Каталог проекта: {PROJECT_ROOT}")
versions = package_versions()
print("Версии библиотек:")
for name, version in versions.items():
print(f" {name}: {version}")
missing = [name for name, version in versions.items() if version == "missing"]
if missing:
print(f"ОШИБКА: не установлены библиотеки: {missing}")
print("Выполните: pip install -r requirements.txt")
return 1
if not DEFAULT_DATA_PATH.exists():
print(f"ОШИБКА: нет файла корпуса {DEFAULT_DATA_PATH}")
return 1
frame = read_corpus(DEFAULT_DATA_PATH)
stats = validate_corpus(frame)
print("Корпус:")
print(f" файл: {DEFAULT_DATA_PATH}")
print(f" SHA-256: {corpus_fingerprint(DEFAULT_DATA_PATH)}")
print(f" реплик: {stats['n_lines']}")
print(f" персонажей: {stats['n_characters']}")
print(f" словоупотреблений: {stats['n_words']}")
try:
tokenizer = Tokenizer(use_nltk=True, strict=True)
except Exception as error:
print(f"ОШИБКА NLTK: {error}")
print("Выполните: python scripts/download_nltk_data.py")
return 1
analyzed = tokenizer.analyze(frame.iloc[0]["text"])
print("NLTK:")
print(f" токенизатор: {'NLTK' if tokenizer.use_nltk else 'резервный'}")
print(f" POS-теггер: {'доступен' if tokenizer.pos_available else 'недоступен'}")
print(f" пример токенов: {analyzed.tokens[:8]}")
print(f" пример POS: {analyzed.upos[:8]}")
if not tokenizer.pos_available:
print("ОШИБКА: POS-теггер недоступен; эксперименты с POS будут неполными.")
return 1
print("\nОкружение готово.")
print(json.dumps({"versions": versions, "n_lines": stats["n_lines"]}, ensure_ascii=False))
return 0
if __name__ == "__main__":
raise SystemExit(main())

56
lab1/scripts/collect_results.sh Executable file
View File

@@ -0,0 +1,56 @@
#!/usr/bin/env bash
# Упаковка результатов для передачи коллеге.
#
# Запуск из каталога lab1:
# bash scripts/collect_results.sh
#
# Создаёт exports/nlp_lab1_results_<timestamp>.tar.gz и файл SHA-256.
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
cd "${PROJECT_ROOT}"
PYTHON="${PYTHON:-python}"
RUN_ID="${RUN_ID:-$(date +%Y%m%d_%H%M%S)}"
OUT_DIR="${OUT_DIR:-${PROJECT_ROOT}/exports}"
ARCHIVE="${OUT_DIR}/nlp_lab1_results_${RUN_ID}.tar.gz"
MANIFEST="${OUT_DIR}/nlp_lab1_results_${RUN_ID}_manifest.txt"
mkdir -p "${OUT_DIR}"
{
echo "Дата: $(date)"
echo "Каталог: ${PROJECT_ROOT}"
echo "Интерпретатор: ${PYTHON}"
echo
"${PYTHON}" scripts/check_environment.py || true
echo
if git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
echo "Git commit: $(git rev-parse HEAD 2>/dev/null || echo 'нет коммита')"
else
echo "Git: репозиторий не инициализирован"
fi
echo
echo "Содержимое results:"
find results -maxdepth 2 -type f | sort | head -n 500
} > "${MANIFEST}"
INCLUDE=()
for path in results features data/processed data/metadata; do
if [[ -e "${path}" ]]; then
INCLUDE+=("${path}")
fi
done
INCLUDE+=("${MANIFEST#"${PROJECT_ROOT}/"}")
tar -czf "${ARCHIVE}" "${INCLUDE[@]}"
sha256sum "${ARCHIVE}" > "${ARCHIVE}.sha256"
echo "Архив: ${ARCHIVE}"
echo "Контрольная сумма: ${ARCHIVE}.sha256"
echo "Манифест: ${MANIFEST}"
echo
echo "Передайте коллеге архив и файл .sha256."

View File

@@ -0,0 +1,29 @@
"""Скачивает ресурсы NLTK в локальный каталог lab1/nltk_data."""
from __future__ import annotations
from pathlib import Path
import nltk
PACKAGES = [
"punkt",
"punkt_tab",
"averaged_perceptron_tagger",
"averaged_perceptron_tagger_eng",
"stopwords",
]
def main() -> int:
target = Path(__file__).resolve().parents[1] / "nltk_data"
target.mkdir(parents=True, exist_ok=True)
for package in PACKAGES:
print(f"Скачивание {package} ...")
nltk.download(package, download_dir=str(target), quiet=False)
print(f"Готово: {target}")
return 0
if __name__ == "__main__":
raise SystemExit(main())

View File

@@ -0,0 +1,169 @@
#!/usr/bin/env bash
# Полная серия экспериментов для лабораторной работы.
#
# Запуск из каталога lab1:
# bash scripts/run_all_experiments.sh
#
# С другим интерпретатором:
# PYTHON=../.venv/bin/python bash scripts/run_all_experiments.sh
#
# Быстрая проверка (1 повтор, без вложенной кросс-валидации):
# QUICK=1 bash scripts/run_all_experiments.sh
#
# Все команды пишутся в results/logs/run_all_<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
REPEATS_MAIN="${REPEATS_MAIN:-5}"
REPEATS_FAST="${REPEATS_FAST:-3}"
REPEATS_LSA="${REPEATS_LSA:-3}"
REPEATS_ABLATE="${REPEATS_ABLATE:-3}"
REPEATS_DELTA="${REPEATS_DELTA:-2}"
SKIP_NESTED="${SKIP_NESTED:-0}"
if [[ "${QUICK:-0}" == "1" ]]; then
REPEATS_MAIN=1
REPEATS_FAST=1
REPEATS_LSA=1
REPEATS_ABLATE=1
REPEATS_DELTA=1
SKIP_NESTED=1
fi
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}/run_all_${RUN_ID}.log"
exec > >(tee -a "${LOG_FILE}") 2>&1
step() {
echo
echo "============================================================"
echo "== $*"
echo "============================================================"
}
run() {
echo
echo ">>> $*"
"$@"
}
latest_dir() {
local prefix="$1"
ls -1dt results/"${prefix}"_* 2>/dev/null | head -n 1 || true
}
echo "Начало серии: $(date)"
echo "Интерпретатор: ${PYTHON}"
echo "Повторы: main=${REPEATS_MAIN}, fast=${REPEATS_FAST}, lsa=${REPEATS_LSA}, ablate=${REPEATS_ABLATE}, delta=${REPEATS_DELTA}"
echo "Вложенная кросс-валидация: ${SKIP_NESTED} (1 — пропустить)"
echo "Лог: ${LOG_FILE}"
step "0. Проверка окружения и тесты"
run "${PYTHON}" scripts/check_environment.py
run "${PYTHON}" -m pytest -q
step "1. Аудит корпуса и ручные признаки (E0)"
run "${PYTHON}" -m stylometry.cli audit
run "${PYTHON}" -m stylometry.cli features
step "2. Нижние границы (E1)"
run "${PYTHON}" -m stylometry.cli run --preset length --model dummy --repeats 1
run "${PYTHON}" -m stylometry.cli run --preset length --model logreg --repeats "${REPEATS_MAIN}"
run "${PYTHON}" -m stylometry.cli run --preset char --model multinomial_nb --repeats "${REPEATS_FAST}"
run "${PYTHON}" -m stylometry.cli run --preset pos --model multinomial_nb --repeats "${REPEATS_FAST}"
run "${PYTHON}" -m stylometry.cli run --preset fw --model logreg --repeats "${REPEATS_FAST}"
step "3. Классическая стилометрия (E2)"
run "${PYTHON}" -m stylometry.cli run --preset stylometry --model logreg --repeats "${REPEATS_MAIN}"
run "${PYTHON}" -m stylometry.cli run --preset stylometry --model linear_svc --repeats "${REPEATS_MAIN}"
step "4. Ручные признаки + n-граммы (E4)"
run "${PYTHON}" -m stylometry.cli run --preset stylometry_pos --model logreg --repeats "${REPEATS_MAIN}"
step "5. Основной объединённый набор (E8)"
run "${PYTHON}" -m stylometry.cli run --preset stylometry_pos_lsa --model logreg --repeats "${REPEATS_MAIN}"
step "6. Варианты LSA по отдельности (E5, E7)"
for variant in word word2 content fw char pos; do
run "${PYTHON}" -m stylometry.cli run --preset "lsa_${variant}" --model logreg --repeats "${REPEATS_LSA}"
done
step "7. Перебор числа компонент LSA (E5)"
run "${PYTHON}" -m stylometry.cli lsa-sweep \
--variants word,content,fw,char,pos \
--components 50,100,200 \
--model logreg --repeats 1
step "8. Сравнение классификаторов (E6)"
run "${PYTHON}" -m stylometry.cli benchmark \
--preset stylometry_pos_lsa \
--models logreg,linear_svc,random_forest,multinomial_nb,knn \
--repeats "${REPEATS_FAST}"
step "9. Поочерёдное исключение групп признаков (E3)"
run "${PYTHON}" -m stylometry.cli ablate \
--preset stylometry_pos_lsa --model logreg --repeats "${REPEATS_ABLATE}"
step "10. Дельта Барроуза (E11)"
run "${PYTHON}" -m stylometry.cli delta \
--features 100,200,500 --metrics manhattan,cosine --repeats "${REPEATS_DELTA}"
step "11. Верификация «свой/чужой» (E12)"
run "${PYTHON}" -m stylometry.cli verify --preset stylometry_pos_lsa
step "12. Кластеризация персонажей (E13)"
run "${PYTHON}" -m stylometry.cli cluster --preset stylometry_pos_lsa
if [[ "${SKIP_NESTED}" != "1" ]]; then
step "13. Вложенная кросс-валидация (E16)"
run "${PYTHON}" -m stylometry.cli run --preset stylometry_pos_lsa --model logreg --nested
run "${PYTHON}" -m stylometry.cli run --preset stylometry_pos_lsa --model linear_svc --nested
else
echo
echo "Вложенная кросс-валидация пропущена (SKIP_NESTED=1 или QUICK=1)."
fi
step "14. Анализ длины, подгрупп и сравнение запусков (E9, E14, E15)"
MAIN_DIR="$(latest_dir stylometry_pos_lsa_logreg)"
POS_DIR="$(latest_dir stylometry_pos_logreg)"
if [[ -n "${MAIN_DIR}" ]]; then
run "${PYTHON}" -m stylometry.cli length-report --run-dir "${MAIN_DIR}"
for group in gender age_group species; do
run "${PYTHON}" -m stylometry.cli subgroup-report \
--run-dir "${MAIN_DIR}" --group "${group}"
done
if [[ -n "${POS_DIR}" ]]; then
run "${PYTHON}" -m stylometry.cli compare \
--run-a "${MAIN_DIR}" --run-b "${POS_DIR}" \
--out "${MAIN_DIR}/comparison_main_vs_pos.json"
fi
else
echo "Не найден каталог основного запуска; анализ пропущен."
fi
step "Готово"
echo "Конец серии: $(date)"
echo
echo "Основные каталоги результатов:"
ls -1dt results/*/ 2>/dev/null | head -n 30 || true
echo
echo "Полный лог: ${LOG_FILE}"
echo "Для упаковки результатов: bash scripts/collect_results.sh"

View File

@@ -0,0 +1,31 @@
#!/usr/bin/env bash
# Базовая серия экспериментов. Запускать из каталога lab1.
# Для финального отчёта увеличьте --repeats до 5.
set -euo pipefail
python -m stylometry.cli audit
python -m stylometry.cli features
# E1: нижние границы
python -m stylometry.cli run --preset length --model dummy --repeats 1
python -m stylometry.cli run --preset length --model logreg --repeats 3
python -m stylometry.cli run --preset char --model multinomial_nb --repeats 3
python -m stylometry.cli run --preset pos --model multinomial_nb --repeats 3
python -m stylometry.cli run --preset fw --model logreg --repeats 3
# E2-E4: ручные признаки, POS и объединение
python -m stylometry.cli run --preset stylometry --model logreg --repeats 3
python -m stylometry.cli run --preset stylometry_pos --model logreg --repeats 3
python -m stylometry.cli run --preset stylometry_pos_lsa --model logreg --repeats 3
python -m stylometry.cli ablate --preset stylometry_pos_lsa --model logreg --repeats 2
# E5-E6: LSA и классификаторы
python -m stylometry.cli lsa-sweep --variants word,pos,char --components 50,100,200 --model logreg --repeats 1
python -m stylometry.cli benchmark --preset stylometry_pos_lsa \
--models logreg,linear_svc,random_forest,multinomial_nb,knn --repeats 2
# E11-E13: дельта, верификация, кластеризация
python -m stylometry.cli delta --features 100,200,500 --metrics manhattan,cosine --repeats 2
python -m stylometry.cli verify --preset stylometry_pos_lsa
python -m stylometry.cli cluster --preset stylometry_pos_lsa