A multilingual self-supervised speech model that doesn't care what language you speak.
huggingface.co/spaces/Aadithya1122/parkinsons-voice-detection
Record three seconds of "aaaah" — get a result in under 3 seconds.
┌──────────────────────────────────────────────────┐
voice ─────▶│ wav2vec2-XLS-R (frozen, 128-language pretrain) │─────▶ 1024-dim embedding
"aaaah" └──────────────────────────────────────────────────┘ │
▼
┌────────────────────┐
│ Logistic Regression│ ───▶ Parkinson's Probability ∈ [0,1]
│ (Italian-trained) │
└────────────────────┘
🎯 What this is — a working voice-screening prototype with rigorous, honestly-reported evaluation. Deployed live on HuggingFace Spaces, with longitudinal patient tracking, SHAP explainability, statistical validation, and a noise robustness study ready for publication.
⚠️ What this isn't — a medical diagnostic device. Don't use it on patients.
All metrics use 5-fold subject-grouped cross-validation — no subject appears in both train and test folds. Anything else silently leaks information through speaker identity and inflates accuracy. We don't do that here.
| 📈 Metric | Value |
|---|---|
| CV AUC (5-fold, subject-grouped) | 0.972 ± 0.034 |
| Bootstrap 95% CI (1000 iterations) | [0.956, 0.982] |
| CV accuracy | 0.942 |
| CV F1 | 0.945 |
| Brier score (calibration) | 0.050 |
| Subject-level AUC (averaged per subject) | 0.996 |
| Subject-level accuracy | 0.951 (58 / 61) |
| Tuned threshold (Youden's J on OOF) | 0.380 |
| Trained on | 831 recordings · 61 subjects |
| Backend | wav2vec2-XLS-R + LogReg |
Model comparison (hand-crafted 54-feature voting ensemble vs wav2vec2):
| Metric | Hand-crafted | wav2vec2 | Advantage |
|---|---|---|---|
| CV AUC | 0.982 [0.974–0.989] | 0.970 [0.956–0.982] | HC in-distribution |
| Accuracy | 0.947 | 0.946 | Tied |
| Brier score | 0.048 | 0.050 | Tied |
| SNR < 8 dB robustness | ↓ degrades faster | ↑ more robust | wav2vec2 |
| Cross-population | ❌ fails on Indian speakers | ✅ generalizes | wav2vec2 |
| Interpretability | ✅ SHAP explainable | ❌ embeddings only | HC |
McNemar's test (χ²=47.04, p<0.001) confirms a statistically significant difference in per-sample correctness, but overlapping 95% CIs show the models are practically equivalent on in-distribution data. The meaningful difference is generalization and noise robustness — which is why wav2vec2 is the deployed default.
TreeExplainer SHAP on the XGBoost sub-model of the tuned voting ensemble.
Top 10 features by mean |SHAP|:
| Rank | Feature | Mean |SHAP| | Clinical significance |
|---|---|---|---|
| 1 | DFA | 1.208 | Long-range vocal correlations — primary PD marker (Little 2007) |
| 2 | MFCC_1_std | 0.747 | Timbral stability — novel extended feature |
| 3 | MFCC_9_mean | 0.716 | Spectral envelope — novel extended feature |
| 4 | MFCC_7_mean | 0.516 | Mid-frequency energy — novel extended feature |
| 5 | MDVP:APQ | 0.503 | Amplitude perturbation quotient |
| 6 | MFCC_11_mean | 0.464 | High-frequency energy — novel extended feature |
| 7 | HNR | 0.399 | Harmonics-to-noise ratio — vocal fold dysfunction |
| 8 | Shimmer:APQ5 | 0.388 | Amplitude perturbation |
| 9 | Shimmer:APQ3 | 0.380 | Short-term amplitude variation |
| 10 | MDVP:Fo(Hz) | 0.290 | Fundamental frequency |
DFA dominates with mean |SHAP| = 1.208 — more than 1.6× the second feature. Four of the top 10 are MFCC-derived features absent from the original UCI set, validating the 54-feature extension.
Run: python scripts/shap_analysis.py → reports/shap/
| Test | Result |
|---|---|
| Bootstrap 95% CI (wav2vec2 AUC) | [0.956, 0.982] |
| Bootstrap 95% CI (hand-crafted AUC) | [0.974, 0.989] |
| McNemar's test | χ²=47.04, p<0.001 |
| Brier score — wav2vec2 | 0.050 |
| Brier score — hand-crafted | 0.048 |
Run: python scripts/statistical_validation.py → reports/stats/
Outputs: calibration curves, ROC comparison, CI table
Tested at 5 SNR levels (−5 to 20 dB), 3 noise types (white, pink, mixed).
Crossover at ~8 dB SNR:
| SNR | Hand-crafted | wav2vec2 | Winner |
|---|---|---|---|
| −5 dB (very noisy) | 0.774 | 0.864 | wav2vec2 +0.090 |
| 0 dB | 0.886 | 0.931 | wav2vec2 +0.045 |
| 5 dB | 0.944 | 0.958 | wav2vec2 +0.014 |
| 10 dB | 0.973 | 0.963 | HC +0.010 |
| 20 dB | 0.989 | 0.967 | HC +0.022 |
Hand-crafted features outperform wav2vec2 in clean conditions (SNR ≥ 10 dB), but wav2vec2 is substantially more robust to noise — home recordings rarely exceed 10 dB SNR.
Run: python scripts/noise_robustness.py → reports/noise/
git clone https://github.com/Aadithyaar22/Parkinson-s_voice_detection.git
cd Parkinson-s_voice_detection
pip install -r requirements.txt
brew install ffmpeg
pip install -r requirements_wav2vec2.txt
python scripts/refit_w2v2_local.py
python app.pyThe starting point was a buggy student project. Silent feature-extraction failures meant the model was running with its top 3 predictive features permanently set to "training-set average." Random train/test splits inflated accuracy through subject leakage. We found both, documented both, and rebuilt from scratch.
| 🐛 Bug | 💥 Impact |
|---|---|
Jitter(%), PPQ, RAP aliased to same Praat call | Three features held identical values |
Shimmer(dB) set to APQ3 value | Wrong scale, wrong meaning |
RPDE, PPE, DFA + 3 more hardcoded to None | 🔥 Top predictive features never computed |
| Random splits ignoring multiple recordings per subject | 🔥 Subject leakage — accuracy was a mirage |
nolds not in requirements.txt | DFA failed silently |
train Italian → test UCI: AUC 0.31 🔻 worse than random
train UCI → test Italian: AUC 0.55 ⚪ basically random
Models don't learn Parkinson's features — they learn language features. This cross-corpus failure is underreported in the literature. We tested it, documented it, and used it to motivate wav2vec2 as a language-agnostic alternative.
Core analysis
- 🎙️ Live recording or file upload (WAV · MP3 · FLAC · OGG · M4A · WebM · up to 25 MB)
- 📊 Parkinson's Probability gauge with operating threshold marked
- 🔊 Voice spectrogram — mel-frequency spectrogram displayed after every analysis
- ✅ Recording quality scorer — 6-metric assessment (duration, SNR, silence ratio, voiced fraction, clipping, F0 detectability) with actionable feedback
- 🤖 AI Clinical Explanation — Groq Llama 3.3 70B in plain English
- 🧭 3-step onboarding for first-time users
Longitudinal tracking (requires free account)
- 🔐 Email + password auth — JWT-based, stateless, survives container restarts
- 💾 Save readings with timestamp, probability, duration, optional notes
- 📈 Personal dashboard — trend graph, 7-reading rolling average, threshold line
- 🏷️ Stable / Improving / Worsening trend indicator (6+ readings)
- ✏️ Edit notes inline on any past reading
- 🗑️ Delete accidental saves
- 🔗 Shareable doctor link — 30-day read-only report, no login needed for doctor
- 🖨️ Export to PDF — full history, stats, spectrogram, clinical disclaimer
🩺 Not a diagnostic device. Research-grade only.
🧍 One Indian healthy speaker tested — we confirmed it correctly says "healthy" on four recordings. We have not tested an Indian PD patient.
🔬 No Indian PD ground truth. The cross-language story needs a labelled Indian corpus.
📉 UCI within-corpus AUC is only 0.69 — 8 healthy subjects is not enough.
🕳️ wav2vec2 embeddings are not interpretable. Use the hand-crafted backend if SHAP explainability matters.
📏 Threshold tuned on OOF predictions from training, not a held-out set.
Click to expand
pva2/
├─ app.py Flask server, dual-backend + full API
├─ requirements.txt / requirements_wav2vec2.txt
│
├─ src/
│ ├─ feature_extractor.py 22 UCI MDVP features via Praat
│ ├─ extra_features.py CPP + MFCC + formants + tilt
│ ├─ nonlinear_features.py RPDE / DFA / D2 / PPE / spread
│ ├─ wav2vec2_inference.py runtime embedding extraction
│ ├─ quality_scorer.py 🆕 6-metric recording quality assessment
│ ├─ llm_explainer.py 🆕 Groq Llama 3.3 explanation generator
│ ├─ database.py 🆕 MongoDB users + readings + shares
│ └─ auth.py 🆕 bcrypt + JWT authentication
│
├─ scripts/
│ ├─ shap_analysis.py 🆕 SHAP explainability
│ ├─ statistical_validation.py 🆕 Bootstrap CI + McNemar + calibration + ROC
│ ├─ noise_robustness.py 🆕 AUC vs SNR across 3 noise types
│ ├─ generate_spectrogram.py 🆕 Mel-spectrogram for UI + PDF
│ ├─ extract_features_from_audio.py
│ ├─ extract_wav2vec2_embeddings.py
│ ├─ tune_italian.py
│ ├─ wav2vec2_experiment.py
│ └─ refit_w2v2_local.py
│
├─ models/ current deployment (wav2vec2)
├─ models_wav2vec2/ wav2vec2 backup
├─ models_italian_tuned/ hand-crafted ensemble
├─ models_joint/ UCI + Italian joint
├─ models_original/ UCI-only baseline
│
├─ reports/
│ ├─ shap/ 🆕 SHAP beeswarm + bar + CSV + JSON
│ ├─ stats/ 🆕 calibration + ROC + CI JSON
│ └─ noise/ 🆕 noise robustness figure + JSON
│
├─ templates/
│ ├─ index.html main UI
│ ├─ login.html 🆕 auth page
│ ├─ dashboard.html 🆕 patient dashboard
│ ├─ report.html 🆕 shareable doctor report
│ └─ export.html 🆕 print-to-PDF template
│
├─ parkinsons_space/ HuggingFace Space template
├─ .github/workflows/deploy_space.yml CI → HF Spaces
└─ tests/test_extractor.py 15 sanity checks
| 🧩 Layer | 🛠️ Choice | 🎯 Why |
|---|---|---|
| Acoustic library | praat-parselmouth | De facto standard for voice analysis |
| Nonlinear features | nolds + custom | Little 2007 RPDE + DFA |
| Explainability | SHAP (TreeExplainer) | Per-feature attribution for XGBoost |
| Tabular ML | XGBoost, LightGBM, RF + Optuna | Strong baselines, subject-aware tuning |
| Speech model | wav2vec2-XLS-R-300m | 128-language pretraining, MPS support |
| LLM explanations | Groq · Llama 3.3 70B | Sub-second inference, clinical tone |
| Auth | bcrypt + PyJWT | Stateless, survives container restarts |
| Database | MongoDB Atlas (free) | Longitudinal reading storage |
| Web | Flask + vanilla JS | Single file, no build step |
| Deployment | HuggingFace Spaces + Docker | Free, always-on, 16 GB RAM |
| CI / CD | GitHub Actions | Test → assemble → deploy on push |
📚 Datasets:
- 🇬🇧 UCI Parkinson's (Little et al. 2007) — 195 recordings, 32 subjects, English
- 🇮🇹 Italian Parkinson's Voice and Speech (Dimauro et al. 2019) — 831 recordings, 61 subjects, Italian
All results are reproducible. No held-out data was used for threshold tuning or model selection — all metrics come from OOF predictions in subject-grouped CV.
Suggested citation:
Aadithya A R, Kenisha P, Yadunandan M Nimbalkar (2026). Voice·PD: Cross-Corpus Voice-Based Parkinson's Disease Screening Using Self-Supervised Speech Representations with Longitudinal Monitoring. https://github.com/Aadithyaar22/Parkinson-s_voice_detection
Aadithya A R · Yadunandan M Nimbalkar · Kenisha P
B.Tech CSE (AI & ML) · Global Academy of Technology, Bengaluru · 2026
The original project was a four-person team effort (Aadithya, Naman, Yadunandan, Kenisha).
This v2 rebuild — feature extractor fix, multi-corpus training, cross-corpus experiments, wav2vec2 deployment,
SHAP analysis, statistical validation, noise robustness, longitudinal tracking, CI/CD pipeline, web UI —
was led by Aadithya A R, Kenisha P and Yadunandan M Nimbalkar.
Released under the MIT License.
Do whatever you want with the code, but if you build a real clinical product on top of it,
please involve actual clinicians and real validation.
Don't ship a model the same week you read its README.
⭐ If this helped, leave a star. ⭐
