Complete documentation for training models, split strategies, cross-validation, and hyperparameter optimization.
| Model | Type | Input | Description | Kappa |
|---|---|---|---|---|
| CNN-LSTM Seq | DL | Raw signals (sequences) | CNN + multi-epoch BiLSTM (best) | 0.721 |
| CNN1D | DL | Raw signals | Convolutional network with residual blocks | 0.680 |
| XGBoost | ML | Features | Gradient boosting with LOSO (selected) | 0.675±0.097 |
| LSTM Bi+Attn | DL | Raw signals | Bidirectional LSTM with attention | 0.651 |
| Random Forest | ML | Features | Tree ensemble, robust and fast | 0.635 |
| LSTM Unidir | DL | Raw signals | Unidirectional LSTM (real-time) | 0.530 |
| LSTM Bidir | DL | Raw signals | Bidirectional LSTM | 0.521 |
Recommendations:
- Best performance: CNN-LSTM Seq (κ=0.721, multi-epoch)
- Single-epoch DL: CNN1D (κ=0.680)
- Interpretable ML + LOSO: XGBoost (κ=0.675±0.097 in LOSO-CV; tuning on a subject holdout)
- Real-time inference: Unidirectional LSTM (κ=0.530)
# 1. Extract features (~20-30 minutes)
python -m src.extract_features \
--manifest data/processed/manifest_trimmed_resamp200.csv \
--output data/processed/features_resamp200.parquet \
--format parquet
# 2. Train models quickly
python -m src.models \
--features-file data/processed/features_resamp200.parquet \
--model-type random_forest \
--output-dir models
python -m src.models \
--features-file data/processed/features_resamp200.parquet \
--model-type xgboost \
--output-dir modelsTip: Use
python -m src.extract_features --helpandpython -m src.models --helpfor all options.
python -m src.models \
--features-file data/processed/features_resamp200.parquet \
--model-type random_forest \
--n-estimators 200 \
--max-depth 20 \
--output-dir modelspython -m src.models \
--features-file data/processed/features_resamp200.parquet \
--model-type xgboost \
--n-estimators 300 \
--max-depth 8 \
--learning-rate 0.1 \
--output-dir modelspython -m src.models \
--manifest data/processed/manifest_trimmed_spt.csv \
--model-type cnn1d \
--n-filters 64 \
--epochs 50 \
--output-dir modelspython -m src.models \
--features-file data/processed/features_resamp200.parquet \
--model-type lstm \
--lstm-units 128 \
--sequence-length 5 \
--epochs 50 \
--output-dir modelsNote: Each model has specific options. Use
python -m src.models --helpto see the full list of configurable hyperparameters.
from src.models import run_training_pipeline
metrics = run_training_pipeline(
manifest_path="data/processed/manifest_trimmed_resamp200.csv",
model_type="random_forest",
output_dir="models",
test_size=0.2,
n_estimators=200,
max_depth=None,
)from src.features import extract_features_from_session
features_df = extract_features_from_session(
psg_path="data/processed/sleep_trimmed_resamp200/psg/SC4001E_trimmed_raw.fif",
hypnogram_path="data/processed/sleep_trimmed_resamp200/hypnograms/SC4001E_trimmed_annotations.csv",
epoch_length=30.0,
sfreq=100.0,
)All epochs from a subject are kept in the same split (train/val/test) to avoid leakage.
python -m src.models \
--features-file data/processed/features_resamp200.parquet \
--model-type random_forest \
--test-size 0.2 \
--val-size 0.2The most recent sessions per subject go to test/val.
python -m src.models \
--features-file data/processed/features_resamp200.parquet \
--model-type random_forest \
--temporal-split \
--output-dir models_temporalNote: Requires
epoch_time_startorepoch_indexin the features.
python -m src.models \
--features-file data/processed/features_resamp200.parquet \
--model-type random_forest \
--cross-validate \
--cv-strategy subject-kfold \
--cv-folds 5Each fold leaves one full subject out. Ideal to evaluate generalization to unseen subjects.
python -m src.models \
--features-file data/processed/features_resamp200.parquet \
--model-type random_forest \
--cross-validate \
--cv-strategy loso \
--strict-class-coverageRespects temporal order within each subject during CV.
python -m src.models \
--features-file data/processed/features_resamp200.parquet \
--model-type random_forest \
--cross-validate \
--cv-strategy group-temporalThe pipeline automatically detects whether it is the first run and enables Bayesian optimization with Optuna.
python -m src.models \
--features-file data/processed/features_resamp200.parquet \
--model-type random_forest \
--optimize \
--n-iter-optimize 50| Flag | Default | Description |
|---|---|---|
--optimize |
auto | Force optimization |
--n-iter-optimize |
50 | Number of Optuna trials |
--cv-folds-optimize |
3 | Folds for internal CV |
The split ensures all classes (W, N1, N2, N3, REM) are present in each set.
# Fail if any class is missing during CV
python -m src.models \
--features-file data/processed/features_resamp200.parquet \
--cross-validate \
--strict-class-coverage- Input: sequences of N consecutive epochs
(batch, seq_length, n_samples, n_channels) seq_length=11by default (~5.5 minutes of temporal context)- CNN extracts per-epoch features, BiLSTM models temporal context
- Per-channel normalization statistics are stored in the model
- Best performance (Kappa 0.72) due to multi-epoch context
- Input: raw signals
(n_epochs, n_channels, n_samples) - Per-channel normalization statistics are stored in the model
- Data augmentation via GaussianNoise
- Optional residual connections
- Input: per-epoch raw signals
(n_epochs, n_samples, n_channels)— single-epoch classification - Per-channel normalization statistics are stored in the model
- Trained variants: unidirectional, bidirectional, bidirectional + attention
- Note: No sequences of adjacent epochs are used; each epoch is classified independently
DL models store normalization statistics from the training split:
- CNN1D:
channel_means_,channel_stds_ - LSTM:
scaler_
Important: evaluate_model fails if these are missing to prevent data leakage.
| Metric | Description |
|---|---|
| Accuracy | Overall accuracy |
| Cohen's Kappa | Agreement beyond chance |
| Macro F1 | Average F1 over classes |
| Weighted F1 | F1 weighted by support |
| Confusion matrix | Per-class errors |
models/
├── random_forest_model.pkl # Trained model
├── random_forest_feature_names.pkl # Feature names
├── random_forest_metrics.json # Evaluation metrics
├── xgboost_model.pkl
├── cnn1d_model.keras # Keras model
├── cnn1d_model_custom_attrs.json # Attributes (scaler, etc.)
└── lstm_model.keras
| Scenario | Recommendation |
|---|---|
| Small dataset (<15 subjects) | Use LOSO CV |
| Medium dataset (15–50 subjects) | Subject K-Fold with 5 folds |
| Large dataset (>50 subjects) | Simple train/val/test |
| Evaluate generalization | LOSO + strict-class-coverage |
| Raw signals | CNN1D |
| Temporal context | LSTM with sequence_length 5–10 |
See Comparative Analysis for full details.
Note
Reports under docs/reports/ are currently Spanish-only, but they are mostly plots/tables and should be easy to interpret.
| Model | Cohen's Kappa | Macro F1 | Accuracy |
|---|---|---|---|
| CNN-LSTM Seq | 0.721 | 73.19% | 80.04% |
| CNN1D | 0.680 | 70.83% | 76.86% |
| XGBoost (LOSO-CV) | 0.675±0.097 | 67.96%±9.85% | 77.15%±6.97% |
| LSTM Bi+Attn | 0.651 | 68.07% | 74.64% |
| Random Forest | 0.635 | 69.50% | 72.82% |
| LSTM Unidir | 0.530 | 58.59% | 66.17% |
N1 is the hardest class (F1 ~40%) across all models, due to its transitional nature.
For GPU training on Kaggle, see Kaggle Notebooks.