Skip to content

Training & Evaluation Audit Report — SoccerPredictAI

Date: 2026-04-24 Auditor: GitHub Copilot (Claude Sonnet 4.6) Scope: Training, evaluation, CV, model selection, feature consistency, calibration Method: анализ src/models/, src/pipelines/classification.py, src/pipelines/tune.py, src/pipelines/final_train.py, params.yaml, src/data/splitting.py


1. Data Split & CV

Train/Test split

Параметр Значение Источник
Метод time-based src/data/splitting.py:split_time_based_on()
test_start 2024-01-01 params.yaml: temporal.test_start
Holdout startTimeUtc >= 2024-01-01 temporal boundary
Train/Val startTimeUtc < 2024-01-01 всё до этой даты

Утечка времени: ✅ Нет. Отсечка по времени строгая, индекс по startTimeUtc.

Walk-forward CV folds

Параметр Значение Источник
Метод year-based walk-forward src/data/splitting.py:make_year_folds()
Folds 2022, 2023 (2 fold) params.yaml: temporal.folds_start_year=2022, folds_end_year=2024
Логика для fold year y: train = всё до y-01-01, valid = [y-01-01, y+1-01-01)
Fold 1: train = everything before 2022-01-01, valid = 2022
Fold 2: train = everything before 2023-01-01, valid = 2023
Holdout: 2024+

✅ Walk-forward CV — правильный подход для временных рядов. Нет data leakage между folds.


2. Training Setup

Stage: classification_models

Аспект Значение
Models baseline (DummyClassifier), logreg, sgd_logloss, HGBT, xgb
Features side=diff, window_sizes=[1,3], ELO + sex categorical
fracs_for_train [0.001, 0.002] ← smoke-режим
Target outcome_1x2 (0=home, 1=draw, 2=away)
CV walk-forward 2 folds

⚠️ P0: fracs_for_train: [0.001, 0.002] — тренирует на 0.1% и 0.2% от train-сета. Это smoke-конфигурация. Результаты не репрезентативны для production. Лучшая модель выбирается из toy-runs.

Stage: tune_xgb

Аспект Значение
n_trials 2 ← smoke
frac 0.1
Метрика mean CV log-loss
Backend Optuna, SQLite in-memory

⚠️ P0: n_trials=2 — Optuna с 2 trials не может найти значимо лучшие гиперпараметры. Tuning результаты non-meaningful.

Stage: final_train

Аспект Значение
Model best_model_name из classification_models (run_id.json)
Params best_params из xgb_best_params.json
Calibration isotonic, calib_frac=0.15, min_calib_samples=100
Calibration split temporальный (не random) — earliest (1-calib_frac) для training, latest calib_frac для calibration
Holdout evaluation ✅ одна оценка в конце — правильно

Calibration design корректен: temporal split для calibration, no random leakage.


3. Metrics

Метрики, логируемые в MLflow

Стадия CV метрики Holdout метрики
classification_models logloss per fold, mean CV logloss holdout logloss
tune_xgb mean CV logloss per trial
final_train holdout logloss, ECE (raw), ECE (calibrated), accuracy, confusion matrix

Калибрация: - Raw ECE логируется для сравнения с calibrated - ECE вычисляется через compute_ece() в src/models/metrics.py

Segment metrics: compute_segment_metrics() логируется в final_train — breakdown по группам


4. Model Selection

Механизм выбора лучшей модели

В make_classification_runs():

_run_candidates: list[tuple[str, float]] = []
# ... для каждой модели / frac:
_run_candidates.append((run_id, holdout_logloss))
# ...
best_run_id = min(_run_candidates, key=lambda x: x[1])[0]

✅ Выбор по holdout log-loss — korektно.

⚠️ P1: При fracs_for_train=[0.001, 0.002] и нескольких моделях selection происходит по очень малой выборке — результаты нестабильны. Лучшая модель на 0.1% данных может быть совсем другой моделью при 100%.

Использование holdout для selection

classification_models использует holdout logloss для выбора best run_id. Это нарушает принцип holdout-only-for-final-evaluation при fracs_for_train iterations — каждая итерация оценивается на holdout.

⚠️ P2: Holdout виден для model selection в classification_models. Это не катастрофическая утечка (выбор между моделями, а не настройка параметров по holdout), но снижает объективность финальной оценки. final_train потом снова оценивает на том же holdout — значения не независимы.


5. Inconsistencies

Feature set между стадиями

Стадия side window_sizes source
classification_models diff [1,3] params.yaml
tune_xgb diff [1,3] params.yaml
final_train diff [1,3] из run_id.json (best_model_name) → params
batch_inference diff [1,3] params.yaml

✅ Все стадии используют одинаковый feature set через select_model_features().

window_sizes расхождение (потенциальное)

  • features.window_sizes = [1,2,3,5,10] (feature engineering)
  • classification.window_sizes = [1,3] (model input)

Нет runtime guard. Если изменить classification.window_sizes добавив [7], а features.window_sizes не включает 7 — silent NaN.


6. Findings (P0/P1/P2)

ID Severity Описание
TR-01 P0 fracs_for_train: [0.001, 0.002] — smoke-конфиг, тренируется на 0.1-0.2% данных. Все метрики и model selection non-representative
TR-02 P0 tuning.n_trials=2 — smoke-конфиг, Optuna tuning бессмысленный при 2 trials
TR-03 P1 Holdout используется для model selection в classification_models — не только для финальной отчётности. Holdout не полностью blind
TR-04 P1 ablation_study stage не включён в DAG зависимостей tune_xgb и final_train — ablation результаты не влияют на model selection
TR-05 P2 Нет seed у HGBT/XGB в classification pipeline для CV runs — воспроизводимость на уровне прогона
TR-06 P2 best_model_name в run_id.json содержит победителя classification_models, но final_train применяет tuned params из xgb_best_params.json к этому model_name — если winner != xgb, params применяются к неправильной модели