Note: This repository is forked from my collaborator @Amir-Aref. I contributed to the nested cross-validation pipeline and MLflow tracking. See the original repo here.
A reproducible machine-learning project for classifying 30-second EEG epochs into five sleep stages:
- Wake
- N1
- N2
- N3
- REM
The project uses the Sleep-EDF Database Expanded sleep-cassette subset and enforces subject-level isolation throughout validation and testing. The primary model-selection metric is Macro-F1.
This repository is an educational and research implementation. It is not a medical diagnostic system.
The complete Phase 2 data pipeline and the complete Phase 3 full-dataset scientific evaluation are available.
Validated full-dataset scope:
- 78 subjects
- 153 recordings
- 306 validated EDF files
- 195,469 thirty-second epochs
- 28 leakage-safe model features
- 5 outer folds and 3 inner folds per outer fold
- 29 model candidates
Final scientific results:
- Mean outer Macro-F1: 0.658662
- Outer Macro-F1 standard deviation: 0.011977
- Pooled Macro-F1: 0.661936
- Pooled balanced accuracy: 0.670416
- Final model:
random_forest__candidate_002
The final model was imported into MLflow as
EEG_Sleep_Stage_Classifier, registered as version 1, and assigned
the champion alias.
Scientific outputs are available in:
reports/phase3_full_scientific_report.mdreports/phase3_full_scientific_summary.jsondocs/figures/phase3_full/data/metadata/phase3_full_artifact_provenance.json
The large final model, processed model input, row-level prediction files, and prediction SQLite database remain outside Git. Their file sizes and SHA-256 values are recorded in the provenance manifest.
The small local Phase 3 artifacts remain engineering-validation artifacts only and must not be presented as final scientific results.
The full scientific evaluation uses nested, subject-grouped cross-validation:
- Five outer folds estimate performance on unseen subjects.
- Three inner folds select models and hyperparameters using development subjects only.
- The outer test partition is never used for model selection.
- Preprocessing is fitted only on each training partition.
- Every subject appears in exactly one outer test fold.
Random epoch-level splitting is forbidden by the evaluation protocol in config/phase3_evaluation_protocol.json.
The configured model families are:
- Dummy prior baseline
- Logistic regression
- SGD logistic classifier
- Random forest
- Extra trees
The complete registry contains 29 candidates. See config/phase3_model_registry.json.
.
├── .github/workflows/ # Continuous integration
├── artifacts/models/ # Trusted local engineering model artifacts
├── config/ # Evaluation, model, and MLflow contracts
├── data/
│ ├── interim/ # Epoch metadata
│ ├── metadata/ # Schemas, manifests, metrics, and provenance
│ ├── processed/ # Feature and model-input datasets
│ └── sample/ # Small compatibility sample
├── database/ # Phase 2 SQLite database
├── docs/ # Database, SQL, Docker, CI, and MLflow docs
├── notebooks/ # Exploratory notebook
├── outputs/ # Legacy Phase 2 outputs
├── reports/ # EDA, audit, report draft, and change log
├── scripts/ # Data, modeling, inference, storage, and tracking code
├── sqlite-db/ # Local Phase 3 prediction-store example
├── tests/ # Contract and integration tests
├── pipeline.py # Legacy Phase 2 compatibility pipeline
├── Dockerfile
└── requirements.txt
The repository development and test environment uses the versions
pinned in requirements.txt.
The retained local engineering models were produced with:
- Python 3.13.x
- NumPy 2.5.1
- pandas 3.0.3
- scikit-learn 1.9.0
- joblib 1.5.3
The final full-dataset deployment model was trained with:
- Python 3.12.x
- NumPy 2.0.2
- pandas 2.3.3
- scikit-learn 1.6.1
- joblib 1.5.3
The full-dataset model must be loaded in a compatible Python 3.12 environment with its recorded package versions. Trusted joblib files must only be loaded from validated project artifacts.
MLflow 3.14.0 uses pandas<3, so the MLflow-compatible environment
pins pandas 2.3.3.
python -m venv .venv
.\.venv\Scripts\Activate.ps1
python -m pip install --upgrade pip
python -m pip install -r requirements.txtpython -m venv .venv
source .venv/bin/activate
python -m pip install --upgrade pip
python -m pip install -r requirements.txtCompile all Python sources:
python -m compileall -q pipeline.py scripts testsRun the complete test suite:
python -m unittest discover -s tests -p "test_*.py" -vValidate the Phase 2 execution plan without running it:
python scripts/run_phase2_pipeline.py --planValidate local Phase 3 artifacts without creating MLflow runs:
python scripts/mlflow_tracking.py validate-config
python scripts/mlflow_tracking.py validate-localThe original compatibility pipeline can be run with:
python pipeline.pyThe production-style Phase 2 runner provides planning and provenance support:
python scripts/run_phase2_pipeline.py --planRaw Sleep-EDF recordings are intentionally excluded from Git. Runtime paths can be redirected with:
EEG_RUNTIME_ROOTEEG_SLEEP_EDFX_RAW_DIR
The Phase 3 implementation includes:
- Dataset contract validation
- Subject-safe split generation
- Model registry and candidate enumeration
- Inner model search
- Selection artifacts without test metrics
- Outer test evaluation
- Saved end-to-end sklearn pipelines
- Final refit
- Targetless prediction pipeline
- SQLite prediction storage
- MLflow experiment tracking and model registration
MLflow uses a local SQLite backend and a local artifact directory. This supports durable run metadata and the open-source Model Registry while keeping all files under a configurable project-local root.
Initialize the tracking store:
python scripts/mlflow_tracking.py initImport the included local engineering artifacts:
python scripts/mlflow_tracking.py import-local --git-commit 11c9ff4f2d79c16f0ac26744212a244d4604dc32The importer:
- Verifies source and model SHA-256 hashes
- Enforces local versus full-dataset scientific-reporting contracts
- Refuses incompatible saved-model runtimes by default
- Logs parameters, metrics, manifests, source joblib files, and MLflow model packages
- Registers local-validation models separately from the final full-dataset model
- Uses an import fingerprint to prevent accidental duplicate imports
Print the exact MLflow server command:
python scripts/mlflow_tracking.py ui-commandRun the printed command and open http://127.0.0.1:5000.
Detailed instructions are available in docs/mlflow_tracking.md.
The completed full-dataset artifacts can be imported with:
python scripts/mlflow_tracking.py import-phase3 \
--scope full_dataset \
--selection data/metadata/phase3_full_inner_model_selection.json \
--outer-evaluation data/metadata/phase3_full_outer_evaluation.json \
--final-refit-manifest data/metadata/phase3_full_final_refit_manifest.json \
--git-commit <commit-sha>The five outer folds are imported as evaluation-only child runs because their fitted model files were intentionally not retained. The validated final refit pipeline is the only full-dataset model registered in the canonical MLflow Model Registry.
The import validates source hashes, final-model size and SHA-256, scientific-reporting permission, deployment readiness, and the saved model runtime before registration.
The saved-model prediction pipeline validates:
- Model manifest and model hash
- Expected feature names and order
- Optional ground-truth columns
- Class probabilities
- Confidence, margin, and normalized entropy
- Deployment-readiness policy
The prediction store persists validated rows to SQLite with foreign-key enforcement and idempotent run handling. Reproducible query examples are in docs/sql_queries.md, and validated local query outputs are stored under docs/phase3_sql_query_outputs/.
Build the image:
docker build -t eeg-sleep-stage-classification .Run the default compatibility pipeline:
docker run --rm eeg-sleep-stage-classificationThe image uses Python 3.13 to match the major/minor runtime of the included saved models. Generated MLflow state, raw EDF files, virtual environments, caches, and temporary archives are excluded from the Docker build context.
- Source artifacts and models are identified by SHA-256.
- Joblib files must only be loaded from trusted project artifacts.
- Local engineering results are separated from final full-dataset results.
- The importer does not rerun training or access new held-out data.
- Full scientific reporting is enabled only for the validated complete outer evaluation and final refit artifacts.